Skip to content

Reconnect stale periodic market streams#113

Merged
imbingox merged 3 commits into
mainfrom
fix/ws-periodic-business-liveness
Jul 4, 2026
Merged

Reconnect stale periodic market streams#113
imbingox merged 3 commits into
mainfrom
fix/ws-periodic-business-liveness

Conversation

@imbingox

@imbingox imbingox commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • add stream-level business liveness policies to SubscriptionMultiplexer
  • restart the raw websocket for stale periodic market subscriptions while preserving existing reconnect/replay behavior
  • enable periodic stale reconnect for Binance funding/markPrice streams only
  • document the periodic liveness exception in the SDK adapter spec

Validation

  • bun run lint
  • bun run type-check
  • bun run test (456 pass / 0 fail)

Review

  • Trellis Codex check worker reviewed the diff with no findings.

Summary by CodeRabbit

  • New Features

    • Added smarter stream freshness handling for periodic data feeds, with automatic refresh and recovery when updates stop.
    • Funding rate streams now reconnect automatically if they become stale, while other streams keep their existing behavior.
  • Bug Fixes

    • Improved stale-state reporting so disconnect reasons stay accurate during reconnects.
    • Replays subscriptions after recovery to restore live updates without manual intervention.

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@imbingox, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 29 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ffc101e-e542-4c0f-bdcf-fe502486fe33

📥 Commits

Reviewing files that changed from the base of the PR and between 0997a63 and f62200d.

📒 Files selected for processing (8)
  • .changeset/periodic-stream-reconnect.md
  • .trellis/spec/sdk/adapters.md
  • src/adapters/binance/adapter.ts
  • src/adapters/binance/stream-protocol.ts
  • src/internal/managed-websocket.ts
  • src/internal/subscription-multiplexer.ts
  • tests/unit/binance-stream-protocol.test.ts
  • tests/unit/subscription-multiplexer.test.ts
📝 Walkthrough

Walkthrough

Adds a per-subscription "business liveness" policy mechanism to SubscriptionMultiplexer, allowing periodic streams (e.g., Binance funding rate) to run stale timers independent of connection-level heartbeats, with optional restart-and-replay on staleness. Extends ManagedWebSocketSession.restart with an optional reason parameter, updates spec docs and tests accordingly.

Changes

Business liveness policy for subscription multiplexer

Layer / File(s) Summary
Liveness policy types and restart signature
src/internal/subscription-multiplexer.ts, src/internal/managed-websocket.ts
Adds StreamLivenessPolicy/LivenessStaleAction types, an optional livenessPolicy hook on VenueStreamProtocol, and an optional reason parameter on ManagedWebSocketSession.restart with guards against no-op restarts.
Per-subscription liveness state
src/internal/subscription-multiplexer.ts
Extends subscription/connection state with liveness policy, timer, stale-notified flag, and disconnect-suppression flag; adds tracksBusinessLiveness helper and initializes new fields.
Staleness handling, restart/replay and readiness wiring
src/internal/subscription-multiplexer.ts
Reworks heartbeat-stale and unexpected-close handling via handleConnectionStale and suppression; updates markSubscriptionReady options; adds noteSubscriptionData, scheduleBusinessLivenessTimer, clearBusinessLivenessTimer, markSubscriptionStale for stale-driven restart and replay.
Binance funding-rate liveness policy
src/adapters/binance/stream-protocol.ts
Adds livenessPolicy returning periodic reconnect-on-stale policy for fundingRate streams, undefined otherwise.
Spec updates and test coverage
.trellis/spec/sdk/adapters.md, tests/unit/subscription-multiplexer.test.ts, tests/unit/binance-stream-protocol.test.ts, tests/integration/market.test.ts
Documents the liveness contract and validation matrix; adds unit tests for stale/restart/replay behavior and Binance policy selection; updates integration test assertions for socket closure.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Timer as BusinessLivenessTimer
  participant Multiplexer as SubscriptionMultiplexer
  participant Session as ManagedWebSocketSession
  participant Subscriber

  Timer->>Multiplexer: liveness timer expires
  Multiplexer->>Multiplexer: markSubscriptionStale(reason: heartbeat_timeout)
  Multiplexer->>Subscriber: onStale(heartbeat_timeout)
  Multiplexer->>Multiplexer: set suppressNextDisconnectNotification
  Multiplexer->>Session: restart(reason)
  Session-->>Multiplexer: socket closed and reconnected
  Multiplexer->>Multiplexer: replay subscription frame
  Multiplexer->>Subscriber: markSubscriptionReady(startLivenessTimer)
