diff --git a/core/jvm/src/main/java/cats/effect/tracing/TracingConstants.java b/core/jvm/src/main/java/cats/effect/tracing/TracingConstants.java index 3aec81bee8..0c7fbb7336 100644 --- a/core/jvm/src/main/java/cats/effect/tracing/TracingConstants.java +++ b/core/jvm/src/main/java/cats/effect/tracing/TracingConstants.java @@ -24,7 +24,7 @@ class TracingConstants { * Sets stack tracing mode for a JVM process, which controls how much stack * trace information is captured. Acceptable values are: NONE, CACHED, FULL. */ - private static final String stackTracingMode = Optional.ofNullable(System.getProperty("cats.effect.stackTracingMode")) + private static final String stackTracingMode = Optional.ofNullable(System.getProperty("cats.effect.tracing.mode")) .filter(x -> !x.isEmpty()).orElse("cached"); static final boolean isCachedStackTracing = stackTracingMode.equalsIgnoreCase("cached"); @@ -32,27 +32,4 @@ class TracingConstants { static final boolean isFullStackTracing = stackTracingMode.equalsIgnoreCase("full"); static final boolean isStackTracing = isFullStackTracing || isCachedStackTracing; - - /** - * The number of trace lines to retain during tracing. If more trace lines are - * produced, then the oldest trace lines will be discarded. Automatically - * rounded up to the nearest power of 2. - */ - static final int traceBufferLogSize = Optional.ofNullable(System.getProperty("cats.effect.traceBufferLogSize")) - .filter(x -> !x.isEmpty()).flatMap(x -> { - try { - return Optional.of(Integer.valueOf(x)); - } catch (Exception e) { - return Optional.empty(); - } - }).orElse(4); - - /** - * Sets the enhanced exceptions flag, which controls whether or not the stack - * traces of IO exceptions are augmented to include async stack trace - * information. Stack tracing must be enabled in order to use this feature. This - * flag is enabled by default. - */ - static final boolean enhancedExceptions = Optional.ofNullable(System.getProperty("cats.effect.enhancedExceptions")) - .map(Boolean::valueOf).orElse(true); } diff --git a/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeConfigCompanionPlatform.scala b/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeConfigCompanionPlatform.scala index d6d8281140..7eb3dda10f 100644 --- a/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeConfigCompanionPlatform.scala +++ b/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeConfigCompanionPlatform.scala @@ -17,16 +17,27 @@ package cats.effect package unsafe +import scala.util.Try + private[unsafe] abstract class IORuntimeConfigCompanionPlatform { this: IORuntimeConfig.type => + // TODO make the cancelation and auto-yield properties have saner names protected final val Default: IORuntimeConfig = { val cancelationCheckThreshold = - System.getProperty("cats.effect.cancelation.check.threshold", "512").toInt + Try(System.getProperty("cats.effect.cancelation.check.threshold").toInt).getOrElse(512) + + val autoYieldThreshold = + Try(System.getProperty("cats.effect.auto.yield.threshold.multiplier").toInt) + .getOrElse(2) * cancelationCheckThreshold + + val enhancedExceptions = + Try(System.getProperty("cats.effect.tracing.exceptions.enhanced").toBoolean) + .getOrElse(DefaultEnhancedExceptions) + + val traceBufferSize = + Try(System.getProperty("cats.effect.tracing.buffer.size").toInt) + .getOrElse(DefaultTraceBufferSize) - apply( - cancelationCheckThreshold, - System - .getProperty("cats.effect.auto.yield.threshold.multiplier", "2") - .toInt * cancelationCheckThreshold) + apply(cancelationCheckThreshold, autoYieldThreshold, enhancedExceptions, traceBufferSize) } } diff --git a/core/shared/src/main/scala/cats/effect/IOFiber.scala b/core/shared/src/main/scala/cats/effect/IOFiber.scala index 6c3c98fd05..4d86d07aa9 100644 --- a/core/shared/src/main/scala/cats/effect/IOFiber.scala +++ b/core/shared/src/main/scala/cats/effect/IOFiber.scala @@ -118,7 +118,8 @@ private final class IOFiber[A]( private[this] val cancelationCheckThreshold: Int = runtime.config.cancelationCheckThreshold private[this] val autoYieldThreshold: Int = runtime.config.autoYieldThreshold - private[this] val tracingEvents: RingBuffer = RingBuffer.empty + private[this] val tracingEvents: RingBuffer = + RingBuffer.empty(runtime.config.traceBufferLogSize) override def run(): Unit = { // insert a read barrier after every async boundary @@ -1266,7 +1267,7 @@ private final class IOFiber[A]( } private[this] def runTerminusFailureK(t: Throwable): IO[Any] = { - Tracing.augmentThrowable(t, tracingEvents) + Tracing.augmentThrowable(runtime.config.enhancedExceptions, t, tracingEvents) done(Outcome.Errored(t)) IOEndFiber } diff --git a/core/shared/src/main/scala/cats/effect/tracing/RingBuffer.scala b/core/shared/src/main/scala/cats/effect/tracing/RingBuffer.scala index ddd70c8417..faf3d79a3a 100644 --- a/core/shared/src/main/scala/cats/effect/tracing/RingBuffer.scala +++ b/core/shared/src/main/scala/cats/effect/tracing/RingBuffer.scala @@ -49,7 +49,5 @@ private[effect] final class RingBuffer private (logSize: Int) { } private[effect] object RingBuffer { - import TracingConstants.traceBufferLogSize - - def empty: RingBuffer = new RingBuffer(traceBufferLogSize) + def empty(logSize: Int): RingBuffer = new RingBuffer(logSize) } diff --git a/core/shared/src/main/scala/cats/effect/tracing/Tracing.scala b/core/shared/src/main/scala/cats/effect/tracing/Tracing.scala index b1f207bfb9..903afc61de 100644 --- a/core/shared/src/main/scala/cats/effect/tracing/Tracing.scala +++ b/core/shared/src/main/scala/cats/effect/tracing/Tracing.scala @@ -53,7 +53,7 @@ private[effect] object Tracing extends ClassValue[TracingEvent] { "scala." ) - def augmentThrowable(t: Throwable, events: RingBuffer): Unit = { + def augmentThrowable(enhancedExceptions: Boolean, t: Throwable, events: RingBuffer): Unit = { def applyRunLoopFilter(ste: StackTraceElement): Boolean = { val name = ste.getClassName var i = 0 diff --git a/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala b/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala index 7499e8704c..54f7c407b0 100644 --- a/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala +++ b/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala @@ -19,15 +19,68 @@ package unsafe final case class IORuntimeConfig private ( val cancelationCheckThreshold: Int, - val autoYieldThreshold: Int) + val autoYieldThreshold: Int, + val enhancedExceptions: Boolean, + val traceBufferSize: Int) { + + def this(cancelationCheckThreshold: Int, autoYieldThreshold: Int) = + this( + cancelationCheckThreshold, + autoYieldThreshold, + IORuntimeConfig.DefaultEnhancedExceptions, + IORuntimeConfig.DefaultTraceBufferSize) + + def copy( + cancelationCheckThreshold: Int = this.cancelationCheckThreshold, + autoYieldThreshold: Int = this.autoYieldThreshold, + enhancedExceptions: Boolean = this.enhancedExceptions, + traceBufferSize: Int = this.traceBufferSize): IORuntimeConfig = + new IORuntimeConfig( + cancelationCheckThreshold, + autoYieldThreshold, + enhancedExceptions, + traceBufferSize) + + // shim for binary compat + private[unsafe] def copy( + cancelationCheckThreshold: Int, + autoYieldThreshold: Int): IORuntimeConfig = + new IORuntimeConfig( + cancelationCheckThreshold, + autoYieldThreshold, + enhancedExceptions, + traceBufferSize) + + private[effect] val traceBufferLogSize: Int = + Math.round(Math.log(traceBufferSize.toDouble) / Math.log(2)).toInt +} object IORuntimeConfig extends IORuntimeConfigCompanionPlatform { + // these have to be defs because we forward-reference them from the companion platform + private[unsafe] def DefaultEnhancedExceptions = true + private[unsafe] def DefaultTraceBufferSize = 16 + def apply(): IORuntimeConfig = Default - def apply(cancelationCheckThreshold: Int, autoYieldThreshold: Int): IORuntimeConfig = { + def apply(cancelationCheckThreshold: Int, autoYieldThreshold: Int): IORuntimeConfig = + apply( + cancelationCheckThreshold, + autoYieldThreshold, + DefaultEnhancedExceptions, + DefaultTraceBufferSize) + + def apply( + cancelationCheckThreshold: Int, + autoYieldThreshold: Int, + enhancedExceptions: Boolean, + traceBufferSize: Int): IORuntimeConfig = { if (autoYieldThreshold % cancelationCheckThreshold == 0) - new IORuntimeConfig(cancelationCheckThreshold, autoYieldThreshold) + new IORuntimeConfig( + cancelationCheckThreshold, + autoYieldThreshold, + enhancedExceptions, + 1 << Math.round(Math.log(traceBufferSize.toDouble) / Math.log(2)).toInt) else throw new AssertionError( s"Auto yield threshold $autoYieldThreshold must be a multiple of cancelation check threshold $cancelationCheckThreshold") diff --git a/docs/tracing.md b/docs/tracing.md index a910f86b42..a77158cadd 100644 --- a/docs/tracing.md +++ b/docs/tracing.md @@ -82,7 +82,7 @@ enabled before deploying it to a production environment! ### Configuration The stack tracing mode of an application is configured by the system property -`cats.effect.stackTracingMode`. There are 3 stack tracing modes: `none`, +`cats.effect.tracing.mode`. There are 3 stack tracing modes: `none`, `cached` and `full`. These values are case-insensitive. The default tracing mode is `cached`, which uses a global cache to avoid expensive recomputation of stack tracing information. @@ -91,15 +91,14 @@ To prevent unbounded memory usage, stack tracing information for a given fiber is accumulated in an internal buffer as execution proceeds. If more traces are collected than the buffer can retain, the oldest trace information will be overwritten. The default size of the buffer is 16. This value can be configured -via the system property `cats.effect.traceBufferLogSize`. Keep in mind that the -provided value is a logarithm of the actual size of the buffer (i.e. the actual -size of the tracing information buffer is -2`cats.effect.traceBufferLogSize`). +via the system property `cats.effect.tracing.buffer.size`. Keep in mind that the +value configured by the system property must be a power of two and will be rounded to the nearest power if not. For example, to enable full stack tracing and a trace buffer of size 1024, specify the following system properties: + ``` --Dcats.effect.stackTracingMode=full -Dcats.effect.traceBufferLogSize=10 +-Dcats.effect.tracing.mode=full -Dcats.effect.tracing.buffer.size=1024 ``` ## Stack tracing modes @@ -239,23 +238,23 @@ deeply nested recursive `IO` programs like the one on this page, even with a fairly small buffer. The example was generated using the following stack tracing configuration: ``` --Dcats.effect.stackTracingMode=full -Dcats.effect.traceBufferLogSize=6 +-Dcats.effect.tracing.mode=full -Dcats.effect.tracing.buffer.size=64 ``` The enhanced exceptions feature is controlled by the system property -`cats.effect.enhancedExceptions`. It is enabled by default. +`cats.effect.tracing.exceptions.enhanced`. It is enabled by default. It can be disabled with the following configuration: ``` --Dcats.effect.enhancedExceptions=false +-Dcats.effect.tracing.exceptions.enhanced=false ``` ### Complete code Here is the code snippet that was used to generate the above examples: ```scala mdoc // Pass the following system property to your JVM: -// -Dcats.effect.stackTracingMode=full -// -Dcats.effect.traceBufferLogSize=6 +// -Dcats.effect.tracing.mode=full +// -Dcats.effect.tracing.buffer.size=64 import cats.effect.{IO, IOApp}