private (AtomicCounter, TestPublisher.Probe <string>, TestSubscriber.Probe <string>, TestPublisher.Probe <string>, TestSubscriber.Probe <string>) SetupFlow( TimeSpan minBackoff, TimeSpan maxBackoff, int maxRestarts = -1, bool onlyOnFailures = false) { var created = new AtomicCounter(0); var probe1 = this.SourceProbe <string>().ToMaterialized(this.SinkProbe <string>(), Keep.Both).Run(Materializer); var flowInSource = probe1.Item1; var flowInProbe = probe1.Item2; var probe2 = this.SourceProbe <string>().ToMaterialized(BroadcastHub.Sink <string>(), Keep.Both).Run(Materializer); var flowOutProbe = probe2.Item1; var flowOutSource = probe2.Item2; // We can't just use ordinary probes here because we're expecting them to get started/restarted. Instead, we // simply use the probes as a message bus for feeding and capturing events. var probe3 = this.SourceProbe <string>().ViaMaterialized(RestartFlowFactory(() => { created.IncrementAndGet(); var snk = Flow.Create <string>() .TakeWhile(s => s != "cancel") .To(Sink.ForEach <string>(c => flowInSource.SendNext(c)) .MapMaterializedValue(task => task.ContinueWith( t1 => { if (t1.IsFaulted || t1.IsCanceled) { flowInSource.SendNext("in error"); } else { flowInSource.SendNext("in complete"); } }))); var src = flowOutSource.TakeWhile(s => s != "complete").Select(c => { if (c == "error") { throw new ArgumentException("failed"); } return(c); }).WatchTermination((s1, task) => { task.ContinueWith(_ => { flowInSource.SendNext("out complete"); return(NotUsed.Instance); }, TaskContinuationOptions.OnlyOnRanToCompletion); return(s1); }); return(Flow.FromSinkAndSource(snk, src)); }, onlyOnFailures, RestartSettings.Create(minBackoff, maxBackoff, 0).WithMaxRestarts(maxRestarts, minBackoff)), Keep.Left) .ToMaterialized(this.SinkProbe <string>(), Keep.Both).Run(Materializer); var source = probe3.Item1; var sink = probe3.Item2; return(created, source, flowInProbe, flowOutProbe, sink); }
public void BroadcastHub_must_ensure_that_from_two_different_speed_consumers_the_slower_controls_the_rate() { this.AssertAllStagesStopped(() => { var other = Source.From(Enumerable.Range(2, 19)) .MapMaterializedValue <TaskCompletionSource <int> >(_ => null); var t = Source.Maybe <int>() .Concat(other) .ToMaterialized(BroadcastHub.Sink <int>(1), Keep.Both) .Run(Materializer); var firstElement = t.Item1; var source = t.Item2; var f1 = source .Throttle(1, TimeSpan.FromMilliseconds(10), 1, ThrottleMode.Shaping) .RunWith(Sink.Seq <int>(), Materializer); // Second cannot be overwhelmed since the first one throttles the overall rate, and second allows a higher rate var f2 = source .Throttle(10, TimeSpan.FromMilliseconds(10), 8, ThrottleMode.Shaping) .RunWith(Sink.Seq <int>(), Materializer); // Ensure subscription of Sinks. This is racy but there is no event we can hook into here. Thread.Sleep(100); firstElement.SetResult(1); f1.AwaitResult().ShouldAllBeEquivalentTo(Enumerable.Range(1, 20)); f2.AwaitResult().ShouldAllBeEquivalentTo(Enumerable.Range(1, 20)); }, Materializer); }
public void BroadcastHub_must_be_able_to_implement_a_keep_dropping_if_unsubscribed_policy_with_a_simple_SinkIgnore() { this.AssertAllStagesStopped(() => { var killSwitch = KillSwitches.Shared("test-switch"); var source = Source.From(Enumerable.Range(1, int.MaxValue)) .Via(killSwitch.Flow <int>()) .RunWith(BroadcastHub.Sink <int>(8), Materializer); // Now the Hub "drops" elements until we attach a new consumer (Source.ignore consumes as fast as possible) source.RunWith(Sink.Ignore <int>(), Materializer); // Now we attached a subscriber which will block the Sink.ignore to "take away" and drop elements anymore, // turning the BroadcastHub to a normal non-dropping mode var downstream = this.CreateSubscriberProbe <int>(); source.RunWith(Sink.FromSubscriber(downstream), Materializer); downstream.Request(1); var first = downstream.ExpectNext(); for (var i = first + 1; i < first + 11; i++) { downstream.Request(1); downstream.ExpectNext(i); } downstream.Cancel(); killSwitch.Shutdown(); }, Materializer); }
public void BroadcastHub_must_properly_signal_error_to_consumers() { this.AssertAllStagesStopped(() => { var upstream = this.CreatePublisherProbe <int>(); var source = Source.FromPublisher(upstream).RunWith(BroadcastHub.Sink <int>(8), Materializer); var downstream1 = this.CreateSubscriberProbe <int>(); var downstream2 = this.CreateSubscriberProbe <int>(); source.RunWith(Sink.FromSubscriber(downstream1), Materializer); source.RunWith(Sink.FromSubscriber(downstream2), Materializer); downstream1.Request(4); downstream2.Request(8); Enumerable.Range(1, 8).ForEach(x => upstream.SendNext(x)); downstream1.ExpectNext(1, 2, 3, 4); downstream2.ExpectNext(1, 2, 3, 4, 5, 6, 7, 8); downstream1.ExpectNoMsg(TimeSpan.FromMilliseconds(100)); downstream2.ExpectNoMsg(TimeSpan.FromMilliseconds(100)); upstream.SendError(new TestException("failed")); downstream1.ExpectError().Message.Should().Be("failed"); downstream2.ExpectError().Message.Should().Be("failed"); }, Materializer); }
public ChangeTrackerActor() { var mat = Context.Materializer(); var(queue, source) = Source.Queue <AppInfo>(10, OverflowStrategy.DropHead).PreMaterialize(mat); _appInfos = queue; var hub = source.ToMaterialized(BroadcastHub.Sink <AppInfo>(), Keep.Right); Receive <QueryChangeSource>("QueryChanedSource", (changeSource, reporter) => reporter.Compled(OperationResult.Success(new AppChangedSource(hub.Run(mat))))); Receive <AppInfo>(ai => _appInfos.OfferAsync(ai).PipeTo(Self)); Receive <IQueueOfferResult>(r => { switch (r) { case QueueOfferResult.Failure f: Log.Error(f.Cause, "Error In Change Tracker"); break; case QueueOfferResult.QueueClosed _: Log.Warning("Unexpectem Tracker Queue Close."); break; } }); }
public void BroadcastHub_must_work_in_the_happy_case() { this.AssertAllStagesStopped(() => { var source = Source.From(Enumerable.Range(1, 10)).RunWith(BroadcastHub.Sink <int>(8), Materializer); source.RunWith(Sink.Seq <int>(), Materializer) .AwaitResult() .ShouldAllBeEquivalentTo(Enumerable.Range(1, 10)); }, Materializer); }
/// <summary> /// Broadcast a transaction, if the same template behavior as been used for other nodes, they will also broadcast /// </summary> /// <param name="transaction">The transaction to broadcast</param> /// <returns>The cause of the rejection or null</returns> public Task <RejectPayload> BroadcastTransactionAsync(Transaction transaction) { AssertGroupAffected(); var hub = BroadcastHub.GetBroadcastHub(_Group.NodeConnectionParameters); if (hub == null) { throw new InvalidOperationException("No broadcast hub detected in the group"); } return(hub.BroadcastTransactionAsync(transaction)); }
public static IPreparedFeature New() => Feature.Create(() => new ChangeTrackerActor(), c => { var mat = c.Materializer(); var(queue, source) = Source.Queue <AppInfo>(10, OverflowStrategy.DropHead).PreMaterialize(mat); var hub = source.ToMaterialized(BroadcastHub.Sink <AppInfo>(), Keep.Right); return(new ChangeTrackeState(mat, queue, hub)); });
public void BroadcastHub_must_properly_signal_completion_to_consumers_arriving_after_producer_finished() { this.AssertAllStagesStopped(() => { var source = Source.Empty <int>().RunWith(BroadcastHub.Sink <int>(8), Materializer); // Wait enough so the Hub gets the completion. This is racy, but this is fine because both // cases should work in the end Thread.Sleep(50); source.RunWith(Sink.Seq <int>(), Materializer).AwaitResult().Should().BeEmpty(); }, Materializer); }
public void BroadcastHub_must_properly_signal_error_to_consumers_arriving_after_producer_finished() { this.AssertAllStagesStopped(() => { var source = Source.Failed <int>(new TestException("Fail!")) .RunWith(BroadcastHub.Sink <int>(8), Materializer); // Wait enough so the Hub gets the completion. This is racy, but this is fine because both // cases should work in the end Thread.Sleep(50); var task = source.RunWith(Sink.Seq <int>(), Materializer); task.Invoking(t => t.Wait(TimeSpan.FromSeconds(3))).ShouldThrow <TestException>(); }, Materializer); }
protected override void ConfigImpl() { var mat = Context.Materializer(); var source = Source.FromObservable(from evt in CurrentState.EventPublisher where CurrentState.Configugration.MonitorChanges select evt); var hubSource = source.ToMaterialized(BroadcastHub.Sink <IConfigEvent>(), Keep.Right).Run(mat); (from evt in CurrentState.EventPublisher where evt is ServerConfigurationEvent from pair in UpdateAndSyncActor((ServerConfigurationEvent)evt) select pair.State with { Configugration = pair.Event.Configugration }
protected override void AttachCore() { _Tracker = AttachedNode.Behaviors.Find <TrackerBehavior>(); if (_Tracker != null) { AttachedNode.Disconnected += AttachedNode_Disconnected; AttachedNode.StateChanged += AttachedNode_StateChanged; } _Broadcast = BroadcastHub.GetBroadcastHub(AttachedNode); if (_Broadcast != null) { _Broadcast.TransactionBroadcasted += _Broadcast_TransactionBroadcasted; _Broadcast.TransactionRejected += _Broadcast_TransactionRejected; } }
public void BroadcastHub_must_ensure_that_subsequent_consumers_see_subsequent_elements_without_gap() { this.AssertAllStagesStopped(() => { var source = Source.From(Enumerable.Range(1, 20)).RunWith(BroadcastHub.Sink <int>(8), Materializer); source.Take(10) .RunWith(Sink.Seq <int>(), Materializer) .AwaitResult() .ShouldAllBeEquivalentTo(Enumerable.Range(1, 10)); source.Take(10) .RunWith(Sink.Seq <int>(), Materializer) .AwaitResult() .ShouldAllBeEquivalentTo(Enumerable.Range(11, 10)); }, Materializer); }
protected override void AttachCore() { #pragma warning disable CS0612 // Type or member is obsolete _Tracker = AttachedNode.Behaviors.Find <TrackerBehavior>(); #pragma warning restore CS0612 // Type or member is obsolete if (_Tracker != null) { AttachedNode.Disconnected += AttachedNode_Disconnected; AttachedNode.StateChanged += AttachedNode_StateChanged; } _Broadcast = BroadcastHub.GetBroadcastHub(AttachedNode); if (_Broadcast != null) { _Broadcast.TransactionBroadcasted += _Broadcast_TransactionBroadcasted; _Broadcast.TransactionRejected += _Broadcast_TransactionRejected; } }
public void BroadcastHub_must_remember_completion_for_materialisations_after_completion() { var t = this.SourceProbe <NotUsed>() .ToMaterialized(BroadcastHub.Sink <NotUsed>(), Keep.Both) .Run(Materializer); var sourceProbe = t.Item1; var source = t.Item2; var sinkProbe = source.RunWith(this.SinkProbe <NotUsed>(), Materializer); sourceProbe.SendComplete(); sinkProbe.Request(1).ExpectComplete(); // Materialize a second time. There was a race here, where we managed to enqueue our Source registration just // immediately before the Hub shut down. var sink2Probe = source.RunWith(this.SinkProbe <NotUsed>(), Materializer); sink2Probe.Request(1).ExpectComplete(); }
public void Hubs_must_demonstrate_creating_a_dynamic_broadcast() { #region broadcast-hub Source <string, ICancelable> producer = Source.Tick(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), "New message"); // Attach a BroadcastHub Sink to the producer. This will materialize to a // corresponding Source. // (We need to use ToMaterialized and Keep.Right since by default the materialized // value to the left is used) IRunnableGraph <Source <string, NotUsed> > runnableGraph = producer.ToMaterialized(BroadcastHub.Sink <string>(bufferSize: 256), Keep.Right); // By running/materializing the producer, we get back a Source, which // gives us access to the elements published by the producer. Source <string, NotUsed> fromProducer = runnableGraph.Run(Materializer); // Print out messages from the producer in two independent consumers fromProducer.RunForeach(msg => Console.WriteLine($"consumer1:{msg}"), Materializer); fromProducer.RunForeach(msg => Console.WriteLine($"consumer2:{msg}"), Materializer); #endregion }
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 BroadcastHub_must_send_the_same_elements_to_consumers_attaching_around_the_same_time_with_a_buffer_size_of_one() { this.AssertAllStagesStopped(() => { var other = Source.From(Enumerable.Range(2, 9)) .MapMaterializedValue <TaskCompletionSource <int> >(_ => null); var t = Source.Maybe <int>() .Concat(other) .ToMaterialized(BroadcastHub.Sink <int>(1), Keep.Both) .Run(Materializer); var firstElement = t.Item1; var source = t.Item2; var f1 = source.RunWith(Sink.Seq <int>(), Materializer); var f2 = source.RunWith(Sink.Seq <int>(), Materializer); // Ensure subscription of Sinks. This is racy but there is no event we can hook into here. Thread.Sleep(500); firstElement.SetResult(1); f1.AwaitResult().ShouldAllBeEquivalentTo(Enumerable.Range(1, 10)); f2.AwaitResult().ShouldAllBeEquivalentTo(Enumerable.Range(1, 10)); }, Materializer); }
//[Trait("UnitTest", "UnitTest")] public void CanBroadcastTransaction() { using (NodeServerTester servers = new NodeServerTester()) { var notifiedTransactions = new List <WalletTransaction>(); var chainBuilder = new BlockchainBuilder(); SetupSPVBehavior(servers, chainBuilder); var tx = new Transaction(); Wallet wallet = new Wallet(new WalletCreation() { Network = servers.Network, RootKeys = new[] { new ExtKey().Neuter() }, UseP2SH = false }, keyPoolSize: 11); NodesGroup connected = CreateGroup(servers, 2); wallet.Configure(connected); wallet.Connect(); AutoResetEvent evt = new AutoResetEvent(false); bool passed = false; bool rejected = false; BroadcastHub hub = BroadcastHub.GetBroadcastHub(wallet.Group.NodeConnectionParameters); hub.ManualBroadcast = true; var broadcasting = wallet.BroadcastTransactionAsync(tx); wallet.TransactionBroadcasted += (t) => { passed = true; evt.Set(); }; wallet.TransactionRejected += (t, r) => { rejected = true; evt.Set(); }; while (connected.ConnectedNodes.Count != 2 && connected.ConnectedNodes.All(n => n.State == NodeState.HandShaked)) { Thread.Sleep(10); } var behaviors = connected.ConnectedNodes.Select(n => n.Behaviors.Find <BroadcastHubBehavior>()).ToArray(); TestUtils.Eventually(() => behaviors.All(b => b.Broadcasts.Count() == 1)); Assert.Equal(1, hub.BroadcastingTransactions.Count()); hub.BroadcastTransactions(); Assert.True(evt.WaitOne(20000)); Assert.True(broadcasting.Wait(2000)); Assert.True(passed); evt.Reset(); TestUtils.Eventually(() => behaviors.All(b => b.Broadcasts.Count() == 0)); Assert.Equal(0, hub.BroadcastingTransactions.Count()); Assert.Null(broadcasting.Result); broadcasting = wallet.BroadcastTransactionAsync(tx); TestUtils.Eventually(() => behaviors.All(b => b.Broadcasts.Count() == 1)); hub.BroadcastTransactions(); Assert.True(evt.WaitOne(20000)); Assert.True(broadcasting.Wait(2000)); Assert.True(rejected); TestUtils.Eventually(() => behaviors.All(b => b.Broadcasts.Count() == 0)); Assert.Equal(0, hub.BroadcastingTransactions.Count()); Assert.NotNull(broadcasting.Result); } }
public void ShouldSyncBlockChainAgainstLocal() { var network = new TestNetwork(); network.AddSeed(new NetworkAddress(new IPEndPoint(IPAddress.Parse("192.168.2.101"), 9999))); var p = new TestTransactionPool(); p.Add("t1", 1); p.Add("t2", 0); p.Spend("t2", "t1", 0); p.Render(); var genesisBlock = new TestBlock(p.TakeOut("t1").Value); var block1 = new TestBlock(p.TakeOut("t2").Value); block1.Parent = genesisBlock; genesisBlock.Render(); block1.Render(); WithBlockChains(1, genesisBlock.Value.Key, blockChains => { // blockChains[0].HandleNewBlock(genesisBlock.Value.Value); // blockChains[0].HandleNewBlock(block1.Value.Value); AutoResetEvent waitForConnection = new AutoResetEvent(false); bool connected = false; blockChains[0].OnAddedToStore += transaction => { Trace.Information("-- Transaction Received (node server)"); // actionReceiver(); }; AddressManager addressManager = new AddressManager(); addressManager.PeersToFind = 1; NodeConnectionParameters nodesGroupParameters = new NodeConnectionParameters(); // nodesGroupParameters.AddressFrom = servers[1].ExternalEndpoint; nodesGroupParameters.TemplateBehaviors.Add(new AddressManagerBehavior(addressManager)); nodesGroupParameters.TemplateBehaviors.Add(new ChainBehavior(blockChains[0])); nodesGroupParameters.TemplateBehaviors.Add(new BroadcastHubBehavior()); nodesGroupParameters.TemplateBehaviors.Add(new SPVBehavior(blockChains[0], BroadcastHub.GetBroadcastHub(nodesGroupParameters.TemplateBehaviors))); NodesGroup nodesGroup = new NodesGroup(network, nodesGroupParameters); nodesGroup.AllowSameGroup = true; nodesGroup.MaximumNodeConnection = 1; nodesGroup.ConnectedNodes.Added += (object sender, NodeEventArgs e) => { Trace.Information("-- Node added to node group"); connected = true; waitForConnection.Set(); }; nodesGroup.Connect(); Assert.True(waitForConnection.WaitOne(10000)); //TODO: use reset events instead of sleep Assert.True(connected); //TODO Thread.Sleep(40000); // actionSender(BroadcastHub.GetBroadcastHub(nodesGroup.NodeConnectionParameters)); Trace.Information("-- Done"); }); }
private void BroadcastTransaction(Action <BroadcastHub> actionSender, Action actionReceiver) { WithServerSet(2, servers => { WithBlockChains(2, null, blockChains => { AutoResetEvent waitForConnection = new AutoResetEvent(false); bool connected = false; servers.SeedServerIndex = 0; AddressManager serverAddressManager = new AddressManager(); serverAddressManager.Add( new NetworkAddress(servers[0].ExternalEndpoint), servers[0].ExternalEndpoint.Address ); serverAddressManager.Connected(new NetworkAddress(servers[0].ExternalEndpoint)); NodeConnectionParameters serverParameters = new NodeConnectionParameters(); serverParameters.TemplateBehaviors.Add(new AddressManagerBehavior(serverAddressManager)); serverParameters.TemplateBehaviors.Add(new BroadcastHubBehavior()); serverParameters.TemplateBehaviors.Add(new SPVBehavior(blockChains[0], BroadcastHub.GetBroadcastHub(serverParameters.TemplateBehaviors))); blockChains[0].OnAddedToMempool += transaction => { Trace.Information("-- Transaction Received (node server)"); actionReceiver(); }; servers[0].InboundNodeConnectionParameters = serverParameters; #region NodeGroup AddressManager addressManager = new AddressManager(); addressManager.PeersToFind = 1; NodeConnectionParameters nodesGroupParameters = new NodeConnectionParameters(); nodesGroupParameters.AddressFrom = servers[1].ExternalEndpoint; nodesGroupParameters.TemplateBehaviors.Add(new AddressManagerBehavior(addressManager)); nodesGroupParameters.TemplateBehaviors.Add(new BroadcastHubBehavior()); nodesGroupParameters.TemplateBehaviors.Add(new SPVBehavior(blockChains[1], BroadcastHub.GetBroadcastHub(nodesGroupParameters.TemplateBehaviors))); blockChains[1].OnAddedToMempool += transaction => { Trace.Information("-- Transaction Received (node group)"); }; NodesGroup nodesGroup = new NodesGroup(servers.Network, nodesGroupParameters); nodesGroup.AllowSameGroup = true; nodesGroup.MaximumNodeConnection = 1; nodesGroup.ConnectedNodes.Added += (object sender, NodeEventArgs e) => { Trace.Information("-- Node added to node group"); connected = true; waitForConnection.Set(); }; nodesGroup.Connect(); #endregion Assert.True(waitForConnection.WaitOne(10000)); //TODO: use reset events instead of sleep Assert.True(connected); actionSender(BroadcastHub.GetBroadcastHub(nodesGroup.NodeConnectionParameters)); Trace.Information("-- Done"); }); }); }
public void CanBroadcastTransaction() { using (NodeServerTester servers = new NodeServerTester(Network.TestNet)) { var notifiedTransactions = new List <WalletTransaction>(); var chainBuilder = new BlockchainBuilder(); SetupSPVBehavior(servers, chainBuilder); var tx = new Transaction(); Wallet wallet = new Wallet(new WalletCreation() { Network = Network.TestNet, RootKeys = new[] { new ExtKey().Neuter() }, UseP2SH = false }, keyPoolSize: 11); NodesGroup connected = CreateGroup(servers, 2); wallet.Configure(connected); wallet.Connect(); AutoResetEvent evt = new AutoResetEvent(false); bool passed = false; bool rejected = false; var broadcasting = wallet.BroadcastTransactionAsync(tx); wallet.TransactionBroadcasted += (t) => { passed = true; evt.Set(); }; wallet.TransactionRejected += (t, r) => { rejected = true; evt.Set(); }; BroadcastHub hub = BroadcastHub.GetBroadcastHub(connected.NodeConnectionParameters); BroadcastHubBehavior behavior = null; while (behavior == null || behavior.AttachedNode.State != NodeState.HandShaked) { behavior = connected.ConnectedNodes.Select(n => n.Behaviors.Find <BroadcastHubBehavior>()).FirstOrDefault(); Thread.Sleep(1); } if (broadcasting.Status != TaskStatus.RanToCompletion) { Assert.Equal(1, behavior.Broadcasts.Count()); Assert.Equal(1, hub.BroadcastingTransactions.Count()); } Assert.True(evt.WaitOne(20000)); Assert.True(broadcasting.Status == TaskStatus.RanToCompletion); Assert.True(passed); evt.Reset(); Assert.Equal(0, behavior.Broadcasts.Count()); Assert.Equal(0, hub.BroadcastingTransactions.Count()); Assert.Null(broadcasting.Result); broadcasting = wallet.BroadcastTransactionAsync(tx); if (broadcasting.Status != TaskStatus.RanToCompletion) { Assert.Equal(1, behavior.Broadcasts.Count()); } Assert.True(evt.WaitOne(20000)); Assert.True(rejected); Assert.Equal(0, behavior.Broadcasts.Count()); Assert.Equal(0, hub.BroadcastingTransactions.Count()); Assert.NotNull(broadcasting.Result); } }
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(); }
public RequestController(BroadcastHub hub, TimerManager timer, IRequestService requestService) { _hub = hub; _timer = timer; _requestService = requestService; }
private async void Broadcast(IHubContext <BroadcastHub> hub, string message) { var broadcast = new BroadcastHub(hub); await broadcast.SendMessage(message); }