Skip to content

perf(rpc): answer requests on the blocking pool; slow reads aside - #81

Merged
httpsphl merged 1 commit into
mainfrom
perf/rpc-off-loop
Oct 1, 2026
Merged

httpsphl merged 1 commit into
mainfrom
perf/rpc-off-loop

Conversation

@httpsphl

@httpsphl httpsphl commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Second performance round, slice 2.

What

The connection loop answered each request inline, on a tokio worker. A slow request held that worker and every notification for the connection until it finished. Examples:

  • files.list walking a large work folder, which runs every 4 s while a bot works with the files panel open;
  • files.read reading a 20 MB file;
  • any request waiting for the store.

Now (rpc/mod.rs):

  • Requests run on the blocking pool (spawn_blocking), so they never hold a runtime thread.
  • They still answer in order, and before any notification that comes after them. The app stores whole objects from answers (putBot, withRoutine, the bulk loads in sync.ts) and treats an answer as newer than everything received before it. A first version answered everything out of order; a review of the app showed that would let a late bots.restart or bots.setPaused answer overwrite a newer bot.state. So the order is kept.
  • Reads the app never sets against notifications run aside: files.list, files.read, screens.list, attachments.read and usage.tokens. They answer when ready, and live text keeps flowing meanwhile.
  • Notifications are serialized straight to text with a small Serialize wrapper, instead of to_value, then json!, then to_string. Browser frames get the same treatment.

The spec (11.1) describes the order rule. It also fixes the outdated lag limit (1024 → 4096, which #75 changed).

Tests

New tests/rpc_queue.rs, which holds the store on another thread for 1 s:

  • files.list waits, and the live text goes out first;
  • crews.list waits, and its answer still goes out before the text;
  • 10 queued requests answer in order.

The first test fails on main; I checked that by running it against the old rpc/. The full suite, clippy and fmt pass.

While writing these tests I also saw that a worker blocked on the store mutex stalls tokio's timers. The 40 ms live-text flush waited for the whole hold. That is the case for moving the store to its own thread, which is noted for later.

Manual check (real Claude)

With the dev daemon and a crew whose work folder is large (for example a folder with node_modules-free source trees of a few thousand files), open the files panel while a bot writes a long answer. The text should stream smoothly, without pauses every 4 s.

Each request ran inline in the connection loop, on a runtime thread:
a files.list walk over a large work folder held both that thread and
every notification behind it. Requests now run on the blocking pool.
They still answer in order and before the notifications after them,
since the app stores a whole Bot or Routine from an answer and takes it
as newer than what came before. Reads the app never sets against
notifications (files, screens, attachments, token usage) answer on
their own, so they hold back no live text. Notifications are written
straight to text, without building a Value first.
@httpsphl
httpsphl merged commit 9165d9b into main Oct 1, 2026
3 checks passed
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