fix(desktop): measure adaptive stream flush through the deferred commit frame - #72801
fix(desktop): measure adaptive stream flush through the deferred commit frame#72801Adolanium wants to merge 1 commit into
Conversation
|
Thanks for tracing the adaptive floor through the deferred view-sync path. The premise is present on current main: Problems
Suggested changes
Automated hermes-sweeper review. |
…it frame scheduleDeltaFlush's adaptive floor is driven by lastFlushCostRef, but runFlush only timed flushQueuedDeltas(), the synchronous store write. While a session streams, syncSessionStateToView defers the $messages publish (React commit + Streamdown re-parse) to its own rAF, so the measured cost stayed near zero and the floor collapsed to the fixed 33ms path no matter how expensive the real commit was. runFlush now records the write cost as a fallback, then extends the measurement through a rAF registered after the view-sync one: it runs in the same frame right after the deferred commit, and the rAF timestamp marks frame start so only in-frame work is counted, not the vsync wait. A stale callback from before a newer flush is ignored, and a hidden renderer that never fires rAF keeps the write-cost fallback.
82cb4fe to
48f6048
Compare
|
Added the composed-hook coverage in 48f6048. A new describe block wires useMessageStream to the real useSessionStateCache update function instead of the mocked one, so the view-sync rAF inside syncSessionStateToView is the production one. The test seeds a busy state, lets a flush run, then asserts exactly two rAF callbacks are pending in registration order: draining the first publishes the deferred $messages commit (which is what identifies it as the view-sync callback), draining the second closes the measurement frame at a simulated 60ms of in-frame work, and the next adaptive gap stretches to 3x. The parked-rAF fallback case is covered against the real cache too. Full use-message-stream suite passes 71/71. |
SummaryOne PR addresses issue #72799. #72801 replaces store-write-only timing with a post-view-sync animation-frame measurement, preserving timer-driven delivery, a write-cost fallback for parked renderers, and stale-callback protection. Related pull requests
Suggested consolidationKeep #72801 open with a salvage path, consistent with the maintainer-bot keep_open verdict: retain the deferred-frame measurement, fallback, stale-callback guard, and real-cache ordering tests, then add integration or performance validation demonstrating that the actual React commit is included in the measured frame cost. No competing PRs or duplicates are present. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I72799(["issue #72799 (open)"])
P72801["PR #72801 (open)"]
P72801 -->|best fix| I72799
class I72799 open
class P72801 open
class P72801 best
class P72801 target
click I72799 "https://github.com/NousResearch/hermes-agent/issues/72799"
click P72801 "https://github.com/NousResearch/hermes-agent/pull/72801"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 14 kB of PR diffs, 9 kB of issue/PR text, <1 kB of discussion (1 comments), 2 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
|
Salvaged as #77652 with your authorship preserved — clean pick. Verified the premise carefully: main's adaptive floor predates your PR and yours is the missing half (the cost clock stopped before the deferred React commit, so the floor collapsed to 33ms under load). No defer-forever (measurement-only deferral, 250ms cap), stale-rAF guard verified. Thank you! Closing in favor of #77652. |
What does this PR do?
The adaptive stream-flush floor from #72504 never engaged on the streaming path.
runFlushtimed onlyflushQueuedDeltas(), the synchronous store write. While a session streams, the$messagespublish (React commit + Streamdown re-parse) is deferred to a view-sync rAF insyncSessionStateToView, so it lands in a later frame, after the measurement. The measured cost stayed near zero, the adaptive branch needs more than ~11ms to engage, and the floor collapsed to the fixed 33ms gap at any load. Multi-stream streaming could still starve the main thread of idle frames and hitch typing, the failure mode the floor was built to prevent.runFlushnow keeps the write cost as a fallback and extends the measurement through a rAF registered after the view-sync one. It runs in the same frame right after the deferred commit, and the rAF timestamp marks frame start so only in-frame work is counted, not the vsync wait. A stale callback from before a newer flush is ignored. A hidden renderer that never fires rAF keeps the write-cost fallback, so delivery in parked renderers is unchanged (still timer-driven, never frame-gated).Related Issue
Fixes #72799
Type of Change
Changes Made
apps/desktop/src/app/session/hooks/use-message-stream/index.ts: measure flush cost through the deferred view-sync frame (write cost + in-frame work via the rAF timestamp), keep the write-cost fallback for hidden renderers, ignore a stale measurement once a newer flush has started.apps/desktop/src/app/session/hooks/use-message-stream/delta-flush.test.tsx: cover the frame-measured adaptive floor, the no-frame fallback, and the stale-callback guard.apps/desktop/src/app/session/hooks/use-message-stream/stream-flush.test.tsx: update the parked-frame test. The flush still never waits on a frame, butrunFlushnow registers a measurement callback that is allowed to wait.How to Test
npx vitest run --project ui src/app/session/hooks/use-message-stream/index.tsreverted, the new tests "stretches the flush gap when the deferred commit frame is expensive" and "ignores a late frame measurement once a newer flush has started" fail because no frame measurement exists. With this branch, all pass.npx vitest run --project ui src/app/session/hooks/npm run typecheckclean.npx eslinton the touched files clean (one pre-existingno-restricted-globalswarning in delta-flush.test.tsx).Checklist
Code
fix(scope):,feat(scope):, etc.)Documentation & Housekeeping
docs/, docstrings) - or N/A (code comments on the measurement path only)cli-config.yaml.exampleif I added/changed config keys - or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows - or N/AScreenshots / Logs