Skip to content

Conversation

@daohoangson
Copy link

This PR fixes #883

The runSubscribersWithMutation function has a critical performance bottleneck causing long execution time with the potential to hang the browser completely.

Impact

  • The example dojo app has 26 active subscribers during streaming
  • Message arrays grow from 0 to 42 messages during a 742KB test run with ~200 events
  • Current execution time: 810ms
Traffic Events Trace
Image Image Image

Root Cause

This is in the hot path, called 2× per streaming event (once for onEvent, once for specific event handler), but it:

  • Clones per subscriber even if they do not mutate
  • Does expensive change detection via JSON.stringify()

The Fix

  • Move cloning from inputs to outputs
  • Use reference equality to identify changes

Expected Results

Image
  • New execution time: ~53ms (15x faster)
  • Eliminate serialization bottleneck visible in flame graphs

Copy link

Copilot AI left a comment

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 addresses a critical performance bottleneck in runSubscribersWithMutation that was causing execution times of ~810ms with potential to hang the browser. The optimization achieves a 15x performance improvement by eliminating unnecessary cloning and replacing expensive JSON.stringify comparisons with reference equality checks.

Key changes:

  • Moved cloning from input parameters (per-subscriber) to output mutations (only when mutations occur)
  • Replaced JSON.stringify equality checks with reference equality checks for change detection
  • Maintained functional correctness while dramatically reducing clone operations in the hot path

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

structuredClone_(messages),
structuredClone_(state),
);
const mutation = await executor(subscriber, messages, state);
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

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

This optimization removes the defensive cloning that protects against subscribers mutating the input parameters directly. While subscribers should only mutate via return values, passing the same reference to multiple subscribers means that if a subscriber mutates the messages or state parameters in-place (even accidentally), those mutations will leak to subsequent subscribers.

The old approach cloned the inputs for each subscriber, providing isolation. Consider whether this risk is acceptable, or if there should be documentation/runtime checks to enforce that subscribers don't mutate inputs directly.

Suggested change
const mutation = await executor(subscriber, messages, state);
const messagesForSubscriber = structuredClone_(messages);
const stateForSubscriber = structuredClone_(state);
const mutation = await executor(
subscriber,
messagesForSubscriber,
stateForSubscriber,
);

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

This is intentional.

@daohoangson
Copy link
Author

The failing job is unrelated to the change in this PR, reported separately here #897

@daohoangson daohoangson force-pushed the perf/structured-clone branch from 429b64b to c381b62 Compare January 12, 2026 06:24
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.

Performance Issue: excessive structuredClone in runSubscribersWithMutation

1 participant