Skip to content

Commit

Permalink
Merge pull request #3708 from typelevel/release/3.5.1-minor
Browse files Browse the repository at this point in the history
Merge changes from 3.5.1 into series/3.x
  • Loading branch information
armanbilge authored Jun 27, 2023
2 parents f0c60ca + ba85e5e commit 86b65a9
Show file tree
Hide file tree
Showing 26 changed files with 409 additions and 187 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,11 @@ jobs:

- name: Configure Windows Pagefile
if: matrix.os == 'windows-latest'
uses: al-cheb/configure-pagefile-action@v1.3
uses: al-cheb/configure-pagefile-action@d298bdee6b133626425040e3788f1055a8b4cf7a
with:
minimum-size: 2GB
maximum-size: 8GB
timeout: 600

- name: Check that workflows are up to date
shell: bash
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- Tired: **2.5.5** (end of life)

```scala
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.4.11"
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.1"
```

The above represents the core, stable dependency which brings in the entirety of Cats Effect. This is *most likely* what you want. All current Cats Effect releases are published for Scala 2.12, 2.13, 3.0, and Scala.js 1.7.
Expand All @@ -30,22 +30,22 @@ Depending on your use-case, you may want to consider one of the several other mo

```scala
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect-kernel" % "3.4.11",
"org.typelevel" %% "cats-effect-laws" % "3.4.11" % Test)
"org.typelevel" %% "cats-effect-kernel" % "3.5.1",
"org.typelevel" %% "cats-effect-laws" % "3.5.1" % Test)
```

If you're a middleware framework (like [Fs2](https://fs2.io/)), you probably want to depend on **std**, which gives you access to `Queue`, `Semaphore`, and much more without introducing a hard-dependency on `IO` outside of your tests:

```scala
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect-std" % "3.4.11",
"org.typelevel" %% "cats-effect" % "3.4.11" % Test)
"org.typelevel" %% "cats-effect-std" % "3.5.1",
"org.typelevel" %% "cats-effect" % "3.5.1" % Test)
```

You may also find some utility in the **testkit** and **kernel-testkit** projects, which contain `TestContext`, generators for `IO`, and a few other things:

```scala
libraryDependencies += "org.typelevel" %% "cats-effect-testkit" % "3.4.11" % Test
libraryDependencies += "org.typelevel" %% "cats-effect-testkit" % "3.5.1" % Test
```

