From 267923214e56dcfed70285c4cf591f474b622b88 Mon Sep 17 00:00:00 2001 From: 05st Date: Tue, 4 Feb 2025 18:15:50 -0500 Subject: [PATCH 1/5] Copy over concurrency classes --- .../src/scala/concurrent/Awaitable.scala | 67 ++ .../scala/concurrent/BatchingExecutor.scala | 270 ++++++ .../src/scala/concurrent/BlockContext.scala | 110 +++ .../src/scala/concurrent/Channel.scala | 59 ++ .../src/scala/concurrent/DelayedLazyVal.scala | 46 + .../scala/concurrent/ExecutionContext.scala | 294 ++++++ .../src/scala/concurrent/Future.scala | 878 ++++++++++++++++++ .../scala/concurrent/JavaConversions.scala | 38 + .../src/scala/concurrent/Promise.scala | 148 +++ .../src/scala/concurrent/SyncChannel.scala | 77 ++ .../src/scala/concurrent/SyncVar.scala | 122 +++ .../scala/concurrent/duration/Deadline.scala | 85 ++ .../scala/concurrent/duration/Duration.scala | 742 +++++++++++++++ .../duration/DurationConversions.scala | 96 ++ .../scala/concurrent/duration/package.scala | 87 ++ .../impl/ExecutionContextImpl.scala | 137 +++ .../impl/FutureConvertersImpl.scala | 101 ++ .../src/scala/concurrent/impl/Promise.scala | 511 ++++++++++ .../src/scala/concurrent/package.scala | 204 ++++ 19 files changed, 4072 insertions(+) create mode 100644 scala2-library-cc/src/scala/concurrent/Awaitable.scala create mode 100644 scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala create mode 100644 scala2-library-cc/src/scala/concurrent/BlockContext.scala create mode 100644 scala2-library-cc/src/scala/concurrent/Channel.scala create mode 100644 scala2-library-cc/src/scala/concurrent/DelayedLazyVal.scala create mode 100644 scala2-library-cc/src/scala/concurrent/ExecutionContext.scala create mode 100644 scala2-library-cc/src/scala/concurrent/Future.scala create mode 100644 scala2-library-cc/src/scala/concurrent/JavaConversions.scala create mode 100644 scala2-library-cc/src/scala/concurrent/Promise.scala create mode 100644 scala2-library-cc/src/scala/concurrent/SyncChannel.scala create mode 100644 scala2-library-cc/src/scala/concurrent/SyncVar.scala create mode 100644 scala2-library-cc/src/scala/concurrent/duration/Deadline.scala create mode 100644 scala2-library-cc/src/scala/concurrent/duration/Duration.scala create mode 100644 scala2-library-cc/src/scala/concurrent/duration/DurationConversions.scala create mode 100644 scala2-library-cc/src/scala/concurrent/duration/package.scala create mode 100644 scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala create mode 100644 scala2-library-cc/src/scala/concurrent/impl/FutureConvertersImpl.scala create mode 100644 scala2-library-cc/src/scala/concurrent/impl/Promise.scala create mode 100644 scala2-library-cc/src/scala/concurrent/package.scala diff --git a/scala2-library-cc/src/scala/concurrent/Awaitable.scala b/scala2-library-cc/src/scala/concurrent/Awaitable.scala new file mode 100644 index 000000000000..634b9a91dafd --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/Awaitable.scala @@ -0,0 +1,67 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +import scala.concurrent.duration.Duration + + + +/** + * An object that may eventually be completed with a result value of type `T` which may be + * awaited using blocking methods. + * + * The [[Await]] object provides methods that allow accessing the result of an `Awaitable` + * by blocking the current thread until the `Awaitable` has been completed or a timeout has + * occurred. + */ +trait Awaitable[+T] { + + /** + * Await the "completed" state of this `Awaitable`. + * + * '''''This method should not be called directly; use [[Await.ready]] instead.''''' + * + * @param atMost + * maximum wait time, which may be negative (no waiting is done), + * [[scala.concurrent.duration.Duration.Inf Duration.Inf]] for unbounded waiting, or a finite positive + * duration + * @return this `Awaitable` + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws TimeoutException if after waiting for the specified time this `Awaitable` is still not ready + * @throws IllegalArgumentException if `atMost` is [[scala.concurrent.duration.Duration.Undefined Duration.Undefined]] + */ + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + def ready(atMost: Duration)(implicit permit: CanAwait): this.type + + /** + * Await and return the result (of type `T`) of this `Awaitable`. + * + * '''''This method should not be called directly; use [[Await.result]] instead.''''' + * + * @param atMost + * maximum wait time, which may be negative (no waiting is done), + * [[scala.concurrent.duration.Duration.Inf Duration.Inf]] for unbounded waiting, or a finite positive + * duration + * @return the result value if the `Awaitable` is completed within the specific maximum wait time + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws TimeoutException if after waiting for the specified time this `Awaitable` is still not ready + * @throws IllegalArgumentException if `atMost` is [[scala.concurrent.duration.Duration.Undefined Duration.Undefined]] + */ + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + def result(atMost: Duration)(implicit permit: CanAwait): T +} + + + diff --git a/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala b/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala new file mode 100644 index 000000000000..ac197c89f8c1 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala @@ -0,0 +1,270 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +import java.util.concurrent.Executor +import java.util.Objects +import scala.util.control.NonFatal +import scala.annotation.{switch, tailrec} + +/** + * Marker trait to indicate that a Runnable is Batchable by BatchingExecutors + */ +trait Batchable { + self: Runnable => +} + +private[concurrent] object BatchingExecutorStatics { + final val emptyBatchArray: Array[Runnable] = new Array[Runnable](0) + + // Max number of Runnables executed nested before starting to batch (to prevent stack exhaustion) + final val syncPreBatchDepth = 16 + + // Max number of Runnables processed in one go (to prevent starvation of other tasks on the pool) + final val runLimit = 1024 + + object MissingParentBlockContext extends BlockContext { + override def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = + try thunk finally throw new IllegalStateException("BUG in BatchingExecutor.Batch: parentBlockContext is null") + } +} + +/** + * Mixin trait for an Executor + * which groups multiple nested `Runnable.run()` calls + * into a single Runnable passed to the original + * Executor. This can be a useful optimization + * because it bypasses the original context's task + * queue and keeps related (nested) code on a single + * thread which may improve CPU affinity. However, + * if tasks passed to the Executor are blocking + * or expensive, this optimization can prevent work-stealing + * and make performance worse. + * A batching executor can create deadlocks if code does + * not use `scala.concurrent.blocking` when it should, + * because tasks created within other tasks will block + * on the outer task completing. + * This executor may run tasks in any order, including LIFO order. + * There are no ordering guarantees. + * + * WARNING: Only use *EITHER* `submitAsyncBatched` OR `submitSyncBatched`!! + * + * When you implement this trait for async executors like thread pools, + * you're going to need to implement it something like the following: + * + * {{{ + * final override def submitAsync(runnable: Runnable): Unit = + * super[SuperClass].execute(runnable) // To prevent reentrancy into `execute` + * + * final override def execute(runnable: Runnable): Unit = + * if (runnable.isInstanceOf[Batchable]) // Or other logic + * submitAsyncBatched(runnable) + * else + * submitAsync(runnable) + * + * final override def reportFailure(cause: Throwable): Unit = … + * }}} + * + * And if you want to implement if for a sync, trampolining, executor you're + * going to implement it something like this: + * + * {{{ + * final override def submitAsync(runnable: Runnable): Unit = () + * + * final override def execute(runnable: Runnable): Unit = + * submitSyncBatched(runnable) // You typically will want to batch everything + * + * final override def reportFailure(cause: Throwable): Unit = + * ExecutionContext.defaultReporter(cause) // Or choose something more fitting + * }}} + * + */ +private[concurrent] trait BatchingExecutor extends Executor { + private[this] final val _tasksLocal = new ThreadLocal[AnyRef]() + + /* + * Batch implements a LIFO queue (stack) and is used as a trampolining Runnable. + * In order to conserve allocations, the first element in the batch is stored "unboxed" in + * the `first` field. Subsequent Runnables are stored in the array called `other`. + */ + private[this] sealed abstract class AbstractBatch protected (protected final var first: Runnable, protected final var other: Array[Runnable], protected final var size: Int) { + + private[this] final def ensureCapacity(curSize: Int): Array[Runnable] = { + val curOther = this.other + val curLen = curOther.length + if (curSize <= curLen) curOther + else { + val newLen = if (curLen == 0) 4 else curLen << 1 + + if (newLen <= curLen) throw new StackOverflowError("Space limit of asynchronous stack reached: " + curLen) + val newOther = new Array[Runnable](newLen) + System.arraycopy(curOther, 0, newOther, 0, curLen) + this.other = newOther + newOther + } + } + + final def push(r: Runnable): Unit = { + val sz = this.size + if(sz == 0) + this.first = r + else + ensureCapacity(sz)(sz - 1) = r + this.size = sz + 1 + } + + @tailrec protected final def runN(n: Int): Unit = + if (n > 0) + (this.size: @switch) match { + case 0 => + case 1 => + val next = this.first + this.first = null + this.size = 0 + next.run() + runN(n - 1) + case sz => + val o = this.other + val next = o(sz - 2) + o(sz - 2) = null + this.size = sz - 1 + next.run() + runN(n - 1) + } + } + + private[this] final class AsyncBatch private(_first: Runnable, _other: Array[Runnable], _size: Int) extends AbstractBatch(_first, _other, _size) with Runnable with BlockContext with (BlockContext => Throwable) { + private[this] final var parentBlockContext: BlockContext = BatchingExecutorStatics.MissingParentBlockContext + + final def this(runnable: Runnable) = this(runnable, BatchingExecutorStatics.emptyBatchArray, 1) + + override final def run(): Unit = { + _tasksLocal.set(this) // This is later cleared in `apply` or `runWithoutResubmit` + + val f = resubmit(BlockContext.usingBlockContext(this)(this)) + + if (f != null) + throw f + } + + /* LOGIC FOR ASYNCHRONOUS BATCHES */ + override final def apply(prevBlockContext: BlockContext): Throwable = try { + parentBlockContext = prevBlockContext + runN(BatchingExecutorStatics.runLimit) + null + } catch { + case t: Throwable => t // We are handling exceptions on the outside of this method + } finally { + parentBlockContext = BatchingExecutorStatics.MissingParentBlockContext + _tasksLocal.remove() + } + + /* Attempts to resubmit this Batch to the underlying ExecutionContext, + * this only happens for Batches where `resubmitOnBlock` is `true`. + * Only attempt to resubmit when there are `Runnables` left to process. + * Note that `cause` can be `null`. + */ + private[this] final def resubmit(cause: Throwable): Throwable = + if (this.size > 0) { + try { submitForExecution(this); cause } catch { + case inner: Throwable => + if (NonFatal(inner)) { + val e = new ExecutionException("Non-fatal error occurred and resubmission failed, see suppressed exception.", cause) + e.addSuppressed(inner) + e + } else inner + } + } else cause // TODO: consider if NonFatals should simply be `reportFailure`:ed rather than rethrown + + private[this] final def cloneAndClear(): AsyncBatch = { + val newBatch = new AsyncBatch(this.first, this.other, this.size) + this.first = null + this.other = BatchingExecutorStatics.emptyBatchArray + this.size = 0 + newBatch + } + + override final def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = { + // If we know there will be blocking, we don't want to keep tasks queued up because it could deadlock. + if(this.size > 0) + submitForExecution(cloneAndClear()) // If this throws then we have bigger problems + + parentBlockContext.blockOn(thunk) // Now delegate the blocking to the previous BC + } + } + + private[this] final class SyncBatch(runnable: Runnable) extends AbstractBatch(runnable, BatchingExecutorStatics.emptyBatchArray, 1) with Runnable { + @tailrec override final def run(): Unit = { + try runN(BatchingExecutorStatics.runLimit) catch { + case ie: InterruptedException => + reportFailure(ie) // TODO: Handle InterruptedException differently? + case f if NonFatal(f) => + reportFailure(f) + } + + if (this.size > 0) + run() + } + } + + /** MUST throw a NullPointerException when `runnable` is null + * When implementing a sync BatchingExecutor, it is RECOMMENDED + * to implement this method as `runnable.run()` + */ + protected def submitForExecution(runnable: Runnable): Unit + + /** Reports that an asynchronous computation failed. + * See `ExecutionContext.reportFailure(throwable: Throwable)` + */ + protected def reportFailure(throwable: Throwable): Unit + + /** + * WARNING: Never use both `submitAsyncBatched` and `submitSyncBatched` in the same + * implementation of `BatchingExecutor` + */ + protected final def submitAsyncBatched(runnable: Runnable): Unit = { + val b = _tasksLocal.get + if (b.isInstanceOf[AsyncBatch]) b.asInstanceOf[AsyncBatch].push(runnable) + else submitForExecution(new AsyncBatch(runnable)) + } + + /** + * WARNING: Never use both `submitAsyncBatched` and `submitSyncBatched` in the same + * implementation of `BatchingExecutor` + */ + protected final def submitSyncBatched(runnable: Runnable): Unit = { + Objects.requireNonNull(runnable, "runnable is null") + val tl = _tasksLocal + val b = tl.get + if (b.isInstanceOf[SyncBatch]) b.asInstanceOf[SyncBatch].push(runnable) + else { + val i = if (b ne null) b.asInstanceOf[java.lang.Integer].intValue else 0 + if (i < BatchingExecutorStatics.syncPreBatchDepth) { + tl.set(java.lang.Integer.valueOf(i + 1)) + try submitForExecution(runnable) // User code so needs to be try-finally guarded here + catch { + case ie: InterruptedException => + reportFailure(ie) // TODO: Handle InterruptedException differently? + case f if NonFatal(f) => + reportFailure(f) + } + finally tl.set(b) + } else { + val batch = new SyncBatch(runnable) + tl.set(batch) + submitForExecution(batch) + tl.set(b) // Batch only throws fatals so no need for try-finally here + } + } + } +} diff --git a/scala2-library-cc/src/scala/concurrent/BlockContext.scala b/scala2-library-cc/src/scala/concurrent/BlockContext.scala new file mode 100644 index 000000000000..37483c307fd0 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/BlockContext.scala @@ -0,0 +1,110 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +/** + * A context to be notified by [[scala.concurrent.blocking]] when + * a thread is about to block. In effect this trait provides + * the implementation for [[scala.concurrent.Await]]. + * [[scala.concurrent.Await.result]] and [[scala.concurrent.Await.ready]] + * locates an instance of `BlockContext` by first looking for one + * provided through [[BlockContext.withBlockContext]] and failing that, + * checking whether `Thread.currentThread` is an instance of `BlockContext`. + * So a thread pool can have its `java.lang.Thread` instances implement + * `BlockContext`. There's a default `BlockContext` used if the thread + * doesn't implement `BlockContext`. + * + * Typically, you'll want to chain to the previous `BlockContext`, + * like this: + * {{{ + * val oldContext = BlockContext.current + * val myContext = new BlockContext { + * override def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = { + * // you'd have code here doing whatever you need to do + * // when the thread is about to block. + * // Then you'd chain to the previous context: + * oldContext.blockOn(thunk) + * } + * } + * BlockContext.withBlockContext(myContext) { + * // then this block runs with myContext as the handler + * // for scala.concurrent.blocking + * } + * }}} + */ +trait BlockContext { + + /** Used internally by the framework; + * Designates (and eventually executes) a thunk which potentially blocks the calling `java.lang.Thread`. + * + * Clients must use `scala.concurrent.blocking` or `scala.concurrent.Await` instead. + * + * In implementations of this method it is RECOMMENDED to first check if `permission` is `null` and + * if it is, throw an `IllegalArgumentException`. + * + * @throws IllegalArgumentException if the `permission` is `null` + */ + def blockOn[T](thunk: => T)(implicit permission: CanAwait): T +} + +object BlockContext { + private[this] object DefaultBlockContext extends BlockContext { + override final def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = thunk + } + + /** + * The default block context will execute the supplied thunk immediately. + * @return the `BlockContext` that will be used if no other is found. + **/ + final def defaultBlockContext: BlockContext = DefaultBlockContext + + private[this] final val contextLocal = new ThreadLocal[BlockContext]() + + private[this] final def prefer(candidate: BlockContext): BlockContext = + if (candidate ne null) candidate + else { + val t = Thread.currentThread + if (t.isInstanceOf[BlockContext]) t.asInstanceOf[BlockContext] + else DefaultBlockContext + } + + /** + * @return the `BlockContext` that would be used for the current `java.lang.Thread` at this point + **/ + final def current: BlockContext = prefer(contextLocal.get) + + /** + * Installs a current `BlockContext` around executing `body`. + **/ + final def withBlockContext[T](blockContext: BlockContext)(body: => T): T = { + val old = contextLocal.get // can be null + if (old eq blockContext) body + else { + contextLocal.set(blockContext) + try body finally contextLocal.set(old) + } + } + + /** + * Installs the BlockContext `blockContext` around the invocation to `f` and passes in the previously installed BlockContext to `f`. + * @return the value produced by applying `f` + **/ + final def usingBlockContext[I, T](blockContext: BlockContext)(f: BlockContext => T): T = { + val old = contextLocal.get // can be null + if (old eq blockContext) f(prefer(old)) + else { + contextLocal.set(blockContext) + try f(prefer(old)) finally contextLocal.set(old) + } + } +} diff --git a/scala2-library-cc/src/scala/concurrent/Channel.scala b/scala2-library-cc/src/scala/concurrent/Channel.scala new file mode 100644 index 000000000000..a9ada60e3da0 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/Channel.scala @@ -0,0 +1,59 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +/** This class provides a simple FIFO queue of data objects, + * which are read by one or more reader threads. + * + * @tparam A type of data exchanged + */ +@deprecated("Use `java.util.concurrent.LinkedTransferQueue` instead.", since = "2.13.0") +class Channel[A] { + private class LinkedList { + var elem: A = _ + var next: LinkedList = _ + } + private[this] var written = new LinkedList // FIFO queue, realized through + private[this] var lastWritten = written // aliasing of a linked list + private[this] var nreaders = 0 + + /** Append a value to the FIFO queue to be read by `read`. + * This operation is nonblocking and can be executed by any thread. + * + * @param x object to enqueue to this channel + */ + def write(x: A): Unit = synchronized { + lastWritten.elem = x + lastWritten.next = new LinkedList + lastWritten = lastWritten.next + if (nreaders > 0) notify() + } + + /** Retrieve the next waiting object from the FIFO queue, + * blocking if necessary until an object is available. + * + * @return next object dequeued from this channel + */ + def read: A = synchronized { + while (written.next == null) { + try { + nreaders += 1 + wait() + } + finally nreaders -= 1 + } + val x = written.elem + written = written.next + x + } +} diff --git a/scala2-library-cc/src/scala/concurrent/DelayedLazyVal.scala b/scala2-library-cc/src/scala/concurrent/DelayedLazyVal.scala new file mode 100644 index 000000000000..8d5e2c278027 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/DelayedLazyVal.scala @@ -0,0 +1,46 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +/** A `DelayedLazyVal` is a wrapper for lengthy computations which have a + * valid partially computed result. + * + * The first argument is a function for obtaining the result at any given + * point in time, and the second is the lengthy computation. Once the + * computation is complete, the `apply` method will stop recalculating it + * and return a fixed value from that point forward. + * + * @param f the function to obtain the current value at any point in time + * @param body the computation to run to completion in another thread + */ +@deprecated("`DelayedLazyVal` Will be removed in the future.", since = "2.13.0") +class DelayedLazyVal[T](f: () => T, body: => Unit)(implicit exec: ExecutionContext){ + @volatile private[this] var _isDone = false + private[this] lazy val complete = f() + + /** Whether the computation is complete. + * + * @return true if the computation is complete. + */ + def isDone: Boolean = _isDone + + /** The current result of f(), or the final result if complete. + * + * @return the current value + */ + def apply(): T = if (isDone) complete else f() + + exec.execute(() => { + body; _isDone = true + }) +} diff --git a/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala new file mode 100644 index 000000000000..b8fb0a6639f6 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala @@ -0,0 +1,294 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +import java.util.concurrent.{ ExecutorService, Executor } +import scala.annotation.implicitNotFound + +/** + * An `ExecutionContext` can execute program logic asynchronously, + * typically but not necessarily on a thread pool. + * + * A general purpose `ExecutionContext` must be asynchronous in executing + * any `Runnable` that is passed into its `execute`-method. A special purpose + * `ExecutionContext` may be synchronous but must only be passed to code that + * is explicitly safe to be run using a synchronously executing `ExecutionContext`. + * + * APIs such as `Future.onComplete` require you to provide a callback + * and an implicit `ExecutionContext`. The implicit `ExecutionContext` + * will be used to execute the callback. + * + * While it is possible to simply import + * `scala.concurrent.ExecutionContext.Implicits.global` to obtain an + * implicit `ExecutionContext`, application developers should carefully + * consider where they want to define the execution policy; + * ideally, one place per application — or per logically related section of code — + * will make a decision about which `ExecutionContext` to use. + * That is, you will mostly want to avoid hardcoding, especially via an import, + * `scala.concurrent.ExecutionContext.Implicits.global`. + * The recommended approach is to add `(implicit ec: ExecutionContext)` to methods, + * or class constructor parameters, which need an `ExecutionContext`. + * + * Then locally import a specific `ExecutionContext` in one place for the entire + * application or module, passing it implicitly to individual methods. + * Alternatively define a local implicit val with the required `ExecutionContext`. + * + * A custom `ExecutionContext` may be appropriate to execute code + * which blocks on IO or performs long-running computations. + * `ExecutionContext.fromExecutorService` and `ExecutionContext.fromExecutor` + * are good ways to create a custom `ExecutionContext`. + * + * The intent of `ExecutionContext` is to lexically scope code execution. + * That is, each method, class, file, package, or application determines + * how to run its own code. This avoids issues such as running + * application callbacks on a thread pool belonging to a networking library. + * The size of a networking library's thread pool can be safely configured, + * knowing that only that library's network operations will be affected. + * Application callback execution can be configured separately. + */ +@implicitNotFound("""Cannot find an implicit ExecutionContext. You might add +an (implicit ec: ExecutionContext) parameter to your method. + +The ExecutionContext is used to configure how and on which +thread pools asynchronous tasks (such as Futures) will run, +so the specific ExecutionContext that is selected is important. + +If your application does not define an ExecutionContext elsewhere, +consider using Scala's global ExecutionContext by defining +the following: + +implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global""") +trait ExecutionContext { + + /** Runs a block of code on this execution context. + * + * @param runnable the task to execute + */ + def execute(runnable: Runnable): Unit + + /** Reports that an asynchronous computation failed. + * + * @param cause the cause of the failure + */ + def reportFailure(@deprecatedName("t") cause: Throwable): Unit + + /** Prepares for the execution of a task. Returns the prepared + * execution context. The recommended implementation of + * `prepare` is to return `this`. + * + * This method should no longer be overridden or called. It was + * originally expected that `prepare` would be called by + * all libraries that consume ExecutionContexts, in order to + * capture thread local context. However, this usage has proven + * difficult to implement in practice and instead it is + * now better to avoid using `prepare` entirely. + * + * Instead, if an `ExecutionContext` needs to capture thread + * local context, it should capture that context when it is + * constructed, so that it doesn't need any additional + * preparation later. + */ + @deprecated("preparation of ExecutionContexts will be removed", "2.12.0") + // This cannot be removed until there is a suitable replacement + def prepare(): ExecutionContext = this +} + +/** + * An [[ExecutionContext]] that is also a + * Java [[java.util.concurrent.Executor Executor]]. + */ +trait ExecutionContextExecutor extends ExecutionContext with Executor + +/** + * An [[ExecutionContext]] that is also a + * Java [[java.util.concurrent.ExecutorService ExecutorService]]. + */ +trait ExecutionContextExecutorService extends ExecutionContextExecutor with ExecutorService + + +/** Contains factory methods for creating execution contexts. + */ +object ExecutionContext { + /** + * The global [[ExecutionContext]]. This default `ExecutionContext` implementation is backed by a work-stealing thread + * pool. It can be configured via the following system properties: + * + * - `scala.concurrent.context.minThreads` = defaults to "1" + * - `scala.concurrent.context.numThreads` = defaults to "x1" (i.e. the current number of available processors * 1) + * - `scala.concurrent.context.maxThreads` = defaults to "x1" (i.e. the current number of available processors * 1) + * - `scala.concurrent.context.maxExtraThreads` = defaults to "256" + * + * The pool size of threads is then `numThreads` bounded by `minThreads` on the lower end and `maxThreads` on the high end. + * + * The `maxExtraThreads` is the maximum number of extra threads to have at any given time to evade deadlock, + * see [[scala.concurrent.blocking]]. + * + * The `global` execution context can be used explicitly, by defining an + * `implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global`, or by importing + * [[ExecutionContext.Implicits.global]]. + * + * == Batching short-lived nested tasks == + * + * Asynchronous code with short-lived nested tasks is executed more efficiently when using + * `ExecutionContext.opportunistic` (continue reading to learn why it is `private[scala]` and how to access it). + * + * `ExecutionContext.opportunistic` uses the same thread pool as `ExecutionContext.global`. It attempts to batch + * nested task and execute them on the same thread as the enclosing task. This is ideally suited to execute + * short-lived tasks as it reduces the overhead of context switching. + * + * WARNING: long-running and/or blocking tasks should be demarcated within [[scala.concurrent.blocking]]-blocks + * to ensure that any pending tasks in the current batch can be executed by another thread on `global`. + * + * === How to use === + * + * This field is `private[scala]` to maintain binary compatibility. It was added in 2.13.4, code that references it + * directly fails to run with a 2.13.0-3 Scala library. + * + * Libraries should not reference this field directly because users of the library might be using an earlier Scala + * version. In order to use the batching `ExecutionContext` in a library, the code needs to fall back to `global` + * in case the `opportunistic` field is missing (example below). The resulting `ExecutionContext` has batching + * behavior in all Scala 2.13 versions (`global` is batching in 2.13.0-3). + * + * {{{ + * implicit val ec: scala.concurrent.ExecutionContext = try { + * scala.concurrent.ExecutionContext.getClass + * .getDeclaredMethod("opportunistic") + * .invoke(scala.concurrent.ExecutionContext) + * .asInstanceOf[scala.concurrent.ExecutionContext] + * } catch { + * case _: NoSuchMethodException => + * scala.concurrent.ExecutionContext.global + * } + * }}} + * + * Application authors can safely use the field because the Scala version at run time is the same as at compile time. + * Options to bypass the access restriction include: + * + * 1. Using a structural type (example below). This uses reflection at run time. + * 1. Writing a Scala `object` in the `scala` package (example below). + * 1. Writing a Java source file. This works because `private[scala]` is emitted as `public` in Java bytecode. + * + * {{{ + * // Option 1 + * implicit val ec: scala.concurrent.ExecutionContext = + * (scala.concurrent.ExecutionContext: + * {def opportunistic: scala.concurrent.ExecutionContextExecutor} + * ).opportunistic + * + * // Option 2 + * package scala { + * object OpportunisticEC { + * implicit val ec: scala.concurrent.ExecutionContext = + * scala.concurrent.ExecutionContext.opportunistic + * } + * } + * }}} + * + * @return the global [[ExecutionContext]] + */ + final lazy val global: ExecutionContextExecutor = impl.ExecutionContextImpl.fromExecutor(null: Executor) + + /** + * WARNING: Only ever execute logic which will quickly return control to the caller. + * + * This `ExecutionContext` steals execution time from other threads by having its + * `Runnable`s run on the `Thread` which calls `execute` and then yielding back control + * to the caller after *all* its `Runnable`s have been executed. + * Nested invocations of `execute` will be trampolined to prevent uncontrolled stack space growth. + * + * When using `parasitic` with abstractions such as `Future` it will in many cases be non-deterministic + * as to which `Thread` will be executing the logic, as it depends on when/if that `Future` is completed. + * + * Do *not* call any blocking code in the `Runnable`s submitted to this `ExecutionContext` + * as it will prevent progress by other enqueued `Runnable`s and the calling `Thread`. + * + * Symptoms of misuse of this `ExecutionContext` include, but are not limited to, deadlocks + * and severe performance problems. + * + * Any `NonFatal` or `InterruptedException`s will be reported to the `defaultReporter`. + */ + object parasitic extends ExecutionContextExecutor with BatchingExecutor { + override final def submitForExecution(runnable: Runnable): Unit = runnable.run() + override final def execute(runnable: Runnable): Unit = submitSyncBatched(runnable) + override final def reportFailure(t: Throwable): Unit = defaultReporter(t) + } + + /** + * See [[ExecutionContext.global]]. + */ + private[scala] lazy val opportunistic: ExecutionContextExecutor = new ExecutionContextExecutor with BatchingExecutor { + final override def submitForExecution(runnable: Runnable): Unit = global.execute(runnable) + + final override def execute(runnable: Runnable): Unit = + if ((!runnable.isInstanceOf[impl.Promise.Transformation[_,_]] || runnable.asInstanceOf[impl.Promise.Transformation[_,_]].benefitsFromBatching) && runnable.isInstanceOf[Batchable]) + submitAsyncBatched(runnable) + else + submitForExecution(runnable) + + override final def reportFailure(t: Throwable): Unit = global.reportFailure(t) + } + + object Implicits { + /** + * An accessor that can be used to import the global `ExecutionContext` into the implicit scope, + * see [[ExecutionContext.global]]. + */ + implicit final def global: ExecutionContext = ExecutionContext.global + } + + /** Creates an `ExecutionContext` from the given `ExecutorService`. + * + * @param e the `ExecutorService` to use. If `null`, a new `ExecutorService` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. + * @param reporter a function for error reporting + * @return the `ExecutionContext` using the given `ExecutorService` + */ + def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit): ExecutionContextExecutorService = + impl.ExecutionContextImpl.fromExecutorService(e, reporter) + + /** Creates an `ExecutionContext` from the given `ExecutorService` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. + * + * If it is guaranteed that none of the executed tasks are blocking, a single-threaded `ExecutorService` + * can be used to create an `ExecutionContext` as follows: + * + * {{{ + * import java.util.concurrent.Executors + * val ec = ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor()) + * }}} + * + * @param e the `ExecutorService` to use. If `null`, a new `ExecutorService` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. + * @return the `ExecutionContext` using the given `ExecutorService` + */ + def fromExecutorService(e: ExecutorService): ExecutionContextExecutorService = fromExecutorService(e, defaultReporter) + + /** Creates an `ExecutionContext` from the given `Executor`. + * + * @param e the `Executor` to use. If `null`, a new `Executor` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. + * @param reporter a function for error reporting + * @return the `ExecutionContext` using the given `Executor` + */ + def fromExecutor(e: Executor, reporter: Throwable => Unit): ExecutionContextExecutor = + impl.ExecutionContextImpl.fromExecutor(e, reporter) + + /** Creates an `ExecutionContext` from the given `Executor` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. + * + * @param e the `Executor` to use. If `null`, a new `Executor` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. + * @return the `ExecutionContext` using the given `Executor` + */ + def fromExecutor(e: Executor): ExecutionContextExecutor = fromExecutor(e, defaultReporter) + + /** The default reporter simply prints the stack trace of the `Throwable` to [[java.lang.System#err System.err]]. + * + * @return the function for error reporting + */ + final val defaultReporter: Throwable => Unit = _.printStackTrace() +} diff --git a/scala2-library-cc/src/scala/concurrent/Future.scala b/scala2-library-cc/src/scala/concurrent/Future.scala new file mode 100644 index 000000000000..371575918e1c --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/Future.scala @@ -0,0 +1,878 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.locks.LockSupport + +import scala.util.control.{NonFatal, NoStackTrace} +import scala.util.{Failure, Success, Try} +import scala.concurrent.duration._ +import scala.collection.BuildFrom +import scala.collection.mutable.{Builder, ArrayBuffer} +import scala.reflect.ClassTag + +import scala.concurrent.ExecutionContext.parasitic + +/** A `Future` represents a value which may or may not be currently available, + * but will be available at some point, or an exception if that value could not be made available. + * + * Asynchronous computations are created by calling `Future.apply`, which yields instances of `Future`. + * Computations are executed using an `ExecutionContext`, which is usually supplied implicitly, + * and which is commonly backed by a thread pool. + * + * {{{ + * import ExecutionContext.Implicits.global + * val s = "Hello" + * val f: Future[String] = Future { + * s + " future!" + * } + * f foreach { + * msg => println(msg) + * } + * }}} + * + * Note that the `global` context is convenient but restricted: + * "fatal" exceptions are reported only by printing a stack trace, + * and the underlying thread pool may be shared by a mix of jobs. + * For any nontrivial application, see the caveats explained at [[ExecutionContext]] + * and also the overview linked below, which explains + * [[https://docs.scala-lang.org/overviews/core/futures.html#exceptions exception handling]] + * in depth. + * + * + * @see [[https://docs.scala-lang.org/overviews/core/futures.html Futures and Promises]] + * + * @define multipleCallbacks + * Multiple callbacks may be registered; there is no guarantee that they will be + * executed in a particular order. + * + * @define caughtThrowables + * This future may contain a throwable object and this means that the future failed. + * Futures obtained through combinators have the same exception as the future they were obtained from. + * The following throwable objects are not contained in the future: + * - `Error` - fatal errors are not contained within futures + * - `InterruptedException` - not contained within futures + * - all `scala.util.control.ControlThrowable` except `NonLocalReturnControl` - not contained within futures + * + * Instead, the future is completed with an ExecutionException that has one of the exceptions above as its cause. + * If a future is failed with a `scala.runtime.NonLocalReturnControl`, + * it is completed with a value from that throwable instead. + * + * @define swallowsExceptions + * Since this method executes asynchronously and does not produce a return value, + * any non-fatal exceptions thrown will be reported to the `ExecutionContext`. + * + * @define nonDeterministic + * Note: using this method yields nondeterministic dataflow programs. + * + * @define forComprehensionExamples + * Example: + * + * {{{ + * val f = Future { 5 } + * val g = Future { 3 } + * val h = for { + * x: Int <- f // returns Future(5) + * y: Int <- g // returns Future(3) + * } yield x + y + * }}} + * + * is translated to: + * + * {{{ + * f flatMap { (x: Int) => g map { (y: Int) => x + y } } + * }}} + * + * @define callbackInContext + * The provided callback always runs in the provided implicit + *`ExecutionContext`, though there is no guarantee that the + * `execute()` method on the `ExecutionContext` will be called once + * per callback or that `execute()` will be called in the current + * thread. That is, the implementation may run multiple callbacks + * in a batch within a single `execute()` and it may run + * `execute()` either immediately or asynchronously. + * Completion of the Future must *happen-before* the invocation of the callback. + */ +trait Future[+T] extends Awaitable[T] { + + /* Callbacks */ + + /** When this future is completed, either through an exception, or a value, + * apply the provided function. + * + * If the future has already been completed, + * this will either be applied immediately or be scheduled asynchronously. + * + * Note that the returned value of `f` will be discarded. + * + * $swallowsExceptions + * $multipleCallbacks + * $callbackInContext + * + * @tparam U only used to accept any return type of the given callback function + * @param f the function to be executed when this `Future` completes + * @group Callbacks + */ + def onComplete[U](f: Try[T] => U)(implicit executor: ExecutionContext): Unit + + + /* Miscellaneous */ + + /** Returns whether the future had already been completed with + * a value or an exception. + * + * $nonDeterministic + * + * @return `true` if the future was completed, `false` otherwise + * @group Polling + */ + def isCompleted: Boolean + + /** The current value of this `Future`. + * + * $nonDeterministic + * + * If the future was not completed the returned value will be `None`. + * If the future was completed the value will be `Some(Success(t))` + * if it contained a valid result, or `Some(Failure(error))` if it contained + * an exception. + * + * @return `None` if the `Future` wasn't completed, `Some` if it was. + * @group Polling + */ + def value: Option[Try[T]] + + + /* Projections */ + + /** The returned `Future` will be successfully completed with the `Throwable` of the original `Future` + * if the original `Future` fails. + * + * If the original `Future` is successful, the returned `Future` is failed with a `NoSuchElementException`. + * + * $caughtThrowables + * + * @return a failed projection of this `Future`. + * @group Transformations + */ + def failed: Future[Throwable] = transform(Future.failedFun)(parasitic) + + + /* Monadic operations */ + + /** Asynchronously processes the value in the future once the value becomes available. + * + * WARNING: Will not be called if this future is never completed or if it is completed with a failure. + * + * $swallowsExceptions + * + * @tparam U only used to accept any return type of the given callback function + * @param f the function which will be executed if this `Future` completes with a result, + * the return value of `f` will be discarded. + * @group Callbacks + */ + def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = onComplete { _ foreach f } + + /** Creates a new future by applying the 's' function to the successful result of + * this future, or the 'f' function to the failed result. If there is any non-fatal + * exception thrown when 's' or 'f' is applied, that exception will be propagated + * to the resulting future. + * + * @tparam S the type of the returned `Future` + * @param s function that transforms a successful result of the receiver into a successful result of the returned future + * @param f function that transforms a failure of the receiver into a failure of the returned future + * @return a `Future` that will be completed with the transformed value + * @group Transformations + */ + def transform[S](s: T => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = + transform { + t => + if (t.isInstanceOf[Success[T]]) t map s + else throw f(t.asInstanceOf[Failure[T]].exception) // will throw fatal errors! + } + + /** Creates a new Future by applying the specified function to the result + * of this Future. If there is any non-fatal exception thrown when 'f' + * is applied then that exception will be propagated to the resulting future. + * + * @tparam S the type of the returned `Future` + * @param f function that transforms the result of this future + * @return a `Future` that will be completed with the transformed value + * @group Transformations + */ + def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] + + /** Creates a new Future by applying the specified function, which produces a Future, to the result + * of this Future. If there is any non-fatal exception thrown when 'f' + * is applied then that exception will be propagated to the resulting future. + * + * @tparam S the type of the returned `Future` + * @param f function that transforms the result of this future + * @return a `Future` that will be completed with the transformed value + * @group Transformations + */ + def transformWith[S](f: Try[T] => Future[S])(implicit executor: ExecutionContext): Future[S] + + + /** Creates a new future by applying a function to the successful result of + * this future. If this future is completed with an exception then the new + * future will also contain this exception. + * + * Example: + * + * {{{ + * val f = Future { "The future" } + * val g = f map { x: String => x + " is now!" } + * }}} + * + * Note that a for comprehension involving a `Future` + * may expand to include a call to `map` and or `flatMap` + * and `withFilter`. See [[scala.concurrent.Future#flatMap]] for an example of such a comprehension. + * + * + * @tparam S the type of the returned `Future` + * @param f the function which will be applied to the successful result of this `Future` + * @return a `Future` which will be completed with the result of the application of the function + * @group Transformations + */ + def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = transform(_ map f) + + /** Creates a new future by applying a function to the successful result of + * this future, and returns the result of the function as the new future. + * If this future is completed with an exception then the new future will + * also contain this exception. + * + * $forComprehensionExamples + * + * @tparam S the type of the returned `Future` + * @param f the function which will be applied to the successful result of this `Future` + * @return a `Future` which will be completed with the result of the application of the function + * @group Transformations + */ + def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = transformWith { + t => + if(t.isInstanceOf[Success[T]]) f(t.asInstanceOf[Success[T]].value) + else this.asInstanceOf[Future[S]] // Safe cast + } + + /** Creates a new future with one level of nesting flattened, this method is equivalent + * to `flatMap(identity)`. + * + * @tparam S the type of the returned `Future` + * @group Transformations + */ + def flatten[S](implicit ev: T <:< Future[S]): Future[S] = flatMap(ev)(parasitic) + + /** Creates a new future by filtering the value of the current future with a predicate. + * + * If the current future contains a value which satisfies the predicate, the new future will also hold that value. + * Otherwise, the resulting future will fail with a `NoSuchElementException`. + * + * If the current future fails, then the resulting future also fails. + * + * Example: + * {{{ + * val f = Future { 5 } + * val g = f filter { _ % 2 == 1 } + * val h = f filter { _ % 2 == 0 } + * g foreach println // Eventually prints 5 + * Await.result(h, Duration.Zero) // throw a NoSuchElementException + * }}} + * + * @param p the predicate to apply to the successful result of this `Future` + * @return a `Future` which will hold the successful result of this `Future` if it matches the predicate or a `NoSuchElementException` + * @group Transformations + */ + def filter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = + transform { + t => + if (t.isInstanceOf[Success[T]]) { + if (p(t.asInstanceOf[Success[T]].value)) t + else Future.filterFailure + } else t + } + + /** Used by for-comprehensions. + * @group Transformations + */ + final def withFilter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = filter(p)(executor) + + /** Creates a new future by mapping the value of the current future, if the given partial function is defined at that value. + * + * If the current future contains a value for which the partial function is defined, the new future will also hold that value. + * Otherwise, the resulting future will fail with a `NoSuchElementException`. + * + * If the current future fails, then the resulting future also fails. + * + * Example: + * {{{ + * val f = Future { -5 } + * val g = f collect { + * case x if x < 0 => -x + * } + * val h = f collect { + * case x if x > 0 => x * 2 + * } + * g foreach println // Eventually prints 5 + * Await.result(h, Duration.Zero) // throw a NoSuchElementException + * }}} + * + * @tparam S the type of the returned `Future` + * @param pf the `PartialFunction` to apply to the successful result of this `Future` + * @return a `Future` holding the result of application of the `PartialFunction` or a `NoSuchElementException` + * @group Transformations + */ + def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = + transform { + t => + if (t.isInstanceOf[Success[T]]) + Success(pf.applyOrElse(t.asInstanceOf[Success[T]].value, Future.collectFailed)) + else t.asInstanceOf[Failure[S]] + } + + /** Creates a new future that will handle any matching throwable that this + * future might contain. If there is no match, or if this future contains + * a valid result then the new future will contain the same. + * + * Example: + * + * {{{ + * Future (6 / 0) recover { case e: ArithmeticException => 0 } // result: 0 + * Future (6 / 0) recover { case e: NotFoundException => 0 } // result: exception + * Future (6 / 2) recover { case e: ArithmeticException => 0 } // result: 3 + * }}} + * + * @tparam U the type of the returned `Future` + * @param pf the `PartialFunction` to apply if this `Future` fails + * @return a `Future` with the successful value of this `Future` or the result of the `PartialFunction` + * @group Transformations + */ + def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = + transform { _ recover pf } + + /** Creates a new future that will handle any matching throwable that this + * future might contain by assigning it a value of another future. + * + * If there is no match, or if this future contains + * a valid result then the new future will contain the same result. + * + * Example: + * + * {{{ + * val f = Future { Int.MaxValue } + * Future (6 / 0) recoverWith { case e: ArithmeticException => f } // result: Int.MaxValue + * }}} + * + * @tparam U the type of the returned `Future` + * @param pf the `PartialFunction` to apply if this `Future` fails + * @return a `Future` with the successful value of this `Future` or the outcome of the `Future` returned by the `PartialFunction` + * @group Transformations + */ + def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = + transformWith { + t => + if (t.isInstanceOf[Failure[T]]) { + val result = pf.applyOrElse(t.asInstanceOf[Failure[T]].exception, Future.recoverWithFailed) + if (result ne Future.recoverWithFailedMarker) result + else this + } else this + } + + /** Zips the values of `this` and `that` future, and creates + * a new future holding the tuple of their results. + * + * If either input future fails, the resulting future is failed with the same + * throwable, without waiting for the other input future to complete. + * + * If the application of `f` throws a non-fatal throwable, the resulting future + * is failed with that throwable. + * + * @tparam U the type of the other `Future` + * @param that the other `Future` + * @return a `Future` with the results of both futures or the failure of the first of them that failed + * @group Transformations + */ + def zip[U](that: Future[U]): Future[(T, U)] = + zipWith(that)(Future.zipWithTuple2Fun)(parasitic) + + /** Zips the values of `this` and `that` future using a function `f`, + * and creates a new future holding the result. + * + * If either input future fails, the resulting future is failed with the same + * throwable, without waiting for the other input future to complete. + * + * If the application of `f` throws a non-fatal throwable, the resulting future + * is failed with that throwable. + * + * @tparam U the type of the other `Future` + * @tparam R the type of the resulting `Future` + * @param that the other `Future` + * @param f the function to apply to the results of `this` and `that` + * @return a `Future` with the result of the application of `f` to the results of `this` and `that` + * @group Transformations + */ + def zipWith[U, R](that: Future[U])(f: (T, U) => R)(implicit executor: ExecutionContext): Future[R] = { + // This is typically overriden by the implementation in DefaultPromise, which provides + // symmetric fail-fast behavior regardless of which future fails first. + // + // TODO: remove this implementation and make Future#zipWith abstract + // when we're next willing to make a binary incompatible change + flatMap(r1 => that.map(r2 => f(r1, r2)))(if (executor.isInstanceOf[BatchingExecutor]) executor else parasitic) + } + + /** Creates a new future which holds the result of this future if it was completed successfully, or, if not, + * the result of the `that` future if `that` is completed successfully. + * If both futures are failed, the resulting future holds the throwable object of the first future. + * + * Using this method will not cause concurrent programs to become nondeterministic. + * + * Example: + * {{{ + * val f = Future { throw new RuntimeException("failed") } + * val g = Future { 5 } + * val h = f fallbackTo g + * h foreach println // Eventually prints 5 + * }}} + * + * @tparam U the type of the other `Future` and the resulting `Future` + * @param that the `Future` whose result we want to use if this `Future` fails. + * @return a `Future` with the successful result of this or that `Future` or the failure of this `Future` if both fail + * @group Transformations + */ + def fallbackTo[U >: T](that: Future[U]): Future[U] = + if (this eq that) this + else { + implicit val ec = parasitic + transformWith { + t => + if (t.isInstanceOf[Success[T]]) this + else that transform { tt => if (tt.isInstanceOf[Success[U]]) tt else t } + } + } + + /** Creates a new `Future[S]` which is completed with this `Future`'s result if + * that conforms to `S`'s erased type or a `ClassCastException` otherwise. + * + * @tparam S the type of the returned `Future` + * @param tag the `ClassTag` which will be used to cast the result of this `Future` + * @return a `Future` holding the casted result of this `Future` or a `ClassCastException` otherwise + * @group Transformations + */ + def mapTo[S](implicit tag: ClassTag[S]): Future[S] = { + implicit val ec = parasitic + val boxedClass = { + val c = tag.runtimeClass + if (c.isPrimitive) Future.toBoxed(c) else c + } + require(boxedClass ne null) + map(s => boxedClass.cast(s).asInstanceOf[S]) + } + + /** Applies the side-effecting function to the result of this future, and returns + * a new future with the result of this future. + * + * This method allows one to enforce that the callbacks are executed in a + * specified order. + * + * Note that if one of the chained `andThen` callbacks throws + * an exception, that exception is not propagated to the subsequent `andThen` + * callbacks. Instead, the subsequent `andThen` callbacks are given the original + * value of this future. + * + * The following example prints out `5`: + * + * {{{ + * val f = Future { 5 } + * f andThen { + * case r => throw new RuntimeException("runtime exception") + * } andThen { + * case Failure(t) => println(t) + * case Success(v) => println(v) + * } + * }}} + * + * $swallowsExceptions + * + * @tparam U only used to accept any return type of the given `PartialFunction` + * @param pf a `PartialFunction` which will be conditionally applied to the outcome of this `Future` + * @return a `Future` which will be completed with the exact same outcome as this `Future` but after the `PartialFunction` has been executed. + * @group Callbacks + */ + def andThen[U](pf: PartialFunction[Try[T], U])(implicit executor: ExecutionContext): Future[T] = + transform { + result => + try pf.applyOrElse[Try[T], Any](result, Future.id[Try[T]]) + catch { case t if NonFatal(t) => executor.reportFailure(t) } + // TODO: use `finally`? + result + } +} + + + +/** Future companion object. + * + * @define nonDeterministic + * Note: using this method yields nondeterministic dataflow programs. + */ +object Future { + + /** + * Utilities, hoisted functions, etc. + */ + + private[concurrent] final val toBoxed = Map[Class[_], Class[_]]( + classOf[Boolean] -> classOf[java.lang.Boolean], + classOf[Byte] -> classOf[java.lang.Byte], + classOf[Char] -> classOf[java.lang.Character], + classOf[Short] -> classOf[java.lang.Short], + classOf[Int] -> classOf[java.lang.Integer], + classOf[Long] -> classOf[java.lang.Long], + classOf[Float] -> classOf[java.lang.Float], + classOf[Double] -> classOf[java.lang.Double], + classOf[Unit] -> classOf[scala.runtime.BoxedUnit] + ) + + private[this] final val _cachedId: AnyRef => AnyRef = Predef.identity _ + + private[concurrent] final def id[T]: T => T = _cachedId.asInstanceOf[T => T] + + private[concurrent] final val collectFailed = + (t: Any) => throw new NoSuchElementException("Future.collect partial function is not defined at: " + t) with NoStackTrace + + private[concurrent] final val filterFailure = + Failure[Nothing](new NoSuchElementException("Future.filter predicate is not satisfied") with NoStackTrace) + + private[this] final val failedFailure = + Failure[Nothing](new NoSuchElementException("Future.failed not completed with a throwable.") with NoStackTrace) + + private[concurrent] final val failedFailureFuture: Future[Nothing] = + scala.concurrent.Future.fromTry(failedFailure) + + private[this] final val _failedFun: Try[Any] => Try[Throwable] = + v => if (v.isInstanceOf[Failure[Any]]) Success(v.asInstanceOf[Failure[Any]].exception) else failedFailure + + private[concurrent] final def failedFun[T]: Try[T] => Try[Throwable] = _failedFun.asInstanceOf[Try[T] => Try[Throwable]] + + private[concurrent] final val recoverWithFailedMarker: Future[Nothing] = + scala.concurrent.Future.failed(new Throwable with NoStackTrace) + + private[concurrent] final val recoverWithFailed = (t: Throwable) => recoverWithFailedMarker + + private[this] final val _zipWithTuple2: (Any, Any) => (Any, Any) = Tuple2.apply _ + private[concurrent] final def zipWithTuple2Fun[T,U] = _zipWithTuple2.asInstanceOf[(T,U) => (T,U)] + + private[this] final val _addToBuilderFun: (Builder[Any, Nothing], Any) => Builder[Any, Nothing] = (b: Builder[Any, Nothing], e: Any) => b += e + private[concurrent] final def addToBuilderFun[A, M] = _addToBuilderFun.asInstanceOf[Function2[Builder[A, M], A, Builder[A, M]]] + + /** A Future which is never completed. + */ + object never extends Future[Nothing] { + + @throws[TimeoutException] + @throws[InterruptedException] + override final def ready(atMost: Duration)(implicit permit: CanAwait): this.type = { + import Duration.{Undefined, Inf, MinusInf} + atMost match { + case u if u eq Undefined => throw new IllegalArgumentException("cannot wait for Undefined period") + case `Inf` => + while(!Thread.interrupted()) { + LockSupport.park(this) + } + throw new InterruptedException + case `MinusInf` => // Drop out + case f: FiniteDuration if f > Duration.Zero => + var now = System.nanoTime() + val deadline = now + f.toNanos + while((deadline - now) > 0) { + LockSupport.parkNanos(this, deadline - now) + if (Thread.interrupted()) + throw new InterruptedException + now = System.nanoTime() + } + // Done waiting, drop out + case _: FiniteDuration => // Drop out if 0 or less + case x: Duration.Infinite => throw new MatchError(x) + } + throw new TimeoutException(s"Future timed out after [$atMost]") + } + + @throws[TimeoutException] + @throws[InterruptedException] + override final def result(atMost: Duration)(implicit permit: CanAwait): Nothing = { + ready(atMost) + throw new TimeoutException(s"Future timed out after [$atMost]") + } + + override final def onComplete[U](f: Try[Nothing] => U)(implicit executor: ExecutionContext): Unit = () + override final def isCompleted: Boolean = false + override final def value: Option[Try[Nothing]] = None + override final def failed: Future[Throwable] = this + override final def foreach[U](f: Nothing => U)(implicit executor: ExecutionContext): Unit = () + override final def transform[S](s: Nothing => S, f: Throwable => Throwable)(implicit executor: ExecutionContext): Future[S] = this + override final def transform[S](f: Try[Nothing] => Try[S])(implicit executor: ExecutionContext): Future[S] = this + override final def transformWith[S](f: Try[Nothing] => Future[S])(implicit executor: ExecutionContext): Future[S] = this + override final def map[S](f: Nothing => S)(implicit executor: ExecutionContext): Future[S] = this + override final def flatMap[S](f: Nothing => Future[S])(implicit executor: ExecutionContext): Future[S] = this + override final def flatten[S](implicit ev: Nothing <:< Future[S]): Future[S] = this + override final def filter(p: Nothing => Boolean)(implicit executor: ExecutionContext): Future[Nothing] = this + override final def collect[S](pf: PartialFunction[Nothing, S])(implicit executor: ExecutionContext): Future[S] = this + override final def recover[U >: Nothing](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = this + override final def recoverWith[U >: Nothing](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = this + override final def zip[U](that: Future[U]): Future[(Nothing, U)] = this + override final def zipWith[U, R](that: Future[U])(f: (Nothing, U) => R)(implicit executor: ExecutionContext): Future[R] = this + override final def fallbackTo[U >: Nothing](that: Future[U]): Future[U] = this + override final def mapTo[S](implicit tag: ClassTag[S]): Future[S] = this + override final def andThen[U](pf: PartialFunction[Try[Nothing], U])(implicit executor: ExecutionContext): Future[Nothing] = this + override final def toString: String = "Future()" + } + + /** A Future which is completed with the Unit value. + */ + final val unit: Future[Unit] = fromTry(Success(())) + + /** Creates an already completed Future with the specified exception. + * + * @tparam T the type of the value in the future + * @param exception the non-null instance of `Throwable` + * @return the newly created `Future` instance + */ + final def failed[T](exception: Throwable): Future[T] = Promise.failed(exception).future + + /** Creates an already completed Future with the specified result. + * + * @tparam T the type of the value in the future + * @param result the given successful value + * @return the newly created `Future` instance + */ + final def successful[T](result: T): Future[T] = Promise.successful(result).future + + /** Creates an already completed Future with the specified result or exception. + * + * @tparam T the type of the value in the `Future` + * @param result the result of the returned `Future` instance + * @return the newly created `Future` instance + */ + final def fromTry[T](result: Try[T]): Future[T] = Promise.fromTry(result).future + + /** Starts an asynchronous computation and returns a `Future` instance with the result of that computation. + * + * The following expressions are equivalent: + * + * {{{ + * val f1 = Future(expr) + * val f2 = Future.unit.map(_ => expr) + * val f3 = Future.unit.transform(_ => Success(expr)) + * }}} + * + * The result becomes available once the asynchronous computation is completed. + * + * @tparam T the type of the result + * @param body the asynchronous computation + * @param executor the execution context on which the future is run + * @return the `Future` holding the result of the computation + */ + final def apply[T](body: => T)(implicit executor: ExecutionContext): Future[T] = + unit.map(_ => body) + + /** Starts an asynchronous computation and returns a `Future` instance with the result of that computation once it completes. + * + * The following expressions are semantically equivalent: + * + * {{{ + * val f1 = Future(expr).flatten + * val f2 = Future.delegate(expr) + * val f3 = Future.unit.flatMap(_ => expr) + * }}} + * + * The result becomes available once the resulting Future of the asynchronous computation is completed. + * + * @tparam T the type of the result + * @param body the asynchronous computation, returning a Future + * @param executor the execution context on which the `body` is evaluated in + * @return the `Future` holding the result of the computation + */ + final def delegate[T](body: => Future[T])(implicit executor: ExecutionContext): Future[T] = + unit.flatMap(_ => body) + + /** Simple version of `Future.traverse`. Asynchronously and non-blockingly transforms, in essence, a `IterableOnce[Future[A]]` + * into a `Future[IterableOnce[A]]`. Useful for reducing many `Future`s into a single `Future`. + * + * @tparam A the type of the value inside the Futures + * @tparam CC the type of the `IterableOnce` of Futures + * @tparam To the type of the resulting collection + * @param in the `IterableOnce` of Futures which will be sequenced + * @return the `Future` of the resulting collection + */ + final def sequence[A, CC[X] <: IterableOnce[X], To](in: CC[Future[A]])(implicit bf: BuildFrom[CC[Future[A]], A, To], executor: ExecutionContext): Future[To] = + in.iterator.foldLeft(successful(bf.newBuilder(in))) { + (fr, fa) => fr.zipWith(fa)(Future.addToBuilderFun) + }.map(_.result())(if (executor.isInstanceOf[BatchingExecutor]) executor else parasitic) + + /** Asynchronously and non-blockingly returns a new `Future` to the result of the first future + * in the list that is completed. This means no matter if it is completed as a success or as a failure. + * + * @tparam T the type of the value in the future + * @param futures the `IterableOnce` of Futures in which to find the first completed + * @return the `Future` holding the result of the future that is first to be completed + */ + final def firstCompletedOf[T](futures: IterableOnce[Future[T]])(implicit executor: ExecutionContext): Future[T] = { + val i = futures.iterator + if (!i.hasNext) Future.never + else { + val p = Promise[T]() + val firstCompleteHandler = new AtomicReference[Promise[T]](p) with (Try[T] => Unit) { + override final def apply(v1: Try[T]): Unit = { + val r = getAndSet(null) + if (r ne null) + r tryComplete v1 // tryComplete is likely to be cheaper than complete + } + } + while(i.hasNext && firstCompleteHandler.get != null) // exit early if possible + i.next().onComplete(firstCompleteHandler) + p.future + } + } + + /** Asynchronously and non-blockingly returns a `Future` that will hold the optional result + * of the first `Future` with a result that matches the predicate, failed `Future`s will be ignored. + * + * @tparam T the type of the value in the future + * @param futures the `scala.collection.immutable.Iterable` of Futures to search + * @param p the predicate which indicates if it's a match + * @return the `Future` holding the optional result of the search + */ + final def find[T](futures: scala.collection.immutable.Iterable[Future[T]])(p: T => Boolean)(implicit executor: ExecutionContext): Future[Option[T]] = { + def searchNext(i: Iterator[Future[T]]): Future[Option[T]] = + if (!i.hasNext) successful(None) + else i.next().transformWith { + case Success(r) if p(r) => successful(Some(r)) + case _ => searchNext(i) + } + + searchNext(futures.iterator) + } + + /** A non-blocking, asynchronous left fold over the specified futures, + * with the start value of the given zero. + * The fold is performed asynchronously in left-to-right order as the futures become completed. + * The result will be the first failure of any of the futures, or any failure in the actual fold, + * or the result of the fold. + * + * Example: + * {{{ + * val futureSum = Future.foldLeft(futures)(0)(_ + _) + * }}} + * + * @tparam T the type of the value of the input Futures + * @tparam R the type of the value of the returned `Future` + * @param futures the `scala.collection.immutable.Iterable` of Futures to be folded + * @param zero the start value of the fold + * @param op the fold operation to be applied to the zero and futures + * @return the `Future` holding the result of the fold + */ + final def foldLeft[T, R](futures: scala.collection.immutable.Iterable[Future[T]])(zero: R)(op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = + foldNext(futures.iterator, zero, op) + + private[this] final def foldNext[T, R](i: Iterator[Future[T]], prevValue: R, op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = + if (!i.hasNext) successful(prevValue) + else i.next().flatMap { value => foldNext(i, op(prevValue, value), op) } + + /** A non-blocking, asynchronous fold over the specified futures, with the start value of the given zero. + * The fold is performed on the thread where the last future is completed, + * the result will be the first failure of any of the futures, or any failure in the actual fold, + * or the result of the fold. + * + * Example: + * {{{ + * val futureSum = Future.fold(futures)(0)(_ + _) + * }}} + * + * @tparam T the type of the value of the input Futures + * @tparam R the type of the value of the returned `Future` + * @param futures the `IterableOnce` of Futures to be folded + * @param zero the start value of the fold + * @param op the fold operation to be applied to the zero and futures + * @return the `Future` holding the result of the fold + */ + @deprecated("use Future.foldLeft instead", "2.12.0") + // not removed in 2.13, to facilitate 2.11/2.12/2.13 cross-building; remove further down the line (see scala/scala#6319) + def fold[T, R](futures: IterableOnce[Future[T]])(zero: R)(@deprecatedName("foldFun") op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = + if (futures.isEmpty) successful(zero) + else sequence(futures)(ArrayBuffer, executor).map(_.foldLeft(zero)(op)) + + /** Initiates a non-blocking, asynchronous, fold over the supplied futures + * where the fold-zero is the result value of the first `Future` in the collection. + * + * Example: + * {{{ + * val futureSum = Future.reduce(futures)(_ + _) + * }}} + * @tparam T the type of the value of the input Futures + * @tparam R the type of the value of the returned `Future` + * @param futures the `IterableOnce` of Futures to be reduced + * @param op the reduce operation which is applied to the results of the futures + * @return the `Future` holding the result of the reduce + */ + @deprecated("use Future.reduceLeft instead", "2.12.0") + // not removed in 2.13, to facilitate 2.11/2.12/2.13 cross-building; remove further down the line (see scala/scala#6319) + final def reduce[T, R >: T](futures: IterableOnce[Future[T]])(op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = + if (futures.isEmpty) failed(new NoSuchElementException("reduce attempted on empty collection")) + else sequence(futures)(ArrayBuffer, executor).map(_ reduceLeft op) + + /** Initiates a non-blocking, asynchronous, left reduction over the supplied futures + * where the zero is the result value of the first `Future`. + * + * Example: + * {{{ + * val futureSum = Future.reduceLeft(futures)(_ + _) + * }}} + * @tparam T the type of the value of the input Futures + * @tparam R the type of the value of the returned `Future` + * @param futures the `scala.collection.immutable.Iterable` of Futures to be reduced + * @param op the reduce operation which is applied to the results of the futures + * @return the `Future` holding the result of the reduce + */ + final def reduceLeft[T, R >: T](futures: scala.collection.immutable.Iterable[Future[T]])(op: (R, T) => R)(implicit executor: ExecutionContext): Future[R] = { + val i = futures.iterator + if (!i.hasNext) failed(new NoSuchElementException("reduceLeft attempted on empty collection")) + else i.next() flatMap { v => foldNext(i, v, op) } + } + + /** Asynchronously and non-blockingly transforms a `IterableOnce[A]` into a `Future[IterableOnce[B]]` + * using the provided function `A => Future[B]`. + * This is useful for performing a parallel map. For example, to apply a function to all items of a list + * in parallel: + * + * {{{ + * val myFutureList = Future.traverse(myList)(x => Future(myFunc(x))) + * }}} + * @tparam A the type of the value inside the Futures in the collection + * @tparam B the type of the value of the returned `Future` + * @tparam M the type of the collection of Futures + * @param in the collection to be mapped over with the provided function to produce a collection of Futures that is then sequenced into a Future collection + * @param fn the function to be mapped over the collection to produce a collection of Futures + * @return the `Future` of the collection of results + */ + final def traverse[A, B, M[X] <: IterableOnce[X]](in: M[A])(fn: A => Future[B])(implicit bf: BuildFrom[M[A], B, M[B]], executor: ExecutionContext): Future[M[B]] = + in.iterator.foldLeft(successful(bf.newBuilder(in))) { + (fr, a) => fr.zipWith(fn(a))(Future.addToBuilderFun) + }.map(_.result())(if (executor.isInstanceOf[BatchingExecutor]) executor else parasitic) +} + +@deprecated("Superseded by `scala.concurrent.Batchable`", "2.13.0") +trait OnCompleteRunnable extends Batchable { + self: Runnable => +} + diff --git a/scala2-library-cc/src/scala/concurrent/JavaConversions.scala b/scala2-library-cc/src/scala/concurrent/JavaConversions.scala new file mode 100644 index 000000000000..3250e656941a --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/JavaConversions.scala @@ -0,0 +1,38 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +import java.util.concurrent.{ExecutorService, Executor} +import scala.language.implicitConversions + +/** The `JavaConversions` object provides implicit conversions supporting + * interoperability between Scala and Java concurrency classes. + */ +@deprecated("Use the factory methods in `ExecutionContext` instead", "2.13.0") +object JavaConversions { + + /** + * Creates a new `ExecutionContext` which uses the provided `ExecutorService`. + */ + @deprecated("Use `ExecutionContext.fromExecutorService` instead", "2.13.0") + implicit def asExecutionContext(exec: ExecutorService): ExecutionContextExecutorService = + ExecutionContext.fromExecutorService(exec) + + /** + * Creates a new `ExecutionContext` which uses the provided `Executor`. + */ + @deprecated("Use `ExecutionContext.fromExecutor` instead", "2.13.0") + implicit def asExecutionContext(exec: Executor): ExecutionContextExecutor = + ExecutionContext.fromExecutor(exec) + +} diff --git a/scala2-library-cc/src/scala/concurrent/Promise.scala b/scala2-library-cc/src/scala/concurrent/Promise.scala new file mode 100644 index 000000000000..cf3f23543c5a --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/Promise.scala @@ -0,0 +1,148 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +import scala.util.{ Try, Success, Failure } + +/** Promise is an object which can be completed with a value or failed + * with an exception. + * + * A promise should always eventually be completed, whether for success or failure, + * in order to avoid unintended resource retention for any associated Futures' + * callbacks or transformations. + * + * @define promiseCompletion + * If the promise has already been fulfilled, failed or has timed out, + * calling this method will throw an IllegalStateException. + * + * @define allowedThrowables + * If the throwable used to fail this promise is an error, a control exception + * or an interrupted exception, it will be wrapped as a cause within an + * `ExecutionException` which will fail the promise. + * + * @define nonDeterministic + * Note: Using this method may result in non-deterministic concurrent programs. + */ +trait Promise[T] { + /** Future containing the value of this promise. + */ + def future: Future[T] + + /** Returns whether the promise has already been completed with + * a value or an exception. + * + * $nonDeterministic + * + * @return `true` if the promise is already completed, `false` otherwise + */ + def isCompleted: Boolean + + /** Completes the promise with either an exception or a value. + * + * @param result Either the value or the exception to complete the promise with. + * + * $promiseCompletion + */ + def complete(result: Try[T]): this.type = + if (tryComplete(result)) this else throw new IllegalStateException("Promise already completed.") + + /** Tries to complete the promise with either a value or the exception. + * + * $nonDeterministic + * + * @return If the promise has already been completed returns `false`, or `true` otherwise. + */ + def tryComplete(result: Try[T]): Boolean + + /** Completes this promise with the specified future, once that future is completed. + * + * @return This promise + */ + def completeWith(other: Future[T]): this.type = { + if (other ne this.future) // this tryCompleteWith this doesn't make much sense + other.onComplete(this tryComplete _)(ExecutionContext.parasitic) + + this + } + + /** Attempts to complete this promise with the specified future, once that future is completed. + * + * @return This promise + */ + @deprecated("Since this method is semantically equivalent to `completeWith`, use that instead.", "2.13.0") + final def tryCompleteWith(other: Future[T]): this.type = completeWith(other) + + /** Completes the promise with a value. + * + * @param value The value to complete the promise with. + * + * $promiseCompletion + */ + def success(value: T): this.type = complete(Success(value)) + + /** Tries to complete the promise with a value. + * + * $nonDeterministic + * + * @return If the promise has already been completed returns `false`, or `true` otherwise. + */ + def trySuccess(value: T): Boolean = tryComplete(Success(value)) + + /** Completes the promise with an exception. + * + * @param cause The throwable to complete the promise with. + * + * $allowedThrowables + * + * $promiseCompletion + */ + def failure(cause: Throwable): this.type = complete(Failure(cause)) + + /** Tries to complete the promise with an exception. + * + * $nonDeterministic + * + * @return If the promise has already been completed returns `false`, or `true` otherwise. + */ + def tryFailure(cause: Throwable): Boolean = tryComplete(Failure(cause)) +} + +object Promise { + /** Creates a promise object which can be completed with a value. + * + * @tparam T the type of the value in the promise + * @return the newly created `Promise` instance + */ + final def apply[T](): Promise[T] = new impl.Promise.DefaultPromise[T]() + + /** Creates an already completed Promise with the specified exception. + * + * @tparam T the type of the value in the promise + * @return the newly created `Promise` instance + */ + final def failed[T](exception: Throwable): Promise[T] = fromTry(Failure(exception)) + + /** Creates an already completed Promise with the specified result. + * + * @tparam T the type of the value in the promise + * @return the newly created `Promise` instance + */ + final def successful[T](result: T): Promise[T] = fromTry(Success(result)) + + /** Creates an already completed Promise with the specified result or exception. + * + * @tparam T the type of the value in the promise + * @return the newly created `Promise` instance + */ + final def fromTry[T](result: Try[T]): Promise[T] = new impl.Promise.DefaultPromise[T](result) +} diff --git a/scala2-library-cc/src/scala/concurrent/SyncChannel.scala b/scala2-library-cc/src/scala/concurrent/SyncChannel.scala new file mode 100644 index 000000000000..8792524524c3 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/SyncChannel.scala @@ -0,0 +1,77 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +/** A `SyncChannel` allows one to exchange data synchronously between + * a reader and a writer thread. The writer thread is blocked until the + * data to be written has been read by a corresponding reader thread. + */ +@deprecated("Use `java.util.concurrent.Exchanger` instead.", since = "2.13.0") +class SyncChannel[A] { + + private final val Signal = () + private type Signal = Unit + private[this] var pendingWrites = List[(A, SyncVar[Signal])]() + private[this] var pendingReads = List[SyncVar[A]]() + + def write(data: A): Unit = { + // create write request + val writeReq = new SyncVar[Signal] + + this.synchronized { + // check whether there is a reader waiting + if (pendingReads.nonEmpty) { + val readReq = pendingReads.head + pendingReads = pendingReads.tail + + // let reader continue + readReq.put(data) + + // resolve write request + writeReq.put(Signal) + } + else { + // enqueue write request + pendingWrites = pendingWrites ::: List((data, writeReq)) + } + } + + writeReq.get + } + + def read: A = { + // create read request + val readReq = new SyncVar[A] + + this.synchronized { + // check whether there is a writer waiting + if (pendingWrites.nonEmpty) { + // read data + val (data, writeReq) = pendingWrites.head + pendingWrites = pendingWrites.tail + + // let writer continue + writeReq.put(Signal) + + // resolve read request + readReq.put(data) + } + else { + // enqueue read request + pendingReads = pendingReads ::: List(readReq) + } + } + + readReq.get + } +} diff --git a/scala2-library-cc/src/scala/concurrent/SyncVar.scala b/scala2-library-cc/src/scala/concurrent/SyncVar.scala new file mode 100644 index 000000000000..66c5fd1bb81d --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/SyncVar.scala @@ -0,0 +1,122 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +import java.util.concurrent.TimeUnit + +/** A class to provide safe concurrent access to a mutable cell. + * All methods are synchronized. + * + * @tparam A type of the contained value + */ +@deprecated("Use `java.util.concurrent.LinkedBlockingQueue with capacity 1` instead.", since = "2.13.0") +class SyncVar[A] { + private[this] var isDefined: Boolean = false + private[this] var value: A = _ + + /** + * Wait for this SyncVar to become defined and then get + * the stored value without modifying it. + * + * @return value that is held in this container + */ + def get: A = synchronized { + while (!isDefined) wait() + value + } + + /** Waits `timeout` millis. If `timeout <= 0` just returns 0. + * It never returns negative results. + */ + private def waitMeasuringElapsed(timeout: Long): Long = if (timeout <= 0) 0 else { + val start = System.nanoTime() + wait(timeout) + val elapsed = System.nanoTime() - start + // nanoTime should be monotonic, but it's not possible to rely on that. + // See https://bugs.java.com/view_bug.do?bug_id=6458294 + if (elapsed < 0) 0 else TimeUnit.NANOSECONDS.toMillis(elapsed) + } + + /** Wait at least `timeout` milliseconds (possibly more) for this `SyncVar` + * to become defined and then get its value. + * + * @param timeout time in milliseconds to wait + * @return `None` if variable is undefined after `timeout`, `Some(value)` otherwise + */ + def get(timeout: Long): Option[A] = synchronized { + /* Defending against the system clock going backward + * by counting time elapsed directly. Loop required + * to deal with spurious wakeups. + */ + var rest = timeout + while (!isDefined && rest > 0) { + val elapsed = waitMeasuringElapsed(rest) + rest -= elapsed + } + if (isDefined) Some(value) else None + } + + /** + * Wait for this SyncVar to become defined and then get + * the stored value, unsetting it as a side effect. + * + * @return value that was held in this container + */ + def take(): A = synchronized { + try get + finally unsetVal() + } + + /** Wait at least `timeout` milliseconds (possibly more) for this `SyncVar` + * to become defined and then get the stored value, unsetting it + * as a side effect. + * + * @param timeout the amount of milliseconds to wait + * @return the value or a throws an exception if the timeout occurs + * @throws NoSuchElementException on timeout + */ + def take(timeout: Long): A = synchronized { + try get(timeout).get + finally unsetVal() + } + + /** Place a value in the SyncVar. If the SyncVar already has a stored value, + * wait until another thread takes it. */ + def put(x: A): Unit = synchronized { + while (isDefined) wait() + setVal(x) + } + + /** Check whether a value is stored in the synchronized variable. */ + def isSet: Boolean = synchronized { + isDefined + } + + // `setVal` exists so as to retroactively deprecate `set` without + // deprecation warnings where we use `set` internally. The + // implementation of `set` was moved to `setVal` to achieve this + private def setVal(x: A): Unit = synchronized { + isDefined = true + value = x + notifyAll() + } + + // `unsetVal` exists so as to retroactively deprecate `unset` without + // deprecation warnings where we use `unset` internally. The + // implementation of `unset` was moved to `unsetVal` to achieve this + private def unsetVal(): Unit = synchronized { + isDefined = false + value = null.asInstanceOf[A] + notifyAll() + } +} diff --git a/scala2-library-cc/src/scala/concurrent/duration/Deadline.scala b/scala2-library-cc/src/scala/concurrent/duration/Deadline.scala new file mode 100644 index 000000000000..353d0f30fff8 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/duration/Deadline.scala @@ -0,0 +1,85 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent.duration + +/** + * This class stores a deadline, as obtained via `Deadline.now` or the + * duration DSL: + * + * {{{ + * import scala.concurrent.duration._ + * 3.seconds.fromNow + * }}} + * + * Its main purpose is to manage repeated attempts to achieve something (like + * awaiting a condition) by offering the methods `hasTimeLeft` and `timeLeft`. All + * durations are measured according to `System.nanoTime`; this + * does not take into account changes to the system clock (such as leap + * seconds). + */ +case class Deadline private (time: FiniteDuration) extends Ordered[Deadline] { + /** + * Return a deadline advanced (i.e., moved into the future) by the given duration. + */ + def +(other: FiniteDuration): Deadline = copy(time = time + other) + /** + * Return a deadline moved backwards (i.e., towards the past) by the given duration. + */ + def -(other: FiniteDuration): Deadline = copy(time = time - other) + /** + * Calculate time difference between this and the other deadline, where the result is directed (i.e., may be negative). + */ + def -(other: Deadline): FiniteDuration = time - other.time + /** + * Calculate time difference between this duration and now; the result is negative if the deadline has passed. + * + * '''''Note that on some systems this operation is costly because it entails a system call.''''' + * Check `System.nanoTime` for your platform. + */ + def timeLeft: FiniteDuration = this - Deadline.now + /** + * Determine whether the deadline still lies in the future at the point where this method is called. + * + * '''''Note that on some systems this operation is costly because it entails a system call.''''' + * Check `System.nanoTime` for your platform. + */ + def hasTimeLeft(): Boolean = !isOverdue() + /** + * Determine whether the deadline lies in the past at the point where this method is called. + * + * '''''Note that on some systems this operation is costly because it entails a system call.''''' + * Check `System.nanoTime` for your platform. + */ + def isOverdue(): Boolean = (time.toNanos - System.nanoTime()) < 0 + /** + * The natural ordering for deadline is determined by the natural order of the underlying (finite) duration. + */ + def compare(other: Deadline): Int = time compare other.time +} + +object Deadline { + /** + * Construct a deadline due exactly at the point where this method is called. Useful for then + * advancing it to obtain a future deadline, or for sampling the current time exactly once and + * then comparing it to multiple deadlines (using subtraction). + */ + def now: Deadline = Deadline(Duration(System.nanoTime, NANOSECONDS)) + + /** + * The natural ordering for deadline is determined by the natural order of the underlying (finite) duration. + */ + implicit object DeadlineIsOrdered extends Ordering[Deadline] { + def compare(a: Deadline, b: Deadline): Int = a compare b + } + +} diff --git a/scala2-library-cc/src/scala/concurrent/duration/Duration.scala b/scala2-library-cc/src/scala/concurrent/duration/Duration.scala new file mode 100644 index 000000000000..1312bb12d1d5 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/duration/Duration.scala @@ -0,0 +1,742 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent.duration + +import java.lang.{ Double => JDouble } +import scala.collection.StringParsers + +object Duration { + + /** + * Construct a Duration from the given length and unit. Observe that nanosecond precision may be lost if + * + * - the unit is NANOSECONDS + * - and the length has an absolute value greater than `2^53` + * + * Infinite inputs (and NaN) are converted into [[Duration.Inf]], [[Duration.MinusInf]] and [[Duration.Undefined]], respectively. + * + * @throws IllegalArgumentException if the length was finite but the resulting duration cannot be expressed as a [[FiniteDuration]] + */ + def apply(length: Double, unit: TimeUnit): Duration = fromNanos(unit.toNanos(1) * length) + + /** + * Construct a finite duration from the given length and time unit. The unit given is retained + * throughout calculations as long as possible, so that it can be retrieved later. + */ + def apply(length: Long, unit: TimeUnit): FiniteDuration = new FiniteDuration(length, unit) + + /** + * Construct a finite duration from the given length and time unit, where the latter is + * looked up in a list of string representation. Valid choices are: + * + * `d, day, h, hr, hour, m, min, minute, s, sec, second, ms, milli, millisecond, µs, micro, microsecond, ns, nano, nanosecond` + * and their pluralized forms (for every but the first mentioned form of each unit, i.e. no "ds", but "days"). + */ + def apply(length: Long, unit: String): FiniteDuration = new FiniteDuration(length, Duration.timeUnit(unit)) + + // Double stores 52 bits mantissa, but there is an implied '1' in front, making the limit 2^53 + // private[this] final val maxPreciseDouble = 9007199254740992d // not used after https://github.com/scala/scala/pull/9233 + + /** + * Parse String into Duration. Format is `""`, where + * whitespace is allowed before, between and after the parts. Infinities are + * designated by `"Inf"`, `"PlusInf"`, `"+Inf"`, `"Duration.Inf"` and `"-Inf"`, `"MinusInf"` or `"Duration.MinusInf"`. + * Undefined is designated by `"Duration.Undefined"`. + * + * @throws NumberFormatException if format is not parsable + */ + def apply(s: String): Duration = { + val s1: String = s filterNot (_.isWhitespace) + s1 match { + case "Inf" | "PlusInf" | "+Inf" | "Duration.Inf" => Inf + case "MinusInf" | "-Inf" | "Duration.MinusInf" => MinusInf + case "Duration.Undefined" => Undefined + case _ => + val unitName = s1.reverse.takeWhile(_.isLetter).reverse + timeUnit get unitName match { + case Some(unit) => + val valueStr = s1 dropRight unitName.length + StringParsers.parseLong(valueStr).map(Duration(_, unit)) + .getOrElse(Duration(JDouble.parseDouble(valueStr), unit)) + case _ => throw new NumberFormatException("format error " + s) + } + } + } + + // "ms milli millisecond" -> List("ms", "milli", "millis", "millisecond", "milliseconds") + private[this] def words(s: String) = (s.trim split "\\s+").toList + private[this] def expandLabels(labels: String): List[String] = { + val hd :: rest = words(labels): @unchecked + hd :: rest.flatMap(s => List(s, s + "s")) + } + private[this] val timeUnitLabels = List( + DAYS -> "d day", + HOURS -> "h hr hour", + MINUTES -> "m min minute", + SECONDS -> "s sec second", + MILLISECONDS -> "ms milli millisecond", + MICROSECONDS -> "µs micro microsecond", + NANOSECONDS -> "ns nano nanosecond" + ) + + // TimeUnit => standard label + protected[duration] val timeUnitName: Map[TimeUnit, String] = + timeUnitLabels.toMap.view.mapValues(s => words(s).last).toMap + + // Label => TimeUnit + protected[duration] val timeUnit: Map[String, TimeUnit] = + timeUnitLabels.flatMap{ case (unit, names) => expandLabels(names) map (_ -> unit) }.toMap + + /** + * Extract length and time unit out of a string, where the format must match the description for [[Duration$.apply(s:String)* apply(String)]]. + * The extractor will not match for malformed strings or non-finite durations. + */ + def unapply(s: String): Option[(Long, TimeUnit)] = + ( try Some(apply(s)) catch { case _: RuntimeException => None } ) flatMap unapply + + /** + * Extract length and time unit out of a duration, if it is finite. + */ + def unapply(d: Duration): Option[(Long, TimeUnit)] = + if (d.isFinite) Some((d.length, d.unit)) else None + + /** + * Construct a possibly infinite or undefined Duration from the given number of nanoseconds. + * + * - `Double.PositiveInfinity` is mapped to [[Duration.Inf]] + * - `Double.NegativeInfinity` is mapped to [[Duration.MinusInf]] + * - `Double.NaN` is mapped to [[Duration.Undefined]] + * - `-0d` is mapped to [[Duration.Zero]] (exactly like `0d`) + * + * The semantics of the resulting Duration objects matches the semantics of their Double + * counterparts with respect to arithmetic operations. + * + * @throws IllegalArgumentException if the length was finite but the resulting duration cannot be expressed as a [[FiniteDuration]] + */ + def fromNanos(nanos: Double): Duration = { + if (nanos.isInfinite) + if (nanos > 0) Inf else MinusInf + else if (JDouble.isNaN(nanos)) + Undefined + else if (nanos > Long.MaxValue || nanos < Long.MinValue) + throw new IllegalArgumentException("trying to construct too large duration with " + nanos + "ns") + else + fromNanos(nanos.round) + } + + private[this] final val ns_per_µs = 1000L + private[this] final val ns_per_ms = ns_per_µs * 1000 + private[this] final val ns_per_s = ns_per_ms * 1000 + private[this] final val ns_per_min = ns_per_s * 60 + private[this] final val ns_per_h = ns_per_min * 60 + private[this] final val ns_per_d = ns_per_h * 24 + + /** + * Construct a finite duration from the given number of nanoseconds. The + * result will have the coarsest possible time unit which can exactly express + * this duration. + * + * @throws IllegalArgumentException for `Long.MinValue` since that would lead to inconsistent behavior afterwards (cannot be negated) + */ + def fromNanos(nanos: Long): FiniteDuration = { + if (nanos % ns_per_d == 0) Duration(nanos / ns_per_d , DAYS) + else if (nanos % ns_per_h == 0) Duration(nanos / ns_per_h , HOURS) + else if (nanos % ns_per_min == 0) Duration(nanos / ns_per_min, MINUTES) + else if (nanos % ns_per_s == 0) Duration(nanos / ns_per_s , SECONDS) + else if (nanos % ns_per_ms == 0) Duration(nanos / ns_per_ms , MILLISECONDS) + else if (nanos % ns_per_µs == 0) Duration(nanos / ns_per_µs , MICROSECONDS) + else Duration(nanos, NANOSECONDS) + } + + /** + * Preconstructed value of `0.days`. + */ + // unit as coarse as possible to keep (_ + Zero) sane unit-wise + val Zero: FiniteDuration = new FiniteDuration(0, DAYS) + + /** + * The Undefined value corresponds closely to Double.NaN: + * + * - it is the result of otherwise invalid operations + * - it does not equal itself (according to `equals()`) + * - it compares greater than any other Duration apart from itself (for which `compare` returns 0) + * + * The particular comparison semantics mirror those of Double.NaN. + * + * '''''Use [[eq]] when checking an input of a method against this value.''''' + */ + val Undefined: Infinite = new Infinite { + override def toString = "Duration.Undefined" + override def equals(other: Any): Boolean = false + override def +(other: Duration): Duration = this + override def -(other: Duration): Duration = this + override def *(factor: Double): Duration = this + override def /(factor: Double): Duration = this + override def /(other: Duration): Double = Double.NaN + def compare(other: Duration): Int = if (other eq this) 0 else 1 + def unary_- : Duration = this + def toUnit(unit: TimeUnit): Double = Double.NaN + private def readResolve(): AnyRef = Undefined // Instructs deserialization to use this same instance + } + + sealed abstract class Infinite extends Duration { + def +(other: Duration): Duration = other match { + case x if x eq Undefined => Undefined + case x: Infinite if x ne this => Undefined + case _ => this + } + def -(other: Duration): Duration = other match { + case x if x eq Undefined => Undefined + case x: Infinite if x eq this => Undefined + case _ => this + } + + def *(factor: Double): Duration = + if (factor == 0d || JDouble.isNaN(factor)) Undefined + else if (factor < 0d) -this + else this + def /(divisor: Double): Duration = + if (JDouble.isNaN(divisor) || divisor.isInfinite) Undefined + else if ((divisor compare 0d) < 0) -this + else this + def /(divisor: Duration): Double = divisor match { + case _: Infinite => Double.NaN + case x => Double.PositiveInfinity * (if ((this > Zero) ^ (divisor >= Zero)) -1 else 1) + } + + final def isFinite = false + + private[this] def fail(what: String) = throw new IllegalArgumentException(s"$what not allowed on infinite Durations") + final def length: Long = fail("length") + final def unit: TimeUnit = fail("unit") + final def toNanos: Long = fail("toNanos") + final def toMicros: Long = fail("toMicros") + final def toMillis: Long = fail("toMillis") + final def toSeconds: Long = fail("toSeconds") + final def toMinutes: Long = fail("toMinutes") + final def toHours: Long = fail("toHours") + final def toDays: Long = fail("toDays") + + final def toCoarsest: Duration = this + } + + /** + * Infinite duration: greater than any other (apart from Undefined) and not equal to any other + * but itself. This value closely corresponds to Double.PositiveInfinity, + * matching its semantics in arithmetic operations. + */ + val Inf: Infinite = new Infinite { + override def toString: String = "Duration.Inf" + def compare(other: Duration): Int = other match { + case x if x eq Undefined => -1 // Undefined != Undefined + case x if x eq this => 0 // `case Inf` will include null checks in the byte code + case _ => 1 + } + def unary_- : Duration = MinusInf + def toUnit(unit: TimeUnit): Double = Double.PositiveInfinity + private def readResolve(): AnyRef = Inf // Instructs deserialization to use this same instance + } + + /** + * Infinite duration: less than any other and not equal to any other + * but itself. This value closely corresponds to Double.NegativeInfinity, + * matching its semantics in arithmetic operations. + */ + val MinusInf: Infinite = new Infinite { + override def toString: String = "Duration.MinusInf" + def compare(other: Duration): Int = if (other eq this) 0 else -1 + def unary_- : Duration = Inf + def toUnit(unit: TimeUnit): Double = Double.NegativeInfinity + private def readResolve(): AnyRef = MinusInf // Instructs deserialization to use this same instance + } + + // Java Factories + + /** + * Construct a finite duration from the given length and time unit. The unit given is retained + * throughout calculations as long as possible, so that it can be retrieved later. + */ + def create(length: Long, unit: TimeUnit): FiniteDuration = apply(length, unit) + /** + * Construct a Duration from the given length and unit. Observe that nanosecond precision may be lost if + * + * - the unit is NANOSECONDS + * - and the length has an absolute value greater than `2^53` + * + * Infinite inputs (and NaN) are converted into [[Duration.Inf]], [[Duration.MinusInf]] and [[Duration.Undefined]], respectively. + * + * @throws IllegalArgumentException if the length was finite but the resulting duration cannot be expressed as a [[FiniteDuration]] + */ + def create(length: Double, unit: TimeUnit): Duration = apply(length, unit) + /** + * Construct a finite duration from the given length and time unit, where the latter is + * looked up in a list of string representation. Valid choices are: + * + * `d, day, h, hour, min, minute, s, sec, second, ms, milli, millisecond, µs, micro, microsecond, ns, nano, nanosecond` + * and their pluralized forms (for every but the first mentioned form of each unit, i.e. no "ds", but "days"). + */ + def create(length: Long, unit: String): FiniteDuration = apply(length, unit) + /** + * Parse String into Duration. Format is `""`, where + * whitespace is allowed before, between and after the parts. Infinities are + * designated by `"Inf"`, `"PlusInf"`, `"+Inf"` and `"-Inf"` or `"MinusInf"`. + * + * @throws NumberFormatException if format is not parsable + */ + def create(s: String): Duration = apply(s) + + /** + * The natural ordering of durations matches the natural ordering for Double, including non-finite values. + */ + implicit object DurationIsOrdered extends Ordering[Duration] { + def compare(a: Duration, b: Duration): Int = a compare b + } +} + +/** + *

Utility for working with java.util.concurrent.TimeUnit durations.

+ * + * '''''This class is not meant as a general purpose representation of time, it is + * optimized for the needs of `scala.concurrent`.''''' + * + *

Basic Usage

+ * + *

+ * Examples: + * {{{ + * import scala.concurrent.duration._ + * + * val duration = Duration(100, MILLISECONDS) + * val duration = Duration(100, "millis") + * + * duration.toNanos + * duration < 1.second + * duration <= Duration.Inf + * }}} + * + * '''''Invoking inexpressible conversions (like calling `toSeconds` on an infinite duration) will throw an IllegalArgumentException.''''' + * + *

+ * Implicits are also provided for Int, Long and Double. Example usage: + * {{{ + * import scala.concurrent.duration._ + * + * val duration = 100.millis + * }}} + * + * '''''The DSL provided by the implicit conversions always allows construction of finite durations, even for infinite Double inputs; use Duration.Inf instead.''''' + * + * Extractors, parsing and arithmetic are also included: + * {{{ + * val d = Duration("1.2 µs") + * val Duration(length, unit) = 5 millis + * val d2 = d * 2.5 + * val d3 = d2 + 1.millisecond + * }}} + * + *

Handling of Time Units

+ * + * Calculations performed on finite durations always retain the more precise unit of either operand, no matter + * whether a coarser unit would be able to exactly express the same duration. This means that Duration can be + * used as a lossless container for a (length, unit) pair if it is constructed using the corresponding methods + * and no arithmetic is performed on it; adding/subtracting durations should in that case be done with care. + * + *

Correspondence to Double Semantics

+ * + * The semantics of arithmetic operations on Duration are two-fold: + * + * - exact addition/subtraction with nanosecond resolution for finite durations, independent of the summands' magnitude + * - isomorphic to `java.lang.Double` when it comes to infinite or undefined values + * + * The conversion between Duration and Double is done using [[Duration.toUnit]] (with unit NANOSECONDS) + * and [[Duration$.fromNanos(nanos:Double)* Duration.fromNanos(Double)]] + * + *

Ordering

+ * + * The default ordering is consistent with the ordering of Double numbers, which means that Undefined is + * considered greater than all other durations, including [[Duration.Inf]]. + * + * @define exc @throws IllegalArgumentException when invoked on a non-finite duration + * + * @define ovf @throws IllegalArgumentException in case of a finite overflow: the range of a finite duration is `+-(2^63-1)`ns, and no conversion to infinite durations takes place. + */ +sealed abstract class Duration extends Serializable with Ordered[Duration] { + /** + * Obtain the length of this Duration measured in the unit obtained by the `unit` method. + * + * $exc + */ + def length: Long + /** + * Obtain the time unit in which the length of this duration is measured. + * + * $exc + */ + def unit: TimeUnit + /** + * Return the length of this duration measured in whole nanoseconds, rounding towards zero. + * + * $exc + */ + def toNanos: Long + /** + * Return the length of this duration measured in whole microseconds, rounding towards zero. + * + * $exc + */ + def toMicros: Long + /** + * Return the length of this duration measured in whole milliseconds, rounding towards zero. + * + * $exc + */ + def toMillis: Long + /** + * Return the length of this duration measured in whole seconds, rounding towards zero. + * + * $exc + */ + def toSeconds: Long + /** + * Return the length of this duration measured in whole minutes, rounding towards zero. + * + * $exc + */ + def toMinutes: Long + /** + * Return the length of this duration measured in whole hours, rounding towards zero. + * + * $exc + */ + def toHours: Long + /** + * Return the length of this duration measured in whole days, rounding towards zero. + * + * $exc + */ + def toDays: Long + /** + * Return the number of nanoseconds as floating point number, scaled down to the given unit. + * The result may not precisely represent this duration due to the Double datatype's inherent + * limitations (mantissa size effectively 53 bits). Non-finite durations are represented as + * - [[Duration.Undefined]] is mapped to Double.NaN + * - [[Duration.Inf]] is mapped to Double.PositiveInfinity + * - [[Duration.MinusInf]] is mapped to Double.NegativeInfinity + */ + def toUnit(unit: TimeUnit): Double + + /** + * Return the sum of that duration and this. When involving non-finite summands the semantics match those + * of Double. + * + * $ovf + */ + def +(other: Duration): Duration + /** + * Return the difference of that duration and this. When involving non-finite summands the semantics match those + * of Double. + * + * $ovf + */ + def -(other: Duration): Duration + /** + * Return this duration multiplied by the scalar factor. When involving non-finite factors the semantics match those + * of Double. + * + * $ovf + */ + def *(factor: Double): Duration + /** + * Return this duration divided by the scalar factor. When involving non-finite factors the semantics match those + * of Double. + * + * $ovf + */ + def /(divisor: Double): Duration + /** + * Return the quotient of this and that duration as floating-point number. The semantics are + * determined by Double as if calculating the quotient of the nanosecond lengths of both factors. + */ + def /(divisor: Duration): Double + /** + * Negate this duration. The only two values which are mapped to themselves are [[Duration.Zero]] and [[Duration.Undefined]]. + */ + def unary_- : Duration + /** + * This method returns whether this duration is finite, which is not the same as + * `!isInfinite` for Double because this method also returns `false` for [[Duration.Undefined]]. + */ + def isFinite: Boolean + /** + * Return the smaller of this and that duration as determined by the natural ordering. + */ + def min(other: Duration): Duration = if (this < other) this else other + /** + * Return the larger of this and that duration as determined by the natural ordering. + */ + def max(other: Duration): Duration = if (this > other) this else other + + // Java API + + /** + * Return this duration divided by the scalar factor. When involving non-finite factors the semantics match those + * of Double. + * + * $ovf + */ + def div(divisor: Double): Duration = this / divisor + /** + * Return the quotient of this and that duration as floating-point number. The semantics are + * determined by Double as if calculating the quotient of the nanosecond lengths of both factors. + */ + def div(other: Duration): Double = this / other + def gt(other: Duration): Boolean = this > other + def gteq(other: Duration): Boolean = this >= other + def lt(other: Duration): Boolean = this < other + def lteq(other: Duration): Boolean = this <= other + /** + * Return the difference of that duration and this. When involving non-finite summands the semantics match those + * of Double. + * + * $ovf + */ + def minus(other: Duration): Duration = this - other + /** + * Return this duration multiplied by the scalar factor. When involving non-finite factors the semantics match those + * of Double. + * + * $ovf + */ + def mul(factor: Double): Duration = this * factor + /** + * Negate this duration. The only two values which are mapped to themselves are [[Duration.Zero]] and [[Duration.Undefined]]. + */ + def neg(): Duration = -this + /** + * Return the sum of that duration and this. When involving non-finite summands the semantics match those + * of Double. + * + * $ovf + */ + def plus(other: Duration): Duration = this + other + /** + * Return duration which is equal to this duration but with a coarsest Unit, or self in case it is already the coarsest Unit + *

+ * Examples: + * {{{ + * Duration(60, MINUTES).toCoarsest // Duration(1, HOURS) + * Duration(1000, MILLISECONDS).toCoarsest // Duration(1, SECONDS) + * Duration(48, HOURS).toCoarsest // Duration(2, DAYS) + * Duration(5, SECONDS).toCoarsest // Duration(5, SECONDS) + * }}} + */ + def toCoarsest: Duration +} + +object FiniteDuration { + + implicit object FiniteDurationIsOrdered extends Ordering[FiniteDuration] { + def compare(a: FiniteDuration, b: FiniteDuration): Int = a compare b + } + + def apply(length: Long, unit: TimeUnit): FiniteDuration = new FiniteDuration(length, unit) + def apply(length: Long, unit: String): FiniteDuration = new FiniteDuration(length, Duration.timeUnit(unit)) + + // limit on abs. value of durations in their units + private final val max_ns = Long.MaxValue + private final val max_µs = max_ns / 1000 + private final val max_ms = max_µs / 1000 + private final val max_s = max_ms / 1000 + private final val max_min= max_s / 60 + private final val max_h = max_min / 60 + private final val max_d = max_h / 24 +} + +/** + * This class represents a finite duration. Its addition and subtraction operators are overloaded to retain + * this guarantee statically. The range of this class is limited to `+-(2^63-1)`ns, which is roughly 292 years. + */ +final class FiniteDuration(val length: Long, val unit: TimeUnit) extends Duration { + import FiniteDuration._ + import Duration._ + + private[this] def bounded(max: Long) = -max <= length && length <= max + + require(unit match { + /* + * enforce the 2^63-1 ns limit, must be pos/neg symmetrical because of unary_- + */ + case NANOSECONDS => bounded(max_ns) + case MICROSECONDS => bounded(max_µs) + case MILLISECONDS => bounded(max_ms) + case SECONDS => bounded(max_s) + case MINUTES => bounded(max_min) + case HOURS => bounded(max_h) + case DAYS => bounded(max_d) + case _ => + val v = DAYS.convert(length, unit) + -max_d <= v && v <= max_d + }, "Duration is limited to +-(2^63-1)ns (ca. 292 years)") + + def toNanos: Long = unit.toNanos(length) + def toMicros: Long = unit.toMicros(length) + def toMillis: Long = unit.toMillis(length) + def toSeconds: Long = unit.toSeconds(length) + def toMinutes: Long = unit.toMinutes(length) + def toHours: Long = unit.toHours(length) + def toDays: Long = unit.toDays(length) + def toUnit(u: TimeUnit): Double = toNanos.toDouble / NANOSECONDS.convert(1, u) + + /** + * Construct a [[Deadline]] from this duration by adding it to the current instant `Deadline.now`. + */ + def fromNow: Deadline = Deadline.now + this + + private[this] def unitString = timeUnitName(unit) + ( if (length == 1) "" else "s" ) + override def toString: String = "" + length + " " + unitString + + def compare(other: Duration): Int = other match { + case x: FiniteDuration => toNanos compare x.toNanos + case _ => -(other compare this) + } + + // see https://www.securecoding.cert.org/confluence/display/java/NUM00-J.+Detect+or+prevent+integer+overflow + private[this] def safeAdd(a: Long, b: Long): Long = { + if ((b > 0) && (a > Long.MaxValue - b) || + (b < 0) && (a < Long.MinValue - b)) throw new IllegalArgumentException("integer overflow") + a + b + } + private[this] def add(otherLength: Long, otherUnit: TimeUnit): FiniteDuration = { + val commonUnit = if (otherUnit.convert(1, unit) == 0) unit else otherUnit + val totalLength = safeAdd(commonUnit.convert(length, unit), commonUnit.convert(otherLength, otherUnit)) + new FiniteDuration(totalLength, commonUnit) + } + + def +(other: Duration): Duration = other match { + case x: FiniteDuration => add(x.length, x.unit) + case _ => other + } + def -(other: Duration): Duration = other match { + case x: FiniteDuration => add(-x.length, x.unit) + case _ => -other + } + + def *(factor: Double): Duration = + if (!factor.isInfinite) fromNanos(toNanos * factor) + else if (JDouble.isNaN(factor)) Undefined + else if ((factor > 0) ^ (this < Zero)) Inf + else MinusInf + + def /(divisor: Double): Duration = + if (!divisor.isInfinite) fromNanos(toNanos / divisor) + else if (JDouble.isNaN(divisor)) Undefined + else Zero + + // if this is made a constant, then scalac will elide the conditional and always return +0.0, scala/bug#6331 + private[this] def minusZero = -0d + def /(divisor: Duration): Double = + if (divisor.isFinite) toNanos.toDouble / divisor.toNanos + else if (divisor eq Undefined) Double.NaN + else if ((length < 0) ^ (divisor > Zero)) 0d + else minusZero + + // overloaded methods taking FiniteDurations, so that you can calculate while statically staying finite + def +(other: FiniteDuration): FiniteDuration = add(other.length, other.unit) + def -(other: FiniteDuration): FiniteDuration = add(-other.length, other.unit) + def plus(other: FiniteDuration): FiniteDuration = this + other + def minus(other: FiniteDuration): FiniteDuration = this - other + def min(other: FiniteDuration): FiniteDuration = if (this < other) this else other + def max(other: FiniteDuration): FiniteDuration = if (this > other) this else other + + // overloaded methods taking Long so that you can calculate while statically staying finite + + /** + * Return the quotient of this duration and the given integer factor. + * + * @throws java.lang.ArithmeticException if the factor is 0 + */ + def /(divisor: Long): FiniteDuration = fromNanos(toNanos / divisor) + + /** + * Return the product of this duration and the given integer factor. + * + * @throws IllegalArgumentException if the result would overflow the range of FiniteDuration + */ + def *(factor: Long): FiniteDuration = new FiniteDuration(safeMul(length, factor), unit) + + /* + * This method avoids the use of Long division, which saves 95% of the time spent, + * by checking that there are enough leading zeros so that the result has a chance + * to fit into a Long again; the remaining edge cases are caught by using the sign + * of the product for overflow detection. + * + * This method is not general purpose because it disallows the (otherwise legal) + * case of Long.MinValue * 1, but that is okay for use in FiniteDuration, since + * Long.MinValue is not a legal `length` anyway. + */ + private def safeMul(_a: Long, _b: Long): Long = { + val a = scala.math.abs(_a) + val b = scala.math.abs(_b) + import java.lang.Long.{ numberOfLeadingZeros => leading } + if (leading(a) + leading(b) < 64) throw new IllegalArgumentException("multiplication overflow") + val product = a * b + if (product < 0) throw new IllegalArgumentException("multiplication overflow") + if (a == _a ^ b == _b) -product else product + } + + /** + * Return the quotient of this duration and the given integer factor. + * + * @throws java.lang.ArithmeticException if the factor is 0 + */ + def div(divisor: Long): FiniteDuration = this / divisor + + /** + * Return the product of this duration and the given integer factor. + * + * @throws IllegalArgumentException if the result would overflow the range of FiniteDuration + */ + def mul(factor: Long): FiniteDuration = this * factor + + def unary_- : FiniteDuration = Duration(-length, unit) + + final def isFinite = true + + final override def toCoarsest: FiniteDuration = { + def loop(length: Long, unit: TimeUnit): FiniteDuration = { + def coarserOrThis(coarser: TimeUnit, divider: Int): FiniteDuration = + if (length % divider == 0) loop(length / divider, coarser) + else if (unit == this.unit) this + else FiniteDuration(length, unit) + + unit match { + case DAYS => FiniteDuration(length, unit) + case HOURS => coarserOrThis(DAYS, 24) + case MINUTES => coarserOrThis(HOURS, 60) + case SECONDS => coarserOrThis(MINUTES, 60) + case MILLISECONDS => coarserOrThis(SECONDS, 1000) + case MICROSECONDS => coarserOrThis(MILLISECONDS, 1000) + case NANOSECONDS => coarserOrThis(MICROSECONDS, 1000) + } + } + + if (unit == DAYS || length == 0) this + else loop(length, unit) + } + + override def equals(other: Any): Boolean = other match { + case x: FiniteDuration => toNanos == x.toNanos + case _ => super.equals(other) + } + override def hashCode: Int = toNanos.toInt +} diff --git a/scala2-library-cc/src/scala/concurrent/duration/DurationConversions.scala b/scala2-library-cc/src/scala/concurrent/duration/DurationConversions.scala new file mode 100644 index 000000000000..30036331be73 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/duration/DurationConversions.scala @@ -0,0 +1,96 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent.duration + +import DurationConversions._ + +// Would be nice to limit the visibility of this trait a little bit, +// but it crashes scalac to do so. +trait DurationConversions extends Any { + protected def durationIn(unit: TimeUnit): FiniteDuration + + def nanoseconds: FiniteDuration = durationIn(NANOSECONDS) + def nanos: FiniteDuration = nanoseconds + def nanosecond: FiniteDuration = nanoseconds + def nano: FiniteDuration = nanoseconds + + def microseconds: FiniteDuration = durationIn(MICROSECONDS) + def micros: FiniteDuration = microseconds + def microsecond: FiniteDuration = microseconds + def micro: FiniteDuration = microseconds + + def milliseconds: FiniteDuration = durationIn(MILLISECONDS) + def millis: FiniteDuration = milliseconds + def millisecond: FiniteDuration = milliseconds + def milli: FiniteDuration = milliseconds + + def seconds: FiniteDuration = durationIn(SECONDS) + def second: FiniteDuration = seconds + + def minutes: FiniteDuration = durationIn(MINUTES) + def minute: FiniteDuration = minutes + + def hours: FiniteDuration = durationIn(HOURS) + def hour: FiniteDuration = hours + + def days: FiniteDuration = durationIn(DAYS) + def day: FiniteDuration = days + + def nanoseconds[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(nanoseconds) + def nanos[C](c: C)(implicit ev: Classifier[C]): ev.R = nanoseconds(c) + def nanosecond[C](c: C)(implicit ev: Classifier[C]): ev.R = nanoseconds(c) + def nano[C](c: C)(implicit ev: Classifier[C]): ev.R = nanoseconds(c) + + def microseconds[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(microseconds) + def micros[C](c: C)(implicit ev: Classifier[C]): ev.R = microseconds(c) + def microsecond[C](c: C)(implicit ev: Classifier[C]): ev.R = microseconds(c) + def micro[C](c: C)(implicit ev: Classifier[C]): ev.R = microseconds(c) + + def milliseconds[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(milliseconds) + def millis[C](c: C)(implicit ev: Classifier[C]): ev.R = milliseconds(c) + def millisecond[C](c: C)(implicit ev: Classifier[C]): ev.R = milliseconds(c) + def milli[C](c: C)(implicit ev: Classifier[C]): ev.R = milliseconds(c) + + def seconds[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(seconds) + def second[C](c: C)(implicit ev: Classifier[C]): ev.R = seconds(c) + + def minutes[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(minutes) + def minute[C](c: C)(implicit ev: Classifier[C]): ev.R = minutes(c) + + def hours[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(hours) + def hour[C](c: C)(implicit ev: Classifier[C]): ev.R = hours(c) + + def days[C](c: C)(implicit ev: Classifier[C]): ev.R = ev.convert(days) + def day[C](c: C)(implicit ev: Classifier[C]): ev.R = days(c) +} + +/** + * This object just holds some cogs which make the DSL machine work, not for direct consumption. + */ +object DurationConversions { + trait Classifier[C] { + type R + def convert(d: FiniteDuration): R + } + + implicit object spanConvert extends Classifier[span.type] { + type R = FiniteDuration + def convert(d: FiniteDuration): FiniteDuration = d + } + + implicit object fromNowConvert extends Classifier[fromNow.type] { + type R = Deadline + def convert(d: FiniteDuration): Deadline = Deadline.now + d + } + +} diff --git a/scala2-library-cc/src/scala/concurrent/duration/package.scala b/scala2-library-cc/src/scala/concurrent/duration/package.scala new file mode 100644 index 000000000000..f81b8777f6d0 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/duration/package.scala @@ -0,0 +1,87 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent + +import scala.language.implicitConversions + +package object duration { + /** + * This object can be used as closing token if you prefer dot-less style but do not want + * to enable language.postfixOps: + * + * {{{ + * import scala.concurrent.duration._ + * + * val duration = 2 seconds span + * }}} + */ + object span + + /** + * This object can be used as closing token for declaring a deadline at some future point + * in time: + * + * {{{ + * import scala.concurrent.duration._ + * + * val deadline = 3 seconds fromNow + * }}} + */ + object fromNow + + type TimeUnit = java.util.concurrent.TimeUnit + final val DAYS = java.util.concurrent.TimeUnit.DAYS + final val HOURS = java.util.concurrent.TimeUnit.HOURS + final val MICROSECONDS = java.util.concurrent.TimeUnit.MICROSECONDS + final val MILLISECONDS = java.util.concurrent.TimeUnit.MILLISECONDS + final val MINUTES = java.util.concurrent.TimeUnit.MINUTES + final val NANOSECONDS = java.util.concurrent.TimeUnit.NANOSECONDS + final val SECONDS = java.util.concurrent.TimeUnit.SECONDS + + implicit def pairIntToDuration(p: (Int, TimeUnit)): Duration = Duration(p._1.toLong, p._2) + implicit def pairLongToDuration(p: (Long, TimeUnit)): FiniteDuration = Duration(p._1, p._2) + implicit def durationToPair(d: Duration): (Long, TimeUnit) = (d.length, d.unit) + + implicit final class DurationInt(private val n: Int) extends AnyVal with DurationConversions { + override protected def durationIn(unit: TimeUnit): FiniteDuration = Duration(n.toLong, unit) + } + + implicit final class DurationLong(private val n: Long) extends AnyVal with DurationConversions { + override protected def durationIn(unit: TimeUnit): FiniteDuration = Duration(n, unit) + } + + implicit final class DurationDouble(private val d: Double) extends AnyVal with DurationConversions { + override protected def durationIn(unit: TimeUnit): FiniteDuration = + Duration(d, unit) match { + case f: FiniteDuration => f + case _ => throw new IllegalArgumentException("Duration DSL not applicable to " + d) + } + } + + /* + * Avoid reflection based invocation by using non-duck type + */ + implicit final class IntMult(private val i: Int) extends AnyVal { + def *(d: Duration): Duration = d * i.toDouble + def *(d: FiniteDuration): FiniteDuration = d * i.toLong + } + + implicit final class LongMult(private val i: Long) extends AnyVal { + def *(d: Duration): Duration = d * i.toDouble + def *(d: FiniteDuration): FiniteDuration = d * i.toLong + } + + implicit final class DoubleMult(private val f: Double) extends AnyVal { + def *(d: Duration): Duration = d * f.toDouble + } +} diff --git a/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala b/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala new file mode 100644 index 000000000000..262a12b1b4b9 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala @@ -0,0 +1,137 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent.impl + +import java.util.concurrent.{ Semaphore, ForkJoinPool, ForkJoinWorkerThread, Callable, Executor, ExecutorService, ThreadFactory, TimeUnit } +import java.util.Collection +import scala.concurrent.{ BlockContext, ExecutionContext, CanAwait, ExecutionContextExecutor, ExecutionContextExecutorService } + +private[scala] class ExecutionContextImpl private[impl] (final val executor: Executor, final val reporter: Throwable => Unit) extends ExecutionContextExecutor { + require(executor ne null, "Executor must not be null") + override final def execute(runnable: Runnable): Unit = executor execute runnable + override final def reportFailure(t: Throwable): Unit = reporter(t) +} + +private[concurrent] object ExecutionContextImpl { + + final class DefaultThreadFactory( + final val daemonic: Boolean, + final val maxBlockers: Int, + final val prefix: String, + final val uncaught: Thread.UncaughtExceptionHandler) extends ThreadFactory with ForkJoinPool.ForkJoinWorkerThreadFactory { + + require(prefix ne null, "DefaultThreadFactory.prefix must be non null") + require(maxBlockers >= 0, "DefaultThreadFactory.maxBlockers must be greater-or-equal-to 0") + + private final val blockerPermits = new Semaphore(maxBlockers) + + @annotation.nowarn("cat=deprecation") + def wire[T <: Thread](thread: T): T = { + thread.setDaemon(daemonic) + thread.setUncaughtExceptionHandler(uncaught) + thread.setName(prefix + "-" + thread.getId()) + thread + } + + def newThread(runnable: Runnable): Thread = wire(new Thread(runnable)) + + def newThread(fjp: ForkJoinPool): ForkJoinWorkerThread = + wire(new ForkJoinWorkerThread(fjp) with BlockContext { + private[this] final var isBlocked: Boolean = false // This is only ever read & written if this thread is the current thread + final override def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = + if ((Thread.currentThread eq this) && !isBlocked && blockerPermits.tryAcquire()) { + try { + val b: ForkJoinPool.ManagedBlocker with (() => T) = + new ForkJoinPool.ManagedBlocker with (() => T) { + private[this] final var result: T = null.asInstanceOf[T] + private[this] final var done: Boolean = false + final override def block(): Boolean = { + if (!done) { + result = thunk // If this throws then it will stop blocking. + done = true + } + + isReleasable + } + + final override def isReleasable = done + final override def apply(): T = result + } + isBlocked = true + ForkJoinPool.managedBlock(b) + b() + } finally { + isBlocked = false + blockerPermits.release() + } + } else thunk // Unmanaged blocking + }) + } + + def createDefaultExecutorService(reporter: Throwable => Unit): ExecutionContextExecutorService = { + def getInt(name: String, default: String) = (try System.getProperty(name, default) catch { + case e: SecurityException => default + }) match { + case s if s.charAt(0) == 'x' => (Runtime.getRuntime.availableProcessors * s.substring(1).toDouble).ceil.toInt + case other => other.toInt + } + + val desiredParallelism = // A range between min and max given num + scala.math.min( + scala.math.max( + getInt("scala.concurrent.context.minThreads", "1"), + getInt("scala.concurrent.context.numThreads", "x1")), + getInt("scala.concurrent.context.maxThreads", "x1") + ) + + val threadFactory = new DefaultThreadFactory(daemonic = true, + maxBlockers = getInt("scala.concurrent.context.maxExtraThreads", "256"), + prefix = "scala-execution-context-global", + uncaught = (thread: Thread, cause: Throwable) => reporter(cause)) + + new ForkJoinPool(desiredParallelism, threadFactory, threadFactory.uncaught, true) with ExecutionContextExecutorService { + final override def reportFailure(cause: Throwable): Unit = + getUncaughtExceptionHandler() match { + case null => + case some => some.uncaughtException(Thread.currentThread, cause) + } + } + } + + def fromExecutor(e: Executor, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextExecutor = + e match { + case null => createDefaultExecutorService(reporter) + case some => new ExecutionContextImpl(some, reporter) + } + + def fromExecutorService(es: ExecutorService, reporter: Throwable => Unit = ExecutionContext.defaultReporter): + ExecutionContextExecutorService = es match { + case null => createDefaultExecutorService(reporter) + case some => + new ExecutionContextImpl(some, reporter) with ExecutionContextExecutorService { + private[this] final def asExecutorService: ExecutorService = executor.asInstanceOf[ExecutorService] + final override def shutdown() = asExecutorService.shutdown() + final override def shutdownNow() = asExecutorService.shutdownNow() + final override def isShutdown = asExecutorService.isShutdown + final override def isTerminated = asExecutorService.isTerminated + final override def awaitTermination(l: Long, timeUnit: TimeUnit) = asExecutorService.awaitTermination(l, timeUnit) + final override def submit[T](callable: Callable[T]) = asExecutorService.submit(callable) + final override def submit[T](runnable: Runnable, t: T) = asExecutorService.submit(runnable, t) + final override def submit(runnable: Runnable) = asExecutorService.submit(runnable) + final override def invokeAll[T](callables: Collection[_ <: Callable[T]]) = asExecutorService.invokeAll(callables) + final override def invokeAll[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = asExecutorService.invokeAll(callables, l, timeUnit) + final override def invokeAny[T](callables: Collection[_ <: Callable[T]]) = asExecutorService.invokeAny(callables) + final override def invokeAny[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = asExecutorService.invokeAny(callables, l, timeUnit) + } + } +} diff --git a/scala2-library-cc/src/scala/concurrent/impl/FutureConvertersImpl.scala b/scala2-library-cc/src/scala/concurrent/impl/FutureConvertersImpl.scala new file mode 100644 index 000000000000..a9eed4cbb055 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/impl/FutureConvertersImpl.scala @@ -0,0 +1,101 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent.impl + +import java.util.concurrent.{CompletableFuture, CompletionStage, TimeUnit} +import java.util.function.{BiConsumer, BiFunction, Consumer, Function => JFunction} + +import scala.concurrent.Future +import scala.concurrent.impl.Promise.DefaultPromise +import scala.util.{Failure, Success, Try} + +private[scala] object FutureConvertersImpl { + final class CF[T](val wrapped: Future[T]) extends CompletableFuture[T] with (Try[T] => Unit) { + override def apply(t: Try[T]): Unit = t match { + case Success(v) => complete(v) + case Failure(e) => completeExceptionally(e) + } + + // Ensure that completions of this future cannot hold the Scala Future's completer hostage + + override def thenApply[U](fn: JFunction[_ >: T, _ <: U]): CompletableFuture[U] = thenApplyAsync(fn) + + override def thenAccept(fn: Consumer[_ >: T]): CompletableFuture[Void] = thenAcceptAsync(fn) + + override def thenRun(fn: Runnable): CompletableFuture[Void] = thenRunAsync(fn) + + override def thenCombine[U, V](cs: CompletionStage[_ <: U], fn: BiFunction[_ >: T, _ >: U, _ <: V]): CompletableFuture[V] = thenCombineAsync(cs, fn) + + override def thenAcceptBoth[U](cs: CompletionStage[_ <: U], fn: BiConsumer[_ >: T, _ >: U]): CompletableFuture[Void] = thenAcceptBothAsync(cs, fn) + + override def runAfterBoth(cs: CompletionStage[_], fn: Runnable): CompletableFuture[Void] = runAfterBothAsync(cs, fn) + + override def applyToEither[U](cs: CompletionStage[_ <: T], fn: JFunction[_ >: T, U]): CompletableFuture[U] = applyToEitherAsync(cs, fn) + + override def acceptEither(cs: CompletionStage[_ <: T], fn: Consumer[_ >: T]): CompletableFuture[Void] = acceptEitherAsync(cs, fn) + + override def runAfterEither(cs: CompletionStage[_], fn: Runnable): CompletableFuture[Void] = runAfterEitherAsync(cs, fn) + + override def thenCompose[U](fn: JFunction[_ >: T, _ <: CompletionStage[U]]): CompletableFuture[U] = thenComposeAsync(fn) + + override def whenComplete(fn: BiConsumer[_ >: T, _ >: Throwable]): CompletableFuture[T] = whenCompleteAsync(fn) + + override def handle[U](fn: BiFunction[_ >: T, Throwable, _ <: U]): CompletableFuture[U] = handleAsync(fn) + + override def exceptionally(fn: JFunction[Throwable, _ <: T]): CompletableFuture[T] = { + val cf = new CompletableFuture[T] + whenCompleteAsync((t, e) => { + if (e == null) cf.complete(t) + else { + val n: AnyRef = + try { + fn(e).asInstanceOf[AnyRef] + } catch { + case thr: Throwable => + cf.completeExceptionally(thr) + this + } + if (n ne this) cf.complete(n.asInstanceOf[T]) + } + } + ) + cf + } + + /** + * @inheritdoc + * + * WARNING: completing the result of this method will not complete the underlying + * Scala Future or Promise (ie, the one that that was passed to `toJava`.) + */ + override def toCompletableFuture: CompletableFuture[T] = this + + override def obtrudeValue(value: T): Unit = throw new UnsupportedOperationException("obtrudeValue may not be used on the result of toJava(scalaFuture)") + + override def obtrudeException(ex: Throwable): Unit = throw new UnsupportedOperationException("obtrudeException may not be used on the result of toJava(scalaFuture)") + + override def get(): T = scala.concurrent.blocking(super.get()) + + override def get(timeout: Long, unit: TimeUnit): T = scala.concurrent.blocking(super.get(timeout, unit)) + + override def toString(): String = super[CompletableFuture].toString + } + + final class P[T](val wrapped: CompletionStage[T]) extends DefaultPromise[T] with BiFunction[T, Throwable, Unit] { + override def apply(v: T, e: Throwable): Unit = { + if (e == null) success(v) + else failure(e) + } + } +} + diff --git a/scala2-library-cc/src/scala/concurrent/impl/Promise.scala b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala new file mode 100644 index 000000000000..a479cad06902 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala @@ -0,0 +1,511 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala.concurrent.impl + +import scala.concurrent.{Batchable, CanAwait, ExecutionContext, ExecutionException, Future, TimeoutException} +import scala.concurrent.duration.Duration +import scala.annotation.{nowarn, switch, tailrec} +import scala.util.control.{ControlThrowable, NonFatal} +import scala.util.{Failure, Success, Try} +import scala.runtime.NonLocalReturnControl +import java.util.concurrent.locks.AbstractQueuedSynchronizer +import java.util.concurrent.atomic.AtomicReference +import java.util.Objects.requireNonNull +import java.io.{IOException, NotSerializableException, ObjectInputStream, ObjectOutputStream} + +/** + * Latch used to implement waiting on a DefaultPromise's result. + * + * Inspired by: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/locks/AbstractQueuedSynchronizer.java + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * https://creativecommons.org/publicdomain/zero/1.0/ + */ +private[impl] final class CompletionLatch[T] extends AbstractQueuedSynchronizer with (Try[T] => Unit) { + //@volatie not needed since we use acquire/release + /*@volatile*/ private[this] var _result: Try[T] = null + final def result: Try[T] = _result + override protected def tryAcquireShared(ignored: Int): Int = if (getState != 0) 1 else -1 + override protected def tryReleaseShared(ignore: Int): Boolean = { + setState(1) + true + } + override def apply(value: Try[T]): Unit = { + _result = value // This line MUST go before releaseShared + releaseShared(1) + } +} + +private[concurrent] object Promise { + /** + * Link represents a completion dependency between 2 DefaultPromises. + * As the DefaultPromise referred to by a Link can itself be linked to another promise + * `relink` traverses such chains and compresses them so that the link always points + * to the root of the dependency chain. + * + * In order to conserve memory, the owner of a Link (a DefaultPromise) is not stored + * on the Link, but is instead passed in as a parameter to the operation(s). + * + * If when compressing a chain of Links it is discovered that the root has been completed, + * the `owner`'s value is completed with that value, and the Link chain is discarded. + **/ + private[concurrent] final class Link[T](to: DefaultPromise[T]) extends AtomicReference[DefaultPromise[T]](to) { + /** + * Compresses this chain and returns the currently known root of this chain of Links. + **/ + final def promise(owner: DefaultPromise[T]): DefaultPromise[T] = { + val c = get() + compressed(current = c, target = c, owner = owner) + } + + /** + * The combination of traversing and possibly unlinking of a given `target` DefaultPromise. + **/ + @inline @tailrec private[this] final def compressed(current: DefaultPromise[T], target: DefaultPromise[T], owner: DefaultPromise[T]): DefaultPromise[T] = { + val value = target.get() + if (value.isInstanceOf[Callbacks[_]]) { + if (compareAndSet(current, target)) target // Link + else compressed(current = get(), target = target, owner = owner) // Retry + } else if (value.isInstanceOf[Link[_]]) compressed(current = current, target = value.asInstanceOf[Link[T]].get(), owner = owner) // Compress + else /*if (value.isInstanceOf[Try[T]])*/ { + owner.unlink(value.asInstanceOf[Try[T]]) // Discard links + owner + } + } + } + + /** + * The process of "resolving" a Try is to validate that it only contains + * those values which makes sense in the context of Futures. + **/ + // requireNonNull is paramount to guard against null completions + private[this] final def resolve[T](value: Try[T]): Try[T] = + if (requireNonNull(value).isInstanceOf[Success[T]]) value + else { + val t = value.asInstanceOf[Failure[T]].exception + if (t.isInstanceOf[ControlThrowable] || t.isInstanceOf[InterruptedException] || t.isInstanceOf[Error]) { + if (t.isInstanceOf[NonLocalReturnControl[T @unchecked]]) + Success(t.asInstanceOf[NonLocalReturnControl[T]].value) + else + Failure(new ExecutionException("Boxed Exception", t)) + } else value + } + + // Left non-final to enable addition of extra fields by Java/Scala converters in scala-java8-compat. + class DefaultPromise[T] private[this] (initial: AnyRef) extends AtomicReference[AnyRef](initial) with scala.concurrent.Promise[T] with scala.concurrent.Future[T] with (Try[T] => Unit) { + /** + * Constructs a new, completed, Promise. + */ + final def this(result: Try[T]) = this(resolve(result): AnyRef) + + /** + * Constructs a new, un-completed, Promise. + */ + final def this() = this(Noop: AnyRef) + + /** + * WARNING: the `resolved` value needs to have been pre-resolved using `resolve()` + * INTERNAL API + */ + override final def apply(resolved: Try[T]): Unit = + tryComplete0(get(), resolved) + + /** + * Returns the associated `Future` with this `Promise` + */ + override final def future: Future[T] = this + + override final def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] = + dispatchOrAddCallbacks(get(), new Transformation[T, S](Xform_transform, f, executor)) + + override final def transformWith[S](f: Try[T] => Future[S])(implicit executor: ExecutionContext): Future[S] = + dispatchOrAddCallbacks(get(), new Transformation[T, S](Xform_transformWith, f, executor)) + + override final def zipWith[U, R](that: Future[U])(f: (T, U) => R)(implicit executor: ExecutionContext): Future[R] = { + val state = get() + if (state.isInstanceOf[Try[_]]) { + if (state.asInstanceOf[Try[T]].isFailure) this.asInstanceOf[Future[R]] + else { + val l = state.asInstanceOf[Success[T]].get + that.map(r => f(l, r)) + } + } else { + val buffer = new AtomicReference[Success[Any]]() + val zipped = new DefaultPromise[R]() + + val thisF: Try[T] => Unit = { + case left: Success[_] => + val right = buffer.getAndSet(left).asInstanceOf[Success[U]] + if (right ne null) + zipped.tryComplete(try Success(f(left.get, right.get)) catch { case e if NonFatal(e) => Failure(e) }) + case f => // Can only be Failure + zipped.tryComplete(f.asInstanceOf[Failure[R]]) + } + + val thatF: Try[U] => Unit = { + case right: Success[_] => + val left = buffer.getAndSet(right).asInstanceOf[Success[T]] + if (left ne null) + zipped.tryComplete(try Success(f(left.get, right.get)) catch { case e if NonFatal(e) => Failure(e) }) + case f => // Can only be Failure + zipped.tryComplete(f.asInstanceOf[Failure[R]]) + } + // Cheaper than this.onComplete since we already polled the state + this.dispatchOrAddCallbacks(state, new Transformation[T, Unit](Xform_onComplete, thisF, executor)) + that.onComplete(thatF) + zipped.future + } + } + + override final def foreach[U](f: T => U)(implicit executor: ExecutionContext): Unit = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, Unit](Xform_foreach, f, executor)) + } + + override final def flatMap[S](f: T => Future[S])(implicit executor: ExecutionContext): Future[S] = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, S](Xform_flatMap, f, executor)) + else this.asInstanceOf[Future[S]] + } + + override final def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S] = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, S](Xform_map, f, executor)) + else this.asInstanceOf[Future[S]] + } + + override final def filter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, T](Xform_filter, p, executor)) // Short-circuit if we get a Success + else this + } + + override final def collect[S](pf: PartialFunction[T, S])(implicit executor: ExecutionContext): Future[S] = { + val state = get() + if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, S](Xform_collect, pf, executor)) // Short-circuit if we get a Success + else this.asInstanceOf[Future[S]] + } + + override final def recoverWith[U >: T](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = { + val state = get() + if (!state.isInstanceOf[Success[_]]) dispatchOrAddCallbacks(state, new Transformation[T, U](Xform_recoverWith, pf, executor)) // Short-circuit if we get a Failure + else this.asInstanceOf[Future[U]] + } + + override final def recover[U >: T](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = { + val state = get() + if (!state.isInstanceOf[Success[_]]) dispatchOrAddCallbacks(state, new Transformation[T, U](Xform_recover, pf, executor)) // Short-circuit if we get a Failure + else this.asInstanceOf[Future[U]] + } + + override final def mapTo[S](implicit tag: scala.reflect.ClassTag[S]): Future[S] = + if (!get().isInstanceOf[Failure[_]]) super[Future].mapTo[S](tag) // Short-circuit if we get a Success + else this.asInstanceOf[Future[S]] + + + override final def onComplete[U](func: Try[T] => U)(implicit executor: ExecutionContext): Unit = + dispatchOrAddCallbacks(get(), new Transformation[T, Unit](Xform_onComplete, func, executor)) + + override final def failed: Future[Throwable] = + if (!get().isInstanceOf[Success[_]]) super.failed + else Future.failedFailureFuture // Cached instance in case of already known success + + @tailrec override final def toString: String = { + val state = get() + if (state.isInstanceOf[Try[_]]) "Future("+state+")" + else if (state.isInstanceOf[Link[_]]) state.asInstanceOf[Link[T]].promise(this).toString + else /*if (state.isInstanceOf[Callbacks[T]]) */ "Future()" + } + + private[this] final def tryAwait0(atMost: Duration): Try[T] = + if (atMost ne Duration.Undefined) { + val v = value0 + if (v ne null) v + else { + val r = + if (atMost <= Duration.Zero) null + else { + val l = new CompletionLatch[T]() + onComplete(l)(ExecutionContext.parasitic) + + if (atMost.isFinite) + l.tryAcquireSharedNanos(1, atMost.toNanos) + else + l.acquireSharedInterruptibly(1) + + l.result + } + if (r ne null) r + else throw new TimeoutException("Future timed out after [" + atMost + "]") + } + } else throw new IllegalArgumentException("Cannot wait for Undefined duration of time") + + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + final def ready(atMost: Duration)(implicit permit: CanAwait): this.type = { + tryAwait0(atMost) + this + } + + @throws(classOf[Exception]) + final def result(atMost: Duration)(implicit permit: CanAwait): T = + tryAwait0(atMost).get // returns the value, or throws the contained exception + + override final def isCompleted: Boolean = value0 ne null + + override final def value: Option[Try[T]] = Option(value0) + + @tailrec // returns null if not completed + private final def value0: Try[T] = { + val state = get() + if (state.isInstanceOf[Try[_]]) state.asInstanceOf[Try[T]] + else if (state.isInstanceOf[Link[_]]) state.asInstanceOf[Link[T]].promise(this).value0 + else /*if (state.isInstanceOf[Callbacks[T]])*/ null + } + + override final def tryComplete(value: Try[T]): Boolean = { + val state = get() + if (state.isInstanceOf[Try[_]]) false + else tryComplete0(state, resolve(value)) + } + + @tailrec // WARNING: important that the supplied Try really is resolve():d + private[Promise] final def tryComplete0(state: AnyRef, resolved: Try[T]): Boolean = + if (state.isInstanceOf[Callbacks[_]]) { + if (compareAndSet(state, resolved)) { + if (state ne Noop) submitWithValue(state.asInstanceOf[Callbacks[T]], resolved) + true + } else tryComplete0(get(), resolved) + } else if (state.isInstanceOf[Link[_]]) { + val p = state.asInstanceOf[Link[T]].promise(this) // If this returns owner/this, we are in a completed link + (p ne this) && p.tryComplete0(p.get(), resolved) // Use this to get tailcall optimization and avoid re-resolution + } else /* if(state.isInstanceOf[Try[T]]) */ false + + override final def completeWith(other: Future[T]): this.type = { + if (other ne this) { + val state = get() + if (!state.isInstanceOf[Try[_]]) { + val resolved = if (other.isInstanceOf[DefaultPromise[_]]) other.asInstanceOf[DefaultPromise[T]].value0 else other.value.orNull + if (resolved ne null) tryComplete0(state, resolved) + else other.onComplete(this)(ExecutionContext.parasitic) + } + } + + this + } + + /** Tries to add the callback, if already completed, it dispatches the callback to be executed. + * Used by `onComplete()` to add callbacks to a promise and by `link()` to transfer callbacks + * to the root promise when linking two promises together. + */ + @tailrec private final def dispatchOrAddCallbacks[C <: Callbacks[T]](state: AnyRef, callbacks: C): C = + if (state.isInstanceOf[Try[_]]) { + submitWithValue(callbacks, state.asInstanceOf[Try[T]]) // invariant: callbacks should never be Noop here + callbacks + } else if (state.isInstanceOf[Callbacks[_]]) { + if(compareAndSet(state, if (state ne Noop) concatCallbacks(callbacks, state.asInstanceOf[Callbacks[T]]) else callbacks)) callbacks + else dispatchOrAddCallbacks(get(), callbacks) + } else /*if (state.isInstanceOf[Link[T]])*/ { + val p = state.asInstanceOf[Link[T]].promise(this) + p.dispatchOrAddCallbacks(p.get(), callbacks) + } + + // IMPORTANT: Noop should never be passed in here, neither as left OR as right + @tailrec private[this] final def concatCallbacks(left: Callbacks[T], right: Callbacks[T]): Callbacks[T] = + if (left.isInstanceOf[Transformation[T,_]]) new ManyCallbacks[T](left.asInstanceOf[Transformation[T,_]], right) + else /*if (left.isInstanceOf[ManyCallbacks[T]) */ { // This should only happen when linking + val m = left.asInstanceOf[ManyCallbacks[T]] + concatCallbacks(m.rest, new ManyCallbacks(m.first, right)) + } + + // IMPORTANT: Noop should not be passed in here, `callbacks` cannot be null + @tailrec + private[this] final def submitWithValue(callbacks: Callbacks[T], resolved: Try[T]): Unit = + if(callbacks.isInstanceOf[ManyCallbacks[T]]) { + val m: ManyCallbacks[T] = callbacks.asInstanceOf[ManyCallbacks[T]] + m.first.submitWithValue(resolved) + submitWithValue(m.rest, resolved) + } else { + callbacks.asInstanceOf[Transformation[T, _]].submitWithValue(resolved) + } + + /** Link this promise to the root of another promise. + */ + @tailrec private[concurrent] final def linkRootOf(target: DefaultPromise[T], link: Link[T]): Unit = + if (this ne target) { + val state = get() + if (state.isInstanceOf[Try[_]]) { + if(!target.tryComplete0(target.get(), state.asInstanceOf[Try[T]])) + throw new IllegalStateException("Cannot link completed promises together") + } else if (state.isInstanceOf[Callbacks[_]]) { + val l = if (link ne null) link else new Link(target) + val p = l.promise(this) + if ((this ne p) && compareAndSet(state, l)) { + if (state ne Noop) p.dispatchOrAddCallbacks(p.get(), state.asInstanceOf[Callbacks[T]]) // Noop-check is important here + } else linkRootOf(p, l) + } else /* if (state.isInstanceOf[Link[T]]) */ + state.asInstanceOf[Link[T]].promise(this).linkRootOf(target, link) + } + + /** + * Unlinks (removes) the link chain if the root is discovered to be already completed, + * and completes the `owner` with that result. + **/ + @tailrec private[concurrent] final def unlink(resolved: Try[T]): Unit = { + val state = get() + if (state.isInstanceOf[Link[_]]) { + val next = if (compareAndSet(state, resolved)) state.asInstanceOf[Link[T]].get() else this + next.unlink(resolved) + } else tryComplete0(state, resolved) + } + + @throws[IOException] + private def writeObject(out: ObjectOutputStream): Unit = + throw new NotSerializableException("Promises and Futures cannot be serialized") + + @throws[IOException] + @throws[ClassNotFoundException] + private def readObject(in: ObjectInputStream): Unit = + throw new NotSerializableException("Promises and Futures cannot be deserialized") + } + + // Constant byte tags for unpacking transformation function inputs or outputs + // These need to be Ints to get compiled into constants. + final val Xform_noop = 0 + final val Xform_map = 1 + final val Xform_flatMap = 2 + final val Xform_transform = 3 + final val Xform_transformWith = 4 + final val Xform_foreach = 5 + final val Xform_onComplete = 6 + final val Xform_recover = 7 + final val Xform_recoverWith = 8 + final val Xform_filter = 9 + final val Xform_collect = 10 + + /* Marker trait + */ + sealed trait Callbacks[-T] + + final class ManyCallbacks[-T](final val first: Transformation[T,_], final val rest: Callbacks[T]) extends Callbacks[T] { + override final def toString: String = "ManyCallbacks" + } + + private[this] final val Noop = new Transformation[Nothing, Nothing](Xform_noop, null, ExecutionContext.parasitic) + + /** + * A Transformation[F, T] receives an F (it is a Callback[F]) and applies a transformation function to that F, + * Producing a value of type T (it is a Promise[T]). + * In order to conserve allocations, indirections, and avoid introducing bi/mega-morphicity the transformation + * function's type parameters are erased, and the _xform tag will be used to reify them. + **/ + final class Transformation[-F, T] private[this] ( + private[this] final var _fun: Any => Any, + private[this] final var _ec: ExecutionContext, + private[this] final var _arg: Try[F], + private[this] final val _xform: Int + ) extends DefaultPromise[T]() with Callbacks[F] with Runnable with Batchable { + final def this(xform: Int, f: _ => _, ec: ExecutionContext) = + this(f.asInstanceOf[Any => Any], ec.prepare(): @nowarn("cat=deprecation"), null, xform) + + final def benefitsFromBatching: Boolean = _xform != Xform_onComplete && _xform != Xform_foreach + + // Gets invoked when a value is available, schedules it to be run():ed by the ExecutionContext + // submitWithValue *happens-before* run(), through ExecutionContext.execute. + // Invariant: _arg is `null`, _ec is non-null. `this` ne Noop. + // requireNonNull(resolved) will hold as guarded by `resolve` + final def submitWithValue(resolved: Try[F]): this.type = { + _arg = resolved + val e = _ec + try e.execute(this) /* Safe publication of _arg, _fun, _ec */ + catch { + case t: Throwable => + _fun = null // allow to GC + _arg = null // see above + _ec = null // see above again + handleFailure(t, e) + } + + this + } + + private[this] final def handleFailure(t: Throwable, e: ExecutionContext): Unit = { + val wasInterrupted = t.isInstanceOf[InterruptedException] + if (wasInterrupted || NonFatal(t)) { + val completed = tryComplete0(get(), resolve(Failure(t))) + if (completed && wasInterrupted) Thread.currentThread.interrupt() + + // Report or rethrow failures which are unlikely to otherwise be noticed + if (_xform == Xform_foreach || _xform == Xform_onComplete || !completed) + e.reportFailure(t) + } else throw t + } + + // Gets invoked by the ExecutionContext, when we have a value to transform. + override final def run(): Unit = { + val v = _arg + val fun = _fun + val ec = _ec + _fun = null // allow to GC + _arg = null // see above + _ec = null // see above + try { + val resolvedResult: Try[_] = + (_xform: @switch) match { + case Xform_noop => + null + case Xform_map => + if (v.isInstanceOf[Success[F]]) Success(fun(v.get)) else v // Faster than `resolve(v map fun)` + case Xform_flatMap => + if (v.isInstanceOf[Success[F]]) { + val f = fun(v.get) + if (f.isInstanceOf[DefaultPromise[_]]) f.asInstanceOf[DefaultPromise[T]].linkRootOf(this, null) else completeWith(f.asInstanceOf[Future[T]]) + null + } else v + case Xform_transform => + resolve(fun(v).asInstanceOf[Try[T]]) + case Xform_transformWith => + val f = fun(v) + if (f.isInstanceOf[DefaultPromise[_]]) f.asInstanceOf[DefaultPromise[T]].linkRootOf(this, null) else completeWith(f.asInstanceOf[Future[T]]) + null + case Xform_foreach => + v.foreach(fun) + null + case Xform_onComplete => + fun(v) + null + case Xform_recover => + if (v.isInstanceOf[Failure[_]]) resolve(v.recover(fun.asInstanceOf[PartialFunction[Throwable, F]])) else v //recover F=:=T + case Xform_recoverWith => + if (v.isInstanceOf[Failure[F]]) { + val f = fun.asInstanceOf[PartialFunction[Throwable, Future[T]]].applyOrElse(v.asInstanceOf[Failure[F]].exception, Future.recoverWithFailed) + if (f ne Future.recoverWithFailedMarker) { + if (f.isInstanceOf[DefaultPromise[T]]) f.asInstanceOf[DefaultPromise[T]].linkRootOf(this, null) else completeWith(f.asInstanceOf[Future[T]]) + null + } else v + } else v + case Xform_filter => + if (v.isInstanceOf[Failure[F]] || fun.asInstanceOf[F => Boolean](v.get)) v else Future.filterFailure + case Xform_collect => + if (v.isInstanceOf[Success[F]]) Success(fun.asInstanceOf[PartialFunction[F, T]].applyOrElse(v.get, Future.collectFailed)) else v + case _ => + Failure(new IllegalStateException("BUG: encountered transformation promise with illegal type: " + _xform)) // Safe not to `resolve` + } + if (resolvedResult ne null) + tryComplete0(get(), resolvedResult.asInstanceOf[Try[T]]) // T is erased anyway so we won't have any use for it above + } catch { + case t: Throwable => handleFailure(t, ec) + } + } + } +} diff --git a/scala2-library-cc/src/scala/concurrent/package.scala b/scala2-library-cc/src/scala/concurrent/package.scala new file mode 100644 index 000000000000..d648a1c90a15 --- /dev/null +++ b/scala2-library-cc/src/scala/concurrent/package.scala @@ -0,0 +1,204 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala + +import scala.concurrent.duration.Duration +import scala.annotation.implicitNotFound + +/** This package object contains primitives for concurrent and parallel programming. + * + * == Guide == + * + * A more detailed guide to Futures and Promises, including discussion and examples + * can be found at + * [[https://docs.scala-lang.org/overviews/core/futures.html]]. + * + * == Common Imports == + * + * When working with Futures, you will often find that importing the whole concurrent + * package is convenient: + * + * {{{ + * import scala.concurrent._ + * }}} + * + * When using things like `Future`s, it is often required to have an implicit `ExecutionContext` + * in scope. The general advice for these implicits are as follows. + * + * If the code in question is a class or method definition, and no `ExecutionContext` is available, + * request one from the caller by adding an implicit parameter list: + * + * {{{ + * def myMethod(myParam: MyType)(implicit ec: ExecutionContext) = … + * //Or + * class MyClass(myParam: MyType)(implicit ec: ExecutionContext) { … } + * }}} + * + * This allows the caller of the method, or creator of the instance of the class, to decide which + * `ExecutionContext` should be used. + * + * For typical REPL usage and experimentation, importing the global `ExecutionContext` is often desired. + * + * {{{ + * import scala.concurrent.ExcutionContext.Implicits.global + * }}} + * + * == Specifying Durations == + * + * Operations often require a duration to be specified. A duration DSL is available + * to make defining these easier: + * + * {{{ + * import scala.concurrent.duration._ + * val d: Duration = 10.seconds + * }}} + * + * == Using Futures For Non-blocking Computation == + * + * Basic use of futures is easy with the factory method on Future, which executes a + * provided function asynchronously, handing you back a future result of that function + * without blocking the current thread. In order to create the Future you will need + * either an implicit or explicit ExecutionContext to be provided: + * + * {{{ + * import scala.concurrent._ + * import ExecutionContext.Implicits.global // implicit execution context + * + * val firstZebra: Future[Int] = Future { + * val words = Files.readAllLines("/etc/dictionaries-common/words").asScala + * words.indexOfSlice("zebra") + * } + * }}} + * + * == Avoid Blocking == + * + * Although blocking is possible in order to await results (with a mandatory timeout duration): + * + * {{{ + * import scala.concurrent.duration._ + * Await.result(firstZebra, 10.seconds) + * }}} + * + * and although this is sometimes necessary to do, in particular for testing purposes, blocking + * in general is discouraged when working with Futures and concurrency in order to avoid + * potential deadlocks and improve performance. Instead, use callbacks or combinators to + * remain in the future domain: + * + * {{{ + * val animalRange: Future[Int] = for { + * aardvark <- firstAardvark + * zebra <- firstZebra + * } yield zebra - aardvark + * + * animalRange.onSuccess { + * case x if x > 500000 => println("It's a long way from Aardvark to Zebra") + * } + * }}} + */ +package object concurrent { + type ExecutionException = java.util.concurrent.ExecutionException + type CancellationException = java.util.concurrent.CancellationException + type TimeoutException = java.util.concurrent.TimeoutException + + /** Used to designate a piece of code which potentially blocks, allowing the current [[BlockContext]] to adjust + * the runtime's behavior. + * Properly marking blocking code may improve performance or avoid deadlocks. + * + * Blocking on an [[Awaitable]] should be done using [[Await.result]] instead of `blocking`. + * + * @param body A piece of code which contains potentially blocking or long running calls. + * @throws CancellationException if the computation was cancelled + * @throws InterruptedException in the case that a wait within the blocking `body` was interrupted + */ + @throws(classOf[Exception]) + final def blocking[T](body: => T): T = BlockContext.current.blockOn(body)(scala.concurrent.AwaitPermission) +} + +package concurrent { + /** + * This marker trait is used by [[Await]] to ensure that [[Awaitable.ready]] and [[Awaitable.result]] + * are not directly called by user code. An implicit instance of this trait is only available when + * user code is currently calling the methods on [[Await]]. + */ + @implicitNotFound("Don't call `Awaitable` methods directly, use the `Await` object.") + sealed trait CanAwait + + /** + * Internal usage only, implementation detail. + */ + private[concurrent] object AwaitPermission extends CanAwait + + /** + * `Await` is what is used to ensure proper handling of blocking for `Awaitable` instances. + * + * While occasionally useful, e.g. for testing, it is recommended that you avoid Await whenever possible— + * instead favoring combinators and/or callbacks. + * Await's `result` and `ready` methods will block the calling thread's execution until they return, + * which will cause performance degradation, and possibly, deadlock issues. + */ + object Await { + /** + * Await the "completed" state of an `Awaitable`. + * + * Although this method is blocking, the internal use of [[scala.concurrent.blocking blocking]] ensures that + * the underlying [[ExecutionContext]] is given an opportunity to properly manage the blocking. + * + * WARNING: It is strongly discouraged to supply lengthy timeouts since the progress of the calling thread will be + * suspended—blocked—until either the `Awaitable` becomes ready or the timeout expires. + * + * @param awaitable + * the `Awaitable` to be awaited + * @param atMost + * maximum wait time, which may be negative (no waiting is done), + * [[scala.concurrent.duration.Duration.Inf Duration.Inf]] for unbounded waiting, or a finite positive + * duration + * @return the `awaitable` + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws TimeoutException if after waiting for the specified time this `Awaitable` is still not ready + * @throws IllegalArgumentException if `atMost` is [[scala.concurrent.duration.Duration.Undefined Duration.Undefined]] + */ + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + final def ready[T](awaitable: Awaitable[T], atMost: Duration): awaitable.type = awaitable match { + case f: Future[T] if f.isCompleted => awaitable.ready(atMost)(AwaitPermission) + case _ => blocking(awaitable.ready(atMost)(AwaitPermission)) + } + + /** + * Await and return the result (of type `T`) of an `Awaitable`. + * + * Although this method is blocking, the internal use of [[scala.concurrent.blocking blocking]] ensures that + * the underlying [[ExecutionContext]] is given an opportunity to properly manage the blocking. + * + * WARNING: It is strongly discouraged to supply lengthy timeouts since the progress of the calling thread will be + * suspended—blocked—until either the `Awaitable` has a result or the timeout expires. + * + * @param awaitable + * the `Awaitable` to be awaited + * @param atMost + * maximum wait time, which may be negative (no waiting is done), + * [[scala.concurrent.duration.Duration.Inf Duration.Inf]] for unbounded waiting, or a finite positive + * duration + * @return the result value if `awaitable` is completed within the specific maximum wait time + * @throws InterruptedException if the current thread is interrupted while waiting + * @throws TimeoutException if after waiting for the specified time `awaitable` is still not ready + * @throws IllegalArgumentException if `atMost` is [[scala.concurrent.duration.Duration.Undefined Duration.Undefined]] + */ + @throws(classOf[TimeoutException]) + @throws(classOf[InterruptedException]) + final def result[T](awaitable: Awaitable[T], atMost: Duration): T = awaitable match { + case f: Future[T] if f.isCompleted => f.result(atMost)(AwaitPermission) + case _ => blocking(awaitable.result(atMost)(AwaitPermission)) + } + } +} From 0b5aa8929378fd073adaab3d7848865da3bbe862 Mon Sep 17 00:00:00 2001 From: 05st Date: Thu, 20 Mar 2025 15:40:40 -0400 Subject: [PATCH 2/5] Port concurrency classes to use capture checking --- .../src/scala/concurrent/Awaitable.scala | 2 +- .../scala/concurrent/BatchingExecutor.scala | 6 ++++++ .../src/scala/concurrent/BlockContext.scala | 2 ++ .../src/scala/concurrent/Channel.scala | 2 ++ .../src/scala/concurrent/DelayedLazyVal.scala | 4 +++- .../scala/concurrent/ExecutionContext.scala | 16 ++++++++------- .../src/scala/concurrent/Future.scala | 6 ++++-- .../scala/concurrent/JavaConversions.scala | 2 ++ .../src/scala/concurrent/Promise.scala | 2 ++ .../src/scala/concurrent/SyncChannel.scala | 2 ++ .../src/scala/concurrent/SyncVar.scala | 2 ++ .../scala/concurrent/duration/Deadline.scala | 2 ++ .../scala/concurrent/duration/Duration.scala | 2 ++ .../duration/DurationConversions.scala | 2 ++ .../scala/concurrent/duration/package.scala | 2 ++ .../impl/ExecutionContextImpl.scala | 20 +++++++++++-------- .../impl/FutureConvertersImpl.scala | 2 ++ .../src/scala/concurrent/impl/Promise.scala | 12 ++++++----- .../src/scala/concurrent/package.scala | 2 ++ 19 files changed, 66 insertions(+), 24 deletions(-) diff --git a/scala2-library-cc/src/scala/concurrent/Awaitable.scala b/scala2-library-cc/src/scala/concurrent/Awaitable.scala index 634b9a91dafd..94edf27b8eb7 100644 --- a/scala2-library-cc/src/scala/concurrent/Awaitable.scala +++ b/scala2-library-cc/src/scala/concurrent/Awaitable.scala @@ -14,7 +14,7 @@ package scala.concurrent import scala.concurrent.duration.Duration - +import language.experimental.captureChecking /** * An object that may eventually be completed with a result value of type `T` which may be diff --git a/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala b/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala index ac197c89f8c1..31f7e4e8f998 100644 --- a/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala +++ b/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala @@ -17,6 +17,8 @@ import java.util.Objects import scala.util.control.NonFatal import scala.annotation.{switch, tailrec} +import language.experimental.captureChecking + /** * Marker trait to indicate that a Runnable is Batchable by BatchingExecutors */ @@ -24,6 +26,10 @@ trait Batchable { self: Runnable => } +trait Batchable2 { + +} + private[concurrent] object BatchingExecutorStatics { final val emptyBatchArray: Array[Runnable] = new Array[Runnable](0) diff --git a/scala2-library-cc/src/scala/concurrent/BlockContext.scala b/scala2-library-cc/src/scala/concurrent/BlockContext.scala index 37483c307fd0..5f0ba025854a 100644 --- a/scala2-library-cc/src/scala/concurrent/BlockContext.scala +++ b/scala2-library-cc/src/scala/concurrent/BlockContext.scala @@ -12,6 +12,8 @@ package scala.concurrent +import language.experimental.captureChecking + /** * A context to be notified by [[scala.concurrent.blocking]] when * a thread is about to block. In effect this trait provides diff --git a/scala2-library-cc/src/scala/concurrent/Channel.scala b/scala2-library-cc/src/scala/concurrent/Channel.scala index a9ada60e3da0..8f36b3d09cd0 100644 --- a/scala2-library-cc/src/scala/concurrent/Channel.scala +++ b/scala2-library-cc/src/scala/concurrent/Channel.scala @@ -12,6 +12,8 @@ package scala.concurrent +import language.experimental.captureChecking + /** This class provides a simple FIFO queue of data objects, * which are read by one or more reader threads. * diff --git a/scala2-library-cc/src/scala/concurrent/DelayedLazyVal.scala b/scala2-library-cc/src/scala/concurrent/DelayedLazyVal.scala index 8d5e2c278027..da5cb21feef0 100644 --- a/scala2-library-cc/src/scala/concurrent/DelayedLazyVal.scala +++ b/scala2-library-cc/src/scala/concurrent/DelayedLazyVal.scala @@ -12,6 +12,8 @@ package scala.concurrent +import language.experimental.captureChecking + /** A `DelayedLazyVal` is a wrapper for lengthy computations which have a * valid partially computed result. * @@ -24,7 +26,7 @@ package scala.concurrent * @param body the computation to run to completion in another thread */ @deprecated("`DelayedLazyVal` Will be removed in the future.", since = "2.13.0") -class DelayedLazyVal[T](f: () => T, body: => Unit)(implicit exec: ExecutionContext){ +class DelayedLazyVal[T](f: () => T, body: -> Unit)(implicit exec: ExecutionContext) { @volatile private[this] var _isDone = false private[this] lazy val complete = f() diff --git a/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala index b8fb0a6639f6..0e5572a80d44 100644 --- a/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala +++ b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala @@ -15,6 +15,8 @@ package scala.concurrent import java.util.concurrent.{ ExecutorService, Executor } import scala.annotation.implicitNotFound +import language.experimental.captureChecking + /** * An `ExecutionContext` can execute program logic asynchronously, * typically but not necessarily on a thread pool. @@ -38,7 +40,7 @@ import scala.annotation.implicitNotFound * `scala.concurrent.ExecutionContext.Implicits.global`. * The recommended approach is to add `(implicit ec: ExecutionContext)` to methods, * or class constructor parameters, which need an `ExecutionContext`. - * + * * Then locally import a specific `ExecutionContext` in one place for the entire * application or module, passing it implicitly to individual methods. * Alternatively define a local implicit val with the required `ExecutionContext`. @@ -100,7 +102,7 @@ trait ExecutionContext { */ @deprecated("preparation of ExecutionContexts will be removed", "2.12.0") // This cannot be removed until there is a suitable replacement - def prepare(): ExecutionContext = this + def prepare(): ExecutionContext^{this} = this } /** @@ -211,7 +213,7 @@ object ExecutionContext { * * Do *not* call any blocking code in the `Runnable`s submitted to this `ExecutionContext` * as it will prevent progress by other enqueued `Runnable`s and the calling `Thread`. - * + * * Symptoms of misuse of this `ExecutionContext` include, but are not limited to, deadlocks * and severe performance problems. * @@ -252,7 +254,7 @@ object ExecutionContext { * @param reporter a function for error reporting * @return the `ExecutionContext` using the given `ExecutorService` */ - def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit): ExecutionContextExecutorService = + def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit): ExecutionContextExecutorService^{reporter} = impl.ExecutionContextImpl.fromExecutorService(e, reporter) /** Creates an `ExecutionContext` from the given `ExecutorService` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. @@ -268,7 +270,7 @@ object ExecutionContext { * @param e the `ExecutorService` to use. If `null`, a new `ExecutorService` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. * @return the `ExecutionContext` using the given `ExecutorService` */ - def fromExecutorService(e: ExecutorService): ExecutionContextExecutorService = fromExecutorService(e, defaultReporter) + def fromExecutorService(e: ExecutorService): ExecutionContextExecutorService^ = fromExecutorService(e, defaultReporter) /** Creates an `ExecutionContext` from the given `Executor`. * @@ -276,7 +278,7 @@ object ExecutionContext { * @param reporter a function for error reporting * @return the `ExecutionContext` using the given `Executor` */ - def fromExecutor(e: Executor, reporter: Throwable => Unit): ExecutionContextExecutor = + def fromExecutor(e: Executor, reporter: Throwable => Unit): ExecutionContextExecutor^{reporter} = impl.ExecutionContextImpl.fromExecutor(e, reporter) /** Creates an `ExecutionContext` from the given `Executor` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. @@ -284,7 +286,7 @@ object ExecutionContext { * @param e the `Executor` to use. If `null`, a new `Executor` is created with [[scala.concurrent.ExecutionContext$.global default configuration]]. * @return the `ExecutionContext` using the given `Executor` */ - def fromExecutor(e: Executor): ExecutionContextExecutor = fromExecutor(e, defaultReporter) + def fromExecutor(e: Executor): ExecutionContextExecutor^ = fromExecutor(e, defaultReporter) /** The default reporter simply prints the stack trace of the `Throwable` to [[java.lang.System#err System.err]]. * diff --git a/scala2-library-cc/src/scala/concurrent/Future.scala b/scala2-library-cc/src/scala/concurrent/Future.scala index 371575918e1c..bb216612a023 100644 --- a/scala2-library-cc/src/scala/concurrent/Future.scala +++ b/scala2-library-cc/src/scala/concurrent/Future.scala @@ -24,6 +24,8 @@ import scala.reflect.ClassTag import scala.concurrent.ExecutionContext.parasitic +import language.experimental.captureChecking + /** A `Future` represents a value which may or may not be currently available, * but will be available at some point, or an exception if that value could not be made available. * @@ -222,7 +224,7 @@ trait Future[+T] extends Awaitable[T] { * @return a `Future` that will be completed with the transformed value * @group Transformations */ - def transformWith[S](f: Try[T] => Future[S])(implicit executor: ExecutionContext): Future[S] + def transformWith[S](f: Try[T] => Future[S]^{this})(implicit executor: ExecutionContext): Future[S] /** Creates a new future by applying a function to the successful result of @@ -450,7 +452,7 @@ trait Future[+T] extends Awaitable[T] { * @return a `Future` with the successful result of this or that `Future` or the failure of this `Future` if both fail * @group Transformations */ - def fallbackTo[U >: T](that: Future[U]): Future[U] = + def fallbackTo[U >: T](that: Future[U]): Future[U]^{this} = if (this eq that) this else { implicit val ec = parasitic diff --git a/scala2-library-cc/src/scala/concurrent/JavaConversions.scala b/scala2-library-cc/src/scala/concurrent/JavaConversions.scala index 3250e656941a..aa42d3a4b396 100644 --- a/scala2-library-cc/src/scala/concurrent/JavaConversions.scala +++ b/scala2-library-cc/src/scala/concurrent/JavaConversions.scala @@ -15,6 +15,8 @@ package scala.concurrent import java.util.concurrent.{ExecutorService, Executor} import scala.language.implicitConversions +import language.experimental.captureChecking + /** The `JavaConversions` object provides implicit conversions supporting * interoperability between Scala and Java concurrency classes. */ diff --git a/scala2-library-cc/src/scala/concurrent/Promise.scala b/scala2-library-cc/src/scala/concurrent/Promise.scala index cf3f23543c5a..c19443ea7baa 100644 --- a/scala2-library-cc/src/scala/concurrent/Promise.scala +++ b/scala2-library-cc/src/scala/concurrent/Promise.scala @@ -14,6 +14,8 @@ package scala.concurrent import scala.util.{ Try, Success, Failure } +import language.experimental.captureChecking + /** Promise is an object which can be completed with a value or failed * with an exception. * diff --git a/scala2-library-cc/src/scala/concurrent/SyncChannel.scala b/scala2-library-cc/src/scala/concurrent/SyncChannel.scala index 8792524524c3..ed5fe5537877 100644 --- a/scala2-library-cc/src/scala/concurrent/SyncChannel.scala +++ b/scala2-library-cc/src/scala/concurrent/SyncChannel.scala @@ -12,6 +12,8 @@ package scala.concurrent +import language.experimental.captureChecking + /** A `SyncChannel` allows one to exchange data synchronously between * a reader and a writer thread. The writer thread is blocked until the * data to be written has been read by a corresponding reader thread. diff --git a/scala2-library-cc/src/scala/concurrent/SyncVar.scala b/scala2-library-cc/src/scala/concurrent/SyncVar.scala index 66c5fd1bb81d..447875511c3e 100644 --- a/scala2-library-cc/src/scala/concurrent/SyncVar.scala +++ b/scala2-library-cc/src/scala/concurrent/SyncVar.scala @@ -14,6 +14,8 @@ package scala.concurrent import java.util.concurrent.TimeUnit +import language.experimental.captureChecking + /** A class to provide safe concurrent access to a mutable cell. * All methods are synchronized. * diff --git a/scala2-library-cc/src/scala/concurrent/duration/Deadline.scala b/scala2-library-cc/src/scala/concurrent/duration/Deadline.scala index 353d0f30fff8..84c06b819a8f 100644 --- a/scala2-library-cc/src/scala/concurrent/duration/Deadline.scala +++ b/scala2-library-cc/src/scala/concurrent/duration/Deadline.scala @@ -12,6 +12,8 @@ package scala.concurrent.duration +import language.experimental.captureChecking + /** * This class stores a deadline, as obtained via `Deadline.now` or the * duration DSL: diff --git a/scala2-library-cc/src/scala/concurrent/duration/Duration.scala b/scala2-library-cc/src/scala/concurrent/duration/Duration.scala index 1312bb12d1d5..46e0fa34c61a 100644 --- a/scala2-library-cc/src/scala/concurrent/duration/Duration.scala +++ b/scala2-library-cc/src/scala/concurrent/duration/Duration.scala @@ -15,6 +15,8 @@ package scala.concurrent.duration import java.lang.{ Double => JDouble } import scala.collection.StringParsers +import language.experimental.captureChecking + object Duration { /** diff --git a/scala2-library-cc/src/scala/concurrent/duration/DurationConversions.scala b/scala2-library-cc/src/scala/concurrent/duration/DurationConversions.scala index 30036331be73..e281d6e9b598 100644 --- a/scala2-library-cc/src/scala/concurrent/duration/DurationConversions.scala +++ b/scala2-library-cc/src/scala/concurrent/duration/DurationConversions.scala @@ -14,6 +14,8 @@ package scala.concurrent.duration import DurationConversions._ +import language.experimental.captureChecking + // Would be nice to limit the visibility of this trait a little bit, // but it crashes scalac to do so. trait DurationConversions extends Any { diff --git a/scala2-library-cc/src/scala/concurrent/duration/package.scala b/scala2-library-cc/src/scala/concurrent/duration/package.scala index f81b8777f6d0..06d3dedbc19b 100644 --- a/scala2-library-cc/src/scala/concurrent/duration/package.scala +++ b/scala2-library-cc/src/scala/concurrent/duration/package.scala @@ -14,6 +14,8 @@ package scala.concurrent import scala.language.implicitConversions +import language.experimental.captureChecking + package object duration { /** * This object can be used as closing token if you prefer dot-less style but do not want diff --git a/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala b/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala index 262a12b1b4b9..9cd774ef8816 100644 --- a/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala @@ -16,6 +16,8 @@ import java.util.concurrent.{ Semaphore, ForkJoinPool, ForkJoinWorkerThread, Cal import java.util.Collection import scala.concurrent.{ BlockContext, ExecutionContext, CanAwait, ExecutionContextExecutor, ExecutionContextExecutorService } +import language.experimental.captureChecking + private[scala] class ExecutionContextImpl private[impl] (final val executor: Executor, final val reporter: Throwable => Unit) extends ExecutionContextExecutor { require(executor ne null, "Executor must not be null") override final def execute(runnable: Runnable): Unit = executor execute runnable @@ -28,7 +30,7 @@ private[concurrent] object ExecutionContextImpl { final val daemonic: Boolean, final val maxBlockers: Int, final val prefix: String, - final val uncaught: Thread.UncaughtExceptionHandler) extends ThreadFactory with ForkJoinPool.ForkJoinWorkerThreadFactory { + final val uncaught: Thread.UncaughtExceptionHandler^) extends ThreadFactory with ForkJoinPool.ForkJoinWorkerThreadFactory { require(prefix ne null, "DefaultThreadFactory.prefix must be non null") require(maxBlockers >= 0, "DefaultThreadFactory.maxBlockers must be greater-or-equal-to 0") @@ -51,8 +53,7 @@ private[concurrent] object ExecutionContextImpl { final override def blockOn[T](thunk: => T)(implicit permission: CanAwait): T = if ((Thread.currentThread eq this) && !isBlocked && blockerPermits.tryAcquire()) { try { - val b: ForkJoinPool.ManagedBlocker with (() => T) = - new ForkJoinPool.ManagedBlocker with (() => T) { + class Block extends ForkJoinPool.ManagedBlocker { private[this] final var result: T = null.asInstanceOf[T] private[this] final var done: Boolean = false final override def block(): Boolean = { @@ -65,8 +66,11 @@ private[concurrent] object ExecutionContextImpl { } final override def isReleasable = done - final override def apply(): T = result - } + final def apply(): T = result + } + + val b: Block^{thunk} = new Block + isBlocked = true ForkJoinPool.managedBlock(b) b() @@ -78,7 +82,7 @@ private[concurrent] object ExecutionContextImpl { }) } - def createDefaultExecutorService(reporter: Throwable => Unit): ExecutionContextExecutorService = { + def createDefaultExecutorService(reporter: Throwable => Unit): ExecutionContextExecutorService^{reporter} = { def getInt(name: String, default: String) = (try System.getProperty(name, default) catch { case e: SecurityException => default }) match { @@ -108,14 +112,14 @@ private[concurrent] object ExecutionContextImpl { } } - def fromExecutor(e: Executor, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextExecutor = + def fromExecutor(e: Executor, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextExecutor^{reporter} = e match { case null => createDefaultExecutorService(reporter) case some => new ExecutionContextImpl(some, reporter) } def fromExecutorService(es: ExecutorService, reporter: Throwable => Unit = ExecutionContext.defaultReporter): - ExecutionContextExecutorService = es match { + ExecutionContextExecutorService^{reporter} = es match { case null => createDefaultExecutorService(reporter) case some => new ExecutionContextImpl(some, reporter) with ExecutionContextExecutorService { diff --git a/scala2-library-cc/src/scala/concurrent/impl/FutureConvertersImpl.scala b/scala2-library-cc/src/scala/concurrent/impl/FutureConvertersImpl.scala index a9eed4cbb055..05863b33004b 100644 --- a/scala2-library-cc/src/scala/concurrent/impl/FutureConvertersImpl.scala +++ b/scala2-library-cc/src/scala/concurrent/impl/FutureConvertersImpl.scala @@ -19,6 +19,8 @@ import scala.concurrent.Future import scala.concurrent.impl.Promise.DefaultPromise import scala.util.{Failure, Success, Try} +import language.experimental.captureChecking + private[scala] object FutureConvertersImpl { final class CF[T](val wrapped: Future[T]) extends CompletableFuture[T] with (Try[T] => Unit) { override def apply(t: Try[T]): Unit = t match { diff --git a/scala2-library-cc/src/scala/concurrent/impl/Promise.scala b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala index a479cad06902..b8c3b3dd73c1 100644 --- a/scala2-library-cc/src/scala/concurrent/impl/Promise.scala +++ b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala @@ -12,7 +12,7 @@ package scala.concurrent.impl -import scala.concurrent.{Batchable, CanAwait, ExecutionContext, ExecutionException, Future, TimeoutException} +import scala.concurrent.{Batchable, Batchable2, CanAwait, ExecutionContext, ExecutionException, Future, TimeoutException} import scala.concurrent.duration.Duration import scala.annotation.{nowarn, switch, tailrec} import scala.util.control.{ControlThrowable, NonFatal} @@ -23,6 +23,8 @@ import java.util.concurrent.atomic.AtomicReference import java.util.Objects.requireNonNull import java.io.{IOException, NotSerializableException, ObjectInputStream, ObjectOutputStream} +import language.experimental.captureChecking + /** * Latch used to implement waiting on a DefaultPromise's result. * @@ -410,13 +412,13 @@ private[concurrent] object Promise { * function's type parameters are erased, and the _xform tag will be used to reify them. **/ final class Transformation[-F, T] private[this] ( - private[this] final var _fun: Any => Any, + private[this] final var _fun: Any -> Any, private[this] final var _ec: ExecutionContext, private[this] final var _arg: Try[F], private[this] final val _xform: Int ) extends DefaultPromise[T]() with Callbacks[F] with Runnable with Batchable { final def this(xform: Int, f: _ => _, ec: ExecutionContext) = - this(f.asInstanceOf[Any => Any], ec.prepare(): @nowarn("cat=deprecation"), null, xform) + this(f.asInstanceOf[Any -> Any], ec.prepare(): @nowarn("cat=deprecation"), null, xform) final def benefitsFromBatching: Boolean = _xform != Xform_onComplete && _xform != Xform_foreach @@ -430,7 +432,7 @@ private[concurrent] object Promise { try e.execute(this) /* Safe publication of _arg, _fun, _ec */ catch { case t: Throwable => - _fun = null // allow to GC + // _fun = null // allow to GC _arg = null // see above _ec = null // see above again handleFailure(t, e) @@ -456,7 +458,7 @@ private[concurrent] object Promise { val v = _arg val fun = _fun val ec = _ec - _fun = null // allow to GC + // _fun = null // allow to GC _arg = null // see above _ec = null // see above try { diff --git a/scala2-library-cc/src/scala/concurrent/package.scala b/scala2-library-cc/src/scala/concurrent/package.scala index d648a1c90a15..a1edc3a53fc5 100644 --- a/scala2-library-cc/src/scala/concurrent/package.scala +++ b/scala2-library-cc/src/scala/concurrent/package.scala @@ -15,6 +15,8 @@ package scala import scala.concurrent.duration.Duration import scala.annotation.implicitNotFound +import language.experimental.captureChecking + /** This package object contains primitives for concurrent and parallel programming. * * == Guide == From 51d3fd2b817e7223d03a216ded39c34c80788c42 Mon Sep 17 00:00:00 2001 From: 05st Date: Wed, 9 Apr 2025 14:45:19 -0400 Subject: [PATCH 3/5] Finish porting concurrency classes without separation checking --- .../scala/concurrent/BatchingExecutor.scala | 6 +--- .../scala/concurrent/ExecutionContext.scala | 3 +- .../src/scala/concurrent/Future.scala | 4 +-- .../src/scala/concurrent/Promise.scala | 2 +- .../impl/ExecutionContextImpl.scala | 6 ++-- .../src/scala/concurrent/impl/Promise.scala | 28 ++++++++++--------- 6 files changed, 24 insertions(+), 25 deletions(-) diff --git a/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala b/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala index 31f7e4e8f998..613765821292 100644 --- a/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala +++ b/scala2-library-cc/src/scala/concurrent/BatchingExecutor.scala @@ -23,11 +23,7 @@ import language.experimental.captureChecking * Marker trait to indicate that a Runnable is Batchable by BatchingExecutors */ trait Batchable { - self: Runnable => -} - -trait Batchable2 { - + self: Runnable^ => } private[concurrent] object BatchingExecutorStatics { diff --git a/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala index 0e5572a80d44..c8427768d22b 100644 --- a/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala +++ b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala @@ -16,6 +16,7 @@ import java.util.concurrent.{ ExecutorService, Executor } import scala.annotation.implicitNotFound import language.experimental.captureChecking +import caps.Capability /** * An `ExecutionContext` can execute program logic asynchronously, @@ -70,7 +71,7 @@ consider using Scala's global ExecutionContext by defining the following: implicit val ec: scala.concurrent.ExecutionContext = scala.concurrent.ExecutionContext.global""") -trait ExecutionContext { +trait ExecutionContext extends Capability { /** Runs a block of code on this execution context. * diff --git a/scala2-library-cc/src/scala/concurrent/Future.scala b/scala2-library-cc/src/scala/concurrent/Future.scala index bb216612a023..d6bbe2e5f9b0 100644 --- a/scala2-library-cc/src/scala/concurrent/Future.scala +++ b/scala2-library-cc/src/scala/concurrent/Future.scala @@ -296,7 +296,7 @@ trait Future[+T] extends Awaitable[T] { * @return a `Future` which will hold the successful result of this `Future` if it matches the predicate or a `NoSuchElementException` * @group Transformations */ - def filter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = + def filter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T]^{p, executor, this} = transform { t => if (t.isInstanceOf[Success[T]]) { @@ -308,7 +308,7 @@ trait Future[+T] extends Awaitable[T] { /** Used by for-comprehensions. * @group Transformations */ - final def withFilter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = filter(p)(executor) + final def withFilter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T]^{p, executor, this} = filter(p)(executor) /** Creates a new future by mapping the value of the current future, if the given partial function is defined at that value. * diff --git a/scala2-library-cc/src/scala/concurrent/Promise.scala b/scala2-library-cc/src/scala/concurrent/Promise.scala index c19443ea7baa..b9afc53f0a3c 100644 --- a/scala2-library-cc/src/scala/concurrent/Promise.scala +++ b/scala2-library-cc/src/scala/concurrent/Promise.scala @@ -38,7 +38,7 @@ import language.experimental.captureChecking trait Promise[T] { /** Future containing the value of this promise. */ - def future: Future[T] + def future: Future[T]^ /** Returns whether the promise has already been completed with * a value or an exception. diff --git a/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala b/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala index 9cd774ef8816..18eb7a9d554f 100644 --- a/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala @@ -82,7 +82,7 @@ private[concurrent] object ExecutionContextImpl { }) } - def createDefaultExecutorService(reporter: Throwable => Unit): ExecutionContextExecutorService^{reporter} = { + def createDefaultExecutorService(reporter: Throwable => Unit): ExecutionContextExecutorService^ = { def getInt(name: String, default: String) = (try System.getProperty(name, default) catch { case e: SecurityException => default }) match { @@ -112,14 +112,14 @@ private[concurrent] object ExecutionContextImpl { } } - def fromExecutor(e: Executor, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextExecutor^{reporter} = + def fromExecutor(e: Executor, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextExecutor^ = e match { case null => createDefaultExecutorService(reporter) case some => new ExecutionContextImpl(some, reporter) } def fromExecutorService(es: ExecutorService, reporter: Throwable => Unit = ExecutionContext.defaultReporter): - ExecutionContextExecutorService^{reporter} = es match { + ExecutionContextExecutorService^ = es match { case null => createDefaultExecutorService(reporter) case some => new ExecutionContextImpl(some, reporter) with ExecutionContextExecutorService { diff --git a/scala2-library-cc/src/scala/concurrent/impl/Promise.scala b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala index b8c3b3dd73c1..0309aa9e8512 100644 --- a/scala2-library-cc/src/scala/concurrent/impl/Promise.scala +++ b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala @@ -12,7 +12,7 @@ package scala.concurrent.impl -import scala.concurrent.{Batchable, Batchable2, CanAwait, ExecutionContext, ExecutionException, Future, TimeoutException} +import scala.concurrent.{Batchable, CanAwait, ExecutionContext, ExecutionException, Future, TimeoutException} import scala.concurrent.duration.Duration import scala.annotation.{nowarn, switch, tailrec} import scala.util.control.{ControlThrowable, NonFatal} @@ -24,6 +24,7 @@ import java.util.Objects.requireNonNull import java.io.{IOException, NotSerializableException, ObjectInputStream, ObjectOutputStream} import language.experimental.captureChecking +import caps.unsafe.* /** * Latch used to implement waiting on a DefaultPromise's result. @@ -61,11 +62,11 @@ private[concurrent] object Promise { * If when compressing a chain of Links it is discovered that the root has been completed, * the `owner`'s value is completed with that value, and the Link chain is discarded. **/ - private[concurrent] final class Link[T](to: DefaultPromise[T]) extends AtomicReference[DefaultPromise[T]](to) { + private[concurrent] final class Link[T](to: DefaultPromise[T]^) extends AtomicReference[DefaultPromise[T]^{to}](to) { /** * Compresses this chain and returns the currently known root of this chain of Links. **/ - final def promise(owner: DefaultPromise[T]): DefaultPromise[T] = { + final def promise(owner: DefaultPromise[T]^): DefaultPromise[T]^{this, to, owner} = { val c = get() compressed(current = c, target = c, owner = owner) } @@ -73,7 +74,7 @@ private[concurrent] object Promise { /** * The combination of traversing and possibly unlinking of a given `target` DefaultPromise. **/ - @inline @tailrec private[this] final def compressed(current: DefaultPromise[T], target: DefaultPromise[T], owner: DefaultPromise[T]): DefaultPromise[T] = { + @inline @tailrec private[this] final def compressed(current: DefaultPromise[T]^, target: DefaultPromise[T]^, owner: DefaultPromise[T]^): DefaultPromise[T]^{this, current, target, owner} = { val value = target.get() if (value.isInstanceOf[Callbacks[_]]) { if (compareAndSet(current, target)) target // Link @@ -105,6 +106,7 @@ private[concurrent] object Promise { // Left non-final to enable addition of extra fields by Java/Scala converters in scala-java8-compat. class DefaultPromise[T] private[this] (initial: AnyRef) extends AtomicReference[AnyRef](initial) with scala.concurrent.Promise[T] with scala.concurrent.Future[T] with (Try[T] => Unit) { + self: DefaultPromise[T]^ => /** * Constructs a new, completed, Promise. */ @@ -125,7 +127,7 @@ private[concurrent] object Promise { /** * Returns the associated `Future` with this `Promise` */ - override final def future: Future[T] = this + override final def future: Future[T]^ = (this : Future[T]^) override final def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] = dispatchOrAddCallbacks(get(), new Transformation[T, S](Xform_transform, f, executor)) @@ -186,7 +188,7 @@ private[concurrent] object Promise { else this.asInstanceOf[Future[S]] } - override final def filter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T] = { + override final def filter(p: T => Boolean)(implicit executor: ExecutionContext): Future[T]^{p, executor, this} = { val state = get() if (!state.isInstanceOf[Failure[_]]) dispatchOrAddCallbacks(state, new Transformation[T, T](Xform_filter, p, executor)) // Short-circuit if we get a Success else this @@ -343,7 +345,7 @@ private[concurrent] object Promise { /** Link this promise to the root of another promise. */ - @tailrec private[concurrent] final def linkRootOf(target: DefaultPromise[T], link: Link[T]): Unit = + @tailrec private[concurrent] final def linkRootOf(target: DefaultPromise[T]^, link: Link[T]^): Unit = if (this ne target) { val state = get() if (state.isInstanceOf[Try[_]]) { @@ -412,13 +414,13 @@ private[concurrent] object Promise { * function's type parameters are erased, and the _xform tag will be used to reify them. **/ final class Transformation[-F, T] private[this] ( - private[this] final var _fun: Any -> Any, - private[this] final var _ec: ExecutionContext, + private[this] final val _fun: Any => Any, + private[this] final val _ec: ExecutionContext, private[this] final var _arg: Try[F], private[this] final val _xform: Int ) extends DefaultPromise[T]() with Callbacks[F] with Runnable with Batchable { final def this(xform: Int, f: _ => _, ec: ExecutionContext) = - this(f.asInstanceOf[Any -> Any], ec.prepare(): @nowarn("cat=deprecation"), null, xform) + this(f.asInstanceOf[Any => Any], ec.prepare(): @nowarn("cat=deprecation"), null, xform) final def benefitsFromBatching: Boolean = _xform != Xform_onComplete && _xform != Xform_foreach @@ -429,12 +431,12 @@ private[concurrent] object Promise { final def submitWithValue(resolved: Try[F]): this.type = { _arg = resolved val e = _ec - try e.execute(this) /* Safe publication of _arg, _fun, _ec */ + try e.execute(this.unsafeAssumePure) /* Safe publication of _arg, _fun, _ec */ catch { case t: Throwable => // _fun = null // allow to GC _arg = null // see above - _ec = null // see above again + // _ec = null // see above again handleFailure(t, e) } @@ -460,7 +462,7 @@ private[concurrent] object Promise { val ec = _ec // _fun = null // allow to GC _arg = null // see above - _ec = null // see above + // _ec = null // see above try { val resolvedResult: Try[_] = (_xform: @switch) match { From 94dd70bdb026f3acabf9d90197a2e47a758f0825 Mon Sep 17 00:00:00 2001 From: 05st Date: Thu, 24 Apr 2025 10:22:59 -0500 Subject: [PATCH 4/5] Transformation class + some separation checking fixes --- .../scala/concurrent/ExecutionContext.scala | 5 ++-- .../src/scala/concurrent/Future.scala | 8 +++--- .../impl/ExecutionContextImpl.scala | 7 +++--- .../src/scala/concurrent/impl/Promise.scala | 25 ++++++++++++------- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala index c8427768d22b..d2b2e8f0f089 100644 --- a/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala +++ b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala @@ -17,6 +17,7 @@ import scala.annotation.implicitNotFound import language.experimental.captureChecking import caps.Capability +import caps.consume /** * An `ExecutionContext` can execute program logic asynchronously, @@ -255,7 +256,7 @@ object ExecutionContext { * @param reporter a function for error reporting * @return the `ExecutionContext` using the given `ExecutorService` */ - def fromExecutorService(e: ExecutorService, reporter: Throwable => Unit): ExecutionContextExecutorService^{reporter} = + def fromExecutorService(e: ExecutorService, @consume reporter: Throwable => Unit): ExecutionContextExecutorService^{reporter} = impl.ExecutionContextImpl.fromExecutorService(e, reporter) /** Creates an `ExecutionContext` from the given `ExecutorService` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. @@ -279,7 +280,7 @@ object ExecutionContext { * @param reporter a function for error reporting * @return the `ExecutionContext` using the given `Executor` */ - def fromExecutor(e: Executor, reporter: Throwable => Unit): ExecutionContextExecutor^{reporter} = + def fromExecutor(e: Executor, @consume reporter: Throwable => Unit): ExecutionContextExecutor^ = impl.ExecutionContextImpl.fromExecutor(e, reporter) /** Creates an `ExecutionContext` from the given `Executor` with the [[scala.concurrent.ExecutionContext$.defaultReporter default reporter]]. diff --git a/scala2-library-cc/src/scala/concurrent/Future.scala b/scala2-library-cc/src/scala/concurrent/Future.scala index d6bbe2e5f9b0..e87a49aa8b3b 100644 --- a/scala2-library-cc/src/scala/concurrent/Future.scala +++ b/scala2-library-cc/src/scala/concurrent/Future.scala @@ -25,6 +25,7 @@ import scala.reflect.ClassTag import scala.concurrent.ExecutionContext.parasitic import language.experimental.captureChecking +import caps.consume /** A `Future` represents a value which may or may not be currently available, * but will be available at some point, or an exception if that value could not be made available. @@ -424,7 +425,7 @@ trait Future[+T] extends Awaitable[T] { * @return a `Future` with the result of the application of `f` to the results of `this` and `that` * @group Transformations */ - def zipWith[U, R](that: Future[U])(f: (T, U) => R)(implicit executor: ExecutionContext): Future[R] = { + def zipWith[U, R](that: Future[U])(@consume f: (T, U) => R)(implicit executor: ExecutionContext): Future[R] = { // This is typically overriden by the implementation in DefaultPromise, which provides // symmetric fail-fast behavior regardless of which future fails first. // @@ -573,7 +574,7 @@ object Future { private[concurrent] final val recoverWithFailed = (t: Throwable) => recoverWithFailedMarker private[this] final val _zipWithTuple2: (Any, Any) => (Any, Any) = Tuple2.apply _ - private[concurrent] final def zipWithTuple2Fun[T,U] = _zipWithTuple2.asInstanceOf[(T,U) => (T,U)] + private[concurrent] final def zipWithTuple2Fun[T,U] = _zipWithTuple2.asInstanceOf //[(T,U) => (T,U)] private[this] final val _addToBuilderFun: (Builder[Any, Nothing], Any) => Builder[Any, Nothing] = (b: Builder[Any, Nothing], e: Any) => b += e private[concurrent] final def addToBuilderFun[A, M] = _addToBuilderFun.asInstanceOf[Function2[Builder[A, M], A, Builder[A, M]]] @@ -633,7 +634,7 @@ object Future { override final def recover[U >: Nothing](pf: PartialFunction[Throwable, U])(implicit executor: ExecutionContext): Future[U] = this override final def recoverWith[U >: Nothing](pf: PartialFunction[Throwable, Future[U]])(implicit executor: ExecutionContext): Future[U] = this override final def zip[U](that: Future[U]): Future[(Nothing, U)] = this - override final def zipWith[U, R](that: Future[U])(f: (Nothing, U) => R)(implicit executor: ExecutionContext): Future[R] = this + override final def zipWith[U, R](that: Future[U])(@consume f: (Nothing, U) => R)(implicit executor: ExecutionContext): Future[R] = this override final def fallbackTo[U >: Nothing](that: Future[U]): Future[U] = this override final def mapTo[S](implicit tag: ClassTag[S]): Future[S] = this override final def andThen[U](pf: PartialFunction[Try[Nothing], U])(implicit executor: ExecutionContext): Future[Nothing] = this @@ -877,4 +878,3 @@ object Future { trait OnCompleteRunnable extends Batchable { self: Runnable => } - diff --git a/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala b/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala index 18eb7a9d554f..f241996f78a9 100644 --- a/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/scala2-library-cc/src/scala/concurrent/impl/ExecutionContextImpl.scala @@ -17,6 +17,7 @@ import java.util.Collection import scala.concurrent.{ BlockContext, ExecutionContext, CanAwait, ExecutionContextExecutor, ExecutionContextExecutorService } import language.experimental.captureChecking +import caps.consume private[scala] class ExecutionContextImpl private[impl] (final val executor: Executor, final val reporter: Throwable => Unit) extends ExecutionContextExecutor { require(executor ne null, "Executor must not be null") @@ -82,7 +83,7 @@ private[concurrent] object ExecutionContextImpl { }) } - def createDefaultExecutorService(reporter: Throwable => Unit): ExecutionContextExecutorService^ = { + def createDefaultExecutorService(@consume reporter: Throwable => Unit): ExecutionContextExecutorService^ = { def getInt(name: String, default: String) = (try System.getProperty(name, default) catch { case e: SecurityException => default }) match { @@ -112,13 +113,13 @@ private[concurrent] object ExecutionContextImpl { } } - def fromExecutor(e: Executor, reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextExecutor^ = + def fromExecutor(e: Executor, @consume reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextExecutor^ = e match { case null => createDefaultExecutorService(reporter) case some => new ExecutionContextImpl(some, reporter) } - def fromExecutorService(es: ExecutorService, reporter: Throwable => Unit = ExecutionContext.defaultReporter): + def fromExecutorService(es: ExecutorService, @consume reporter: Throwable => Unit = ExecutionContext.defaultReporter): ExecutionContextExecutorService^ = es match { case null => createDefaultExecutorService(reporter) case some => diff --git a/scala2-library-cc/src/scala/concurrent/impl/Promise.scala b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala index 0309aa9e8512..643b4bc16231 100644 --- a/scala2-library-cc/src/scala/concurrent/impl/Promise.scala +++ b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala @@ -24,6 +24,9 @@ import java.util.Objects.requireNonNull import java.io.{IOException, NotSerializableException, ObjectInputStream, ObjectOutputStream} import language.experimental.captureChecking +import scala.annotation.unchecked.uncheckedCaptures +import caps.cap +import caps.consume import caps.unsafe.* /** @@ -66,7 +69,7 @@ private[concurrent] object Promise { /** * Compresses this chain and returns the currently known root of this chain of Links. **/ - final def promise(owner: DefaultPromise[T]^): DefaultPromise[T]^{this, to, owner} = { + final def promise(owner: DefaultPromise[T]^): DefaultPromise[T]^{cap.rd, this, to, owner} = { val c = get() compressed(current = c, target = c, owner = owner) } @@ -74,7 +77,7 @@ private[concurrent] object Promise { /** * The combination of traversing and possibly unlinking of a given `target` DefaultPromise. **/ - @inline @tailrec private[this] final def compressed(current: DefaultPromise[T]^, target: DefaultPromise[T]^, owner: DefaultPromise[T]^): DefaultPromise[T]^{this, current, target, owner} = { + @inline @tailrec private[this] final def compressed(current: DefaultPromise[T]^{cap.rd, this}, target: DefaultPromise[T]^, owner: DefaultPromise[T]^): DefaultPromise[T]^{this, current, target, owner} = { val value = target.get() if (value.isInstanceOf[Callbacks[_]]) { if (compareAndSet(current, target)) target // Link @@ -127,6 +130,7 @@ private[concurrent] object Promise { /** * Returns the associated `Future` with this `Promise` */ + @consume override final def future: Future[T]^ = (this : Future[T]^) override final def transform[S](f: Try[T] => Try[S])(implicit executor: ExecutionContext): Future[S] = @@ -135,7 +139,7 @@ private[concurrent] object Promise { override final def transformWith[S](f: Try[T] => Future[S])(implicit executor: ExecutionContext): Future[S] = dispatchOrAddCallbacks(get(), new Transformation[T, S](Xform_transformWith, f, executor)) - override final def zipWith[U, R](that: Future[U])(f: (T, U) => R)(implicit executor: ExecutionContext): Future[R] = { + override final def zipWith[U, R](that: Future[U])(@consume f: (T, U) => R)(implicit executor: ExecutionContext): Future[R] = { val state = get() if (state.isInstanceOf[Try[_]]) { if (state.asInstanceOf[Try[T]].isFailure) this.asInstanceOf[Future[R]] @@ -414,11 +418,14 @@ private[concurrent] object Promise { * function's type parameters are erased, and the _xform tag will be used to reify them. **/ final class Transformation[-F, T] private[this] ( - private[this] final val _fun: Any => Any, - private[this] final val _ec: ExecutionContext, + __fun: Any => Any, + __ec: ExecutionContext, private[this] final var _arg: Try[F], private[this] final val _xform: Int ) extends DefaultPromise[T]() with Callbacks[F] with Runnable with Batchable { + @uncheckedCaptures private[this] final var _fun: Any => Any = __fun + @uncheckedCaptures private[this] final var _ec: ExecutionContext = __ec + final def this(xform: Int, f: _ => _, ec: ExecutionContext) = this(f.asInstanceOf[Any => Any], ec.prepare(): @nowarn("cat=deprecation"), null, xform) @@ -434,9 +441,9 @@ private[concurrent] object Promise { try e.execute(this.unsafeAssumePure) /* Safe publication of _arg, _fun, _ec */ catch { case t: Throwable => - // _fun = null // allow to GC + _fun = null // allow to GC _arg = null // see above - // _ec = null // see above again + _ec = null // see above again handleFailure(t, e) } @@ -460,9 +467,9 @@ private[concurrent] object Promise { val v = _arg val fun = _fun val ec = _ec - // _fun = null // allow to GC + _fun = null // allow to GC _arg = null // see above - // _ec = null // see above + _ec = null // see above try { val resolvedResult: Try[_] = (_xform: @switch) match { From 7a55f7bd0a8df356cb46483923522abb884b891a Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 5 May 2025 18:55:44 +0200 Subject: [PATCH 5/5] Brute force patches to make separation checker happy --- .../src/scala/concurrent/ExecutionContext.scala | 2 +- .../src/scala/concurrent/impl/Promise.scala | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala index d2b2e8f0f089..db6104e6927d 100644 --- a/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala +++ b/scala2-library-cc/src/scala/concurrent/ExecutionContext.scala @@ -294,5 +294,5 @@ object ExecutionContext { * * @return the function for error reporting */ - final val defaultReporter: Throwable => Unit = _.printStackTrace() + final val defaultReporter: Throwable -> Unit = _.printStackTrace() } diff --git a/scala2-library-cc/src/scala/concurrent/impl/Promise.scala b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala index 643b4bc16231..170ec323f818 100644 --- a/scala2-library-cc/src/scala/concurrent/impl/Promise.scala +++ b/scala2-library-cc/src/scala/concurrent/impl/Promise.scala @@ -69,7 +69,7 @@ private[concurrent] object Promise { /** * Compresses this chain and returns the currently known root of this chain of Links. **/ - final def promise(owner: DefaultPromise[T]^): DefaultPromise[T]^{cap.rd, this, to, owner} = { + final def promise(owner: DefaultPromise[T]^): DefaultPromise[T]^{to, owner} = { val c = get() compressed(current = c, target = c, owner = owner) } @@ -77,7 +77,7 @@ private[concurrent] object Promise { /** * The combination of traversing and possibly unlinking of a given `target` DefaultPromise. **/ - @inline @tailrec private[this] final def compressed(current: DefaultPromise[T]^{cap.rd, this}, target: DefaultPromise[T]^, owner: DefaultPromise[T]^): DefaultPromise[T]^{this, current, target, owner} = { + @inline @tailrec private[this] final def compressed(current: DefaultPromise[T]^{this, to}, target: DefaultPromise[T]^{cap, this, current}, owner: DefaultPromise[T]^): DefaultPromise[T]^{to, target, owner} = { val value = target.get() if (value.isInstanceOf[Callbacks[_]]) { if (compareAndSet(current, target)) target // Link @@ -151,7 +151,7 @@ private[concurrent] object Promise { val buffer = new AtomicReference[Success[Any]]() val zipped = new DefaultPromise[R]() - val thisF: Try[T] => Unit = { + val thisF: Try[T] ->{f} Unit = { case left: Success[_] => val right = buffer.getAndSet(left).asInstanceOf[Success[U]] if (right ne null) @@ -160,7 +160,7 @@ private[concurrent] object Promise { zipped.tryComplete(f.asInstanceOf[Failure[R]]) } - val thatF: Try[U] => Unit = { + val thatF: Try[U] ->{f} Unit = { case right: Success[_] => val left = buffer.getAndSet(right).asInstanceOf[Success[T]] if (left ne null) @@ -349,7 +349,7 @@ private[concurrent] object Promise { /** Link this promise to the root of another promise. */ - @tailrec private[concurrent] final def linkRootOf(target: DefaultPromise[T]^, link: Link[T]^): Unit = + @tailrec private[concurrent] final def linkRootOf(target: DefaultPromise[T]^, link: Link[T]^{cap, target}): Unit = if (this ne target) { val state = get() if (state.isInstanceOf[Try[_]]) {