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 havefromshiftsince it runs in place, so it has no way of leakingwithContext. Whenshiftis called from withinshiftit will cancel all `Job`s running inside thewithContextofCoroutineScope.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 ☺️