2015-12-14 17:02:00 +01:00
|
|
|
/**
|
|
|
|
|
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
|
|
|
|
*/
|
|
|
|
|
package akka.stream
|
|
|
|
|
|
|
|
|
|
import akka.stream._
|
|
|
|
|
import akka.stream.scaladsl._
|
|
|
|
|
import akka.stream.testkit.AkkaSpec
|
|
|
|
|
import org.scalactic.ConversionCheckedTripleEquals
|
2015-12-15 16:44:48 +01:00
|
|
|
import akka.stream.Attributes._
|
|
|
|
|
import akka.stream.Fusing.FusedGraph
|
|
|
|
|
import scala.annotation.tailrec
|
2016-01-11 17:15:44 +01:00
|
|
|
import akka.stream.impl.StreamLayout.{ CopiedModule, Module }
|
2015-12-17 13:35:37 +01:00
|
|
|
import org.scalatest.concurrent.ScalaFutures
|
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
|
import akka.stream.impl.fusing.GraphInterpreter
|
|
|
|
|
import akka.event.BusLogging
|
2015-12-14 17:02:00 +01:00
|
|
|
|
2015-12-17 13:35:37 +01:00
|
|
|
class FusingSpec extends AkkaSpec with ScalaFutures with ConversionCheckedTripleEquals {
|
2015-12-14 17:02:00 +01:00
|
|
|
|
2015-12-15 16:44:48 +01:00
|
|
|
final val Debug = false
|
2015-12-14 17:02:00 +01:00
|
|
|
implicit val materializer = ActorMaterializer()
|
2015-12-17 13:35:37 +01:00
|
|
|
implicit val patience = PatienceConfig(1.second)
|
2015-12-14 17:02:00 +01:00
|
|
|
|
2015-12-15 16:44:48 +01:00
|
|
|
def graph(async: Boolean) =
|
2016-01-11 17:15:44 +01:00
|
|
|
Source.unfold(1)(x ⇒ Some(x -> x)).filter(_ % 2 == 1)
|
2015-12-15 16:44:48 +01:00
|
|
|
.alsoTo(Flow[Int].fold(0)(_ + _).to(Sink.head.named("otherSink")).addAttributes(if (async) Attributes.asyncBoundary else Attributes.none))
|
|
|
|
|
.via(Flow[Int].fold(1)(_ + _).named("mainSink"))
|
|
|
|
|
|
|
|
|
|
def singlePath[S <: Shape, M](fg: FusedGraph[S, M], from: Attribute, to: Attribute): Unit = {
|
|
|
|
|
val starts = fg.module.info.allModules.filter(_.attributes.contains(from))
|
|
|
|
|
starts.size should ===(1)
|
|
|
|
|
val start = starts.head
|
|
|
|
|
val ups = fg.module.info.upstreams
|
|
|
|
|
val owner = fg.module.info.outOwners
|
|
|
|
|
|
|
|
|
|
@tailrec def rec(curr: Module): Unit = {
|
|
|
|
|
if (Debug) println(extractName(curr, "unknown"))
|
2016-01-11 17:15:44 +01:00
|
|
|
curr match {
|
|
|
|
|
case CopiedModule(_, attributes, copyOf) if (attributes and copyOf.attributes).contains(to) ⇒ ()
|
|
|
|
|
case other if other.attributes.contains(to) ⇒ ()
|
|
|
|
|
case _ ⇒
|
|
|
|
|
val outs = curr.inPorts.map(ups)
|
|
|
|
|
outs.size should ===(1)
|
|
|
|
|
rec(owner(outs.head))
|
2015-12-15 16:44:48 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rec(start)
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
"Fusing" must {
|
|
|
|
|
|
2015-12-15 16:44:48 +01:00
|
|
|
def verify[S <: Shape, M](fused: FusedGraph[S, M], modules: Int, downstreams: Int): Unit = {
|
|
|
|
|
val module = fused.module
|
|
|
|
|
module.subModules.size should ===(modules)
|
|
|
|
|
module.downstreams.size should ===(modules - 1)
|
|
|
|
|
module.info.downstreams.size should be >= downstreams
|
|
|
|
|
module.info.upstreams.size should be >= downstreams
|
2016-01-11 17:15:44 +01:00
|
|
|
singlePath(fused, Attributes.Name("mainSink"), Attributes.Name("unfold"))
|
|
|
|
|
singlePath(fused, Attributes.Name("otherSink"), Attributes.Name("unfold"))
|
2015-12-15 16:44:48 +01:00
|
|
|
}
|
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
"fuse a moderately complex graph" in {
|
2015-12-15 16:44:48 +01:00
|
|
|
val g = graph(false)
|
2015-12-14 17:02:00 +01:00
|
|
|
val fused = Fusing.aggressive(g)
|
2015-12-15 16:44:48 +01:00
|
|
|
verify(fused, modules = 1, downstreams = 5)
|
2015-12-14 17:02:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"not fuse across AsyncBoundary" in {
|
2015-12-15 16:44:48 +01:00
|
|
|
val g = graph(true)
|
2015-12-14 17:02:00 +01:00
|
|
|
val fused = Fusing.aggressive(g)
|
2015-12-15 16:44:48 +01:00
|
|
|
verify(fused, modules = 2, downstreams = 5)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"not fuse a FusedGraph again" in {
|
|
|
|
|
val g = Fusing.aggressive(graph(false))
|
|
|
|
|
Fusing.aggressive(g) should be theSameInstanceAs g
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"properly fuse a FusedGraph that has been extended (no AsyncBoundary)" in {
|
|
|
|
|
val src = Fusing.aggressive(graph(false))
|
|
|
|
|
val fused = Fusing.aggressive(Source.fromGraph(src).to(Sink.head))
|
|
|
|
|
verify(fused, modules = 1, downstreams = 6)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"properly fuse a FusedGraph that has been extended (with AsyncBoundary)" in {
|
|
|
|
|
val src = Fusing.aggressive(graph(true))
|
|
|
|
|
val fused = Fusing.aggressive(Source.fromGraph(src).to(Sink.head))
|
|
|
|
|
verify(fused, modules = 2, downstreams = 6)
|
2015-12-14 17:02:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-17 13:35:37 +01:00
|
|
|
"SubFusingActorMaterializer" must {
|
|
|
|
|
|
|
|
|
|
"work with asynchronous boundaries in the subflows" in {
|
|
|
|
|
val async = Flow[Int].map(_ * 2).withAttributes(Attributes.asyncBoundary)
|
|
|
|
|
Source(0 to 9)
|
|
|
|
|
.map(_ * 10)
|
|
|
|
|
.flatMapMerge(5, i ⇒ Source(i to (i + 9)).via(async))
|
|
|
|
|
.grouped(1000)
|
|
|
|
|
.runWith(Sink.head)
|
|
|
|
|
.futureValue
|
|
|
|
|
.sorted should ===(0 to 198 by 2)
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-22 21:15:57 +01:00
|
|
|
"use multiple actors when there are asynchronous boundaries in the subflows (manual)" in {
|
2015-12-17 13:35:37 +01:00
|
|
|
def ref = {
|
|
|
|
|
val bus = GraphInterpreter.currentInterpreter.log.asInstanceOf[BusLogging]
|
|
|
|
|
bus.logSource
|
|
|
|
|
}
|
|
|
|
|
val async = Flow[Int].map(x ⇒ { testActor ! ref; x }).withAttributes(Attributes.asyncBoundary)
|
|
|
|
|
Source(0 to 9)
|
|
|
|
|
.map(x ⇒ { testActor ! ref; x })
|
|
|
|
|
.flatMapMerge(5, i ⇒ Source.single(i).via(async))
|
|
|
|
|
.grouped(1000)
|
|
|
|
|
.runWith(Sink.head)
|
|
|
|
|
.futureValue
|
|
|
|
|
.sorted should ===(0 to 9)
|
|
|
|
|
val refs = receiveN(20)
|
|
|
|
|
withClue(s"refs=\n${refs.mkString("\n")}") {
|
2015-12-22 21:15:57 +01:00
|
|
|
refs.toSet.size should ===(11) // main flow + 10 subflows
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"use multiple actors when there are asynchronous boundaries in the subflows (combinator)" in {
|
|
|
|
|
def ref = {
|
|
|
|
|
val bus = GraphInterpreter.currentInterpreter.log.asInstanceOf[BusLogging]
|
|
|
|
|
bus.logSource
|
|
|
|
|
}
|
|
|
|
|
val flow = Flow[Int].map(x ⇒ { testActor ! ref; x })
|
|
|
|
|
Source(0 to 9)
|
|
|
|
|
.map(x ⇒ { testActor ! ref; x })
|
|
|
|
|
.flatMapMerge(5, i ⇒ Source.single(i).viaAsync(flow))
|
|
|
|
|
.grouped(1000)
|
|
|
|
|
.runWith(Sink.head)
|
|
|
|
|
.futureValue
|
|
|
|
|
.sorted should ===(0 to 9)
|
|
|
|
|
val refs = receiveN(20)
|
|
|
|
|
withClue(s"refs=\n${refs.mkString("\n")}") {
|
|
|
|
|
refs.toSet.size should ===(11) // main flow + 10 subflows
|
2015-12-17 13:35:37 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-14 17:02:00 +01:00
|
|
|
}
|