Cats Effect provides backward binary compatibility within the 2.x and 3.x version lines, and both forward and backward compatibility within any major/minor line. This is analogous to the versioning scheme used by Cats itself, as well as other major projects such as Scala.js. Thus, any project depending upon Cats Effect 2.2.1 can be used with libraries compiled against Cats Effect 2.0.0 or 2.2.3, but *not* with libraries compiled against 2.3.0 or higher.
Expand Down
106 changes: 61 additions & 45 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,12 @@ ThisBuild / githubWorkflowBuildPreamble ++= Seq(
cond = Some(s"matrix.java == '${GraalVM.render}'")
),
WorkflowStep.Use(
UseRef.Public("al-cheb", "configure-pagefile-action", "v1.3"),
UseRef.Public(
"al-cheb",
"configure-pagefile-action",
"d298bdee6b133626425040e3788f1055a8b4cf7a"),
name = Some("Configure Windows Pagefile"),
params = Map("minimum-size" -> "2GB", "maximum-size" -> "8GB"),
params = Map("minimum-size" -> "2GB", "maximum-size" -> "8GB", "timeout" -> "600"),
cond = Some(s"matrix.os == '$Windows'")
)
)
Expand Down Expand Up @@ -795,7 +798,10 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
"cats.effect.unsafe.ES2021FiberMonitor.this"),
// introduced by #3324, which specialized CallbackStack for JS
// internal API change
ProblemFilters.exclude[IncompatibleTemplateDefProblem]("cats.effect.CallbackStack")
ProblemFilters.exclude[IncompatibleTemplateDefProblem]("cats.effect.CallbackStack"),
// introduced by #3642, which optimized the BatchingMacrotaskExecutor
ProblemFilters.exclude[MissingClassProblem](
"cats.effect.unsafe.BatchingMacrotaskExecutor$executeBatchTaskRunnable$")
)
},
mimaBinaryIssueFilters ++= {
Expand Down Expand Up @@ -913,48 +919,58 @@ lazy val std = crossProject(JSPlatform, JVMPlatform, NativePlatform)
"org.scalacheck" %%% "scalacheck" % ScalaCheckVersion % Test,
"org.specs2" %%% "specs2-scalacheck" % Specs2Version % Test
),
mimaBinaryIssueFilters ++= Seq(
// introduced by #2604, Fix Console on JS
// changes to `cats.effect.std` package private code
ProblemFilters.exclude[MissingClassProblem]("cats.effect.std.Console$SyncConsole"),
// introduced by #2951
// added configurability to Supervisor's scope termination behavior
// the following are package-private APIs
ProblemFilters.exclude[IncompatibleMethTypeProblem](
"cats.effect.std.Supervisor#State.add"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"cats.effect.std.Supervisor#State.add"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"cats.effect.std.Supervisor#State.joinAll"),
// introduced by #3000
// package-private or private stuff
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.Queue#AbstractQueue.onOfferNoCapacity"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"cats.effect.std.Queue#AbstractQueue.onOfferNoCapacity"),
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.Queue#BoundedQueue.onOfferNoCapacity"),
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.Queue#CircularBufferQueue.onOfferNoCapacity"),
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.Queue#DroppingQueue.onOfferNoCapacity"),
// #3524, private class
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.MapRef#ConcurrentHashMapImpl.keys"),
// introduced by #3346
// private stuff
ProblemFilters.exclude[MissingClassProblem]("cats.effect.std.Mutex$Impl"),
// introduced by #3347
// private stuff
ProblemFilters.exclude[MissingClassProblem]("cats.effect.std.AtomicCell$Impl"),
// introduced by #3409
// extracted UnsafeUnbounded private data structure
ProblemFilters.exclude[MissingClassProblem]("cats.effect.std.Queue$UnsafeUnbounded"),
ProblemFilters.exclude[MissingClassProblem]("cats.effect.std.Queue$UnsafeUnbounded$Cell"),
// introduced by #3480
// adds method to sealed Hotswap
ProblemFilters.exclude[ReversedMissingMethodProblem]("cats.effect.std.Hotswap.get")
)
mimaBinaryIssueFilters ++= {
if (tlIsScala3.value) {
Seq(
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.Supervisor.apply$default$2")
)
} else Seq()
},
mimaBinaryIssueFilters ++=
Seq(
// introduced by #2604, Fix Console on JS
// changes to `cats.effect.std` package private code
ProblemFilters.exclude[MissingClassProblem]("cats.effect.std.Console$SyncConsole"),
// introduced by #2951
// added configurability to Supervisor's scope termination behavior
// the following are package-private APIs
ProblemFilters.exclude[IncompatibleMethTypeProblem](
"cats.effect.std.Supervisor#State.add"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"cats.effect.std.Supervisor#State.add"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"cats.effect.std.Supervisor#State.joinAll"),
// introduced by #3000
// package-private or private stuff
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.Queue#AbstractQueue.onOfferNoCapacity"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"cats.effect.std.Queue#AbstractQueue.onOfferNoCapacity"),
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.Queue#BoundedQueue.onOfferNoCapacity"),
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.Queue#CircularBufferQueue.onOfferNoCapacity"),
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.Queue#DroppingQueue.onOfferNoCapacity"),
// #3524, private class
ProblemFilters.exclude[DirectMissingMethodProblem](
"cats.effect.std.MapRef#ConcurrentHashMapImpl.keys"),
// introduced by #3346
// private stuff
ProblemFilters.exclude[MissingClassProblem]("cats.effect.std.Mutex$Impl"),
// introduced by #3347
// private stuff
ProblemFilters.exclude[MissingClassProblem]("cats.effect.std.AtomicCell$Impl"),
// introduced by #3409
// extracted UnsafeUnbounded private data structure
ProblemFilters.exclude[MissingClassProblem]("cats.effect.std.Queue$UnsafeUnbounded"),
ProblemFilters.exclude[MissingClassProblem](
"cats.effect.std.Queue$UnsafeUnbounded$Cell"),
// introduced by #3480
// adds method to sealed Hotswap
ProblemFilters.exclude[ReversedMissingMethodProblem]("cats.effect.std.Hotswap.get")
)
)
.jsSettings(
libraryDependencies += "org.scala-js" %%% "scala-js-macrotask-executor" % MacrotaskExecutorVersion % Test,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private[effect] final class BatchingMacrotaskExecutor(
if (js.typeOf(js.Dynamic.global.queueMicrotask) == "function")
js.Dynamic.global.queueMicrotask.asInstanceOf[js.Function1[js.Function0[Any], Any]]
else {
val resolved = js.Dynamic.global.Promise.resolved(())
val resolved = js.Dynamic.global.Promise.resolve(())
task => resolved.`then`(task)
}

Expand All @@ -56,7 +56,7 @@ private[effect] final class BatchingMacrotaskExecutor(
private[this] var needsReschedule = true
private[this] val fibers = new JSArrayQueue[IOFiber[_]]

private[this] object executeBatchTaskRunnable extends Runnable {
private[this] val executeBatchTaskRunnable = new Runnable {
def run() = {
// do up to batchSize tasks
var i = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,16 @@ private[effect] final class WorkStealingThreadPool[P](
*/
override def reportFailure(cause: Throwable): Unit = reportFailure0(cause)

override def monotonicNanos(): Long = System.nanoTime()
override def monotonicNanos(): Long = {
val back = System.nanoTime()

val thread = Thread.currentThread()
if (thread.isInstanceOf[WorkerThread[_]]) {
thread.asInstanceOf[WorkerThread[_]].now = back
}

back
}

override def nowMillis(): Long = System.currentTimeMillis()

Expand Down
32 changes: 26 additions & 6 deletions core/jvm/src/main/scala/cats/effect/unsafe/WorkerThread.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ private final class WorkerThread[P](
*/
private[this] var blocking: Boolean = false

/**
* Contains the current monotonic time. Updated by the main worker loop as well as the
* `Scheduler` functions when accessing system time in userspace.
*/
private[unsafe] var now: Long = System.nanoTime()

/**
* Holds a reference to the fiber currently being executed by this worker thread. This field
* is sometimes published by the `head` and `tail` of the [[LocalQueue]] and sometimes by the
Expand Down Expand Up @@ -150,9 +156,13 @@ private final class WorkerThread[P](
}

def sleep(delay: FiniteDuration, callback: Right[Nothing, Unit] => Unit): Runnable = {
// take the opportunity to update the current time, just in case other timers can benefit
val _now = System.nanoTime()
now = _now

// note that blockers aren't owned by the pool, meaning we only end up here if !blocking
sleepers.insert(
now = System.nanoTime(),
now = _now,
delay = delay.toNanos,
callback = callback,
tlr = random
Expand Down Expand Up @@ -342,12 +352,14 @@ private final class WorkerThread[P](
}
}

now = System.nanoTime()

if (nextState != 4) {
// after being unparked, we re-check sleepers;
// if we find an already expired one, we go
// immediately to state 4 (local queue stuff):
val nextTrigger = sleepers.peekFirstTriggerTime()
if ((nextTrigger != MIN_VALUE) && (nextTrigger - System.nanoTime() <= 0L)) {
if ((nextTrigger != MIN_VALUE) && (nextTrigger - now <= 0L)) {
pool.transitionWorkerFromSearching(rnd)
4
} else {
Expand Down Expand Up @@ -390,7 +402,7 @@ private final class WorkerThread[P](
// no sleeper (it was removed)
parkLoop()
} else {
val now = System.nanoTime()
now = System.nanoTime()
val nanos = triggerTime - now

if (nanos > 0L) {
Expand All @@ -400,9 +412,12 @@ private final class WorkerThread[P](
pool.shutdown()
false // we know `done` is `true`
} else {
// we already parked and time passed, so update time again
// it doesn't matter if we timed out or were awakened, the update is free-ish
now = System.nanoTime()
if (parked.get()) {
// we were either awakened spuriously, or we timed out or polled an event
if (polled || (triggerTime - System.nanoTime() <= 0)) {
if (polled || (triggerTime - now <= 0)) {
// we timed out or polled an event
if (parked.getAndSet(false)) {
pool.doneSleeping()
Expand Down Expand Up @@ -547,6 +562,9 @@ private final class WorkerThread[P](
}
}

// update the current time
now = System.nanoTime()

// Transition to executing fibers from the local queue.
state = 4

Expand Down Expand Up @@ -612,8 +630,11 @@ private final class WorkerThread[P](
}

case 2 =>
// update the current time
now = System.nanoTime()

// First try to steal some expired timers:
if (pool.stealTimers(System.nanoTime(), rnd)) {
if (pool.stealTimers(now, rnd)) {
// some stolen timer created new work for us
pool.transitionWorkerFromSearching(rnd)
state = 4
Expand Down Expand Up @@ -710,7 +731,6 @@ private final class WorkerThread[P](

case _ =>
// Call all of our expired timers:
val now = System.nanoTime()
var cont = true
while (cont) {
val cb = sleepers.pollFirstIfTriggered(now)
Expand Down
10 changes: 9 additions & 1 deletion core/shared/src/main/scala/cats/effect/IOFiber.scala
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,15 @@ private final class IOFiber[A](

val get: IO[Any] = IOCont.Get(state)

val next = body[IO].apply(cb, get, FunctionK.id)
val next =
try {
body[IO].apply(cb, get, FunctionK.id)
} catch {
case t if NonFatal(t) =>
IO.raiseError(t)
case t: Throwable =>
onFatalFailure(t)
}

runLoop(next, nextCancelation, nextAutoCede)

Expand Down
4 changes: 2 additions & 2 deletions docs/core/fiber-dumps.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ Summary statistics for the global fiber runtime are printed following the fiber

## Triggering a fiber dump

Triggering the above fiber dump is a matter of sending a POSIX signal to the process using the `kill` command. The exact signal is dependent on the JVM (and version thereof) and operating system under which your application is running. Rather than attempting to hard-code all possible compatible signal configurations, Cats Effect simply *attempts* to register *both* `INFO` and `USR1` (for JVM applications) or `USR2` (for Node.js applications). In practice, `INFO` will most commonly be used on macOS and BSD, while `USR1` is more common on Linux. Thus, `kill -INFO <pid>` on macOS and `kill -USR1 <pid>` on Linux (or `USR2` for Node.js applications). POSIX signals do not exist on Windows (except under WSL, which behaves exactly like a normal Linux), and thus the mechanism is disabled.
Triggering the above fiber dump is a matter of sending a POSIX signal to the process using the `kill` command. The exact signal is dependent on the JVM (and version thereof) and operating system under which your application is running. Rather than attempting to hard-code all possible compatible signal configurations, Cats Effect simply *attempts* to register *both* `INFO` and `USR1` (for JVM applications) or `USR2` (for Node.js applications). In practice, `INFO` will most commonly be used on macOS and BSD, while `USR1` is more common on Linux. Thus, `kill -INFO <pid>` on macOS and `kill -USR1 <pid>` on Linux (or `USR2` for Node.js applications). POSIX signals do not exist on Windows (except under WSL, which behaves exactly like a normal Linux), and thus the mechanism is disabled. Unfortunately it is also disabled on JDK 8, which does not have any free signals available for applications to use.

Since `INFO` is the signal used on macOS and BSD, this combined with a quirk of Apple's TTY implementation means that **anyone running a Cats Effect application on macOS can simply hit <kbd>Ctrl</kbd>-<kbd>T</kbd>** within the active application to trigger a fiber dump, similar to how you can use <kbd>Ctrl</kbd>-<kbd>\\</kbd> to trigger a thread dump. Note that this trick only works on macOS, since that is the only platform which maps a particular keybind to either the `INFO` or `USR1` signals.

In the event that you're either running on a platform which doesn't support POSIX signals, or the signal registration failed for whatever reason, Cats Effect on the JVM will *also* automatically register an [MBean](https://docs.oracle.com/javase/7/docs/technotes/guides/management/overview.html) under `cats.effect.unsafe.metrics.LiveFiberSnapshotTriggerMBean` which can produce a string representation of the fiber dump when its only method is invoked.
In the event that you're either running on a platform which doesn't support POSIX signals, or the signal registration failed for whatever reason, Cats Effect on the JVM will *also* automatically register an [MBean](https://docs.oracle.com/javase/8/docs/technotes/guides/management/overview.html) under `cats.effect.unsafe.metrics.LiveFiberSnapshotTriggerMBean` which can produce a string representation of the fiber dump when its only method is invoked.

## Configuration

Expand Down
2 changes: 1 addition & 1 deletion docs/core/native-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ ThisBuild / scalaVersion := "2.13.8"

lazy val root = (project in file(".")).enablePlugins(NativeImagePlugin).settings(
name := "cats-effect-3-hello-world",
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.4.11",
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.1",
Compile / mainClass := Some("com.example.Main"),
nativeImageOptions += "--no-fallback",
nativeImageVersion := "22.1.0" // It should be at least version 21.0.0
Expand Down
2 changes: 1 addition & 1 deletion docs/core/scala-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ lazy val root = project.in(file("."))
.enablePlugins(ScalaNativePlugin)
.settings(
name := "cats-effect-3-hello-world",
libraryDependencies += "org.typelevel" %%% "cats-effect" % "3.4.11",
libraryDependencies += "org.typelevel" %%% "cats-effect" % "3.5.1",
Compile / mainClass := Some("com.example.Main")
)

Expand Down
2 changes: 1 addition & 1 deletion docs/core/test-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ For those migrating code from Cats Effect 2, `TestControl` is a considerably mor
In order to use `TestControl`, you will need to bring in the **cats-effect-testkit** dependency:

```scala
libraryDependencies += "org.typelevel" %% "cats-effect-testkit" % "3.4.11" % Test
libraryDependencies += "org.typelevel" %% "cats-effect-testkit" % "3.5.1" % Test
```

## Example
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ title: Getting Started
Add the following to your **build.sbt**:

```scala
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.4.11"
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.5.1"
```

Naturally, if you're using ScalaJS, you should replace the double `%%` with a triple `%%%`. If you're on Scala 2, it is *highly* recommended that you enable the [better-monadic-for](https://github.com/oleg-py/better-monadic-for) plugin, which fixes a number of surprising elements of the `for`-comprehension syntax in the Scala language:
Expand Down
Loading

0 comments on commit 86b65a9

Please sign in to comment.