Skip to content

Commit 1df410a

Browse files
authored
Merge pull request #9296 from viktorklang/wip-opportunistic-√
2 parents 8507cf0 + 0044904 commit 1df410a

File tree

3 files changed

+99
-27
lines changed

3 files changed

+99
-27
lines changed

build.sbt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,10 @@ val mimaFilterSettings = Seq {
505505
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.this"),
506506
ProblemFilters.exclude[ReversedAbstractMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.black"),
507507
ProblemFilters.exclude[ReversedAbstractMethodProblem]("scala.collection.immutable.RedBlackTree#Tree.red"),
508-
ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.TreeMap$TreeMapBuilder")
508+
ProblemFilters.exclude[MissingClassProblem]("scala.collection.immutable.TreeMap$TreeMapBuilder"),
509+
510+
// Escape hatch for getting Scala 2.13.0 behavior of the global ExecutionContext
511+
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.concurrent.ExecutionContext.opportunistic"),
509512
),
510513
}
511514

src/library/scala/concurrent/BlockContext.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
package scala.concurrent
1414

1515
/**
16-
* A context to be notified by `scala.concurrent.blocking` when
16+
* A context to be notified by [[scala.concurrent.blocking]] when
1717
* a thread is about to block. In effect this trait provides
18-
* the implementation for `scala.concurrent.Await`.
19-
* `scala.concurrent.Await.result()` and `scala.concurrent.Await.ready()`
18+
* the implementation for [[scala.concurrent.Await]].
19+
* [[scala.concurrent.Await.result]] and [[scala.concurrent.Await.ready]]
2020
* locates an instance of `BlockContext` by first looking for one
21-
* provided through `BlockContext.withBlockContext()` and failing that,
21+
* provided through [[BlockContext.withBlockContext]] and failing that,
2222
* checking whether `Thread.currentThread` is an instance of `BlockContext`.
2323
* So a thread pool can have its `java.lang.Thread` instances implement
2424
* `BlockContext`. There's a default `BlockContext` used if the thread

src/library/scala/concurrent/ExecutionContext.scala

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import scala.annotation.implicitNotFound
3232
* While it is possible to simply import
3333
* `scala.concurrent.ExecutionContext.Implicits.global` to obtain an
3434
* implicit `ExecutionContext`, application developers should carefully
35-
* consider where they want to set execution policy;
36-
* ideally, one place per applicationor per logically related section of code—
35+
* consider where they want to define the execution policy;
36+
* ideally, one place per applicationor per logically related section of code
3737
* will make a decision about which `ExecutionContext` to use.
3838
* That is, you will mostly want to avoid hardcoding, especially via an import,
3939
* `scala.concurrent.ExecutionContext.Implicits.global`.
@@ -57,12 +57,12 @@ import scala.annotation.implicitNotFound
5757
* knowing that only that library's network operations will be affected.
5858
* Application callback execution can be configured separately.
5959
*/
60-
@implicitNotFound("""Cannot find an implicit ExecutionContext. You might pass
60+
@implicitNotFound("""Cannot find an implicit ExecutionContext. You might add
6161
an (implicit ec: ExecutionContext) parameter to your method.
6262
6363
The ExecutionContext is used to configure how and on which
64-
thread pools Futures will run, so the specific ExecutionContext
65-
that is selected is important.
64+
thread pools asynchronous tasks (such as Futures) will run,
65+
so the specific ExecutionContext that is selected is important.
6666
6767
If your application does not define an ExecutionContext elsewhere,
6868
consider using Scala's global ExecutionContext by defining
@@ -121,23 +121,81 @@ trait ExecutionContextExecutorService extends ExecutionContextExecutor with Exec
121121
*/
122122
object ExecutionContext {
123123
/**
124-
* The explicit global `ExecutionContext`. Invoke `global` when you want to provide the global
125-
* `ExecutionContext` explicitly.
124+
* The global [[ExecutionContext]]. This default `ExecutionContext` implementation is backed by a work-stealing thread
125+
* pool. It can be configured via the following system properties:
126126
*
127-
* The default `ExecutionContext` implementation is backed by a work-stealing thread pool.
128-
* It can be configured via the following [[scala.sys.SystemProperties]]:
129-
*
130-
* `scala.concurrent.context.minThreads` = defaults to "1"
131-
* `scala.concurrent.context.numThreads` = defaults to "x1" (i.e. the current number of available processors * 1)
132-
* `scala.concurrent.context.maxThreads` = defaults to "x1" (i.e. the current number of available processors * 1)
133-
* `scala.concurrent.context.maxExtraThreads` = defaults to "256"
127+
* - `scala.concurrent.context.minThreads` = defaults to "1"
128+
* - `scala.concurrent.context.numThreads` = defaults to "x1" (i.e. the current number of available processors * 1)
129+
* - `scala.concurrent.context.maxThreads` = defaults to "x1" (i.e. the current number of available processors * 1)
130+
* - `scala.concurrent.context.maxExtraThreads` = defaults to "256"
134131
*
135132
* The pool size of threads is then `numThreads` bounded by `minThreads` on the lower end and `maxThreads` on the high end.
136133
*
137134
* The `maxExtraThreads` is the maximum number of extra threads to have at any given time to evade deadlock,
138-
* see [[scala.concurrent.BlockContext]].
135+
* see [[scala.concurrent.blocking]].
136+
*
137+
* The `global` execution context can be used explicitly, by defining an
138+
* `implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global`, or by importing
139+
* [[ExecutionContext.Implicits.global]].
140+
*
141+
* == Batching short-lived nested tasks ==
142+
*
143+
* Asynchronous code with short-lived nested tasks is executed more efficiently when using
144+
* `ExecutionContext.opportunistic` (continue reading to learn why it is `private[scala]` and how to access it).
145+
*
146+
* `ExecutionContext.opportunistic` uses the same thread pool as `ExecutionContext.global`. It attempts to batch
147+
* nested task and execute them on the same thread as the enclosing task. This is ideally suited to execute
148+
* short-lived tasks as it reduces the overhead of context switching.
149+
*
150+
* WARNING: long-running and/or blocking tasks should be demarcated within [[scala.concurrent.blocking]]-blocks
151+
* to ensure that any pending tasks in the current batch can be executed by another thread on `global`.
152+
*
153+
* === How to use ===
154+
*
155+
* This field is `private[scala]` to maintain binary compatibility. It was added in 2.13.4, code that references it
156+
* directly fails to run with a 2.13.0-3 Scala library.
157+
*
158+
* Libraries should not reference this field directly because users of the library might be using an earlier Scala
159+
* version. In order to use the batching `ExecutionContext` in a library, the code needs to fall back to `global`
160+
* in case the `opportunistic` field is missing (example below). The resulting `ExecutionContext` has batching
161+
* behavior in all Scala 2.13 versions (`global` is batching in 2.13.0-3).
162+
*
163+
* {{{
164+
* implicit val ec: scala.concurrent.ExecutionContext = try {
165+
* scala.concurrent.ExecutionContext.getClass
166+
* .getDeclaredMethod("opportunistic")
167+
* .invoke(scala.concurrent.ExecutionContext)
168+
* .asInstanceOf[scala.concurrent.ExecutionContext]
169+
* } catch {
170+
* case _: NoSuchMethodException =>
171+
* scala.concurrent.ExecutionContext.global
172+
* }
173+
* }}}
174+
*
175+
* Application authors can safely use the field because the Scala version at run time is the same as at compile time.
176+
* Options to bypass the access restriction include:
139177
*
140-
* @return the global `ExecutionContext`
178+
* 1. Using a structural type (example below). This uses reflection at run time.
179+
* 1. Writing a Scala `object` in the `scala` package (example below).
180+
* 1. Writing a Java source file. This works because `private[scala]` is emitted as `public` in Java bytecode.
181+
*
182+
* {{{
183+
* // Option 1
184+
* implicit val ec: scala.concurrent.ExecutionContext =
185+
* (scala.concurrent.ExecutionContext:
186+
* {def opportunistic: scala.concurrent.ExecutionContextExecutor}
187+
* ).opportunistic
188+
*
189+
* // Option 2
190+
* package scala {
191+
* object OpportunisticEC {
192+
* implicit val ec: scala.concurrent.ExecutionContext =
193+
* scala.concurrent.ExecutionContext.opportunistic
194+
* }
195+
* }
196+
* }}}
197+
*
198+
* @return the global [[ExecutionContext]]
141199
*/
142200
final lazy val global: ExecutionContextExecutor = impl.ExecutionContextImpl.fromExecutor(null: Executor)
143201

@@ -166,14 +224,25 @@ object ExecutionContext {
166224
override final def reportFailure(t: Throwable): Unit = defaultReporter(t)
167225
}
168226

227+
/**
228+
* See [[ExecutionContext.global]].
229+
*/
230+
private[scala] lazy val opportunistic: ExecutionContextExecutor = new ExecutionContextExecutor with BatchingExecutor {
231+
final override def submitForExecution(runnable: Runnable): Unit = global.execute(runnable)
232+
233+
final override def execute(runnable: Runnable): Unit =
234+
if ((!runnable.isInstanceOf[impl.Promise.Transformation[_,_]] || runnable.asInstanceOf[impl.Promise.Transformation[_,_]].benefitsFromBatching) && runnable.isInstanceOf[Batchable])
235+
submitAsyncBatched(runnable)
236+
else
237+
submitForExecution(runnable)
238+
239+
override final def reportFailure(t: Throwable): Unit = global.reportFailure(t)
240+
}
241+
169242
object Implicits {
170243
/**
171-
* The implicit global `ExecutionContext`. Import `global` when you want to provide the global
172-
* `ExecutionContext` implicitly.
173-
*
174-
* The default `ExecutionContext` implementation is backed by a work-stealing thread pool. By default,
175-
* the thread pool uses a target number of worker threads equal to the number of
176-
* [[https://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#availableProcessors-- available processors]].
244+
* An accessor that can be used to import the global `ExecutionContext` into the implicit scope,
245+
* see [[ExecutionContext.global]].
177246
*/
178247
implicit final def global: ExecutionContext = ExecutionContext.global
179248
}

0 commit comments

Comments
 (0)