-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add wait-for-subscribers feature #7652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
acae403
e72cbd4
8f05840
0177811
d30b8a2
001e056
98a6999
8083046
c0af9fe
89ecf1d
b4b1c5e
00761f5
6cf2e00
4c0f026
6792915
cba4f30
13d05ca
6f26d41
ef05c31
1dbdd89
8a36742
71d4dd2
ec979e2
8635d6c
2021f83
4d2130b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,7 @@ | |
| using Akka.Cluster.Tools.PublishSubscribe.Internal; | ||
| using Akka.Event; | ||
| using Akka.Pattern; | ||
| using Akka.Remote; | ||
| using Akka.Routing; | ||
| using Akka.Util; | ||
| using Status = Akka.Cluster.Tools.PublishSubscribe.Internal.Status; | ||
|
|
@@ -102,6 +103,7 @@ public class DistributedPubSubMediator : ReceiveActor, IWithTimers | |
| { | ||
| private const string GossipTimerKey = "GossipTimer"; | ||
| private const string PruneTimerKey = "PruneTimer"; | ||
| private const string PruneBufferTimerKey = "PruneBufferTimer"; | ||
|
|
||
| /// <summary> | ||
| /// TBD | ||
|
|
@@ -127,20 +129,19 @@ public static Props Props(DistributedPubSubSettings settings) | |
| private readonly string _topicPrefix; | ||
| private readonly PubSubCache _cache; | ||
|
|
||
| private readonly int _maxBufferPerTopic; | ||
| private readonly TimeSpan _bufferedMessageTimeoutCheckInterval; | ||
| private readonly Dictionary<string, List<BufferedMessage>> _bufferedMessages = new(); | ||
|
Arkatufus marked this conversation as resolved.
|
||
|
|
||
| public ITimerScheduler Timers { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// TBD | ||
| /// </summary> | ||
| public IImmutableDictionary<Address, long> OwnVersions | ||
| { | ||
| get | ||
| { | ||
| return _registry | ||
| .Select(entry => new KeyValuePair<Address, long>(entry.Key, entry.Value.Version)) | ||
| .ToImmutableDictionary(kv => kv.Key, kv => kv.Value); | ||
| } | ||
| } | ||
| => _registry | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No logic change, just modernization |
||
| .Select(entry => new KeyValuePair<Address, long>(entry.Key, entry.Value.Version)) | ||
| .ToImmutableDictionary(kv => kv.Key, kv => kv.Value); | ||
|
|
||
| /// <summary> | ||
| /// TBD | ||
|
|
@@ -161,6 +162,9 @@ public DistributedPubSubMediator(DistributedPubSubSettings settings) | |
| _log = Context.GetLogger(); | ||
|
|
||
| _role = settings.Role; | ||
| _maxBufferPerTopic = settings.MaxBufferedMessagePerTopic; | ||
| _bufferedMessageTimeoutCheckInterval = settings.BufferedMessageTimeoutCheckInterval; | ||
|
|
||
| _pruneInterval = new TimeSpan(_settings.RemovedTimeToLive.Ticks / 2); | ||
| _buffer = new PerGroupingBuffer(); | ||
|
|
||
|
|
@@ -195,7 +199,7 @@ public DistributedPubSubMediator(DistributedPubSubSettings settings) | |
| new Router(_settings.RoutingLogic, routees.ToArray()).Route( | ||
| Internal.Utils.WrapIfNeeded(send.Message), Sender); | ||
| else | ||
| IgnoreOrSendToDeadLetters(send); | ||
| IgnoreOrSendToDeadLetters(send, Sender); | ||
| }); | ||
| Receive<SendToAll>(sendToAll => | ||
| { | ||
|
|
@@ -320,14 +324,54 @@ public DistributedPubSubMediator(DistributedPubSubSettings settings) | |
| if (!_registry.TryGetValue(bucket.Owner, out var myBucket)) | ||
| myBucket = new Bucket(bucket.Owner); | ||
|
|
||
| if (bucket.Version > myBucket.Version) | ||
| _registry[bucket.Owner] = new Bucket(myBucket.Owner, bucket.Version, myBucket.Content.SetItems(bucket.Content)); | ||
| if (bucket.Version <= myBucket.Version) | ||
| continue; | ||
|
|
||
| // Create a bucket diff, we're only interested in new items being added | ||
| var keys = new HashSet<string>(bucket.Content.Keys); | ||
| keys.ExceptWith(new HashSet<string>(myBucket.Content.Keys)); | ||
| if (keys.Count > 0) | ||
| { | ||
| // Send the diff to ourselves | ||
| Self.Tell(new NewBucketKeysAdded(keys)); | ||
| } | ||
|
Comment on lines
+331
to
+353
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Capture |
||
|
|
||
| // Merge remote bucket with ours | ||
| var newBucket = new Bucket(myBucket.Owner, bucket.Version, myBucket.Content.SetItems(bucket.Content)); | ||
| _registry[bucket.Owner] = newBucket; | ||
|
Comment on lines
+356
to
+357
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Old code, no logic change |
||
| } | ||
| } | ||
| } | ||
| }); | ||
| Receive<GossipTick>(_ => HandleGossip()); | ||
| Receive<Prune>(_ => HandlePrune()); | ||
| Receive<NewBucketKeysAdded>(subs => | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New message handler, this handles new keys being added into the registry bucket |
||
| { | ||
| foreach (var key in subs.Topics) | ||
| { | ||
| if (!_bufferedMessages.TryGetValue(key, out var buffer)) | ||
| continue; | ||
|
|
||
| foreach (var bufferedMessage in buffer) | ||
| { | ||
| Self.Tell(bufferedMessage.Message, bufferedMessage.Sender); | ||
| } | ||
|
|
||
| _bufferedMessages.Remove(key); | ||
| } | ||
|
Comment on lines
+366
to
+377
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For each newly added keys, see if we have a matching buffer. If we do, resend all of them |
||
| }); | ||
| Receive<PruneBufferTick>(_ => | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New message handler for timed-out message pruning |
||
| { | ||
| foreach (var buffer in _bufferedMessages.Values) | ||
| { | ||
| var removed = buffer.Where(bufferedMessage => bufferedMessage.Deadline.IsOverdue).ToArray(); | ||
| foreach (var removedMsg in removed) | ||
| { | ||
| buffer.Remove(removedMsg); | ||
| IgnoreOrSendToDeadLetters(removedMsg.Message, removedMsg.Sender); | ||
| } | ||
| } | ||
|
Aaronontheweb marked this conversation as resolved.
|
||
| }); | ||
| Receive<Terminated>(terminated => | ||
| { | ||
| var key = Internal.Utils.MakeKey(terminated.ActorRef); | ||
|
|
@@ -417,6 +461,32 @@ public DistributedPubSubMediator(DistributedPubSubSettings settings) | |
| }); | ||
| } | ||
|
|
||
| private void BufferMessageOrDeadLetter(PublishWithAck message, IActorRef sender) | ||
| { | ||
| var topic = message.Topic; | ||
| var encodedTopic = _cache.EncodeName(topic); | ||
| var key = _cache.MakeKey(Self.Path, encodedTopic); | ||
|
Comment on lines
+481
to
+483
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generate the key for the topic |
||
|
|
||
| if (!_bufferedMessages.TryGetValue(key, out var buffer)) | ||
| { | ||
| buffer = []; | ||
| _bufferedMessages[key] = buffer; | ||
| } | ||
|
|
||
| if(buffer.Count >= _maxBufferPerTopic) | ||
| { | ||
| _log.Warning("PublishWithAck buffer overflowed for topic [{0}]. New message inserted: {1}", topic, message); | ||
| while (buffer.Count >= _maxBufferPerTopic) | ||
| { | ||
| var removed = buffer[0]; | ||
| buffer.RemoveAt(0); | ||
| IgnoreOrSendToDeadLetters(removed.Message, removed.Sender); | ||
| } | ||
| } | ||
|
Aaronontheweb marked this conversation as resolved.
|
||
|
|
||
| buffer.Add(new BufferedMessage(message, new Deadline(DateTime.UtcNow + message.Timeout), sender)); | ||
|
Arkatufus marked this conversation as resolved.
|
||
| } | ||
|
|
||
| private bool OtherHasNewerVersions(IImmutableDictionary<Address, long> versions) | ||
| { | ||
| return versions.Any(entry => | ||
|
|
@@ -438,11 +508,8 @@ private IEnumerable<Bucket> CollectDelta(IImmutableDictionary<Address, long> ver | |
| } | ||
|
|
||
| var count = 0; | ||
| foreach (var entry in filledOtherVersions) | ||
| foreach (var (owner, v) in filledOtherVersions) | ||
| { | ||
| var owner = entry.Key; | ||
| var v = entry.Value; | ||
|
|
||
| if (!_registry.TryGetValue(owner, out var bucket)) | ||
| bucket = new Bucket(owner); | ||
|
|
||
|
|
@@ -493,15 +560,21 @@ private void HandleRegisterTopic(IActorRef actorRef) | |
| private void PutToRegistry(string key, IActorRef value) | ||
| { | ||
| var v = NextVersion(); | ||
| if (!_registry.TryGetValue(_cluster.SelfAddress, out var bucket)) | ||
| _registry.Add(_cluster.SelfAddress, | ||
| new Bucket(_cluster.SelfAddress, v, ImmutableDictionary<string, ValueHolder>.Empty.Add(key, new ValueHolder(v, value)))); | ||
| else | ||
| _registry[_cluster.SelfAddress] = new Bucket(bucket.Owner, v, bucket.Content.SetItem(key, new ValueHolder(v, value))); | ||
| var newBucket = _registry.TryGetValue(_cluster.SelfAddress, out var bucket) | ||
| ? new Bucket(bucket.Owner, v, bucket.Content.SetItem(key, new ValueHolder(v, value))) | ||
| : new Bucket(_cluster.SelfAddress, v, ImmutableDictionary<string, ValueHolder>.Empty.Add(key, new ValueHolder(v, value))); | ||
|
|
||
| _registry[_cluster.SelfAddress] = newBucket; | ||
|
Comment on lines
+578
to
+582
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Old code, no logic changed |
||
| Self.Tell(new NewBucketKeysAdded([key])); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Send a signal to self that a new key has been added |
||
| } | ||
|
|
||
| private void IgnoreOrSendToDeadLetters(IWrappedMessage message) | ||
| private void IgnoreOrSendToDeadLetters(object message, IActorRef sender) | ||
| { | ||
| if (message is PublishWithAck needAck) | ||
| { | ||
| sender.Tell(new PublishFailed(needAck)); | ||
| } | ||
|
Comment on lines
+573
to
+594
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the message is |
||
|
|
||
| if (_settings.SendToDeadLettersWhenNoSubscribers) | ||
| { | ||
| var topic = message switch | ||
|
|
@@ -514,7 +587,7 @@ private void IgnoreOrSendToDeadLetters(IWrappedMessage message) | |
| // Use the specialized DeadLetterWithNoSubscribers class to clearly indicate | ||
| // that the message was not delivered because there were no subscribers, | ||
| // not because the mediator itself is dead. | ||
| var deadLetter = new DeadLetterWithNoSubscribers(message, topic, Sender, Context.Self); | ||
| var deadLetter = new DeadLetterWithNoSubscribers(message, topic, sender, Context.Self); | ||
| Context.System.DeadLetters.Tell(deadLetter); | ||
| } | ||
| } | ||
|
|
@@ -527,9 +600,17 @@ private void PublishMessage(string path, IWrappedMessage publish, bool allButSel | |
| if (r == null) continue; | ||
| r.Forward(publish.Message); | ||
| counter++; | ||
| if(publish is PublishWithAck needAck) | ||
| Sender.Tell(new PublishSucceeded(needAck)); | ||
|
Arkatufus marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| if (counter == 0) IgnoreOrSendToDeadLetters(publish); | ||
| if (counter == 0) | ||
| { | ||
| if(publish is PublishWithAck needAck) | ||
| BufferMessageOrDeadLetter(needAck, Sender); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Buffer message if its a |
||
| else | ||
| IgnoreOrSendToDeadLetters(publish, Sender); | ||
| } | ||
| return; | ||
|
|
||
| IEnumerable<IActorRef> Refs() | ||
|
|
@@ -555,7 +636,10 @@ private void PublishToEachGroup(string path, Publish publish) | |
|
|
||
| if (groups.Count == 0) | ||
| { | ||
| IgnoreOrSendToDeadLetters(publish); | ||
| if(publish is PublishWithAck needAck) | ||
| BufferMessageOrDeadLetter(needAck, Sender); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Buffer message if its a |
||
| else | ||
| IgnoreOrSendToDeadLetters(publish, Sender); | ||
| } | ||
| else | ||
| { | ||
|
|
@@ -565,6 +649,9 @@ private void PublishToEachGroup(string path, Publish publish) | |
| if (routees.Length != 0) | ||
| new Router(_settings.RoutingLogic, routees).Route(wrappedMessage, Sender); | ||
| } | ||
|
|
||
| if(publish is PublishWithAck needAck) | ||
| Sender.Tell(new PublishSucceeded(needAck)); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If message is |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -582,8 +669,8 @@ private IEnumerable<KeyValuePair<string, Routee>> ExtractGroups(string prefix, s | |
|
|
||
| private void HandlePrune() | ||
| { | ||
| var modifications = new Dictionary<Address, Bucket>(); | ||
| foreach (var (owner, bucket) in _registry) | ||
| var modifications = new List<Bucket>(); | ||
| foreach (var bucket in _registry.Values) | ||
|
Comment on lines
+692
to
+693
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplify logic, the bucket itself already stores the bucker owner, there is no need to scan the dictionary keys. |
||
| { | ||
| var oldRemoved = bucket.Content | ||
| .Where(kv => kv.Value.Ref.IsNobody() && (bucket.Version - kv.Value.Version) > _settings.RemovedTimeToLive.TotalMilliseconds) | ||
|
|
@@ -592,13 +679,13 @@ private void HandlePrune() | |
|
|
||
| if (oldRemoved.Length > 0) | ||
| { | ||
| modifications.Add(owner, new Bucket(bucket.Owner, bucket.Version, bucket.Content.RemoveRange(oldRemoved))); | ||
| modifications.Add(new Bucket(bucket.Owner, bucket.Version, bucket.Content.RemoveRange(oldRemoved))); | ||
| } | ||
| } | ||
|
|
||
| foreach (var entry in modifications) | ||
| { | ||
| _registry[entry.Key] = entry.Value; | ||
| _registry[entry.Owner] = entry; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -634,6 +721,7 @@ protected override void PreStart() | |
| //Start periodic gossip to random nodes in cluster | ||
| Timers.StartPeriodicTimer(GossipTimerKey, GossipTick.Instance, _settings.GossipInterval, _settings.GossipInterval, Self); | ||
| Timers.StartPeriodicTimer(PruneTimerKey, Prune.Instance, _pruneInterval, _pruneInterval, Self); | ||
| Timers.StartPeriodicTimer(PruneBufferTimerKey, PruneBufferTick.Instance, _bufferedMessageTimeoutCheckInterval, _bufferedMessageTimeoutCheckInterval, Self); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Start timed out message pruning timer |
||
| } | ||
|
|
||
| /// <summary> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.