Which is the correct ordering? Does it matter? 1....
# arrow
o
Which is the correct ordering? Does it matter? 1.
either
inside
withContext
Copy code
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
  either {
    // Code that uses all of shift, bind,
    // and currentCoroutineContext().ensureActive()
  }
}
2.
withContext
inside
either
Copy code
either {
  withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
    // Code that uses all of shift, bind,
    // and currentCoroutineContext().ensureActive()
  }
}
p
I think it would depend on the code. If all of the effectual code should be within an IO dispatcher then I would probably opt for
withContext(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 🤷 😄
r
From the Effect data type docs:
withContext
It's always safe to call
shift
from
withContext
since it runs in place, so it has no way of leaking
shift
. When
shift
is called from within
withContext
it will cancel all `Job`s running inside the
CoroutineScope
of
withContext
.
https://arrow-kt.io/docs/apidocs/arrow-core/arrow.core.continuations/-effect/ My guess is that if you have
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.
Copy code
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.
Copy code
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.
o
Thanks. I thought there was no documentation. Dokka has a lot of bugs that result in many parts of Arrow documentation not being displayed! Even this direct link renders blank for me due to the page automatically unselecting the [Common] tag and not allowing me to select it again 😬.
s
Hey all ☺️ There is no “correct” ordering, since withContext is called in-place and
EXACTLY_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:
Copy code
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.
Copy code
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 ☺️