fusable SplitWhen and reusable HTTP blueprint

plus further optimizations

Lots of contributions from drewhk (Endre Varga).
This commit is contained in:
Roland Kuhn 2015-12-20 12:54:05 +01:00
parent 3c3545020b
commit 469e15581f
45 changed files with 1251 additions and 838 deletions

View file

@ -21,7 +21,7 @@ import akka.stream.impl.fusing.GraphStages.MaterializedValueSource
*
* (See the class for the documentation of the internals)
*/
private[stream] object GraphInterpreter {
private[akka] object GraphInterpreter {
/**
* Compile time constant, enable it for debug logging to the console.
*/
@ -44,6 +44,9 @@ private[stream] object GraphInterpreter {
final val PushStartFlip = 12 //1100
final val PushEndFlip = 5 //0101
final val KeepGoingFlag = 0x4000000
final val KeepGoingMask = 0x3ffffff
/**
* Marker object that indicates that a port holds no element since it was already grabbed. The port is still pullable,
* but there is no more element to grab.
@ -250,14 +253,14 @@ private[stream] object GraphInterpreter {
/**
* INTERNAL API
*/
private[stream] def currentInterpreter: GraphInterpreter =
private[akka] def currentInterpreter: GraphInterpreter =
_currentInterpreter.get()(0).asInstanceOf[GraphInterpreter].nonNull
// nonNull is just a debug helper to find nulls more timely
/**
* INTERNAL API
*/
private[stream] def currentInterpreterOrNull: GraphInterpreter =
private[akka] def currentInterpreterOrNull: GraphInterpreter =
_currentInterpreter.get()(0).asInstanceOf[GraphInterpreter]
}
@ -368,8 +371,7 @@ private[stream] final class GraphInterpreter(
// Counts how many active connections a stage has. Once it reaches zero, the stage is automatically stopped.
private[this] val shutdownCounter = Array.tabulate(assembly.stages.length) { i
val shape = assembly.stages(i).shape
val keepGoing = if (logics(i).keepGoingAfterAllPortsClosed) 1 else 0
shape.inlets.size + shape.outlets.size + keepGoing
shape.inlets.size + shape.outlets.size
}
private var _subFusingMaterializer: Materializer = _
@ -512,12 +514,15 @@ private[stream] final class GraphInterpreter(
case owner logics(owner).toString
}
private def shutdownCounters: String =
shutdownCounter.map(x if (x >= KeepGoingFlag) s"${x & KeepGoingMask}(KeepGoing)" else x.toString).mkString(",")
/**
* Executes pending events until the given limit is met. If there were remaining events, isSuspended will return
* true.
*/
def execute(eventLimit: Int): Unit = {
if (Debug) println(s"$Name ---------------- EXECUTE (running=$runningStages, shutdown=${shutdownCounter.mkString(",")})")
if (Debug) println(s"$Name ---------------- EXECUTE $queueStatus (running=$runningStages, shutdown=$shutdownCounters)")
val currentInterpreterHolder = _currentInterpreter.get()
val previousInterpreter = currentInterpreterHolder(0)
currentInterpreterHolder(0) = this
@ -537,10 +542,26 @@ private[stream] final class GraphInterpreter(
} finally {
currentInterpreterHolder(0) = previousInterpreter
}
if (Debug) println(s"$Name ---------------- $queueStatus (running=$runningStages, shutdown=${shutdownCounter.mkString(",")})")
if (Debug) println(s"$Name ---------------- $queueStatus (running=$runningStages, shutdown=$shutdownCounters)")
// TODO: deadlock detection
}
def runAsyncInput(logic: GraphStageLogic, evt: Any, handler: (Any) Unit): Unit =
if (!isStageCompleted(logic)) {
if (GraphInterpreter.Debug) println(s"$Name ASYNC $evt ($handler) [$logic]")
val currentInterpreterHolder = _currentInterpreter.get()
val previousInterpreter = currentInterpreterHolder(0)
currentInterpreterHolder(0) = this
try {
activeStage = logic
try handler(evt)
catch {
case NonFatal(ex) logic.failStage(ex)
}
afterStageHasRun(logic)
} finally currentInterpreterHolder(0) = previousInterpreter
}
// Decodes and processes a single event for the given connection
private def processEvent(connection: Int): Unit = {
def safeLogics(id: Int) =
@ -638,11 +659,9 @@ private[stream] final class GraphInterpreter(
}
}
// Call only for keep-alive stages
def closeKeptAliveStageIfNeeded(stageId: Int): Unit =
if (stageId != Boundary && shutdownCounter(stageId) == 1) {
shutdownCounter(stageId) = 0
}
private[stream] def setKeepGoing(logic: GraphStageLogic, enabled: Boolean): Unit =
if (enabled) shutdownCounter(logic.stageId) |= KeepGoingFlag
else shutdownCounter(logic.stageId) &= KeepGoingMask
private def finalizeStage(logic: GraphStageLogic): Unit = {
try {
@ -675,7 +694,7 @@ private[stream] final class GraphInterpreter(
val currentState = portStates(connection)
if (Debug) println(s"$Name complete($connection) [$currentState]")
portStates(connection) = currentState | OutClosed
if ((currentState & (InClosed | Pushing | Pulling)) == 0) enqueue(connection)
if ((currentState & (InClosed | Pushing | Pulling | OutClosed)) == 0) enqueue(connection)
if ((currentState & OutClosed) == 0) completeConnection(assembly.outOwners(connection))
}
@ -699,9 +718,50 @@ private[stream] final class GraphInterpreter(
portStates(connection) = currentState | InClosed
if ((currentState & OutClosed) == 0) {
connectionSlots(connection) = Empty
if ((currentState & (Pulling | Pushing)) == 0) enqueue(connection)
if ((currentState & (Pulling | Pushing | InClosed)) == 0) enqueue(connection)
}
if ((currentState & InClosed) == 0) completeConnection(assembly.inOwners(connection))
}
/**
* Debug utility to dump the "waits-on" relationships in DOT format to the console for analysis of deadlocks.
*
* Only invoke this after the interpreter completely settled, otherwise the results might be off. This is a very
* simplistic tool, make sure you are understanding what you are doing and then it will serve you well.
*/
def dumpWaits(): Unit = {
println("digraph waits {")
for (i assembly.stages.indices) {
println(s"""N$i [label="${assembly.stages(i)}"]""")
}
def nameIn(port: Int): String = {
val owner = assembly.inOwners(port)
if (owner == Boundary) "Out" + port
else "N" + owner
}
def nameOut(port: Int): String = {
val owner = assembly.outOwners(port)
if (owner == Boundary) "In" + port
else "N" + owner
}
for (i portStates.indices) {
portStates(i) match {
case InReady
println(s""" ${nameIn(i)} -> ${nameOut(i)} [label="shouldPull"; color=blue]; """)
case OutReady
println(s""" ${nameOut(i)} -> ${nameIn(i)} [label="shouldPush"; color=red]; """)
case x if (x | InClosed | OutClosed) == (InClosed | OutClosed)
println(s""" ${nameIn(i)} -> ${nameOut(i)} [style=dotted; label="closed" dir=both]; """)
case _
}
}
println("}")
println(s"// $queueStatus (running=$runningStages, shutdown=${shutdownCounter.mkString(",")})")
}
}