Skip to content

Honor downstream demand in coalesceLatest (fixes launch trap) - #8260

Closed
ejc3 wants to merge 2 commits into
manaflow-ai:mainfrom
ejc3:coalesce-latest-honor-demand
Closed

ejc3 wants to merge 2 commits into
manaflow-ai:mainfrom
ejc3:coalesce-latest-honor-demand

Conversation

@ejc3

@ejc3 ejc3 commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

cmux traps on launch as soon as the sidebar starts observing a workspace:

Combine/Publisher+AsyncSequence.swift:112: Fatal error: Received an output without requesting demand

Three launches out of three here, dead within five seconds. The cmux-unit test
host dies the same way before a single test runs, so the suite can't be run
either.

Root cause

sidebarImmediateObservationPublisher ends in coalesceLatest, and the sidebar
reads it through .values. An AsyncPublisher requests one value at a time and
traps on any value it did not request, while coalesceLatest forwards every
value the moment it arrives.

receive(subscription:) hands the subscription downstream and then requests
unlimited demand upstream. @Published replays current state synchronously
inside that request, when downstream demand is still zero, so the replay traps on
arrival. The operator has forwarded without demand since #6807 added it; the
sidebar moved onto .values in #8211, which is when it started mattering.

Fix

Track downstream demand and forward only against it. A value with no demand
behind it becomes the coalesced pending value and is forwarded once demand
arrives, which is already what the operator does with a value that lands inside
an open window, and it holds at most one value either way. Forwarding on demand
does not re-stamp the window: the interval paces upstream bursts, not the
consumer's pull rate.

sink requests unlimited demand, so every existing subscriber behaves exactly as
before, and the sixteen tests that were already here pass untouched.

Why nothing caught it

The precondition was a comment — "sink-style subscribers only", "downstream
demand is ignored" — and nothing enforced it. Every existing test subscribed with
sink, whose unlimited demand is the one subscriber shape that cannot observe
the violation, so the operator's own tests were green by construction. Moving the
sidebar onto .values broke launch rather than failing a test. Honoring demand
puts the contract in the code, where a future subscriber can't quietly void it.

Tests

The failing test lands first, then the fix. coalesceLatestDeliversNoMoreValuesThanDownstreamDemanded
subscribes with an explicit demand of one value and records what arrives; against
the operator as it stands today:

✘ Expectation failed: (subscriber.received → [1, 2]) == [1]

coalesceLatestForwardsValueHeldForDemandOnceDemandArrives pins the other half,
so a fix that simply drops the undemanded value doesn't pass either.

With the fix, WorkspaceSidebarObservationTests is 18/18 and the app launches
and stays up.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes a launch crash by honoring downstream demand in coalesceLatest. This prevents “Received an output without requesting demand” when the sidebar consumes the stream via .values.

  • Bug Fixes
    • Track downstream demand in coalesceLatest; forward only when there is demand. If none, hold the latest and deliver once demand arrives without re-stamping the window.
    • sink behavior is unchanged (unlimited demand).
    • Added demand-limited tests to cover this path; sidebar tests pass and the app now launches reliably.

Written for commit 16e2af5. Summary will update on new commits.

Review in cubic

ejc3 added 2 commits July 16, 2026 01:24
The operator forwards a value to its subscriber whether or not the
subscriber asked for one. Every existing test subscribes with `sink`,
whose demand is unlimited, so none of them can observe that.

This test subscribes with an explicit demand of one value and records what
arrives. It fails today: the replay consumes the single demand, and the
next value is forwarded regardless.
cmux traps on launch as soon as the sidebar starts observing a workspace:

    Combine/Publisher+AsyncSequence.swift:112: Fatal error: Received an
    output without requesting demand

`sidebarImmediateObservationPublisher` ends in coalesceLatest, and the
sidebar reads it through `.values`. An AsyncPublisher requests one value
at a time and traps on a value it never requested, while the operator
forwards every value the moment it arrives.

`receive(subscription:)` hands the subscription downstream and then
requests unlimited demand upstream. `@Published` replays current state
synchronously inside that request, when downstream demand is still zero,
so the replay traps on arrival.

Track downstream demand and forward only against it. A value with no
demand behind it becomes the coalesced pending value and is forwarded once
demand arrives, which is already what the operator does with a value that
lands inside an open window, and it holds at most one value either way.
Forwarding on demand does not re-stamp the window: the interval paces
upstream bursts, not the consumer's pull rate. `sink` requests unlimited
demand, so every existing subscriber behaves exactly as before.

The precondition was written down as "sink-style subscribers only" and
nothing enforced it, so moving the sidebar onto `.values` broke launch
rather than failing a test.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 443337fe-b504-4d24-aa87-b1cc3c118341

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ejc3 ejc3 closed this Jul 16, 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.

1 participant