From f1c5e8df8d894f3c2268acb1a1a8bba63291126c Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Sat, 24 Jul 2021 14:01:35 -0600 Subject: [PATCH 1/6] Adjusted tracing property names and moved them into IORuntimeConfig --- .../cats/effect/tracing/TracingConstants.java | 2 +- .../IORuntimeConfigCompanionPlatform.scala | 12 ++++++- .../cats/effect/unsafe/IORuntimeConfig.scala | 33 +++++++++++++++++-- docs/tracing.md | 21 ++++++------ 4 files changed, 52 insertions(+), 16 deletions(-) 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..e3d3243aee 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"); 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..2efffc781b 100644 --- a/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeConfigCompanionPlatform.scala +++ b/core/jvm/src/main/scala/cats/effect/unsafe/IORuntimeConfigCompanionPlatform.scala @@ -19,6 +19,7 @@ package unsafe 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 @@ -27,6 +28,15 @@ private[unsafe] abstract class IORuntimeConfigCompanionPlatform { this: IORuntim cancelationCheckThreshold, System .getProperty("cats.effect.auto.yield.threshold.multiplier", "2") - .toInt * cancelationCheckThreshold) + .toInt * cancelationCheckThreshold, + System + .getProperty( + "cats.effect.tracing.exceptions.enhanced", + DefaultEnhancedExceptions.toString) + .toBoolean, + System + .getProperty("cats.effect.tracing.buffer.size", DefaultTraceBufferSize.toString) + .toInt + ) } } 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..9d08c2ac5d 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,42 @@ 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) +} object IORuntimeConfig extends IORuntimeConfigCompanionPlatform { + private[unsafe] val DefaultEnhancedExceptions = true + private[unsafe] val 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, + 2 << (Math.round(Math.log(traceBufferSize.toDouble) / Math.log(2)).toInt - 1)) 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} From d89d9c8f0a5a0c933d514fdb38657ebe69b6b100 Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Sat, 24 Jul 2021 14:14:51 -0600 Subject: [PATCH 2/6] Wired runtime-based tracing config into actual working things --- .../cats/effect/tracing/TracingConstants.java | 23 ------------------- .../src/main/scala/cats/effect/IOFiber.scala | 5 ++-- .../cats/effect/tracing/RingBuffer.scala | 4 +--- .../scala/cats/effect/tracing/Tracing.scala | 2 +- .../cats/effect/unsafe/IORuntimeConfig.scala | 9 ++++++-- 5 files changed, 12 insertions(+), 31 deletions(-) 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 e3d3243aee..0c7fbb7336 100644 --- a/core/jvm/src/main/java/cats/effect/tracing/TracingConstants.java +++ b/core/jvm/src/main/java/cats/effect/tracing/TracingConstants.java @@ -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/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 9d08c2ac5d..d57ab2340f 100644 --- a/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala +++ b/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala @@ -29,11 +29,16 @@ final case class IORuntimeConfig private ( autoYieldThreshold, IORuntimeConfig.DefaultEnhancedExceptions, IORuntimeConfig.DefaultTraceBufferSize) + + private[effect] val traceBufferLogSize: Int = + Math.round(Math.log(traceBufferSize.toDouble) / Math.log(2)).toInt } object IORuntimeConfig extends IORuntimeConfigCompanionPlatform { - private[unsafe] val DefaultEnhancedExceptions = true - private[unsafe] val DefaultTraceBufferSize = 16 + + // 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 From 627e6514ed0b91931108381954054706765cb7fa Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Sat, 24 Jul 2021 14:40:44 -0600 Subject: [PATCH 3/6] Added binary compatibility shim for `IORuntimeConfig#copy` --- .../scala/cats/effect/unsafe/IORuntimeConfig.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) 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 d57ab2340f..a474ef34a6 100644 --- a/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala +++ b/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala @@ -32,6 +32,16 @@ final case class IORuntimeConfig private ( private[effect] val traceBufferLogSize: Int = Math.round(Math.log(traceBufferSize.toDouble) / Math.log(2)).toInt + + // shim for binary compat + private[unsafe] def copy( + cancelationCheckThreshold: Int = this.cancelationCheckThreshold, + autoYieldThreshold: Int = this.autoYieldThreshold): IORuntimeConfig = + new IORuntimeConfig( + cancelationCheckThreshold, + autoYieldThreshold, + enhancedExceptions, + traceBufferSize) } object IORuntimeConfig extends IORuntimeConfigCompanionPlatform { From e25d7dfffbae07c3f00d3693ccb8ef3bc1f80dcc Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Sat, 24 Jul 2021 15:50:18 -0600 Subject: [PATCH 4/6] Fixed bincompat copy shim --- .../cats/effect/unsafe/IORuntimeConfig.scala | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) 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 a474ef34a6..885479bc6c 100644 --- a/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala +++ b/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala @@ -30,18 +30,29 @@ final case class IORuntimeConfig private ( IORuntimeConfig.DefaultEnhancedExceptions, IORuntimeConfig.DefaultTraceBufferSize) - private[effect] val traceBufferLogSize: Int = - Math.round(Math.log(traceBufferSize.toDouble) / Math.log(2)).toInt + 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 = this.cancelationCheckThreshold, - autoYieldThreshold: Int = this.autoYieldThreshold): IORuntimeConfig = + 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 { From 9bcea87354649febb811c5b95926b6b152c7a726 Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Sat, 24 Jul 2021 16:01:38 -0600 Subject: [PATCH 5/6] Safer parsing logic for system properties --- .../IORuntimeConfigCompanionPlatform.scala | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) 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 2efffc781b..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,26 +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 - - apply( - cancelationCheckThreshold, - System - .getProperty("cats.effect.auto.yield.threshold.multiplier", "2") - .toInt * cancelationCheckThreshold, - System - .getProperty( - "cats.effect.tracing.exceptions.enhanced", - DefaultEnhancedExceptions.toString) - .toBoolean, - System - .getProperty("cats.effect.tracing.buffer.size", DefaultTraceBufferSize.toString) - .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, autoYieldThreshold, enhancedExceptions, traceBufferSize) } } From 2f3e984cb5c9b2de83c572cc1cc6c866f2db0e37 Mon Sep 17 00:00:00 2001 From: Daniel Spiewak Date: Sat, 24 Jul 2021 16:02:22 -0600 Subject: [PATCH 6/6] Make off-by-one errors slightly less likely --- .../src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 885479bc6c..54f7c407b0 100644 --- a/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala +++ b/core/shared/src/main/scala/cats/effect/unsafe/IORuntimeConfig.scala @@ -80,7 +80,7 @@ object IORuntimeConfig extends IORuntimeConfigCompanionPlatform { cancelationCheckThreshold, autoYieldThreshold, enhancedExceptions, - 2 << (Math.round(Math.log(traceBufferSize.toDouble) / Math.log(2)).toInt - 1)) + 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")