示例#1
0
        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!");
        }
示例#2
0
        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);
        }
示例#3
0
        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);
        }
示例#4
0
        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);
        }
示例#5
0
        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);
        }
示例#6
0
        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);
        }
示例#7
0
        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);
        }
示例#8
0
        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);
        }
示例#9
0
        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);
        }
示例#10
0
        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
        }
示例#11
0
        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();
        }