!str #16565 Make Flexi* limitations explicit

* remove isDemandAvailable
* hide emit from CompletionHandler context
* throw if more than one emit in response to an input
* had to remove the OrderedMerge test/sample because emitting
  from CompletionHandler is currently not supported
* FlexiRoute and FlexiMerge might become more capable later, see issue 16753
This commit is contained in:
Patrik Nordwall 2015-01-29 15:58:23 +01:00
parent 8095ebb3cc
commit 2740d67c61
14 changed files with 206 additions and 303 deletions

View file

@ -312,44 +312,4 @@ class FlexiDocSpec extends AkkaSpec {
}.run()
}
"flexi route completion handling emitting element upstream completion" in {
class ElementsAndStatus[A] extends FlexiRoute[A] {
import FlexiRoute._
val out = createOutputPort[A]()
override def createRouteLogic() = new RouteLogic[A] {
override def outputHandles(outputCount: Int) = Vector(out)
// format: OFF
//#flexiroute-completion-upstream-completed-signalling
var buffer: List[A]
//#flexiroute-completion-upstream-completed-signalling
= List[A]()
// format: ON
//#flexiroute-completion-upstream-completed-signalling
def drainBuffer(ctx: RouteLogicContext[Any]): Unit =
while (ctx.isDemandAvailable(out) && buffer.nonEmpty) {
ctx.emit(out, buffer.head)
buffer = buffer.tail
}
val signalStatusOnTermination = CompletionHandling(
onUpstreamFinish = ctx => drainBuffer(ctx),
onUpstreamFailure = (ctx, cause) => drainBuffer(ctx),
onDownstreamFinish = (_, _) => SameState)
//#flexiroute-completion-upstream-completed-signalling
override def initialCompletionHandling = signalStatusOnTermination
override def initialState = State[A](DemandFromAny(out)) {
(ctx, output, element) =>
ctx.emit(output, element)
SameState
}
}
}
}
}

View file

@ -203,6 +203,9 @@ completion or errors to the merges downstream stage.
The state function must always return the next behaviour to be used when an element should be pulled from its upstreams,
we use the special :class:`SameState` object which signals :class:`FlexiMerge` that no state transition is needed.
.. note::
As response to an input element it is allowed to emit at most one output element.
Implementing Zip-like merges
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
More complex fan-in junctions may require not only multiple States but also sharing state between those states.
@ -254,6 +257,9 @@ to this stages downstream effectively shutting down the stream.
In case you want to change back to the default completion handling, it is available as ``MergeLogic#defaultCompletionHandling``.
It is not possible to emit elements from the completion handling, since completion
handlers may be invoked at any time (without regard to downstream demand being available).
Using FlexiRoute
----------------
Similarily to using :class:`FlexiMerge`, implementing custom fan-out stages requires extending the :class:`FlexiRoute` class
@ -284,12 +290,15 @@ of the tuple to the ``right`` stream. Notice that since we are emitting values o
the type parameter of this ``State[_]`` must be set to ``Any``. This type can be utilised more efficiently when a junction
is emitting the same type of element to its downstreams e.g. in all *strictly routing* stages.
The state function must always return the next behaviour to be used when an element should be emited,
The state function must always return the next behaviour to be used when an element should be emitted,
we use the special :class:`SameState` object which signals :class:`FlexiRoute` that no state transition is needed.
.. warning::
While a :class:`RouteLogic` instance *may* be stateful, the :class:`FlexiRoute` instance
*must not* hold any mutable state, since it may be shared across several materialized ``FlowGraph`` instances.
.. note::
It is only allowed to `emit` at most one element to each output in response to `onInput`, `IllegalStateException` is thrown.
Completion handling
^^^^^^^^^^^^^^^^^^^
@ -313,14 +322,6 @@ Notice that State changes are only allowed in reaction to downstream cancellatio
cases. This is because since there is only one upstream, there is nothing else to do than possibly flush buffered elements
and continue with shutting down the entire stream.
Sometimes you may want to emit buffered or additional elements from the completion handler when the stream is shutting down.
However calling ``ctx.emit`` is only legal when the stream we emit to *has demand available*. In normal operation,
this is guaranteed by properly using demand conditions, however as completion handlers may be invokead at any time (without
regard to downstream demand being available) we must explicitly check that the downstream has demand available before signalling it.
It is not possible to emit elements from the completion handling, since completion
handlers may be invoked at any time (without regard to downstream demand being available).
The completion strategy below assumes that we have implemented some kind of :class:`FlexiRoute` which buffers elements,
yet when its upstream completes it should drain as much as possible to its downstream ``out`` output port. We use the
``ctx.isDemandAvailable(outputHandle)`` method to make sure calling emit with the buffered elements is valid and
complete this flushing once all demand (or the buffer) is drained:
.. includecode:: code/docs/stream/FlexiDocSpec.scala#flexiroute-completion-upstream-completed-signalling

View file

@ -39,7 +39,7 @@ Akka Streams currently provide these junctions:
- ``ZipWith[A,B,...,Out]`` (n inputs (defined upfront), 1 output), which takes a function of n inputs that, given all inputs are signalled, transforms and emits 1 output,
- ``Zip[A,B]`` (2 inputs, 1 output), which is a :class:`ZipWith` specialised to zipping input streams of ``A`` and ``B`` into an ``(A,B)`` tuple stream,
- ``Concat[A]`` (2 inputs, 1 output), which enables to concatenate streams (first consume one, then the second one), thus the order of which stream is ``first`` and which ``second`` matters,
- ``FlexiMerge[Out]`` (n inputs, 1 output), which enables writing custom fan out elements using a simple DSL.
- ``FlexiMerge[Out]`` (n inputs, 1 output), which enables writing custom fan-in elements using a simple DSL.
One of the goals of the FlowGraph DSL is to look similar to how one would draw a graph on a whiteboard, so that it is
simple to translate a design from whiteboard to code and be able to relate those two. Let's illustrate this by translating