Loading

Possibly related PRs

  • imbingox/acex#32: Both PRs modify the core WebSocket multiplexing stack in src/internal/subscription-multiplexer.ts, adding/adjusting per-subscription liveness/restart semantics on the multiplexing foundation introduced there.
  • imbingox/acex#79: Directly connected at the subscription-multiplexer staleness semantics level, as it reworks stale-timer and reconnect/backoff behavior in the same file this PR extends with business liveness timers.

Poem

A timer ticks, a funding beat,
When silence falls, I sense defeat—
So restart, replay, hop anew,
Fresh mark price comes shining through! 🐇
No false alarms of "disconnected" cries,
Just heartbeat_timeout under watchful eyes.
Thump thump — this rabbit approves the fix!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the main change: reconnecting stale periodic market streams.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ws-periodic-business-liveness

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/internal/subscription-multiplexer.ts (1)

1146-1187: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

staleAfterMs fallback shares the connection-level threshold.

When policy.staleAfterMs is undefined, the per-subscription business timer falls back to this.options.staleAfterMs — the same threshold used for the connection-wide "any traffic" watchdog. Binance's funding/mark-price policy ({ kind: "periodic", onStale: "reconnect" }) doesn't set its own staleAfterMs, so in production it inherits whatever generic connection-level idle threshold is configured, which may not match funding-rate's actual push cadence (Binance mark price pushes every ~1-3s). Consider requiring/encouraging an explicit staleAfterMs on periodic policies so the interval is tuned to the actual venue payload cadence rather than reusing the generic connection watchdog value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/internal/subscription-multiplexer.ts` around lines 1146 - 1187, The
business liveness timer in scheduleBusinessLivenessTimer is reusing
this.options.staleAfterMs when policy.staleAfterMs is missing, which couples
per-subscription periodic checks to the generic connection watchdog. Update the
liveness policy handling in subscription-multiplexer so periodic business
liveness policies (including the Binance funding/mark-price path) require or
strongly enforce an explicit staleAfterMs, and avoid falling back to the
connection-level threshold inside scheduleBusinessLivenessTimer.
tests/unit/subscription-multiplexer.test.ts (1)

5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Good coverage of the single-subscription restart/replay path; consider adding a shared-connection regression test.

These tests correctly validate: periodic-stale triggers restart + replay, default staleness doesn't restart, and no-ACK subscribe frames start the liveness timer. However, none of them place a second, non-periodic subscription on the same connection as the periodic one. Given the connection-level suppressNextDisconnectNotification concern raised in subscription-multiplexer.ts, a test asserting that an unrelated co-located subscription still receives onDisconnected() (or is appropriately handled) when the periodic sibling restarts the socket would close an important coverage gap.

