-
Notifications
You must be signed in to change notification settings - Fork 91
Added verification of the consumer sequence number for pull ordered consumers #981
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 1 commit
a34811b
8fb7354
e51d3fd
bd5f504
2653b7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…onsumer
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,6 +79,7 @@ public async IAsyncEnumerable<NatsJSMsg<T>> ConsumeAsync<T>( | |
| consumerName = consumer.Info.Name; | ||
| _logger.LogInformation(NatsJSLogEvents.NewConsumer, "Created {ConsumerName} with sequence {Seq}", consumerName, seq); | ||
|
|
||
| ulong cseq = 0; | ||
| NatsJSProtocolException? protocolException = default; | ||
|
|
||
| await using (var cc = await consumer.OrderedConsumeInternalAsync(serializer, opts, cancellationToken)) | ||
|
|
@@ -126,7 +127,15 @@ public async IAsyncEnumerable<NatsJSMsg<T>> ConsumeAsync<T>( | |
| if (msg.Metadata is not { } metadata) | ||
| continue; | ||
|
|
||
| var expected = cseq + 1; | ||
| if (metadata.Sequence.Consumer != expected) | ||
| { | ||
| _logger.LogWarning(NatsJSLogEvents.Retry, $"Consumer sequence mismatch. Expected {expected}, was {metadata.Sequence.Consumer} Retrying..."); | ||
| goto CONSUME_LOOP; | ||
| } | ||
|
|
||
| seq = metadata.Sequence.Stream; | ||
| cseq = metadata.Sequence.Consumer; | ||
|
|
||
| yield return msg; | ||
| } | ||
|
|
@@ -180,23 +189,57 @@ public async IAsyncEnumerable<NatsJSMsg<T>> FetchAsync<T>( | |
| [EnumeratorCancellation] CancellationToken cancellationToken = default) | ||
| { | ||
| cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, cancellationToken).Token; | ||
| var processed = 0; | ||
|
|
||
| var consumer = await RecreateConsumer(_fetchConsumerName, _fetchSeq, cancellationToken); | ||
| _fetchConsumerName = consumer.Info.Name; | ||
|
|
||
| await foreach (var msg in consumer.FetchAsync(opts, serializer, cancellationToken)) | ||
| while (!cancellationToken.IsCancellationRequested) | ||
| { | ||
| if (msg.Metadata is not { } metadata) | ||
| continue; | ||
| if (processed >= opts.MaxMsgs) | ||
| yield break; | ||
|
|
||
| _fetchSeq = metadata.Sequence.Stream; | ||
| yield return msg; | ||
| } | ||
| var mismatch = false; | ||
| ulong cseq = 0; | ||
|
|
||
| var consumer = await RecreateConsumer(_fetchConsumerName, _fetchSeq, cancellationToken); | ||
| _fetchConsumerName = consumer.Info.Name; | ||
|
|
||
| try | ||
| { | ||
| var fetchOpts = opts with { MaxMsgs = opts.MaxMsgs - processed }; | ||
mtmk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| await foreach (var msg in consumer.FetchAsync(fetchOpts, serializer, cancellationToken)) | ||
| { | ||
| if (msg.Metadata is not { } metadata) | ||
| continue; | ||
|
|
||
| var expected = cseq + 1; | ||
| if (metadata.Sequence.Consumer != expected) | ||
| { | ||
| _logger.LogWarning(NatsJSLogEvents.Retry, $"Consumer sequence mismatch. Expected {expected}, was {metadata.Sequence.Consumer} Retrying..."); | ||
| mismatch = true; | ||
| break; | ||
| } | ||
|
|
||
| _fetchSeq = metadata.Sequence.Stream; | ||
| cseq = metadata.Sequence.Consumer; | ||
|
|
||
| processed++; | ||
|
|
||
| yield return msg; | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| var deleted = await TryDeleteConsumer(_fetchConsumerName, cancellationToken); | ||
|
Contributor
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. Why do you care if the delete succeeds? It should be ephemeral anyway. The key is to always generate an ordered consumer name. We agreed that the user can provide a prefix for ordered consumer names, which will allow them to be found more easily in logs
Member
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 (deleted) | ||
| _fetchConsumerName = string.Empty; | ||
| } | ||
|
|
||
| var deleted = await TryDeleteConsumer(_fetchConsumerName, cancellationToken); | ||
| if (!mismatch) | ||
| yield break; | ||
|
|
||
| if (deleted) | ||
| _fetchConsumerName = string.Empty; | ||
| await Task.Delay(100, cancellationToken); | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
|
|
@@ -206,26 +249,55 @@ public async IAsyncEnumerable<NatsJSMsg<T>> FetchNoWaitAsync<T>( | |
| [EnumeratorCancellation] CancellationToken cancellationToken = default) | ||
| { | ||
| cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, cancellationToken).Token; | ||
| var processed = 0; | ||
|
|
||
| var consumer = await RecreateConsumer(_fetchConsumerName, _fetchSeq, cancellationToken); | ||
| _fetchConsumerName = consumer.Info.Name; | ||
| try | ||
| while (!cancellationToken.IsCancellationRequested) | ||
| { | ||
| await foreach (var msg in consumer.FetchNoWaitAsync(opts, serializer, cancellationToken)) | ||
| if (processed >= opts.MaxMsgs) | ||
| yield break; | ||
|
|
||
| var mismatch = false; | ||
| ulong cseq = 0; | ||
| var consumer = await RecreateConsumer(_fetchConsumerName, _fetchSeq, cancellationToken); | ||
| _fetchConsumerName = consumer.Info.Name; | ||
|
|
||
| try | ||
| { | ||
| if (msg.Metadata is not { } metadata) | ||
| continue; | ||
| var fetchOpts = opts with { MaxMsgs = opts.MaxMsgs - processed }; | ||
|
|
||
| await foreach (var msg in consumer.FetchNoWaitAsync(fetchOpts, serializer, cancellationToken)) | ||
| { | ||
| if (msg.Metadata is not { } metadata) | ||
| continue; | ||
|
|
||
| var expected = cseq + 1; | ||
| if (metadata.Sequence.Consumer != expected) | ||
| { | ||
| _logger.LogWarning(NatsJSLogEvents.Retry, $"Consumer sequence mismatch. Expected {expected}, was {metadata.Sequence.Consumer} Retrying..."); | ||
| mismatch = true; | ||
| break; | ||
| } | ||
|
|
||
| _fetchSeq = metadata.Sequence.Stream; | ||
| yield return msg; | ||
| _fetchSeq = metadata.Sequence.Stream; | ||
| cseq = metadata.Sequence.Consumer; | ||
|
|
||
| processed++; | ||
|
|
||
| yield return msg; | ||
| } | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| var deleted = await TryDeleteConsumer(_fetchConsumerName, cancellationToken); | ||
| finally | ||
| { | ||
| var deleted = await TryDeleteConsumer(_fetchConsumerName, cancellationToken); | ||
|
|
||
| if (deleted) | ||
| _fetchConsumerName = string.Empty; | ||
| } | ||
|
|
||
| if (!mismatch) | ||
| yield break; | ||
|
|
||
| if (deleted) | ||
| _fetchConsumerName = string.Empty; | ||
| await Task.Delay(100, cancellationToken); | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.