Skip to content

Commit e9aeb8c

Browse files
authored
Merge pull request #3332 from armanbilge/feature/jvm-polling-system
Polling system
2 parents 9780973 + 58f695f commit e9aeb8c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1930
-246
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,16 @@ jobs:
4242
java: graalvm@11
4343
- os: windows-latest
4444
scala: 3.3.0
45+
ci: ciJVM
4546
- os: macos-latest
4647
scala: 3.3.0
48+
ci: ciJVM
4749
- os: windows-latest
4850
scala: 2.12.18
51+
ci: ciJVM
4952
- os: macos-latest
5053
scala: 2.12.18
54+
ci: ciJVM
5155
- ci: ciFirefox
5256
scala: 3.3.0
5357
- ci: ciChrome
@@ -97,9 +101,6 @@ jobs:
97101
- os: macos-latest
98102
ci: ciNative
99103
scala: 2.12.18
100-
- os: macos-latest
101-
ci: ciNative
102-
scala: 3.3.0
103104
- os: windows-latest
104105
java: graalvm@11
105106
runs-on: ${{ matrix.os }}

benchmarks/src/main/scala/cats/effect/benchmarks/WorkStealingBenchmark.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,13 @@ class WorkStealingBenchmark {
165165
(ExecutionContext.fromExecutor(executor), () => executor.shutdown())
166166
}
167167

168-
val compute = new WorkStealingThreadPool(
168+
val compute = new WorkStealingThreadPool[AnyRef](
169169
256,
170170
"io-compute",
171171
"io-blocker",
172172
60.seconds,
173173
false,
174+
SleepSystem,
174175
_.printStackTrace())
175176

176177
val cancelationCheckThreshold =

build.sbt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ ThisBuild / git.gitUncommittedChanges := {
4141
}
4242
}
4343

44-
ThisBuild / tlBaseVersion := "3.5"
44+
ThisBuild / tlBaseVersion := "3.6"
4545
ThisBuild / tlUntaggedAreSnapshots := false
4646

4747
ThisBuild / organization := "org.typelevel"
@@ -224,8 +224,8 @@ ThisBuild / githubWorkflowBuildMatrixExclusions := {
224224
val windowsAndMacScalaFilters =
225225
(ThisBuild / githubWorkflowScalaVersions).value.filterNot(Set(Scala213)).flatMap { scala =>
226226
Seq(
227-
MatrixExclude(Map("os" -> Windows, "scala" -> scala)),
228-
MatrixExclude(Map("os" -> MacOS, "scala" -> scala)))
227+
MatrixExclude(Map("os" -> Windows, "scala" -> scala, "ci" -> CI.JVM.command)),
228+
MatrixExclude(Map("os" -> MacOS, "scala" -> scala, "ci" -> CI.JVM.command)))
229229
}
230230

231231
val jsScalaFilters = for {
@@ -254,9 +254,7 @@ ThisBuild / githubWorkflowBuildMatrixExclusions := {
254254

255255
javaFilters ++ Seq(
256256
MatrixExclude(Map("os" -> Windows, "ci" -> ci)),
257-
MatrixExclude(Map("os" -> MacOS, "ci" -> ci, "scala" -> Scala212)),
258-
// keep a native+2.13+macos job
259-
MatrixExclude(Map("os" -> MacOS, "ci" -> ci, "scala" -> Scala3))
257+
MatrixExclude(Map("os" -> MacOS, "ci" -> ci, "scala" -> Scala212))
260258
)
261259
}
262260

@@ -640,7 +638,10 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
640638
"cats.effect.IOFiberConstants.ExecuteRunnableR"),
641639
ProblemFilters.exclude[ReversedMissingMethodProblem]("cats.effect.IOLocal.scope"),
642640
ProblemFilters.exclude[DirectMissingMethodProblem](
643-
"cats.effect.IOFiberConstants.ContStateResult")
641+
"cats.effect.IOFiberConstants.ContStateResult"),
642+
// introduced by #3332, polling system
643+
ProblemFilters.exclude[DirectMissingMethodProblem](
644+
"cats.effect.unsafe.IORuntimeBuilder.this")
644645
) ++ {
645646
if (tlIsScala3.value) {
646647
// Scala 3 specific exclusions
@@ -824,6 +825,14 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
824825
} else Seq()
825826
}
826827
)
828+
.nativeSettings(
829+
mimaBinaryIssueFilters ++= Seq(
830+
ProblemFilters.exclude[MissingClassProblem](
831+
"cats.effect.unsafe.PollingExecutorScheduler$SleepTask"),
832+
ProblemFilters.exclude[MissingClassProblem]("cats.effect.unsafe.QueueExecutorScheduler"),
833+
ProblemFilters.exclude[MissingClassProblem]("cats.effect.unsafe.QueueExecutorScheduler$")
834+
)
835+
)
827836
.disablePlugins(JCStressPlugin)
828837

