pekko/akka-stream/src/main/scala/akka/stream/impl2/ActorBasedFlowMaterializer.scala
Roland Kuhn 61b77ea50c =str #15755 #15756 rework Source/Sink materialization
The philosophy is that the FlowMaterializer has complete control over
how it interprets the AST, no restrictions. Therefore it only involves
one specified method: materialize() which returns a MaterializedFlow.
Within the ActorBasedFlowMaterializer we materialize Sources and Sinks
that implement the specified SimpleSource/SourceWithKey interfaces (same
for Sinks), others are not supported. These traits are extensible and
they require that an ActorBasedFlowMaterializer is passed into the
factory methods. Other materializers can of course interpret these AST
nodes differently, or they can use the actor-based facilities by
creating a suitable materializer for them to use.

This means that everything is fully extensible, but the infrastructure
we provide concretely for ourselves is built exactly for that and
nothing more. Overgeneralization would just lead nowhere.

Also made FutureSink isActive and implement it using a light-weight
Subscriber instead of a Flow/Transformer.
2014-09-04 14:05:51 +02:00

192 lines
7 KiB
Scala

/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.impl2
import java.util.concurrent.atomic.AtomicLong
import scala.annotation.tailrec
import scala.collection.immutable
import scala.concurrent.Await
import org.reactivestreams.{ Processor, Publisher, Subscriber }
import akka.actor._
import akka.pattern.ask
import akka.stream.{ MaterializerSettings, Transformer }
import akka.stream.impl.{ ActorProcessor, ActorPublisher, ExposedPublisher, TransformProcessorImpl }
import akka.stream.scaladsl2._
/**
* INTERNAL API
*/
private[akka] object Ast {
sealed trait AstNode {
def name: String
}
case class Transform(name: String, mkTransformer: () Transformer[Any, Any]) extends AstNode
}
/**
* INTERNAL API
*/
case class ActorBasedFlowMaterializer(override val settings: MaterializerSettings,
supervisor: ActorRef,
flowNameCounter: AtomicLong,
namePrefix: String)
extends FlowMaterializer(settings) {
import akka.stream.impl2.Ast._
def withNamePrefix(name: String): FlowMaterializer = this.copy(namePrefix = name)
private def nextFlowNameCount(): Long = flowNameCounter.incrementAndGet()
private def createFlowName(): String = s"$namePrefix-${nextFlowNameCount()}"
@tailrec private def processorChain(topSubscriber: Subscriber[_], ops: immutable.Seq[AstNode],
flowName: String, n: Int): Subscriber[_] = {
ops match {
case op :: tail
val opProcessor: Processor[Any, Any] = processorForNode(op, flowName, n)
opProcessor.subscribe(topSubscriber.asInstanceOf[Subscriber[Any]])
processorChain(opProcessor, tail, flowName, n - 1)
case _ topSubscriber
}
}
// Ops come in reverse order
override def materialize[In, Out](source: Source[In], sink: Sink[Out], ops: List[Ast.AstNode]): MaterializedFlow = {
val flowName = createFlowName()
def attachSink(pub: Publisher[Out]) = sink match {
case s: SimpleSink[Out] s.attach(pub, this, flowName)
case s: SinkWithKey[Out, _] s.attach(pub, this, flowName)
case _ throw new MaterializationException("unknown Sink type " + sink.getClass)
}
def attachSource(sub: Subscriber[In]) = source match {
case s: SimpleSource[In] s.attach(sub, this, flowName)
case s: SourceWithKey[In, _] s.attach(sub, this, flowName)
case _ throw new MaterializationException("unknown Source type " + sink.getClass)
}
def createSink() = sink.asInstanceOf[Sink[In]] match {
case s: SimpleSink[In] s.create(this, flowName) -> (())
case s: SinkWithKey[In, _] s.create(this, flowName)
case _ throw new MaterializationException("unknown Sink type " + sink.getClass)
}
def createSource() = source.asInstanceOf[Source[Out]] match {
case s: SimpleSource[Out] s.create(this, flowName) -> (())
case s: SourceWithKey[Out, _] s.create(this, flowName)
case _ throw new MaterializationException("unknown Source type " + sink.getClass)
}
def isActive(s: AnyRef) = s match {
case source: SimpleSource[_] source.isActive
case source: SourceWithKey[_, _] source.isActive
case sink: SimpleSink[_] sink.isActive
case sink: SinkWithKey[_, _] sink.isActive
case _: Source[_] throw new MaterializationException("unknown Source type " + sink.getClass)
case _: Sink[_] throw new MaterializationException("unknown Sink type " + sink.getClass)
}
val (sourceValue, sinkValue) =
if (ops.isEmpty) {
if (isActive(sink)) {
val (sub, value) = createSink()
(attachSource(sub), value)
} else if (isActive(source)) {
val (pub, value) = createSource()
(value, attachSink(pub))
} else {
val id: Processor[In, Out] = processorForNode(identityTransform, flowName, 1).asInstanceOf[Processor[In, Out]]
(attachSource(id), attachSink(id))
}
} else {
val opsSize = ops.size
val last = processorForNode(ops.head, flowName, opsSize).asInstanceOf[Processor[Any, Out]]
val first = processorChain(last, ops.tail, flowName, opsSize - 1).asInstanceOf[Processor[In, Any]]
(attachSource(first), attachSink(last))
}
new MaterializedFlow(source, sourceValue, sink, sinkValue)
}
private val identityTransform = Transform("identity", ()
new Transformer[Any, Any] {
override def onNext(element: Any) = List(element)
})
private def processorForNode(op: AstNode, flowName: String, n: Int): Processor[Any, Any] = {
val impl = actorOf(ActorProcessorFactory.props(settings, op), s"$flowName-$n-${op.name}")
ActorProcessorFactory(impl)
}
def actorOf(props: Props, name: String): ActorRef = supervisor match {
case ref: LocalActorRef
ref.underlying.attachChild(props, name, systemService = false)
case ref: RepointableActorRef
if (ref.isStarted)
ref.underlying.asInstanceOf[ActorCell].attachChild(props, name, systemService = false)
else {
implicit val timeout = ref.system.settings.CreationTimeout
val f = (supervisor ? StreamSupervisor.Materialize(props, name)).mapTo[ActorRef]
Await.result(f, timeout.duration)
}
case _
throw new IllegalStateException(s"Stream supervisor must be a local actor, was [${supervisor.getClass.getName}]")
}
}
/**
* INTERNAL API
*/
private[akka] object FlowNameCounter extends ExtensionId[FlowNameCounter] with ExtensionIdProvider {
override def get(system: ActorSystem): FlowNameCounter = super.get(system)
override def lookup = FlowNameCounter
override def createExtension(system: ExtendedActorSystem): FlowNameCounter = new FlowNameCounter
}
/**
* INTERNAL API
*/
private[akka] class FlowNameCounter extends Extension {
val counter = new AtomicLong(0)
}
/**
* INTERNAL API
*/
private[akka] object StreamSupervisor {
def props(settings: MaterializerSettings): Props = Props(new StreamSupervisor(settings))
case class Materialize(props: Props, name: String)
}
private[akka] class StreamSupervisor(settings: MaterializerSettings) extends Actor {
import StreamSupervisor._
def receive = {
case Materialize(props, name)
val impl = context.actorOf(props, name)
sender() ! impl
}
}
/**
* INTERNAL API
*/
private[akka] object ActorProcessorFactory {
import Ast._
def props(settings: MaterializerSettings, op: AstNode): Props =
(op match {
case t: Transform Props(new TransformProcessorImpl(settings, t.mkTransformer()))
}).withDispatcher(settings.dispatcher)
def apply[I, O](impl: ActorRef): ActorProcessor[I, O] = {
val p = new ActorProcessor[I, O](impl)
impl ! ExposedPublisher(p.asInstanceOf[ActorPublisher[Any]])
p
}
}