Merge pull request #15790 from akka/wip-15755-15756-source-sink-patriknw

=str #15755 #15756 source and sink
This commit is contained in:
Roland Kuhn 2014-09-04 14:07:03 +02:00
commit 72080a7cc2
24 changed files with 1595 additions and 328 deletions

View file

@ -14,8 +14,7 @@ object FlowMaterializer {
* Scala API: Creates a FlowMaterializer which will execute every step of a transformation
* pipeline within its own [[akka.actor.Actor]]. The required [[akka.actor.ActorRefFactory]]
* (which can be either an [[akka.actor.ActorSystem]] or an [[akka.actor.ActorContext]])
* will be used to create these actors, therefore it is *forbidden* to pass this object
* to another actor if the factory is an ActorContext.
* will be used to create one actor that in turn creates actors for the transformation steps.
*
* The materializer's [[akka.stream.MaterializerSettings]] will be obtained from the
* configuration of the `context`'s underlying [[akka.actor.ActorSystem]].
@ -83,8 +82,7 @@ object FlowMaterializer {
* Java API: Creates a FlowMaterializer which will execute every step of a transformation
* pipeline within its own [[akka.actor.Actor]]. The required [[akka.actor.ActorRefFactory]]
* (which can be either an [[akka.actor.ActorSystem]] or an [[akka.actor.ActorContext]])
* will be used to create these actors, therefore it is *forbidden* to pass this object
* to another actor if the factory is an ActorContext.
* will be used to create one actor that in turn creates actors for the transformation steps.
*/
def create(settings: MaterializerSettings, context: ActorRefFactory): FlowMaterializer =
apply(Option(settings), None)(context)

View file

@ -76,13 +76,13 @@ private[akka] object Ast {
final case class IteratorPublisherNode[I](iterator: Iterator[I]) extends PublisherNode[I] {
final def createPublisher(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[I] =
if (iterator.isEmpty) EmptyPublisher.asInstanceOf[Publisher[I]]
if (iterator.isEmpty) EmptyPublisher[I]
else ActorPublisher[I](materializer.actorOf(IteratorPublisher.props(iterator, materializer.settings),
name = s"$flowName-0-iterator"))
}
final case class IterablePublisherNode[I](iterable: immutable.Iterable[I]) extends PublisherNode[I] {
def createPublisher(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[I] =
if (iterable.isEmpty) EmptyPublisher.asInstanceOf[Publisher[I]]
if (iterable.isEmpty) EmptyPublisher[I]
else ActorPublisher[I](materializer.actorOf(IterablePublisher.props(iterable, materializer.settings),
name = s"$flowName-0-iterable"), Some(iterable))
}

View file

@ -10,6 +10,7 @@ import org.reactivestreams.{ Subscriber, Publisher }
*/
private[akka] case object EmptyPublisher extends Publisher[Nothing] {
def subscribe(subscriber: Subscriber[Nothing]): Unit = subscriber.onComplete()
def apply[T]: Publisher[T] = this.asInstanceOf[Publisher[T]]
}
/**
@ -17,4 +18,5 @@ private[akka] case object EmptyPublisher extends Publisher[Nothing] {
*/
private[akka] case class ErrorPublisher(t: Throwable) extends Publisher[Nothing] {
def subscribe(subscriber: Subscriber[Nothing]): Unit = subscriber.onError(t)
def apply[T]: Publisher[T] = this.asInstanceOf[Publisher[T]]
}

View file

@ -14,7 +14,7 @@ import scala.util.control.NonFatal
*/
private[akka] object SynchronousPublisherFromIterable {
def apply[T](iterable: immutable.Iterable[T]): Publisher[T] =
if (iterable.isEmpty) EmptyPublisher.asInstanceOf[Publisher[T]]
if (iterable.isEmpty) EmptyPublisher[T]
else new SynchronousPublisherFromIterable(iterable)
private class IteratorSubscription[T](subscriber: Subscriber[T], iterator: Iterator[T]) extends Subscription {

View file

@ -1,5 +1,5 @@
/**
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.impl

View file

@ -4,23 +4,18 @@
package akka.stream.impl2
import java.util.concurrent.atomic.AtomicLong
import akka.actor.{ Actor, ActorCell, ActorRef, ActorSystem, ExtendedActorSystem, Extension, ExtensionId, ExtensionIdProvider, LocalActorRef, Props, RepointableActorRef }
import akka.pattern.ask
import org.reactivestreams.{ Processor, Publisher, Subscriber }
import scala.annotation.tailrec
import scala.collection.immutable
import scala.concurrent.{ Await, Future }
import scala.concurrent.duration._
import scala.util.{ Failure, Success }
import akka.stream.Transformer
import akka.stream.scaladsl2.FlowMaterializer
import akka.stream.MaterializerSettings
import akka.stream.impl.EmptyPublisher
import akka.stream.impl.ActorPublisher
import akka.stream.impl.IterablePublisher
import akka.stream.impl.TransformProcessorImpl
import akka.stream.impl.ActorProcessor
import akka.stream.impl.ExposedPublisher
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
@ -32,32 +27,17 @@ private[akka] object Ast {
case class Transform(name: String, mkTransformer: () Transformer[Any, Any]) extends AstNode
trait PublisherNode[I] {
private[akka] def createPublisher(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[I]
}
final case class ExistingPublisher[I](publisher: Publisher[I]) extends PublisherNode[I] {
def createPublisher(materializer: ActorBasedFlowMaterializer, flowName: String) = publisher
}
final case class IterablePublisherNode[I](iterable: immutable.Iterable[I]) extends PublisherNode[I] {
def createPublisher(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[I] =
if (iterable.isEmpty) EmptyPublisher.asInstanceOf[Publisher[I]]
else ActorPublisher[I](materializer.actorOf(IterablePublisher.props(iterable, materializer.settings),
name = s"$flowName-0-iterable"), Some(iterable))
}
}
/**
* INTERNAL API
*/
private[akka] case class ActorBasedFlowMaterializer(
override val settings: MaterializerSettings,
supervisor: ActorRef,
flowNameCounter: AtomicLong,
namePrefix: String)
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)
@ -78,16 +58,57 @@ private[akka] case class ActorBasedFlowMaterializer(
}
// Ops come in reverse order
override def toPublisher[I, O](publisherNode: PublisherNode[I], ops: List[AstNode]): Publisher[O] = {
override def materialize[In, Out](source: Source[In], sink: Sink[Out], ops: List[Ast.AstNode]): MaterializedFlow = {
val flowName = createFlowName()
if (ops.isEmpty) publisherNode.createPublisher(this, flowName).asInstanceOf[Publisher[O]]
else {
val opsSize = ops.size
val opProcessor = processorForNode(ops.head, flowName, opsSize)
val topSubscriber = processorChain(opProcessor, ops.tail, flowName, opsSize - 1)
publisherNode.createPublisher(this, flowName).subscribe(topSubscriber.asInstanceOf[Subscriber[I]])
opProcessor.asInstanceOf[Publisher[O]]
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", ()
@ -95,7 +116,7 @@ private[akka] case class ActorBasedFlowMaterializer(
override def onNext(element: Any) = List(element)
})
def processorForNode(op: AstNode, flowName: String, n: Int): Processor[Any, Any] = {
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)
}
@ -168,4 +189,4 @@ private[akka] object ActorProcessorFactory {
impl ! ExposedPublisher(p.asInstanceOf[ActorPublisher[Any]])
p
}
}
}

View file

@ -6,122 +6,47 @@ package akka.stream.scaladsl2
import scala.language.higherKinds
import scala.collection.immutable
import scala.concurrent.Future
import org.reactivestreams.Publisher
import org.reactivestreams.Subscriber
import akka.stream.Transformer
import akka.stream._
import akka.stream.impl.BlackholeSubscriber
import akka.stream.impl2.Ast._
import scala.annotation.unchecked.uncheckedVariance
import akka.stream.impl.BlackholeSubscriber
import scala.concurrent.Promise
import akka.stream.impl.EmptyPublisher
import akka.stream.impl.IterablePublisher
import akka.stream.impl2.ActorBasedFlowMaterializer
import org.reactivestreams._
import scala.concurrent.duration.FiniteDuration
import scala.util.Try
import scala.util.Failure
import scala.util.Success
/**
* This is the interface from which all concrete Flows inherit. No generic
* operations are presented because the concrete type of Flow (i.e. whether
* it has a [[Source]] or a [[Sink]]) determines what is available.
*/
sealed trait Flow
object FlowFrom {
/**
* Helper to create `Flow` without [[Input]].
* Example usage: `FlowFrom[Int]`
*/
def apply[T]: ProcessorFlow[T, T] = ProcessorFlow[T, T](Nil)
/**
* Helper to create `Flow` with Input from `Iterable`.
* Example usage: `FlowFrom(Seq(1,2,3))`
*/
def apply[T](i: immutable.Iterable[T]): PublisherFlow[T, T] = FlowFrom[T].withInput(IterableIn(i))
/**
* Helper to create `Flow` with [[Input]] from `Publisher`.
*/
def apply[T](p: Publisher[T]): PublisherFlow[T, T] = FlowFrom[T].withInput(PublisherIn(p))
}
trait Input[-In]
/**
* Marker interface for flows that have a free (attachable) input side.
*/
sealed trait HasNoSource[-In] extends Flow
/**
* Default input.
* Allows to materialize a Flow with this input to Subscriber.
* Marker interface for flows that have a free (attachable) output side.
*/
final case class SubscriberIn[-In]() extends Input[In] {
def subscriber[I <: In]: Subscriber[I] = ???
}
sealed trait HasNoSink[+Out] extends Flow
/**
* Input from Publisher.
* Operations offered by flows with a free output side: the DSL flows left-to-right only.
*/
final case class PublisherIn[-In](p: Publisher[_ >: In]) extends Input[In]
/**
* Input from Iterable
*
* Changing In from Contravariant to Covariant is needed because Iterable[+A].
* But this brakes IterableIn variance and we get IterableIn(Seq(1,2,3)): IterableIn[Any]
*/
final case class IterableIn[-In](i: immutable.Iterable[_ >: In]) extends Input[In]
/**
* Input from Future
*
* Changing In from Contravariant to Covariant is needed because Future[+A].
* But this brakes FutureIn variance and we get FutureIn(Future{1}): FutureIn[Any]
*/
final case class FutureIn[-In](f: Future[_ >: In]) extends Input[In]
trait Output[+Out]
/**
* Default output.
* Allows to materialize a Flow with this output to Publisher.
*/
final case class PublisherOut[+Out]() extends Output[Out] {
def publisher[O >: Out]: Publisher[O] = ???
}
final case class BlackholeOut[+Out]() extends Output[Out] {
def publisher[O >: Out]: Publisher[O] = ???
}
/**
* Output to a Subscriber.
*/
final case class SubscriberOut[+Out](s: Subscriber[_ <: Out]) extends Output[Out]
/**
* Fold output. Reduces output stream according to the given fold function.
*/
final case class FoldOut[T, +Out](zero: T)(f: (T, Out) T) extends Output[Out] {
def future: Future[T] = ???
}
/**
* Operations with a Flow which has open (no attached) Input.
*
* No Out type parameter would be useful for Graph signatures, but we need it here
* for `withInput` and `prependTransform` methods.
*/
sealed trait HasOpenInput[-In, +Out] extends Flow {
type Repr[-In, +Out] <: HasOpenInput[In, Out]
type AfterCloseInput[-In, +Out] <: Flow
def withInput[I <: In](in: Input[I]): AfterCloseInput[I, Out]
def prepend[T](f: ProcessorFlow[T, In]): Repr[T, Out]
def prepend[T](f: PublisherFlow[T, In]): Repr[T, Out]#AfterCloseInput[T, Out]
}
/**
* Operations with a Flow which has open (no attached) Output.
*
* No In type parameter would be useful for Graph signatures, but we need it here
* for `withOutput`.
*/
trait HasOpenOutput[-In, +Out] extends Flow {
type Repr[-In, +Out] <: HasOpenOutput[In, Out]
type AfterCloseOutput[-In, +Out] <: Flow
trait FlowOps[-In, +Out] extends HasNoSink[Out] {
type Repr[-I, +O] <: FlowOps[I, O]
// Storing ops in reverse order
protected def andThen[U](op: AstNode): Repr[In, U]
def withOutput[O >: Out](out: Output[O]): AfterCloseOutput[In, O]
def map[T](f: Out T): Repr[In, T] =
transform("map", () new Transformer[Out, T] {
override def onNext(in: Out) = List(f(in))
@ -130,89 +55,98 @@ trait HasOpenOutput[-In, +Out] extends Flow {
def transform[T](name: String, mkTransformer: () Transformer[Out, T]): Repr[In, T] = {
andThen(Transform(name, mkTransformer.asInstanceOf[() Transformer[Any, Any]]))
}
def append[T](f: ProcessorFlow[Out, T]): Repr[In, T]
def append[T](f: SubscriberFlow[Out, T]): Repr[In, T]#AfterCloseOutput[In, T]
}
/**
* Flow without attached input and without attached output, can be used as a `Processor`.
*/
final case class ProcessorFlow[-In, +Out](ops: List[AstNode]) extends HasOpenOutput[In, Out] with HasOpenInput[In, Out] {
override type Repr[-In, +Out] = ProcessorFlow[In, Out]
type AfterCloseOutput[-In, +Out] = SubscriberFlow[In, Out]
type AfterCloseInput[-In, +Out] = PublisherFlow[In, Out]
final case class ProcessorFlow[-In, +Out](ops: List[AstNode]) extends FlowOps[In, Out] with HasNoSource[In] {
override type Repr[-I, +O] = ProcessorFlow[I, O]
override protected def andThen[U](op: AstNode): Repr[In, U] = this.copy(ops = op :: ops)
def withOutput[O >: Out](out: Output[O]): AfterCloseOutput[In, O] = SubscriberFlow(out, ops)
def withInput[I <: In](in: Input[I]): AfterCloseInput[I, Out] = PublisherFlow(in, ops)
def withSink(out: Sink[Out]): FlowWithSink[In, Out] = FlowWithSink(out, ops)
def withSource(in: Source[In]): FlowWithSource[In, Out] = FlowWithSource(in, ops)
override def prepend[T](f: ProcessorFlow[T, In]): Repr[T, Out] =
ProcessorFlow(ops ::: f.ops)
override def prepend[T](f: PublisherFlow[T, In]): Repr[T, Out]#AfterCloseInput[T, Out] =
PublisherFlow(f.input, ops ::: f.ops)
def prepend[T](f: ProcessorFlow[T, In]): ProcessorFlow[T, Out] = ProcessorFlow(ops ::: f.ops)
def prepend[T](f: FlowWithSource[T, In]): FlowWithSource[T, Out] = f.append(this)
override def append[T](f: ProcessorFlow[Out, T]): Repr[In, T] = ProcessorFlow(f.ops ++: ops)
override def append[T](f: SubscriberFlow[Out, T]): Repr[In, T]#AfterCloseOutput[In, T] =
SubscriberFlow(f.output, f.ops ++: ops)
def append[T](f: ProcessorFlow[Out, T]): ProcessorFlow[In, T] = ProcessorFlow(f.ops ++: ops)
def append[T](f: FlowWithSink[Out, T]): FlowWithSink[In, T] = f.prepend(this)
}
/**
* Flow with attached output, can be used as a `Subscriber`.
*/
final case class SubscriberFlow[-In, +Out](output: Output[Out], ops: List[AstNode]) extends HasOpenInput[In, Out] {
type Repr[-In, +Out] = SubscriberFlow[In, Out]
type AfterCloseInput[-In, +Out] = RunnableFlow[In, Out]
final case class FlowWithSink[-In, +Out](private[scaladsl2] val output: Sink[Out @uncheckedVariance], ops: List[AstNode]) extends HasNoSource[In] {
def withInput[I <: In](in: Input[I]): AfterCloseInput[I, Out] = RunnableFlow(in, output, ops)
def withoutOutput: ProcessorFlow[In, Out] = ProcessorFlow(ops)
def withSource(in: Source[In]): RunnableFlow[In, Out] = RunnableFlow(in, output, ops)
def withoutSink: ProcessorFlow[In, Out] = ProcessorFlow(ops)
override def prepend[T](f: ProcessorFlow[T, In]): Repr[T, Out] =
SubscriberFlow(output, ops ::: f.ops)
override def prepend[T](f: PublisherFlow[T, In]): Repr[T, Out]#AfterCloseInput[T, Out] =
RunnableFlow(f.input, output, ops ::: f.ops)
def prepend[T](f: ProcessorFlow[T, In]): FlowWithSink[T, Out] = FlowWithSink(output, ops ::: f.ops)
def prepend[T](f: FlowWithSource[T, In]): RunnableFlow[T, Out] = RunnableFlow(f.input, output, ops ::: f.ops)
def toSubscriber()(implicit materializer: FlowMaterializer): Subscriber[In @uncheckedVariance] = {
val subIn = SubscriberSource[In]()
val mf = withSource(subIn).run()
subIn.subscriber(mf)
}
}
/**
* Flow with attached input, can be used as a `Publisher`.
*/
final case class PublisherFlow[-In, +Out](input: Input[In], ops: List[AstNode]) extends HasOpenOutput[In, Out] {
override type Repr[-In, +Out] = PublisherFlow[In, Out]
type AfterCloseOutput[-In, +Out] = RunnableFlow[In, Out]
final case class FlowWithSource[-In, +Out](private[scaladsl2] val input: Source[In @uncheckedVariance], ops: List[AstNode]) extends FlowOps[In, Out] {
override type Repr[-I, +O] = FlowWithSource[I, O]
override protected def andThen[U](op: AstNode): Repr[In, U] = this.copy(ops = op :: ops)
def withOutput[O >: Out](out: Output[O]): AfterCloseOutput[In, O] = RunnableFlow(input, out, ops)
def withoutInput: ProcessorFlow[In, Out] = ProcessorFlow(ops)
def withSink(out: Sink[Out]): RunnableFlow[In, Out] = RunnableFlow(input, out, ops)
def withoutSource: ProcessorFlow[In, Out] = ProcessorFlow(ops)
override def append[T](f: ProcessorFlow[Out, T]): Repr[In, T] = PublisherFlow(input, f.ops ++: ops)
override def append[T](f: SubscriberFlow[Out, T]): Repr[In, T]#AfterCloseOutput[In, T] =
RunnableFlow(input, f.output, f.ops ++: ops)
def append[T](f: ProcessorFlow[Out, T]): FlowWithSource[In, T] = FlowWithSource(input, f.ops ++: ops)
def append[T](f: FlowWithSink[Out, T]): RunnableFlow[In, T] = RunnableFlow(input, f.output, f.ops ++: ops)
def toPublisher()(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] = {
val pubOut = PublisherSink[Out]
val mf = withSink(pubOut).run()
pubOut.publisher(mf)
}
def publishTo(subscriber: Subscriber[Out @uncheckedVariance])(implicit materializer: FlowMaterializer): Unit =
toPublisher().subscribe(subscriber)
def consume()(implicit materializer: FlowMaterializer): Unit =
withSink(BlackholeSink).run()
}
/**
* Flow with attached input and output, can be executed.
*/
final case class RunnableFlow[-In, +Out](input: Input[In], output: Output[Out], ops: List[AstNode]) extends Flow {
def withoutOutput: PublisherFlow[In, Out] = PublisherFlow(input, ops)
def withoutInput: SubscriberFlow[In, Out] = SubscriberFlow(output, ops)
// FIXME
def run()(implicit materializer: FlowMaterializer): Unit =
produceTo(new BlackholeSubscriber[Any](materializer.settings.maxInputBufferSize))
// FIXME replace with run and input/output factories
def toPublisher[U >: Out]()(implicit materializer: FlowMaterializer): Publisher[U] =
input match {
case PublisherIn(p) materializer.toPublisher(ExistingPublisher(p), ops)
case IterableIn(iter) materializer.toPublisher(IterablePublisherNode(iter), ops)
case _ ???
}
def produceTo(subscriber: Subscriber[_ >: Out])(implicit materializer: FlowMaterializer): Unit =
toPublisher().subscribe(subscriber.asInstanceOf[Subscriber[Out]])
final case class RunnableFlow[-In, +Out](private[scaladsl2] val input: Source[In @uncheckedVariance],
private[scaladsl2] val output: Sink[Out @uncheckedVariance], ops: List[AstNode]) extends Flow {
def withoutSink: FlowWithSource[In, Out] = FlowWithSource(input, ops)
def withoutSource: FlowWithSink[In, Out] = FlowWithSink(output, ops)
def run()(implicit materializer: FlowMaterializer): MaterializedFlow =
materializer.materialize(input, output, ops)
}
class MaterializedFlow(sourceKey: AnyRef, matSource: Any, sinkKey: AnyRef, matSink: Any) extends MaterializedSource with MaterializedSink {
override def getSourceFor[T](key: SourceWithKey[_, T]): T =
if (key == sourceKey) matSource.asInstanceOf[T]
else throw new IllegalArgumentException(s"Source key [$key] doesn't match the source [$sourceKey] of this flow")
def getSinkFor[T](key: SinkWithKey[_, T]): T =
if (key == sinkKey) matSink.asInstanceOf[T]
else throw new IllegalArgumentException(s"Sink key [$key] doesn't match the sink [$sinkKey] of this flow")
}
trait MaterializedSource {
def getSourceFor[T](sourceKey: SourceWithKey[_, T]): T
}
trait MaterializedSink {
def getSinkFor[T](sinkKey: SinkWithKey[_, T]): T
}

View file

@ -3,21 +3,32 @@
*/
package akka.stream.scaladsl2
import scala.concurrent.duration.FiniteDuration
import akka.actor.ActorRefFactory
import akka.stream.impl2.ActorBasedFlowMaterializer
import akka.stream.impl2.Ast
import org.reactivestreams.{ Publisher, Subscriber }
import scala.concurrent.duration._
import akka.actor.Deploy
import akka.actor.ExtendedActorSystem
import akka.actor.ActorContext
import akka.stream.impl2.StreamSupervisor
import akka.stream.impl2.FlowNameCounter
import akka.actor.{ ActorContext, ActorRefFactory, ActorSystem, ExtendedActorSystem }
import akka.stream.MaterializerSettings
import akka.stream.impl2.{ ActorBasedFlowMaterializer, Ast, FlowNameCounter, StreamSupervisor }
object FlowMaterializer {
/**
* Scala API: Creates a FlowMaterializer which will execute every step of a transformation
* pipeline within its own [[akka.actor.Actor]]. The required [[akka.actor.ActorRefFactory]]
* (which can be either an [[akka.actor.ActorSystem]] or an [[akka.actor.ActorContext]])
* will be used to create one actor that in turn creates actors for the transformation steps.
*
* The materializer's [[akka.stream.MaterializerSettings]] will be obtained from the
* configuration of the `context`'s underlying [[akka.actor.ActorSystem]].
*
* The `namePrefix` is used as the first part of the names of the actors running
* the processing steps. The default `namePrefix` is `"flow"`. The actor names are built up of
* `namePrefix-flowNumber-flowStepNumber-stepName`.
*/
def apply(materializerSettings: Option[MaterializerSettings] = None, namePrefix: Option[String] = None)(implicit context: ActorRefFactory): FlowMaterializer = {
val system = actorSystemOf(context)
val settings = materializerSettings getOrElse MaterializerSettings(system)
apply(settings, namePrefix.getOrElse("flow"))(context)
}
/**
* Scala API: Creates a FlowMaterializer which will execute every step of a transformation
* pipeline within its own [[akka.actor.Actor]]. The required [[akka.actor.ActorRefFactory]]
@ -29,31 +40,77 @@ object FlowMaterializer {
* the processing steps. The default `namePrefix` is `"flow"`. The actor names are built up of
* `namePrefix-flowNumber-flowStepNumber-stepName`.
*/
def apply(settings: MaterializerSettings, namePrefix: Option[String] = None)(implicit context: ActorRefFactory): FlowMaterializer = {
val system = context match {
case s: ExtendedActorSystem s
case c: ActorContext c.system
case null throw new IllegalArgumentException("ActorRefFactory context must be defined")
case _ throw new IllegalArgumentException(s"ActorRefFactory context must be a ActorSystem or ActorContext, " +
"got [${_contex.getClass.getName}]")
}
def apply(materializerSettings: MaterializerSettings, namePrefix: String)(implicit context: ActorRefFactory): FlowMaterializer = {
val system = actorSystemOf(context)
new ActorBasedFlowMaterializer(
settings,
context.actorOf(StreamSupervisor.props(settings).withDispatcher(settings.dispatcher)),
materializerSettings,
context.actorOf(StreamSupervisor.props(materializerSettings).withDispatcher(materializerSettings.dispatcher)),
FlowNameCounter(system).counter,
namePrefix.getOrElse("flow"))
namePrefix)
}
/**
* Scala API: Creates a FlowMaterializer which will execute every step of a transformation
* pipeline within its own [[akka.actor.Actor]]. The required [[akka.actor.ActorRefFactory]]
* (which can be either an [[akka.actor.ActorSystem]] or an [[akka.actor.ActorContext]])
* will be used to create these actors, therefore it is *forbidden* to pass this object
* to another actor if the factory is an ActorContext.
*
* The `namePrefix` is used as the first part of the names of the actors running
* the processing steps. The default `namePrefix` is `"flow"`. The actor names are built up of
* `namePrefix-flowNumber-flowStepNumber-stepName`.
*/
def apply(materializerSettings: MaterializerSettings)(implicit context: ActorRefFactory): FlowMaterializer =
apply(Some(materializerSettings), None)
/**
* Java API: Creates a FlowMaterializer which will execute every step of a transformation
* pipeline within its own [[akka.actor.Actor]]. The required [[akka.actor.ActorRefFactory]]
* (which can be either an [[akka.actor.ActorSystem]] or an [[akka.actor.ActorContext]])
* will be used to create these actors, therefore it is *forbidden* to pass this object
* to another actor if the factory is an ActorContext.
*
* Defaults the actor name prefix used to name actors running the processing steps to `"flow"`.
* The actor names are built up of `namePrefix-flowNumber-flowStepNumber-stepName`.
*/
def create(context: ActorRefFactory): FlowMaterializer =
apply()(context)
/**
* Java API: Creates a FlowMaterializer which will execute every step of a transformation
* pipeline within its own [[akka.actor.Actor]]. The required [[akka.actor.ActorRefFactory]]
* (which can be either an [[akka.actor.ActorSystem]] or an [[akka.actor.ActorContext]])
* will be used to create one actor that in turn creates actors for the transformation steps.
*/
def create(settings: MaterializerSettings, context: ActorRefFactory): FlowMaterializer =
apply(settings)(context)
apply(Option(settings), None)(context)
/**
* Java API: Creates a FlowMaterializer which will execute every step of a transformation
* pipeline within its own [[akka.actor.Actor]]. The required [[akka.actor.ActorRefFactory]]
* (which can be either an [[akka.actor.ActorSystem]] or an [[akka.actor.ActorContext]])
* will be used to create these actors, therefore it is *forbidden* to pass this object
* to another actor if the factory is an ActorContext.
*
* The `namePrefix` is used as the first part of the names of the actors running
* the processing steps. The default `namePrefix` is `"flow"`. The actor names are built up of
* `namePrefix-flowNumber-flowStepNumber-stepName`.
*/
def create(settings: MaterializerSettings, context: ActorRefFactory, namePrefix: String): FlowMaterializer =
apply(Option(settings), Option(namePrefix))(context)
private def actorSystemOf(context: ActorRefFactory): ActorSystem = {
val system = context match {
case s: ExtendedActorSystem s
case c: ActorContext c.system
case null throw new IllegalArgumentException("ActorRefFactory context must be defined")
case _
throw new IllegalArgumentException(s"ActorRefFactory context must be a ActorSystem or ActorContext, got [${context.getClass.getName}]")
}
system
}
}
/**
@ -66,16 +123,25 @@ object FlowMaterializer {
abstract class FlowMaterializer(val settings: MaterializerSettings) {
/**
* The `namePrefix` is used as the first part of the names of the actors running
* the processing steps.
* The `namePrefix` shall be used for deriving the names of processing
* entities that are created during materialization. This is meant to aid
* logging and error reporting both during materialization and while the
* stream is running.
*/
def withNamePrefix(name: String): FlowMaterializer
/**
* INTERNAL API
* ops are stored in reverse order
* This method interprets the given Flow description and creates the running
* stream. The result can be highly implementation specific, ranging from
* local actor chains to remote-deployed processing networks.
*/
private[akka] def toPublisher[I, O](publisherNode: Ast.PublisherNode[I], ops: List[Ast.AstNode]): Publisher[O]
def materialize[In, Out](source: Source[In], sink: Sink[Out], ops: List[Ast.AstNode]): MaterializedFlow
}
/**
* This exception or subtypes thereof should be used to signal materialization
* failures.
*/
class MaterializationException(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause)

View file

@ -0,0 +1,218 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import scala.annotation.unchecked.uncheckedVariance
import scala.concurrent.{ Future, Promise }
import scala.util.{ Failure, Success, Try }
import org.reactivestreams.{ Publisher, Subscriber, Subscription }
import akka.stream.Transformer
import akka.stream.impl.BlackholeSubscriber
import akka.stream.impl2.ActorBasedFlowMaterializer
import java.util.concurrent.atomic.AtomicReference
/**
* This trait is a marker for a pluggable stream sink. Concrete instances should
* implement [[SinkWithKey]] or [[SimpleSink]], otherwise a custom [[FlowMaterializer]]
* will have to be used to be able to attach them.
*
* All Sinks defined in this package rely upon an [[ActorBasedFlowMaterializer]] being
* made available to them in order to use the <code>attach</code> method. Other
* FlowMaterializers can be used but must then implement the functionality of these
* Sink nodes themselves (or construct an ActorBasedFlowMaterializer).
*/
trait Sink[-Out]
/**
* A sink that does not need to create a user-accessible object during materialization.
*/
trait SimpleSink[-Out] extends Sink[Out] {
/**
* Attach this sink to the given [[org.reactivestreams.Publisher]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this sink belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowPublisher the Publisher to consume elements from
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowPublisher: Publisher[Out @uncheckedVariance], materializer: ActorBasedFlowMaterializer, flowName: String): Unit
/**
* This method is only used for Sinks that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[Out] @uncheckedVariance =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Sink can create a Subscriber instead of being
* attached to a Publisher. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
}
/**
* A sink that will create an object during materialization that the user will need
* to retrieve in order to access aspects of this sink (could be a completion Future
* or a cancellation handle, etc.)
*/
trait SinkWithKey[-Out, T] extends Sink[Out] {
/**
* Attach this sink to the given [[org.reactivestreams.Publisher]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this sink belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowPublisher the Publisher to consume elements from
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowPublisher: Publisher[Out @uncheckedVariance], materializer: ActorBasedFlowMaterializer, flowName: String): T
/**
* This method is only used for Sinks that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Subscriber[Out] @uncheckedVariance, T) =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Sink can create a Subscriber instead of being
* attached to a Publisher. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
}
/**
* Holds the downstream-most [[org.reactivestreams.Publisher]] interface of the materialized flow.
* The stream will not have any subscribers attached at this point, which means that after prefetching
* elements to fill the internal buffers it will assert back-pressure until
* a subscriber connects and creates demand for elements to be emitted.
*/
object PublisherSink {
private val instance = new PublisherSink[Nothing]
def apply[T]: PublisherSink[T] = instance.asInstanceOf[PublisherSink[T]]
}
class PublisherSink[Out]() extends SinkWithKey[Out, Publisher[Out]] {
def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] = flowPublisher
def publisher(m: MaterializedSink): Publisher[Out] = m.getSinkFor(this)
override def toString: String = "PublisherSink"
}
/**
* Holds a [[scala.concurrent.Future]] that will be fulfilled with the first
* thing that is signaled to this stream, which can be either an element (after
* which the upstream subscription is canceled), an error condition (putting
* the Future into the corresponding failed state) or the end-of-stream
* (failing the Future with a NoSuchElementException).
*/
object FutureSink {
private val instance = new FutureSink[Nothing]
def apply[T]: FutureSink[T] = instance.asInstanceOf[FutureSink[T]]
}
class FutureSink[Out] extends SinkWithKey[Out, Future[Out]] {
def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Future[Out] = {
val (sub, f) = create(materializer, flowName)
flowPublisher.subscribe(sub)
f
}
override def isActive = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Subscriber[Out], Future[Out]) = {
val p = Promise[Out]()
val sub = new Subscriber[Out] { // TODO #15804 verify this using the RS TCK
private val sub = new AtomicReference[Subscription]
override def onSubscribe(s: Subscription): Unit =
if (!sub.compareAndSet(null, s)) s.cancel()
else s.request(1)
override def onNext(t: Out): Unit = { p.trySuccess(t); sub.get.cancel() }
override def onError(t: Throwable): Unit = p.tryFailure(t)
override def onComplete(): Unit = p.tryFailure(new NoSuchElementException("empty stream"))
}
(sub, p.future)
}
def future(m: MaterializedSink): Future[Out] = m.getSinkFor(this)
override def toString: String = "FutureSink"
}
/**
* Attaches a subscriber to this stream which will just discard all received
* elements.
*/
final case object BlackholeSink extends SimpleSink[Any] {
override def attach(flowPublisher: Publisher[Any], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
flowPublisher.subscribe(create(materializer, flowName))
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[Any] =
new BlackholeSubscriber[Any](materializer.settings.maxInputBufferSize)
}
/**
* Attaches a subscriber to this stream.
*/
final case class SubscriberSink[Out](subscriber: Subscriber[Out]) extends SimpleSink[Out] {
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
flowPublisher.subscribe(subscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[Out] = subscriber
}
object OnCompleteSink {
private val SuccessUnit = Success[Unit](())
}
/**
* When the flow is completed, either through an error or normal
* completion, apply the provided function with [[scala.util.Success]]
* or [[scala.util.Failure]].
*/
final case class OnCompleteSink[Out](callback: Try[Unit] Unit) extends SimpleSink[Out] {
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
FlowFrom(flowPublisher).transform("onCompleteSink", () new Transformer[Out, Unit] {
override def onNext(in: Out) = Nil
override def onError(e: Throwable) = {
callback(Failure(e))
throw e
}
override def onTermination(e: Option[Throwable]) = {
callback(OnCompleteSink.SuccessUnit)
Nil
}
}).consume()(materializer.withNamePrefix(flowName))
}
/**
* Invoke the given procedure for each received element. The sink holds a [[scala.concurrent.Future]]
* that will be completed with `Success` when reaching the normal end of the stream, or completed
* with `Failure` if there is an error is signaled in the stream.
*/
final case class ForeachSink[Out](f: Out Unit) extends SinkWithKey[Out, Future[Unit]] {
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Future[Unit] = {
val promise = Promise[Unit]()
FlowFrom(flowPublisher).transform("foreach", () new Transformer[Out, Unit] {
override def onNext(in: Out) = { f(in); Nil }
override def onError(cause: Throwable): Unit = ()
override def onTermination(e: Option[Throwable]) = {
e match {
case None promise.success(())
case Some(e) promise.failure(e)
}
Nil
}
}).consume()(materializer.withNamePrefix(flowName))
promise.future
}
def future(m: MaterializedSink): Future[Unit] = m.getSinkFor(this)
}

View file

@ -0,0 +1,271 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import scala.annotation.unchecked.uncheckedVariance
import scala.collection.immutable
import scala.concurrent.Future
import scala.concurrent.duration.FiniteDuration
import scala.util.{ Failure, Success }
import org.reactivestreams.{ Publisher, Subscriber }
import akka.stream.impl.{ ActorPublisher, EmptyPublisher, ErrorPublisher, FuturePublisher, IterablePublisher, IteratorPublisher, SimpleCallbackPublisher, TickPublisher }
import akka.stream.impl2.ActorBasedFlowMaterializer
object FlowFrom {
/**
* Helper to create `Flow` without [[Source]].
* Example usage: `FlowFrom[Int]`
*/
def apply[T]: ProcessorFlow[T, T] = ProcessorFlow[T, T](Nil)
/**
* Helper to create `Flow` with [[Source]] from `Publisher`.
*
* Construct a transformation starting with given publisher. The transformation steps
* are executed by a series of [[org.reactivestreams.Processor]] instances
* that mediate the flow of elements downstream and the propagation of
* back-pressure upstream.
*/
def apply[T](publisher: Publisher[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(PublisherSource(publisher))
/**
* Helper to create `Flow` with [[Source]] from `Iterator`.
* Example usage: `FlowFrom(Seq(1,2,3).iterator)`
*
* Start a new `Flow` from the given Iterator. The produced stream of elements
* will continue until the iterator runs empty or fails during evaluation of
* the `next()` method. Elements are pulled out of the iterator
* in accordance with the demand coming from the downstream transformation
* steps.
*/
def apply[T](iterator: Iterator[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(IteratorSource(iterator))
/**
* Helper to create `Flow` with [[Source]] from `Iterable`.
* Example usage: `FlowFrom(Seq(1,2,3))`
*
* Starts a new `Flow` from the given `Iterable`. This is like starting from an
* Iterator, but every Subscriber directly attached to the Publisher of this
* stream will see an individual flow of elements (always starting from the
* beginning) regardless of when they subscribed.
*/
def apply[T](iterable: immutable.Iterable[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(IterableSource(iterable))
/**
* Define the sequence of elements to be produced by the given closure.
* The stream ends normally when evaluation of the closure returns a `None`.
* The stream ends exceptionally when an exception is thrown from the closure.
*/
def apply[T](f: () Option[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(ThunkSource(f))
/**
* Start a new `Flow` from the given `Future`. The stream will consist of
* one element when the `Future` is completed with a successful value, which
* may happen before or after materializing the `Flow`.
* The stream terminates with an error if the `Future` is completed with a failure.
*/
def apply[T](future: Future[T]): FlowWithSource[T, T] = FlowFrom[T].withSource(FutureSource(future))
/**
* Elements are produced from the tick closure periodically with the specified interval.
* The tick element will be delivered to downstream consumers that has requested any elements.
* If a consumer has not requested any elements at the point in time when the tick
* element is produced it will not receive that tick element later. It will
* receive new tick elements as soon as it has requested more elements.
*/
def apply[T](initialDelay: FiniteDuration, interval: FiniteDuration, tick: () T): FlowWithSource[T, T] =
FlowFrom[T].withSource(TickSource(initialDelay, interval, tick))
}
/**
* This trait is a marker for a pluggable stream source. Concrete instances should
* implement [[SourceWithKey]] or [[SimpleSource]], otherwise a custom [[FlowMaterializer]]
* will have to be used to be able to attach them.
*
* All Sources defined in this package rely upon an ActorBasedFlowMaterializer being
* made available to them in order to use the <code>attach</code> method. Other
* FlowMaterializers can be used but must then implement the functionality of these
* Source nodes themselves (or construct an ActorBasedFlowMaterializer).
*/
trait Source[+In]
/**
* A source that does not need to create a user-accessible object during materialization.
*/
trait SimpleSource[+In] extends Source[In] {
/**
* Attach this source to the given [[org.reactivestreams.Subscriber]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this source belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowSubscriber the Subscriber to produce elements to
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowSubscriber: Subscriber[In] @uncheckedVariance, materializer: ActorBasedFlowMaterializer, flowName: String): Unit
/**
* This method is only used for Sources that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] @uncheckedVariance =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Source can create a Publisher instead of being
* attached to a Subscriber. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
}
/**
* A source that will create an object during materialization that the user will need
* to retrieve in order to access aspects of this source (could be a Subscriber, a
* Future/Promise, etc.).
*/
trait SourceWithKey[+In, T] extends Source[In] {
/**
* Attach this source to the given [[org.reactivestreams.Subscriber]]. Using the given
* [[FlowMaterializer]] is completely optional, especially if this source belongs to
* a different Reactive Streams implementation. It is the responsibility of the
* caller to provide a suitable FlowMaterializer that can be used for running
* Flows if necessary.
*
* @param flowSubscriber the Subscriber to produce elements to
* @param materializer a FlowMaterializer that may be used for creating flows
* @param flowName the name of the current flow, which should be used in log statements or error messages
*/
def attach(flowSubscriber: Subscriber[In] @uncheckedVariance, materializer: ActorBasedFlowMaterializer, flowName: String): T
/**
* This method is only used for Sources that return true from [[#isActive]], which then must
* implement it.
*/
def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Publisher[In] @uncheckedVariance, T) =
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
/**
* This method indicates whether this Source can create a Publisher instead of being
* attached to a Subscriber. This is only used if the Flow does not contain any
* operations.
*/
def isActive: Boolean = false
// these are unique keys, case class equality would break them
final override def equals(other: Any): Boolean = super.equals(other)
final override def hashCode: Int = super.hashCode
}
/**
* Holds a `Subscriber` representing the input side of the flow.
* The `Subscriber` can later be connected to an upstream `Publisher`.
*/
final case class SubscriberSource[In]() extends SourceWithKey[In, Subscriber[In]] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[In] =
flowSubscriber
def subscriber(m: MaterializedSource): Subscriber[In] = m.getSourceFor(this)
}
/**
* Construct a transformation starting with given publisher. The transformation steps
* are executed by a series of [[org.reactivestreams.Processor]] instances
* that mediate the flow of elements downstream and the propagation of
* back-pressure upstream.
*/
final case class PublisherSource[In](p: Publisher[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
p.subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] = p
}
/**
* Start a new `Flow` from the given Iterator. The produced stream of elements
* will continue until the iterator runs empty or fails during evaluation of
* the `next()` method. Elements are pulled out of the iterator
* in accordance with the demand coming from the downstream transformation
* steps.
*/
final case class IteratorSource[In](iterator: Iterator[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
if (iterator.isEmpty) EmptyPublisher[In]
else ActorPublisher[In](materializer.actorOf(IteratorPublisher.props(iterator, materializer.settings),
name = s"$flowName-0-iterator"))
}
/**
* Starts a new `Flow` from the given `Iterable`. This is like starting from an
* Iterator, but every Subscriber directly attached to the Publisher of this
* stream will see an individual flow of elements (always starting from the
* beginning) regardless of when they subscribed.
*/
final case class IterableSource[In](iterable: immutable.Iterable[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
if (iterable.isEmpty) EmptyPublisher[In]
else ActorPublisher[In](materializer.actorOf(IterablePublisher.props(iterable, materializer.settings),
name = s"$flowName-0-iterable"), Some(iterable))
}
/**
* Define the sequence of elements to be produced by the given closure.
* The stream ends normally when evaluation of the closure returns a `None`.
* The stream ends exceptionally when an exception is thrown from the closure.
*/
final case class ThunkSource[In](f: () Option[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
ActorPublisher[In](materializer.actorOf(SimpleCallbackPublisher.props(materializer.settings, f),
name = s"$flowName-0-thunk"))
}
/**
* Start a new `Flow` from the given `Future`. The stream will consist of
* one element when the `Future` is completed with a successful value, which
* may happen before or after materializing the `Flow`.
* The stream terminates with an error if the `Future` is completed with a failure.
*/
final case class FutureSource[In](future: Future[In]) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
future.value match {
case Some(Success(element))
ActorPublisher[In](materializer.actorOf(IterablePublisher.props(List(element), materializer.settings),
name = s"$flowName-0-future"), Some(future))
case Some(Failure(t))
ErrorPublisher(t).asInstanceOf[Publisher[In]]
case None
ActorPublisher[In](materializer.actorOf(FuturePublisher.props(future, materializer.settings),
name = s"$flowName-0-future"), Some(future))
}
}
/**
* Elements are produced from the tick closure periodically with the specified interval.
* The tick element will be delivered to downstream consumers that has requested any elements.
* If a consumer has not requested any elements at the point in time when the tick
* element is produced it will not receive that tick element later. It will
* receive new tick elements as soon as it has requested more elements.
*/
final case class TickSource[In](initialDelay: FiniteDuration, interval: FiniteDuration, tick: () In) extends SimpleSource[In] {
override def attach(flowSubscriber: Subscriber[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
create(materializer, flowName).subscribe(flowSubscriber)
override def isActive: Boolean = true
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] =
ActorPublisher[In](materializer.actorOf(TickPublisher.props(initialDelay, interval, tick, materializer.settings),
name = s"$flowName-0-tick"))
}

View file

@ -1,5 +1,5 @@
/**
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream

View file

@ -1,5 +1,5 @@
/**
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream

View file

@ -1,5 +1,5 @@
/**
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream

View file

@ -0,0 +1,55 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import akka.stream.testkit.{ AkkaSpec, StreamTestKit }
import scala.concurrent.forkjoin.ThreadLocalRandom.{ current random }
import scala.util.control.NoStackTrace
class FlowForeachSpec extends AkkaSpec {
implicit val mat = FlowMaterializer()
import system.dispatcher
"A Foreach" must {
"call the procedure for each element" in {
val foreachSink = ForeachSink[Int](testActor ! _)
val mf = FlowFrom(1 to 3).withSink(foreachSink).run()
foreachSink.future(mf).onSuccess {
case _ testActor ! "done"
}
expectMsg(1)
expectMsg(2)
expectMsg(3)
expectMsg("done")
}
"complete the future for an empty stream" in {
val foreachSink = ForeachSink[Int](testActor ! _)
val mf = FlowFrom(Nil).withSink(foreachSink).run()
foreachSink.future(mf).onSuccess {
case _ testActor ! "done"
}
expectMsg("done")
}
"yield the first error" in {
val p = StreamTestKit.PublisherProbe[Int]()
val foreachSink = ForeachSink[Int](testActor ! _)
val mf = FlowFrom(p).withSink(foreachSink).run()
foreachSink.future(mf).onFailure {
case ex testActor ! ex
}
val proc = p.expectSubscription
proc.expectRequest()
val ex = new RuntimeException("ex") with NoStackTrace
proc.sendError(ex)
expectMsg(ex)
}
}
}

View file

@ -0,0 +1,119 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import akka.stream.testkit.{ AkkaSpec, StreamTestKit }
import scala.concurrent.{ Future, Promise }
import scala.concurrent.duration._
import akka.stream.MaterializerSettings
import scala.util.control.NoStackTrace
class FlowFromFutureSpec extends AkkaSpec {
val settings = MaterializerSettings(system)
implicit val materializer = FlowMaterializer(settings)
"A Flow based on a Future" must {
"produce one element from already successful Future" in {
val p = FlowFrom(Future.successful(1)).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
c.expectNoMsg(100.millis)
sub.request(1)
c.expectNext(1)
c.expectComplete()
}
"produce error from already failed Future" in {
val ex = new RuntimeException("test") with NoStackTrace
val p = FlowFrom(Future.failed[Int](ex)).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
c.expectError(ex)
}
"produce one element when Future is completed" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
sub.request(1)
c.expectNoMsg(100.millis)
promise.success(1)
c.expectNext(1)
c.expectComplete()
c.expectNoMsg(100.millis)
}
"produce one element when Future is completed but not before request" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
promise.success(1)
c.expectNoMsg(200.millis)
sub.request(1)
c.expectNext(1)
c.expectComplete()
}
"produce elements with multiple subscribers" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
p.subscribe(c2)
val sub1 = c1.expectSubscription()
val sub2 = c2.expectSubscription()
sub1.request(1)
promise.success(1)
sub2.request(2)
c1.expectNext(1)
c2.expectNext(1)
c1.expectComplete()
c2.expectComplete()
}
"produce elements to later subscriber" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val keepAlive = StreamTestKit.SubscriberProbe[Int]()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(keepAlive)
p.subscribe(c1)
val sub1 = c1.expectSubscription()
sub1.request(1)
promise.success(1)
c1.expectNext(1)
c1.expectComplete()
p.subscribe(c2)
val sub2 = c2.expectSubscription()
sub2.request(1)
c2.expectNext(1)
c2.expectComplete()
}
"allow cancel before receiving element" in {
val promise = Promise[Int]()
val p = FlowFrom(promise.future).toPublisher()
val keepAlive = StreamTestKit.SubscriberProbe[Int]()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(keepAlive)
p.subscribe(c)
val sub = c.expectSubscription()
sub.request(1)
sub.cancel()
c.expectNoMsg(500.millis)
promise.success(1)
c.expectNoMsg(200.millis)
}
}
}

View file

@ -0,0 +1,154 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import akka.stream.testkit.{ AkkaSpec, StreamTestKit }
import akka.stream.testkit.StreamTestKit.{ OnComplete, OnError, OnNext }
import scala.concurrent.duration._
import akka.stream.MaterializerSettings
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class FlowIterableSpec extends AkkaSpec {
val settings = MaterializerSettings(system)
.withInputBuffer(initialSize = 2, maxSize = 512)
implicit val materializer = FlowMaterializer(settings)
"A Flow based on an iterable" must {
"produce elements" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
sub.request(1)
c.expectNext(1)
c.expectNoMsg(100.millis)
sub.request(2)
c.expectNext(2)
c.expectNext(3)
c.expectComplete()
}
"complete empty" in {
val p = FlowFrom(List.empty[Int]).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
c.expectComplete()
c.expectNoMsg(100.millis)
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c2)
c2.expectComplete()
}
"produce elements with multiple subscribers" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
p.subscribe(c2)
val sub1 = c1.expectSubscription()
val sub2 = c2.expectSubscription()
sub1.request(1)
sub2.request(2)
c1.expectNext(1)
c2.expectNext(1)
c2.expectNext(2)
c1.expectNoMsg(100.millis)
c2.expectNoMsg(100.millis)
sub1.request(2)
sub2.request(2)
c1.expectNext(2)
c1.expectNext(3)
c2.expectNext(3)
c1.expectComplete()
c2.expectComplete()
}
"produce elements to later subscriber" in {
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
val sub1 = c1.expectSubscription()
sub1.request(1)
c1.expectNext(1)
c1.expectNoMsg(100.millis)
p.subscribe(c2)
val sub2 = c2.expectSubscription()
sub2.request(2)
// starting from first element, new iterator per subscriber
c2.expectNext(1)
c2.expectNext(2)
c2.expectNoMsg(100.millis)
sub2.request(1)
c2.expectNext(3)
c2.expectComplete()
sub1.request(2)
c1.expectNext(2)
c1.expectNext(3)
c1.expectComplete()
}
"produce elements with one transformation step" in {
val p = FlowFrom(List(1, 2, 3)).map(_ * 2).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
sub.request(10)
c.expectNext(2)
c.expectNext(4)
c.expectNext(6)
c.expectComplete()
}
"produce elements with two transformation steps" ignore {
// val p = FlowFrom(List(1, 2, 3, 4)).filter(_ % 2 == 0).map(_ * 2).toPublisher()
// val c = StreamTestKit.SubscriberProbe[Int]()
// p.subscribe(c)
// val sub = c.expectSubscription()
// sub.request(10)
// c.expectNext(4)
// c.expectNext(8)
// c.expectComplete()
}
"allow cancel before receiving all elements" in {
val count = 100000
val p = FlowFrom(1 to count).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
sub.request(count)
c.expectNext(1)
sub.cancel()
val got = c.probe.receiveWhile(3.seconds) {
case _: OnNext[_]
case OnComplete fail("Cancel expected before OnComplete")
case OnError(e) fail(e)
}
got.size should be < (count - 1)
}
"have value equality of publisher" in {
val p1 = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(List(1, 2, 3)).toPublisher()
p1 should be(p2)
p2 should be(p1)
val p3 = FlowFrom(List(1, 2, 3, 4)).toPublisher()
p1 should not be (p3)
p3 should not be (p1)
val p4 = FlowFrom(Vector.empty[String]).toPublisher()
val p5 = FlowFrom(Set.empty[String]).toPublisher()
p1 should not be (p4)
p4 should be(p5)
p5 should be(p4)
val p6 = FlowFrom(List(1, 2, 3).iterator).toPublisher()
p1 should not be (p6)
p6 should not be (p1)
}
}
}

View file

@ -0,0 +1,139 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import scala.concurrent.duration._
import akka.stream.testkit.StreamTestKit
import akka.stream.testkit.AkkaSpec
import akka.stream.testkit.StreamTestKit.OnNext
import akka.stream.testkit.StreamTestKit.OnComplete
import akka.stream.testkit.StreamTestKit.OnError
import akka.stream.MaterializerSettings
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class FlowIteratorSpec extends AkkaSpec {
val settings = MaterializerSettings(system)
.withInputBuffer(initialSize = 2, maxSize = 2)
.withFanOutBuffer(initialSize = 4, maxSize = 4)
implicit val materializer = FlowMaterializer(settings)
"A Flow based on an iterator" must {
"produce elements" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
sub.request(1)
c.expectNext(1)
c.expectNoMsg(100.millis)
sub.request(3)
c.expectNext(2)
c.expectNext(3)
c.expectComplete()
}
"complete empty" in {
val p = FlowFrom(List.empty[Int].iterator).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
c.expectComplete()
c.expectNoMsg(100.millis)
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c2)
c2.expectComplete()
}
"produce elements with multiple subscribers" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
p.subscribe(c2)
val sub1 = c1.expectSubscription()
val sub2 = c2.expectSubscription()
sub1.request(1)
sub2.request(2)
c1.expectNext(1)
c2.expectNext(1)
c2.expectNext(2)
c1.expectNoMsg(100.millis)
c2.expectNoMsg(100.millis)
sub1.request(2)
sub2.request(2)
c1.expectNext(2)
c1.expectNext(3)
c2.expectNext(3)
c1.expectComplete()
c2.expectComplete()
}
"produce elements to later subscriber" in {
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
val c2 = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c1)
val sub1 = c1.expectSubscription()
sub1.request(1)
c1.expectNext(1)
c1.expectNoMsg(100.millis)
p.subscribe(c2)
val sub2 = c2.expectSubscription()
sub2.request(3)
// element 1 is already gone
c2.expectNext(2)
c2.expectNext(3)
c2.expectComplete()
sub1.request(3)
c1.expectNext(2)
c1.expectNext(3)
c1.expectComplete()
}
"produce elements with one transformation step" in {
val p = FlowFrom(List(1, 2, 3).iterator).map(_ * 2).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
sub.request(10)
c.expectNext(2)
c.expectNext(4)
c.expectNext(6)
c.expectComplete()
}
// FIXME enable test when filter is implemented
"produce elements with two transformation steps" ignore {
// val p = FlowFrom(List(1, 2, 3, 4).iterator).filter(_ % 2 == 0).map(_ * 2).toPublisher()
// val c = StreamTestKit.SubscriberProbe[Int]()
// p.subscribe(c)
// val sub = c.expectSubscription()
// sub.request(10)
// c.expectNext(4)
// c.expectNext(8)
// c.expectComplete()
}
"allow cancel before receiving all elements" in {
val count = 100000
val p = FlowFrom((1 to count).iterator).toPublisher()
val c = StreamTestKit.SubscriberProbe[Int]()
p.subscribe(c)
val sub = c.expectSubscription()
sub.request(count)
c.expectNext(1)
sub.cancel()
val got = c.probe.receiveWhile(3.seconds) {
case _: OnNext[_]
case OnComplete fail("Cancel expected before OnComplete")
case OnError(e) fail(e)
}
got.size should be < (count - 1)
}
}
}

View file

@ -0,0 +1,83 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import akka.stream.testkit.{ AkkaSpec, ScriptedTest, StreamTestKit }
import akka.testkit.TestProbe
import scala.concurrent.duration._
import scala.concurrent.forkjoin.ThreadLocalRandom.{ current random }
import scala.util.control.NoStackTrace
import scala.util.{ Failure, Success }
import akka.stream.MaterializerSettings
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class FlowOnCompleteSpec extends AkkaSpec with ScriptedTest {
val settings = MaterializerSettings(system)
.withInputBuffer(initialSize = 2, maxSize = 16)
.withFanOutBuffer(initialSize = 1, maxSize = 16)
implicit val materializer = FlowMaterializer(settings)
"A Flow with onComplete" must {
"invoke callback on normal completion" in {
val onCompleteProbe = TestProbe()
val p = StreamTestKit.PublisherProbe[Int]()
FlowFrom(p).withSink(OnCompleteSink(onCompleteProbe.ref ! _)).run()
val proc = p.expectSubscription
proc.expectRequest()
proc.sendNext(42)
onCompleteProbe.expectNoMsg(100.millis)
proc.sendComplete()
onCompleteProbe.expectMsg(Success(()))
}
"yield the first error" in {
val onCompleteProbe = TestProbe()
val p = StreamTestKit.PublisherProbe[Int]()
FlowFrom(p).withSink(OnCompleteSink(onCompleteProbe.ref ! _)).run()
val proc = p.expectSubscription
proc.expectRequest()
val ex = new RuntimeException("ex") with NoStackTrace
proc.sendError(ex)
onCompleteProbe.expectMsg(Failure(ex))
onCompleteProbe.expectNoMsg(100.millis)
}
"invoke callback for an empty stream" in {
val onCompleteProbe = TestProbe()
val p = StreamTestKit.PublisherProbe[Int]()
FlowFrom(p).withSink(OnCompleteSink(onCompleteProbe.ref ! _)).run()
val proc = p.expectSubscription
proc.expectRequest()
proc.sendComplete()
onCompleteProbe.expectMsg(Success(()))
onCompleteProbe.expectNoMsg(100.millis)
}
"invoke callback after transform and foreach steps " in {
val onCompleteProbe = TestProbe()
val p = StreamTestKit.PublisherProbe[Int]()
import system.dispatcher // for the Future.onComplete
val foreachSink = ForeachSink[Int] {
x onCompleteProbe.ref ! ("foreach-" + x)
}
val mf = FlowFrom(p).map { x
onCompleteProbe.ref ! ("map-" + x)
x
}.withSink(foreachSink).run()
foreachSink.future(mf) onComplete { onCompleteProbe.ref ! _ }
val proc = p.expectSubscription
proc.expectRequest()
proc.sendNext(42)
proc.sendComplete()
onCompleteProbe.expectMsg("map-42")
onCompleteProbe.expectMsg("foreach-42")
onCompleteProbe.expectMsg(Success(()))
}
}
}

View file

@ -0,0 +1,32 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import akka.stream.testkit.AkkaSpec
import akka.stream.testkit.StreamTestKit
import akka.stream.MaterializerSettings
class FlowPublishToSubscriberSpec extends AkkaSpec {
val settings = MaterializerSettings(system)
.withInputBuffer(initialSize = 2, maxSize = 16)
.withFanOutBuffer(initialSize = 1, maxSize = 16)
implicit val materializer = FlowMaterializer(settings)
"A Flow with SubscriberSink" must {
"publish elements to the subscriber" in {
val c = StreamTestKit.SubscriberProbe[Int]()
FlowFrom(List(1, 2, 3)).withSink(SubscriberSink(c)).run()
val s = c.expectSubscription()
s.request(3)
c.expectNext(1)
c.expectNext(2)
c.expectNext(3)
c.expectComplete()
}
}
}

View file

@ -11,30 +11,30 @@ import scala.concurrent.Future
class FlowSpec extends AkkaSpec {
val intSeq = IterableIn(Seq(1, 2, 3))
val strSeq = IterableIn(Seq("a", "b", "c"))
val intSeq = IterableSource(Seq(1, 2, 3))
val strSeq = IterableSource(Seq("a", "b", "c"))
import scala.concurrent.ExecutionContext.Implicits.global
val intFut = FutureIn(Future { 3 })
val intFut = FutureSource(Future { 3 })
implicit val materializer = FlowMaterializer(MaterializerSettings(system))
"ProcessorFlow" should {
"go through all states" in {
val f: ProcessorFlow[Int, Int] = FlowFrom[Int]
.withInput(intSeq)
.withOutput(PublisherOut())
.withoutInput
.withoutOutput
.withSource(intSeq)
.withSink(PublisherSink[Int])
.withoutSource
.withoutSink
}
"should not run" in {
val open: ProcessorFlow[Int, Int] = FlowFrom[Int]
"open.run()" shouldNot compile
}
"accept IterableIn" in {
val f: PublisherFlow[Int, Int] = FlowFrom[Int].withInput(intSeq)
"accept IterableSource" in {
val f: FlowWithSource[Int, Int] = FlowFrom[Int].withSource(intSeq)
}
"accept FutureIn" in {
val f: PublisherFlow[Int, Int] = FlowFrom[Int].withInput(intFut)
"accept FutureSource" in {
val f: FlowWithSource[Int, Int] = FlowFrom[Int].withSource(intFut)
}
"append ProcessorFlow" in {
val open1: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
@ -42,14 +42,14 @@ class FlowSpec extends AkkaSpec {
val open3: ProcessorFlow[Int, Int] = open1.append(open2)
"open3.run()" shouldNot compile
val closedInput: PublisherFlow[Int, Int] = open3.withInput(intSeq)
"closedInput.run()" shouldNot compile
val closedSource: FlowWithSource[Int, Int] = open3.withSource(intSeq)
"closedSource.run()" shouldNot compile
val closedOutput: SubscriberFlow[Int, Int] = open3.withOutput(PublisherOut())
"closedOutput.run()" shouldNot compile
val closedSink: FlowWithSink[Int, Int] = open3.withSink(PublisherSink[Int])
"closedSink.run()" shouldNot compile
closedInput.withOutput(PublisherOut()).run()
closedOutput.withInput(intSeq).run()
closedSource.withSink(PublisherSink[Int]).run()
closedSink.withSource(intSeq).run()
}
"prepend ProcessorFlow" in {
val open1: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
@ -57,89 +57,89 @@ class FlowSpec extends AkkaSpec {
val open3: ProcessorFlow[String, String] = open1.prepend(open2)
"open3.run()" shouldNot compile
val closedInput: PublisherFlow[String, String] = open3.withInput(strSeq)
"closedInput.run()" shouldNot compile
val closedSource: FlowWithSource[String, String] = open3.withSource(strSeq)
"closedSource.run()" shouldNot compile
val closedOutput: SubscriberFlow[String, String] = open3.withOutput(PublisherOut())
"closedOutput.run()" shouldNot compile
val closedSink: FlowWithSink[String, String] = open3.withSink(PublisherSink[String])
"closedSink.run()" shouldNot compile
closedInput.withOutput(PublisherOut()).run
closedOutput.withInput(strSeq).run
closedSource.withSink(PublisherSink[String]).run
closedSink.withSource(strSeq).run
}
"append SubscriberFlow" in {
"append FlowWithSink" in {
val open: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
val closedOutput: SubscriberFlow[String, Int] = FlowFrom[String].map(_.hashCode).withOutput(PublisherOut())
val appended: SubscriberFlow[Int, Int] = open.append(closedOutput)
val closedSink: FlowWithSink[String, Int] = FlowFrom[String].map(_.hashCode).withSink(PublisherSink[Int])
val appended: FlowWithSink[Int, Int] = open.append(closedSink)
"appended.run()" shouldNot compile
"appended.toFuture" shouldNot compile
appended.withInput(intSeq).run
appended.withSource(intSeq).run
}
"prepend PublisherFlow" in {
"prepend FlowWithSource" in {
val open: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
val closedInput: PublisherFlow[String, Int] = FlowFrom[String].map(_.hashCode).withInput(strSeq)
val prepended: PublisherFlow[String, String] = open.prepend(closedInput)
val closedSource: FlowWithSource[String, Int] = FlowFrom[String].map(_.hashCode).withSource(strSeq)
val prepended: FlowWithSource[String, String] = open.prepend(closedSource)
"prepended.run()" shouldNot compile
"prepended.withInput(strSeq)" shouldNot compile
prepended.withOutput(PublisherOut()).run
"prepended.withSource(strSeq)" shouldNot compile
prepended.withSink(PublisherSink[String]).run
}
}
"SubscriberFlow" should {
val openInput: SubscriberFlow[Int, String] =
FlowFrom[Int].map(_.toString).withOutput(PublisherOut())
"accept Input" in {
openInput.withInput(intSeq)
"FlowWithSink" should {
val openSource: FlowWithSink[Int, String] =
FlowFrom[Int].map(_.toString).withSink(PublisherSink[String])
"accept Source" in {
openSource.withSource(intSeq)
}
"drop Output" in {
openInput.withoutOutput
"drop Sink" in {
openSource.withoutSink
}
"not drop Input" in {
"openInput.withoutInput" shouldNot compile
"not drop Source" in {
"openSource.withoutSource" shouldNot compile
}
"not accept Output" in {
"openInput.ToFuture" shouldNot compile
"not accept Sink" in {
"openSource.ToFuture" shouldNot compile
}
"not run()" in {
"openInput.run()" shouldNot compile
"openSource.run()" shouldNot compile
}
}
"PublisherFlow" should {
val openOutput: PublisherFlow[Int, String] =
"FlowWithSource" should {
val openSink: FlowWithSource[Int, String] =
FlowFrom(Seq(1, 2, 3)).map(_.toString)
"accept Output" in {
openOutput.withOutput(PublisherOut())
"accept Sink" in {
openSink.withSink(PublisherSink[String])
}
"drop Input" in {
openOutput.withoutInput
"drop Source" in {
openSink.withoutSource
}
"not drop Output" in {
"openOutput.withoutOutput" shouldNot compile
"not drop Sink" in {
"openSink.withoutSink" shouldNot compile
}
"not accept Input" in {
"openOutput.withInput(intSeq)" shouldNot compile
"not accept Source" in {
"openSink.withSource(intSeq)" shouldNot compile
}
"not run()" in {
"openOutput.run()" shouldNot compile
"openSink.run()" shouldNot compile
}
}
"RunnableFlow" should {
val closed: RunnableFlow[Int, String] =
FlowFrom(Seq(1, 2, 3)).map(_.toString).withOutput(PublisherOut())
FlowFrom(Seq(1, 2, 3)).map(_.toString).withSink(PublisherSink[String])
"run" in {
closed.run()
}
"drop Input" in {
closed.withoutInput
"drop Source" in {
closed.withoutSource
}
"drop Output" in {
closed.withoutOutput
"drop Sink" in {
closed.withoutSink
}
"not accept Input" in {
"closed.withInput(intSeq)" shouldNot compile
"not accept Source" in {
"closed.withSource(intSeq)" shouldNot compile
}
"not accept Output" in {
"not accept Sink" in {
"closed.ToFuture" shouldNot compile
}
}

View file

@ -0,0 +1,77 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import akka.stream.testkit.{ AkkaSpec, ScriptedTest, StreamTestKit }
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.concurrent.forkjoin.ThreadLocalRandom.{ current random }
import scala.util.Failure
import akka.stream.MaterializerSettings
class FlowToFutureSpec extends AkkaSpec with ScriptedTest {
val settings = MaterializerSettings(system)
.withInputBuffer(initialSize = 2, maxSize = 16)
.withFanOutBuffer(initialSize = 1, maxSize = 16)
implicit val materializer = FlowMaterializer(settings)
"A Flow with toFuture" must {
"yield the first value" in {
val p = StreamTestKit.PublisherProbe[Int]()
val f = FutureSink[Int]
val m = FlowFrom(p).withSink(f).run()
val proc = p.expectSubscription
proc.expectRequest()
proc.sendNext(42)
Await.result(f.future(m), 100.millis) should be(42)
proc.expectCancellation()
}
"yield the first value when actively constructing" in {
val p = StreamTestKit.PublisherProbe[Int]()
val f = FutureSink[Int]
val s = SubscriberSource[Int]
val m = FlowFrom[Int].withSource(s).withSink(f).run()
p.subscribe(s.subscriber(m))
val proc = p.expectSubscription
proc.expectRequest()
proc.sendNext(42)
Await.result(f.future(m), 100.millis) should be(42)
proc.expectCancellation()
}
"yield the first error" in {
val p = StreamTestKit.PublisherProbe[Int]()
val f = FutureSink[Int]
val m = FlowFrom(p).withSink(f).run()
val proc = p.expectSubscription
proc.expectRequest()
val ex = new RuntimeException("ex")
proc.sendError(ex)
val future = f.future(m)
Await.ready(future, 100.millis)
future.value.get should be(Failure(ex))
}
"yield NoSuchElementExcption for empty stream" in {
val p = StreamTestKit.PublisherProbe[Int]()
val f = FutureSink[Int]
val m = FlowFrom(p).withSink(f).run()
val proc = p.expectSubscription
proc.expectRequest()
proc.sendComplete()
val future = f.future(m)
Await.ready(future, 100.millis)
future.value.get match {
case Failure(e: NoSuchElementException) e.getMessage() should be("empty stream")
case x fail("expected NoSuchElementException, got " + x)
}
}
}
}

View file

@ -23,7 +23,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
"A Flow with transform operations" must {
"produce one-to-one transformation as expected" in {
val p = FlowFrom(List(1, 2, 3)).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[Int, Int] {
var tot = 0
@ -32,7 +32,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
List(tot)
}
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
p2.subscribe(subscriber)
val subscription = subscriber.expectSubscription()
@ -46,7 +46,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"produce one-to-several transformation as expected" in {
val p = FlowFrom(List(1, 2, 3)).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[Int, Int] {
var tot = 0
@ -55,7 +55,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
Vector.fill(elem)(tot)
}
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
p2.subscribe(subscriber)
val subscription = subscriber.expectSubscription()
@ -72,7 +72,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"produce dropping transformation as expected" in {
val p = FlowFrom(List(1, 2, 3, 4)).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List(1, 2, 3, 4)).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[Int, Int] {
var tot = 0
@ -85,7 +85,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
}
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
p2.subscribe(subscriber)
val subscription = subscriber.expectSubscription()
@ -99,7 +99,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"produce multi-step transformation as expected" in {
val p = FlowFrom(List("a", "bc", "def")).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List("a", "bc", "def")).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[String, Int] {
var concat = ""
@ -115,7 +115,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
List(tot)
}
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val c1 = StreamTestKit.SubscriberProbe[Int]()
p2.subscribe(c1)
val sub1 = c1.expectSubscription()
@ -138,7 +138,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"invoke onComplete when done" in {
val p = FlowFrom(List("a")).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List("a")).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[String, String] {
var s = ""
@ -148,7 +148,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
override def onTermination(e: Option[Throwable]) = List(s + "B")
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val c = StreamTestKit.SubscriberProbe[String]()
p2.subscribe(c)
val s = c.expectSubscription()
@ -159,7 +159,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
"invoke cleanup when done" in {
val cleanupProbe = TestProbe()
val p = FlowFrom(List("a")).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List("a")).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[String, String] {
var s = ""
@ -170,7 +170,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
override def onTermination(e: Option[Throwable]) = List(s + "B")
override def cleanup() = cleanupProbe.ref ! s
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val c = StreamTestKit.SubscriberProbe[String]()
p2.subscribe(c)
val s = c.expectSubscription()
@ -182,7 +182,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
"invoke cleanup when done consume" in {
val cleanupProbe = TestProbe()
val p = FlowFrom(List("a")).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List("a")).toPublisher()
FlowFrom(p).
transform("transform", () new Transformer[String, String] {
var s = "x"
@ -192,13 +192,13 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
override def cleanup() = cleanupProbe.ref ! s
}).
withOutput(BlackholeOut()).run()
withSink(BlackholeSink).run()
cleanupProbe.expectMsg("a")
}
"invoke cleanup when done after error" in {
val cleanupProbe = TestProbe()
val p = FlowFrom(List("a", "b", "c")).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List("a", "b", "c")).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[String, String] {
var s = ""
@ -214,7 +214,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
override def onTermination(e: Option[Throwable]) = List(s + "B")
override def cleanup() = cleanupProbe.ref ! s
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val c = StreamTestKit.SubscriberProbe[String]()
p2.subscribe(c)
val s = c.expectSubscription()
@ -236,7 +236,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
override def isComplete = s == "1"
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val proc = p.expectSubscription
val c = StreamTestKit.SubscriberProbe[Int]()
p2.subscribe(c)
@ -263,7 +263,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
override def onTermination(e: Option[Throwable]) = List(s.length + 10)
override def cleanup() = cleanupProbe.ref ! s
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val proc = p.expectSubscription
val c = StreamTestKit.SubscriberProbe[Int]()
p2.subscribe(c)
@ -279,7 +279,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"report error when exception is thrown" in {
val p = FlowFrom(List(1, 2, 3)).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[Int, Int] {
override def onNext(elem: Int) = {
@ -290,7 +290,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
}
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
p2.subscribe(subscriber)
val subscription = subscriber.expectSubscription()
@ -304,12 +304,12 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"support cancel as expected" in {
val p = FlowFrom(List(1, 2, 3)).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List(1, 2, 3)).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[Int, Int] {
override def onNext(elem: Int) = List(elem, elem)
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
p2.subscribe(subscriber)
val subscription = subscriber.expectSubscription()
@ -323,13 +323,13 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
}
"support producing elements from empty inputs" in {
val p = FlowFrom(List.empty[Int]).withOutput(PublisherOut()).toPublisher()
val p = FlowFrom(List.empty[Int]).toPublisher()
val p2 = FlowFrom(p).
transform("transform", () new Transformer[Int, Int] {
override def onNext(elem: Int) = Nil
override def onTermination(e: Option[Throwable]) = List(1, 2, 3)
}).
withOutput(PublisherOut()).toPublisher()
toPublisher()
val subscriber = StreamTestKit.SubscriberProbe[Int]()
p2.subscribe(subscriber)
val subscription = subscriber.expectSubscription()
@ -363,7 +363,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
case _ Nil
}
}
}).withOutput(PublisherOut()).produceTo(subscriber)
}).publishTo(subscriber)
val subscription = subscriber.expectSubscription()
subscription.request(10)
@ -383,16 +383,16 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
count += 1
List(count)
}
}).withOutput(PublisherOut())
})
val s1 = StreamTestKit.SubscriberProbe[Int]()
flow.produceTo(s1)
flow.publishTo(s1)
s1.expectSubscription().request(3)
s1.expectNext(1, 2, 3)
s1.expectComplete()
val s2 = StreamTestKit.SubscriberProbe[Int]()
flow.produceTo(s2)
flow.publishTo(s2)
s2.expectSubscription().request(3)
s2.expectNext(1, 2, 3)
s2.expectComplete()

View file

@ -0,0 +1,98 @@
/**
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
*/
package akka.stream.scaladsl2
import scala.concurrent.duration._
import akka.stream.testkit.AkkaSpec
import akka.stream.testkit.StreamTestKit
import scala.util.control.NoStackTrace
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class TickPublisherSpec extends AkkaSpec {
implicit val materializer = FlowMaterializer()
"A Flow based on tick publisher" must {
"produce ticks" in {
val tickGen = Iterator from 1
val c = StreamTestKit.SubscriberProbe[String]()
FlowFrom(1.second, 500.millis, () "tick-" + tickGen.next()).publishTo(c)
val sub = c.expectSubscription()
sub.request(3)
c.expectNoMsg(600.millis)
c.expectNext("tick-1")
c.expectNoMsg(200.millis)
c.expectNext("tick-2")
c.expectNoMsg(200.millis)
c.expectNext("tick-3")
sub.cancel()
c.expectNoMsg(200.millis)
}
"drop ticks when not requested" in {
val tickGen = Iterator from 1
val c = StreamTestKit.SubscriberProbe[String]()
FlowFrom(1.second, 1.second, () "tick-" + tickGen.next()).publishTo(c)
val sub = c.expectSubscription()
sub.request(2)
c.expectNext("tick-1")
c.expectNoMsg(200.millis)
c.expectNext("tick-2")
c.expectNoMsg(1400.millis)
sub.request(2)
c.expectNext("tick-4")
c.expectNoMsg(200.millis)
c.expectNext("tick-5")
sub.cancel()
c.expectNoMsg(200.millis)
}
"produce ticks with multiple subscribers" in {
val tickGen = Iterator from 1
val p = FlowFrom(1.second, 1.second, () "tick-" + tickGen.next()).toPublisher()
val c1 = StreamTestKit.SubscriberProbe[String]()
val c2 = StreamTestKit.SubscriberProbe[String]()
p.subscribe(c1)
p.subscribe(c2)
val sub1 = c1.expectSubscription()
val sub2 = c2.expectSubscription()
sub1.request(1)
sub2.request(2)
c1.expectNext("tick-1")
c2.expectNext("tick-1")
c2.expectNoMsg(200.millis)
c2.expectNext("tick-2")
c1.expectNoMsg(200.millis)
sub1.request(2)
sub2.request(2)
c1.expectNext("tick-3")
c2.expectNext("tick-3")
sub1.cancel()
sub2.cancel()
}
"signal onError when tick closure throws" in {
val c = StreamTestKit.SubscriberProbe[String]()
FlowFrom(1.second, 1.second, () throw new RuntimeException("tick err") with NoStackTrace).publishTo(c)
val sub = c.expectSubscription()
sub.request(3)
c.expectError.getMessage should be("tick err")
}
// FIXME enable this test again when zip is back
"be usable with zip for a simple form of rate limiting" ignore {
// val c = StreamTestKit.SubscriberProbe[Int]()
// val rate = FlowFrom(1.second, 1.second, () "tick").toPublisher()
// FlowFrom(1 to 100).zip(rate).map { case (n, _) n }.publishTo(c)
// val sub = c.expectSubscription()
// sub.request(1000)
// c.expectNext(1)
// c.expectNoMsg(200.millis)
// c.expectNext(2)
// c.expectNoMsg(200.millis)
// sub.cancel()
}
}
}

View file

@ -25,7 +25,7 @@ object StreamTestKit {
*/
def errorPublisher[T](cause: Throwable): Publisher[T] = ErrorPublisher(cause: Throwable).asInstanceOf[Publisher[T]]
def emptyPublisher[T](): Publisher[T] = EmptyPublisher.asInstanceOf[Publisher[T]]
def emptyPublisher[T](): Publisher[T] = EmptyPublisher[T]
/**
* Subscribes the subscriber and signals error after the first request.