Skip to content

Commit

Permalink
Ported improved stack traces from monix (#190)
Browse files Browse the repository at this point in the history
* ported traces from monix

* update mima filters

* processed some feedback

* added tests

* update monix version
  • Loading branch information
WesselVS authored Nov 10, 2020
1 parent dcce831 commit 185193f
Show file tree
Hide file tree
Showing 26 changed files with 1,348 additions and 102 deletions.
31 changes: 29 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sbt.url

addCommandAlias("ci-js", s";clean ;coreJS/test")
addCommandAlias("ci-jvm", s";clean ;benchmarks/compile ;coreJVM/test")
addCommandAlias("ci-jvm", s";clean ;benchmarks/compile ;coreJVM/test ;tracingTests/test")
addCommandAlias("ci-jvm-mima", s";coreJVM/mimaReportBinaryIssues")

inThisBuild(List(
Expand All @@ -17,7 +17,7 @@ inThisBuild(List(
))
))

val monixVersion = "3.2.2"
val monixVersion = "3.3.0"
val minitestVersion = "2.8.2"
val catsEffectVersion = "2.1.4"

Expand Down Expand Up @@ -76,6 +76,33 @@ lazy val docs = project
"io.monix" %% "monix-eval" % monixVersion
))

// monix-tracing-tests (not published)

lazy val FullTracingTest = config("fulltracing").extend(Test)

lazy val tracingTests = project.in(file("tracingTests"))
.dependsOn(coreJVM % "compile->compile; test->test")
.settings(sharedSettings)
.configs(FullTracingTest)
.settings(testFrameworks := Seq(new TestFramework("minitest.runner.Framework")))
.settings(inConfig(FullTracingTest)(Defaults.testSettings): _*)
.settings(
unmanagedSourceDirectories in FullTracingTest += {
baseDirectory.value.getParentFile / "src" / "fulltracing" / "scala"
},
test in Test := (test in Test).dependsOn(test in FullTracingTest).value,
fork in Test := true,
fork in FullTracingTest := true,
javaOptions in Test ++= Seq(
"-Dmonix.bio.enhancedExceptions=true",
"-Dmonix.bio.stackTracingMode=cached"
),
javaOptions in FullTracingTest ++= Seq(
"-Dmonix.bio.enhancedExceptions=true",
"-Dmonix.bio.stackTracingMode=full"
)
)

