okarm
10/20/2022, 5:43 PMeither
inside withContext
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
either {
// Code that uses all of shift, bind,
// and currentCoroutineContext().ensureActive()
}
}
2. withContext
inside either
either {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
// Code that uses all of shift, bind,
// and currentCoroutineContext().ensureActive()
}
}
phldavies
10/21/2022, 11:15 AMwithContext(IO) { either { … } }
but if only a few calls need IO and the rest can be on the caller’s dispatcher then I’d wrap just that section with withContext(IO)
inside the either.
I could also be very wrong on this 🤷 😄raulraja
10/21/2022, 3:08 PMwithContext
It's always safe to callhttps://arrow-kt.io/docs/apidocs/arrow-core/arrow.core.continuations/-effect/ My guess is that if you havefromshift
since it runs in place, so it has no way of leakingwithContext
. Whenshift
is called from withinshift
it will cancel all `Job`s running inside thewithContext
ofCoroutineScope
.withContext
withContext { either { shift(e) }}
the either block captures the shift
and withContext
receives a value of type Either
which does not cancel other jobs inside the withContext
block outside of the either demarcation.
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
val r = either {
// Code that uses all of shift, bind,
// and currentCoroutineContext().ensureActive()
}
r // this is an Either that intercepted the shift
}
If you instead have either { withContext { shift(e) } }
the shifted value will travel up to the either
and in fact cancel the withContext
internal jobs.
either {
val r = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
// Code that uses all of shift, bind,
// and currentCoroutineContext().ensureActive()
}
r // shift got thrown as exception all the way to the `either` and cancelled all jobs in <http://Dispatchers.IO|Dispatchers.IO> in that block
}
I may be wrong and @simon.vergauwen or someone else can probably answer this better.okarm
10/22/2022, 9:00 AMsimon.vergauwen
10/22/2022, 9:04 AMEXACTLY_ONCE
there is no risk of leaking anything.
As Raul mentioned there is a difference in behavior between having shift
inside withContext
or outside. Since withContext
creates a new coroutineScope
and thus awaits all launch
and async
to finish before returning its value. Small example:
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
launch { delay(100.seconds) }
1
} // 1, but takes 100 seconds before it returns
Call shift
from within withContext
will result in the launch
to be cancelled. So, to show the difference in behavior.
either<String, Int> {
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
launch { delay(100.seconds) }
shift<Int>("Failed")
}
} // Either.Left("Failed") returns immediately
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
either<String, Int> {
launch { delay(100.seconds) }
shift<Int>("Failed")
}
} // Either.Left("Failed"), but takes 100 seconds before it returns
This follows the same rules as Structured Concurrency from KotlinX. Since the logical failure of shift
occurs completely inside of withContext
its CoroutineScope
doesn’t get cancelled. This could be a good example to update the example with (?), a PR would be greatly appreciated if it can clear this pattern further up ☺️