Skip to content

Preserve unread events when NewEvents iteration stops early#549

Open
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanave:fix-inproc-newevents-early-break
Open

Preserve unread events when NewEvents iteration stops early#549
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanave:fix-inproc-newevents-early-break

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

Run.NewEvents advanced the read bookmark to the end of the event buffer at iterator start, before yielding any event:

current := run.lastBookmark
run.lastBookmark = len(run.eventSink) // advanced to end up front
for _, evt := range run.eventSink[current:] {
    if !yield(evt) {
        return
    }
}

A consumer that stops iterating early — an ordinary break in a for evt := range run.NewEvents() loop, e.g. stopping once it sees the output event — therefore permanently loses every event it had not yet seen. NewEventCount() drops to 0 and a later NewEvents() call yields nothing, even though those events were never delivered.

The method already guards early termination with if !yield(evt) { return }, so early break is an anticipated usage; only the bookmark accounting was wrong.

Fix

Advance the bookmark as each event is delivered, so the cursor tracks what the consumer has actually seen:

for run.lastBookmark < len(run.eventSink) {
    evt := run.eventSink[run.lastBookmark]
    run.lastBookmark++
    if !yield(evt) {
        return
    }
}

This yields exactly-once delivery: no drops on early break, no re-delivery on full drain. Every in-tree caller drains fully today, so their behavior is unchanged.

Test

TestRun_NewEventsEarlyBreakPreservesUnreadEvents runs a workflow (6 buffered events), consumes exactly one via an early break, then asserts the remaining events are still reported by NewEventCount() and re-iterable via NewEvents(). It fails on the old code (remaining reported as 0, want 5) and passes with the fix.

Run.NewEvents advanced the read bookmark to the end of the event buffer
at iterator start, before yielding any event. A consumer that stopped
iterating early (a break in the range loop) therefore permanently lost
every event it had not yet seen: NewEventCount dropped to zero and a
later NewEvents call yielded nothing.

Advance the bookmark as each event is delivered instead, so an early
break leaves the remaining events available to the next NewEvents call.
Full-drain callers are unaffected.
@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 19, 2026 08:36
Copilot AI review requested due to automatic review settings July 19, 2026 08:36

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

Fixes workflow/inproc.Run.NewEvents() so that an early stop (e.g., break from for evt := range run.NewEvents()) no longer advances the read bookmark past unread buffered events, preventing silent event loss. This aligns the bookmark with what has actually been yielded and adds a regression test to lock in the behavior.

Changes:

  • Update Run.NewEvents() to advance lastBookmark incrementally per yielded event rather than jumping to the end up front.
  • Add TestRun_NewEventsEarlyBreakPreservesUnreadEvents to verify unread events remain available after an early break.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
workflow/inproc/run.go Adjusts NewEvents() bookmark advancement to prevent dropping unread events on early iteration termination.
workflow/inproc/events_test.go Adds a regression test ensuring unread events remain countable and iterable after an early break from NewEvents().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

2 participants