lazy val mdocSettings = Seq(
scalacOptions --= Seq("-Xfatal-warnings", "-Ywarn-unused"),
crossScalaVersions := Seq(scalaVersion.value),
Expand Down
30 changes: 30 additions & 0 deletions core/js/src/main/scala/monix/bio/internal/TracingPlatform.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2019-2020 by The Monix Project Developers.
* See the project homepage at: https://monix.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package monix.bio.internal

private[monix] object TracingPlatform {
final val isCachedStackTracing: Boolean = false

final val isFullStackTracing: Boolean = false

final val isStackTracing: Boolean = isFullStackTracing || isCachedStackTracing

final val traceBufferLogSize: Int = 4

final val enhancedExceptions: Boolean = false
}
76 changes: 76 additions & 0 deletions core/jvm/src/main/java/monix/bio/internal/TracingPlatform.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2019-2020 by The Monix Project Developers.
* See the project homepage at: https://monix.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package monix.bio.internal;

import java.util.Optional;

/**
* All Credits to https://github.com/typelevel/cats-effect and https://github.com/RaasAhsan
*
* Holds platform-specific flags that control tracing behavior.
*
* The Scala compiler inserts a volatile bitmap access for module field accesses.
* Because the `tracingMode` flag is read in various Task combinators, we are opting
* to define it in a Java source file to avoid the volatile access.
*
* INTERNAL API.
*/
public final class TracingPlatform {

/**
* 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("monix.bio.stackTracingMode"))
.filter(x -> !x.isEmpty())
.orElse("cached");

public static final boolean isCachedStackTracing = stackTracingMode.equalsIgnoreCase("cached");

public static final boolean isFullStackTracing = stackTracingMode.equalsIgnoreCase("full");

public 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.
*/
public static final int traceBufferLogSize = Optional.ofNullable(System.getProperty("monix.bio.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.
*/
public static final boolean enhancedExceptions = Optional.ofNullable(System.getProperty("monix.bio.enhancedExceptions"))
.map(Boolean::valueOf)
.orElse(true);

}
43 changes: 38 additions & 5 deletions core/jvm/src/main/scala/monix/bio/internal/TaskRunSyncUnsafe.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import java.util.concurrent.TimeoutException
import java.util.concurrent.locks.AbstractQueuedSynchronizer

import monix.bio.{BiCallback, IO}
import monix.bio.IO.{Async, Context, Error, Eval, EvalTotal, FlatMap, Map, Now, Suspend, SuspendTotal, Termination}
import monix.bio.IO.{Async, Context, Error, Eval, EvalTotal, FlatMap, Map, Now, Suspend, SuspendTotal, Termination, Trace}
import monix.bio.internal.TracingPlatform.{enhancedExceptions, isStackTracing}
import monix.bio.tracing.IOEvent
import monix.bio.internal.TaskRunLoop._
import monix.execution.Scheduler
import monix.execution.exceptions.UncaughtErrorException
Expand All @@ -44,9 +46,18 @@ private[bio] object TaskRunSyncUnsafe {
var hasUnboxed: Boolean = false
var unboxed: AnyRef = null

// we might not need to initialize full Task.Context
var tracingCtx: StackTracedContext = null

do {
current match {
case FlatMap(fa, bindNext) =>
case bind @ FlatMap(fa, bindNext, _) =>
if (isStackTracing) {
val trace = bind.trace
if (tracingCtx eq null) tracingCtx = new StackTracedContext
if (trace ne null) tracingCtx.pushEvent(trace.asInstanceOf[IOEvent])
}

if (bFirst ne null) {
if (bRest eq null) bRest = ChunkedArrayStack()
bRest.push(bFirst)
Expand Down Expand Up @@ -78,6 +89,11 @@ private[bio] object TaskRunSyncUnsafe {
}

case bindNext @ Map(fa, _, _) =>
if (isStackTracing) {
val trace = bindNext.trace
if (tracingCtx eq null) tracingCtx = new StackTracedContext
if (trace ne null) tracingCtx.pushEvent(trace.asInstanceOf[IOEvent])
}
if (bFirst ne null) {
if (bRest eq null) bRest = ChunkedArrayStack()
bRest.push(bFirst)
Expand All @@ -102,6 +118,12 @@ private[bio] object TaskRunSyncUnsafe {
}

case Error(error) =>
if (isStackTracing && enhancedExceptions) {
if (tracingCtx eq null) tracingCtx = new StackTracedContext
if (error.isInstanceOf[Throwable]) {
augmentException(error.asInstanceOf[Throwable], tracingCtx)
}
}
findErrorHandler[Any](bFirst, bRest) match {
case null => throw UncaughtErrorException.wrap(error)
case bind =>
Expand All @@ -113,6 +135,10 @@ private[bio] object TaskRunSyncUnsafe {
}

case Termination(error) =>
if (isStackTracing && enhancedExceptions) {
if (tracingCtx eq null) tracingCtx = new StackTracedContext
augmentException(error.asInstanceOf[Throwable], tracingCtx)
}
findTerminationHandler[Any](bFirst, bRest) match {
case null => throw error
case bind =>
Expand All @@ -123,8 +149,14 @@ private[bio] object TaskRunSyncUnsafe {
bFirst = null
}

case Trace(sourceTask, frame) =>
if (tracingCtx eq null) tracingCtx = new StackTracedContext
tracingCtx.pushEvent(frame)
current = sourceTask

case async =>
return blockForResult(async, timeout, scheduler, opts, bFirst, bRest)
if (tracingCtx eq null) tracingCtx = new StackTracedContext
return blockForResult(async, timeout, scheduler, opts, bFirst, bRest, tracingCtx)
}

if (hasUnboxed) {
Expand Down Expand Up @@ -155,12 +187,13 @@ private[bio] object TaskRunSyncUnsafe {
scheduler: Scheduler,
opts: IO.Options,
bFirst: Bind,
bRest: CallStack
bRest: CallStack,
tracingCtx: StackTracedContext
): A = {

val latch = new OneShotLatch
val cb = new BlockingCallback[Any, Any](latch)
val context = Context[Any](scheduler, opts)
val context = Context[Any](scheduler, opts, TaskConnection[Any](), tracingCtx)

// Starting actual execution
source match {
Expand Down
Loading

0 comments on commit 185193f

Please sign in to comment.