Merge pull request #16007 from akka/wip-15977-changin-all-the-small-dsl-things-ban
+str #15977 Rename and change types in the new DSL
This commit is contained in:
commit
a8013f20b2
63 changed files with 1858 additions and 1675 deletions
|
|
@ -40,7 +40,7 @@ private object RenderSupport {
|
|||
val messageBytes =
|
||||
if (!skipEntity) Flow(messageStart).concat(entityBytes).toPublisher()
|
||||
else {
|
||||
// FIXME: This should be fixed by a CancelledSink once #15903 is done. Currently this is needed for the tests
|
||||
// FIXME: This should be fixed by a CancelledDrain once #15903 is done. Currently this is needed for the tests
|
||||
entityBytes.subscribe(cancelledSusbcriber)
|
||||
messageStart
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ package object util {
|
|||
})
|
||||
}
|
||||
|
||||
// FIXME: This should be fixed by a CancelledSink once #15903 is done. Currently this is needed for the tests
|
||||
// FIXME: This should be fixed by a CancelledDrain once #15903 is done. Currently this is needed for the tests
|
||||
private[http] def cancelledSusbcriber[T]: Subscriber[T] = new Subscriber[T] {
|
||||
override def onSubscribe(s: Subscription): Unit = s.cancel()
|
||||
override def onError(t: Throwable): Unit = ()
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class HttpModelIntegrationSpec extends WordSpec with Matchers with BeforeAndAfte
|
|||
entity = HttpEntity.Default(
|
||||
contentType = ContentTypes.`application/json`,
|
||||
contentLength = 5,
|
||||
FlowFrom(List(ByteString("hello"))).toPublisher()))
|
||||
Source(List(ByteString("hello"))).toPublisher()))
|
||||
|
||||
// Our library uses a simple model of headers: a Seq[(String, String)].
|
||||
// The body is represented as an Array[Byte]. To get the headers in
|
||||
|
|
@ -141,7 +141,7 @@ class HttpModelIntegrationSpec extends WordSpec with Matchers with BeforeAndAfte
|
|||
// convert the body into a Publisher[ByteString].
|
||||
|
||||
val byteStringBody = ByteString(byteArrayBody)
|
||||
val publisherBody = FlowFrom(List(byteStringBody)).toPublisher()
|
||||
val publisherBody = Source(List(byteStringBody)).toPublisher()
|
||||
|
||||
// Finally we can create our HttpResponse.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,23 +6,21 @@ import akka.stream.scaladsl2._
|
|||
import akka.stream.testkit.StreamTestKit
|
||||
import org.reactivestreams.Publisher
|
||||
|
||||
import scala.annotation.unchecked.uncheckedVariance
|
||||
|
||||
class ChainSetup[In, Out](
|
||||
stream: ProcessorFlow[In, In] ⇒ ProcessorFlow[In, Out],
|
||||
stream: Flow[In, In] ⇒ Flow[In, Out],
|
||||
val settings: MaterializerSettings,
|
||||
materializer: FlowMaterializer,
|
||||
toPublisher: (FlowWithSource[In, Out], FlowMaterializer) ⇒ Publisher[Out])(implicit val system: ActorSystem) {
|
||||
toPublisher: (Source[Out], FlowMaterializer) ⇒ Publisher[Out])(implicit val system: ActorSystem) {
|
||||
|
||||
def this(stream: ProcessorFlow[In, In] ⇒ ProcessorFlow[In, Out], settings: MaterializerSettings, toPublisher: (FlowWithSource[In, Out], FlowMaterializer) ⇒ Publisher[Out])(implicit system: ActorSystem) =
|
||||
def this(stream: Flow[In, In] ⇒ Flow[In, Out], settings: MaterializerSettings, toPublisher: (Source[Out], FlowMaterializer) ⇒ Publisher[Out])(implicit system: ActorSystem) =
|
||||
this(stream, settings, FlowMaterializer(settings)(system), toPublisher)(system)
|
||||
|
||||
def this(stream: ProcessorFlow[In, In] ⇒ ProcessorFlow[In, Out], settings: MaterializerSettings, materializerCreator: (MaterializerSettings, ActorRefFactory) ⇒ FlowMaterializer, toPublisher: (FlowWithSource[In, Out], FlowMaterializer) ⇒ Publisher[Out])(implicit system: ActorSystem) =
|
||||
def this(stream: Flow[In, In] ⇒ Flow[In, Out], settings: MaterializerSettings, materializerCreator: (MaterializerSettings, ActorRefFactory) ⇒ FlowMaterializer, toPublisher: (Source[Out], FlowMaterializer) ⇒ Publisher[Out])(implicit system: ActorSystem) =
|
||||
this(stream, settings, materializerCreator(settings, system), toPublisher)(system)
|
||||
|
||||
val upstream = StreamTestKit.PublisherProbe[In]()
|
||||
val downstream = StreamTestKit.SubscriberProbe[Out]()
|
||||
private val s = stream(FlowFrom[In]).withSource(PublisherSource(upstream))
|
||||
private val s = Source(upstream).connect(stream(Flow[In]))
|
||||
val publisher = toPublisher(s, materializer)
|
||||
val upstreamSubscription = upstream.expectSubscription()
|
||||
publisher.subscribe(downstream)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ package akka.stream.testkit2
|
|||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.stream.MaterializerSettings
|
||||
import akka.stream.scaladsl2.{ FlowMaterializer, FlowWithSource, ProcessorFlow }
|
||||
import akka.stream.scaladsl2.{ FlowMaterializer, Source, Flow }
|
||||
import akka.stream.testkit.StreamTestKit._
|
||||
import org.reactivestreams.Publisher
|
||||
import org.scalatest.Matchers
|
||||
|
|
@ -18,7 +18,7 @@ trait ScriptedTest extends Matchers {
|
|||
|
||||
class ScriptException(msg: String) extends RuntimeException(msg)
|
||||
|
||||
def toPublisher[In, Out]: (FlowWithSource[In, Out], FlowMaterializer) ⇒ Publisher[Out] =
|
||||
def toPublisher[In, Out]: (Source[Out], FlowMaterializer) ⇒ Publisher[Out] =
|
||||
(f, m) ⇒ f.toPublisher()(m)
|
||||
|
||||
object Script {
|
||||
|
|
@ -81,7 +81,7 @@ trait ScriptedTest extends Matchers {
|
|||
}
|
||||
|
||||
class ScriptRunner[In, Out](
|
||||
op: ProcessorFlow[In, In] ⇒ ProcessorFlow[In, Out],
|
||||
op: Flow[In, In] ⇒ Flow[In, Out],
|
||||
settings: MaterializerSettings,
|
||||
script: Script[In, Out],
|
||||
maximumOverrun: Int,
|
||||
|
|
@ -191,7 +191,7 @@ trait ScriptedTest extends Matchers {
|
|||
}
|
||||
|
||||
def runScript[In, Out](script: Script[In, Out], settings: MaterializerSettings, maximumOverrun: Int = 3, maximumRequest: Int = 3, maximumBuffer: Int = 3)(
|
||||
op: ProcessorFlow[In, In] ⇒ ProcessorFlow[In, Out])(implicit system: ActorSystem): Unit = {
|
||||
op: Flow[In, In] ⇒ Flow[In, Out])(implicit system: ActorSystem): Unit = {
|
||||
new ScriptRunner(op, settings, script, maximumOverrun, maximumRequest, maximumBuffer).run()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ abstract class TwoStreamsSetup extends AkkaSpec {
|
|||
import FlowGraphImplicits._
|
||||
val left = operationUnderTestLeft()
|
||||
val right = operationUnderTestRight()
|
||||
val x = FlowFrom(p1) ~> left ~> FlowFrom[Outputs] ~> SubscriberSink(subscriber)
|
||||
FlowFrom(p2) ~> right
|
||||
val x = Source(p1) ~> left ~> Flow[Outputs] ~> SubscriberDrain(subscriber)
|
||||
Source(p2) ~> right
|
||||
}.run()
|
||||
|
||||
subscriber
|
||||
|
|
@ -41,7 +41,7 @@ abstract class TwoStreamsSetup extends AkkaSpec {
|
|||
|
||||
def completedPublisher[T]: Publisher[T] = StreamTestKit.emptyPublisher[T]
|
||||
|
||||
def nonemptyPublisher[T](elems: Iterator[T]): Publisher[T] = FlowFrom(elems).toPublisher()
|
||||
def nonemptyPublisher[T](elems: Iterator[T]): Publisher[T] = Source(elems).toPublisher()
|
||||
|
||||
def soonToFailPublisher[T]: Publisher[T] = StreamTestKit.lazyErrorPublisher[T](TestException)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,32 +14,28 @@ class FlowAppendSpec extends AkkaSpec with River {
|
|||
val settings = MaterializerSettings(system)
|
||||
implicit val materializer = FlowMaterializer(settings)
|
||||
|
||||
"ProcessorFlow" should {
|
||||
"append ProcessorFlow" in riverOf[String] { subscriber ⇒
|
||||
FlowFrom[Int]
|
||||
.append(otherFlow)
|
||||
.withSource(IterableSource(elements))
|
||||
.publishTo(subscriber)
|
||||
"Flow" should {
|
||||
"append Flow" in riverOf[String] { subscriber ⇒
|
||||
val flow = Flow[Int].connect(otherFlow)
|
||||
Source(elements).connect(flow).publishTo(subscriber)
|
||||
}
|
||||
|
||||
"append FlowWithSink" in riverOf[String] { subscriber ⇒
|
||||
FlowFrom[Int]
|
||||
.append(otherFlow.withSink(SubscriberSink(subscriber)))
|
||||
.withSource(IterableSource(elements))
|
||||
.run()
|
||||
"append Sink" in riverOf[String] { subscriber ⇒
|
||||
val sink = Flow[Int].connect(otherFlow.connect(SubscriberDrain(subscriber)))
|
||||
Source(elements).connect(sink).run()
|
||||
}
|
||||
}
|
||||
|
||||
"FlowWithSource" should {
|
||||
"append ProcessorFlow" in riverOf[String] { subscriber ⇒
|
||||
FlowFrom(elements)
|
||||
.append(otherFlow)
|
||||
"Source" should {
|
||||
"append Flow" in riverOf[String] { subscriber ⇒
|
||||
Source(elements)
|
||||
.connect(otherFlow)
|
||||
.publishTo(subscriber)
|
||||
}
|
||||
|
||||
"append FlowWithSink" in riverOf[String] { subscriber ⇒
|
||||
FlowFrom(elements)
|
||||
.append(otherFlow.withSink(SubscriberSink(subscriber)))
|
||||
"append Sink" in riverOf[String] { subscriber ⇒
|
||||
Source(elements)
|
||||
.connect(otherFlow.connect(SubscriberDrain(subscriber)))
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
|
@ -49,7 +45,7 @@ class FlowAppendSpec extends AkkaSpec with River {
|
|||
trait River { self: Matchers ⇒
|
||||
|
||||
val elements = (1 to 10)
|
||||
val otherFlow = FlowFrom[Int].map(_.toString)
|
||||
val otherFlow = Flow[Int].map(_.toString)
|
||||
|
||||
def riverOf[T](flowConstructor: Subscriber[T] ⇒ Unit)(implicit system: ActorSystem) = {
|
||||
val subscriber = StreamTestKit.SubscriberProbe[T]()
|
||||
|
|
|
|||
|
|
@ -20,24 +20,24 @@ class FlowBufferSpec extends AkkaSpec {
|
|||
"Buffer" must {
|
||||
|
||||
"pass elements through normally in backpressured mode" in {
|
||||
val futureSink = FutureSink[Seq[Int]]
|
||||
val mf = FlowFrom((1 to 1000).iterator).buffer(100, overflowStrategy = OverflowStrategy.backpressure).grouped(1001).
|
||||
withSink(futureSink).run()
|
||||
val future = futureSink.future(mf)
|
||||
val futureDrain = FutureDrain[Seq[Int]]
|
||||
val mf = Source((1 to 1000).iterator).buffer(100, overflowStrategy = OverflowStrategy.backpressure).grouped(1001).
|
||||
connect(futureDrain).run()
|
||||
val future = futureDrain.future(mf)
|
||||
Await.result(future, 3.seconds) should be(1 to 1000)
|
||||
}
|
||||
|
||||
"pass elements through normally in backpressured mode with buffer size one" in {
|
||||
val futureSink = FutureSink[Seq[Int]]
|
||||
val mf = FlowFrom((1 to 1000).iterator).buffer(1, overflowStrategy = OverflowStrategy.backpressure).grouped(1001).
|
||||
withSink(futureSink).run()
|
||||
val future = futureSink.future(mf)
|
||||
val futureDrain = FutureDrain[Seq[Int]]
|
||||
val mf = Source((1 to 1000).iterator).buffer(1, overflowStrategy = OverflowStrategy.backpressure).grouped(1001).
|
||||
connect(futureDrain).run()
|
||||
val future = futureDrain.future(mf)
|
||||
Await.result(future, 3.seconds) should be(1 to 1000)
|
||||
}
|
||||
|
||||
"pass elements through a chain of backpressured buffers of different size" in {
|
||||
val futureSink = FutureSink[Seq[Int]]
|
||||
val mf = FlowFrom((1 to 1000).iterator)
|
||||
val futureDrain = FutureDrain[Seq[Int]]
|
||||
val mf = Source((1 to 1000).iterator)
|
||||
.buffer(1, overflowStrategy = OverflowStrategy.backpressure)
|
||||
.buffer(10, overflowStrategy = OverflowStrategy.backpressure)
|
||||
.buffer(256, overflowStrategy = OverflowStrategy.backpressure)
|
||||
|
|
@ -45,8 +45,8 @@ class FlowBufferSpec extends AkkaSpec {
|
|||
.buffer(5, overflowStrategy = OverflowStrategy.backpressure)
|
||||
.buffer(128, overflowStrategy = OverflowStrategy.backpressure)
|
||||
.grouped(1001)
|
||||
.withSink(futureSink).run()
|
||||
val future = futureSink.future(mf)
|
||||
.connect(futureDrain).run()
|
||||
val future = futureDrain.future(mf)
|
||||
Await.result(future, 3.seconds) should be(1 to 1000)
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ class FlowBufferSpec extends AkkaSpec {
|
|||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowFrom(publisher).buffer(100, overflowStrategy = OverflowStrategy.backpressure).publishTo(subscriber)
|
||||
Source(publisher).buffer(100, overflowStrategy = OverflowStrategy.backpressure).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
@ -74,7 +74,7 @@ class FlowBufferSpec extends AkkaSpec {
|
|||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowFrom(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropHead).publishTo(subscriber)
|
||||
Source(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropHead).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
@ -102,7 +102,7 @@ class FlowBufferSpec extends AkkaSpec {
|
|||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowFrom(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropTail).publishTo(subscriber)
|
||||
Source(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropTail).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
@ -133,7 +133,7 @@ class FlowBufferSpec extends AkkaSpec {
|
|||
val publisher = StreamTestKit.PublisherProbe[Int]
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowFrom(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropBuffer).publishTo(subscriber)
|
||||
Source(publisher).buffer(100, overflowStrategy = OverflowStrategy.dropBuffer).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
@ -164,7 +164,7 @@ class FlowBufferSpec extends AkkaSpec {
|
|||
val publisher = StreamTestKit.PublisherProbe[Int]
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowFrom(publisher).buffer(1, overflowStrategy = strategy).publishTo(subscriber)
|
||||
Source(publisher).buffer(1, overflowStrategy = strategy).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -5,143 +5,103 @@ package akka.stream.scaladsl2
|
|||
|
||||
import akka.stream.MaterializerSettings
|
||||
import akka.stream.testkit.AkkaSpec
|
||||
|
||||
import scala.collection.immutable.Seq
|
||||
import scala.concurrent.Future
|
||||
|
||||
class FlowCompileSpec extends AkkaSpec {
|
||||
|
||||
val intSeq = IterableSource(Seq(1, 2, 3))
|
||||
val strSeq = IterableSource(Seq("a", "b", "c"))
|
||||
val intSeq = IterableTap(Seq(1, 2, 3))
|
||||
val strSeq = IterableTap(Seq("a", "b", "c"))
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val intFut = FutureSource(Future { 3 })
|
||||
val intFut = FutureTap(Future { 3 })
|
||||
implicit val materializer = FlowMaterializer(MaterializerSettings(system))
|
||||
|
||||
"ProcessorFlow" should {
|
||||
"go through all states" in {
|
||||
val f: ProcessorFlow[Int, Int] = FlowFrom[Int]
|
||||
.withSource(intSeq)
|
||||
.withSink(PublisherSink[Int])
|
||||
.withoutSource
|
||||
.withoutSink
|
||||
}
|
||||
"Flow" should {
|
||||
"should not run" in {
|
||||
val open: ProcessorFlow[Int, Int] = FlowFrom[Int]
|
||||
val open: Flow[Int, Int] = Flow[Int]
|
||||
"open.run()" shouldNot compile
|
||||
}
|
||||
"accept IterableSource" in {
|
||||
val f: FlowWithSource[Int, Int] = FlowFrom[Int].withSource(intSeq)
|
||||
"accept Iterable" in {
|
||||
val f: Source[Int] = intSeq.connect(Flow[Int])
|
||||
}
|
||||
"accept FutureSource" in {
|
||||
val f: FlowWithSource[Int, Int] = FlowFrom[Int].withSource(intFut)
|
||||
"accept Future" in {
|
||||
val f: Source[Int] = intFut.connect(Flow[Int])
|
||||
}
|
||||
"append ProcessorFlow" in {
|
||||
val open1: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
|
||||
val open2: ProcessorFlow[String, Int] = FlowFrom[String].map(_.hashCode)
|
||||
val open3: ProcessorFlow[Int, Int] = open1.append(open2)
|
||||
"append Flow" in {
|
||||
val open1: Flow[Int, String] = Flow[Int].map(_.toString)
|
||||
val open2: Flow[String, Int] = Flow[String].map(_.hashCode)
|
||||
val open3: Flow[Int, Int] = open1.connect(open2)
|
||||
"open3.run()" shouldNot compile
|
||||
|
||||
val closedSource: FlowWithSource[Int, Int] = open3.withSource(intSeq)
|
||||
val closedSource: Source[Int] = intSeq.connect(open3)
|
||||
"closedSource.run()" shouldNot compile
|
||||
|
||||
val closedSink: FlowWithSink[Int, Int] = open3.withSink(PublisherSink[Int])
|
||||
val closedSink: Sink[Int] = open3.connect(PublisherDrain[Int])
|
||||
"closedSink.run()" shouldNot compile
|
||||
|
||||
closedSource.withSink(PublisherSink[Int]).run()
|
||||
closedSink.withSource(intSeq).run()
|
||||
closedSource.connect(PublisherDrain[Int]).run()
|
||||
intSeq.connect(closedSink).run()
|
||||
}
|
||||
"prepend ProcessorFlow" in {
|
||||
val open1: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
|
||||
val open2: ProcessorFlow[String, Int] = FlowFrom[String].map(_.hashCode)
|
||||
val open3: ProcessorFlow[String, String] = open1.prepend(open2)
|
||||
"open3.run()" shouldNot compile
|
||||
|
||||
val closedSource: FlowWithSource[String, String] = open3.withSource(strSeq)
|
||||
"closedSource.run()" shouldNot compile
|
||||
|
||||
val closedSink: FlowWithSink[String, String] = open3.withSink(PublisherSink[String])
|
||||
"closedSink.run()" shouldNot compile
|
||||
|
||||
closedSource.withSink(PublisherSink[String]).run
|
||||
closedSink.withSource(strSeq).run
|
||||
}
|
||||
"append FlowWithSink" in {
|
||||
val open: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
|
||||
val closedSink: FlowWithSink[String, Int] = FlowFrom[String].map(_.hashCode).withSink(PublisherSink[Int])
|
||||
val appended: FlowWithSink[Int, Int] = open.append(closedSink)
|
||||
"append Sink" in {
|
||||
val open: Flow[Int, String] = Flow[Int].map(_.toString)
|
||||
val closedDrain: Sink[String] = Flow[String].map(_.hashCode).connect(PublisherDrain[Int])
|
||||
val appended: Sink[Int] = open.connect(closedDrain)
|
||||
"appended.run()" shouldNot compile
|
||||
"appended.toFuture" shouldNot compile
|
||||
appended.withSource(intSeq).run
|
||||
"appended.connect(FutureDrain[Int])" shouldNot compile
|
||||
intSeq.connect(appended).run
|
||||
}
|
||||
"prepend FlowWithSource" in {
|
||||
val open: ProcessorFlow[Int, String] = FlowFrom[Int].map(_.toString)
|
||||
val closedSource: FlowWithSource[String, Int] = FlowFrom[String].map(_.hashCode).withSource(strSeq)
|
||||
val prepended: FlowWithSource[String, String] = open.prepend(closedSource)
|
||||
"prepended.run()" shouldNot compile
|
||||
"prepended.withSource(strSeq)" shouldNot compile
|
||||
prepended.withSink(PublisherSink[String]).run
|
||||
"be appended to Source" in {
|
||||
val open: Flow[Int, String] = Flow[Int].map(_.toString)
|
||||
val closedTap: Source[Int] = strSeq.connect(Flow[String].map(_.hashCode))
|
||||
val closedSource: Source[String] = closedTap.connect(open)
|
||||
"closedSource.run()" shouldNot compile
|
||||
"strSeq.connect(closedSource)" shouldNot compile
|
||||
closedSource.connect(PublisherDrain[String]).run
|
||||
}
|
||||
}
|
||||
|
||||
"FlowWithSink" should {
|
||||
val openSource: FlowWithSink[Int, String] =
|
||||
FlowFrom[Int].map(_.toString).withSink(PublisherSink[String])
|
||||
"Sink" should {
|
||||
val openSource: Sink[Int] =
|
||||
Flow[Int].map(_.toString).connect(PublisherDrain[String])
|
||||
"accept Source" in {
|
||||
openSource.withSource(intSeq)
|
||||
}
|
||||
"drop Sink" in {
|
||||
openSource.withoutSink
|
||||
}
|
||||
"not drop Source" in {
|
||||
"openSource.withoutSource" shouldNot compile
|
||||
intSeq.connect(openSource)
|
||||
}
|
||||
"not accept Sink" in {
|
||||
"openSource.ToFuture" shouldNot compile
|
||||
"openSource.connect(FutureDrain[String])" shouldNot compile
|
||||
}
|
||||
"not run()" in {
|
||||
"openSource.run()" shouldNot compile
|
||||
}
|
||||
}
|
||||
|
||||
"FlowWithSource" should {
|
||||
val openSink: FlowWithSource[Int, String] =
|
||||
FlowFrom(Seq(1, 2, 3)).map(_.toString)
|
||||
"Source" should {
|
||||
val openSource: Source[String] =
|
||||
Source(Seq(1, 2, 3)).map(_.toString)
|
||||
"accept Sink" in {
|
||||
openSink.withSink(PublisherSink[String])
|
||||
openSource.connect(PublisherDrain[String])
|
||||
}
|
||||
"drop Source" in {
|
||||
openSink.withoutSource
|
||||
}
|
||||
"not drop Sink" in {
|
||||
"openSink.withoutSink" shouldNot compile
|
||||
}
|
||||
"not accept Source" in {
|
||||
"openSink.withSource(intSeq)" shouldNot compile
|
||||
"not be accepted by Source" in {
|
||||
"openSource.connect(intSeq)" shouldNot compile
|
||||
}
|
||||
"not run()" in {
|
||||
"openSink.run()" shouldNot compile
|
||||
"openSource.run()" shouldNot compile
|
||||
}
|
||||
}
|
||||
|
||||
"RunnableFlow" should {
|
||||
val closed: RunnableFlow[Int, String] =
|
||||
FlowFrom(Seq(1, 2, 3)).map(_.toString).withSink(PublisherSink[String])
|
||||
FutureDrain[String]
|
||||
val closed: RunnableFlow =
|
||||
Source(Seq(1, 2, 3)).map(_.toString).connect(PublisherDrain[String])
|
||||
"run" in {
|
||||
closed.run()
|
||||
}
|
||||
"drop Source" in {
|
||||
closed.withoutSource
|
||||
}
|
||||
"drop Sink" in {
|
||||
closed.withoutSink
|
||||
}
|
||||
"not accept Source" in {
|
||||
"closed.withSource(intSeq)" shouldNot compile
|
||||
"not be accepted by Source" in {
|
||||
"intSeq.connect(closed)" shouldNot compile
|
||||
}
|
||||
|
||||
"not accept Sink" in {
|
||||
"closed.ToFuture" shouldNot compile
|
||||
"closed.connect(FutureDrain[String])" shouldNot compile
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ class FlowConcatAllSpec extends AkkaSpec {
|
|||
val testException = new Exception("test") with NoStackTrace
|
||||
|
||||
"work in the happy case" in {
|
||||
val s1 = FlowFrom((1 to 2).iterator)
|
||||
val s2 = FlowFrom(List.empty[Int])
|
||||
val s3 = FlowFrom(List(3))
|
||||
val s4 = FlowFrom((4 to 6).iterator)
|
||||
val s5 = FlowFrom((7 to 10).iterator)
|
||||
val s1 = Source((1 to 2).iterator)
|
||||
val s2 = Source(List.empty[Int])
|
||||
val s3 = Source(List(3))
|
||||
val s4 = Source((4 to 6).iterator)
|
||||
val s5 = Source((7 to 10).iterator)
|
||||
|
||||
val main = FlowFrom(List(s1, s2, s3, s4, s5))
|
||||
val main = Source(List(s1, s2, s3, s4, s5))
|
||||
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
main.flatten(FlattenStrategy.concat).publishTo(subscriber)
|
||||
|
|
@ -42,7 +42,7 @@ class FlowConcatAllSpec extends AkkaSpec {
|
|||
|
||||
"work together with SplitWhen" in {
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom((1 to 10).iterator).splitWhen(_ % 2 == 0).flatten(FlattenStrategy.concat).publishTo(subscriber)
|
||||
Source((1 to 10).iterator).splitWhen(_ % 2 == 0).flatten(FlattenStrategy.concat).publishTo(subscriber)
|
||||
val subscription = subscriber.expectSubscription()
|
||||
subscription.request(10)
|
||||
subscriber.probe.receiveN(10) should be((1 to 10).map(StreamTestKit.OnNext(_)))
|
||||
|
|
@ -51,16 +51,16 @@ class FlowConcatAllSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"on onError on master stream cancel the current open substream and signal error" in {
|
||||
val publisher = StreamTestKit.PublisherProbe[FlowWithSource[Int, Int]]()
|
||||
val publisher = StreamTestKit.PublisherProbe[Source[Int]]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
|
||||
Source(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
|
||||
|
||||
val upstream = publisher.expectSubscription()
|
||||
val downstream = subscriber.expectSubscription()
|
||||
downstream.request(1000)
|
||||
|
||||
val substreamPublisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val substreamFlow = FlowFrom(substreamPublisher)
|
||||
val substreamFlow = Source(substreamPublisher)
|
||||
upstream.expectRequest()
|
||||
upstream.sendNext(substreamFlow)
|
||||
val subUpstream = substreamPublisher.expectSubscription()
|
||||
|
|
@ -71,16 +71,16 @@ class FlowConcatAllSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"on onError on open substream, cancel the master stream and signal error " in {
|
||||
val publisher = StreamTestKit.PublisherProbe[FlowWithSource[Int, Int]]()
|
||||
val publisher = StreamTestKit.PublisherProbe[Source[Int]]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
|
||||
Source(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
|
||||
|
||||
val upstream = publisher.expectSubscription()
|
||||
val downstream = subscriber.expectSubscription()
|
||||
downstream.request(1000)
|
||||
|
||||
val substreamPublisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val substreamFlow = FlowFrom(substreamPublisher)
|
||||
val substreamFlow = Source(substreamPublisher)
|
||||
upstream.expectRequest()
|
||||
upstream.sendNext(substreamFlow)
|
||||
val subUpstream = substreamPublisher.expectSubscription()
|
||||
|
|
@ -91,16 +91,16 @@ class FlowConcatAllSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"on cancellation cancel the current open substream and the master stream" in {
|
||||
val publisher = StreamTestKit.PublisherProbe[FlowWithSource[Int, Int]]()
|
||||
val publisher = StreamTestKit.PublisherProbe[Source[Int]]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
|
||||
Source(publisher).flatten(FlattenStrategy.concat).publishTo(subscriber)
|
||||
|
||||
val upstream = publisher.expectSubscription()
|
||||
val downstream = subscriber.expectSubscription()
|
||||
downstream.request(1000)
|
||||
|
||||
val substreamPublisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val substreamFlow = FlowFrom(substreamPublisher)
|
||||
val substreamFlow = Source(substreamPublisher)
|
||||
upstream.expectRequest()
|
||||
upstream.sendNext(substreamFlow)
|
||||
val subUpstream = substreamPublisher.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class FlowConflateSpec extends AkkaSpec {
|
|||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowFrom(publisher).conflate[Int](seed = i ⇒ i, aggregate = (sum, i) ⇒ sum + i).publishTo(subscriber)
|
||||
Source(publisher).conflate[Int](seed = i ⇒ i, aggregate = (sum, i) ⇒ sum + i).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
@ -41,7 +41,7 @@ class FlowConflateSpec extends AkkaSpec {
|
|||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowFrom(publisher).conflate[Int](seed = i ⇒ i, aggregate = (sum, i) ⇒ sum + i).publishTo(subscriber)
|
||||
Source(publisher).conflate[Int](seed = i ⇒ i, aggregate = (sum, i) ⇒ sum + i).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
@ -56,13 +56,13 @@ class FlowConflateSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"work on a variable rate chain" in {
|
||||
val foldSink = FoldSink[Int, Int](0)(_ + _)
|
||||
val mf = FlowFrom((1 to 1000).iterator)
|
||||
val foldDrain = FoldDrain[Int, Int](0)(_ + _)
|
||||
val mf = Source((1 to 1000).iterator)
|
||||
.conflate[Int](seed = i ⇒ i, aggregate = (sum, i) ⇒ sum + i)
|
||||
.map { i ⇒ if (ThreadLocalRandom.current().nextBoolean()) Thread.sleep(10); i }
|
||||
.withSink(foldSink)
|
||||
.connect(foldDrain)
|
||||
.run()
|
||||
val future = foldSink.future(mf)
|
||||
val future = foldDrain.future(mf)
|
||||
Await.result(future, 10.seconds) should be(500500)
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ class FlowConflateSpec extends AkkaSpec {
|
|||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowFrom(publisher).conflate[Int](seed = i ⇒ i, aggregate = (sum, i) ⇒ sum + i).publishTo(subscriber)
|
||||
Source(publisher).conflate[Int](seed = i ⇒ i, aggregate = (sum, i) ⇒ sum + i).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class FlowDispatcherSpec extends AkkaSpec {
|
|||
"Flow with dispatcher setting" must {
|
||||
"use the specified dispatcher" in {
|
||||
val probe = TestProbe()
|
||||
val p = FlowFrom(List(1, 2, 3)).map(i ⇒
|
||||
val p = Source(List(1, 2, 3)).map(i ⇒
|
||||
{ probe.ref ! Thread.currentThread().getName(); i }).
|
||||
consume()
|
||||
probe.receiveN(3) foreach {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class FlowDropSpec extends AkkaSpec with ScriptedTest {
|
|||
|
||||
"not drop anything for negative n" in {
|
||||
val probe = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(List(1, 2, 3)).drop(-1).publishTo(probe)
|
||||
Source(List(1, 2, 3)).drop(-1).publishTo(probe)
|
||||
probe.expectSubscription().request(10)
|
||||
probe.expectNext(1)
|
||||
probe.expectNext(2)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class FlowDropWithinSpec extends AkkaSpec {
|
|||
val input = Iterator.from(1)
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(p).dropWithin(1.second).publishTo(c)
|
||||
Source(p).dropWithin(1.second).publishTo(c)
|
||||
val pSub = p.expectSubscription
|
||||
val cSub = c.expectSubscription
|
||||
cSub.request(100)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class FlowExpandSpec extends AkkaSpec {
|
|||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
// Simply repeat the last element as an extrapolation step
|
||||
FlowFrom(publisher).expand[Int, Int](seed = i ⇒ i, extrapolate = i ⇒ (i, i)).publishTo(subscriber)
|
||||
Source(publisher).expand[Int, Int](seed = i ⇒ i, extrapolate = i ⇒ (i, i)).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
@ -44,7 +44,7 @@ class FlowExpandSpec extends AkkaSpec {
|
|||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
// Simply repeat the last element as an extrapolation step
|
||||
FlowFrom(publisher).expand[Int, Int](seed = i ⇒ i, extrapolate = i ⇒ (i, i)).publishTo(subscriber)
|
||||
Source(publisher).expand[Int, Int](seed = i ⇒ i, extrapolate = i ⇒ (i, i)).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
@ -64,13 +64,13 @@ class FlowExpandSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"work on a variable rate chain" in {
|
||||
val foldSink = FoldSink[Set[Int], Int](Set.empty[Int])(_ + _)
|
||||
val mf = FlowFrom((1 to 100).iterator)
|
||||
val foldDrain = FoldDrain[Set[Int], Int](Set.empty[Int])(_ + _)
|
||||
val mf = Source((1 to 100).iterator)
|
||||
.map { i ⇒ if (ThreadLocalRandom.current().nextBoolean()) Thread.sleep(10); i }
|
||||
.expand[Int, Int](seed = i ⇒ i, extrapolate = i ⇒ (i, i))
|
||||
.withSink(foldSink)
|
||||
.connect(foldDrain)
|
||||
.run()
|
||||
val future = foldSink.future(mf)
|
||||
val future = foldDrain.future(mf)
|
||||
|
||||
Await.result(future, 10.seconds) should be(Set.empty[Int] ++ (1 to 100))
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ class FlowExpandSpec extends AkkaSpec {
|
|||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowFrom(publisher).expand[Int, Int](seed = i ⇒ i, extrapolate = i ⇒ (i, i)).publishTo(subscriber)
|
||||
Source(publisher).expand[Int, Int](seed = i ⇒ i, extrapolate = i ⇒ (i, i)).publishTo(subscriber)
|
||||
|
||||
val autoPublisher = new StreamTestKit.AutoPublisher(publisher)
|
||||
val sub = subscriber.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class FlowFilterSpec extends AkkaSpec with ScriptedTest {
|
|||
implicit val materializer = FlowMaterializer(settings)
|
||||
|
||||
val probe = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(Iterator.fill(1000)(0) ++ List(1)).filter(_ != 0).
|
||||
Source(Iterator.fill(1000)(0) ++ List(1)).filter(_ != 0).
|
||||
toPublisher().subscribe(probe)
|
||||
|
||||
val subscription = probe.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -15,17 +15,17 @@ class FlowFoldSpec extends AkkaSpec with DefaultTimeout {
|
|||
|
||||
"fold" in {
|
||||
val input = 1 to 100
|
||||
val foldSink = FoldSink[Int, Int](0)(_ + _)
|
||||
val mf = FlowFrom(input).withSink(foldSink).run()
|
||||
val future = foldSink.future(mf)
|
||||
val foldDrain = FoldDrain[Int, Int](0)(_ + _)
|
||||
val mf = Source(input).connect(foldDrain).run()
|
||||
val future = foldDrain.future(mf)
|
||||
val expected = input.fold(0)(_ + _)
|
||||
Await.result(future, timeout.duration) should be(expected)
|
||||
}
|
||||
|
||||
"propagate an error" in {
|
||||
val error = new Exception with NoStackTrace
|
||||
val foldSink = FoldSink[Unit, Unit](())((_, _) ⇒ ())
|
||||
val mf = FlowFrom[Unit](() ⇒ throw error).withSink(foldSink).run()
|
||||
val foldSink = FoldDrain[Unit, Unit](())((_, _) ⇒ ())
|
||||
val mf = Source[Unit](() ⇒ throw error).connect(foldSink).run()
|
||||
val future = foldSink.future(mf)
|
||||
the[Exception] thrownBy Await.result(future, timeout.duration) should be(error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ class FlowForeachSpec extends AkkaSpec {
|
|||
"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 {
|
||||
val foreachDrain = ForeachDrain[Int](testActor ! _)
|
||||
val mf = Source(1 to 3).connect(foreachDrain).run()
|
||||
foreachDrain.future(mf).onSuccess {
|
||||
case _ ⇒ testActor ! "done"
|
||||
}
|
||||
expectMsg(1)
|
||||
|
|
@ -28,9 +28,9 @@ class FlowForeachSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"complete the future for an empty stream" in {
|
||||
val foreachSink = ForeachSink[Int](testActor ! _)
|
||||
val mf = FlowFrom(Nil).withSink(foreachSink).run()
|
||||
foreachSink.future(mf).onSuccess {
|
||||
val foreachDrain = ForeachDrain[Int](testActor ! _)
|
||||
val mf = Source(Nil).connect(foreachDrain).run()
|
||||
foreachDrain.future(mf).onSuccess {
|
||||
case _ ⇒ testActor ! "done"
|
||||
}
|
||||
expectMsg("done")
|
||||
|
|
@ -38,9 +38,9 @@ class FlowForeachSpec extends AkkaSpec {
|
|||
|
||||
"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 {
|
||||
val foreachDrain = ForeachDrain[Int](testActor ! _)
|
||||
val mf = Source(p).connect(foreachDrain).run()
|
||||
foreachDrain.future(mf).onFailure {
|
||||
case ex ⇒ testActor ! ex
|
||||
}
|
||||
val proc = p.expectSubscription
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class FlowFromFutureSpec extends AkkaSpec {
|
|||
|
||||
"A Flow based on a Future" must {
|
||||
"produce one element from already successful Future" in {
|
||||
val p = FlowFrom(Future.successful(1)).toPublisher()
|
||||
val p = Source(Future.successful(1)).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -29,7 +29,7 @@ class FlowFromFutureSpec extends AkkaSpec {
|
|||
|
||||
"produce error from already failed Future" in {
|
||||
val ex = new RuntimeException("test") with NoStackTrace
|
||||
val p = FlowFrom(Future.failed[Int](ex)).toPublisher()
|
||||
val p = Source(Future.failed[Int](ex)).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
c.expectError(ex)
|
||||
|
|
@ -37,7 +37,7 @@ class FlowFromFutureSpec extends AkkaSpec {
|
|||
|
||||
"produce one element when Future is completed" in {
|
||||
val promise = Promise[Int]()
|
||||
val p = FlowFrom(promise.future).toPublisher()
|
||||
val p = Source(promise.future).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -51,7 +51,7 @@ class FlowFromFutureSpec extends AkkaSpec {
|
|||
|
||||
"produce one element when Future is completed but not before request" in {
|
||||
val promise = Promise[Int]()
|
||||
val p = FlowFrom(promise.future).toPublisher()
|
||||
val p = Source(promise.future).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -64,7 +64,7 @@ class FlowFromFutureSpec extends AkkaSpec {
|
|||
|
||||
"produce elements with multiple subscribers" in {
|
||||
val promise = Promise[Int]()
|
||||
val p = FlowFrom(promise.future).toPublisher()
|
||||
val p = Source(promise.future).toPublisher()
|
||||
val c1 = StreamTestKit.SubscriberProbe[Int]()
|
||||
val c2 = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c1)
|
||||
|
|
@ -82,7 +82,7 @@ class FlowFromFutureSpec extends AkkaSpec {
|
|||
|
||||
"produce elements to later subscriber" in {
|
||||
val promise = Promise[Int]()
|
||||
val p = FlowFrom(promise.future).toPublisher()
|
||||
val p = Source(promise.future).toPublisher()
|
||||
val keepAlive = StreamTestKit.SubscriberProbe[Int]()
|
||||
val c1 = StreamTestKit.SubscriberProbe[Int]()
|
||||
val c2 = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
|
@ -103,7 +103,7 @@ class FlowFromFutureSpec extends AkkaSpec {
|
|||
|
||||
"allow cancel before receiving element" in {
|
||||
val promise = Promise[Int]()
|
||||
val p = FlowFrom(promise.future).toPublisher()
|
||||
val p = Source(promise.future).toPublisher()
|
||||
val keepAlive = StreamTestKit.SubscriberProbe[Int]()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(keepAlive)
|
||||
|
|
|
|||
|
|
@ -25,17 +25,17 @@ class FlowGraphCompileSpec extends AkkaSpec {
|
|||
}
|
||||
}
|
||||
|
||||
val f1 = FlowFrom[String].transform("f1", op[String, String])
|
||||
val f2 = FlowFrom[String].transform("f2", op[String, String])
|
||||
val f3 = FlowFrom[String].transform("f3", op[String, String])
|
||||
val f4 = FlowFrom[String].transform("f4", op[String, String])
|
||||
val f5 = FlowFrom[String].transform("f5", op[String, String])
|
||||
val f6 = FlowFrom[String].transform("f6", op[String, String])
|
||||
val f1 = Flow[String].transform("f1", op[String, String])
|
||||
val f2 = Flow[String].transform("f2", op[String, String])
|
||||
val f3 = Flow[String].transform("f3", op[String, String])
|
||||
val f4 = Flow[String].transform("f4", op[String, String])
|
||||
val f5 = Flow[String].transform("f5", op[String, String])
|
||||
val f6 = Flow[String].transform("f6", op[String, String])
|
||||
|
||||
val in1 = IterableSource(List("a", "b", "c"))
|
||||
val in2 = IterableSource(List("d", "e", "f"))
|
||||
val out1 = PublisherSink[String]
|
||||
val out2 = FutureSink[String]
|
||||
val in1 = IterableTap(List("a", "b", "c"))
|
||||
val in2 = IterableTap(List("d", "e", "f"))
|
||||
val out1 = PublisherDrain[String]
|
||||
val out2 = FutureDrain[String]
|
||||
|
||||
"FlowGraph" should {
|
||||
"build simple merge" in {
|
||||
|
|
@ -97,7 +97,7 @@ class FlowGraphCompileSpec extends AkkaSpec {
|
|||
val merge = Merge[String]
|
||||
val bcast1 = Broadcast[String]
|
||||
val bcast2 = Broadcast[String]
|
||||
val feedbackLoopBuffer = FlowFrom[String].buffer(10, OverflowStrategy.dropBuffer)
|
||||
val feedbackLoopBuffer = Flow[String].buffer(10, OverflowStrategy.dropBuffer)
|
||||
b.
|
||||
addEdge(in1, f1, merge).
|
||||
addEdge(merge, f2, bcast1).
|
||||
|
|
@ -116,7 +116,7 @@ class FlowGraphCompileSpec extends AkkaSpec {
|
|||
val merge = Merge[String]
|
||||
val bcast1 = Broadcast[String]
|
||||
val bcast2 = Broadcast[String]
|
||||
val feedbackLoopBuffer = FlowFrom[String].buffer(10, OverflowStrategy.dropBuffer)
|
||||
val feedbackLoopBuffer = Flow[String].buffer(10, OverflowStrategy.dropBuffer)
|
||||
import FlowGraphImplicits._
|
||||
in1 ~> f1 ~> merge ~> f2 ~> bcast1 ~> f3 ~> out1
|
||||
bcast1 ~> feedbackLoopBuffer ~> bcast2 ~> f5 ~> merge
|
||||
|
|
@ -145,13 +145,13 @@ class FlowGraphCompileSpec extends AkkaSpec {
|
|||
val m9 = Merge[String]
|
||||
val m10 = Merge[String]
|
||||
val m11 = Merge[String]
|
||||
val in3 = IterableSource(List("b"))
|
||||
val in5 = IterableSource(List("b"))
|
||||
val in7 = IterableSource(List("a"))
|
||||
val out2 = PublisherSink[String]
|
||||
val out9 = PublisherSink[String]
|
||||
val out10 = PublisherSink[String]
|
||||
def f(s: String) = FlowFrom[String].transform(s, op[String, String])
|
||||
val in3 = IterableTap(List("b"))
|
||||
val in5 = IterableTap(List("b"))
|
||||
val in7 = IterableTap(List("a"))
|
||||
val out2 = PublisherDrain[String]
|
||||
val out9 = PublisherDrain[String]
|
||||
val out10 = PublisherDrain[String]
|
||||
def f(s: String) = Flow[String].transform(s, op[String, String])
|
||||
import FlowGraphImplicits._
|
||||
|
||||
in7 ~> f("a") ~> b7 ~> f("b") ~> m11 ~> f("c") ~> b11 ~> f("d") ~> out2
|
||||
|
|
@ -164,53 +164,53 @@ class FlowGraphCompileSpec extends AkkaSpec {
|
|||
}.run()
|
||||
}
|
||||
|
||||
"attachSource and attachSink" in {
|
||||
"attachTap and attachDrain" in {
|
||||
val mg = FlowGraph { b ⇒
|
||||
val merge = Merge[String]
|
||||
val undefinedSrc1 = UndefinedSource[String]
|
||||
val undefinedSrc2 = UndefinedSource[String]
|
||||
val undefinedSink1 = UndefinedSink[String]
|
||||
val undefinedSrc1 = UndefinedTap[String]
|
||||
val undefinedSrc2 = UndefinedTap[String]
|
||||
val undefinedDrain1 = UndefinedDrain[String]
|
||||
b.
|
||||
addEdge(undefinedSrc1, f1, merge).
|
||||
addEdge(UndefinedSource[String]("src2"), f2, merge).
|
||||
addEdge(merge, f3, undefinedSink1)
|
||||
addEdge(UndefinedTap[String]("src2"), f2, merge).
|
||||
addEdge(merge, f3, undefinedDrain1)
|
||||
|
||||
b.attachSource(undefinedSrc1, in1)
|
||||
b.attachSource(UndefinedSource[String]("src2"), in2)
|
||||
b.attachSink(undefinedSink1, out1)
|
||||
b.attachTap(undefinedSrc1, in1)
|
||||
b.attachTap(UndefinedTap[String]("src2"), in2)
|
||||
b.attachDrain(undefinedDrain1, out1)
|
||||
|
||||
}.run()
|
||||
out1.publisher(mg) should not be (null)
|
||||
}
|
||||
|
||||
"build partial flow graphs" in {
|
||||
val undefinedSrc1 = UndefinedSource[String]
|
||||
val undefinedSrc2 = UndefinedSource[String]
|
||||
val undefinedSink1 = UndefinedSink[String]
|
||||
val undefinedSrc1 = UndefinedTap[String]
|
||||
val undefinedSrc2 = UndefinedTap[String]
|
||||
val undefinedDrain1 = UndefinedDrain[String]
|
||||
val bcast = Broadcast[String]
|
||||
|
||||
val partial1 = PartialFlowGraph { implicit b ⇒
|
||||
import FlowGraphImplicits._
|
||||
val merge = Merge[String]
|
||||
undefinedSrc1 ~> f1 ~> merge ~> f2 ~> bcast ~> f3 ~> undefinedSink1
|
||||
undefinedSrc1 ~> f1 ~> merge ~> f2 ~> bcast ~> f3 ~> undefinedDrain1
|
||||
undefinedSrc2 ~> f4 ~> merge
|
||||
|
||||
}
|
||||
partial1.undefinedSources should be(Set(undefinedSrc1, undefinedSrc2))
|
||||
partial1.undefinedSinks should be(Set(undefinedSink1))
|
||||
partial1.undefinedTaps should be(Set(undefinedSrc1, undefinedSrc2))
|
||||
partial1.undefinedDrains should be(Set(undefinedDrain1))
|
||||
|
||||
val partial2 = PartialFlowGraph(partial1) { implicit b ⇒
|
||||
import FlowGraphImplicits._
|
||||
b.attachSource(undefinedSrc1, in1)
|
||||
b.attachSource(undefinedSrc2, in2)
|
||||
bcast ~> f5 ~> UndefinedSink[String]("sink2")
|
||||
b.attachTap(undefinedSrc1, in1)
|
||||
b.attachTap(undefinedSrc2, in2)
|
||||
bcast ~> f5 ~> UndefinedDrain[String]("drain2")
|
||||
}
|
||||
partial2.undefinedSources should be(Set.empty)
|
||||
partial2.undefinedSinks should be(Set(undefinedSink1, UndefinedSink[String]("sink2")))
|
||||
partial2.undefinedTaps should be(Set.empty)
|
||||
partial2.undefinedDrains should be(Set(undefinedDrain1, UndefinedDrain[String]("drain2")))
|
||||
|
||||
FlowGraph(partial2) { implicit b ⇒
|
||||
b.attachSink(undefinedSink1, out1)
|
||||
b.attachSink(UndefinedSink[String]("sink2"), out2)
|
||||
b.attachDrain(undefinedDrain1, out1)
|
||||
b.attachDrain(UndefinedDrain[String]("drain2"), out2)
|
||||
}.run()
|
||||
}
|
||||
|
||||
|
|
@ -225,22 +225,13 @@ class FlowGraphCompileSpec extends AkkaSpec {
|
|||
}.run()
|
||||
}
|
||||
|
||||
"use FlowWithSource and FlowWithSink" in {
|
||||
FlowGraph { implicit b ⇒
|
||||
val bcast = Broadcast[String]
|
||||
import FlowGraphImplicits._
|
||||
f1.withSource(in1) ~> bcast ~> f2.withSink(out1)
|
||||
bcast ~> f3.withSink(out2)
|
||||
}.run()
|
||||
}
|
||||
|
||||
"chain input and output ports" in {
|
||||
FlowGraph { implicit b ⇒
|
||||
val zip = Zip[Int, String]
|
||||
val out = PublisherSink[(Int, String)]
|
||||
val out = PublisherDrain[(Int, String)]
|
||||
import FlowGraphImplicits._
|
||||
FlowFrom(List(1, 2, 3)) ~> zip.left ~> out
|
||||
FlowFrom(List("a", "b", "c")) ~> zip.right
|
||||
Source(List(1, 2, 3)) ~> zip.left ~> out
|
||||
Source(List("a", "b", "c")) ~> zip.right
|
||||
}.run()
|
||||
}
|
||||
|
||||
|
|
@ -248,10 +239,10 @@ class FlowGraphCompileSpec extends AkkaSpec {
|
|||
FlowGraph { implicit b ⇒
|
||||
val zip = Zip[Int, String]
|
||||
val unzip = Unzip[Int, String]
|
||||
val out = PublisherSink[(Int, String)]
|
||||
val out = PublisherDrain[(Int, String)]
|
||||
import FlowGraphImplicits._
|
||||
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.left ~> FlowFrom[Int].map(_ * 2) ~> zip.left
|
||||
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.left ~> Flow[Int].map(_ * 2) ~> zip.left
|
||||
unzip.right ~> zip.right
|
||||
zip.out ~> out
|
||||
}.run()
|
||||
|
|
@ -262,15 +253,15 @@ class FlowGraphCompileSpec extends AkkaSpec {
|
|||
FlowGraph { implicit b ⇒
|
||||
val zip = Zip[Int, String]
|
||||
val unzip = Unzip[Int, String]
|
||||
val wrongOut = PublisherSink[(Int, Int)]
|
||||
val whatever = PublisherSink[Any]
|
||||
val wrongOut = PublisherDrain[(Int, Int)]
|
||||
val whatever = PublisherDrain[Any]
|
||||
import FlowGraphImplicits._
|
||||
"FlowFrom(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
|
||||
"""FlowFrom(List("a", "b", "c")) ~> zip.left""" shouldNot compile
|
||||
"""FlowFrom(List("a", "b", "c")) ~> zip.out""" shouldNot compile
|
||||
"Flow(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
|
||||
"""Flow(List("a", "b", "c")) ~> zip.left""" shouldNot compile
|
||||
"""Flow(List("a", "b", "c")) ~> zip.out""" shouldNot compile
|
||||
"zip.left ~> zip.right" shouldNot compile
|
||||
"FlowFrom(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
|
||||
"""FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in ~> whatever""" shouldNot compile
|
||||
"Flow(List(1, 2, 3)) ~> zip.left ~> wrongOut" shouldNot compile
|
||||
"""Flow(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in ~> whatever""" shouldNot compile
|
||||
}
|
||||
}.getMessage should include("empty")
|
||||
}
|
||||
|
|
@ -298,50 +289,50 @@ class FlowGraphCompileSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"build with variance" in {
|
||||
val out = SubscriberSink(SubscriberProbe[Fruit]())
|
||||
val out = SubscriberDrain(SubscriberProbe[Fruit]())
|
||||
FlowGraph { b ⇒
|
||||
val merge = Merge[Fruit]
|
||||
b.
|
||||
addEdge(FlowFrom[Fruit](() ⇒ Some(new Apple)), merge).
|
||||
addEdge(FlowFrom[Apple](() ⇒ Some(new Apple)), merge).
|
||||
addEdge(merge, FlowFrom[Fruit].map(identity), out)
|
||||
addEdge(Source[Fruit](() ⇒ Some(new Apple)), merge).
|
||||
addEdge(Source[Apple](() ⇒ Some(new Apple)), merge).
|
||||
addEdge(merge, Flow[Fruit].map(identity), out)
|
||||
}
|
||||
}
|
||||
|
||||
"build with implicits and variance" in {
|
||||
PartialFlowGraph { implicit b ⇒
|
||||
val inA = PublisherSource(PublisherProbe[Fruit]())
|
||||
val inB = PublisherSource(PublisherProbe[Apple]())
|
||||
val outA = SubscriberSink(SubscriberProbe[Fruit]())
|
||||
val outB = SubscriberSink(SubscriberProbe[Fruit]())
|
||||
val inA = PublisherTap(PublisherProbe[Fruit]())
|
||||
val inB = PublisherTap(PublisherProbe[Apple]())
|
||||
val outA = SubscriberDrain(SubscriberProbe[Fruit]())
|
||||
val outB = SubscriberDrain(SubscriberProbe[Fruit]())
|
||||
val merge = Merge[Fruit]
|
||||
val unzip = Unzip[Int, String]
|
||||
val whatever = PublisherSink[Any]
|
||||
val whatever = PublisherDrain[Any]
|
||||
import FlowGraphImplicits._
|
||||
FlowFrom[Fruit](() ⇒ Some(new Apple)) ~> merge
|
||||
FlowFrom[Apple](() ⇒ Some(new Apple)) ~> merge
|
||||
Source[Fruit](() ⇒ Some(new Apple)) ~> merge
|
||||
Source[Apple](() ⇒ Some(new Apple)) ~> merge
|
||||
inA ~> merge
|
||||
inB ~> merge
|
||||
inA ~> FlowFrom[Fruit].map(identity) ~> merge
|
||||
inB ~> FlowFrom[Apple].map(identity) ~> merge
|
||||
UndefinedSource[Apple] ~> merge
|
||||
UndefinedSource[Apple] ~> FlowFrom[Fruit].map(identity) ~> merge
|
||||
UndefinedSource[Apple] ~> FlowFrom[Apple].map(identity) ~> merge
|
||||
merge ~> FlowFrom[Fruit].map(identity) ~> outA
|
||||
inA ~> Flow[Fruit].map(identity) ~> merge
|
||||
inB ~> Flow[Apple].map(identity) ~> merge
|
||||
UndefinedTap[Apple] ~> merge
|
||||
UndefinedTap[Apple] ~> Flow[Fruit].map(identity) ~> merge
|
||||
UndefinedTap[Apple] ~> Flow[Apple].map(identity) ~> merge
|
||||
merge ~> Flow[Fruit].map(identity) ~> outA
|
||||
|
||||
FlowFrom[Apple](() ⇒ Some(new Apple)) ~> Broadcast[Apple] ~> merge
|
||||
FlowFrom[Apple](() ⇒ Some(new Apple)) ~> Broadcast[Apple] ~> outB
|
||||
FlowFrom[Apple](() ⇒ Some(new Apple)) ~> Broadcast[Apple] ~> UndefinedSink[Fruit]
|
||||
Source[Apple](() ⇒ Some(new Apple)) ~> Broadcast[Apple] ~> merge
|
||||
Source[Apple](() ⇒ Some(new Apple)) ~> Broadcast[Apple] ~> outB
|
||||
Source[Apple](() ⇒ Some(new Apple)) ~> Broadcast[Apple] ~> UndefinedDrain[Fruit]
|
||||
inB ~> Broadcast[Apple] ~> merge
|
||||
|
||||
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.right ~> whatever
|
||||
unzip.left ~> UndefinedSink[Any]
|
||||
unzip.left ~> UndefinedDrain[Any]
|
||||
|
||||
"UndefinedSource[Fruit] ~> FlowFrom[Apple].map(identity) ~> merge" shouldNot compile
|
||||
"UndefinedSource[Fruit] ~> Broadcast[Apple]" shouldNot compile
|
||||
"UndefinedTap[Fruit] ~> Flow[Apple].map(identity) ~> merge" shouldNot compile
|
||||
"UndefinedTap[Fruit] ~> Broadcast[Apple]" shouldNot compile
|
||||
"merge ~> Broadcast[Apple]" shouldNot compile
|
||||
"merge ~> FlowFrom[Fruit].map(identity) ~> Broadcast[Apple]" shouldNot compile
|
||||
"merge ~> Flow[Fruit].map(identity) ~> Broadcast[Apple]" shouldNot compile
|
||||
"inB ~> merge ~> Broadcast[Apple]" shouldNot compile
|
||||
"inA ~> Broadcast[Apple]" shouldNot compile
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,19 +32,19 @@ class FlowGroupBySpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
class SubstreamsSupport(groupCount: Int = 2, elementCount: Int = 6) {
|
||||
val source = FlowFrom((1 to elementCount).iterator).toPublisher()
|
||||
val groupStream = FlowFrom(source).groupBy(_ % groupCount).toPublisher()
|
||||
val masterSubscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
|
||||
val tap = Source((1 to elementCount).iterator).toPublisher()
|
||||
val groupStream = Source(tap).groupBy(_ % groupCount).toPublisher()
|
||||
val masterSubscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
|
||||
|
||||
groupStream.subscribe(masterSubscriber)
|
||||
val masterSubscription = masterSubscriber.expectSubscription()
|
||||
|
||||
def getSubFlow(expectedKey: Int): FlowWithSource[Int, Int] = {
|
||||
def getSubFlow(expectedKey: Int): Source[Int] = {
|
||||
masterSubscription.request(1)
|
||||
expectSubFlow(expectedKey: Int)
|
||||
}
|
||||
|
||||
def expectSubFlow(expectedKey: Int): FlowWithSource[Int, Int] = {
|
||||
def expectSubFlow(expectedKey: Int): Source[Int] = {
|
||||
val (key, substream) = masterSubscriber.expectNext()
|
||||
key should be(expectedKey)
|
||||
substream
|
||||
|
|
@ -110,8 +110,8 @@ class FlowGroupBySpec extends AkkaSpec {
|
|||
|
||||
"accept cancellation of master stream when not consumed anything" in {
|
||||
val publisherProbeProbe = StreamTestKit.PublisherProbe[Int]()
|
||||
val publisher = FlowFrom(publisherProbeProbe).groupBy(_ % 2).toPublisher()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
|
||||
val publisher = Source(publisherProbeProbe).groupBy(_ % 2).toPublisher()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
val upstreamSubscription = publisherProbeProbe.expectSubscription()
|
||||
|
|
@ -141,8 +141,8 @@ class FlowGroupBySpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"work with empty input stream" in {
|
||||
val publisher = FlowFrom(List.empty[Int]).groupBy(_ % 2).toPublisher()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
|
||||
val publisher = Source(List.empty[Int]).groupBy(_ % 2).toPublisher()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
subscriber.expectCompletedOrSubscriptionFollowedByComplete()
|
||||
|
|
@ -150,8 +150,8 @@ class FlowGroupBySpec extends AkkaSpec {
|
|||
|
||||
"abort on onError from upstream" in {
|
||||
val publisherProbeProbe = StreamTestKit.PublisherProbe[Int]()
|
||||
val publisher = FlowFrom(publisherProbeProbe).groupBy(_ % 2).toPublisher()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
|
||||
val publisher = Source(publisherProbeProbe).groupBy(_ % 2).toPublisher()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
val upstreamSubscription = publisherProbeProbe.expectSubscription()
|
||||
|
|
@ -167,8 +167,8 @@ class FlowGroupBySpec extends AkkaSpec {
|
|||
|
||||
"abort on onError from upstream when substreams are running" in {
|
||||
val publisherProbeProbe = StreamTestKit.PublisherProbe[Int]()
|
||||
val publisher = FlowFrom(publisherProbeProbe).groupBy(_ % 2).toPublisher()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(Int, FlowWithSource[Int, Int])]()
|
||||
val publisher = Source(publisherProbeProbe).groupBy(_ % 2).toPublisher()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(Int, Source[Int])]()
|
||||
publisher.subscribe(subscriber)
|
||||
|
||||
val upstreamSubscription = publisherProbeProbe.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
|
|||
val input = Iterator.from(1)
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
|
||||
FlowFrom(p).groupedWithin(1000, 1.second).publishTo(c)
|
||||
Source(p).groupedWithin(1000, 1.second).publishTo(c)
|
||||
val pSub = p.expectSubscription
|
||||
val cSub = c.expectSubscription
|
||||
cSub.request(100)
|
||||
|
|
@ -49,7 +49,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
|
|||
|
||||
"deliver bufferd elements onComplete before the timeout" in {
|
||||
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
|
||||
FlowFrom(1 to 3).groupedWithin(1000, 10.second).publishTo(c)
|
||||
Source(1 to 3).groupedWithin(1000, 10.second).publishTo(c)
|
||||
val cSub = c.expectSubscription
|
||||
cSub.request(100)
|
||||
c.expectNext((1 to 3).toList)
|
||||
|
|
@ -61,7 +61,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
|
|||
val input = Iterator.from(1)
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
|
||||
FlowFrom(p).groupedWithin(1000, 1.second).publishTo(c)
|
||||
Source(p).groupedWithin(1000, 1.second).publishTo(c)
|
||||
val pSub = p.expectSubscription
|
||||
val cSub = c.expectSubscription
|
||||
cSub.request(1)
|
||||
|
|
@ -81,7 +81,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
|
|||
"drop empty groups" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
|
||||
FlowFrom(p).groupedWithin(1000, 500.millis).publishTo(c)
|
||||
Source(p).groupedWithin(1000, 500.millis).publishTo(c)
|
||||
val pSub = p.expectSubscription
|
||||
val cSub = c.expectSubscription
|
||||
cSub.request(2)
|
||||
|
|
@ -103,7 +103,7 @@ class FlowGroupedWithinSpec extends AkkaSpec with ScriptedTest {
|
|||
val input = Iterator.from(1)
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val c = StreamTestKit.SubscriberProbe[immutable.Seq[Int]]()
|
||||
FlowFrom(p).groupedWithin(3, 2.second).publishTo(c)
|
||||
Source(p).groupedWithin(3, 2.second).publishTo(c)
|
||||
val pSub = p.expectSubscription
|
||||
val cSub = c.expectSubscription
|
||||
cSub.request(4)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class FlowIterableSpec extends AkkaSpec {
|
|||
|
||||
"A Flow based on an iterable" must {
|
||||
"produce elements" in {
|
||||
val p = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
val p = Source(List(1, 2, 3)).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -32,7 +32,7 @@ class FlowIterableSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"complete empty" in {
|
||||
val p = FlowFrom(List.empty[Int]).toPublisher()
|
||||
val p = Source(List.empty[Int]).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
c.expectComplete()
|
||||
|
|
@ -44,7 +44,7 @@ class FlowIterableSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce elements with multiple subscribers" in {
|
||||
val p = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
val p = Source(List(1, 2, 3)).toPublisher()
|
||||
val c1 = StreamTestKit.SubscriberProbe[Int]()
|
||||
val c2 = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c1)
|
||||
|
|
@ -68,7 +68,7 @@ class FlowIterableSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce elements to later subscriber" in {
|
||||
val p = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
val p = Source(List(1, 2, 3)).toPublisher()
|
||||
val c1 = StreamTestKit.SubscriberProbe[Int]()
|
||||
val c2 = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c1)
|
||||
|
|
@ -94,7 +94,7 @@ class FlowIterableSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce elements with one transformation step" in {
|
||||
val p = FlowFrom(List(1, 2, 3)).map(_ * 2).toPublisher()
|
||||
val p = Source(List(1, 2, 3)).map(_ * 2).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -106,7 +106,7 @@ class FlowIterableSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce elements with two transformation steps" in {
|
||||
val p = FlowFrom(List(1, 2, 3, 4)).filter(_ % 2 == 0).map(_ * 2).toPublisher()
|
||||
val p = Source(List(1, 2, 3, 4)).filter(_ % 2 == 0).map(_ * 2).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -118,7 +118,7 @@ class FlowIterableSpec extends AkkaSpec {
|
|||
|
||||
"allow cancel before receiving all elements" in {
|
||||
val count = 100000
|
||||
val p = FlowFrom(1 to count).toPublisher()
|
||||
val p = Source(1 to count).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -134,19 +134,19 @@ class FlowIterableSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"have value equality of publisher" in {
|
||||
val p1 = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
val p2 = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
val p1 = Source(List(1, 2, 3)).toPublisher()
|
||||
val p2 = Source(List(1, 2, 3)).toPublisher()
|
||||
p1 should be(p2)
|
||||
p2 should be(p1)
|
||||
val p3 = FlowFrom(List(1, 2, 3, 4)).toPublisher()
|
||||
val p3 = Source(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()
|
||||
val p4 = Source(Vector.empty[String]).toPublisher()
|
||||
val p5 = Source(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()
|
||||
val p6 = Source(List(1, 2, 3).iterator).toPublisher()
|
||||
p1 should not be (p6)
|
||||
p6 should not be (p1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class FlowIteratorSpec extends AkkaSpec {
|
|||
|
||||
"A Flow based on an iterator" must {
|
||||
"produce elements" in {
|
||||
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
|
||||
val p = Source(List(1, 2, 3).iterator).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -36,7 +36,7 @@ class FlowIteratorSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"complete empty" in {
|
||||
val p = FlowFrom(List.empty[Int].iterator).toPublisher()
|
||||
val p = Source(List.empty[Int].iterator).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
c.expectComplete()
|
||||
|
|
@ -48,7 +48,7 @@ class FlowIteratorSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce elements with multiple subscribers" in {
|
||||
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
|
||||
val p = Source(List(1, 2, 3).iterator).toPublisher()
|
||||
val c1 = StreamTestKit.SubscriberProbe[Int]()
|
||||
val c2 = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c1)
|
||||
|
|
@ -72,7 +72,7 @@ class FlowIteratorSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce elements to later subscriber" in {
|
||||
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
|
||||
val p = Source(List(1, 2, 3).iterator).toPublisher()
|
||||
val c1 = StreamTestKit.SubscriberProbe[Int]()
|
||||
val c2 = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c1)
|
||||
|
|
@ -95,7 +95,7 @@ class FlowIteratorSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce elements with one transformation step" in {
|
||||
val p = FlowFrom(List(1, 2, 3).iterator).map(_ * 2).toPublisher()
|
||||
val p = Source(List(1, 2, 3).iterator).map(_ * 2).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -107,7 +107,7 @@ class FlowIteratorSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce elements with two transformation steps" in {
|
||||
val p = FlowFrom(List(1, 2, 3, 4).iterator).filter(_ % 2 == 0).map(_ * 2).toPublisher()
|
||||
val p = Source(List(1, 2, 3, 4).iterator).filter(_ % 2 == 0).map(_ * 2).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -119,7 +119,7 @@ class FlowIteratorSpec extends AkkaSpec {
|
|||
|
||||
"allow cancel before receiving all elements" in {
|
||||
val count = 100000
|
||||
val p = FlowFrom((1 to count).iterator).toPublisher()
|
||||
val p = Source((1 to count).iterator).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
|
|||
"produce future elements" in {
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
implicit val ec = system.dispatcher
|
||||
val p = FlowFrom(1 to 3).mapAsync(n ⇒ Future(n)).publishTo(c)
|
||||
val p = Source(1 to 3).mapAsync(n ⇒ Future(n)).publishTo(c)
|
||||
val sub = c.expectSubscription()
|
||||
sub.request(2)
|
||||
c.expectNext(1)
|
||||
|
|
@ -37,7 +37,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
|
|||
"produce future elements in order" in {
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
implicit val ec = system.dispatcher
|
||||
val p = FlowFrom(1 to 50).mapAsync(n ⇒ Future {
|
||||
val p = Source(1 to 50).mapAsync(n ⇒ Future {
|
||||
Thread.sleep(ThreadLocalRandom.current().nextInt(1, 10))
|
||||
n
|
||||
}).publishTo(c)
|
||||
|
|
@ -51,7 +51,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
|
|||
val probe = TestProbe()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
implicit val ec = system.dispatcher
|
||||
val p = FlowFrom(1 to 20).mapAsync(n ⇒ Future {
|
||||
val p = Source(1 to 20).mapAsync(n ⇒ Future {
|
||||
probe.ref ! n
|
||||
n
|
||||
}).publishTo(c)
|
||||
|
|
@ -76,7 +76,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
|
|||
val latch = TestLatch(1)
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
implicit val ec = system.dispatcher
|
||||
val p = FlowFrom(1 to 5).mapAsync(n ⇒ Future {
|
||||
val p = Source(1 to 5).mapAsync(n ⇒ Future {
|
||||
if (n == 3) throw new RuntimeException("err1") with NoStackTrace
|
||||
else {
|
||||
Await.ready(latch, 10.seconds)
|
||||
|
|
@ -93,7 +93,7 @@ class FlowMapAsyncSpec extends AkkaSpec {
|
|||
val latch = TestLatch(1)
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
implicit val ec = system.dispatcher
|
||||
val p = FlowFrom(1 to 5).mapAsync(n ⇒
|
||||
val p = Source(1 to 5).mapAsync(n ⇒
|
||||
if (n == 3) throw new RuntimeException("err2") with NoStackTrace
|
||||
else {
|
||||
Future {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class FlowMapAsyncUnorderedSpec extends AkkaSpec {
|
|||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
implicit val ec = system.dispatcher
|
||||
val latch = (1 to 4).map(_ -> TestLatch(1)).toMap
|
||||
val p = FlowFrom(1 to 4).mapAsyncUnordered(n ⇒ Future {
|
||||
val p = Source(1 to 4).mapAsyncUnordered(n ⇒ Future {
|
||||
Await.ready(latch(n), 5.seconds)
|
||||
n
|
||||
}).publishTo(c)
|
||||
|
|
@ -44,7 +44,7 @@ class FlowMapAsyncUnorderedSpec extends AkkaSpec {
|
|||
val probe = TestProbe()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
implicit val ec = system.dispatcher
|
||||
val p = FlowFrom(1 to 20).mapAsyncUnordered(n ⇒ Future {
|
||||
val p = Source(1 to 20).mapAsyncUnordered(n ⇒ Future {
|
||||
probe.ref ! n
|
||||
n
|
||||
}).publishTo(c)
|
||||
|
|
@ -70,7 +70,7 @@ class FlowMapAsyncUnorderedSpec extends AkkaSpec {
|
|||
val latch = TestLatch(1)
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
implicit val ec = system.dispatcher
|
||||
val p = FlowFrom(1 to 5).mapAsyncUnordered(n ⇒ Future {
|
||||
val p = Source(1 to 5).mapAsyncUnordered(n ⇒ Future {
|
||||
if (n == 3) throw new RuntimeException("err1") with NoStackTrace
|
||||
else {
|
||||
Await.ready(latch, 10.seconds)
|
||||
|
|
@ -87,7 +87,7 @@ class FlowMapAsyncUnorderedSpec extends AkkaSpec {
|
|||
val latch = TestLatch(1)
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
implicit val ec = system.dispatcher
|
||||
val p = FlowFrom(1 to 5).mapAsync(n ⇒
|
||||
val p = Source(1 to 5).mapAsync(n ⇒
|
||||
if (n == 3) throw new RuntimeException("err2") with NoStackTrace
|
||||
else {
|
||||
Future {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class FlowMapSpec extends AkkaSpec with ScriptedTest {
|
|||
|
||||
"not blow up with high request counts" in {
|
||||
val probe = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(List(1).iterator).
|
||||
Source(List(1).iterator).
|
||||
map(_ + 1).map(_ + 1).map(_ + 1).map(_ + 1).map(_ + 1).
|
||||
toPublisher().subscribe(probe)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class FlowOnCompleteSpec extends AkkaSpec with ScriptedTest {
|
|||
"invoke callback on normal completion" in {
|
||||
val onCompleteProbe = TestProbe()
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
FlowFrom(p).withSink(OnCompleteSink(onCompleteProbe.ref ! _)).run()
|
||||
Source(p).connect(OnCompleteDrain[Int](onCompleteProbe.ref ! _)).run()
|
||||
val proc = p.expectSubscription
|
||||
proc.expectRequest()
|
||||
proc.sendNext(42)
|
||||
|
|
@ -39,7 +39,7 @@ class FlowOnCompleteSpec extends AkkaSpec with ScriptedTest {
|
|||
"yield the first error" in {
|
||||
val onCompleteProbe = TestProbe()
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
FlowFrom(p).withSink(OnCompleteSink(onCompleteProbe.ref ! _)).run()
|
||||
Source(p).connect(OnCompleteDrain[Int](onCompleteProbe.ref ! _)).run()
|
||||
val proc = p.expectSubscription
|
||||
proc.expectRequest()
|
||||
val ex = new RuntimeException("ex") with NoStackTrace
|
||||
|
|
@ -51,7 +51,7 @@ class FlowOnCompleteSpec extends AkkaSpec with ScriptedTest {
|
|||
"invoke callback for an empty stream" in {
|
||||
val onCompleteProbe = TestProbe()
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
FlowFrom(p).withSink(OnCompleteSink(onCompleteProbe.ref ! _)).run()
|
||||
Source(p).connect(OnCompleteDrain[Int](onCompleteProbe.ref ! _)).run()
|
||||
val proc = p.expectSubscription
|
||||
proc.expectRequest()
|
||||
proc.sendComplete()
|
||||
|
|
@ -63,14 +63,14 @@ class FlowOnCompleteSpec extends AkkaSpec with ScriptedTest {
|
|||
val onCompleteProbe = TestProbe()
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
import system.dispatcher // for the Future.onComplete
|
||||
val foreachSink = ForeachSink[Int] {
|
||||
val foreachDrain = ForeachDrain[Int] {
|
||||
x ⇒ onCompleteProbe.ref ! ("foreach-" + x)
|
||||
}
|
||||
val mf = FlowFrom(p).map { x ⇒
|
||||
val mf = Source(p).map { x ⇒
|
||||
onCompleteProbe.ref ! ("map-" + x)
|
||||
x
|
||||
}.withSink(foreachSink).run()
|
||||
foreachSink.future(mf) onComplete { onCompleteProbe.ref ! _ }
|
||||
}.connect(foreachDrain).run()
|
||||
foreachDrain.future(mf) onComplete { onCompleteProbe.ref ! _ }
|
||||
val proc = p.expectSubscription
|
||||
proc.expectRequest()
|
||||
proc.sendNext(42)
|
||||
|
|
|
|||
|
|
@ -25,12 +25,12 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
|
|||
|
||||
val testException = new Exception("test") with NoStackTrace
|
||||
|
||||
def newFutureSink = FutureSink[(immutable.Seq[Int], FlowWithSource[Int, Int])]
|
||||
def newFutureDrain = FutureDrain[(immutable.Seq[Int], Source[Int])]
|
||||
|
||||
"work on empty input" in {
|
||||
val futureSink = newFutureSink
|
||||
val mf = FlowFrom(Nil).prefixAndTail(10).withSink(futureSink).run()
|
||||
val fut = futureSink.future(mf)
|
||||
val futureDrain = newFutureDrain
|
||||
val mf = Source(Nil).prefixAndTail(10).connect(futureDrain).run()
|
||||
val fut = futureDrain.future(mf)
|
||||
val (prefix, tailFlow) = Await.result(fut, 3.seconds)
|
||||
prefix should be(Nil)
|
||||
val tailSubscriber = SubscriberProbe[Int]
|
||||
|
|
@ -39,9 +39,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"work on short input" in {
|
||||
val futureSink = newFutureSink
|
||||
val mf = FlowFrom(List(1, 2, 3)).prefixAndTail(10).withSink(futureSink).run()
|
||||
val fut = futureSink.future(mf)
|
||||
val futureDrain = newFutureDrain
|
||||
val mf = Source(List(1, 2, 3)).prefixAndTail(10).connect(futureDrain).run()
|
||||
val fut = futureDrain.future(mf)
|
||||
val (prefix, tailFlow) = Await.result(fut, 3.seconds)
|
||||
prefix should be(List(1, 2, 3))
|
||||
val tailSubscriber = SubscriberProbe[Int]
|
||||
|
|
@ -50,48 +50,48 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"work on longer inputs" in {
|
||||
val futureSink = newFutureSink
|
||||
val mf = FlowFrom((1 to 10).iterator).prefixAndTail(5).withSink(futureSink).run()
|
||||
val fut = futureSink.future(mf)
|
||||
val futureDrain = newFutureDrain
|
||||
val mf = Source((1 to 10).iterator).prefixAndTail(5).connect(futureDrain).run()
|
||||
val fut = futureDrain.future(mf)
|
||||
val (takes, tail) = Await.result(fut, 3.seconds)
|
||||
takes should be(1 to 5)
|
||||
|
||||
val futureSink2 = FutureSink[immutable.Seq[Int]]
|
||||
val mf2 = tail.grouped(6).withSink(futureSink2).run()
|
||||
val fut2 = futureSink2.future(mf2)
|
||||
val futureDrain2 = FutureDrain[immutable.Seq[Int]]
|
||||
val mf2 = tail.grouped(6).connect(futureDrain2).run()
|
||||
val fut2 = futureDrain2.future(mf2)
|
||||
Await.result(fut2, 3.seconds) should be(6 to 10)
|
||||
}
|
||||
|
||||
"handle zero take count" in {
|
||||
val futureSink = newFutureSink
|
||||
val mf = FlowFrom((1 to 10).iterator).prefixAndTail(0).withSink(futureSink).run()
|
||||
val fut = futureSink.future(mf)
|
||||
val futureDrain = newFutureDrain
|
||||
val mf = Source((1 to 10).iterator).prefixAndTail(0).connect(futureDrain).run()
|
||||
val fut = futureDrain.future(mf)
|
||||
val (takes, tail) = Await.result(fut, 3.seconds)
|
||||
takes should be(Nil)
|
||||
|
||||
val futureSink2 = FutureSink[immutable.Seq[Int]]
|
||||
val mf2 = tail.grouped(11).withSink(futureSink2).run()
|
||||
val fut2 = futureSink2.future(mf2)
|
||||
val futureDrain2 = FutureDrain[immutable.Seq[Int]]
|
||||
val mf2 = tail.grouped(11).connect(futureDrain2).run()
|
||||
val fut2 = futureDrain2.future(mf2)
|
||||
Await.result(fut2, 3.seconds) should be(1 to 10)
|
||||
}
|
||||
|
||||
"handle negative take count" in {
|
||||
val futureSink = newFutureSink
|
||||
val mf = FlowFrom((1 to 10).iterator).prefixAndTail(-1).withSink(futureSink).run()
|
||||
val fut = futureSink.future(mf)
|
||||
val futureDrain = newFutureDrain
|
||||
val mf = Source((1 to 10).iterator).prefixAndTail(-1).connect(futureDrain).run()
|
||||
val fut = futureDrain.future(mf)
|
||||
val (takes, tail) = Await.result(fut, 3.seconds)
|
||||
takes should be(Nil)
|
||||
|
||||
val futureSink2 = FutureSink[immutable.Seq[Int]]
|
||||
val mf2 = tail.grouped(11).withSink(futureSink2).run()
|
||||
val fut2 = futureSink2.future(mf2)
|
||||
val futureDrain2 = FutureDrain[immutable.Seq[Int]]
|
||||
val mf2 = tail.grouped(11).connect(futureDrain2).run()
|
||||
val fut2 = futureDrain2.future(mf2)
|
||||
Await.result(fut2, 3.seconds) should be(1 to 10)
|
||||
}
|
||||
|
||||
"work if size of take is equal to stream size" in {
|
||||
val futureSink = newFutureSink
|
||||
val mf = FlowFrom((1 to 10).iterator).prefixAndTail(10).withSink(futureSink).run()
|
||||
val fut = futureSink.future(mf)
|
||||
val futureDrain = newFutureDrain
|
||||
val mf = Source((1 to 10).iterator).prefixAndTail(10).connect(futureDrain).run()
|
||||
val fut = futureDrain.future(mf)
|
||||
val (takes, tail) = Await.result(fut, 3.seconds)
|
||||
takes should be(1 to 10)
|
||||
|
||||
|
|
@ -102,9 +102,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
|
|||
|
||||
"handle onError when no substream open" in {
|
||||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], FlowWithSource[Int, Int])]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], Source[Int])]()
|
||||
|
||||
FlowFrom(publisher).prefixAndTail(3).publishTo(subscriber)
|
||||
Source(publisher).prefixAndTail(3).publishTo(subscriber)
|
||||
|
||||
val upstream = publisher.expectSubscription()
|
||||
val downstream = subscriber.expectSubscription()
|
||||
|
|
@ -120,9 +120,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
|
|||
|
||||
"handle onError when substream is open" in {
|
||||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], FlowWithSource[Int, Int])]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], Source[Int])]()
|
||||
|
||||
FlowFrom(publisher).prefixAndTail(1).publishTo(subscriber)
|
||||
Source(publisher).prefixAndTail(1).publishTo(subscriber)
|
||||
|
||||
val upstream = publisher.expectSubscription()
|
||||
val downstream = subscriber.expectSubscription()
|
||||
|
|
@ -147,9 +147,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
|
|||
|
||||
"handle master stream cancellation" in {
|
||||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], FlowWithSource[Int, Int])]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], Source[Int])]()
|
||||
|
||||
FlowFrom(publisher).prefixAndTail(3).publishTo(subscriber)
|
||||
Source(publisher).prefixAndTail(3).publishTo(subscriber)
|
||||
|
||||
val upstream = publisher.expectSubscription()
|
||||
val downstream = subscriber.expectSubscription()
|
||||
|
|
@ -165,9 +165,9 @@ class FlowPrefixAndTailSpec extends AkkaSpec {
|
|||
|
||||
"handle substream cancellation" in {
|
||||
val publisher = StreamTestKit.PublisherProbe[Int]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], FlowWithSource[Int, Int])]()
|
||||
val subscriber = StreamTestKit.SubscriberProbe[(immutable.Seq[Int], Source[Int])]()
|
||||
|
||||
FlowFrom(publisher).prefixAndTail(1).publishTo(subscriber)
|
||||
Source(publisher).prefixAndTail(1).publishTo(subscriber)
|
||||
|
||||
val upstream = publisher.expectSubscription()
|
||||
val downstream = subscriber.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.stream.scaladsl2
|
||||
|
||||
import akka.stream.MaterializerSettings
|
||||
import akka.stream.testkit.AkkaSpec
|
||||
|
||||
class FlowPrependSpec extends AkkaSpec with River {
|
||||
|
||||
val settings = MaterializerSettings(system)
|
||||
implicit val materializer = FlowMaterializer(settings)
|
||||
|
||||
"ProcessorFlow" should {
|
||||
"prepend ProcessorFlow" in riverOf[String] { subscriber ⇒
|
||||
FlowFrom[String]
|
||||
.prepend(otherFlow)
|
||||
.withSource(IterableSource(elements))
|
||||
.publishTo(subscriber)
|
||||
}
|
||||
|
||||
"prepend FlowWithSource" in riverOf[String] { subscriber ⇒
|
||||
FlowFrom[String]
|
||||
.prepend(otherFlow.withSource(IterableSource(elements)))
|
||||
.publishTo(subscriber)
|
||||
}
|
||||
}
|
||||
|
||||
"FlowWithSink" should {
|
||||
"prepend ProcessorFlow" in riverOf[String] { subscriber ⇒
|
||||
FlowFrom[String]
|
||||
.withSink(SubscriberSink(subscriber))
|
||||
.prepend(otherFlow)
|
||||
.withSource(IterableSource(elements))
|
||||
.run()
|
||||
}
|
||||
|
||||
"prepend FlowWithSource" in riverOf[String] { subscriber ⇒
|
||||
FlowFrom[String]
|
||||
.withSink(SubscriberSink(subscriber))
|
||||
.prepend(otherFlow.withSource(IterableSource(elements)))
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -15,11 +15,11 @@ class FlowPublishToSubscriberSpec extends AkkaSpec {
|
|||
|
||||
implicit val materializer = FlowMaterializer(settings)
|
||||
|
||||
"A Flow with SubscriberSink" must {
|
||||
"A Flow with SubscriberDrain" must {
|
||||
|
||||
"publish elements to the subscriber" in {
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(List(1, 2, 3)).withSink(SubscriberSink(c)).run()
|
||||
Source(List(1, 2, 3)).connect(SubscriberDrain(c)).run()
|
||||
val s = c.expectSubscription()
|
||||
s.request(3)
|
||||
c.expectNext(1)
|
||||
|
|
|
|||
|
|
@ -79,19 +79,19 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
|
||||
implicit val mat = FlowMaterializer(settings)
|
||||
|
||||
val identity: ProcessorFlow[Any, Any] ⇒ ProcessorFlow[Any, Any] = in ⇒ in.map(e ⇒ e)
|
||||
val identity2: ProcessorFlow[Any, Any] ⇒ ProcessorFlow[Any, Any] = in ⇒ identity(in)
|
||||
val identity: Flow[Any, Any] ⇒ Flow[Any, Any] = in ⇒ in.map(e ⇒ e)
|
||||
val identity2: Flow[Any, Any] ⇒ Flow[Any, Any] = in ⇒ identity(in)
|
||||
|
||||
val toPublisher: (FlowWithSource[Any, Any], FlowMaterializer) ⇒ Publisher[Any] =
|
||||
val toPublisher: (Source[Any], FlowMaterializer) ⇒ Publisher[Any] =
|
||||
(f, m) ⇒ f.toPublisher()(m)
|
||||
def toFanoutPublisher[In, Out](initialBufferSize: Int, maximumBufferSize: Int): (FlowWithSource[In, Out], FlowMaterializer) ⇒ Publisher[Out] =
|
||||
def toFanoutPublisher[In, Out](initialBufferSize: Int, maximumBufferSize: Int): (Source[Out], FlowMaterializer) ⇒ Publisher[Out] =
|
||||
(f, m) ⇒ f.toFanoutPublisher(initialBufferSize, maximumBufferSize)(m)
|
||||
|
||||
def materializeIntoSubscriberAndPublisher[In, Out](processorFlow: ProcessorFlow[In, Out]): (Subscriber[In], Publisher[Out]) = {
|
||||
val source = SubscriberSource[In]
|
||||
val sink = PublisherSink[Out]
|
||||
val mf = processorFlow.withSource(source).withSink(sink).run()
|
||||
(source.subscriber(mf), sink.publisher(mf))
|
||||
def materializeIntoSubscriberAndPublisher[In, Out](flow: Flow[In, Out]): (Subscriber[In], Publisher[Out]) = {
|
||||
val tap = SubscriberTap[In]
|
||||
val drain = PublisherDrain[Out]
|
||||
val mf = tap.connect(flow).connect(drain).run()
|
||||
(tap.subscriber(mf), drain.publisher(mf))
|
||||
}
|
||||
|
||||
"A Flow" must {
|
||||
|
|
@ -168,14 +168,14 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
}
|
||||
|
||||
"materialize into Publisher/Subscriber" in {
|
||||
val processorFlow = FlowFrom[String]
|
||||
val (flowIn: Subscriber[String], flowOut: Publisher[String]) = materializeIntoSubscriberAndPublisher(processorFlow)
|
||||
val flow = Flow[String]
|
||||
val (flowIn: Subscriber[String], flowOut: Publisher[String]) = materializeIntoSubscriberAndPublisher(flow)
|
||||
|
||||
val c1 = StreamTestKit.SubscriberProbe[String]()
|
||||
flowOut.subscribe(c1)
|
||||
|
||||
val source: Publisher[String] = FlowFrom(List("1", "2", "3")).toPublisher()
|
||||
source.subscribe(flowIn)
|
||||
val tap: Publisher[String] = Source(List("1", "2", "3")).toPublisher()
|
||||
tap.subscribe(flowIn)
|
||||
|
||||
val sub1 = c1.expectSubscription
|
||||
sub1.request(3)
|
||||
|
|
@ -186,8 +186,8 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
}
|
||||
|
||||
"materialize into Publisher/Subscriber and transformation processor" in {
|
||||
val processorFlow = FlowFrom[Int].map((i: Int) ⇒ i.toString)
|
||||
val (flowIn: Subscriber[Int], flowOut: Publisher[String]) = materializeIntoSubscriberAndPublisher(processorFlow)
|
||||
val flow = Flow[Int].map((i: Int) ⇒ i.toString)
|
||||
val (flowIn: Subscriber[Int], flowOut: Publisher[String]) = materializeIntoSubscriberAndPublisher(flow)
|
||||
|
||||
val c1 = StreamTestKit.SubscriberProbe[String]()
|
||||
flowOut.subscribe(c1)
|
||||
|
|
@ -195,8 +195,8 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
sub1.request(3)
|
||||
c1.expectNoMsg(200.millis)
|
||||
|
||||
val source: Publisher[Int] = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
source.subscribe(flowIn)
|
||||
val tap: Publisher[Int] = Source(List(1, 2, 3)).toPublisher()
|
||||
tap.subscribe(flowIn)
|
||||
|
||||
c1.expectNext("1")
|
||||
c1.expectNext("2")
|
||||
|
|
@ -205,8 +205,8 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
}
|
||||
|
||||
"materialize into Publisher/Subscriber and multiple transformation processors" in {
|
||||
val processorFlow = FlowFrom[Int].map(_.toString).map("elem-" + _)
|
||||
val (flowIn, flowOut) = materializeIntoSubscriberAndPublisher(processorFlow)
|
||||
val flow = Flow[Int].map(_.toString).map("elem-" + _)
|
||||
val (flowIn, flowOut) = materializeIntoSubscriberAndPublisher(flow)
|
||||
|
||||
val c1 = StreamTestKit.SubscriberProbe[String]()
|
||||
flowOut.subscribe(c1)
|
||||
|
|
@ -214,8 +214,8 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
sub1.request(3)
|
||||
c1.expectNoMsg(200.millis)
|
||||
|
||||
val source: Publisher[Int] = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
source.subscribe(flowIn)
|
||||
val tap: Publisher[Int] = Source(List(1, 2, 3)).toPublisher()
|
||||
tap.subscribe(flowIn)
|
||||
|
||||
c1.expectNext("elem-1")
|
||||
c1.expectNext("elem-2")
|
||||
|
|
@ -224,11 +224,11 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
}
|
||||
|
||||
"subscribe Subscriber" in {
|
||||
val processorFlow: ProcessorFlow[String, String] = FlowFrom[String]
|
||||
val flow: Flow[String, String] = Flow[String]
|
||||
val c1 = StreamTestKit.SubscriberProbe[String]()
|
||||
val flow: FlowWithSink[String, String] = processorFlow.withSink(SubscriberSink(c1))
|
||||
val source: Publisher[String] = FlowFrom(List("1", "2", "3")).toPublisher()
|
||||
flow.withSource(PublisherSource(source)).run()
|
||||
val sink: Sink[String] = flow.connect(SubscriberDrain(c1))
|
||||
val publisher: Publisher[String] = Source(List("1", "2", "3")).toPublisher()
|
||||
Source(publisher).connect(sink).run()
|
||||
|
||||
val sub1 = c1.expectSubscription
|
||||
sub1.request(3)
|
||||
|
|
@ -239,10 +239,10 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
}
|
||||
|
||||
"perform transformation operation" in {
|
||||
val processorFlow = FlowFrom[Int].map(i ⇒ { testActor ! i.toString; i.toString })
|
||||
val flow = Flow[Int].map(i ⇒ { testActor ! i.toString; i.toString })
|
||||
|
||||
val source = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
processorFlow.withSource(PublisherSource(source)).consume()
|
||||
val publisher = Source(List(1, 2, 3)).toPublisher()
|
||||
Source(publisher).connect(flow).consume()
|
||||
|
||||
expectMsg("1")
|
||||
expectMsg("2")
|
||||
|
|
@ -250,11 +250,11 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
}
|
||||
|
||||
"perform transformation operation and subscribe Subscriber" in {
|
||||
val processorFlow = FlowFrom[Int].map(_.toString)
|
||||
val flow = Flow[Int].map(_.toString)
|
||||
val c1 = StreamTestKit.SubscriberProbe[String]()
|
||||
val flow: FlowWithSink[Int, String] = processorFlow.withSink(SubscriberSink(c1))
|
||||
val source: Publisher[Int] = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
flow.withSource(PublisherSource(source)).run()
|
||||
val sink: Sink[Int] = flow.connect(SubscriberDrain(c1))
|
||||
val publisher: Publisher[Int] = Source(List(1, 2, 3)).toPublisher()
|
||||
Source(publisher).connect(sink).run()
|
||||
|
||||
val sub1 = c1.expectSubscription
|
||||
sub1.request(3)
|
||||
|
|
@ -265,7 +265,7 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
}
|
||||
|
||||
"be materializable several times with fanout publisher" in {
|
||||
val flow = FlowFrom(List(1, 2, 3)).map(_.toString)
|
||||
val flow = Source(List(1, 2, 3)).map(_.toString)
|
||||
val p1 = flow.toFanoutPublisher(2, 2)
|
||||
val p2 = flow.toFanoutPublisher(2, 2)
|
||||
val s1 = StreamTestKit.SubscriberProbe[String]()
|
||||
|
|
@ -298,14 +298,14 @@ class FlowSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.debug.rece
|
|||
}
|
||||
|
||||
"be covariant" in {
|
||||
val f1: FlowWithSource[Fruit, Fruit] = FlowFrom[Fruit](() ⇒ Some(new Apple))
|
||||
val p1: Publisher[Fruit] = FlowFrom[Fruit](() ⇒ Some(new Apple)).toPublisher()
|
||||
val f2: FlowWithSource[Fruit, FlowWithSource[Fruit, Fruit]] = FlowFrom[Fruit](() ⇒ Some(new Apple)).splitWhen(_ ⇒ true)
|
||||
val f3: FlowWithSource[Fruit, (Boolean, FlowWithSource[Fruit, Fruit])] = FlowFrom[Fruit](() ⇒ Some(new Apple)).groupBy(_ ⇒ true)
|
||||
val f4: FlowWithSource[Fruit, (immutable.Seq[Fruit], FlowWithSource[Fruit, Fruit])] = FlowFrom[Fruit](() ⇒ Some(new Apple)).prefixAndTail(1)
|
||||
val d1: ProcessorFlow[String, FlowWithSource[Fruit, Fruit]] = FlowFrom[String].map(_ ⇒ new Apple).splitWhen(_ ⇒ true)
|
||||
val d2: ProcessorFlow[String, (Boolean, FlowWithSource[Fruit, Fruit])] = FlowFrom[String].map(_ ⇒ new Apple).groupBy(_ ⇒ true)
|
||||
val d3: ProcessorFlow[String, (immutable.Seq[Apple], FlowWithSource[Fruit, Fruit])] = FlowFrom[String].map(_ ⇒ new Apple).prefixAndTail(1)
|
||||
val f1: Source[Fruit] = Source[Fruit](() ⇒ Some(new Apple))
|
||||
val p1: Publisher[Fruit] = Source[Fruit](() ⇒ Some(new Apple)).toPublisher()
|
||||
val f2: Source[Source[Fruit]] = Source[Fruit](() ⇒ Some(new Apple)).splitWhen(_ ⇒ true)
|
||||
val f3: Source[(Boolean, Source[Fruit])] = Source[Fruit](() ⇒ Some(new Apple)).groupBy(_ ⇒ true)
|
||||
val f4: Source[(immutable.Seq[Fruit], Source[Fruit])] = Source[Fruit](() ⇒ Some(new Apple)).prefixAndTail(1)
|
||||
val d1: Flow[String, Source[Fruit]] = Flow[String].map(_ ⇒ new Apple).splitWhen(_ ⇒ true)
|
||||
val d2: Flow[String, (Boolean, Source[Fruit])] = Flow[String].map(_ ⇒ new Apple).groupBy(_ ⇒ true)
|
||||
val d3: Flow[String, (immutable.Seq[Apple], Source[Fruit])] = Flow[String].map(_ ⇒ new Apple).prefixAndTail(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,19 +31,19 @@ class FlowSplitWhenSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
class SubstreamsSupport(splitWhen: Int = 3, elementCount: Int = 6) {
|
||||
val source = FlowFrom((1 to elementCount).iterator)
|
||||
val groupStream = source.splitWhen(_ == splitWhen).toPublisher()
|
||||
val masterSubscriber = StreamTestKit.SubscriberProbe[FlowWithSource[Int, Int]]()
|
||||
val tap = Source((1 to elementCount).iterator)
|
||||
val groupStream = tap.splitWhen(_ == splitWhen).toPublisher()
|
||||
val masterSubscriber = StreamTestKit.SubscriberProbe[Source[Int]]()
|
||||
|
||||
groupStream.subscribe(masterSubscriber)
|
||||
val masterSubscription = masterSubscriber.expectSubscription()
|
||||
|
||||
def getSubFlow(): FlowWithSource[Int, Int] = {
|
||||
def getSubFlow(): Source[Int] = {
|
||||
masterSubscription.request(1)
|
||||
expectSubPublisher()
|
||||
}
|
||||
|
||||
def expectSubPublisher(): FlowWithSource[Int, Int] = {
|
||||
def expectSubPublisher(): Source[Int] = {
|
||||
val substream = masterSubscriber.expectNext()
|
||||
substream
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class FlowTakeSpec extends AkkaSpec with ScriptedTest {
|
|||
|
||||
"not take anything for negative n" in {
|
||||
val probe = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(List(1, 2, 3)).take(-1).publishTo(probe)
|
||||
Source(List(1, 2, 3)).take(-1).publishTo(probe)
|
||||
probe.expectSubscription().request(10)
|
||||
probe.expectComplete()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class FlowTakeWithinSpec extends AkkaSpec {
|
|||
val input = Iterator.from(1)
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(p).takeWithin(1.second).publishTo(c)
|
||||
Source(p).takeWithin(1.second).publishTo(c)
|
||||
val pSub = p.expectSubscription()
|
||||
val cSub = c.expectSubscription()
|
||||
cSub.request(100)
|
||||
|
|
@ -38,7 +38,7 @@ class FlowTakeWithinSpec extends AkkaSpec {
|
|||
|
||||
"deliver bufferd elements onComplete before the timeout" in {
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(1 to 3).takeWithin(1.second).publishTo(c)
|
||||
Source(1 to 3).takeWithin(1.second).publishTo(c)
|
||||
val cSub = c.expectSubscription()
|
||||
c.expectNoMsg(200.millis)
|
||||
cSub.request(100)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class FlowThunkSpec extends AkkaSpec {
|
|||
"produce elements" in {
|
||||
|
||||
val iter = List(1, 2, 3).iterator
|
||||
val p = FlowFrom(() ⇒ if (iter.hasNext) Some(iter.next()) else None).map(_ + 10).toPublisher()
|
||||
val p = Source(() ⇒ if (iter.hasNext) Some(iter.next()) else None).map(_ + 10).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -32,7 +32,7 @@ class FlowThunkSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"complete empty" in {
|
||||
val p = FlowFrom(() ⇒ None).toPublisher()
|
||||
val p = Source(() ⇒ None).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
@ -44,7 +44,7 @@ class FlowThunkSpec extends AkkaSpec {
|
|||
"allow cancel before receiving all elements" in {
|
||||
val count = 100000
|
||||
val iter = (1 to count).iterator
|
||||
val p = FlowFrom(() ⇒ if (iter.hasNext) Some(iter.next()) else None).toPublisher()
|
||||
val p = Source(() ⇒ if (iter.hasNext) Some(iter.next()) else None).toPublisher()
|
||||
val c = StreamTestKit.SubscriberProbe[Int]()
|
||||
p.subscribe(c)
|
||||
val sub = c.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class FlowTimerTransformerSpec extends AkkaSpec {
|
|||
"A Flow with TimerTransformer operations" must {
|
||||
"produce scheduled ticks as expected" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val p2 = FlowFrom(p).
|
||||
val p2 = Source(p).
|
||||
timerTransform("timer", () ⇒ new TimerTransformer[Int, Int] {
|
||||
schedulePeriodically("tick", 100.millis)
|
||||
var tickCount = 0
|
||||
|
|
@ -41,7 +41,7 @@ class FlowTimerTransformerSpec extends AkkaSpec {
|
|||
|
||||
"schedule ticks when last transformation step (consume)" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val p2 = FlowFrom(p).
|
||||
val p2 = Source(p).
|
||||
timerTransform("timer", () ⇒ new TimerTransformer[Int, Int] {
|
||||
schedulePeriodically("tick", 100.millis)
|
||||
var tickCount = 0
|
||||
|
|
@ -65,7 +65,7 @@ class FlowTimerTransformerSpec extends AkkaSpec {
|
|||
"propagate error if onTimer throws an exception" in {
|
||||
val exception = new Exception("Expected exception to the rule") with NoStackTrace
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val p2 = FlowFrom(p).
|
||||
val p2 = Source(p).
|
||||
timerTransform("timer", () ⇒ new TimerTransformer[Int, Int] {
|
||||
scheduleOnce("tick", 100.millis)
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ class FlowToFutureSpec extends AkkaSpec with ScriptedTest {
|
|||
|
||||
"yield the first value" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val f = FutureSink[Int]
|
||||
val m = FlowFrom(p).withSink(f).run()
|
||||
val f = FutureDrain[Int]
|
||||
val m = Source(p).connect(f).run()
|
||||
val proc = p.expectSubscription
|
||||
proc.expectRequest()
|
||||
proc.sendNext(42)
|
||||
|
|
@ -34,9 +34,9 @@ class FlowToFutureSpec extends AkkaSpec with ScriptedTest {
|
|||
|
||||
"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()
|
||||
val f = FutureDrain[Int]
|
||||
val s = SubscriberTap[Int]
|
||||
val m = s.connect(f).run()
|
||||
p.subscribe(s.subscriber(m))
|
||||
val proc = p.expectSubscription
|
||||
proc.expectRequest()
|
||||
|
|
@ -47,8 +47,8 @@ class FlowToFutureSpec extends AkkaSpec with ScriptedTest {
|
|||
|
||||
"yield the first error" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val f = FutureSink[Int]
|
||||
val m = FlowFrom(p).withSink(f).run()
|
||||
val f = FutureDrain[Int]
|
||||
val m = Source(p).connect(f).run()
|
||||
val proc = p.expectSubscription
|
||||
proc.expectRequest()
|
||||
val ex = new RuntimeException("ex")
|
||||
|
|
@ -60,8 +60,8 @@ class FlowToFutureSpec extends AkkaSpec with ScriptedTest {
|
|||
|
||||
"yield NoSuchElementExcption for empty stream" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val f = FutureSink[Int]
|
||||
val m = FlowFrom(p).withSink(f).run()
|
||||
val f = FutureDrain[Int]
|
||||
val m = Source(p).connect(f).run()
|
||||
val proc = p.expectSubscription
|
||||
proc.expectRequest()
|
||||
proc.sendComplete()
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
|
||||
"A Flow with transformRecover operations" must {
|
||||
"produce one-to-one transformation as expected" in {
|
||||
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3).iterator).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
var tot = 0
|
||||
override def onNext(elem: Int) = {
|
||||
|
|
@ -68,8 +68,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce one-to-several transformation as expected" in {
|
||||
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3).iterator).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
var tot = 0
|
||||
override def onNext(elem: Int) = {
|
||||
|
|
@ -99,8 +99,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce dropping transformation as expected" in {
|
||||
val p = FlowFrom(List(1, 2, 3, 4).iterator).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3, 4).iterator).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
var tot = 0
|
||||
override def onNext(elem: Int) = {
|
||||
|
|
@ -127,8 +127,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"produce multi-step transformation as expected" in {
|
||||
val p = FlowFrom(List("a", "bc", "def").iterator).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List("a", "bc", "def").iterator).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new TryRecoveryTransformer[String, Int] {
|
||||
var concat = ""
|
||||
override def onNext(element: Try[String]) = {
|
||||
|
|
@ -170,8 +170,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"invoke onComplete when done" in {
|
||||
val p = FlowFrom(List("a").iterator).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List("a").iterator).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new TryRecoveryTransformer[String, String] {
|
||||
var s = ""
|
||||
override def onNext(element: Try[String]) = {
|
||||
|
|
@ -191,7 +191,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
|
||||
"allow cancellation using isComplete" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val p2 = FlowFrom(p).
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new TryRecoveryTransformer[Int, Int] {
|
||||
var s = ""
|
||||
override def onNext(element: Try[Int]) = {
|
||||
|
|
@ -215,7 +215,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
|
||||
"call onComplete after isComplete signaled completion" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val p2 = FlowFrom(p).
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new TryRecoveryTransformer[Int, Int] {
|
||||
var s = ""
|
||||
override def onNext(element: Try[Int]) = {
|
||||
|
|
@ -240,8 +240,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"report error when exception is thrown" in {
|
||||
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3).iterator).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
override def onNext(elem: Int) = {
|
||||
if (elem == 2) throw new IllegalArgumentException("two not allowed")
|
||||
|
|
@ -266,7 +266,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
|
||||
"report error after emitted elements" in {
|
||||
EventFilter[IllegalArgumentException]("two not allowed") intercept {
|
||||
val p2 = FlowFrom(List(1, 2, 3).iterator).
|
||||
val p2 = Source(List(1, 2, 3).iterator).
|
||||
mapConcat { elem ⇒
|
||||
if (elem == 2) throw new IllegalArgumentException("two not allowed")
|
||||
else (1 to 5).map(elem * 100 + _)
|
||||
|
|
@ -315,7 +315,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
|
||||
"transform errors in sequence with normal messages" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val p2 = FlowFrom(p).
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, String] {
|
||||
var s = ""
|
||||
override def onNext(element: Int) = {
|
||||
|
|
@ -348,7 +348,7 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
|
||||
"forward errors when received and thrown" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val p2 = FlowFrom(p).
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
override def onNext(in: Int) = List(in)
|
||||
override def onError(e: Throwable) = throw e
|
||||
|
|
@ -366,8 +366,8 @@ class FlowTransformRecoverSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"support cancel as expected" in {
|
||||
val p = FlowFrom(List(1, 2, 3).iterator).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3).iterator).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
override def onNext(elem: Int) = List(elem, elem)
|
||||
override def onError(e: Throwable) = List(-1)
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ 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)).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3)).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
var tot = 0
|
||||
override def onNext(elem: Int) = {
|
||||
|
|
@ -46,8 +46,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
}
|
||||
|
||||
"produce one-to-several transformation as expected" in {
|
||||
val p = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3)).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
var tot = 0
|
||||
override def onNext(elem: Int) = {
|
||||
|
|
@ -72,8 +72,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
}
|
||||
|
||||
"produce dropping transformation as expected" in {
|
||||
val p = FlowFrom(List(1, 2, 3, 4)).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3, 4)).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
var tot = 0
|
||||
override def onNext(elem: Int) = {
|
||||
|
|
@ -99,8 +99,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
}
|
||||
|
||||
"produce multi-step transformation as expected" in {
|
||||
val p = FlowFrom(List("a", "bc", "def")).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List("a", "bc", "def")).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[String, Int] {
|
||||
var concat = ""
|
||||
override def onNext(elem: String) = {
|
||||
|
|
@ -138,8 +138,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
}
|
||||
|
||||
"invoke onComplete when done" in {
|
||||
val p = FlowFrom(List("a")).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List("a")).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[String, String] {
|
||||
var s = ""
|
||||
override def onNext(element: String) = {
|
||||
|
|
@ -159,8 +159,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
|
||||
"invoke cleanup when done" in {
|
||||
val cleanupProbe = TestProbe()
|
||||
val p = FlowFrom(List("a")).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List("a")).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[String, String] {
|
||||
var s = ""
|
||||
override def onNext(element: String) = {
|
||||
|
|
@ -182,8 +182,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
|
||||
"invoke cleanup when done consume" in {
|
||||
val cleanupProbe = TestProbe()
|
||||
val p = FlowFrom(List("a")).toPublisher()
|
||||
FlowFrom(p).
|
||||
val p = Source(List("a")).toPublisher()
|
||||
Source(p).
|
||||
transform("transform", () ⇒ new Transformer[String, String] {
|
||||
var s = "x"
|
||||
override def onNext(element: String) = {
|
||||
|
|
@ -192,14 +192,14 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
}
|
||||
override def cleanup() = cleanupProbe.ref ! s
|
||||
}).
|
||||
withSink(BlackholeSink).run()
|
||||
connect(BlackholeDrain).run()
|
||||
cleanupProbe.expectMsg("a")
|
||||
}
|
||||
|
||||
"invoke cleanup when done after error" in {
|
||||
val cleanupProbe = TestProbe()
|
||||
val p = FlowFrom(List("a", "b", "c")).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List("a", "b", "c")).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[String, String] {
|
||||
var s = ""
|
||||
override def onNext(in: String) = {
|
||||
|
|
@ -227,7 +227,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
|
||||
"allow cancellation using isComplete" in {
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val p2 = FlowFrom(p).
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
var s = ""
|
||||
override def onNext(element: Int) = {
|
||||
|
|
@ -252,7 +252,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
"call onComplete after isComplete signaled completion" in {
|
||||
val cleanupProbe = TestProbe()
|
||||
val p = StreamTestKit.PublisherProbe[Int]()
|
||||
val p2 = FlowFrom(p).
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
var s = ""
|
||||
override def onNext(element: Int) = {
|
||||
|
|
@ -279,8 +279,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
}
|
||||
|
||||
"report error when exception is thrown" in {
|
||||
val p = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3)).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
override def onNext(elem: Int) = {
|
||||
if (elem == 2) {
|
||||
|
|
@ -304,8 +304,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
}
|
||||
|
||||
"support cancel as expected" in {
|
||||
val p = FlowFrom(List(1, 2, 3)).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List(1, 2, 3)).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
override def onNext(elem: Int) = List(elem, elem)
|
||||
}).
|
||||
|
|
@ -323,8 +323,8 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
}
|
||||
|
||||
"support producing elements from empty inputs" in {
|
||||
val p = FlowFrom(List.empty[Int]).toPublisher()
|
||||
val p2 = FlowFrom(p).
|
||||
val p = Source(List.empty[Int]).toPublisher()
|
||||
val p2 = Source(p).
|
||||
transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
override def onNext(elem: Int) = Nil
|
||||
override def onTermination(e: Option[Throwable]) = List(1, 2, 3)
|
||||
|
|
@ -343,7 +343,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
|
||||
"support converting onComplete into onError" in {
|
||||
val subscriber = StreamTestKit.SubscriberProbe[Int]()
|
||||
FlowFrom(List(5, 1, 2, 3)).transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
Source(List(5, 1, 2, 3)).transform("transform", () ⇒ new Transformer[Int, Int] {
|
||||
var expectedNumberOfElements: Option[Int] = None
|
||||
var count = 0
|
||||
override def onNext(elem: Int) =
|
||||
|
|
@ -375,7 +375,7 @@ class FlowTransformSpec extends AkkaSpec(ConfigFactory.parseString("akka.actor.d
|
|||
}
|
||||
|
||||
"be safe to reuse" in {
|
||||
val flow = FlowFrom(1 to 3).transform("transform", () ⇒
|
||||
val flow = Source(1 to 3).transform("transform", () ⇒
|
||||
new Transformer[Int, Int] {
|
||||
var count = 0
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ class GraphBroadcastSpec extends AkkaSpec {
|
|||
|
||||
FlowGraph { implicit b ⇒
|
||||
val bcast = Broadcast[Int]("broadcast")
|
||||
FlowFrom(List(1, 2, 3)) ~> bcast
|
||||
bcast ~> FlowFrom[Int].buffer(16, OverflowStrategy.backpressure) ~> SubscriberSink(c1)
|
||||
bcast ~> FlowFrom[Int].buffer(16, OverflowStrategy.backpressure) ~> SubscriberSink(c2)
|
||||
Source(List(1, 2, 3)) ~> bcast
|
||||
bcast ~> Flow[Int].buffer(16, OverflowStrategy.backpressure) ~> SubscriberDrain(c1)
|
||||
bcast ~> Flow[Int].buffer(16, OverflowStrategy.backpressure) ~> SubscriberDrain(c2)
|
||||
}.run()
|
||||
|
||||
val sub1 = c1.expectSubscription()
|
||||
|
|
@ -46,27 +46,27 @@ class GraphBroadcastSpec extends AkkaSpec {
|
|||
}
|
||||
|
||||
"work with n-way broadcast" in {
|
||||
val f1 = FutureSink[Seq[Int]]
|
||||
val f2 = FutureSink[Seq[Int]]
|
||||
val f3 = FutureSink[Seq[Int]]
|
||||
val f4 = FutureSink[Seq[Int]]
|
||||
val f5 = FutureSink[Seq[Int]]
|
||||
val f1 = FutureDrain[Seq[Int]]
|
||||
val f2 = FutureDrain[Seq[Int]]
|
||||
val f3 = FutureDrain[Seq[Int]]
|
||||
val f4 = FutureDrain[Seq[Int]]
|
||||
val f5 = FutureDrain[Seq[Int]]
|
||||
|
||||
val g = FlowGraph { implicit b ⇒
|
||||
val bcast = Broadcast[Int]("broadcast")
|
||||
FlowFrom(List(1, 2, 3)) ~> bcast
|
||||
bcast ~> FlowFrom[Int].grouped(5) ~> f1
|
||||
bcast ~> FlowFrom[Int].grouped(5) ~> f2
|
||||
bcast ~> FlowFrom[Int].grouped(5) ~> f3
|
||||
bcast ~> FlowFrom[Int].grouped(5) ~> f4
|
||||
bcast ~> FlowFrom[Int].grouped(5) ~> f5
|
||||
Source(List(1, 2, 3)) ~> bcast
|
||||
bcast ~> Flow[Int].grouped(5) ~> f1
|
||||
bcast ~> Flow[Int].grouped(5) ~> f2
|
||||
bcast ~> Flow[Int].grouped(5) ~> f3
|
||||
bcast ~> Flow[Int].grouped(5) ~> f4
|
||||
bcast ~> Flow[Int].grouped(5) ~> f5
|
||||
}.run()
|
||||
|
||||
Await.result(g.getSinkFor(f1), 3.seconds) should be(List(1, 2, 3))
|
||||
Await.result(g.getSinkFor(f2), 3.seconds) should be(List(1, 2, 3))
|
||||
Await.result(g.getSinkFor(f3), 3.seconds) should be(List(1, 2, 3))
|
||||
Await.result(g.getSinkFor(f4), 3.seconds) should be(List(1, 2, 3))
|
||||
Await.result(g.getSinkFor(f5), 3.seconds) should be(List(1, 2, 3))
|
||||
Await.result(g.getDrainFor(f1), 3.seconds) should be(List(1, 2, 3))
|
||||
Await.result(g.getDrainFor(f2), 3.seconds) should be(List(1, 2, 3))
|
||||
Await.result(g.getDrainFor(f3), 3.seconds) should be(List(1, 2, 3))
|
||||
Await.result(g.getDrainFor(f4), 3.seconds) should be(List(1, 2, 3))
|
||||
Await.result(g.getDrainFor(f5), 3.seconds) should be(List(1, 2, 3))
|
||||
}
|
||||
|
||||
"produce to other even though downstream cancels" in {
|
||||
|
|
@ -75,9 +75,9 @@ class GraphBroadcastSpec extends AkkaSpec {
|
|||
|
||||
FlowGraph { implicit b ⇒
|
||||
val bcast = Broadcast[Int]("broadcast")
|
||||
FlowFrom(List(1, 2, 3)) ~> bcast
|
||||
bcast ~> FlowFrom[Int] ~> SubscriberSink(c1)
|
||||
bcast ~> FlowFrom[Int] ~> SubscriberSink(c2)
|
||||
Source(List(1, 2, 3)) ~> bcast
|
||||
bcast ~> Flow[Int] ~> SubscriberDrain(c1)
|
||||
bcast ~> Flow[Int] ~> SubscriberDrain(c2)
|
||||
}.run()
|
||||
|
||||
val sub1 = c1.expectSubscription()
|
||||
|
|
@ -96,9 +96,9 @@ class GraphBroadcastSpec extends AkkaSpec {
|
|||
|
||||
FlowGraph { implicit b ⇒
|
||||
val bcast = Broadcast[Int]("broadcast")
|
||||
FlowFrom(List(1, 2, 3)) ~> bcast
|
||||
bcast ~> FlowFrom[Int] ~> SubscriberSink(c1)
|
||||
bcast ~> FlowFrom[Int] ~> SubscriberSink(c2)
|
||||
Source(List(1, 2, 3)) ~> bcast
|
||||
bcast ~> Flow[Int] ~> SubscriberDrain(c1)
|
||||
bcast ~> Flow[Int] ~> SubscriberDrain(c2)
|
||||
}.run()
|
||||
|
||||
val sub1 = c1.expectSubscription()
|
||||
|
|
@ -118,9 +118,9 @@ class GraphBroadcastSpec extends AkkaSpec {
|
|||
|
||||
FlowGraph { implicit b ⇒
|
||||
val bcast = Broadcast[Int]("broadcast")
|
||||
FlowFrom(p1.getPublisher) ~> bcast
|
||||
bcast ~> FlowFrom[Int] ~> SubscriberSink(c1)
|
||||
bcast ~> FlowFrom[Int] ~> SubscriberSink(c2)
|
||||
Source(p1.getPublisher) ~> bcast
|
||||
bcast ~> Flow[Int] ~> SubscriberDrain(c1)
|
||||
bcast ~> Flow[Int] ~> SubscriberDrain(c2)
|
||||
}.run()
|
||||
|
||||
val bsub = p1.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ class GraphConcatSpec extends TwoStreamsSetup {
|
|||
val concat1 = Concat[Int]("concat1")
|
||||
val concat2 = Concat[Int]("concat2")
|
||||
|
||||
FlowFrom(List.empty[Int].iterator) ~> concat1.first
|
||||
FlowFrom((1 to 4).iterator) ~> concat1.second
|
||||
Source(List.empty[Int].iterator) ~> concat1.first
|
||||
Source((1 to 4).iterator) ~> concat1.second
|
||||
|
||||
concat1.out ~> concat2.first
|
||||
FlowFrom((5 to 10).iterator) ~> concat2.second
|
||||
Source((5 to 10).iterator) ~> concat2.second
|
||||
|
||||
concat2.out ~> SubscriberSink(probe)
|
||||
concat2.out ~> SubscriberDrain(probe)
|
||||
}.run()
|
||||
|
||||
val subscription = probe.expectSubscription()
|
||||
|
|
@ -109,9 +109,9 @@ class GraphConcatSpec extends TwoStreamsSetup {
|
|||
|
||||
FlowGraph { implicit b ⇒
|
||||
val concat = Concat[Int]
|
||||
FlowFrom(List(1, 2, 3)) ~> concat.first
|
||||
FlowFrom(promise.future) ~> concat.second
|
||||
concat.out ~> SubscriberSink(subscriber)
|
||||
Source(List(1, 2, 3)) ~> concat.first
|
||||
Source(promise.future) ~> concat.second
|
||||
concat.out ~> SubscriberDrain(subscriber)
|
||||
}.run()
|
||||
|
||||
val subscription = subscriber.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ class GraphMergeSpec extends TwoStreamsSetup {
|
|||
|
||||
"work in the happy case" in {
|
||||
// Different input sizes (4 and 6)
|
||||
val source1 = FlowFrom((0 to 3).iterator)
|
||||
val source2 = FlowFrom((4 to 9).iterator)
|
||||
val source3 = FlowFrom(List.empty[Int].iterator)
|
||||
val tap1 = Source((0 to 3).iterator)
|
||||
val tap2 = Source((4 to 9).iterator)
|
||||
val tap3 = Source(List.empty[Int].iterator)
|
||||
val probe = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowGraph { implicit b ⇒
|
||||
|
|
@ -29,9 +29,9 @@ class GraphMergeSpec extends TwoStreamsSetup {
|
|||
val m2 = Merge[Int]("m2")
|
||||
val m3 = Merge[Int]("m3")
|
||||
|
||||
source1 ~> m1 ~> FlowFrom[Int].map(_ * 2) ~> m2 ~> FlowFrom[Int].map(_ / 2).map(_ + 1) ~> SubscriberSink(probe)
|
||||
source2 ~> m1
|
||||
source3 ~> m2
|
||||
tap1 ~> m1 ~> Flow[Int].map(_ * 2) ~> m2 ~> Flow[Int].map(_ / 2).map(_ + 1) ~> SubscriberDrain(probe)
|
||||
tap2 ~> m1
|
||||
tap3 ~> m2
|
||||
|
||||
}.run()
|
||||
|
||||
|
|
@ -48,24 +48,24 @@ class GraphMergeSpec extends TwoStreamsSetup {
|
|||
}
|
||||
|
||||
"work with n-way merge" in {
|
||||
val source1 = FlowFrom(List(1))
|
||||
val source2 = FlowFrom(List(2))
|
||||
val source3 = FlowFrom(List(3))
|
||||
val source4 = FlowFrom(List(4))
|
||||
val source5 = FlowFrom(List(5))
|
||||
val source6 = FlowFrom(List.empty[Int])
|
||||
val tap1 = Source(List(1))
|
||||
val tap2 = Source(List(2))
|
||||
val tap3 = Source(List(3))
|
||||
val tap4 = Source(List(4))
|
||||
val tap5 = Source(List(5))
|
||||
val tap6 = Source(List.empty[Int])
|
||||
|
||||
val probe = StreamTestKit.SubscriberProbe[Int]()
|
||||
|
||||
FlowGraph { implicit b ⇒
|
||||
val merge = Merge[Int]("merge")
|
||||
|
||||
source1 ~> merge ~> FlowFrom[Int] ~> SubscriberSink(probe)
|
||||
source2 ~> merge
|
||||
source3 ~> merge
|
||||
source4 ~> merge
|
||||
source5 ~> merge
|
||||
source6 ~> merge
|
||||
tap1 ~> merge ~> Flow[Int] ~> SubscriberDrain(probe)
|
||||
tap2 ~> merge
|
||||
tap3 ~> merge
|
||||
tap4 ~> merge
|
||||
tap5 ~> merge
|
||||
tap6 ~> merge
|
||||
|
||||
}.run()
|
||||
|
||||
|
|
|
|||
|
|
@ -19,26 +19,26 @@ class GraphOpsIntegrationSpec extends AkkaSpec {
|
|||
"FlowGraphs" must {
|
||||
|
||||
"support broadcast - merge layouts" in {
|
||||
val resultFuture = FutureSink[Seq[Int]]
|
||||
val resultFuture = FutureDrain[Seq[Int]]
|
||||
|
||||
val g = FlowGraph { implicit b ⇒
|
||||
val bcast = Broadcast[Int]("broadcast")
|
||||
val merge = Merge[Int]("merge")
|
||||
|
||||
FlowFrom(List(1, 2, 3)) ~> bcast
|
||||
Source(List(1, 2, 3)) ~> bcast
|
||||
bcast ~> merge
|
||||
bcast ~> FlowFrom[Int].map(_ + 3) ~> merge
|
||||
merge ~> FlowFrom[Int].grouped(10) ~> resultFuture
|
||||
bcast ~> Flow[Int].map(_ + 3) ~> merge
|
||||
merge ~> Flow[Int].grouped(10) ~> resultFuture
|
||||
}.run()
|
||||
|
||||
Await.result(g.getSinkFor(resultFuture), 3.seconds).sorted should be(List(1, 2, 3, 4, 5, 6))
|
||||
Await.result(g.getDrainFor(resultFuture), 3.seconds).sorted should be(List(1, 2, 3, 4, 5, 6))
|
||||
}
|
||||
|
||||
"support wikipedia Topological_sorting 2" in {
|
||||
// see https://en.wikipedia.org/wiki/Topological_sorting#mediaviewer/File:Directed_acyclic_graph.png
|
||||
val resultFuture2 = FutureSink[Seq[Int]]
|
||||
val resultFuture9 = FutureSink[Seq[Int]]
|
||||
val resultFuture10 = FutureSink[Seq[Int]]
|
||||
val resultFuture2 = FutureDrain[Seq[Int]]
|
||||
val resultFuture9 = FutureDrain[Seq[Int]]
|
||||
val resultFuture10 = FutureDrain[Seq[Int]]
|
||||
|
||||
val g = FlowGraph { implicit b ⇒
|
||||
val b3 = Broadcast[Int]("b3")
|
||||
|
|
@ -48,9 +48,9 @@ class GraphOpsIntegrationSpec extends AkkaSpec {
|
|||
val m9 = Merge[Int]("m9")
|
||||
val m10 = Merge[Int]("m10")
|
||||
val m11 = Merge[Int]("m11")
|
||||
val in3 = IterableSource(List(3))
|
||||
val in5 = IterableSource(List(5))
|
||||
val in7 = IterableSource(List(7))
|
||||
val in3 = IterableTap(List(3))
|
||||
val in5 = IterableTap(List(5))
|
||||
val in7 = IterableTap(List(7))
|
||||
|
||||
// First layer
|
||||
in7 ~> b7
|
||||
|
|
@ -65,24 +65,39 @@ class GraphOpsIntegrationSpec extends AkkaSpec {
|
|||
|
||||
// Second layer
|
||||
m11 ~> b11
|
||||
b11 ~> FlowFrom[Int].grouped(1000) ~> resultFuture2 // Vertex 2 is omitted since it has only one in and out
|
||||
b11 ~> Flow[Int].grouped(1000) ~> resultFuture2 // Vertex 2 is omitted since it has only one in and out
|
||||
b11 ~> m9
|
||||
b11 ~> m10
|
||||
|
||||
m8 ~> m9
|
||||
|
||||
// Third layer
|
||||
m9 ~> FlowFrom[Int].grouped(1000) ~> resultFuture9
|
||||
m10 ~> FlowFrom[Int].grouped(1000) ~> resultFuture10
|
||||
m9 ~> Flow[Int].grouped(1000) ~> resultFuture9
|
||||
m10 ~> Flow[Int].grouped(1000) ~> resultFuture10
|
||||
|
||||
}.run()
|
||||
|
||||
Await.result(g.getSinkFor(resultFuture2), 3.seconds).sorted should be(List(5, 7))
|
||||
Await.result(g.getSinkFor(resultFuture9), 3.seconds).sorted should be(List(3, 5, 7, 7))
|
||||
Await.result(g.getSinkFor(resultFuture10), 3.seconds).sorted should be(List(3, 5, 7))
|
||||
Await.result(g.getDrainFor(resultFuture2), 3.seconds).sorted should be(List(5, 7))
|
||||
Await.result(g.getDrainFor(resultFuture9), 3.seconds).sorted should be(List(3, 5, 7, 7))
|
||||
Await.result(g.getDrainFor(resultFuture10), 3.seconds).sorted should be(List(3, 5, 7))
|
||||
|
||||
}
|
||||
|
||||
"allow adding of flows to sources and sinks to flows" in {
|
||||
val resultFuture = FutureDrain[Seq[Int]]
|
||||
|
||||
val g = FlowGraph { implicit b ⇒
|
||||
val bcast = Broadcast[Int]("broadcast")
|
||||
val merge = Merge[Int]("merge")
|
||||
|
||||
Source(List(1, 2, 3)) ~> Flow[Int].map(_ * 2) ~> bcast
|
||||
bcast ~> merge
|
||||
bcast ~> Flow[Int].map(_ + 3) ~> merge
|
||||
merge ~> Flow[Int].grouped(10).connect(resultFuture)
|
||||
}.run()
|
||||
|
||||
Await.result(g.getDrainFor(resultFuture), 3.seconds) should contain theSameElementsAs (Seq(2, 4, 6, 5, 7, 9))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ class GraphUnzipSpec extends AkkaSpec {
|
|||
|
||||
FlowGraph { implicit b ⇒
|
||||
val unzip = Unzip[Int, String]("unzip")
|
||||
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.right ~> FlowFrom[String].buffer(16, OverflowStrategy.backpressure) ~> SubscriberSink(c2)
|
||||
unzip.left ~> FlowFrom[Int].buffer(16, OverflowStrategy.backpressure).map(_ * 2) ~> SubscriberSink(c1)
|
||||
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.right ~> Flow[String].buffer(16, OverflowStrategy.backpressure) ~> SubscriberDrain(c2)
|
||||
unzip.left ~> Flow[Int].buffer(16, OverflowStrategy.backpressure).map(_ * 2) ~> SubscriberDrain(c1)
|
||||
}.run()
|
||||
|
||||
val sub1 = c1.expectSubscription()
|
||||
|
|
@ -51,9 +51,9 @@ class GraphUnzipSpec extends AkkaSpec {
|
|||
|
||||
FlowGraph { implicit b ⇒
|
||||
val unzip = Unzip[Int, String]("unzip")
|
||||
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.left ~> SubscriberSink(c1)
|
||||
unzip.right ~> SubscriberSink(c2)
|
||||
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.left ~> SubscriberDrain(c1)
|
||||
unzip.right ~> SubscriberDrain(c2)
|
||||
}.run()
|
||||
|
||||
val sub1 = c1.expectSubscription()
|
||||
|
|
@ -72,9 +72,9 @@ class GraphUnzipSpec extends AkkaSpec {
|
|||
|
||||
FlowGraph { implicit b ⇒
|
||||
val unzip = Unzip[Int, String]("unzip")
|
||||
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.left ~> SubscriberSink(c1)
|
||||
unzip.right ~> SubscriberSink(c2)
|
||||
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.left ~> SubscriberDrain(c1)
|
||||
unzip.right ~> SubscriberDrain(c2)
|
||||
}.run()
|
||||
|
||||
val sub1 = c1.expectSubscription()
|
||||
|
|
@ -94,9 +94,9 @@ class GraphUnzipSpec extends AkkaSpec {
|
|||
|
||||
FlowGraph { implicit b ⇒
|
||||
val unzip = Unzip[Int, String]("unzip")
|
||||
FlowFrom(p1.getPublisher) ~> unzip.in
|
||||
unzip.left ~> SubscriberSink(c1)
|
||||
unzip.right ~> SubscriberSink(c2)
|
||||
Source(p1.getPublisher) ~> unzip.in
|
||||
unzip.left ~> SubscriberDrain(c1)
|
||||
unzip.right ~> SubscriberDrain(c2)
|
||||
}.run()
|
||||
|
||||
val p1Sub = p1.expectSubscription()
|
||||
|
|
@ -122,10 +122,10 @@ class GraphUnzipSpec extends AkkaSpec {
|
|||
val zip = Zip[Int, String]
|
||||
val unzip = Unzip[Int, String]
|
||||
import FlowGraphImplicits._
|
||||
FlowFrom(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
Source(List(1 -> "a", 2 -> "b", 3 -> "c")) ~> unzip.in
|
||||
unzip.left ~> zip.left
|
||||
unzip.right ~> zip.right
|
||||
zip.out ~> SubscriberSink(c1)
|
||||
zip.out ~> SubscriberDrain(c1)
|
||||
}.run()
|
||||
|
||||
val sub1 = c1.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ class GraphZipSpec extends TwoStreamsSetup {
|
|||
FlowGraph { implicit b ⇒
|
||||
val zip = Zip[Int, String]
|
||||
|
||||
FlowFrom(1 to 4) ~> zip.left
|
||||
FlowFrom(List("A", "B", "C", "D", "E", "F")) ~> zip.right
|
||||
Source(1 to 4) ~> zip.left
|
||||
Source(List("A", "B", "C", "D", "E", "F")) ~> zip.right
|
||||
|
||||
zip.out ~> SubscriberSink(probe)
|
||||
zip.out ~> SubscriberDrain(probe)
|
||||
}.run()
|
||||
|
||||
val subscription = probe.expectSubscription()
|
||||
|
|
|
|||
|
|
@ -16,15 +16,15 @@ object ImplicitFlowMaterializerSpec {
|
|||
.withInputBuffer(initialSize = 2, maxSize = 16)
|
||||
.withFanOutBuffer(initialSize = 1, maxSize = 16)
|
||||
|
||||
val flow = FlowFrom(input).map(_.toUpperCase())
|
||||
val flow = Source(input).map(_.toUpperCase())
|
||||
|
||||
def receive = {
|
||||
case "run" ⇒
|
||||
// run takes an implicit FlowMaterializer parameter, which is provided by ImplicitFlowMaterializer
|
||||
import context.dispatcher
|
||||
val foldSink = FoldSink[String, String]("")(_ + _)
|
||||
val mf = flow.withSink(foldSink).run()
|
||||
foldSink.future(mf) pipeTo sender()
|
||||
val foldDrain = FoldDrain[String, String]("")(_ + _)
|
||||
val mf = flow.connect(foldDrain).run()
|
||||
foldDrain.future(mf) pipeTo sender()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class TickPublisherSpec extends AkkaSpec {
|
|||
"produce ticks" in {
|
||||
val tickGen = Iterator from 1
|
||||
val c = StreamTestKit.SubscriberProbe[String]()
|
||||
FlowFrom(1.second, 500.millis, () ⇒ "tick-" + tickGen.next()).publishTo(c)
|
||||
Source(1.second, 500.millis, () ⇒ "tick-" + tickGen.next()).publishTo(c)
|
||||
val sub = c.expectSubscription()
|
||||
sub.request(3)
|
||||
c.expectNoMsg(600.millis)
|
||||
|
|
@ -33,7 +33,7 @@ class TickPublisherSpec extends AkkaSpec {
|
|||
"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)
|
||||
Source(1.second, 1.second, () ⇒ "tick-" + tickGen.next()).publishTo(c)
|
||||
val sub = c.expectSubscription()
|
||||
sub.request(2)
|
||||
c.expectNext("tick-1")
|
||||
|
|
@ -50,7 +50,7 @@ class TickPublisherSpec extends AkkaSpec {
|
|||
|
||||
"produce ticks with multiple subscribers" in {
|
||||
val tickGen = Iterator from 1
|
||||
val p = FlowFrom(1.second, 1.second, () ⇒ "tick-" + tickGen.next()).toPublisher()
|
||||
val p = Source(1.second, 1.second, () ⇒ "tick-" + tickGen.next()).toPublisher()
|
||||
val c1 = StreamTestKit.SubscriberProbe[String]()
|
||||
val c2 = StreamTestKit.SubscriberProbe[String]()
|
||||
p.subscribe(c1)
|
||||
|
|
@ -74,7 +74,7 @@ class TickPublisherSpec extends AkkaSpec {
|
|||
|
||||
"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)
|
||||
Source[String](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")
|
||||
|
|
@ -83,8 +83,8 @@ class TickPublisherSpec extends AkkaSpec {
|
|||
// 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 rate = Source(1.second, 1.second, () ⇒ "tick").toPublisher()
|
||||
// Source(1 to 100).zip(rate).map { case (n, _) ⇒ n }.publishTo(c)
|
||||
// val sub = c.expectSubscription()
|
||||
// sub.request(1000)
|
||||
// c.expectNext(1)
|
||||
|
|
|
|||
|
|
@ -132,57 +132,57 @@ case class ActorBasedFlowMaterializer(override val settings: MaterializerSetting
|
|||
}
|
||||
|
||||
// Ops come in reverse order
|
||||
override def materialize[In, Out](source: Source[In], sink: Sink[Out], ops: List[Ast.AstNode]): MaterializedFlow = {
|
||||
override def materialize[In, Out](tap: Tap[In], drain: Drain[Out], ops: List[Ast.AstNode]): MaterializedPipe = {
|
||||
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 attachDrain(pub: Publisher[Out]) = drain match {
|
||||
case s: SimpleDrain[Out] ⇒ s.attach(pub, this, flowName)
|
||||
case s: DrainWithKey[Out, _] ⇒ s.attach(pub, this, flowName)
|
||||
case _ ⇒ throw new MaterializationException("unknown Drain type " + drain.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 attachTap(sub: Subscriber[In]) = tap match {
|
||||
case s: SimpleTap[In] ⇒ s.attach(sub, this, flowName)
|
||||
case s: TapWithKey[In, _] ⇒ s.attach(sub, this, flowName)
|
||||
case _ ⇒ throw new MaterializationException("unknown Tap type " + drain.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 createDrain() = drain.asInstanceOf[Drain[In]] match {
|
||||
case s: SimpleDrain[In] ⇒ s.create(this, flowName) -> (())
|
||||
case s: DrainWithKey[In, _] ⇒ s.create(this, flowName)
|
||||
case _ ⇒ throw new MaterializationException("unknown Drain type " + drain.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 createTap() = tap.asInstanceOf[Tap[Out]] match {
|
||||
case s: SimpleTap[Out] ⇒ s.create(this, flowName) -> (())
|
||||
case s: TapWithKey[Out, _] ⇒ s.create(this, flowName)
|
||||
case _ ⇒ throw new MaterializationException("unknown Tap type " + drain.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)
|
||||
case tap: SimpleTap[_] ⇒ tap.isActive
|
||||
case tap: TapWithKey[_, _] ⇒ tap.isActive
|
||||
case drain: SimpleDrain[_] ⇒ drain.isActive
|
||||
case drain: DrainWithKey[_, _] ⇒ drain.isActive
|
||||
case _: Tap[_] ⇒ throw new MaterializationException("unknown Tap type " + drain.getClass)
|
||||
case _: Drain[_] ⇒ throw new MaterializationException("unknown Drain type " + drain.getClass)
|
||||
}
|
||||
|
||||
val (sourceValue, sinkValue) =
|
||||
val (tapValue, drainValue) =
|
||||
if (ops.isEmpty) {
|
||||
if (isActive(sink)) {
|
||||
val (sub, value) = createSink()
|
||||
(attachSource(sub), value)
|
||||
} else if (isActive(source)) {
|
||||
val (pub, value) = createSource()
|
||||
(value, attachSink(pub))
|
||||
if (isActive(drain)) {
|
||||
val (sub, value) = createDrain()
|
||||
(attachTap(sub), value)
|
||||
} else if (isActive(tap)) {
|
||||
val (pub, value) = createTap()
|
||||
(value, attachDrain(pub))
|
||||
} else {
|
||||
val id: Processor[In, Out] = processorForNode(identityTransform, flowName, 1).asInstanceOf[Processor[In, Out]]
|
||||
(attachSource(id), attachSink(id))
|
||||
(attachTap(id), attachDrain(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))
|
||||
(attachTap(first), attachDrain(last))
|
||||
}
|
||||
new MaterializedFlow(source, sourceValue, sink, sinkValue)
|
||||
new MaterializedPipe(tap, tapValue, drain, drainValue)
|
||||
}
|
||||
|
||||
private val identityTransform = Ast.Transform("identity", () ⇒
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ package akka.stream.impl2
|
|||
|
||||
import akka.stream.impl.TransferPhase
|
||||
import akka.stream.impl.MultiStreamInputProcessor
|
||||
import akka.stream.scaladsl2.FlowWithSource
|
||||
import akka.stream.scaladsl2.Source
|
||||
import akka.stream.scaladsl2.FlowMaterializer
|
||||
|
||||
/**
|
||||
|
|
@ -17,7 +17,7 @@ private[akka] class ConcatAllImpl(materializer: FlowMaterializer)
|
|||
import MultiStreamInputProcessor._
|
||||
|
||||
val takeNextSubstream = TransferPhase(primaryInputs.NeedsInput && primaryOutputs.NeedsDemand) { () ⇒
|
||||
val flow = primaryInputs.dequeueInputElement().asInstanceOf[FlowWithSource[Any, Any]]
|
||||
val flow = primaryInputs.dequeueInputElement().asInstanceOf[Source[Any]]
|
||||
val publisher = flow.toPublisher()(materializer)
|
||||
// FIXME we can pass the flow to createSubstreamInput (but avoiding copy impl now)
|
||||
val inputs = createAndSubscribeSubstreamInput(publisher)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ package akka.stream.impl2
|
|||
|
||||
import akka.stream.MaterializerSettings
|
||||
import akka.stream.impl.TransferPhase
|
||||
import akka.stream.scaladsl2.FlowFrom
|
||||
import akka.stream.scaladsl2.Source
|
||||
import akka.stream.impl.MultiStreamOutputProcessor
|
||||
|
||||
/**
|
||||
|
|
@ -45,7 +45,7 @@ private[akka] class GroupByProcessorImpl(settings: MaterializerSettings, val key
|
|||
nextPhase(waitNext)
|
||||
} else {
|
||||
val substreamOutput = createSubstreamOutput()
|
||||
val substreamFlow = FlowFrom(substreamOutput) // substreamOutput is a Publisher
|
||||
val substreamFlow = Source(substreamOutput) // substreamOutput is a Publisher
|
||||
primaryOutputs.enqueueOutputElement((key, substreamFlow))
|
||||
keyToSubstreamOutput(key) = substreamOutput
|
||||
nextPhase(dispatchToSubstream(elem, substreamOutput))
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import scala.collection.immutable
|
|||
import akka.stream.impl.TransferPhase
|
||||
import akka.stream.impl.EmptyPublisher
|
||||
import akka.stream.impl.MultiStreamOutputProcessor
|
||||
import akka.stream.scaladsl2.FlowFrom
|
||||
import akka.stream.scaladsl2.Source
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
|
|
@ -44,13 +44,13 @@ private[akka] class PrefixAndTailImpl(_settings: MaterializerSettings, val takeM
|
|||
}
|
||||
|
||||
def emitEmptyTail(): Unit = {
|
||||
primaryOutputs.enqueueOutputElement((taken, FlowFrom(EmptyPublisher[Any])))
|
||||
primaryOutputs.enqueueOutputElement((taken, Source(EmptyPublisher[Any])))
|
||||
nextPhase(completedPhase)
|
||||
}
|
||||
|
||||
def emitNonEmptyTail(): Unit = {
|
||||
val substreamOutput = createSubstreamOutput()
|
||||
val substreamFlow = FlowFrom(substreamOutput) // substreamOutput is a Publisher
|
||||
val substreamFlow = Source(substreamOutput) // substreamOutput is a Publisher
|
||||
primaryOutputs.enqueueOutputElement((taken, substreamFlow))
|
||||
primaryOutputs.complete()
|
||||
nextPhase(streamTailPhase(substreamOutput))
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ package akka.stream.impl2
|
|||
import akka.stream.MaterializerSettings
|
||||
import akka.stream.impl.TransferPhase
|
||||
import akka.stream.impl.MultiStreamOutputProcessor
|
||||
import akka.stream.scaladsl2.FlowFrom
|
||||
import akka.stream.scaladsl2.Source
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
|
|
@ -24,7 +24,7 @@ private[akka] class SplitWhenProcessorImpl(_settings: MaterializerSettings, val
|
|||
|
||||
def openSubstream(elem: Any): TransferPhase = TransferPhase(primaryOutputs.NeedsDemand) { () ⇒
|
||||
val substreamOutput = createSubstreamOutput()
|
||||
val substreamFlow = FlowFrom(substreamOutput) // substreamOutput is a Publisher
|
||||
val substreamFlow = Source(substreamOutput) // substreamOutput is a Publisher
|
||||
primaryOutputs.enqueueOutputElement(substreamFlow)
|
||||
currentSubstream = substreamOutput
|
||||
nextPhase(serveSubstreamFirst(currentSubstream, elem))
|
||||
|
|
|
|||
276
akka-stream/src/main/scala/akka/stream/scaladsl2/Drain.scala
Normal file
276
akka-stream/src/main/scala/akka/stream/scaladsl2/Drain.scala
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.stream.scaladsl2
|
||||
|
||||
import akka.actor.Props
|
||||
|
||||
import scala.collection.immutable
|
||||
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.{ FanoutProcessorImpl, BlackholeSubscriber }
|
||||
import akka.stream.impl2.{ ActorProcessorFactory, ActorBasedFlowMaterializer }
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
/**
|
||||
* This trait is a marker for a pluggable stream drain. Concrete instances should
|
||||
* implement [[DrainWithKey]] or [[SimpleDrain]], otherwise a custom [[FlowMaterializer]]
|
||||
* will have to be used to be able to attach them.
|
||||
*
|
||||
* All Drains defined in this package rely upon an [[akka.stream.impl2.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
|
||||
* Drain nodes themselves (or construct an ActorBasedFlowMaterializer).
|
||||
*/
|
||||
trait Drain[-In] extends Sink[In]
|
||||
|
||||
/**
|
||||
* A drain that does not need to create a user-accessible object during materialization.
|
||||
*/
|
||||
trait SimpleDrain[-In] extends Drain[In] with DrainOps[In] {
|
||||
/**
|
||||
* Attach this drain to the given [[org.reactivestreams.Publisher]]. Using the given
|
||||
* [[FlowMaterializer]] is completely optional, especially if this drain 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[In @uncheckedVariance], materializer: ActorBasedFlowMaterializer, flowName: String): Unit
|
||||
/**
|
||||
* This method is only used for Drains that return true from [[#isActive]], which then must
|
||||
* implement it.
|
||||
*/
|
||||
def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[In] @uncheckedVariance =
|
||||
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
|
||||
/**
|
||||
* This method indicates whether this Drain 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 drain that will create an object during materialization that the user will need
|
||||
* to retrieve in order to access aspects of this drain (could be a completion Future
|
||||
* or a cancellation handle, etc.)
|
||||
*/
|
||||
trait DrainWithKey[-In, T] extends DrainOps[In] {
|
||||
/**
|
||||
* Attach this drain to the given [[org.reactivestreams.Publisher]]. Using the given
|
||||
* [[FlowMaterializer]] is completely optional, especially if this drain 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[In @uncheckedVariance], materializer: ActorBasedFlowMaterializer, flowName: String): T
|
||||
/**
|
||||
* This method is only used for Drains that return true from [[#isActive]], which then must
|
||||
* implement it.
|
||||
*/
|
||||
def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Subscriber[In] @uncheckedVariance, T) =
|
||||
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
|
||||
/**
|
||||
* This method indicates whether this Drain 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 PublisherDrain {
|
||||
private val instance = new PublisherDrain[Nothing]
|
||||
def apply[T]: PublisherDrain[T] = instance.asInstanceOf[PublisherDrain[T]]
|
||||
def withFanout[T](initialBufferSize: Int, maximumBufferSize: Int): FanoutPublisherDrain[T] =
|
||||
new FanoutPublisherDrain[T](initialBufferSize, maximumBufferSize)
|
||||
}
|
||||
|
||||
class PublisherDrain[In] extends DrainWithKey[In, Publisher[In]] {
|
||||
def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] = flowPublisher
|
||||
def publisher(m: MaterializedDrain): Publisher[In] = m.getDrainFor(this)
|
||||
|
||||
override def toString: String = "PublisherDrain"
|
||||
}
|
||||
|
||||
class FanoutPublisherDrain[In](initialBufferSize: Int, maximumBufferSize: Int) extends DrainWithKey[In, Publisher[In]] {
|
||||
def publisher(m: MaterializedDrain): Publisher[In] = m.getDrainFor(this)
|
||||
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[In] = {
|
||||
val fanoutActor = materializer.actorOf(
|
||||
Props(new FanoutProcessorImpl(materializer.settings, initialBufferSize, maximumBufferSize)), s"$flowName-fanoutPublisher")
|
||||
val fanoutProcessor = ActorProcessorFactory[In, In](fanoutActor)
|
||||
flowPublisher.subscribe(fanoutProcessor)
|
||||
fanoutProcessor
|
||||
}
|
||||
|
||||
override def toString: String = "Fanout"
|
||||
}
|
||||
|
||||
object FutureDrain {
|
||||
def apply[T]: FutureDrain[T] = new FutureDrain[T]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
class FutureDrain[In] extends DrainWithKey[In, Future[In]] {
|
||||
def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Future[In] = {
|
||||
val (sub, f) = create(materializer, flowName)
|
||||
flowPublisher.subscribe(sub)
|
||||
f
|
||||
}
|
||||
override def isActive = true
|
||||
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Subscriber[In], Future[In]) = {
|
||||
val p = Promise[In]()
|
||||
val sub = new Subscriber[In] { // 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: In): 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: MaterializedDrain): Future[In] = m.getDrainFor(this)
|
||||
|
||||
override def toString: String = "FutureDrain"
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a subscriber to this stream which will just discard all received
|
||||
* elements.
|
||||
*/
|
||||
final case object BlackholeDrain extends SimpleDrain[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 SubscriberDrain[In](subscriber: Subscriber[In]) extends SimpleDrain[In] {
|
||||
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
|
||||
flowPublisher.subscribe(subscriber)
|
||||
override def isActive: Boolean = true
|
||||
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[In] = subscriber
|
||||
}
|
||||
|
||||
object OnCompleteDrain {
|
||||
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 OnCompleteDrain[In](callback: Try[Unit] ⇒ Unit) extends SimpleDrain[In] {
|
||||
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
|
||||
Source(flowPublisher).transform("onCompleteDrain", () ⇒ new Transformer[In, Unit] {
|
||||
override def onNext(in: In) = Nil
|
||||
override def onError(e: Throwable) = ()
|
||||
override def onTermination(e: Option[Throwable]) = {
|
||||
e match {
|
||||
case None ⇒ callback(OnCompleteDrain.SuccessUnit)
|
||||
case Some(e) ⇒ callback(Failure(e))
|
||||
}
|
||||
Nil
|
||||
}
|
||||
}).consume()(materializer.withNamePrefix(flowName))
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given procedure for each received element. The drain 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 ForeachDrain[In](f: In ⇒ Unit) extends DrainWithKey[In, Future[Unit]] {
|
||||
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Future[Unit] = {
|
||||
val promise = Promise[Unit]()
|
||||
Source(flowPublisher).transform("foreach", () ⇒ new Transformer[In, Unit] {
|
||||
override def onNext(in: In) = { 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: MaterializedDrain): Future[Unit] = m.getDrainFor(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given function for every received element, giving it its previous
|
||||
* output (or the given `zero` value) and the element as input. The drain holds a
|
||||
* [[scala.concurrent.Future]] that will be completed with value of the final
|
||||
* function evaluation when the input stream ends, or completed with `Failure`
|
||||
* if there is an error is signaled in the stream.
|
||||
*/
|
||||
final case class FoldDrain[U, In](zero: U)(f: (U, In) ⇒ U) extends DrainWithKey[In, Future[U]] {
|
||||
override def attach(flowPublisher: Publisher[In], materializer: ActorBasedFlowMaterializer, flowName: String): Future[U] = {
|
||||
val promise = Promise[U]()
|
||||
|
||||
Source(flowPublisher).transform("fold", () ⇒ new Transformer[In, U] {
|
||||
var state: U = zero
|
||||
override def onNext(in: In): immutable.Seq[U] = { state = f(state, in); Nil }
|
||||
override def onError(cause: Throwable) = ()
|
||||
override def onTermination(e: Option[Throwable]) = {
|
||||
e match {
|
||||
case None ⇒ promise.success(state)
|
||||
case Some(e) ⇒ promise.failure(e)
|
||||
}
|
||||
Nil
|
||||
}
|
||||
}).consume()(materializer.withNamePrefix(flowName))
|
||||
|
||||
promise.future
|
||||
}
|
||||
def future(m: MaterializedDrain): Future[U] = m.getDrainFor(this)
|
||||
}
|
||||
|
||||
trait MaterializedDrain {
|
||||
/**
|
||||
* Do not call directly. Use accessor method in the concrete `Drain`, e.g. [[PublisherDrain#publisher]].
|
||||
*/
|
||||
def getDrainFor[T](drainKey: DrainWithKey[_, T]): T
|
||||
}
|
||||
|
||||
trait DrainOps[-In] extends Drain[In] {
|
||||
override def toSubscriber()(implicit materializer: FlowMaterializer): Subscriber[In @uncheckedVariance] =
|
||||
Flow[In].connect(this).toSubscriber()
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ object FlattenStrategy {
|
|||
* emitting its elements directly to the output until it completes and then taking the next stream. This has the
|
||||
* consequence that if one of the input stream is infinite, no other streams after that will be consumed from.
|
||||
*/
|
||||
def concat[T]: FlattenStrategy[FlowWithSource[_, T], T] = Concat[T]()
|
||||
def concat[T]: FlattenStrategy[Source[T], T] = Concat[T]()
|
||||
|
||||
private[akka] case class Concat[T]() extends FlattenStrategy[FlowWithSource[_, T], T]
|
||||
}
|
||||
private[akka] case class Concat[T]() extends FlattenStrategy[Source[T], T]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,69 +3,55 @@
|
|||
*/
|
||||
package akka.stream.scaladsl2
|
||||
|
||||
import akka.stream.{ TimerTransformer, Transformer, OverflowStrategy }
|
||||
import scala.collection.immutable
|
||||
import scala.collection.immutable
|
||||
import akka.stream.impl2.Ast._
|
||||
import org.reactivestreams._
|
||||
import scala.annotation.unchecked.uncheckedVariance
|
||||
import scala.concurrent.Future
|
||||
import scala.language.higherKinds
|
||||
import akka.stream.Transformer
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.concurrent.duration.Duration
|
||||
import akka.util.Collections.EmptyImmutableSeq
|
||||
import akka.stream.TimerTransformer
|
||||
import akka.stream.OverflowStrategy
|
||||
import scala.concurrent.Future
|
||||
|
||||
import scala.language.higherKinds
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* A `Flow` is a set of stream processing steps that has one open input and one open output.
|
||||
*/
|
||||
sealed trait Flow
|
||||
trait Flow[-In, +Out] extends FlowOps[Out] {
|
||||
override type Repr[+O] <: Flow[In, O]
|
||||
|
||||
object FlowOps {
|
||||
private case object TakeWithinTimerKey
|
||||
private case object DropWithinTimerKey
|
||||
private case object GroupedWithinTimerKey
|
||||
/**
|
||||
* Transform this flow by appending the given processing steps.
|
||||
*/
|
||||
def connect[T](flow: Flow[Out, T]): Flow[In, T]
|
||||
|
||||
private val takeCompletedTransformer: Transformer[Any, Any] = new Transformer[Any, Any] {
|
||||
override def onNext(elem: Any) = Nil
|
||||
override def isComplete = true
|
||||
}
|
||||
/**
|
||||
* Connect this flow to a sink, concatenating the processing steps of both.
|
||||
*/
|
||||
def connect(sink: Sink[Out]): Sink[In]
|
||||
}
|
||||
|
||||
private val identityTransformer: Transformer[Any, Any] = new Transformer[Any, Any] {
|
||||
override def onNext(elem: Any) = List(elem)
|
||||
}
|
||||
object Flow {
|
||||
/**
|
||||
* Helper to create `Flow` without a [[Source]] or a [[Sink]].
|
||||
* Example usage: `Flow[Int]`
|
||||
*/
|
||||
def apply[T]: Flow[T, T] = Pipe.empty[T]
|
||||
}
|
||||
|
||||
/**
|
||||
* Scala API: Operations offered by flows with a free output side: the DSL flows left-to-right only.
|
||||
* Scala API: Operations offered by Flows and Sources with a free output side: the DSL flows left-to-right only.
|
||||
*/
|
||||
trait FlowOps[-In, +Out] {
|
||||
import FlowOps._
|
||||
type Repr[-I, +O] <: FlowOps[I, O]
|
||||
|
||||
// Storing ops in reverse order
|
||||
protected def andThen[U](op: AstNode): Repr[In, U]
|
||||
trait FlowOps[+Out] {
|
||||
type Repr[+O]
|
||||
|
||||
/**
|
||||
* Transform this stream by applying the given function to each of the elements
|
||||
* as they pass through this processing step.
|
||||
*/
|
||||
def map[T](f: Out ⇒ T): Repr[In, T] =
|
||||
transform("map", () ⇒ new Transformer[Out, T] {
|
||||
override def onNext(in: Out) = List(f(in))
|
||||
})
|
||||
def map[T](f: Out ⇒ T): Repr[T]
|
||||
|
||||
/**
|
||||
* Transform each input element into a sequence of output elements that is
|
||||
* then flattened into the output stream.
|
||||
*/
|
||||
def mapConcat[T](f: Out ⇒ immutable.Seq[T]): Repr[In, T] =
|
||||
transform("mapConcat", () ⇒ new Transformer[Out, T] {
|
||||
override def onNext(in: Out) = f(in)
|
||||
})
|
||||
def mapConcat[T](f: Out ⇒ immutable.Seq[T]): Repr[T]
|
||||
|
||||
/**
|
||||
* Transform this stream by applying the given function to each of the elements
|
||||
|
|
@ -76,8 +62,7 @@ trait FlowOps[-In, +Out] {
|
|||
*
|
||||
* @see [[#mapAsyncUnordered]]
|
||||
*/
|
||||
def mapAsync[T](f: Out ⇒ Future[T]): Repr[In, T] =
|
||||
andThen(MapAsync(f.asInstanceOf[Any ⇒ Future[Any]]))
|
||||
def mapAsync[T](f: Out ⇒ Future[T]): Repr[T]
|
||||
|
||||
/**
|
||||
* Transform this stream by applying the given function to each of the elements
|
||||
|
|
@ -89,26 +74,19 @@ trait FlowOps[-In, +Out] {
|
|||
*
|
||||
* @see [[#mapAsync]]
|
||||
*/
|
||||
def mapAsyncUnordered[T](f: Out ⇒ Future[T]): Repr[In, T] =
|
||||
andThen(MapAsyncUnordered(f.asInstanceOf[Any ⇒ Future[Any]]))
|
||||
def mapAsyncUnordered[T](f: Out ⇒ Future[T]): Repr[T]
|
||||
|
||||
/**
|
||||
* Only pass on those elements that satisfy the given predicate.
|
||||
*/
|
||||
def filter(p: Out ⇒ Boolean): Repr[In, Out] =
|
||||
transform("filter", () ⇒ new Transformer[Out, Out] {
|
||||
override def onNext(in: Out) = if (p(in)) List(in) else Nil
|
||||
})
|
||||
def filter(p: Out ⇒ Boolean): Repr[Out]
|
||||
|
||||
/**
|
||||
* Transform this stream by applying the given partial function to each of the elements
|
||||
* on which the function is defined as they pass through this processing step.
|
||||
* Non-matching elements are filtered out.
|
||||
*/
|
||||
def collect[T](pf: PartialFunction[Out, T]): Repr[In, T] =
|
||||
transform("collect", () ⇒ new Transformer[Out, T] {
|
||||
override def onNext(in: Out) = if (pf.isDefinedAt(in)) List(pf(in)) else Nil
|
||||
})
|
||||
def collect[T](pf: PartialFunction[Out, T]): Repr[T]
|
||||
|
||||
/**
|
||||
* Chunk up this stream into groups of the given size, with the last group
|
||||
|
|
@ -116,22 +94,7 @@ trait FlowOps[-In, +Out] {
|
|||
*
|
||||
* `n` must be positive, otherwise IllegalArgumentException is thrown.
|
||||
*/
|
||||
def grouped(n: Int): Repr[In, immutable.Seq[Out]] = {
|
||||
require(n > 0, "n must be greater than 0")
|
||||
transform("grouped", () ⇒ new Transformer[Out, immutable.Seq[Out]] {
|
||||
var buf: Vector[Out] = Vector.empty
|
||||
override def onNext(in: Out) = {
|
||||
buf :+= in
|
||||
if (buf.size == n) {
|
||||
val group = buf
|
||||
buf = Vector.empty
|
||||
List(group)
|
||||
} else
|
||||
Nil
|
||||
}
|
||||
override def onTermination(e: Option[Throwable]) = if (buf.isEmpty) Nil else List(buf)
|
||||
})
|
||||
}
|
||||
def grouped(n: Int): Repr[immutable.Seq[Out]]
|
||||
|
||||
/**
|
||||
* Chunk up this stream into groups of elements received within a time window,
|
||||
|
|
@ -143,72 +106,18 @@ trait FlowOps[-In, +Out] {
|
|||
* `n` must be positive, and `d` must be greater than 0 seconds, otherwise
|
||||
* IllegalArgumentException is thrown.
|
||||
*/
|
||||
def groupedWithin(n: Int, d: FiniteDuration): Repr[In, immutable.Seq[Out]] = {
|
||||
require(n > 0, "n must be greater than 0")
|
||||
require(d > Duration.Zero)
|
||||
timerTransform("groupedWithin", () ⇒ new TimerTransformer[Out, immutable.Seq[Out]] {
|
||||
schedulePeriodically(GroupedWithinTimerKey, d)
|
||||
var buf: Vector[Out] = Vector.empty
|
||||
|
||||
override def onNext(in: Out) = {
|
||||
buf :+= in
|
||||
if (buf.size == n) {
|
||||
// start new time window
|
||||
schedulePeriodically(GroupedWithinTimerKey, d)
|
||||
emitGroup()
|
||||
} else Nil
|
||||
}
|
||||
override def onTermination(e: Option[Throwable]) = if (buf.isEmpty) Nil else List(buf)
|
||||
override def onTimer(timerKey: Any) = emitGroup()
|
||||
private def emitGroup(): immutable.Seq[immutable.Seq[Out]] =
|
||||
if (buf.isEmpty) EmptyImmutableSeq
|
||||
else {
|
||||
val group = buf
|
||||
buf = Vector.empty
|
||||
List(group)
|
||||
}
|
||||
})
|
||||
}
|
||||
def groupedWithin(n: Int, d: FiniteDuration): Repr[immutable.Seq[Out]]
|
||||
|
||||
/**
|
||||
* Discard the given number of elements at the beginning of the stream.
|
||||
* No elements will be dropped if `n` is zero or negative.
|
||||
*/
|
||||
def drop(n: Int): Repr[In, Out] =
|
||||
transform("drop", () ⇒ new Transformer[Out, Out] {
|
||||
var delegate: Transformer[Out, Out] =
|
||||
if (n <= 0) identityTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
else new Transformer[Out, Out] {
|
||||
var c = n
|
||||
override def onNext(in: Out) = {
|
||||
c -= 1
|
||||
if (c == 0)
|
||||
delegate = identityTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
override def onNext(in: Out) = delegate.onNext(in)
|
||||
})
|
||||
def drop(n: Int): Repr[Out]
|
||||
|
||||
/**
|
||||
* Discard the elements received within the given duration at beginning of the stream.
|
||||
*/
|
||||
def dropWithin(d: FiniteDuration): Repr[In, Out] =
|
||||
timerTransform("dropWithin", () ⇒ new TimerTransformer[Out, Out] {
|
||||
scheduleOnce(DropWithinTimerKey, d)
|
||||
|
||||
var delegate: Transformer[Out, Out] =
|
||||
new Transformer[Out, Out] {
|
||||
override def onNext(in: Out) = Nil
|
||||
}
|
||||
|
||||
override def onNext(in: Out) = delegate.onNext(in)
|
||||
override def onTimer(timerKey: Any) = {
|
||||
delegate = identityTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
Nil
|
||||
}
|
||||
})
|
||||
def dropWithin(d: FiniteDuration): Repr[Out]
|
||||
|
||||
/**
|
||||
* Terminate processing (and cancel the upstream publisher) after the given
|
||||
|
|
@ -219,23 +128,7 @@ trait FlowOps[-In, +Out] {
|
|||
* The stream will be completed without producing any elements if `n` is zero
|
||||
* or negative.
|
||||
*/
|
||||
def take(n: Int): Repr[In, Out] =
|
||||
transform("take", () ⇒ new Transformer[Out, Out] {
|
||||
var delegate: Transformer[Out, Out] =
|
||||
if (n <= 0) takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
else new Transformer[Out, Out] {
|
||||
var c = n
|
||||
override def onNext(in: Out) = {
|
||||
c -= 1
|
||||
if (c == 0)
|
||||
delegate = takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
List(in)
|
||||
}
|
||||
}
|
||||
|
||||
override def onNext(in: Out) = delegate.onNext(in)
|
||||
override def isComplete = delegate.isComplete
|
||||
})
|
||||
def take(n: Int): Repr[Out]
|
||||
|
||||
/**
|
||||
* Terminate processing (and cancel the upstream publisher) after the given
|
||||
|
|
@ -246,19 +139,7 @@ trait FlowOps[-In, +Out] {
|
|||
* Note that this can be combined with [[#take]] to limit the number of elements
|
||||
* within the duration.
|
||||
*/
|
||||
def takeWithin(d: FiniteDuration): Repr[In, Out] =
|
||||
timerTransform("takeWithin", () ⇒ new TimerTransformer[Out, Out] {
|
||||
scheduleOnce(TakeWithinTimerKey, d)
|
||||
|
||||
var delegate: Transformer[Out, Out] = identityTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
|
||||
override def onNext(in: Out) = delegate.onNext(in)
|
||||
override def isComplete = delegate.isComplete
|
||||
override def onTimer(timerKey: Any) = {
|
||||
delegate = takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
Nil
|
||||
}
|
||||
})
|
||||
def takeWithin(d: FiniteDuration): Repr[Out]
|
||||
|
||||
/**
|
||||
* Allows a faster upstream to progress independently of a slower subscriber by conflating elements into a summary
|
||||
|
|
@ -271,8 +152,7 @@ trait FlowOps[-In, +Out] {
|
|||
* @param seed Provides the first state for a conflated value using the first unconsumed element as a start
|
||||
* @param aggregate Takes the currently aggregated value and the current pending element to produce a new aggregate
|
||||
*/
|
||||
def conflate[S](seed: Out ⇒ S, aggregate: (S, Out) ⇒ S): Repr[In, S] =
|
||||
andThen(Conflate(seed.asInstanceOf[Any ⇒ Any], aggregate.asInstanceOf[(Any, Any) ⇒ Any]))
|
||||
def conflate[S](seed: Out ⇒ S, aggregate: (S, Out) ⇒ S): Repr[S]
|
||||
|
||||
/**
|
||||
* Allows a faster downstream to progress independently of a slower publisher by extrapolating elements from an older
|
||||
|
|
@ -287,8 +167,7 @@ trait FlowOps[-In, +Out] {
|
|||
* @param extrapolate Takes the current extrapolation state to produce an output element and the next extrapolation
|
||||
* state.
|
||||
*/
|
||||
def expand[S, U](seed: Out ⇒ S, extrapolate: S ⇒ (U, S)): Repr[In, U] =
|
||||
andThen(Expand(seed.asInstanceOf[Any ⇒ Any], extrapolate.asInstanceOf[Any ⇒ (Any, Any)]))
|
||||
def expand[S, U](seed: Out ⇒ S, extrapolate: S ⇒ (U, S)): Repr[U]
|
||||
|
||||
/**
|
||||
* Adds a fixed size buffer in the flow that allows to store elements from a faster upstream until it becomes full.
|
||||
|
|
@ -298,10 +177,7 @@ trait FlowOps[-In, +Out] {
|
|||
* @param size The size of the buffer in element count
|
||||
* @param overflowStrategy Strategy that is used when incoming elements cannot fit inside the buffer
|
||||
*/
|
||||
def buffer(size: Int, overflowStrategy: OverflowStrategy): Repr[In, Out] = {
|
||||
require(size > 0, s"Buffer size must be larger than zero but was [$size]")
|
||||
andThen(Buffer(size, overflowStrategy))
|
||||
}
|
||||
def buffer(size: Int, overflowStrategy: OverflowStrategy): Repr[Out]
|
||||
|
||||
/**
|
||||
* Generic transformation of a stream: for each element the [[akka.stream.Transformer#onNext]]
|
||||
|
|
@ -325,17 +201,14 @@ trait FlowOps[-In, +Out] {
|
|||
*
|
||||
* Note that you can use [[#timerTransform]] if you need support for scheduled events in the transformer.
|
||||
*/
|
||||
def transform[T](name: String, mkTransformer: () ⇒ Transformer[Out, T]): Repr[In, T] = {
|
||||
andThen(Transform(name, mkTransformer.asInstanceOf[() ⇒ Transformer[Any, Any]]))
|
||||
}
|
||||
def transform[T](name: String, mkTransformer: () ⇒ Transformer[Out, T]): Repr[T]
|
||||
|
||||
/**
|
||||
* Takes up to `n` elements from the stream and returns a pair containing a strict sequence of the taken element
|
||||
* and a stream representing the remaining elements. If ''n'' is zero or negative, then this will return a pair
|
||||
* of an empty collection and a stream containing the whole upstream unchanged.
|
||||
*/
|
||||
def prefixAndTail[U >: Out](n: Int): Repr[In, (immutable.Seq[Out], FlowWithSource[U, U])] =
|
||||
andThen(PrefixAndTail(n))
|
||||
def prefixAndTail[U >: Out](n: Int): Repr[(immutable.Seq[Out], Source[U])]
|
||||
|
||||
/**
|
||||
* This operation demultiplexes the incoming stream into separate output
|
||||
|
|
@ -348,8 +221,7 @@ trait FlowOps[-In, +Out] {
|
|||
* care to unblock (or cancel) all of the produced streams even if you want
|
||||
* to consume only one of them.
|
||||
*/
|
||||
def groupBy[K, U >: Out](f: Out ⇒ K): Repr[In, (K, FlowWithSource[U, U])] =
|
||||
andThen(GroupBy(f.asInstanceOf[Any ⇒ Any]))
|
||||
def groupBy[K, U >: Out](f: Out ⇒ K): Repr[(K, Source[U])]
|
||||
|
||||
/**
|
||||
* This operation applies the given predicate to all incoming elements and
|
||||
|
|
@ -364,17 +236,13 @@ trait FlowOps[-In, +Out] {
|
|||
* true, false, false // elements go into third substream
|
||||
* }}}
|
||||
*/
|
||||
def splitWhen[U >: Out](p: Out ⇒ Boolean): Repr[In, FlowWithSource[U, U]] =
|
||||
andThen(SplitWhen(p.asInstanceOf[Any ⇒ Boolean]))
|
||||
def splitWhen[U >: Out](p: Out ⇒ Boolean): Repr[Source[U]]
|
||||
|
||||
/**
|
||||
* Transforms a stream of streams into a contiguous stream of elements using the provided flattening strategy.
|
||||
* This operation can be used on a stream of element type [[StreamWithSource]].
|
||||
* This operation can be used on a stream of element type [[Source]].
|
||||
*/
|
||||
def flatten[U](strategy: FlattenStrategy[Out, U]): Repr[In, U] = strategy match {
|
||||
case _: FlattenStrategy.Concat[Out] ⇒ andThen(ConcatAll)
|
||||
case _ ⇒ throw new IllegalArgumentException(s"Unsupported flattening strategy [${strategy.getClass.getSimpleName}]")
|
||||
}
|
||||
def flatten[U](strategy: FlattenStrategy[Out, U]): Repr[U]
|
||||
|
||||
/**
|
||||
* Transformation of a stream, with additional support for scheduled events.
|
||||
|
|
@ -400,122 +268,19 @@ trait FlowOps[-In, +Out] {
|
|||
*
|
||||
* Note that you can use [[#transform]] if you just need to transform elements time plays no role in the transformation.
|
||||
*/
|
||||
def timerTransform[U](name: String, mkTransformer: () ⇒ TimerTransformer[Out, U]): Repr[In, U] =
|
||||
andThen(TimerTransform(name, mkTransformer.asInstanceOf[() ⇒ TimerTransformer[Any, Any]]))
|
||||
}
|
||||
|
||||
object ProcessorFlow {
|
||||
private val emptyInstance = ProcessorFlow[Any, Any](ops = Nil)
|
||||
def empty[T]: ProcessorFlow[T, T] = emptyInstance.asInstanceOf[ProcessorFlow[T, 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 FlowOps[In, Out] {
|
||||
override type Repr[-I, +O] = ProcessorFlow[I, O]
|
||||
|
||||
override protected def andThen[U](op: AstNode): Repr[In, U] = this.copy(ops = op :: ops)
|
||||
|
||||
def withSink(out: Sink[Out]): FlowWithSink[In, Out] = FlowWithSink(out, ops)
|
||||
def withSource(in: Source[In]): FlowWithSource[In, Out] = FlowWithSource(in, 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)
|
||||
|
||||
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 FlowWithSink[-In, +Out](private[scaladsl2] val output: Sink[Out @uncheckedVariance], ops: List[AstNode]) {
|
||||
|
||||
def withSource(in: Source[In]): RunnableFlow[In, Out] = RunnableFlow(in, output, ops)
|
||||
def withoutSink: ProcessorFlow[In, Out] = ProcessorFlow(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 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 withSink(out: Sink[Out]): RunnableFlow[In, Out] = RunnableFlow(input, out, ops)
|
||||
def withoutSource: ProcessorFlow[In, Out] = ProcessorFlow(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 toFanoutPublisher(initialBufferSize: Int, maximumBufferSize: Int)(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] = {
|
||||
val pubOut = PublisherSink.withFanout[Out](initialBufferSize, maximumBufferSize)
|
||||
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()
|
||||
|
||||
def timerTransform[U](name: String, mkTransformer: () ⇒ TimerTransformer[Out, U]): Repr[U]
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow with attached input and output, can be executed.
|
||||
*/
|
||||
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)
|
||||
trait RunnableFlow {
|
||||
def run()(implicit materializer: FlowMaterializer): MaterializedFlow
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned by [[RunnableFlow#run]] and can be used as parameter to the
|
||||
* accessor method to retrieve the materialized `Source` or `Sink`, e.g.
|
||||
* [[SubscriberSource#subscriber]] or [[PublisherSink#publisher]].
|
||||
* accessor method to retrieve the materialized `Tap` or `Drain`, e.g.
|
||||
* [[SubscriberTap#subscriber]] or [[PublisherDrain#publisher]].
|
||||
*/
|
||||
class MaterializedFlow(sourceKey: AnyRef, matSource: Any, sinkKey: AnyRef, matSink: Any) extends MaterializedSource with MaterializedSink {
|
||||
/**
|
||||
* Do not call directly. Use accessor method in the concrete `Source`, e.g. [[SubscriberSource#subscriber]].
|
||||
*/
|
||||
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")
|
||||
|
||||
/**
|
||||
* Do not call directly. Use accessor method in the concrete `Sink`, e.g. [[PublisherSink#publisher]].
|
||||
*/
|
||||
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
|
||||
}
|
||||
trait MaterializedFlow extends MaterializedTap with MaterializedDrain
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@
|
|||
package akka.stream.scaladsl2
|
||||
|
||||
import scala.language.existentials
|
||||
import scalax.collection.edge.LkDiEdge
|
||||
import scalax.collection.edge.{ LkBase, LkDiEdge }
|
||||
import scalax.collection.mutable.Graph
|
||||
import scalax.collection.immutable.{ Graph ⇒ ImmutableGraph }
|
||||
import org.reactivestreams.Subscriber
|
||||
import akka.stream.impl.BlackholeSubscriber
|
||||
import org.reactivestreams.Publisher
|
||||
import akka.stream.impl2.Ast
|
||||
|
||||
|
|
@ -73,8 +72,8 @@ object Merge {
|
|||
* Merge several streams, taking elements as they arrive from input streams
|
||||
* (picking randomly when several have elements ready).
|
||||
*
|
||||
* When building the [[FlowGraph]] you must connect one or more input flows/sources
|
||||
* and one output flow/sink to the `Merge` vertex.
|
||||
* When building the [[FlowGraph]] you must connect one or more input pipes/taps
|
||||
* and one output pipe/drain to the `Merge` vertex.
|
||||
*/
|
||||
final class Merge[T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex with Junction[T] {
|
||||
override private[akka] val vertex = this
|
||||
|
|
@ -259,82 +258,83 @@ final class Concat[T](override val name: Option[String]) extends FlowGraphIntern
|
|||
override private[akka] def astNode = Ast.Concat
|
||||
}
|
||||
|
||||
object UndefinedSink {
|
||||
object UndefinedDrain {
|
||||
/**
|
||||
* Create a new anonymous `UndefinedSink` vertex with the specified input type.
|
||||
* Note that a `UndefinedSink` instance can only be used at one place (one vertex)
|
||||
* Create a new anonymous `UndefinedDrain` vertex with the specified input type.
|
||||
* Note that a `UndefinedDrain` instance can only be used at one place (one vertex)
|
||||
* in the `FlowGraph`. This method creates a new instance every time it
|
||||
* is called and those instances are not `equal`.
|
||||
*/
|
||||
def apply[T]: UndefinedSink[T] = new UndefinedSink[T](None)
|
||||
def apply[T]: UndefinedDrain[T] = new UndefinedDrain[T](None)
|
||||
/**
|
||||
* Create a named `UndefinedSink` vertex with the specified input type.
|
||||
* Note that a `UndefinedSink` with a specific name can only be used at one place (one vertex)
|
||||
* Create a named `UndefinedDrain` vertex with the specified input type.
|
||||
* Note that a `UndefinedDrain` with a specific name can only be used at one place (one vertex)
|
||||
* in the `FlowGraph`. Calling this method several times with the same name
|
||||
* returns instances that are `equal`.
|
||||
*/
|
||||
def apply[T](name: String): UndefinedSink[T] = new UndefinedSink[T](Some(name))
|
||||
def apply[T](name: String): UndefinedDrain[T] = new UndefinedDrain[T](Some(name))
|
||||
}
|
||||
/**
|
||||
* It is possible to define a [[PartialFlowGraph]] with output flows that are not connected
|
||||
* yet by using this placeholder instead of the real [[Sink]]. Later the placeholder can
|
||||
* be replaced with [[FlowGraphBuilder#attachSink]].
|
||||
* It is possible to define a [[PartialFlowGraph]] with output pipes that are not connected
|
||||
* yet by using this placeholder instead of the real [[Drain]]. Later the placeholder can
|
||||
* be replaced with [[FlowGraphBuilder#attachDrain]].
|
||||
*/
|
||||
final class UndefinedSink[-T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex {
|
||||
final class UndefinedDrain[-T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex {
|
||||
override def minimumInputCount: Int = 1
|
||||
override def maximumInputCount: Int = 1
|
||||
override def minimumOutputCount: Int = 0
|
||||
override def maximumOutputCount: Int = 0
|
||||
|
||||
override private[akka] def astNode = throw new UnsupportedOperationException("Undefined sinks cannot be materialized")
|
||||
override private[akka] def astNode = throw new UnsupportedOperationException("Undefined drains cannot be materialized")
|
||||
}
|
||||
|
||||
object UndefinedSource {
|
||||
object UndefinedTap {
|
||||
/**
|
||||
* Create a new anonymous `UndefinedSource` vertex with the specified input type.
|
||||
* Note that a `UndefinedSource` instance can only be used at one place (one vertex)
|
||||
* Create a new anonymous `UndefinedTap` vertex with the specified input type.
|
||||
* Note that a `UndefinedTap` instance can only be used at one place (one vertex)
|
||||
* in the `FlowGraph`. This method creates a new instance every time it
|
||||
* is called and those instances are not `equal`.
|
||||
*/
|
||||
def apply[T]: UndefinedSource[T] = new UndefinedSource[T](None)
|
||||
def apply[T]: UndefinedTap[T] = new UndefinedTap[T](None)
|
||||
/**
|
||||
* Create a named `UndefinedSource` vertex with the specified output type.
|
||||
* Note that a `UndefinedSource` with a specific name can only be used at one place (one vertex)
|
||||
* Create a named `UndefinedTap` vertex with the specified output type.
|
||||
* Note that a `UndefinedTap` with a specific name can only be used at one place (one vertex)
|
||||
* in the `FlowGraph`. Calling this method several times with the same name
|
||||
* returns instances that are `equal`.
|
||||
*/
|
||||
def apply[T](name: String): UndefinedSource[T] = new UndefinedSource[T](Some(name))
|
||||
def apply[T](name: String): UndefinedTap[T] = new UndefinedTap[T](Some(name))
|
||||
}
|
||||
/**
|
||||
* It is possible to define a [[PartialFlowGraph]] with input flows that are not connected
|
||||
* yet by using this placeholder instead of the real [[Source]]. Later the placeholder can
|
||||
* be replaced with [[FlowGraphBuilder#attachSource]].
|
||||
* It is possible to define a [[PartialFlowGraph]] with input pipes that are not connected
|
||||
* yet by using this placeholder instead of the real [[Tap]]. Later the placeholder can
|
||||
* be replaced with [[FlowGraphBuilder#attachTap]].
|
||||
*/
|
||||
final class UndefinedSource[+T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex {
|
||||
final class UndefinedTap[+T](override val name: Option[String]) extends FlowGraphInternal.InternalVertex {
|
||||
override def minimumInputCount: Int = 0
|
||||
override def maximumInputCount: Int = 0
|
||||
override def minimumOutputCount: Int = 1
|
||||
override def maximumOutputCount: Int = 1
|
||||
|
||||
override private[akka] def astNode = throw new UnsupportedOperationException("Undefined sources cannot be materialized")
|
||||
override private[akka] def astNode = throw new UnsupportedOperationException("Undefined taps cannot be materialized")
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
*/
|
||||
private[akka] object FlowGraphInternal {
|
||||
val OnlyPipesErrorMessage = "Only pipes are supported currently!"
|
||||
|
||||
def UnlabeledPort = -1
|
||||
|
||||
sealed trait Vertex
|
||||
case class SourceVertex(source: Source[_]) extends Vertex {
|
||||
override def toString = source.toString
|
||||
case class TapVertex(tap: Tap[_]) extends Vertex {
|
||||
override def toString = tap.toString
|
||||
// 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
|
||||
}
|
||||
case class SinkVertex(sink: Sink[_]) extends Vertex {
|
||||
override def toString = sink.toString
|
||||
case class DrainVertex(drain: Drain[_]) extends Vertex {
|
||||
override def toString = drain.toString
|
||||
// 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
|
||||
|
|
@ -370,107 +370,159 @@ private[akka] object FlowGraphInternal {
|
|||
|
||||
// flow not part of equals/hashCode
|
||||
case class EdgeLabel(qualifier: Int)(
|
||||
val flow: ProcessorFlow[Any, Any],
|
||||
val pipe: Pipe[Any, Nothing],
|
||||
val inputPort: Int,
|
||||
val outputPort: Int) {
|
||||
|
||||
override def toString: String = flow.toString
|
||||
override def toString: String = pipe.toString
|
||||
}
|
||||
|
||||
type EdgeType[T] = LkDiEdge[T] { type L1 = EdgeLabel }
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder of [[FlowGraph]] and [[PartialFlowGraph]].
|
||||
* Syntactic sugar is provided by [[FlowGraphImplicits]].
|
||||
*/
|
||||
class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]) {
|
||||
class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType]) {
|
||||
import FlowGraphInternal._
|
||||
|
||||
private[akka] def this() = this(Graph.empty[FlowGraphInternal.Vertex, LkDiEdge])
|
||||
private[akka] def this() = this(Graph.empty[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])
|
||||
|
||||
private[akka] def this(immutableGraph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge]) =
|
||||
this(Graph.from(edges = immutableGraph.edges.map(e ⇒ LkDiEdge(e.from.value, e.to.value)(e.label)).toIterable))
|
||||
private[akka] def this(immutableGraph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType]) =
|
||||
this({
|
||||
val edges: Iterable[FlowGraphInternal.EdgeType[FlowGraphInternal.Vertex]] =
|
||||
immutableGraph.edges.map(e ⇒ LkDiEdge(e.from.value, e.to.value)(e.label))
|
||||
Graph.from(edges = edges)
|
||||
})
|
||||
|
||||
private implicit val edgeFactory = scalax.collection.edge.LkDiEdge
|
||||
private implicit val edgeFactory = scalax.collection.edge.LkDiEdge.asInstanceOf[LkBase.LkEdgeCompanion[EdgeType]]
|
||||
|
||||
var edgeQualifier = graph.edges.size
|
||||
|
||||
private var cyclesAllowed = false
|
||||
|
||||
def addEdge[In, Out](source: Source[In], flow: ProcessorFlow[In, Out], sink: JunctionInPort[Out]): this.type = {
|
||||
val sourceVertex = SourceVertex(source)
|
||||
checkAddSourceSinkPrecondition(sourceVertex)
|
||||
checkJunctionInPortPrecondition(sink)
|
||||
addGraphEdge(sourceVertex, sink.vertex, flow, inputPort = sink.port, outputPort = UnlabeledPort)
|
||||
private def addTapPipeEdge[In, Out](tap: Tap[In], pipe: Pipe[In, Out], drain: JunctionInPort[Out]): this.type = {
|
||||
val tapVertex = TapVertex(tap)
|
||||
checkAddTapDrainPrecondition(tapVertex)
|
||||
checkJunctionInPortPrecondition(drain)
|
||||
addGraphEdge(tapVertex, drain.vertex, pipe, inputPort = drain.port, outputPort = UnlabeledPort)
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](source: UndefinedSource[In], flow: ProcessorFlow[In, Out], sink: JunctionInPort[Out]): this.type = {
|
||||
checkAddSourceSinkPrecondition(source)
|
||||
checkJunctionInPortPrecondition(sink)
|
||||
addGraphEdge(source, sink.vertex, flow, inputPort = sink.port, outputPort = UnlabeledPort)
|
||||
private def addPipeDrainEdge[In, Out](tap: JunctionOutPort[In], pipe: Pipe[In, Out], drain: Drain[Out]): this.type = {
|
||||
val drainVertex = DrainVertex(drain)
|
||||
checkAddTapDrainPrecondition(drainVertex)
|
||||
checkJunctionOutPortPrecondition(tap)
|
||||
addGraphEdge(tap.vertex, drainVertex, pipe, inputPort = UnlabeledPort, outputPort = tap.port)
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](source: JunctionOutPort[In], flow: ProcessorFlow[In, Out], sink: Sink[Out]): this.type = {
|
||||
val sinkVertex = SinkVertex(sink)
|
||||
checkAddSourceSinkPrecondition(sinkVertex)
|
||||
checkJunctionOutPortPrecondition(source)
|
||||
addGraphEdge(source.vertex, sinkVertex, flow, inputPort = UnlabeledPort, outputPort = source.port)
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](source: JunctionOutPort[In], flow: ProcessorFlow[In, Out], sink: UndefinedSink[Out]): this.type = {
|
||||
checkAddSourceSinkPrecondition(sink)
|
||||
checkJunctionOutPortPrecondition(source)
|
||||
addGraphEdge(source.vertex, sink, flow, inputPort = UnlabeledPort, outputPort = source.port)
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](source: JunctionOutPort[In], flow: ProcessorFlow[In, Out], sink: JunctionInPort[Out]): this.type = {
|
||||
checkJunctionOutPortPrecondition(source)
|
||||
checkJunctionInPortPrecondition(sink)
|
||||
addGraphEdge(source.vertex, sink.vertex, flow, inputPort = sink.port, outputPort = source.port)
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](flow: FlowWithSource[In, Out], sink: JunctionInPort[Out]): this.type = {
|
||||
addEdge(flow.input, flow.withoutSource, sink)
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](source: JunctionOutPort[In], flow: FlowWithSink[In, Out]): this.type = {
|
||||
addEdge(source, flow.withoutSink, flow.output)
|
||||
this
|
||||
}
|
||||
|
||||
private def addGraphEdge[In, Out](from: Vertex, to: Vertex, flow: ProcessorFlow[In, Out], inputPort: Int, outputPort: Int): Unit = {
|
||||
if (edgeQualifier == Int.MaxValue) throw new IllegalArgumentException(s"Too many edges")
|
||||
val label = EdgeLabel(edgeQualifier)(flow.asInstanceOf[ProcessorFlow[Any, Any]], inputPort, outputPort)
|
||||
graph.addLEdge(from, to)(label)
|
||||
edgeQualifier += 1
|
||||
}
|
||||
|
||||
def attachSink[Out](token: UndefinedSink[Out], sink: Sink[Out]): this.type = {
|
||||
graph.find(token) match {
|
||||
case Some(existing) ⇒
|
||||
require(existing.value.isInstanceOf[UndefinedSink[_]], s"Flow already attached to a sink [${existing.value}]")
|
||||
val edge = existing.incoming.head
|
||||
graph.remove(existing)
|
||||
graph.addLEdge(edge.from.value, SinkVertex(sink))(edge.label)
|
||||
case None ⇒ throw new IllegalArgumentException(s"No matching UndefinedSink [${token}]")
|
||||
def addEdge[In, Out](tap: UndefinedTap[In], flow: Flow[In, Out], drain: JunctionInPort[Out]): this.type = {
|
||||
checkAddTapDrainPrecondition(tap)
|
||||
checkJunctionInPortPrecondition(drain)
|
||||
flow match {
|
||||
case pipe: Pipe[In, Out] ⇒
|
||||
addGraphEdge(tap, drain.vertex, pipe, inputPort = drain.port, outputPort = UnlabeledPort)
|
||||
case _ ⇒
|
||||
throw new IllegalArgumentException(OnlyPipesErrorMessage)
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
def attachSource[In](token: UndefinedSource[In], source: Source[In]): this.type = {
|
||||
def addEdge[In, Out](tap: JunctionOutPort[In], flow: Flow[In, Out], drain: UndefinedDrain[Out]): this.type = {
|
||||
checkAddTapDrainPrecondition(drain)
|
||||
checkJunctionOutPortPrecondition(tap)
|
||||
flow match {
|
||||
case pipe: Pipe[In, Out] ⇒
|
||||
addGraphEdge(tap.vertex, drain, pipe, inputPort = UnlabeledPort, outputPort = tap.port)
|
||||
case _ ⇒
|
||||
throw new IllegalArgumentException(OnlyPipesErrorMessage)
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](tap: JunctionOutPort[In], flow: Flow[In, Out], drain: JunctionInPort[Out]): this.type = {
|
||||
checkJunctionOutPortPrecondition(tap)
|
||||
checkJunctionInPortPrecondition(drain)
|
||||
flow match {
|
||||
case pipe: Pipe[In, Out] ⇒
|
||||
addGraphEdge(tap.vertex, drain.vertex, pipe, inputPort = drain.port, outputPort = tap.port)
|
||||
case _ ⇒
|
||||
throw new IllegalArgumentException(OnlyPipesErrorMessage)
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](source: Source[In], flow: Flow[In, Out], drain: JunctionInPort[Out]): this.type = {
|
||||
(source, flow) match {
|
||||
case (tap: Tap[In], pipe: Pipe[In, Out]) ⇒
|
||||
addTapPipeEdge(tap, pipe, drain)
|
||||
case (spipe: SourcePipe[In], pipe: Pipe[In, Out]) ⇒
|
||||
addEdge(spipe.connect(pipe), drain)
|
||||
case _ ⇒
|
||||
throw new IllegalArgumentException(OnlyPipesErrorMessage)
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[Out](source: Source[Out], drain: JunctionInPort[Out]): this.type = {
|
||||
source match {
|
||||
case tap: Tap[Out] ⇒
|
||||
addTapPipeEdge(tap, Pipe.empty[Out], drain)
|
||||
case pipe: SourcePipe[Out] ⇒
|
||||
addTapPipeEdge(pipe.input, Pipe(pipe.ops), drain)
|
||||
case _ ⇒
|
||||
throw new IllegalArgumentException(OnlyPipesErrorMessage)
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](tap: JunctionOutPort[In], sink: Sink[In]): this.type = {
|
||||
sink match {
|
||||
case drain: Drain[In] ⇒ addPipeDrainEdge(tap, Pipe.empty[In], drain)
|
||||
case pipe: SinkPipe[In] ⇒ addPipeDrainEdge(tap, Pipe(pipe.ops), pipe.output)
|
||||
case _ ⇒ throw new IllegalArgumentException(OnlyPipesErrorMessage)
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
def addEdge[In, Out](tap: JunctionOutPort[In], flow: Flow[In, Out], sink: Sink[Out]): this.type = {
|
||||
(flow, sink) match {
|
||||
case (pipe: Pipe[In, Out], drain: Drain[Out]) ⇒
|
||||
addPipeDrainEdge(tap, pipe, drain)
|
||||
case (pipe: Pipe[In, Out], spipe: SinkPipe[Out]) ⇒
|
||||
addEdge(tap, pipe.connect(spipe))
|
||||
case _ ⇒ throw new IllegalArgumentException(OnlyPipesErrorMessage)
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
private def addGraphEdge[In, Out](from: Vertex, to: Vertex, pipe: Pipe[In, Out], inputPort: Int, outputPort: Int): Unit = {
|
||||
if (edgeQualifier == Int.MaxValue) throw new IllegalArgumentException(s"Too many edges")
|
||||
val label = EdgeLabel(edgeQualifier)(pipe.asInstanceOf[Pipe[Any, Nothing]], inputPort, outputPort)
|
||||
graph.addLEdge(from, to)(label)
|
||||
edgeQualifier += 1
|
||||
}
|
||||
|
||||
def attachDrain[Out](token: UndefinedDrain[Out], drain: Drain[Out]): this.type = {
|
||||
graph.find(token) match {
|
||||
case Some(existing) ⇒
|
||||
val edge = existing.incoming.head
|
||||
graph.remove(existing)
|
||||
graph.addLEdge(edge.from.value, DrainVertex(drain))(edge.label)
|
||||
case None ⇒ throw new IllegalArgumentException(s"No matching UndefinedDrain [${token}]")
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
def attachTap[In](token: UndefinedTap[In], tap: Tap[In]): this.type = {
|
||||
graph.find(token) match {
|
||||
case Some(existing) ⇒
|
||||
require(existing.value.isInstanceOf[UndefinedSource[_]], s"Flow already attached to a source [${existing.value}]")
|
||||
val edge = existing.outgoing.head
|
||||
graph.remove(existing)
|
||||
graph.addLEdge(SourceVertex(source), edge.to.value)(edge.label)
|
||||
case None ⇒ throw new IllegalArgumentException(s"No matching UndefinedSource [${token}]")
|
||||
graph.addLEdge(TapVertex(tap), edge.to.value)(edge.label)
|
||||
case None ⇒ throw new IllegalArgumentException(s"No matching UndefinedTap [${token}]")
|
||||
}
|
||||
this
|
||||
}
|
||||
|
|
@ -485,7 +537,7 @@ class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]
|
|||
cyclesAllowed = true
|
||||
}
|
||||
|
||||
private def checkAddSourceSinkPrecondition(node: Vertex): Unit =
|
||||
private def checkAddTapDrainPrecondition(node: Vertex): Unit =
|
||||
require(graph.find(node) == None, s"[$node] instance is already used in this flow graph")
|
||||
|
||||
private def checkJunctionInPortPrecondition(junction: JunctionInPort[_]): Unit = {
|
||||
|
|
@ -534,8 +586,10 @@ class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]
|
|||
}
|
||||
|
||||
//convert it to an immutable.Graph
|
||||
private def immutableGraph(): ImmutableGraph[Vertex, LkDiEdge] =
|
||||
ImmutableGraph.from(edges = graph.edges.map(e ⇒ LkDiEdge(e.from.value, e.to.value)(e.label)).toIterable)
|
||||
private def immutableGraph(): ImmutableGraph[Vertex, FlowGraphInternal.EdgeType] = {
|
||||
val edges = graph.edges.map(e ⇒ LkDiEdge(e.from.value, e.to.value)(e.label))
|
||||
ImmutableGraph.from(edges = edges: Iterable[FlowGraphInternal.EdgeType[FlowGraphInternal.Vertex]])
|
||||
}
|
||||
|
||||
private def checkPartialBuildPreconditions(): Unit = {
|
||||
if (!cyclesAllowed) graph.findCycle match {
|
||||
|
|
@ -545,18 +599,18 @@ class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]
|
|||
}
|
||||
|
||||
private def checkBuildPreconditions(): Unit = {
|
||||
val undefinedSourcesSinks = graph.nodes.filter {
|
||||
val undefinedTapsDrains = graph.nodes.filter {
|
||||
_.value match {
|
||||
case _: UndefinedSource[_] | _: UndefinedSink[_] ⇒ true
|
||||
case x ⇒ false
|
||||
case _: UndefinedTap[_] | _: UndefinedDrain[_] ⇒ true
|
||||
case x ⇒ false
|
||||
}
|
||||
}
|
||||
if (undefinedSourcesSinks.nonEmpty) {
|
||||
val formatted = undefinedSourcesSinks.map(n ⇒ n.value match {
|
||||
case u: UndefinedSource[_] ⇒ s"$u -> ${n.outgoing.head.label} -> ${n.outgoing.head.to}"
|
||||
case u: UndefinedSink[_] ⇒ s"${n.incoming.head.from} -> ${n.incoming.head.label} -> $u"
|
||||
if (undefinedTapsDrains.nonEmpty) {
|
||||
val formatted = undefinedTapsDrains.map(n ⇒ n.value match {
|
||||
case u: UndefinedTap[_] ⇒ s"$u -> ${n.outgoing.head.label} -> ${n.outgoing.head.to}"
|
||||
case u: UndefinedDrain[_] ⇒ s"${n.incoming.head.from} -> ${n.incoming.head.label} -> $u"
|
||||
})
|
||||
throw new IllegalArgumentException("Undefined sources or sinks: " + formatted.mkString(", "))
|
||||
throw new IllegalArgumentException("Undefined taps or drains: " + formatted.mkString(", "))
|
||||
}
|
||||
|
||||
graph.nodes.foreach { node ⇒
|
||||
|
|
@ -580,9 +634,9 @@ class FlowGraphBuilder private (graph: Graph[FlowGraphInternal.Vertex, LkDiEdge]
|
|||
|
||||
require(graph.nonEmpty, "Graph must not be empty")
|
||||
require(graph.exists(graph having ((node = { n ⇒ n.isLeaf && n.diSuccessors.isEmpty }))),
|
||||
"Graph must have at least one sink")
|
||||
"Graph must have at least one drain")
|
||||
require(graph.exists(graph having ((node = { n ⇒ n.isLeaf && n.diPredecessors.isEmpty }))),
|
||||
"Graph must have at least one source")
|
||||
"Graph must have at least one tap")
|
||||
|
||||
require(graph.isConnected, "Graph must be connected")
|
||||
}
|
||||
|
|
@ -600,12 +654,12 @@ object FlowGraph {
|
|||
* Build a [[FlowGraph]] from scratch.
|
||||
*/
|
||||
def apply(block: FlowGraphBuilder ⇒ Unit): FlowGraph =
|
||||
apply(ImmutableGraph.empty[FlowGraphInternal.Vertex, LkDiEdge])(block)
|
||||
apply(ImmutableGraph.empty[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])(block)
|
||||
|
||||
/**
|
||||
* Continue building a [[FlowGraph]] from an existing `PartialFlowGraph`.
|
||||
* For example you can attach undefined sources and sinks with
|
||||
* [[FlowGraphBuilder#attachSource]] and [[FlowGraphBuilder#attachSink]]
|
||||
* For example you can attach undefined taps and drains with
|
||||
* [[FlowGraphBuilder#attachTap]] and [[FlowGraphBuilder#attachDrain]]
|
||||
*/
|
||||
def apply(partialFlowGraph: PartialFlowGraph)(block: FlowGraphBuilder ⇒ Unit): FlowGraph =
|
||||
apply(partialFlowGraph.graph)(block)
|
||||
|
|
@ -617,7 +671,7 @@ object FlowGraph {
|
|||
def apply(flowGraph: FlowGraph)(block: FlowGraphBuilder ⇒ Unit): FlowGraph =
|
||||
apply(flowGraph.graph)(block)
|
||||
|
||||
private def apply(graph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge])(block: FlowGraphBuilder ⇒ Unit): FlowGraph = {
|
||||
private def apply(graph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])(block: FlowGraphBuilder ⇒ Unit): FlowGraph = {
|
||||
val builder = new FlowGraphBuilder(graph)
|
||||
block(builder)
|
||||
builder.build()
|
||||
|
|
@ -630,23 +684,23 @@ object FlowGraph {
|
|||
* Build a `FlowGraph` by starting with one of the `apply` methods in
|
||||
* in [[FlowGraph$ companion object]]. Syntactic sugar is provided by [[FlowGraphImplicits]].
|
||||
*/
|
||||
class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge]) {
|
||||
class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType]) {
|
||||
import FlowGraphInternal._
|
||||
|
||||
/**
|
||||
* Materialize the `FlowGraph` and attach all sinks and sources.
|
||||
* Materialize the `FlowGraph` and attach all drains and taps.
|
||||
*/
|
||||
def run()(implicit materializer: FlowMaterializer): MaterializedFlowGraph = {
|
||||
def run()(implicit materializer: FlowMaterializer): MaterializedPipeGraph = {
|
||||
import scalax.collection.GraphTraversal._
|
||||
|
||||
// start with sinks
|
||||
// start with drains
|
||||
val startingNodes = graph.nodes.filter(n ⇒ n.isLeaf && n.diSuccessors.isEmpty)
|
||||
|
||||
case class Memo(visited: Set[graph.EdgeT] = Set.empty,
|
||||
downstreamSubscriber: Map[graph.EdgeT, Subscriber[Any]] = Map.empty,
|
||||
upstreamPublishers: Map[graph.EdgeT, Publisher[Any]] = Map.empty,
|
||||
sources: Map[SourceVertex, FlowWithSink[Any, Any]] = Map.empty,
|
||||
materializedSinks: Map[SinkWithKey[_, _], Any] = Map.empty)
|
||||
taps: Map[TapVertex, SinkPipe[Any]] = Map.empty,
|
||||
materializedDrains: Map[DrainWithKey[_, _], Any] = Map.empty)
|
||||
|
||||
val result = startingNodes.foldLeft(Memo()) {
|
||||
case (memo, start) ⇒
|
||||
|
|
@ -658,38 +712,38 @@ class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraph
|
|||
if (memo.visited(edge)) {
|
||||
memo
|
||||
} else {
|
||||
val flow = edge.label.asInstanceOf[EdgeLabel].flow
|
||||
val pipe = edge.label.pipe
|
||||
|
||||
// returns the materialized sink, if any
|
||||
def connectToDownstream(publisher: Publisher[Any]): Option[(SinkWithKey[_, _], Any)] = {
|
||||
val f = flow.withSource(PublisherSource(publisher))
|
||||
// returns the materialized drain, if any
|
||||
def connectToDownstream(publisher: Publisher[Any]): Option[(DrainWithKey[_, _], Any)] = {
|
||||
val f = pipe.withTap(PublisherTap(publisher))
|
||||
edge.to.value match {
|
||||
case SinkVertex(sink: SinkWithKey[_, _]) ⇒
|
||||
val mf = f.withSink(sink.asInstanceOf[Sink[Any]]).run()
|
||||
Some(sink -> mf.getSinkFor(sink))
|
||||
case SinkVertex(sink) ⇒
|
||||
f.withSink(sink.asInstanceOf[Sink[Any]]).run()
|
||||
case DrainVertex(drain: DrainWithKey[_, _]) ⇒
|
||||
val mf = f.withDrain(drain).run()
|
||||
Some(drain -> mf.getDrainFor(drain))
|
||||
case DrainVertex(drain) ⇒
|
||||
f.withDrain(drain).run()
|
||||
None
|
||||
case _ ⇒
|
||||
f.withSink(SubscriberSink(memo.downstreamSubscriber(edge))).run()
|
||||
f.withDrain(SubscriberDrain(memo.downstreamSubscriber(edge))).run()
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
edge.from.value match {
|
||||
case src: SourceVertex ⇒
|
||||
val f = flow.withSink(SubscriberSink(memo.downstreamSubscriber(edge)))
|
||||
// connect the source with the flow later
|
||||
case src: TapVertex ⇒
|
||||
val f = pipe.withDrain(SubscriberDrain(memo.downstreamSubscriber(edge)))
|
||||
// connect the tap with the pipe later
|
||||
memo.copy(visited = memo.visited + edge,
|
||||
sources = memo.sources.updated(src, f))
|
||||
taps = memo.taps.updated(src, f))
|
||||
|
||||
case v: InternalVertex ⇒
|
||||
if (memo.upstreamPublishers.contains(edge)) {
|
||||
// vertex already materialized
|
||||
val materializedSink = connectToDownstream(memo.upstreamPublishers(edge))
|
||||
val materializedDrain = connectToDownstream(memo.upstreamPublishers(edge))
|
||||
memo.copy(
|
||||
visited = memo.visited + edge,
|
||||
materializedSinks = memo.materializedSinks ++ materializedSink)
|
||||
materializedDrains = memo.materializedDrains ++ materializedDrain)
|
||||
} else {
|
||||
|
||||
val op = v.astNode
|
||||
|
|
@ -697,16 +751,16 @@ class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraph
|
|||
materializer.materializeJunction[Any, Any](op, edge.from.inDegree, edge.from.outDegree)
|
||||
// TODO: Check for gaps in port numbers
|
||||
val edgeSubscribers =
|
||||
edge.from.incoming.toSeq.sortBy(_.label.asInstanceOf[EdgeLabel].inputPort).zip(subscribers)
|
||||
edge.from.incoming.toSeq.sortBy(_.label.inputPort).zip(subscribers)
|
||||
val edgePublishers =
|
||||
edge.from.outgoing.toSeq.sortBy(_.label.asInstanceOf[EdgeLabel].outputPort).zip(publishers).toMap
|
||||
edge.from.outgoing.toSeq.sortBy(_.label.outputPort).zip(publishers).toMap
|
||||
val publisher = edgePublishers(edge)
|
||||
val materializedSink = connectToDownstream(publisher)
|
||||
val materializedDrain = connectToDownstream(publisher)
|
||||
memo.copy(
|
||||
visited = memo.visited + edge,
|
||||
downstreamSubscriber = memo.downstreamSubscriber ++ edgeSubscribers,
|
||||
upstreamPublishers = memo.upstreamPublishers ++ edgePublishers,
|
||||
materializedSinks = memo.materializedSinks ++ materializedSink)
|
||||
materializedDrains = memo.materializedDrains ++ materializedDrain)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -716,17 +770,17 @@ class FlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraph
|
|||
|
||||
}
|
||||
|
||||
// connect all input sources as the last thing
|
||||
val materializedSources = result.sources.foldLeft(Map.empty[SourceWithKey[_, _], Any]) {
|
||||
case (acc, (SourceVertex(src), flow)) ⇒
|
||||
val mf = flow.withSource(src).run()
|
||||
// connect all input taps as the last thing
|
||||
val materializedTaps = result.taps.foldLeft(Map.empty[TapWithKey[_, _], Any]) {
|
||||
case (acc, (TapVertex(src), pipe)) ⇒
|
||||
val mf = pipe.withTap(src).run()
|
||||
src match {
|
||||
case srcKey: SourceWithKey[_, _] ⇒ acc.updated(srcKey, mf.getSourceFor(srcKey))
|
||||
case _ ⇒ acc
|
||||
case srcKey: TapWithKey[_, _] ⇒ acc.updated(srcKey, mf.getTapFor(srcKey))
|
||||
case _ ⇒ acc
|
||||
}
|
||||
}
|
||||
|
||||
new MaterializedFlowGraph(materializedSources, result.materializedSinks)
|
||||
new MaterializedPipeGraph(materializedTaps, result.materializedDrains)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -742,7 +796,7 @@ object PartialFlowGraph {
|
|||
* Build a [[PartialFlowGraph]] from scratch.
|
||||
*/
|
||||
def apply(block: FlowGraphBuilder ⇒ Unit): PartialFlowGraph =
|
||||
apply(ImmutableGraph.empty[FlowGraphInternal.Vertex, LkDiEdge])(block)
|
||||
apply(ImmutableGraph.empty[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])(block)
|
||||
|
||||
/**
|
||||
* Continue building a [[PartialFlowGraph]] from an existing `PartialFlowGraph`.
|
||||
|
|
@ -756,7 +810,7 @@ object PartialFlowGraph {
|
|||
def apply(flowGraph: FlowGraph)(block: FlowGraphBuilder ⇒ Unit): PartialFlowGraph =
|
||||
apply(flowGraph.graph)(block)
|
||||
|
||||
private def apply(graph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge])(block: FlowGraphBuilder ⇒ Unit): PartialFlowGraph = {
|
||||
private def apply(graph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType])(block: FlowGraphBuilder ⇒ Unit): PartialFlowGraph = {
|
||||
val builder = new FlowGraphBuilder(graph)
|
||||
block(builder)
|
||||
builder.partialBuild()
|
||||
|
|
@ -765,53 +819,53 @@ object PartialFlowGraph {
|
|||
}
|
||||
|
||||
/**
|
||||
* `PartialFlowGraph` may have sources and sinks that are not attached, and it can therefore not
|
||||
* `PartialFlowGraph` may have taps and drains that are not attached, and it can therefore not
|
||||
* be `run` until those are attached.
|
||||
*
|
||||
* Build a `PartialFlowGraph` by starting with one of the `apply` methods in
|
||||
* in [[FlowGraph$ companion object]]. Syntactic sugar is provided by [[FlowGraphImplicits]].
|
||||
*/
|
||||
class PartialFlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraphInternal.Vertex, LkDiEdge]) {
|
||||
class PartialFlowGraph private[akka] (private[akka] val graph: ImmutableGraph[FlowGraphInternal.Vertex, FlowGraphInternal.EdgeType]) {
|
||||
import FlowGraphInternal._
|
||||
|
||||
def undefinedSources: Set[UndefinedSource[_]] =
|
||||
graph.nodes.collect {
|
||||
case n if n.value.isInstanceOf[UndefinedSource[_]] ⇒ n.value.asInstanceOf[UndefinedSource[_]]
|
||||
}(collection.breakOut)
|
||||
def undefinedTaps: Set[UndefinedTap[_]] =
|
||||
graph.nodes.iterator.map(_.value).collect {
|
||||
case n: UndefinedTap[_] ⇒ n
|
||||
}.toSet
|
||||
|
||||
def undefinedSinks: Set[UndefinedSink[_]] =
|
||||
graph.nodes.collect {
|
||||
case n if n.value.isInstanceOf[UndefinedSink[_]] ⇒ n.value.asInstanceOf[UndefinedSink[_]]
|
||||
}(collection.breakOut)
|
||||
def undefinedDrains: Set[UndefinedDrain[_]] =
|
||||
graph.nodes.iterator.map(_.value).collect {
|
||||
case n: UndefinedDrain[_] ⇒ n
|
||||
}.toSet
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned by [[FlowGraph#run]] and can be used as parameter to the
|
||||
* accessor method to retrieve the materialized `Source` or `Sink`, e.g.
|
||||
* [[SubscriberSource#subscriber]] or [[PublisherSink#publisher]].
|
||||
* accessor method to retrieve the materialized `Tap` or `Drain`, e.g.
|
||||
* [[SubscriberTap#subscriber]] or [[PublisherDrain#publisher]].
|
||||
*/
|
||||
class MaterializedFlowGraph(materializedSources: Map[SourceWithKey[_, _], Any], materializedSinks: Map[SinkWithKey[_, _], Any])
|
||||
extends MaterializedSource with MaterializedSink {
|
||||
class MaterializedPipeGraph(materializedTaps: Map[TapWithKey[_, _], Any], materializedDrains: Map[DrainWithKey[_, _], Any])
|
||||
extends MaterializedTap with MaterializedDrain {
|
||||
|
||||
/**
|
||||
* Do not call directly. Use accessor method in the concrete `Source`, e.g. [[SubscriberSource#subscriber]].
|
||||
* Do not call directly. Use accessor method in the concrete `Tap`, e.g. [[SubscriberTap#subscriber]].
|
||||
*/
|
||||
override def getSourceFor[T](key: SourceWithKey[_, T]): T =
|
||||
materializedSources.get(key) match {
|
||||
case Some(matSource) ⇒ matSource.asInstanceOf[T]
|
||||
override def getTapFor[T](key: TapWithKey[_, T]): T =
|
||||
materializedTaps.get(key) match {
|
||||
case Some(matTap) ⇒ matTap.asInstanceOf[T]
|
||||
case None ⇒
|
||||
throw new IllegalArgumentException(s"Source key [$key] doesn't exist in this flow graph")
|
||||
throw new IllegalArgumentException(s"Tap key [$key] doesn't exist in this flow graph")
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not call directly. Use accessor method in the concrete `Sink`, e.g. [[PublisherSink#publisher]].
|
||||
* Do not call directly. Use accessor method in the concrete `Drain`, e.g. [[PublisherDrain#publisher]].
|
||||
*/
|
||||
def getSinkFor[T](key: SinkWithKey[_, T]): T =
|
||||
materializedSinks.get(key) match {
|
||||
case Some(matSink) ⇒ matSink.asInstanceOf[T]
|
||||
def getDrainFor[T](key: DrainWithKey[_, T]): T =
|
||||
materializedDrains.get(key) match {
|
||||
case Some(matDrain) ⇒ matDrain.asInstanceOf[T]
|
||||
case None ⇒
|
||||
throw new IllegalArgumentException(s"Sink key [$key] doesn't exist in this flow graph")
|
||||
throw new IllegalArgumentException(s"Drain key [$key] doesn't exist in this flow graph")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -819,82 +873,70 @@ class MaterializedFlowGraph(materializedSources: Map[SourceWithKey[_, _], Any],
|
|||
* Implicit conversions that provides syntactic sugar for building flow graphs.
|
||||
*/
|
||||
object FlowGraphImplicits {
|
||||
implicit class SourceOps[In](val source: Source[In]) extends AnyVal {
|
||||
def ~>[Out](flow: ProcessorFlow[In, Out])(implicit builder: FlowGraphBuilder): SourceNextStep[In, Out] = {
|
||||
new SourceNextStep(source, flow, builder)
|
||||
}
|
||||
|
||||
def ~>(sink: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[sink.NextT] = {
|
||||
builder.addEdge(source, ProcessorFlow.empty[In], sink)
|
||||
sink.next
|
||||
}
|
||||
}
|
||||
|
||||
class SourceNextStep[In, Out](source: Source[In], flow: ProcessorFlow[In, Out], builder: FlowGraphBuilder) {
|
||||
def ~>(sink: JunctionInPort[Out]): JunctionOutPort[sink.NextT] = {
|
||||
builder.addEdge(source, flow, sink)
|
||||
sink.next
|
||||
class SourceNextStep[In, Out](source: Source[In], flow: Flow[In, Out], builder: FlowGraphBuilder) {
|
||||
def ~>(drain: JunctionInPort[Out]): JunctionOutPort[drain.NextT] = {
|
||||
builder.addEdge(source, flow, drain)
|
||||
drain.next
|
||||
}
|
||||
}
|
||||
|
||||
implicit class JunctionOps[In](val junction: JunctionOutPort[In]) extends AnyVal {
|
||||
def ~>[Out](flow: ProcessorFlow[In, Out])(implicit builder: FlowGraphBuilder): JunctionNextStep[In, Out] = {
|
||||
def ~>[Out](flow: Flow[In, Out])(implicit builder: FlowGraphBuilder): JunctionNextStep[In, Out] =
|
||||
new JunctionNextStep(junction, flow, builder)
|
||||
|
||||
def ~>(drain: UndefinedDrain[In])(implicit builder: FlowGraphBuilder): Unit =
|
||||
builder.addEdge(junction, Pipe.empty[In], drain)
|
||||
|
||||
def ~>(drain: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[drain.NextT] = {
|
||||
builder.addEdge(junction, Pipe.empty[In], drain)
|
||||
drain.next
|
||||
}
|
||||
|
||||
def ~>(sink: Sink[In])(implicit builder: FlowGraphBuilder): Unit =
|
||||
builder.addEdge(junction, ProcessorFlow.empty[In], sink)
|
||||
|
||||
def ~>(sink: UndefinedSink[In])(implicit builder: FlowGraphBuilder): Unit =
|
||||
builder.addEdge(junction, ProcessorFlow.empty[In], sink)
|
||||
|
||||
def ~>(sink: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[sink.NextT] = {
|
||||
builder.addEdge(junction, ProcessorFlow.empty[In], sink)
|
||||
sink.next
|
||||
}
|
||||
|
||||
def ~>(flow: FlowWithSink[In, _])(implicit builder: FlowGraphBuilder): Unit =
|
||||
builder.addEdge(junction, flow)
|
||||
def ~>(sink: Sink[In])(implicit builder: FlowGraphBuilder): Unit = builder.addEdge(junction, sink)
|
||||
}
|
||||
|
||||
class JunctionNextStep[In, Out](junction: JunctionOutPort[In], flow: ProcessorFlow[In, Out], builder: FlowGraphBuilder) {
|
||||
def ~>(sink: JunctionInPort[Out]): JunctionOutPort[sink.NextT] = {
|
||||
builder.addEdge(junction, flow, sink)
|
||||
sink.next
|
||||
class JunctionNextStep[In, Out](junction: JunctionOutPort[In], flow: Flow[In, Out], builder: FlowGraphBuilder) {
|
||||
def ~>(drain: JunctionInPort[Out]): JunctionOutPort[drain.NextT] = {
|
||||
builder.addEdge(junction, flow, drain)
|
||||
drain.next
|
||||
}
|
||||
|
||||
def ~>(sink: Sink[Out]): Unit = {
|
||||
builder.addEdge(junction, flow, sink)
|
||||
}
|
||||
|
||||
def ~>(sink: UndefinedSink[Out]): Unit = {
|
||||
builder.addEdge(junction, flow, sink)
|
||||
def ~>(drain: UndefinedDrain[Out]): Unit = {
|
||||
builder.addEdge(junction, flow, drain)
|
||||
}
|
||||
}
|
||||
|
||||
implicit class FlowWithSourceOps[In, Out](val flow: FlowWithSource[In, Out]) extends AnyVal {
|
||||
def ~>(sink: JunctionInPort[Out])(implicit builder: FlowGraphBuilder): JunctionOutPort[sink.NextT] = {
|
||||
builder.addEdge(flow, sink)
|
||||
sink.next
|
||||
implicit class SourceOps[Out](val source: Source[Out]) extends AnyVal {
|
||||
|
||||
def ~>[O](flow: Flow[Out, O])(implicit builder: FlowGraphBuilder): SourceNextStep[Out, O] =
|
||||
new SourceNextStep(source, flow, builder)
|
||||
|
||||
def ~>(drain: JunctionInPort[Out])(implicit builder: FlowGraphBuilder): JunctionOutPort[drain.NextT] = {
|
||||
builder.addEdge(source, drain)
|
||||
drain.next
|
||||
}
|
||||
}
|
||||
|
||||
implicit class UndefinedSourceOps[In](val source: UndefinedSource[In]) extends AnyVal {
|
||||
def ~>[Out](flow: ProcessorFlow[In, Out])(implicit builder: FlowGraphBuilder): UndefinedSourceNextStep[In, Out] = {
|
||||
new UndefinedSourceNextStep(source, flow, builder)
|
||||
}
|
||||
implicit class UndefinedTapOps[In](val tap: UndefinedTap[In]) extends AnyVal {
|
||||
def ~>[Out](flow: Flow[In, Out])(implicit builder: FlowGraphBuilder): UndefinedTapNextStep[In, Out] =
|
||||
new UndefinedTapNextStep(tap, flow, builder)
|
||||
|
||||
def ~>(sink: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[sink.NextT] = {
|
||||
builder.addEdge(source, ProcessorFlow.empty[In], sink)
|
||||
sink.next
|
||||
def ~>(drain: JunctionInPort[In])(implicit builder: FlowGraphBuilder): JunctionOutPort[drain.NextT] = {
|
||||
builder.addEdge(tap, Pipe.empty[In], drain)
|
||||
drain.next
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UndefinedSourceNextStep[In, Out](source: UndefinedSource[In], flow: ProcessorFlow[In, Out], builder: FlowGraphBuilder) {
|
||||
def ~>(sink: JunctionInPort[Out]): JunctionOutPort[sink.NextT] = {
|
||||
builder.addEdge(source, flow, sink)
|
||||
sink.next
|
||||
class UndefinedTapNextStep[In, Out](tap: UndefinedTap[In], flow: Flow[In, Out], builder: FlowGraphBuilder) {
|
||||
def ~>(drain: JunctionInPort[Out]): JunctionOutPort[drain.NextT] = {
|
||||
builder.addEdge(tap, flow, drain)
|
||||
drain.next
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ abstract class FlowMaterializer(val settings: MaterializerSettings) {
|
|||
* stream. The result can be highly implementation specific, ranging from
|
||||
* local actor chains to remote-deployed processing networks.
|
||||
*/
|
||||
def materialize[In, Out](source: Source[In], sink: Sink[Out], ops: List[Ast.AstNode]): MaterializedFlow
|
||||
def materialize[In, Out](tap: Tap[In], drain: Drain[Out], ops: List[Ast.AstNode]): MaterializedPipe
|
||||
|
||||
/**
|
||||
* Create publishers and subscribers for fan-in and fan-out operations.
|
||||
|
|
|
|||
330
akka-stream/src/main/scala/akka/stream/scaladsl2/Pipe.scala
Normal file
330
akka-stream/src/main/scala/akka/stream/scaladsl2/Pipe.scala
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.stream.scaladsl2
|
||||
|
||||
import scala.collection.immutable
|
||||
import akka.stream.impl2.Ast._
|
||||
import org.reactivestreams._
|
||||
import scala.concurrent.Future
|
||||
import akka.stream.Transformer
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.concurrent.duration.Duration
|
||||
import akka.util.Collections.EmptyImmutableSeq
|
||||
import akka.stream.TimerTransformer
|
||||
import akka.stream.OverflowStrategy
|
||||
|
||||
import scala.annotation.unchecked.uncheckedVariance
|
||||
import scala.language.higherKinds
|
||||
import scala.language.existentials
|
||||
|
||||
private[scaladsl2] object PipeOps {
|
||||
private case object TakeWithinTimerKey
|
||||
private case object DropWithinTimerKey
|
||||
private case object GroupedWithinTimerKey
|
||||
|
||||
private val takeCompletedTransformer: Transformer[Any, Any] = new Transformer[Any, Any] {
|
||||
override def onNext(elem: Any) = Nil
|
||||
override def isComplete = true
|
||||
}
|
||||
|
||||
private val identityTransformer: Transformer[Any, Any] = new Transformer[Any, Any] {
|
||||
override def onNext(elem: Any) = List(elem)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scala API: Operations offered by flows with a free output side: the DSL flows left-to-right only.
|
||||
*/
|
||||
private[scaladsl2] trait PipeOps[+Out] extends FlowOps[Out] {
|
||||
import PipeOps._
|
||||
type Repr[+O]
|
||||
|
||||
// Storing ops in reverse order
|
||||
protected def andThen[U](op: AstNode): Repr[U]
|
||||
|
||||
override def map[T](f: Out ⇒ T): Repr[T] =
|
||||
transform("map", () ⇒ new Transformer[Out, T] {
|
||||
override def onNext(in: Out) = List(f(in))
|
||||
})
|
||||
|
||||
override def mapConcat[T](f: Out ⇒ immutable.Seq[T]): Repr[T] =
|
||||
transform("mapConcat", () ⇒ new Transformer[Out, T] {
|
||||
override def onNext(in: Out) = f(in)
|
||||
})
|
||||
|
||||
override def mapAsync[T](f: Out ⇒ Future[T]): Repr[T] =
|
||||
andThen(MapAsync(f.asInstanceOf[Any ⇒ Future[Any]]))
|
||||
|
||||
override def mapAsyncUnordered[T](f: Out ⇒ Future[T]): Repr[T] =
|
||||
andThen(MapAsyncUnordered(f.asInstanceOf[Any ⇒ Future[Any]]))
|
||||
|
||||
override def filter(p: Out ⇒ Boolean): Repr[Out] =
|
||||
transform("filter", () ⇒ new Transformer[Out, Out] {
|
||||
override def onNext(in: Out) = if (p(in)) List(in) else Nil
|
||||
})
|
||||
|
||||
override def collect[T](pf: PartialFunction[Out, T]): Repr[T] =
|
||||
transform("collect", () ⇒ new Transformer[Out, T] {
|
||||
override def onNext(in: Out) = if (pf.isDefinedAt(in)) List(pf(in)) else Nil
|
||||
})
|
||||
|
||||
override def grouped(n: Int): Repr[immutable.Seq[Out]] = {
|
||||
require(n > 0, "n must be greater than 0")
|
||||
transform("grouped", () ⇒ new Transformer[Out, immutable.Seq[Out]] {
|
||||
var buf: Vector[Out] = Vector.empty
|
||||
override def onNext(in: Out) = {
|
||||
buf :+= in
|
||||
if (buf.size == n) {
|
||||
val group = buf
|
||||
buf = Vector.empty
|
||||
List(group)
|
||||
} else
|
||||
Nil
|
||||
}
|
||||
override def onTermination(e: Option[Throwable]) = if (buf.isEmpty) Nil else List(buf)
|
||||
})
|
||||
}
|
||||
|
||||
override def groupedWithin(n: Int, d: FiniteDuration): Repr[immutable.Seq[Out]] = {
|
||||
require(n > 0, "n must be greater than 0")
|
||||
require(d > Duration.Zero)
|
||||
timerTransform("groupedWithin", () ⇒ new TimerTransformer[Out, immutable.Seq[Out]] {
|
||||
schedulePeriodically(GroupedWithinTimerKey, d)
|
||||
var buf: Vector[Out] = Vector.empty
|
||||
|
||||
override def onNext(in: Out) = {
|
||||
buf :+= in
|
||||
if (buf.size == n) {
|
||||
// start new time window
|
||||
schedulePeriodically(GroupedWithinTimerKey, d)
|
||||
emitGroup()
|
||||
} else Nil
|
||||
}
|
||||
override def onTermination(e: Option[Throwable]) = if (buf.isEmpty) Nil else List(buf)
|
||||
override def onTimer(timerKey: Any) = emitGroup()
|
||||
private def emitGroup(): immutable.Seq[immutable.Seq[Out]] =
|
||||
if (buf.isEmpty) EmptyImmutableSeq
|
||||
else {
|
||||
val group = buf
|
||||
buf = Vector.empty
|
||||
List(group)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override def drop(n: Int): Repr[Out] =
|
||||
transform("drop", () ⇒ new Transformer[Out, Out] {
|
||||
var delegate: Transformer[Out, Out] =
|
||||
if (n <= 0) identityTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
else new Transformer[Out, Out] {
|
||||
var c = n
|
||||
override def onNext(in: Out) = {
|
||||
c -= 1
|
||||
if (c == 0)
|
||||
delegate = identityTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
override def onNext(in: Out) = delegate.onNext(in)
|
||||
})
|
||||
|
||||
override def dropWithin(d: FiniteDuration): Repr[Out] =
|
||||
timerTransform("dropWithin", () ⇒ new TimerTransformer[Out, Out] {
|
||||
scheduleOnce(DropWithinTimerKey, d)
|
||||
|
||||
var delegate: Transformer[Out, Out] =
|
||||
new Transformer[Out, Out] {
|
||||
override def onNext(in: Out) = Nil
|
||||
}
|
||||
|
||||
override def onNext(in: Out) = delegate.onNext(in)
|
||||
override def onTimer(timerKey: Any) = {
|
||||
delegate = identityTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
Nil
|
||||
}
|
||||
})
|
||||
|
||||
override def take(n: Int): Repr[Out] =
|
||||
transform("take", () ⇒ new Transformer[Out, Out] {
|
||||
var delegate: Transformer[Out, Out] =
|
||||
if (n <= 0) takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
else new Transformer[Out, Out] {
|
||||
var c = n
|
||||
override def onNext(in: Out) = {
|
||||
c -= 1
|
||||
if (c == 0)
|
||||
delegate = takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
List(in)
|
||||
}
|
||||
}
|
||||
|
||||
override def onNext(in: Out) = delegate.onNext(in)
|
||||
override def isComplete = delegate.isComplete
|
||||
})
|
||||
|
||||
override def takeWithin(d: FiniteDuration): Repr[Out] =
|
||||
timerTransform("takeWithin", () ⇒ new TimerTransformer[Out, Out] {
|
||||
scheduleOnce(TakeWithinTimerKey, d)
|
||||
|
||||
var delegate: Transformer[Out, Out] = identityTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
|
||||
override def onNext(in: Out) = delegate.onNext(in)
|
||||
override def isComplete = delegate.isComplete
|
||||
override def onTimer(timerKey: Any) = {
|
||||
delegate = takeCompletedTransformer.asInstanceOf[Transformer[Out, Out]]
|
||||
Nil
|
||||
}
|
||||
})
|
||||
|
||||
override def conflate[S](seed: Out ⇒ S, aggregate: (S, Out) ⇒ S): Repr[S] =
|
||||
andThen(Conflate(seed.asInstanceOf[Any ⇒ Any], aggregate.asInstanceOf[(Any, Any) ⇒ Any]))
|
||||
|
||||
override def expand[S, U](seed: Out ⇒ S, extrapolate: S ⇒ (U, S)): Repr[U] =
|
||||
andThen(Expand(seed.asInstanceOf[Any ⇒ Any], extrapolate.asInstanceOf[Any ⇒ (Any, Any)]))
|
||||
|
||||
override def buffer(size: Int, overflowStrategy: OverflowStrategy): Repr[Out] = {
|
||||
require(size > 0, s"Buffer size must be larger than zero but was [$size]")
|
||||
andThen(Buffer(size, overflowStrategy))
|
||||
}
|
||||
|
||||
override def transform[T](name: String, mkTransformer: () ⇒ Transformer[Out, T]): Repr[T] = {
|
||||
andThen(Transform(name, mkTransformer.asInstanceOf[() ⇒ Transformer[Any, Any]]))
|
||||
}
|
||||
|
||||
override def prefixAndTail[U >: Out](n: Int): Repr[(immutable.Seq[Out], Source[U])] =
|
||||
andThen(PrefixAndTail(n))
|
||||
|
||||
override def groupBy[K, U >: Out](f: Out ⇒ K): Repr[(K, Source[U])] =
|
||||
andThen(GroupBy(f.asInstanceOf[Any ⇒ Any]))
|
||||
|
||||
override def splitWhen[U >: Out](p: Out ⇒ Boolean): Repr[Source[U]] =
|
||||
andThen(SplitWhen(p.asInstanceOf[Any ⇒ Boolean]))
|
||||
|
||||
override def flatten[U](strategy: FlattenStrategy[Out, U]): Repr[U] = strategy match {
|
||||
case _: FlattenStrategy.Concat[Out] ⇒ andThen(ConcatAll)
|
||||
case _ ⇒ throw new IllegalArgumentException(s"Unsupported flattening strategy [${strategy.getClass.getSimpleName}]")
|
||||
}
|
||||
|
||||
override def timerTransform[U](name: String, mkTransformer: () ⇒ TimerTransformer[Out, U]): Repr[U] =
|
||||
andThen(TimerTransform(name, mkTransformer.asInstanceOf[() ⇒ TimerTransformer[Any, Any]]))
|
||||
}
|
||||
|
||||
private[scaladsl2] object Pipe {
|
||||
private val emptyInstance = Pipe[Any, Any](ops = Nil)
|
||||
def empty[T]: Pipe[T, T] = emptyInstance.asInstanceOf[Pipe[T, T]]
|
||||
|
||||
val OnlyPipesErrorMessage = "Only pipes are supported currently!"
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow with one open input and one open output..
|
||||
*/
|
||||
private[scaladsl2] final case class Pipe[-In, +Out](ops: List[AstNode]) extends Flow[In, Out] with PipeOps[Out] {
|
||||
override type Repr[+O] = Pipe[In @uncheckedVariance, O]
|
||||
|
||||
override protected def andThen[U](op: AstNode): Repr[U] = this.copy(ops = op :: ops)
|
||||
|
||||
def withDrain(out: Drain[Out]): SinkPipe[In] = SinkPipe(out, ops)
|
||||
|
||||
def withTap(in: Tap[In]): SourcePipe[Out] = SourcePipe(in, ops)
|
||||
|
||||
override def connect[T](flow: Flow[Out, T]): Flow[In, T] = flow match {
|
||||
case p: Pipe[T, In] ⇒ Pipe(p.ops ++: ops)
|
||||
case _ ⇒ throw new IllegalArgumentException(Pipe.OnlyPipesErrorMessage)
|
||||
}
|
||||
|
||||
override def connect(sink: Sink[Out]): Sink[In] = sink match {
|
||||
case sp: SinkPipe[Out] ⇒ sp.prependPipe(this)
|
||||
case d: Drain[Out] ⇒ this.withDrain(d)
|
||||
case _ ⇒ throw new IllegalArgumentException(Pipe.OnlyPipesErrorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pipe with open input and attached output. Can be used as a `Subscriber`.
|
||||
*/
|
||||
private[scaladsl2] final case class SinkPipe[-In](output: Drain[_], ops: List[AstNode]) extends Sink[In] {
|
||||
|
||||
def withTap(in: Tap[In]): RunnablePipe = RunnablePipe(in, output, ops)
|
||||
|
||||
def prependPipe[T](pipe: Pipe[T, In]): SinkPipe[T] = SinkPipe(output, ops ::: pipe.ops)
|
||||
|
||||
override def toSubscriber()(implicit materializer: FlowMaterializer): Subscriber[In @uncheckedVariance] = {
|
||||
val subIn = SubscriberTap[In]()
|
||||
val mf = withTap(subIn).run()
|
||||
subIn.subscriber(mf)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pipe with open output and attached input. Can be used as a `Publisher`.
|
||||
*/
|
||||
private[scaladsl2] final case class SourcePipe[+Out](input: Tap[_], ops: List[AstNode]) extends Source[Out] with PipeOps[Out] {
|
||||
override type Repr[+O] = SourcePipe[O]
|
||||
|
||||
override protected def andThen[U](op: AstNode): Repr[U] = SourcePipe(input, op :: ops)
|
||||
|
||||
def withDrain(out: Drain[Out]): RunnablePipe = RunnablePipe(input, out, ops)
|
||||
|
||||
def appendPipe[T](pipe: Pipe[Out, T]): SourcePipe[T] = SourcePipe(input, pipe.ops ++: ops)
|
||||
|
||||
override def connect[T](flow: Flow[Out, T]): Source[T] = flow match {
|
||||
case p: Pipe[Out, T] ⇒ appendPipe(p)
|
||||
case _ ⇒ throw new IllegalArgumentException(Pipe.OnlyPipesErrorMessage)
|
||||
}
|
||||
|
||||
override def connect(sink: Sink[Out]): RunnableFlow = sink match {
|
||||
case sp: SinkPipe[Out] ⇒ RunnablePipe(input, sp.output, sp.ops ++: ops)
|
||||
case d: Drain[Out] ⇒ this.withDrain(d)
|
||||
case _ ⇒ throw new IllegalArgumentException(Pipe.OnlyPipesErrorMessage)
|
||||
}
|
||||
|
||||
override def toPublisher()(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] = {
|
||||
val pubOut = PublisherDrain[Out]
|
||||
val mf = withDrain(pubOut).run()
|
||||
pubOut.publisher(mf)
|
||||
}
|
||||
|
||||
override def toFanoutPublisher(initialBufferSize: Int, maximumBufferSize: Int)(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] = {
|
||||
val pubOut = PublisherDrain.withFanout[Out](initialBufferSize, maximumBufferSize)
|
||||
val mf = withDrain(pubOut).run()
|
||||
pubOut.publisher(mf)
|
||||
}
|
||||
|
||||
override def publishTo(subscriber: Subscriber[Out @uncheckedVariance])(implicit materializer: FlowMaterializer): Unit =
|
||||
toPublisher().subscribe(subscriber)
|
||||
|
||||
override def consume()(implicit materializer: FlowMaterializer): Unit =
|
||||
withDrain(BlackholeDrain).run()
|
||||
}
|
||||
|
||||
/**
|
||||
* Pipe with attached input and output, can be executed.
|
||||
*/
|
||||
private[scaladsl2] final case class RunnablePipe(input: Tap[_], output: Drain[_], ops: List[AstNode]) extends RunnableFlow {
|
||||
def run()(implicit materializer: FlowMaterializer): MaterializedPipe =
|
||||
materializer.materialize(input, output, ops)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned by [[RunnablePipe#run]] and can be used as parameter to the
|
||||
* accessor method to retrieve the materialized `Tap` or `Drain`, e.g.
|
||||
* [[SubscriberTap#subscriber]] or [[PublisherDrain#publisher]].
|
||||
*/
|
||||
private[stream] class MaterializedPipe(tapKey: AnyRef, matTap: Any, drainKey: AnyRef, matDrain: Any) extends MaterializedFlow {
|
||||
/**
|
||||
* Do not call directly. Use accessor method in the concrete `Tap`, e.g. [[SubscriberTap#subscriber]].
|
||||
*/
|
||||
override def getTapFor[T](key: TapWithKey[_, T]): T =
|
||||
if (key == tapKey) matTap.asInstanceOf[T]
|
||||
else throw new IllegalArgumentException(s"Tap key [$key] doesn't match the tap [$tapKey] of this flow")
|
||||
|
||||
/**
|
||||
* Do not call directly. Use accessor method in the concrete `Drain`, e.g. [[PublisherDrain#publisher]].
|
||||
*/
|
||||
def getDrainFor[T](key: DrainWithKey[_, T]): T =
|
||||
if (key == drainKey) matDrain.asInstanceOf[T]
|
||||
else throw new IllegalArgumentException(s"Drain key [$key] doesn't match the drain [$drainKey] of this flow")
|
||||
}
|
||||
|
|
@ -3,263 +3,15 @@
|
|||
*/
|
||||
package akka.stream.scaladsl2
|
||||
|
||||
import akka.actor.Props
|
||||
import org.reactivestreams.Subscriber
|
||||
|
||||
import scala.collection.immutable
|
||||
import scala.language.implicitConversions
|
||||
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.{ FanoutProcessorImpl, BlackholeSubscriber }
|
||||
import akka.stream.impl2.{ ActorProcessorFactory, 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 [[akka.stream.impl2.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).
|
||||
* A `Sink` is a set of stream processing steps that has one open input and an attached output.
|
||||
* Can be used as a `Subscriber`
|
||||
*/
|
||||
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
|
||||
|
||||
trait Sink[-In] {
|
||||
def toSubscriber()(implicit materializer: FlowMaterializer): Subscriber[In @uncheckedVariance]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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]]
|
||||
def withFanout[T](initialBufferSize: Int, maximumBufferSize: Int): FanoutPublisherSink[T] =
|
||||
new FanoutPublisherSink[T](initialBufferSize, maximumBufferSize)
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
class FanoutPublisherSink[Out](initialBufferSize: Int, maximumBufferSize: Int) extends SinkWithKey[Out, Publisher[Out]] {
|
||||
def publisher(m: MaterializedSink): Publisher[Out] = m.getSinkFor(this)
|
||||
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] = {
|
||||
val fanoutActor = materializer.actorOf(
|
||||
Props(new FanoutProcessorImpl(materializer.settings, initialBufferSize, maximumBufferSize)), s"$flowName-fanoutPublisher")
|
||||
val fanoutProcessor = ActorProcessorFactory[Out, Out](fanoutActor)
|
||||
flowPublisher.subscribe(fanoutProcessor)
|
||||
fanoutProcessor
|
||||
}
|
||||
|
||||
override def toString: String = "Fanout"
|
||||
}
|
||||
|
||||
object FutureSink {
|
||||
def apply[T]: FutureSink[T] = new FutureSink[T]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
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) = ()
|
||||
override def onTermination(e: Option[Throwable]) = {
|
||||
e match {
|
||||
case None ⇒ callback(OnCompleteSink.SuccessUnit)
|
||||
case Some(e) ⇒ callback(Failure(e))
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given function for every received element, giving it its previous
|
||||
* output (or the given `zero` value) and the element as input. The sink holds a
|
||||
* [[scala.concurrent.Future]] that will be completed with value of the final
|
||||
* function evaluation when the input stream ends, or completed with `Failure`
|
||||
* if there is an error is signaled in the stream.
|
||||
*/
|
||||
final case class FoldSink[U, Out](zero: U)(f: (U, Out) ⇒ U) extends SinkWithKey[Out, Future[U]] {
|
||||
override def attach(flowPublisher: Publisher[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Future[U] = {
|
||||
val promise = Promise[U]()
|
||||
|
||||
FlowFrom(flowPublisher).transform("fold", () ⇒ new Transformer[Out, U] {
|
||||
var state: U = zero
|
||||
override def onNext(in: Out): immutable.Seq[U] = { state = f(state, in); Nil }
|
||||
override def onError(cause: Throwable) = ()
|
||||
override def onTermination(e: Option[Throwable]) = {
|
||||
e match {
|
||||
case None ⇒ promise.success(state)
|
||||
case Some(e) ⇒ promise.failure(e)
|
||||
}
|
||||
Nil
|
||||
}
|
||||
}).consume()(materializer.withNamePrefix(flowName))
|
||||
|
||||
promise.future
|
||||
}
|
||||
def future(m: MaterializedSink): Future[U] = m.getSinkFor(this)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,71 +3,90 @@
|
|||
*/
|
||||
package akka.stream.scaladsl2
|
||||
|
||||
import org.reactivestreams.{ Subscriber, Publisher }
|
||||
|
||||
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 scala.language.higherKinds
|
||||
import scala.language.implicitConversions
|
||||
|
||||
import akka.stream.impl.{ ActorPublisher, EmptyPublisher, ErrorPublisher, FuturePublisher, IterablePublisher, IteratorPublisher, SimpleCallbackPublisher, TickPublisher, Stop }
|
||||
import akka.stream.impl2.ActorBasedFlowMaterializer
|
||||
/**
|
||||
* A `Source` is a set of stream processing steps that has one open output and an attached input.
|
||||
* Can be used as a `Publisher`
|
||||
*/
|
||||
trait Source[+Out] extends FlowOps[Out] {
|
||||
override type Repr[+O] <: Source[O]
|
||||
|
||||
object FlowFrom {
|
||||
/**
|
||||
* Helper to create `Flow` without [[Source]].
|
||||
* Example usage: `FlowFrom[Int]`
|
||||
* Transform this source by appending the given processing stages.
|
||||
*/
|
||||
def apply[T]: ProcessorFlow[T, T] = ProcessorFlow.empty[T]
|
||||
def connect[T](flow: Flow[Out, T]): Source[T]
|
||||
|
||||
/**
|
||||
* Helper to create `Flow` with [[Source]] from `Publisher`.
|
||||
* Connect this source to a sink, concatenating the processing steps of both.
|
||||
*/
|
||||
def connect(sink: Sink[Out]): RunnableFlow
|
||||
|
||||
def toPublisher()(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance]
|
||||
|
||||
def toFanoutPublisher(initialBufferSize: Int, maximumBufferSize: Int)(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance]
|
||||
|
||||
def publishTo(subscriber: Subscriber[Out @uncheckedVariance])(implicit materializer: FlowMaterializer)
|
||||
|
||||
def consume()(implicit materializer: FlowMaterializer): Unit
|
||||
}
|
||||
|
||||
object Source {
|
||||
/**
|
||||
* Helper to create [[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))
|
||||
def apply[T](publisher: Publisher[T]): Tap[T] = PublisherTap(publisher)
|
||||
|
||||
/**
|
||||
* Helper to create `Flow` with [[Source]] from `Iterator`.
|
||||
* Example usage: `FlowFrom(Seq(1,2,3).iterator)`
|
||||
* Helper to create [[Source]] from `Iterator`.
|
||||
* Example usage: `Source(Seq(1,2,3).iterator)`
|
||||
*
|
||||
* Start a new `Flow` from the given Iterator. The produced stream of elements
|
||||
* Start a new `Source` 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))
|
||||
def apply[T](iterator: Iterator[T]): Tap[T] = IteratorTap(iterator)
|
||||
|
||||
/**
|
||||
* Helper to create `Flow` with [[Source]] from `Iterable`.
|
||||
* Example usage: `FlowFrom(Seq(1,2,3))`
|
||||
* Helper to create [[Source]] from `Iterable`.
|
||||
* Example usage: `Source(Seq(1,2,3))`
|
||||
*
|
||||
* Starts a new `Flow` from the given `Iterable`. This is like starting from an
|
||||
* Starts a new `Source` 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))
|
||||
def apply[T](iterable: immutable.Iterable[T]): Tap[T] = IterableTap(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))
|
||||
def apply[T](f: () ⇒ Option[T]): Tap[T] = ThunkTap(f)
|
||||
|
||||
/**
|
||||
* Start a new `Flow` from the given `Future`. The stream will consist of
|
||||
* Start a new `Source` 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))
|
||||
def apply[T](future: Future[T]): Tap[T] = FutureTap(future)
|
||||
|
||||
/**
|
||||
* Elements are produced from the tick closure periodically with the specified interval.
|
||||
|
|
@ -76,200 +95,6 @@ object FlowFrom {
|
|||
* 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))
|
||||
|
||||
def apply[T](initialDelay: FiniteDuration, interval: FiniteDuration, tick: () ⇒ T): Tap[T] =
|
||||
TickTap(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
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().getOrElse(throw Stop)), 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"))
|
||||
}
|
||||
|
||||
|
|
|
|||
279
akka-stream/src/main/scala/akka/stream/scaladsl2/Tap.scala
Normal file
279
akka-stream/src/main/scala/akka/stream/scaladsl2/Tap.scala
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.stream.scaladsl2
|
||||
|
||||
import akka.stream.impl._
|
||||
import akka.stream.impl2.ActorBasedFlowMaterializer
|
||||
import akka.stream.{ Transformer, OverflowStrategy, TimerTransformer }
|
||||
import org.reactivestreams.{ Publisher, Subscriber }
|
||||
import scala.collection.immutable
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{ Failure, Success }
|
||||
|
||||
import scala.annotation.unchecked.uncheckedVariance
|
||||
|
||||
/**
|
||||
* This trait is a marker for a pluggable stream tap. Concrete instances should
|
||||
* implement [[TapWithKey]] or [[SimpleTap]], otherwise a custom [[FlowMaterializer]]
|
||||
* will have to be used to be able to attach them.
|
||||
*
|
||||
* All Taps 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
|
||||
* Tap nodes themselves (or construct an ActorBasedFlowMaterializer).
|
||||
*/
|
||||
trait Tap[+Out] extends Source[Out]
|
||||
|
||||
/**
|
||||
* A tap that does not need to create a user-accessible object during materialization.
|
||||
*/
|
||||
trait SimpleTap[+Out] extends Tap[Out] with TapOps[Out] {
|
||||
/**
|
||||
* Attach this tap to the given [[org.reactivestreams.Subscriber]]. Using the given
|
||||
* [[FlowMaterializer]] is completely optional, especially if this tap 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[Out] @uncheckedVariance, materializer: ActorBasedFlowMaterializer, flowName: String): Unit
|
||||
/**
|
||||
* This method is only used for Taps that return true from [[#isActive]], which then must
|
||||
* implement it.
|
||||
*/
|
||||
def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] @uncheckedVariance =
|
||||
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
|
||||
/**
|
||||
* This method indicates whether this Tap 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
|
||||
}
|
||||
|
||||
/**
|
||||
* A tap that will create an object during materialization that the user will need
|
||||
* to retrieve in order to access aspects of this tap (could be a Subscriber, a
|
||||
* Future/Promise, etc.).
|
||||
*/
|
||||
trait TapWithKey[+Out, T] extends TapOps[Out] {
|
||||
/**
|
||||
* Attach this tap to the given [[org.reactivestreams.Subscriber]]. Using the given
|
||||
* [[FlowMaterializer]] is completely optional, especially if this tap 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[Out] @uncheckedVariance, materializer: ActorBasedFlowMaterializer, flowName: String): T
|
||||
/**
|
||||
* This method is only used for Taps that return true from [[#isActive]], which then must
|
||||
* implement it.
|
||||
*/
|
||||
def create(materializer: ActorBasedFlowMaterializer, flowName: String): (Publisher[Out] @uncheckedVariance, T) =
|
||||
throw new UnsupportedOperationException(s"forgot to implement create() for $getClass that says isActive==true")
|
||||
/**
|
||||
* This method indicates whether this Tap 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 SubscriberTap[Out]() extends TapWithKey[Out, Subscriber[Out]] {
|
||||
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Subscriber[Out] =
|
||||
flowSubscriber
|
||||
|
||||
def subscriber(m: MaterializedTap): Subscriber[Out] = m.getTapFor(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 PublisherTap[Out](p: Publisher[Out]) extends SimpleTap[Out] {
|
||||
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
|
||||
p.subscribe(flowSubscriber)
|
||||
override def isActive: Boolean = true
|
||||
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] = p
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new `Source` 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 IteratorTap[Out](iterator: Iterator[Out]) extends SimpleTap[Out] {
|
||||
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
|
||||
create(materializer, flowName).subscribe(flowSubscriber)
|
||||
override def isActive: Boolean = true
|
||||
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
|
||||
if (iterator.isEmpty) EmptyPublisher[Out]
|
||||
else ActorPublisher[Out](materializer.actorOf(IteratorPublisher.props(iterator, materializer.settings),
|
||||
name = s"$flowName-0-iterator"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new `Source` 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 IterableTap[Out](iterable: immutable.Iterable[Out]) extends SimpleTap[Out] {
|
||||
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
|
||||
create(materializer, flowName).subscribe(flowSubscriber)
|
||||
override def isActive: Boolean = true
|
||||
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
|
||||
if (iterable.isEmpty) EmptyPublisher[Out]
|
||||
else ActorPublisher[Out](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 ThunkTap[Out](f: () ⇒ Option[Out]) extends SimpleTap[Out] {
|
||||
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
|
||||
create(materializer, flowName).subscribe(flowSubscriber)
|
||||
override def isActive: Boolean = true
|
||||
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
|
||||
ActorPublisher[Out](materializer.actorOf(SimpleCallbackPublisher.props(materializer.settings,
|
||||
() ⇒ f().getOrElse(throw Stop)), name = s"$flowName-0-thunk"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new `Source` 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 FutureTap[Out](future: Future[Out]) extends SimpleTap[Out] {
|
||||
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
|
||||
create(materializer, flowName).subscribe(flowSubscriber)
|
||||
override def isActive: Boolean = true
|
||||
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
|
||||
future.value match {
|
||||
case Some(Success(element)) ⇒
|
||||
ActorPublisher[Out](materializer.actorOf(IterablePublisher.props(List(element), materializer.settings),
|
||||
name = s"$flowName-0-future"), Some(future))
|
||||
case Some(Failure(t)) ⇒
|
||||
ErrorPublisher(t).asInstanceOf[Publisher[Out]]
|
||||
case None ⇒
|
||||
ActorPublisher[Out](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 TickTap[Out](initialDelay: FiniteDuration, interval: FiniteDuration, tick: () ⇒ Out) extends SimpleTap[Out] {
|
||||
override def attach(flowSubscriber: Subscriber[Out], materializer: ActorBasedFlowMaterializer, flowName: String): Unit =
|
||||
create(materializer, flowName).subscribe(flowSubscriber)
|
||||
override def isActive: Boolean = true
|
||||
override def create(materializer: ActorBasedFlowMaterializer, flowName: String): Publisher[Out] =
|
||||
ActorPublisher[Out](materializer.actorOf(TickPublisher.props(initialDelay, interval, tick, materializer.settings),
|
||||
name = s"$flowName-0-tick"))
|
||||
}
|
||||
|
||||
trait MaterializedTap {
|
||||
/**
|
||||
* Do not call directly. Use accessor method in the concrete `Tap`, e.g. [[SubscriberTap#subscriber]].
|
||||
*/
|
||||
def getTapFor[T](tapKey: TapWithKey[_, T]): T
|
||||
}
|
||||
|
||||
trait TapOps[+Out] extends Tap[Out] {
|
||||
override type Repr[+O] = SourcePipe[O]
|
||||
|
||||
private def sourcePipe = Pipe.empty[Out].withTap(this)
|
||||
|
||||
override def connect[T](flow: Flow[Out, T]): Source[T] = sourcePipe.connect(flow)
|
||||
|
||||
override def connect(sink: Sink[Out]): RunnableFlow = sourcePipe.connect(sink)
|
||||
|
||||
override def toPublisher()(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] =
|
||||
sourcePipe.toPublisher()(materializer)
|
||||
|
||||
override def toFanoutPublisher(initialBufferSize: Int, maximumBufferSize: Int)(implicit materializer: FlowMaterializer): Publisher[Out @uncheckedVariance] =
|
||||
sourcePipe.toFanoutPublisher(initialBufferSize, maximumBufferSize)(materializer)
|
||||
|
||||
override def publishTo(subscriber: Subscriber[Out @uncheckedVariance])(implicit materializer: FlowMaterializer): Unit =
|
||||
sourcePipe.publishTo(subscriber)(materializer)
|
||||
|
||||
override def consume()(implicit materializer: FlowMaterializer): Unit =
|
||||
sourcePipe.consume()
|
||||
|
||||
override def map[T](f: Out ⇒ T): Repr[T] = sourcePipe.map(f)
|
||||
|
||||
override def mapConcat[T](f: Out ⇒ immutable.Seq[T]): Repr[T] = sourcePipe.mapConcat(f)
|
||||
|
||||
override def mapAsync[T](f: Out ⇒ Future[T]): Repr[T] = sourcePipe.mapAsync(f)
|
||||
|
||||
override def mapAsyncUnordered[T](f: Out ⇒ Future[T]): Repr[T] = sourcePipe.mapAsyncUnordered(f)
|
||||
|
||||
override def filter(p: Out ⇒ Boolean): Repr[Out] = sourcePipe.filter(p)
|
||||
|
||||
override def collect[T](pf: PartialFunction[Out, T]): Repr[T] = sourcePipe.collect(pf)
|
||||
|
||||
override def grouped(n: Int): Repr[immutable.Seq[Out]] = sourcePipe.grouped(n)
|
||||
|
||||
override def groupedWithin(n: Int, d: FiniteDuration): Repr[immutable.Seq[Out]] = sourcePipe.groupedWithin(n, d)
|
||||
|
||||
override def drop(n: Int): Repr[Out] = sourcePipe.drop(n)
|
||||
|
||||
override def dropWithin(d: FiniteDuration): Repr[Out] = sourcePipe.dropWithin(d)
|
||||
|
||||
override def take(n: Int): Repr[Out] = sourcePipe.take(n)
|
||||
|
||||
override def takeWithin(d: FiniteDuration): Repr[Out] = sourcePipe.takeWithin(d)
|
||||
|
||||
override def conflate[S](seed: Out ⇒ S, aggregate: (S, Out) ⇒ S): Repr[S] = sourcePipe.conflate(seed, aggregate)
|
||||
|
||||
override def expand[S, U](seed: Out ⇒ S, extrapolate: S ⇒ (U, S)): Repr[U] = sourcePipe.expand(seed, extrapolate)
|
||||
|
||||
override def buffer(size: Int, overflowStrategy: OverflowStrategy): Repr[Out] = sourcePipe.buffer(size, overflowStrategy)
|
||||
|
||||
override def transform[T](name: String, mkTransformer: () ⇒ Transformer[Out, T]): Repr[T] = sourcePipe.transform(name, mkTransformer)
|
||||
|
||||
override def prefixAndTail[U >: Out](n: Int): Repr[(immutable.Seq[Out], Source[U])] = sourcePipe.prefixAndTail(n)
|
||||
|
||||
override def groupBy[K, U >: Out](f: Out ⇒ K): Repr[(K, Source[U])] = sourcePipe.groupBy(f)
|
||||
|
||||
override def splitWhen[U >: Out](p: Out ⇒ Boolean): Repr[Source[U]] = sourcePipe.splitWhen(p)
|
||||
|
||||
override def flatten[U](strategy: FlattenStrategy[Out, U]): Repr[U] = sourcePipe.flatten(strategy)
|
||||
|
||||
override def timerTransform[U](name: String, mkTransformer: () ⇒ TimerTransformer[Out, U]): Repr[U] =
|
||||
sourcePipe.timerTransform(name, mkTransformer)
|
||||
}
|
||||
|
|
@ -5,29 +5,29 @@ package akka.stream
|
|||
|
||||
/**
|
||||
* Scala API: The flow DSL allows the formulation of stream transformations based on some
|
||||
* input. The starting point is called [[Source]] and can be a collection, an iterator,
|
||||
* input. The starting point is called [[Tap]] and can be a collection, an iterator,
|
||||
* a block of code which is evaluated repeatedly or a [[org.reactivestreams.Publisher]].
|
||||
* A flow with an attached `Source` is a [[FlowWithSource]] and is constructed
|
||||
* with the `apply` methods in [[FlowFrom]].
|
||||
* A flow with an attached input and open output, including `Tap` is a [[Source]], and is
|
||||
* constructed with the `apply` methods in [[Source]].
|
||||
*
|
||||
* A flow may also be defined without an attached input `Source` and that is then
|
||||
* a [[ProcessorFlow]]. The `Source` can be attached later with [[ProcessorFlow#withSource]]
|
||||
* and it becomes a [[FlowWithSource]].
|
||||
* A flow may also be defined without an attached input or output and that is then
|
||||
* a [[Flow]]. The `Flow` can be connected to the `Source` later by using [[Source#connect]] with
|
||||
* the flow as argument, and it remains a [[Source]].
|
||||
*
|
||||
* Transformations can appended to `FlowWithSource` and `ProcessorFlow` with the operations
|
||||
* Transformations can be appended to `Source` and `Flow` with the operations
|
||||
* defined in [[FlowOps]]. Each DSL element produces a new flow that can be further transformed,
|
||||
* building up a description of the complete transformation pipeline.
|
||||
*
|
||||
* The output of the flow can be attached to a [[Sink]] with [[FlowWithSource#withSink]]
|
||||
* and if it also has an attached `Source` it becomes a [[RunnableFlow]]. In order to execute
|
||||
* this pipeline the flow must be materialized by calling [[RunnableFlow#run]] on it.
|
||||
* The termination point of a flow is called [[Drain]] and can for example be a `Future` or
|
||||
* [[org.reactivestreams.Subscriber]]. A flow with an attached output and open input,
|
||||
* including `Drain`, is a [[Sink]].
|
||||
*
|
||||
* You may also first attach the `Sink` to a `ProcessorFlow` with [[ProcessorFlow#withSink]]
|
||||
* and then it becomes a [[FlowWithSink]] and then attach the `Source` to make
|
||||
* it runnable.
|
||||
* If a flow has both an attached input and an attached output it becomes a [[RunnableFlow]].
|
||||
* In order to execute this pipeline the flow must be materialized by calling [[RunnableFlow#run]] on it.
|
||||
*
|
||||
* Flows can be wired together before they are materialized by appending or prepending them, or
|
||||
* connecting them into a [[FlowGraph]] with fan-in and fan-out elements.
|
||||
* You can create your `Source`, `Flow` and `Sink` in any order and then wire them together before
|
||||
* they are materialized by connecting them using [[Flow#connect]], or connecting them into a
|
||||
* [[FlowGraph]] with fan-in and fan-out elements.
|
||||
*
|
||||
* See <a href="https://github.com/reactive-streams/reactive-streams/">Reactive Streams</a> for
|
||||
* details on [[org.reactivestreams.Publisher]] and [[org.reactivestreams.Subscriber]].
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue