Skip to content

Release PCGroups 1.0.0-preview.7#67

Merged
mtmk merged 1 commit into
mainfrom
release-1.0.0-preview.7
May 30, 2026
Merged

Release PCGroups 1.0.0-preview.7#67
mtmk merged 1 commit into
mainfrom
release-1.0.0-preview.7

Conversation

@mtmk
Copy link
Copy Markdown
Collaborator

@mtmk mtmk commented May 30, 2026

Releases the PCGroups package with drain-on-dispose support for consumers.

@synadia-claude-reviewer
Copy link
Copy Markdown

synadia-claude-reviewer Bot commented May 30, 2026

Claude finished @mtmk's task in 1m 52s —— View job


PR Review: Release PCGroups 1.0.0-preview.7

  • Gather context and read CLAUDE.md
  • Examine PR diff and version change
  • Review PCGroups source for drain-on-dispose changes
  • Post review summary

Summary

This PR bumps Synadia.Orbit.PCGroups from 1.0.0-preview.6 to 1.0.0-preview.7. The only file changed in this PR is version.txt; the actual drain-on-dispose implementation was merged via PR #66 and is already on main.

The review below covers the underlying feature from #66.


Drain-on-Dispose Logic

Static (NatsPcgStaticConsumeContext, line 181) and Elastic (NatsPcgElasticConsumeContext, line 226) both apply the same pattern:

if (!hasNext)
{
    if (_js.Connection.Opts.DrainSubscriptionsOnDispose)
    {
        yield break;   // terminate enumeration gracefully
    }

    break;   // batch exhausted, restart outer loop to fetch more
}

Correctness: The semantics are sound. Under normal (non-drain) operation the JetStream ConsumeAsync enumerator returns false when the current batch is exhausted, so the outer while loop correctly continues to fetch more. Under drain the underlying JetStream consumer finishes and also returns false, so checking DrainSubscriptionsOnDispose to distinguish the two cases is the right approach.

No false-positive termination risk: The two false cases (batch exhausted vs. drain-complete) look indistinguishable from the PCGroups layer, but the consequence of a misidentification during drain is benign — the consumer loop exits, which is exactly what drain intends. And in normal operation, the flag is false so the yield break path is never taken.

Cancellation propagation: _cts and linkedToken are correctly threaded through all async paths. DisposeAsync cancels _cts and awaits _watchTask, preventing any orphaned background loops.


Minor Observations (non-blocking)

  1. Test assertion strength — The drain tests assert consumed > bailAt (more than 5 of 30 messages were processed after drain starts). The stronger invariant consumed == totalMsgs is not asserted, only that NumAckPending == 0 after. The current assertions are correct and sufficient given that drain does not guarantee delivery of un-fetched messages (only in-flight ones), but the intent could be more explicit in a comment.

  2. s_random in generic type — Both contexts have // ReSharper disable once StaticMemberInGenericType on static readonly Random s_random. Each closed generic type gets its own instance, meaning concurrent types share no lock contention but also no distribution. This is harmless here since the field is only used for backoff jitter, but worth noting for future readers.

  3. _stopped write ordering in DisposeAsync_stopped is volatile, so the write before _cts.Cancel() is visible to all readers. This is correct.


Verdict

No correctness, security, or protocol issues found. The drain-on-dispose implementation is clean, well-tested (static + elastic regression tests with gate-based determinism), and documented in PACKAGE.md. The version bump to 1.0.0-preview.7 is appropriate.

@mtmk mtmk merged commit a405fdc into main May 30, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant