Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1051 from akka/wip-agent-rework-√
Migrating Agents to greener pastures
- Loading branch information
Showing
9 changed files
with
463 additions
and
436 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
akka-actor/src/main/scala/akka/util/SerializedSuspendableExecutionContext.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package akka.util | ||
|
||
import java.util.concurrent.ConcurrentLinkedQueue | ||
import java.util.concurrent.atomic.AtomicInteger | ||
import scala.concurrent.ExecutionContext | ||
import scala.util.control.NonFatal | ||
import scala.annotation.{ tailrec, switch } | ||
|
||
private[akka] object SerializedSuspendableExecutionContext { | ||
final val Off = 0 | ||
final val On = 1 | ||
final val Suspended = 2 | ||
|
||
def apply(batchSize: Int)(implicit context: ExecutionContext): SerializedSuspendableExecutionContext = | ||
new SerializedSuspendableExecutionContext(batchSize)(context match { | ||
case s: SerializedSuspendableExecutionContext ⇒ s.context | ||
case other ⇒ other | ||
}) | ||
} | ||
|
||
/** | ||
* This `ExecutionContext` allows to wrap an underlying `ExecutionContext` and provide guaranteed serial execution | ||
* of tasks submitted to it. On top of that it also allows for *suspending* and *resuming* processing of tasks. | ||
* | ||
* WARNING: This type must never leak into User code as anything but `ExecutionContext` | ||
* | ||
* @param throughput maximum number of tasks to be executed in serial before relinquishing the executing thread. | ||
* @param context the underlying context which will be used to actually execute the submitted tasks | ||
*/ | ||
private[akka] final class SerializedSuspendableExecutionContext(throughput: Int)(val context: ExecutionContext) | ||
extends ConcurrentLinkedQueue[Runnable] with Runnable with ExecutionContext { | ||
import SerializedSuspendableExecutionContext._ | ||
require(throughput > 0, s"SerializedSuspendableExecutionContext.throughput must be greater than 0 but was $throughput") | ||
|
||
private final val state = new AtomicInteger(Off) | ||
@tailrec private final def addState(newState: Int): Boolean = { | ||
val c = state.get | ||
state.compareAndSet(c, c | newState) || addState(newState) | ||
} | ||
@tailrec private final def remState(oldState: Int) { | ||
val c = state.get | ||
if (state.compareAndSet(c, c & ~oldState)) attach() else remState(oldState) | ||
} | ||
|
||
/** | ||
* Resumes execution of tasks until `suspend` is called, | ||
* if it isn't currently suspended, it is a no-op. | ||
* This operation is idempotent. | ||
*/ | ||
final def resume(): Unit = remState(Suspended) | ||
|
||
/** | ||
* Suspends execution of tasks until `resume` is called, | ||
* this operation is idempotent. | ||
*/ | ||
final def suspend(): Unit = addState(Suspended) | ||
|
||
final def run(): Unit = { | ||
@tailrec def run(done: Int): Unit = | ||
if (done < throughput && state.get == On) { | ||
poll() match { | ||
case null ⇒ () | ||
case some ⇒ | ||
try some.run() catch { case NonFatal(t) ⇒ context reportFailure t } | ||
run(done + 1) | ||
} | ||
} | ||
try run(0) finally remState(On) | ||
} | ||
|
||
final def attach(): Unit = if (!isEmpty && state.compareAndSet(Off, On)) context execute this | ||
override final def execute(task: Runnable): Unit = try add(task) finally attach() | ||
override final def reportFailure(t: Throwable): Unit = context reportFailure t | ||
|
||
override final def toString: String = (state.get: @switch) match { | ||
case 0 ⇒ "Off" | ||
case 1 ⇒ "On" | ||
case 2 ⇒ "Off & Suspended" | ||
case 3 ⇒ "On & Suspended" | ||
} | ||
} |
Oops, something went wrong.