diff --git a/build.sbt b/build.sbt index 1187d31bd8..b71524a1a9 100644 --- a/build.sbt +++ b/build.sbt @@ -937,7 +937,7 @@ lazy val tests: CrossProject = crossProject(JSPlatform, JVMPlatform, NativePlatf ) .jvmSettings( fork := true, - Test / javaOptions += "-Dcats.effect.ioLocalPropagation=true" + Test / javaOptions += "-Dcats.effect.trackFiberContext=true" ) .nativeSettings( Compile / mainClass := Some("catseffect.examples.NativeRunner") diff --git a/core/js-native/src/main/scala/cats/effect/IOFiberConstants.scala b/core/js-native/src/main/scala/cats/effect/IOFiberConstants.scala index efd9594bb0..69a1483d68 100644 --- a/core/js-native/src/main/scala/cats/effect/IOFiberConstants.scala +++ b/core/js-native/src/main/scala/cats/effect/IOFiberConstants.scala @@ -46,7 +46,7 @@ private object IOFiberConstants { final val AutoCedeR = 7 final val DoneR = 8 - final val ioLocalPropagation = false + final val TrackFiberContext = false @nowarn212 @inline def isVirtualThread(t: Thread): Boolean = false diff --git a/core/jvm/src/main/java/cats/effect/IOFiberConstants.java b/core/jvm/src/main/java/cats/effect/IOFiberConstants.java index 7cb6585b3d..1286844633 100644 --- a/core/jvm/src/main/java/cats/effect/IOFiberConstants.java +++ b/core/jvm/src/main/java/cats/effect/IOFiberConstants.java @@ -48,7 +48,7 @@ final class IOFiberConstants { static final byte AutoCedeR = 7; static final byte DoneR = 8; - static final boolean ioLocalPropagation = Boolean.getBoolean("cats.effect.ioLocalPropagation"); + static final boolean TrackFiberContext = Boolean.getBoolean("cats.effect.trackFiberContext"); static boolean isVirtualThread(final Thread thread) { try { diff --git a/core/jvm/src/main/scala/cats/effect/IOLocalPlatform.scala b/core/jvm/src/main/scala/cats/effect/IOLocalPlatform.scala index 4a66d31b23..17e89e8e22 100644 --- a/core/jvm/src/main/scala/cats/effect/IOLocalPlatform.scala +++ b/core/jvm/src/main/scala/cats/effect/IOLocalPlatform.scala @@ -16,17 +16,17 @@ package cats.effect -import IOFiberConstants.ioLocalPropagation +import IOFiberConstants.TrackFiberContext private[effect] trait IOLocalPlatform[A] { self: IOLocal[A] => /** * Returns a [[java.lang.ThreadLocal]] view of this [[IOLocal]] that allows to unsafely get, * set, and remove (aka reset) the value in the currently running fiber. The system property - * `cats.effect.ioLocalPropagation` must be `true`, otherwise throws an + * `cats.effect.trackFiberContext` must be `true`, otherwise throws an * [[java.lang.UnsupportedOperationException]]. */ - def unsafeThreadLocal(): ThreadLocal[A] = if (ioLocalPropagation) + def unsafeThreadLocal(): ThreadLocal[A] = if (TrackFiberContext) new ThreadLocal[A] { override def get(): A = { val fiber = IOFiber.currentIOFiber() @@ -51,7 +51,7 @@ private[effect] trait IOLocalPlatform[A] { self: IOLocal[A] => else throw new UnsupportedOperationException( "IOLocal-ThreadLocal propagation is disabled.\n" + - "Enable by setting cats.effect.ioLocalPropagation=true." + "Enable by setting cats.effect.trackFiberContext=true." ) } diff --git a/core/jvm/src/main/scala/cats/effect/IOPlatform.scala b/core/jvm/src/main/scala/cats/effect/IOPlatform.scala index c53654eafc..7dd0115d38 100644 --- a/core/jvm/src/main/scala/cats/effect/IOPlatform.scala +++ b/core/jvm/src/main/scala/cats/effect/IOPlatform.scala @@ -83,7 +83,7 @@ abstract private[effect] class IOPlatform[+A] extends Serializable { self: IO[A] case _: InterruptedException => None } finally { - if (IOFiberConstants.ioLocalPropagation) + if (IOFiberConstants.TrackFiberContext) IOLocal.setThreadLocalState(fiber.getLocalState()) } } diff --git a/core/shared/src/main/scala/cats/effect/IO.scala b/core/shared/src/main/scala/cats/effect/IO.scala index a4c597547c..0104cd7571 100644 --- a/core/shared/src/main/scala/cats/effect/IO.scala +++ b/core/shared/src/main/scala/cats/effect/IO.scala @@ -1123,7 +1123,7 @@ sealed abstract class IO[+A] private () extends IOPlatform[A] { implicit runtime: unsafe.IORuntime): IOFiber[A @uncheckedVariance] = { val fiber = new IOFiber[A]( - if (IOFiberConstants.ioLocalPropagation) IOLocal.getThreadLocalState() + if (IOFiberConstants.TrackFiberContext) IOLocal.getThreadLocalState() else IOLocalState.empty, { oc => if (registerCallback) { diff --git a/core/shared/src/main/scala/cats/effect/IOFiber.scala b/core/shared/src/main/scala/cats/effect/IOFiber.scala index 8d095587fe..9e0357b734 100644 --- a/core/shared/src/main/scala/cats/effect/IOFiber.scala +++ b/core/shared/src/main/scala/cats/effect/IOFiber.scala @@ -115,7 +115,7 @@ private final class IOFiber[A]( // insert a read barrier after every async boundary readBarrier() - if (ioLocalPropagation) { + if (TrackFiberContext) { IOFiber.setCurrentIOFiber(this) } @@ -131,7 +131,7 @@ private final class IOFiber[A]( case 8 => () // DoneR } - if (ioLocalPropagation) { + if (TrackFiberContext) { IOFiber.setCurrentIOFiber(null) } } diff --git a/core/shared/src/main/scala/cats/effect/IOLocal.scala b/core/shared/src/main/scala/cats/effect/IOLocal.scala index e045cdd625..fe76093544 100644 --- a/core/shared/src/main/scala/cats/effect/IOLocal.scala +++ b/core/shared/src/main/scala/cats/effect/IOLocal.scala @@ -271,7 +271,7 @@ object IOLocal { /** * `true` if IOLocal-Threadlocal propagation is enabled */ - def isPropagating: Boolean = IOFiberConstants.ioLocalPropagation + def isPropagating: Boolean = IOFiberConstants.TrackFiberContext private[effect] def getThreadLocalState() = { val fiber = IOFiber.currentIOFiber() diff --git a/core/shared/src/main/scala/cats/effect/unsafe/IORuntime.scala b/core/shared/src/main/scala/cats/effect/unsafe/IORuntime.scala index 64d1f34c76..3b405504c6 100644 --- a/core/shared/src/main/scala/cats/effect/unsafe/IORuntime.scala +++ b/core/shared/src/main/scala/cats/effect/unsafe/IORuntime.scala @@ -116,6 +116,14 @@ object IORuntime extends IORuntimeCompanionPlatform { def builder(): IORuntimeBuilder = IORuntimeBuilder() + /** + * Returns `true` if invoked within an executing `IOFiber`. If the property + * `cats.effect.trackFiberContext=true` then this method is always accurate. Otherwise, it is + * best-effort and may return `false` even when you are executing within an `IOFiber`. + */ + @static def isUnderFiberContext(): Boolean = + IOFiber.currentIOFiber() ne null + private[effect] def testRuntime(ec: ExecutionContext, scheduler: Scheduler): IORuntime = { val config = IORuntimeConfig() val metrics = IORuntimeMetrics(ec) diff --git a/docs/core/io-local.md b/docs/core/io-local.md index af3af30284..e735f62537 100644 --- a/docs/core/io-local.md +++ b/docs/core/io-local.md @@ -185,4 +185,4 @@ TraceIdScope.fromIOLocal.flatMap { implicit traceIdScope: TraceIdScope[IO] => To support integration with Java libraries, `IOLocal` interoperates with the JDK `ThreadLocal` API via `IOLocal#unsafeThreadLocal`. This makes it possible to unsafely read and write the value of an `IOLocal` on the currently running fiber within a suspended side-effect (e.g. `IO.delay` or `IO.blocking`). -To use this feature you must set the property `cats.effect.ioLocalPropagation=true`. Note that enabling propagation causes a performance hit of up to 25% in some of our microbenchmarks. However, it is not clear that this performance impact matters in practice. +To use this feature you must set the property `cats.effect.trackFiberContext=true`. Note that enabling propagation causes a performance hit of up to 25% in some of our microbenchmarks. However, it is not clear that this performance impact matters in practice. diff --git a/docs/core/io-runtime-config.md b/docs/core/io-runtime-config.md index 3810cab0c0..4a4c2163d1 100644 --- a/docs/core/io-runtime-config.md +++ b/docs/core/io-runtime-config.md @@ -37,4 +37,4 @@ This can be done for example with the [EnvironmentPlugin for Webpack](https://we | `cats.effect.cpu.starvation.check.interval`
`CATS_EFFECT_CPU_STARVATION_CHECK_INTERVAL` | `FiniteDuration` (`1.second`) | The starvation checker repeatedly sleeps for this interval and then checks `monotonic` time when it awakens. It will then print a warning to stderr if it finds that the current time is greater than expected (see `threshold` below). | | `cats.effect.cpu.starvation.check.initialDelay`
`CATS_EFFECT_CPU_STARVATION_CHECK_INITIAL_DELAY` | `Duration` (`10.seconds`) | The initial delay before the CPU starvation checker starts running. Avoids spurious warnings due to the JVM not being warmed up yet. Set to `Duration.Inf` to disable CPU starvation checking. | | `cats.effect.cpu.starvation.check.threshold`
`CATS_EFFECT_CPU_STARVATION_CHECK_THRESHOLD` | `Double` (`0.1`) | The starvation checker will print a warning if it finds that it has been asleep for at least `interval * (1 + threshold)` (where `interval` from above is the expected time to be asleep for). Sleeping for too long is indicative of fibers hogging a worker thread either by performing blocking operations on it or by `cede`ing insufficiently frequently. | -| `cats.effect.ioLocalPropagation`
N/A | `Boolean` (`false`) | Enables `IOLocal`s to be propagated as `ThreadLocal`s. | +| `cats.effect.trackFiberContext`
N/A | `Boolean` (`false`) | Tracks the currently running fiber on each thread. Necessary for `IOLocal`s to be propagated as `ThreadLocal`s. |