Skip to content

fix(server): ask for a resync after every event-stream overflow - #759

Merged
Ishaan Gangwani (ishaan1124) merged 1 commit into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/event-stream-overflow-resync
Sep 28, 2026
Merged

Ishaan Gangwani (ishaan1124) merged 1 commit into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/event-stream-overflow-resync

Conversation

@aniruddhaadak80

Copy link
Copy Markdown
Contributor

What does this PR do, and why?

Both event-stream routes keep a bounded per-connection queue and, on overflow,
unshift a server.connected frame so the client re-hydrates. Whether that
happens was controlled by a latch that only ever went one way:

const state = { closed: false, draining: false, overflowed: false }
// ...
if (!state.overflowed) {
  state.overflowed = true
  queue.unshift(connected())
}

state.overflowed was never cleared, so only the first overflow on a
connection
ever raised a resync frame. After the client consumed that one
frame and re-hydrated, a later overflow dropped events with nothing to announce
it — the workspace stayed stale until the tab reloaded.

The fix clears the latch when the resync frame is actually written to the
socket, so each overflow raises its own frame once the previous one has been
delivered:

await stream.writeSSE({ data: JSON.stringify(event) })
// One resync frame covers the losses since the client last
// re-hydrated, so the next overflow has to raise its own.
if (event.payload.type === "server.connected") state.overflowed = false

Applied to both /event (server.ts) and /global/event (routes/global.ts),
which carry the same latch.

Overflows that happen while a resync frame is still queued keep the latch set,
so one pending frame still covers every loss since it was queued — the client
is not asked to re-hydrate twice for the same gap.

No client change is needed: the workspace already treats a server.connected
arriving mid-stream as a re-hydrate (see frontend/workspace/src/context/reconnecting-event-stream.test.ts
and global-sync-bootstrap.test.ts).

Linked issue

Fixes #756

How did you verify it?

One focused regression per route, both added to the existing files. Each opens
one connection, overflows it, lets the client consume the resync frame and the
whole backlog
(so the next overflow starts from an empty queue), then
overflows again on the same connection and requires a second resync frame.

On unmodified 3e94875c both fail — the second overflow produces no frame, so
the wait runs out:

(fail) event.subscribe > a second overflow on one connection asks the client to resync again [30008ms]
(fail) global.event   > a second overflow on one connection asks the client to resync again [30010ms]

With the fix, all eight tests in the two files pass, and the two new ones stop
depending on timing (853 ms and 536 ms, against a 30 s budget):

bun test --timeout 40000 ./test/server/event-stream.test.ts ./test/server/global-event-stream.test.ts
→ 8 pass, 0 fail

The pre-existing assertions that a single overflow yields exactly two
server.connected frames still pass, so the change does not add frames to the
common path.

Also run:

  • bun run --cwd backend/cli typecheck → exit 0
  • Select-String over backend/cli/test/server/*.ts for internal/event and
    global/event returns only these two files, so nothing else in the server
    suite drives the changed routes.

Pre-existing reds on this Windows checkout, not from this diff:

  • bun run format:check cannot pass here: git materializes the LF blobs as
    CRLF, so Prettier flags hundreds of untouched files, including tsconfig.json
    and turbo.json. I verified all five changed files are formatted per the repo
    config with line endings normalized.
  • bun run typecheck: backend is clean; @synsci/workspace fails because
    frontend/workspace/src/custom-elements.d.ts is a symlink (mode 120000) that
    Windows checked out as a text file containing ../../ui/src/custom-elements.d.ts,
    which TypeScript then parses as source → TS1128.

I did not run the full ./test/server directory: it exceeded 30 minutes on this
machine, and the grep above bounds the blast radius to the two files I ran.

Checklist

  • bun run check is green (format, typecheck, backend + frontend/ui + SDK tests) — blocked on this Windows checkout by the CRLF and symlink artifacts described above; backend typecheck is clean and both changed test files are green
  • bun run --cwd frontend/workspace build succeeds if I touched frontend/workspace or frontend/ui — not touched
  • ./tooling/repo/generate.ts was run and the tooling/sdk output committed if I changed backend/cli/src/server — not needed: no route, schema or response changed, only when an existing frame is queued, so the OpenAPI contract is untouched. Please confirm that reading is right.
  • CHANGELOG.md has an Unreleased entry if the change is user-visible
  • The matching docs page under frontend/docs/src/content/openscience/ is updated if behavior changed — no doc change needed: the queue bound and the resync frame already behave this way, this only stops the second one from being suppressed
  • Screenshots or a short video are attached for UI changes — not a UI change
  • No version bumps (package.json versions and tags are written by the release workflow)
  • install and frontend/landing/public/install are still byte-identical if I touched either — not touched

@vercel

vercel Bot commented Sep 27, 2026

Copy link
Copy Markdown

ANIRUDDHA ADAK (@aniruddhaadak80) is attempting to deploy a commit to the InkVell Team on Vercel.

A member of the Team first needs to authorize it.

The overflow marker was a one-way latch, so only the first overflow on a
connection raised the `server.connected` frame that makes the client
re-hydrate. A later overflow dropped events with no frame to announce it and
the workspace stayed stale until it reloaded. Clear the latch once that frame
has been delivered, so every overflow raises its own.
@ishaan1124
Ishaan Gangwani (ishaan1124) merged commit 79da954 into synthetic-sciences:main Sep 28, 2026
1 check failed
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.

Event streams should request resync after every overflow

2 participants