Also applies to: 212-227, 1149-1255

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/subscription-multiplexer.test.ts` at line 5, The subscription
multiplexer tests cover restart/replay and timer behavior, but they miss the
shared-connection case where a periodic-stale subscription and a non-periodic
subscription coexist on the same socket. Add a regression test in
subscription-multiplexer.test.ts that uses the existing
SubscriptionMultiplexer/StreamLivenessPolicy paths to place two subscriptions on
one connection and verifies the unrelated subscription still receives
onDisconnected() (or equivalent handling) when the periodic one triggers a
restart, guarding the suppressNextDisconnectNotification behavior.
src/internal/managed-websocket.ts (1)

62-67: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

restart() implementation is safe and idempotent, but its void return contributes to a caller-side race.

The guard against closed/missing/CLOSING/CLOSED sockets is correct and prevents double-close errors. However, since restart() returns void, callers (see SubscriptionMultiplexer.scheduleBusinessLivenessTimer) cannot tell whether the call actually initiated a close or was a no-op, which is a contributing factor to the connection-level suppression race flagged in subscription-multiplexer.ts. Consider returning a boolean (or exposing socket state) so callers can react only when a restart is actually initiated.

Also applies to: 480-491

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/internal/managed-websocket.ts` around lines 62 - 67, The
ManagedWebSocketSession restart() API is currently void, so callers like
SubscriptionMultiplexer.scheduleBusinessLivenessTimer cannot tell whether a
restart actually happened or was skipped, which contributes to the suppression
race. Update the ManagedWebSocketSession contract and the restart implementation
in managed-websocket.ts to return a boolean (or equivalent state) indicating
whether a close/reconnect was actually initiated, then adjust
scheduleBusinessLivenessTimer to react only when restart() returns true.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/internal/subscription-multiplexer.ts`:
- Line 144: The disconnect suppression flag in SubscriptionMultiplexer is too
broad and can hide real transport disconnects for unrelated subscriptions.
Update the suppression logic around onDisconnected() and restart() so it is tied
to the specific triggering subscription rather than the whole socket, and make
sure the flag is cleared even when restart() no-ops on a closing or closed
connection. Use the SubscriptionMultiplexer methods/fields around
suppressNextDisconnectNotification, onDisconnected, and restart to locate the
fix.

---

Nitpick comments:
In `@src/internal/managed-websocket.ts`:
- Around line 62-67: The ManagedWebSocketSession restart() API is currently
void, so callers like SubscriptionMultiplexer.scheduleBusinessLivenessTimer
cannot tell whether a restart actually happened or was skipped, which
contributes to the suppression race. Update the ManagedWebSocketSession contract
and the restart implementation in managed-websocket.ts to return a boolean (or
equivalent state) indicating whether a close/reconnect was actually initiated,
then adjust scheduleBusinessLivenessTimer to react only when restart() returns
true.

In `@src/internal/subscription-multiplexer.ts`:
- Around line 1146-1187: The business liveness timer in
scheduleBusinessLivenessTimer is reusing this.options.staleAfterMs when
policy.staleAfterMs is missing, which couples per-subscription periodic checks
to the generic connection watchdog. Update the liveness policy handling in
subscription-multiplexer so periodic business liveness policies (including the
Binance funding/mark-price path) require or strongly enforce an explicit
staleAfterMs, and avoid falling back to the connection-level threshold inside
scheduleBusinessLivenessTimer.

In `@tests/unit/subscription-multiplexer.test.ts`:
- Line 5: The subscription multiplexer tests cover restart/replay and timer
behavior, but they miss the shared-connection case where a periodic-stale
subscription and a non-periodic subscription coexist on the same socket. Add a
regression test in subscription-multiplexer.test.ts that uses the existing
SubscriptionMultiplexer/StreamLivenessPolicy paths to place two subscriptions on
one connection and verifies the unrelated subscription still receives
onDisconnected() (or equivalent handling) when the periodic one triggers a
restart, guarding the suppressNextDisconnectNotification behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c57c6eb4-7762-4df4-8082-4eb8611ce84d

📥 Commits

Reviewing files that changed from the base of the PR and between ae56824 and 0997a63.

📒 Files selected for processing (7)
  • .trellis/spec/sdk/adapters.md
  • src/adapters/binance/stream-protocol.ts
  • src/internal/managed-websocket.ts
  • src/internal/subscription-multiplexer.ts
  • tests/integration/market.test.ts
  • tests/unit/binance-stream-protocol.test.ts
  • tests/unit/subscription-multiplexer.test.ts

Comment thread src/internal/subscription-multiplexer.ts Outdated
@imbingox
imbingox merged commit 06aabe0 into main Jul 4, 2026
2 checks passed
@imbingox
imbingox deleted the fix/ws-periodic-business-liveness branch July 4, 2026 09:16
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