public void Hubs_must_demonstrate_creating_a_dynamic_merge() { void WriteLine(string s) => TestActor.Tell(s); #region merge-hub // A simple consumer that will print to the console for now Sink <string, Task> consumer = Sink.ForEach <string>(WriteLine); // Attach a MergeHub Source to the consumer. This will materialize to a // corresponding Sink. IRunnableGraph <Sink <string, NotUsed> > runnableGraph = MergeHub.Source <string>(perProducerBufferSize: 16).To(consumer); // By running/materializing the consumer we get back a Sink, and hence // now have access to feed elements into it. This Sink can be materialized // any number of times, and every element that enters the Sink will // be consumed by our consumer. Sink <string, NotUsed> toConsumer = runnableGraph.Run(Materializer); // Feeding two independent sources into the hub. Source.Single("Hello!").RunWith(toConsumer, Materializer); Source.Single("Hub!").RunWith(toConsumer, Materializer); #endregion ExpectMsgAllOf("Hello!", "Hub!"); }
public void MergeHub_must_respect_the_buffer_size() { this.AssertAllStagesStopped(() => { var downstream = this.CreateManualSubscriberProbe <int>(); var sink = Sink.FromSubscriber(downstream).RunWith(MergeHub.Source <int>(3), Materializer); Source.From(Enumerable.Range(1, 10)).Select(i => { TestActor.Tell(i); return(i); }).RunWith(sink, Materializer); var sub = downstream.ExpectSubscription(); sub.Request(1); // Demand starts from 3 ExpectMsg(1); ExpectMsg(2); ExpectMsg(3); ExpectNoMsg(TimeSpan.FromMilliseconds(100)); // One element consumed (it was requested), demand 0 remains at producer downstream.ExpectNext(1); // Requesting next element, results in next element to be consumed. sub.Request(1); downstream.ExpectNext(2); // Two elements have been consumed, so threshold of 2 is reached, additional 2 demand is dispatched. // There is 2 demand at the producer now ExpectMsg(4); ExpectMsg(5); ExpectNoMsg(TimeSpan.FromMilliseconds(100)); // Two additional elements have been sent: // - 3, 4, 5 are pending // - demand is 0 at the producer // - next demand batch is after two elements have been consumed again // Requesting next gives the next element // Demand is not yet refreshed for the producer as there is one more element until threshold is met sub.Request(1); downstream.ExpectNext(3); ExpectNoMsg(TimeSpan.FromMilliseconds(100)); sub.Request(1); downstream.ExpectNext(4); ExpectMsg(6); ExpectMsg(7); sub.Cancel(); }, Materializer); }
public void MergeHub_must_notify_new_producers_if_consumer_cancels_before_first_producer() { this.AssertAllStagesStopped(() => { var sink = Sink.Cancelled <int>().RunWith(MergeHub.Source <int>(16), Materializer); var upstream = this.CreatePublisherProbe <int>(); Source.FromPublisher(upstream).RunWith(sink, Materializer); upstream.ExpectCancellation(); }, Materializer); }
public void MergeHub_must_work_in_the_happy_case() { this.AssertAllStagesStopped(() => { var t = MergeHub.Source <int>(16).Take(20).ToMaterialized(Sink.Seq <int>(), Keep.Both).Run(Materializer); var sink = t.Item1; var result = t.Item2; Source.From(Enumerable.Range(1, 10)).RunWith(sink, Materializer); Source.From(Enumerable.Range(11, 10)).RunWith(sink, Materializer); result.AwaitResult().OrderBy(x => x).ShouldAllBeEquivalentTo(Enumerable.Range(1, 20)); }, Materializer); }
public void MergeHub_must_work_with_long_streams_when_buffer_size_is_1() { this.AssertAllStagesStopped(() => { var t = MergeHub.Source <int>(1).Take(20000).ToMaterialized(Sink.Seq <int>(), Keep.Both).Run(Materializer); var sink = t.Item1; var result = t.Item2; Source.From(Enumerable.Range(1, 10000)).RunWith(sink, Materializer); Source.From(Enumerable.Range(10001, 10000)).RunWith(sink, Materializer); result.AwaitResult().OrderBy(x => x).ShouldAllBeEquivalentTo(Enumerable.Range(1, 20000)); }, Materializer); }
public void MergeHub_must_work_with_long_streams_if_one_of_the_producers_is_slower() { this.AssertAllStagesStopped(() => { var t = MergeHub.Source <int>(16).Take(2000).ToMaterialized(Sink.Seq <int>(), Keep.Both).Run(Materializer); var sink = t.Item1; var result = t.Item2; Source.From(Enumerable.Range(1, 1000)) .Throttle(10, TimeSpan.FromMilliseconds(1), 100, ThrottleMode.Shaping) .RunWith(sink, Materializer); Source.From(Enumerable.Range(1001, 1000)).RunWith(sink, Materializer); result.AwaitResult().OrderBy(x => x).ShouldAllBeEquivalentTo(Enumerable.Range(1, 2000)); }, Materializer); }
public void MergeHub_must_keep_working_even_if_one_of_the_producers_fail() { this.AssertAllStagesStopped(() => { var t = MergeHub.Source <int>(16).Take(10).ToMaterialized(Sink.Seq <int>(), Keep.Both).Run(Materializer); var sink = t.Item1; var result = t.Item2; EventFilter.Error(contains: "Upstream producer failed with exception").ExpectOne(() => { Source.Failed <int>(new TestException("failing")).RunWith(sink, Materializer); Source.From(Enumerable.Range(1, 10)).RunWith(sink, Materializer); }); result.AwaitResult().ShouldAllBeEquivalentTo(Enumerable.Range(1, 10)); }, Materializer); }
public void MergeHub_must_work_with_different_producers_separated_over_time() { this.AssertAllStagesStopped(() => { var downstream = this.CreateSubscriberProbe <IEnumerable <int> >(); var sink = MergeHub.Source <int>(16) .Grouped(100) .ToMaterialized(Sink.FromSubscriber(downstream), Keep.Left) .Run(Materializer); Source.From(Enumerable.Range(1, 100)).RunWith(sink, Materializer); downstream.RequestNext().ShouldAllBeEquivalentTo(Enumerable.Range(1, 100)); Source.From(Enumerable.Range(101, 100)).RunWith(sink, Materializer); downstream.RequestNext().ShouldAllBeEquivalentTo(Enumerable.Range(101, 100)); downstream.Cancel(); }, Materializer); }
public void MergeHub_must_notify_existing_producers_if_consumer_cancels_after_a_few_elements() { this.AssertAllStagesStopped(() => { var t = MergeHub.Source <int>(16).Take(5).ToMaterialized(Sink.Seq <int>(), Keep.Both).Run(Materializer); var sink = t.Item1; var result = t.Item2; var upstream = this.CreatePublisherProbe <int>(); Source.FromPublisher(upstream).RunWith(sink, Materializer); for (var i = 1; i < 6; i++) { upstream.SendNext(i); } upstream.ExpectCancellation(); result.AwaitResult().ShouldAllBeEquivalentTo(Enumerable.Range(1, 5)); }, Materializer); }
public void Hubs_must_demonstrate_combination() { void WriteLine(string s) => TestActor.Tell(s); #region pub-sub-1 // Obtain a Sink and Source which will publish and receive from the "bus" respectively. var(sink, source) = MergeHub .Source <string>(perProducerBufferSize: 16) .ToMaterialized(BroadcastHub.Sink <string>(bufferSize: 256), Keep.Both) .Run(Materializer); #endregion #region pub-sub-2 // Ensure that the Broadcast output is dropped if there are no listening parties. // If this dropping Sink is not attached, then the broadcast hub will not drop any // elements itself when there are no subscribers, backpressuring the producer instead. source.RunWith(Sink.Ignore <string>(), Materializer); #endregion #region pub-sub-3 // We create now a Flow that represents a publish-subscribe channel using the above // started stream as its "topic". We add two more features, external cancellation of // the registration and automatic cleanup for very slow subscribers. Flow <string, string, UniqueKillSwitch> busFlow = Flow.FromSinkAndSource(sink, source) .JoinMaterialized(KillSwitches.SingleBidi <string, string>(), Keep.Right) .BackpressureTimeout(TimeSpan.FromSeconds(3)); #endregion #region pub-sub-4 UniqueKillSwitch killSwitch = Source .Repeat("Hello world!") .ViaMaterialized(busFlow, Keep.Right) .To(Sink.ForEach <string>(WriteLine)) .Run(Materializer); // Shut down externally killSwitch.Shutdown(); #endregion }
public void PublishSubscribeOnHubsAddsAndRemovesPublishersAndSubscribers() { const int publisherMaxCount = 16; const int subscriberMaxCount = 16; const int bufferSize = 4; // Source ToMat Bidi // +------------+ +------------+ // | MergeHub | |BroadcastHub| // | Source | ~> Message ~> | Sink | // | | | | // +------------+ +------------+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~ (Sink<Message>, Source<Message>) ~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ (Sink <string, NotUsed> mergeSink, Source <string, NotUsed> mergeSource) = MergeHub.Source <string>(perProducerBufferSize: publisherMaxCount) .ToMaterialized(BroadcastHub.Sink <string>(bufferSize: subscriberMaxCount), Keep.Both) .Run(_materializer); TestProbe sub0 = CreateTestProbe(); TestProbe sub1 = CreateTestProbe(); // Flow JoinMat Bidi // +------------+ +------------+ // | FromSink | ~> Message ~> |KillSwitches| ~> Message // | And | | Single | // | Source | <~ Message <~ | Bidi | <~ Message // +------------+ +------------+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~ UniqueKillSwitch ~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Flow <string, string, UniqueKillSwitch> busFlow = Flow.FromSinkAndSource(mergeSink, mergeSource) .JoinMaterialized(KillSwitches.SingleBidi <string, string>(), Keep.Right); var(pub0, uniqueKillSwitch0) = Source.ActorRef <string>(bufferSize, OverflowStrategy.Fail) .ViaMaterialized(busFlow, Keep.Both) .To(Sink.ActorRef <string>(sub0, "complete")) .Run(_materializer); pub0.Tell("It's chat member 0!"); sub0.ExpectMsg("It's chat member 0!"); // Echo. sub0.ExpectNoMsg(TimeSpan.FromMilliseconds(50)); var(pub1, uniqueKillSwitch1) = Source.ActorRef <string>(bufferSize, OverflowStrategy.Fail) .ViaMaterialized(busFlow, Keep.Both) .To(Sink.ActorRef <string>(sub1, "complete")) .Run(_materializer); pub1.Tell("Hi! It's chat member 1!"); sub1.ExpectMsg("Hi! It's chat member 1!"); // Echo. sub0.ExpectMsg("Hi! It's chat member 1!"); pub0.Tell("Oh, Hi! Sry, but I gotta go, bye!"); sub0.ExpectMsg("Oh, Hi! Sry, but I gotta go, bye!"); // Echo. uniqueKillSwitch0.Shutdown(); // Looks like this Shutdown is non-blocking. sub0.ExpectMsg("complete", TimeSpan.FromMilliseconds(1000)); // Wait for the running graph to stop. sub1.ExpectMsg("Oh, Hi! Sry, but I gotta go, bye!"); pub1.Tell("Oh, looks like I stayed alone."); sub1.ExpectMsg("Oh, looks like I stayed alone."); // Echo. sub0.ExpectNoMsg(); }