Repository navigation
fix(studio-server): a project's signature no longer freezes the host while it walks the files - #7073
Conversation
There was a problem hiding this comment.
Request changes at 136f67c5. The code is sound. The blocker is formatting, and it fails CI.
Blocker: two changed files fail oxfmt --check, so CI is red at this head
The body says oxfmt passed on the changed files, but I get a failure locally and so does CI. CI → Format fails, and so does Preflight (lint + format) in preview-regression and Windows render verification. Every other failure on this head (Tests/Render on windows-latest, preview-regression "Check results") comes after that preflight step. Both files are clean on main.
packages/studio-server/src/routes/preview.ts~675: theinjectStudioPreviewAugmentations(...)call was split by hand with the options object left unindented. oxfmt wantsinjectStudioPreviewAugmentations(html, signature, project.dir, compPath, {.packages/studio-server/src/helpers/projectSignature.ts:267: thereturn (await adapter.getProjectSignature?.(projectDir)) ?? createProjectSignatureAsync(projectDir);line is too long; oxfmt wraps it in parentheses.
Run npx oxfmt on those two files. No logic change is needed.
Verified
- Fails without the fix: with
projectSignature.tsandprojects.tsrestored from main, "lets the event loop run while it walks a project" fails withexpected 0 to be greater than 0. It passes at this head. projects,compositionInputs,thumbnail*andpreview*tests pass: 203/203.- No caller outside studio-server breaks on the newly async signatures. Nothing else calls
resolveProjectSignatureorcompositionInputSignature, and both adapters (cliand thestudiovite adapter) return astring, which still fitsstring | Promise<string>. - Passing the signature the route already resolved into
injectStudioPreviewAugmentationsis safe, and the stale-on-change direction in the behaviour note is the right one. The thumbnail route still runs the unchanged-inputs checks in their original order and short-circuits as before.
Non-blocking
- The CLI adapter's
getProjectSignature(packages/cli/src/server/studioServer.ts:457) is still a synchronouscreateProjectSignaturebehind a cache that every relevant watcher event clears. With the CLI server, the first poll or preview after each edit still walks synchronously. That is outside this PR's stated scope (hosts that use studio-server's own fallback), but a follow-up that makes that cache async would extend the fix to the CLI host.
Edit accuracy: accurate 267 (base branch 267), smooth 113 of thoseThe gate passes. Quarantined, measured but not gated (8)
Unstable (1)
|
|
Formatted at fce1433: |
There was a problem hiding this comment.
Approve at fce14330.
My blocker was formatting. The diff from 136f67c5 (an ancestor of this head) is formatting only: the oxfmt wrapping in projectSignature.ts:267 and preview.ts ~675, plus one wrapped loop line in projects.test.ts. No logic changed.
oxfmt --checkis clean on all 8 changed files, locally and in CI: CI → Format and Windows render verification → Preflight (lint + format) both pass. The Comments check passes too.projects.test.tsandcompositionInputs.test.tspass at this head. The fails-without-the-fix check I ran in the first review still applies, since the logic hasn't changed.
The non-blocking note from my first review still stands: the CLI adapter's cached signature is still computed synchronously on a cache miss.
fce1433 to
a47ff86
Compare
There was a problem hiding this comment.
Approve at a47ff863, rebased onto main after #7041.
- Rebase: the only conflict was
injectStudioPreviewAugmentations. It now takesadapterandsignature:adapterstays because #7041 readsadapter.runtimeUrlfor the early-errors script, andsignaturereplaces the secondresolveProjectSignaturewalk. All four call sites pass the signature their route already resolved. Outsidepreview.ts, the diff against main is line-for-line the same as atfce14330, the head I approved. - Checks:
oxfmt --checkis clean on all 8 changed files.tsc --noEmitpasses for studio-server. Theprojects,compositionInputs,thumbnail*andpreview*tests pass (205/205). In CI so far, Comments, preview-regression, regression and PR captures pass and no job has failed; CI, Windows render verification and CodeQL were still running when I approved.
The earlier non-blocking note still stands: the CLI adapter's cached signature is still computed synchronously on a cache miss.
What
Working out a project's signature no longer holds the host's event loop. The signature is what preview caching and the
/projects/:id/signaturepoll compare against. Every request-path computation in studio-server now uses the async walk that already exists (createProjectSignatureAsync), and an adapter'sgetProjectSignaturemay return a Promise. For a host that runs studio-server in its own main process, a large project no longer freezes the whole app while each file is checked.Why
createProjectSignaturewalks every file in the project with a synchronouslstatbefore it consults its cache. The cache saves the hashing, never the walk. On a project with many files, or a machine with slow disk or on-access scanning, that walk runs for tens of seconds. While it runs, nothing else on the event loop moves.That walk ran synchronously on these request paths:
GET /projects/:id/signature: a poll target, so one full walk per poll.injectStudioPreviewAugmentations;A desktop host measured main-process stalls of 18 to 125 s inside this walk.
Related work
None.
How
types.ts:getProjectSignature?: (projectDir) => string | Promise<string>. Adapters that return a string are unchanged.resolveProjectSignatureis async. It awaits the adapter's value and falls back tocreateProjectSignatureAsync.resolveProjectAndSignatureand/projects/:id/signatureawait it.preview.ts:injectStudioPreviewAugmentationstakes the signature the route already resolved, instead of walking again. That is one walk fewer per build.compositionInputSignatureis async and uses the async walk. The thumbnail route awaits it, keeping the original order of its unchanged-inputs checks.createProjectSignature(sync) stays exported for callers that need it outside a request.Behaviour note: the preview page's injected signature is now the one its request resolved, rather than one read again after bundling. If files change during the build, the page carries the older signature, so the next poll refreshes it once. Before, it could carry a newer signature than its content.
Test plan
fails without the fix:
routes/projects.test.ts, "lets the event loop run while it walks a project". It writes a 300-file project and countssetImmediateturns whileGET /projects/:id/signatureanswers. WithprojectSignature.tsandprojects.tsrestored from main it fails (expected 0 to be greater than 0). With the fix it passes, 3 runs in a row.Run on a Linux build box at this head:
packages/studio-servervitest suite: 1,602 passed, 2 skipped, exit 0.tsc --noEmitfor studio-server passed.oxlintpassed on the changed files.oxfmt --checkpasses on every changed file. The first push was unformatted: the formatter's rewrites stayed on the build box, and the checks that followed in CI failed on that.fallow audit --base origin/mainpassed.check-attr --source. The diff adds no comment lines to product source; the only new comments are in a test, which the ratchet skips. CI runs it.