Skip to content

perf(vscode): pre-spawned rs fmt standby for the active editor - #6

Merged
fi3ework merged 6 commits into
mainfrom
feat/fmt-prewarm
Aug 7, 2026
Merged

perf(vscode): pre-spawned rs fmt standby for the active editor#6
fi3ework merged 6 commits into
mainfrom
feat/fmt-prewarm

Conversation

@fi3ework

@fi3ework fi3ework commented Aug 7, 2026

Copy link
Copy Markdown
Member

What

Before After
Google Chrome 2026-08-07 14 48 42 iShot 2026-08-07 14 48 49

Follow-up to #5, which left the ~130 ms cold-start latency on the table. Every format request paid it: process start-up (~70-140 ms), CLI bootstrap (~25 ms) and config load (~20-30 ms) all happened after the keystroke.

The fmt stack now keeps one pre-spawned rs fmt child parked on stdin for the active editor's file, so the next format of that file writes to a process that has already paid all three. rs fmt --stdin-filepath fixes the target path in argv and starts its config load concurrently with draining stdin, which is exactly why the standby is scoped this narrowly — one process, one file, one request.

  • Lifecycle — armed 2 s after the active editor settles (and immediately at registration and after each format), consumed by at most one request, killed whenever it could have gone stale, and expired after five idle minutes. Staleness triggers: a config content edit (its own change-only FileSystemWatcher, since detection only tracks the config file list), a detection change, restart, dispose, or the parked process exiting on its own.
  • Fallback — anything the standby cannot serve takes the unchanged cold path: a different file, an ineligible language, a second concurrent request, or an already-consumed slot. There is no new failure mode, only a faster path.
  • No setting — deliberately none. This is an ad-hoc measure until rs fmt speaks LSP; a setting shipped now would be a setting to deprecate later.
  • Observability — the completion line gains a hot/cold marker (Formatting completed in 12ms (hot)); every arm/consume/kill decision logs its reason at debug.
  • Internalsrun.ts splits into spawnRsFmt (start a child, park it) and serveRsFmt (write and collect), with the existing runRsFmt as their composition, so the cold and hot paths share one implementation of the protocol.

CONTEXT.md records the vocabulary (standby / arm / consume / hot / expire) that the code, logs and this PR use, and the fmt gotcha in packages/vscode/AGENTS.md now states the exception and its limits: not a daemon, no pool, no cross-request state, retires with the upstream LSP.

Also quiets the F5 playground: the isolated profile installs no extensions, so --disable-extensions only added its own notification, while the built-in Git extension (which that flag never affected) offered to open the parent repository on every launch.

Test plan

  • pnpm lint — 0 errors, 0 type errors.
  • pnpm test:unit — all passing, including a new standby.test.ts that drives arm/consume/kill/reap against real spawned stub processes (shared stubProcess.ts scaffolding, not a build entry).
  • pnpm test:e2e:vscode — exit 0. The fmt suite gains a hot-path test: show the document, poll until the standby reports it as armed, format, then assert lastServe() === 'hot' — the cold path produces identical text, so that assertion is the only proof the request actually consumed the standby.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).

A cold format pays ~130ms of process start-up and config load before any
formatting happens. The fmt stack now keeps one pre-spawned rs fmt child
parked on stdin for the active editor's file, so the next format of that
file writes to a process that has already paid both.

The standby is bounded on purpose: one process, one file, one request. It is
armed 2s after the active editor settles (immediately at registration and
after a format), killed whenever it could have gone stale — a config content
edit, a detection change, restart or dispose — and reaped after five idle
minutes. Anything it cannot serve falls back to the cold path unchanged, and
the whole mechanism is invisible above debug level except for the hot/cold
marker on the completion line.

CONTEXT.md records the vocabulary the code and logs use, and the fmt gotcha
in AGENTS.md now states the exception and its limits.
The isolated playground profile installs no extensions, so
--disable-extensions only contributed its own notification on every launch.
Git is a built-in and ignores that flag entirely: it offered to open the
parent repository each time, because every fixture lives inside this repo.
Disable that one extension instead.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53424eff2e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/vscode/src/stacks/fmt/index.ts
An active editor the provider matches but the stack cannot resolve — an
undetected folder, a missing or too-old rstack package — logged the skip and
returned, leaving the previous file's standby parked for up to five idle
minutes. The editor had still moved on, so that standby no longer tracked it
and could serve a programmatic request for a file the user had left.

Arming already kills a standby it replaces; this path has nothing to arm, so
it has to kill on its own. An editor holding nothing formattable at all still
leaves the standby alone: a peek at the output panel must not cost the user
their warm process.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b0efb09eb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/vscode/src/stacks/fmt/index.ts
The docstring justified keeping a standby across an ineligible editor with
a peek at the output panel, which cannot happen: the active editor is the
focused one or, when nothing is focused, the most recently changed one, so
focusing a panel leaves it pointing at the same file and fires no change.

Name the cases that do reach it — no text document at all, or one this stack
does not format — and say why neither is worth a kill. CONTEXT.md stated the
invariant more strongly than the code holds it; it now states both outcomes.
…p to expire

The docstring justified keeping a standby across an ineligible editor with a
peek at the output panel, which cannot happen: the active editor is the
focused one or, when nothing is focused, the most recently changed one, so
focusing a panel leaves it pointing at the same file and fires no change.

Name the cases that do reach it — no text document at all, or one this stack
does not format — and say why neither is worth a kill. CONTEXT.md stated the
invariant more strongly than the code holds it; it now states both outcomes.

'Reap' reads as jargon for what the idle timer does, so the term is now
'expire' in the glossary, the log reason and the tests.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54789e4133

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/vscode/src/stacks/fmt/index.ts Outdated
Comment thread packages/vscode/src/stacks/fmt/index.ts
The config watcher listened for content changes only, on the reasoning that
a create or a delete moves the detection signature and arrives as a detection
change instead. That holds for a real create or delete, not for the
delete/create pair an editor emits when it saves by atomically replacing the
file: the config path is gone and back within one scan, the signature records
paths rather than content, and no detection change fires. Both halves were
also ignored here, so nothing invalidated the standby and the parked process
kept serving the config it had loaded at spawn — the same file then formatted
one way hot and another way cold.

Watch all three events. A redundant kill costs nothing: it is idempotent and
the active editor re-arms through the usual debounce.
@fi3ework
fi3ework merged commit 105839e into main Aug 7, 2026
3 of 6 checks passed
@fi3ework
fi3ework deleted the feat/fmt-prewarm branch August 7, 2026 08:40
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