Skip to content

Commit

Permalink
Merge pull request #3056 from djspiewak/scaladoc/cede
Browse files Browse the repository at this point in the history
Added scaladoc for `IO.cede` and enhanced `GenSpawn#cede`
  • Loading branch information
djspiewak authored Jun 28, 2022
2 parents 1288d61 + ccd77f8 commit 733a4b4
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 0 deletions.
39 changes: 39 additions & 0 deletions core/shared/src/main/scala/cats/effect/IO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,45 @@ object IO extends IOCompanionPlatform with IOLowPriorityImplicits {

def canceled: IO[Unit] = Canceled

/**
* Introduces a fairness boundary that yields control back to the scheduler of the runtime
* system. This allows the carrier thread to resume execution of another waiting fiber.
*
* This function is primarily useful when performing long-running computation that is outside
* of the monadic context. For example:
*
* {{{
* fa.map(data => expensiveWork(data))
* }}}
*
* In the above, we're assuming that `expensiveWork` is a function which is entirely
* compute-bound but very long-running. A good rule of thumb is to consider a function
* "expensive" when its runtime is around three or more orders of magnitude higher than the
* overhead of the `map` function itself (which runs in around 5 nanoseconds on modern
* hardware). Thus, any `expensiveWork` function which requires around 10 microseconds or
* longer to execute should be considered "long-running".
*
* The danger is that these types of long-running actions outside of the monadic context can
* result in degraded fairness properties. The solution is to add an explicit `cede` both
* before and after the expensive operation:
*
* {{{
* (fa <* IO.cede).map(data => expensiveWork(data)) <* IO.cede
* }}}
*
* Note that extremely long-running `expensiveWork` functions can still cause fairness issues,
* even when used with `cede`. This problem is somewhat fundamental to the nature of
* scheduling such computation on carrier threads. Whenever possible, it is best to break
* apart any such functions into multiple pieces invoked independently (e.g. via chained `map`
* calls) whenever the execution time exceeds five or six orders of magnitude beyond the
* overhead of `map` itself (around 1 millisecond on most hardware).
*
* This operation is not ''required'' in most applications, particularly those which are
* primarily I/O bound, as `IO` itself will automatically introduce fairness boundaries
* without requiring user input. These automatic boundaries are controlled by the
* [[cats.effect.unsafe.IORuntimeConfig.autoYieldThreshold]] configuration parameter, which in
* turn may be adjusted by overriding [[IOApp.runtimeConfig]].
*/
def cede: IO[Unit] = Cede

/**
Expand Down
29 changes: 29 additions & 0 deletions kernel/shared/src/main/scala/cats/effect/kernel/GenSpawn.scala
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,35 @@ trait GenSpawn[F[_], E] extends MonadCancel[F, E] with Unique[F] {
* Introduces a fairness boundary that yields control back to the scheduler of the runtime
* system. This allows the carrier thread to resume execution of another waiting fiber.
*
* This function is primarily useful when performing long-running computation that is outside
* of the monadic context. For example:
*
* {{{
* fa.map(data => expensiveWork(data))
* }}}
*
* In the above, we're assuming that `expensiveWork` is a function which is entirely
* compute-bound but very long-running. A good rule of thumb is to consider a function
* "expensive" when its runtime is around three or more orders of magnitude higher than the
* overhead of the `map` function itself (which runs in around 5 nanoseconds on modern
* hardware). Thus, any `expensiveWork` function which requires around 10 microseconds or
* longer to execute should be considered "long-running".
*
* The danger is that these types of long-running actions outside of the monadic context can
* result in degraded fairness properties. The solution is to add an explicit `cede` both
* before and after the expensive operation:
*
* {{{
* (fa <* F.cede).map(data => expensiveWork(data)) <* F.cede
* }}}
*
* Note that extremely long-running `expensiveWork` functions can still cause fairness issues,
* even when used with `cede`. This problem is somewhat fundamental to the nature of
* scheduling such computation on carrier threads. Whenever possible, it is best to break
* apart any such functions into multiple pieces invoked independently (e.g. via chained `map`
* calls) whenever the execution time exceeds five or six orders of magnitude beyond the
* overhead of `map` itself (around 1 millisecond on most hardware).
*
* Note that `cede` is merely a hint to the runtime system; implementations have the liberty
* to interpret this method to their liking as long as it obeys the respective laws. For
* example, a lawful, but atypical, implementation of this function is `F.unit`, in which case
Expand Down

0 comments on commit 733a4b4

Please sign in to comment.