829838
/**

core/js-native/src/main/scala/cats/effect/unsafe/WorkStealingThreadPool.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import scala.concurrent.duration.FiniteDuration
2323
// Can you imagine a thread pool on JS? Have fun trying to extend or instantiate
2424
// this class. Unfortunately, due to the explicit branching, this type leaks
2525
// into the shared source code of IOFiber.scala.
26-
private[effect] sealed abstract class WorkStealingThreadPool private ()
26+
private[effect] sealed abstract class WorkStealingThreadPool[P] private ()
2727
extends ExecutionContext {
2828
def execute(runnable: Runnable): Unit
2929
def reportFailure(cause: Throwable): Unit
@@ -38,12 +38,12 @@ private[effect] sealed abstract class WorkStealingThreadPool private ()
3838
private[effect] def canExecuteBlockingCode(): Boolean
3939
private[unsafe] def liveTraces(): (
4040
Map[Runnable, Trace],
41-
Map[WorkerThread, (Thread.State, Option[(Runnable, Trace)], Map[Runnable, Trace])],
41+
Map[WorkerThread[P], (Thread.State, Option[(Runnable, Trace)], Map[Runnable, Trace])],
4242
Map[Runnable, Trace])
4343
}
4444

45-
private[unsafe] sealed abstract class WorkerThread private () extends Thread {
46-
private[unsafe] def isOwnedBy(threadPool: WorkStealingThreadPool): Boolean
45+
private[unsafe] sealed abstract class WorkerThread[P] private () extends Thread {
46+
private[unsafe] def isOwnedBy(threadPool: WorkStealingThreadPool[_]): Boolean
4747
private[unsafe] def monitor(fiber: Runnable): WeakBag.Handle
4848
private[unsafe] def index: Int
4949
}

core/jvm-native/src/main/scala/cats/effect/unsafe/FiberMonitor.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import java.util.concurrent.ConcurrentLinkedQueue
4444
private[effect] sealed class FiberMonitor(
4545
// A reference to the compute pool of the `IORuntime` in which this suspended fiber bag
4646
// operates. `null` if the compute pool of the `IORuntime` is not a `WorkStealingThreadPool`.
47-
private[this] val compute: WorkStealingThreadPool
47+
private[this] val compute: WorkStealingThreadPool[_]
4848
) extends FiberMonitorShared {
4949

5050
private[this] final val BagReferences =
@@ -69,8 +69,8 @@ private[effect] sealed class FiberMonitor(
6969
*/
7070
def monitorSuspended(fiber: IOFiber[_]): WeakBag.Handle = {
7171
val thread = Thread.currentThread()
72-
if (thread.isInstanceOf[WorkerThread]) {
73-
val worker = thread.asInstanceOf[WorkerThread]
72+
if (thread.isInstanceOf[WorkerThread[_]]) {
73+
val worker = thread.asInstanceOf[WorkerThread[_]]
7474
// Guard against tracking errors when multiple work stealing thread pools exist.
7575
if (worker.isOwnedBy(compute)) {
7676
worker.monitor(fiber)
@@ -116,14 +116,14 @@ private[effect] sealed class FiberMonitor(
116116
val externalFibers = external.collect(justFibers)
117117
val suspendedFibers = suspended.collect(justFibers)
118118
val workersMapping: Map[
119-
WorkerThread,
119+
WorkerThread[_],
120120
(Thread.State, Option[(IOFiber[_], Trace)], Map[IOFiber[_], Trace])] =
121121
workers.map {
122122
case (thread, (state, opt, set)) =>
123123
val filteredOpt = opt.collect(justFibers)
124124
val filteredSet = set.collect(justFibers)
125125
(thread, (state, filteredOpt, filteredSet))
126-
}
126+
}.toMap
127127

128128
(externalFibers, workersMapping, suspendedFibers)
129129
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2020-2023 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cats.effect
18+
package unsafe
19+
20+
abstract class PollingSystem {
21+
22+
/**
23+
* The user-facing interface.
24+
*/
25+
type Api <: AnyRef
26+
27+
/**
28+
* The thread-local data structure used for polling.
29+
*/
30+
type Poller <: AnyRef
31+
32+
def close(): Unit
33+
34+
def makeApi(register: (Poller => Unit) => Unit): Api
35+
36+
def makePoller(): Poller
37+
38+
def closePoller(poller: Poller): Unit
39+
40+
/**
41+
* @param nanos
42+
* the maximum duration for which to block, where `nanos == -1` indicates to block
43+
* indefinitely.
44+
*
45+
* @return
46+
* whether any events were polled
47+
*/
48+
def poll(poller: Poller, nanos: Long, reportFailure: Throwable => Unit): Boolean
49+
50+
/**
51+
* @return
52+
* whether poll should be called again (i.e., there are more events to be polled)
53+
*/
54+
def needsPoll(poller: Poller): Boolean
55+
56+
def interrupt(targetThread: Thread, targetPoller: Poller): Unit
57+
58+
}
59+
60+
private object PollingSystem {
61+
type WithPoller[P] = PollingSystem {
62+
type Poller = P
63+
}
64+
}

core/jvm/src/main/scala/cats/effect/IOApp.scala

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ trait IOApp {
165165
*/
166166
protected def runtimeConfig: unsafe.IORuntimeConfig = unsafe.IORuntimeConfig()
167167

168+
protected def pollingSystem: unsafe.PollingSystem =
169+
unsafe.IORuntime.createDefaultPollingSystem()
170+
168171
/**
169172
* Controls the number of worker threads which will be allocated to the compute pool in the
170173
* underlying runtime. In general, this should be no ''greater'' than the number of physical
@@ -338,11 +341,12 @@ trait IOApp {
338341
import unsafe.IORuntime
339342

340343
val installed = IORuntime installGlobal {
341-
val (compute, compDown) =
344+
val (compute, poller, compDown) =
342345
IORuntime.createWorkStealingComputeThreadPool(
343346
threads = computeWorkerThreadCount,
344347
reportFailure = t => reportFailure(t).unsafeRunAndForgetWithoutCallback()(runtime),
345-
blockedThreadDetectionEnabled = blockedThreadDetectionEnabled
348+
blockedThreadDetectionEnabled = blockedThreadDetectionEnabled,
349+
pollingSystem = pollingSystem
346350
)
347351

348352
val (blocking, blockDown) =
@@ -352,6 +356,7 @@ trait IOApp {
352356
compute,
353357
blocking,
354358
compute,
359+
List(poller),
355360
{ () =>
356361
compDown()
357362
blockDown()

core/jvm/src/main/scala/cats/effect/IOCompanionPlatform.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,5 @@ private[effect] abstract class IOCompanionPlatform { this: IO.type =>
141141
*/
142142
def readLine: IO[String] =
143143
Console[IO].readLine
144+
144145
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2020-2023 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cats.effect
18+
19+
import java.nio.channels.SelectableChannel
20+
import java.nio.channels.spi.SelectorProvider
21+
22+
trait Selector {
23+
24+
/**
25+
* The [[java.nio.channels.spi.SelectorProvider]] that should be used to create
26+
* [[java.nio.channels.SelectableChannel]]s that are compatible with this polling system.
27+
*/
28+
def provider: SelectorProvider
29+
30+
/**
31+
* Fiber-block until a [[java.nio.channels.SelectableChannel]] is ready on at least one of the
32+
* designated operations. The returned value will indicate which operations are ready.
33+
*/
34+
def select(ch: SelectableChannel, ops: Int): IO[Int]
35+
36+
}

core/jvm/src/main/scala/cats/effect/unsafe/FiberMonitorCompanionPlatform.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import scala.concurrent.ExecutionContext
2222

2323
private[unsafe] trait FiberMonitorCompanionPlatform {
2424
def apply(compute: ExecutionContext): FiberMonitor = {
25-
if (TracingConstants.isStackTracing && compute.isInstanceOf[WorkStealingThreadPool]) {
26-
val wstp = compute.asInstanceOf[WorkStealingThreadPool]
25+
if (TracingConstants.isStackTracing && compute.isInstanceOf[WorkStealingThreadPool[_]]) {
26+
val wstp = compute.asInstanceOf[WorkStealingThreadPool[_]]
2727
new FiberMonitor(wstp)
2828
} else {
2929
new FiberMonitor(null)

0 commit comments

Comments
 (0)