Skip to content

fix(streaming): reject active implicit rewinds - #10662

Merged
ReubenBond merged 1 commit into
dotnet:mainfrom
ReubenBond:rb-issue-901-delayed-implicit-stream-rewind
Aug 24, 2026
Merged

ReubenBond merged 1 commit into
dotnet:mainfrom
ReubenBond:rb-issue-901-delayed-implicit-stream-rewind

Conversation

@ReubenBond

@ReubenBond ReubenBond commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

An active implicit stream subscription could request an earlier sequence token after it had already completed delivery. Implicit pub/sub is stateless, so it has no authoritative producer coordination for repositioning that live subscription. The request could appear to succeed and then wait for unrelated stream activity.

Solution

  • make each active implicit subscription attachment monotonic after its first successful delivery
  • reject non-null ResumeAsync tokens locally before replacing the observer
  • preserve the current handshake position and filter when ResumeAsync(null) replaces an observer, including replacement from inside a delivery callback
  • continue accepting persisted recovery tokens on newly supplied, observerless implicit handles after activation
  • retain token-based repositioning for explicit subscriptions
  • document the lifecycle boundary and cover direct, replacement, recovery, and forward-delivery behavior

Rationale

This gives unsupported post-processing rewinds an immediate, deterministic result while preserving the stateless implicit pub/sub design. Applications which intentionally reposition an active subscription use explicit subscriptions, whose pub/sub protocol coordinates that operation.

Fixes #901

Microsoft Reviewers: Open in CodeFlow

Copilot AI lite review requested due to automatic review settings August 19, 2026 07:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a long-standing gap in the implicit stream subscription path where rewinding/resuming from an earlier sequence token would not “wake” the persistent pulling agent until a new event was produced. It does so by retaining the active producer identity during the consumer handshake and re-notifying that producer when an observing subscription resumes, enabling immediate replay from retained cache.

Changes:

  • Propagate/retain the pulling-agent GrainId during the consumer handshake (via RequestContext) so the consumer can remember which producer to refresh.
  • On ResumeAsync, preserve filter data and proactively refresh the producer for already-observing subscriptions to trigger immediate replay.
  • Add a regression test covering “drain 5, rewind to first token, replay without new production” for implicit subscriptions.
Show a summary per file
File Description
test/Orleans.Streaming.Tests/StreamingTests/MemoryStreamResumeTests.cs Adds a regression test validating immediate replay on implicit rewind without producing new events.
test/Grains/TestGrains/ImplicitSubscriptionCounterGrain.cs Captures the first token and adds a rewind helper to resume from the first retained token.
test/Grains/TestGrainInterfaces/IImplicitSubscriptionCounterGrain.cs Exposes the new rewind helper on the test grain interface.
src/Orleans.Streaming/PersistentStreams/PersistentStreamPullingAgent.cs Wraps the sequence-token handshake call to flow producer identity via RequestContext.
src/Orleans.Streaming/Internal/StreamSubscriptionHandleImpl.cs Exposes filter data and whether the handle currently has an observer attached (internal).
src/Orleans.Streaming/Internal/StreamConsumerExtension.cs Remembers producer per subscription and adds a refresh hook to re-add the subscriber on resume.
src/Orleans.Streaming/Internal/StreamConsumer.cs On resume, preserves filter data and refreshes the producer for already-observing subscriptions (with rollback on failure).
src/Orleans.Streaming/Internal/IStreamGrainExtensions.cs Adds an internal RequestContext key for producer identity.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread test/Grains/TestGrains/ImplicitSubscriptionCounterGrain.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

test/Grains/TestGrains/ImplicitSubscriptionCounterGrain.cs:101

  • RewindToFirstToken() resumes using the ResumeAsync(onNext, token) overload, which uses default no-op error/completed handlers. That means any delivery errors during the rewind/replay path will no longer increment ErrorCounter or persist state like the initial subscription path does (OnSubscribed wires an OnError handler which does that). Consider resuming with the same OnError/OnCompleted behavior for consistency.
                throw new InvalidOperationException("The stream must deliver an event before it can rewind.");
            }

            this.streamHandle = await this.streamHandle.ResumeAsync(OnNext, this.State.FirstToken);
        }
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings August 20, 2026 08:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/Orleans.Streaming/Internal/StreamConsumerExtension.cs:224

  • GetSequenceToken records the stream producer in streamProducers even when there is no local observer/handle for the subscriptionId. In a race where the observer was already removed, this can leave behind stale producer mappings for subscriptions which will never be refreshed/removed (until activation teardown). Only persist the producer mapping when an observer exists for that subscriptionId.
        public Task<StreamHandshakeToken?> GetSequenceToken(GuidId subscriptionId)
        {
            if (RequestContext.Get(StreamRequestContextKeys.StreamProducer) is GrainId streamProducer)
            {
                streamProducers[subscriptionId] = streamProducer;
  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings August 21, 2026 06:06
@ReubenBond
ReubenBond force-pushed the rb-issue-901-delayed-implicit-stream-rewind branch from 5f892f5 to d48bc43 Compare August 21, 2026 06:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread test/Orleans.Streaming.Tests/StreamingTests/MemoryStreamResumeTests.cs Outdated
Copilot AI review requested due to automatic review settings August 21, 2026 07:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings August 21, 2026 13:07
@ReubenBond
ReubenBond force-pushed the rb-issue-901-delayed-implicit-stream-rewind branch from 6b19ecf to c031b2b Compare August 21, 2026 13:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 9/9 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/Orleans.Streaming/Internal/StreamConsumerExtension.cs
Copilot AI review requested due to automatic review settings August 21, 2026 13:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review details

  • Files reviewed: 9/9 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

This was referenced Sep 7, 2026
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.

Delayed implicit streams rewinding

2 participants