[GH-ISSUE #3816] [Bug] Windows service-worker cancellation can outlive its cleanup deadline #1474

Closed
opened 2026-10-16 21:05:53 +00:00 by bsakaguchi · 5 comments
Owner

Originally created by @N0zoM1z0 on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3816

Originally assigned to: @aakash-env on GitHub.

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Process capture or runtime observation

Observed behavior

On Windows Edge, cancellation of an attached service_workers: "block" scenario can take slightly longer than the policy owner's single 1,000 ms confirmation window. The policy restoration continues and completes, but the capture has already reported cleanup failure and retained its Playwright connection for retry.

The production real-browser verifier's storage cancellation check accepted every failed result as a valid cancellation and discarded the provider without calling close(). When the one-second boundary was crossed, the retained connection remained active and the following service-worker scenario could not attach: its CLI returned execution_failure, while an in-process replay remained queued until cancellation. The target still showed the preceding /scenario-environment page because the new scenario never completed browser admission.

Reproduction

  1. Build current main with its locked Node dependencies and stage it on Windows.
  2. From an ordinary, non-administrator Windows desktop session, run the real-browser storage cancellation scenario against Edge 154.0.4258.48 with service_workers: "block".
  3. Cancel while the attached target is navigating, then immediately run the attached service-worker scenario on the same target.
  4. Under the observed timing, policy cleanup starts and finishes about 1.096 seconds later, just beyond the 1,000 ms confirmation deadline. The cancellation result is accepted by the verifier, but the provider is not closed.
  5. The next CLI fails during browser connection; an in-process provider remains queued on the retained admission.

Observed frequency in the original exact-source pair was one failure in two runs. A focused diagnostic reproduced the bad sequence once; a later run in which cleanup completed within the deadline passed the full storage→service-worker workflow.

Expected behavior

A normal Windows cancellation cleanup that completes shortly after one second should finish within a bounded policy-cleanup window and preserve the typed cancellation result. The verifier must reject unrelated cleanup failures, close every provider it creates, and leave the attached target reusable before starting the next scenario.

Impact and scope

The affected capture fails closed and retains its cleanup owner, so target mutation is not silently reported as clean. A caller that keeps and closes the provider can retry cleanup. The shipped real-browser verifier, however, could falsely pass the cancellation check, leak the retained provider connection inside the verifier process, and block all later browser checks in that run.

Observed on Windows Edge only. Other browsers and ordinary workloads are NOT_ENOUGH_INFO.

REA version

Reproduced on source main d69acc3455c33f90a2fc4381db43d38b6af50622 (includes #3778); diagnosis rechecked against main 4692bb33300c7cb77b095b6cc0c33da861c1b860 with identical relevant browser sources.

Runtime and platform

Windows 11 x64, ordinary non-administrator user, Node 24.18.0, Edge 154.0.4258.48, sandbox enabled

Target and artifact context

Source-owned loopback browser verifier fixture, fresh verifier-owned Edge profile, exact discovered page target, attached production browser-scenario provider

Interface

CLI and production provider

Earliest failure stage

Cleanup after cancellation

Relevant output or evidence

Instrumented timestamps from the failing focused run:

03:14:16.133 policy restore started with stopLoading=true
03:14:17.228 owned CDP target detached
03:14:17.229 policy restore completed
03:14:17.237 next service-worker phase started

The 1.096-second restoration exceeded the policy owner's 1,000 ms confirmation boundary. The next direct replay reached PlaywrightScenarioSession.open but not open:browser-opened, which identifies admission/browser connection rather than service-worker policy installation as the blocked stage.

A control run that explicitly closed the cancellation provider passed storage and every production service-worker assertion in 165.08 seconds. It used 127.0 sampled process-family CPU seconds, peaked at 1,118,912,512 bytes of native Job commit and 1,030,111,232 bytes of sampled family working set, and ended with zero active Job processes.

Typed outcome or recovery expected

The cancellation must return AnalysisCancelledError after confirmed target restoration. A real cleanup failure must remain a cleanup failure and the provider owner must be retained until close() succeeds; the verifier must not classify that failure as a valid cancellation.

Analysis or suspected cause

PlaywrightScenarioServiceWorkers.close() applies one 1,000 ms deadline to the complete restoration sequence: optional Page.stopLoading, pending target initialization, init-script removal, live-frame restoration, exact-session detach, and transport close. That aggregate deadline is too narrow for Windows scheduling and multiple sequential CDP round trips.

Separately, verifyCancelledStorage() uses !result.ok || cancelled-step and creates its provider inline. This accepts cleanup/protocol failures and makes it impossible to close the provider. The structural fix is to use a bounded confirmation interval sized for the complete policy cleanup, keep the provider owner explicit, close it in finally, and require the exact cancellation error or a captured cancelled step.

Additional context

Related closed #3752 fixed retained attached environment overrides. This report is a distinct policy-cleanup confirmation and verifier-lifecycle defect.

Originally created by @N0zoM1z0 on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3816 Originally assigned to: @aakash-env on GitHub. ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Process capture or runtime observation ### Observed behavior On Windows Edge, cancellation of an attached `service_workers: "block"` scenario can take slightly longer than the policy owner's single 1,000 ms confirmation window. The policy restoration continues and completes, but the capture has already reported cleanup failure and retained its Playwright connection for retry. The production real-browser verifier's storage cancellation check accepted every failed result as a valid cancellation and discarded the provider without calling `close()`. When the one-second boundary was crossed, the retained connection remained active and the following service-worker scenario could not attach: its CLI returned `execution_failure`, while an in-process replay remained queued until cancellation. The target still showed the preceding `/scenario-environment` page because the new scenario never completed browser admission. ### Reproduction 1. Build current main with its locked Node dependencies and stage it on Windows. 2. From an ordinary, non-administrator Windows desktop session, run the real-browser storage cancellation scenario against Edge 154.0.4258.48 with `service_workers: "block"`. 3. Cancel while the attached target is navigating, then immediately run the attached service-worker scenario on the same target. 4. Under the observed timing, policy cleanup starts and finishes about 1.096 seconds later, just beyond the 1,000 ms confirmation deadline. The cancellation result is accepted by the verifier, but the provider is not closed. 5. The next CLI fails during browser connection; an in-process provider remains queued on the retained admission. Observed frequency in the original exact-source pair was one failure in two runs. A focused diagnostic reproduced the bad sequence once; a later run in which cleanup completed within the deadline passed the full storage→service-worker workflow. ### Expected behavior A normal Windows cancellation cleanup that completes shortly after one second should finish within a bounded policy-cleanup window and preserve the typed cancellation result. The verifier must reject unrelated cleanup failures, close every provider it creates, and leave the attached target reusable before starting the next scenario. ### Impact and scope The affected capture fails closed and retains its cleanup owner, so target mutation is not silently reported as clean. A caller that keeps and closes the provider can retry cleanup. The shipped real-browser verifier, however, could falsely pass the cancellation check, leak the retained provider connection inside the verifier process, and block all later browser checks in that run. Observed on Windows Edge only. Other browsers and ordinary workloads are `NOT_ENOUGH_INFO`. ### REA version Reproduced on source main `d69acc3455c33f90a2fc4381db43d38b6af50622` (includes #3778); diagnosis rechecked against main `4692bb33300c7cb77b095b6cc0c33da861c1b860` with identical relevant browser sources. ### Runtime and platform Windows 11 x64, ordinary non-administrator user, Node 24.18.0, Edge 154.0.4258.48, sandbox enabled ### Target and artifact context Source-owned loopback browser verifier fixture, fresh verifier-owned Edge profile, exact discovered page target, attached production browser-scenario provider ### Interface CLI and production provider ### Earliest failure stage Cleanup after cancellation ### Relevant output or evidence Instrumented timestamps from the failing focused run: ```text 03:14:16.133 policy restore started with stopLoading=true 03:14:17.228 owned CDP target detached 03:14:17.229 policy restore completed 03:14:17.237 next service-worker phase started ``` The 1.096-second restoration exceeded the policy owner's 1,000 ms confirmation boundary. The next direct replay reached `PlaywrightScenarioSession.open` but not `open:browser-opened`, which identifies admission/browser connection rather than service-worker policy installation as the blocked stage. A control run that explicitly closed the cancellation provider passed storage and every production service-worker assertion in 165.08 seconds. It used 127.0 sampled process-family CPU seconds, peaked at 1,118,912,512 bytes of native Job commit and 1,030,111,232 bytes of sampled family working set, and ended with zero active Job processes. ### Typed outcome or recovery expected The cancellation must return `AnalysisCancelledError` after confirmed target restoration. A real cleanup failure must remain a cleanup failure and the provider owner must be retained until `close()` succeeds; the verifier must not classify that failure as a valid cancellation. ### Analysis or suspected cause `PlaywrightScenarioServiceWorkers.close()` applies one 1,000 ms deadline to the complete restoration sequence: optional `Page.stopLoading`, pending target initialization, init-script removal, live-frame restoration, exact-session detach, and transport close. That aggregate deadline is too narrow for Windows scheduling and multiple sequential CDP round trips. Separately, `verifyCancelledStorage()` uses `!result.ok || cancelled-step` and creates its provider inline. This accepts cleanup/protocol failures and makes it impossible to close the provider. The structural fix is to use a bounded confirmation interval sized for the complete policy cleanup, keep the provider owner explicit, close it in `finally`, and require the exact cancellation error or a captured cancelled step. ### Additional context Related closed #3752 fixed retained attached environment overrides. This report is a distinct policy-cleanup confirmation and verifier-lifecycle defect.
bsakaguchi 2026-10-16 21:05:53 +00:00
  • closed this issue
  • added the
    triage
    bug
    labels
Author
Owner

@aakash-env commented on GitHub (Oct 15, 2026):

@N0zoM1z0 @baron kindly assign this issue to me

<!-- gh-comment-id:6183973723 --> @aakash-env commented on GitHub (Oct 15, 2026): @N0zoM1z0 @baron kindly assign this issue to me
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Good catch on the failure cascade — the root problem is that the verifier accepts any failed result as a valid cancellation and skips close(), so the provider leak turns a 100 ms overshoot into a blocked next scenario. Agree the fix needs both ends: bound the cleanup window (or at least allow a short grace for cleanup that already started) and make the verifier distinguish a genuine cancellation from an unrelated failure, always closing the provider either way. The existing #3841 direction of scoping service workers to the navigator global should also reduce how often these scenarios race in the first place.

<!-- gh-comment-id:6184815352 --> @XinwangTao commented on GitHub (Oct 15, 2026): Good catch on the failure cascade — the root problem is that the verifier accepts any failed result as a valid cancellation and skips close(), so the provider leak turns a 100 ms overshoot into a blocked next scenario. Agree the fix needs both ends: bound the cleanup window (or at least allow a short grace for cleanup that already started) and make the verifier distinguish a genuine cancellation from an unrelated failure, always closing the provider either way. The existing #3841 direction of scoping service workers to the navigator global should also reduce how often these scenarios race in the first place.
Author
Owner

@N0zoM1z0 commented on GitHub (Oct 15, 2026):

Final root-cause update after the Windows replay:

The one-second aggregate cleanup window was one contributor, but the producing-boundary defects were broader:

  • storage and service-worker cleanup could both own Page.stopLoading, creating a navigation race;
  • a detached flat iframe could leave its initialization promise pending;
  • Chromium legitimately replied to a command for that vanished flat session with browser-level -32001 Session with given id not found. and no sessionId; the connection parser treated that exact terminal rejection as a connection-wide protocol failure;
  • selected-main and uncommitted child-frame restoration went through Playwright frame waits during navigation (the empty child wait measured about 10.4 seconds);
  • the real verifier accepted any error as cancellation and did not close the provider it created.

The fix gives Page.stopLoading one owner, models target detach as an explicit lifetime, restores the selected main realm through the owned CDP session, skips only child frames without a committed document after removing future-document scripts, and recognizes only Chromium's exact terminal missing-session rejection. Other missing/foreign session responses remain fail-closed. Verifier cancellation now requires the typed cancellation outcome and closes its provider in finally.

Validation on the latest-main merge head:

  • focused browser/session and CDP tests: 36/36 passed;
  • npm run check:fast: passed;
  • ordinary-user Windows Edge production storage→service-worker workflow: passed on head df7a223f2879d2c7c15fb47f579bf7a4cf0d63a6; every storage and service-worker assertion was true and cleanup left zero Job processes. After the final main refresh, all affected sources and verifier inputs are unchanged at final head bfc0545b1b641edf7194909443e7ed300cf4c7df, where focused 36/36 and check:fast passed again.

The final r18 workflow used 203.53 seconds of Job wall time, 197.67 sampled family CPU seconds, 1,098,883,072-byte peak Job commit, 1,364,963,328-byte sampled working-set peak, and zero remaining Job processes. The host lane was limited to two logical CPUs, BelowNormal priority, and 2 GiB Job memory. A pre-refresh pass also completed, but host variance does not support a speedup claim.

The implementation is on branch fix/windows-edge-service-worker-navigation-3816; the PR will link this issue.

<!-- gh-comment-id:6186728950 --> @N0zoM1z0 commented on GitHub (Oct 15, 2026): Final root-cause update after the Windows replay: The one-second aggregate cleanup window was one contributor, but the producing-boundary defects were broader: - storage and service-worker cleanup could both own `Page.stopLoading`, creating a navigation race; - a detached flat iframe could leave its initialization promise pending; - Chromium legitimately replied to a command for that vanished flat session with browser-level `-32001 Session with given id not found.` and no `sessionId`; the connection parser treated that exact terminal rejection as a connection-wide protocol failure; - selected-main and uncommitted child-frame restoration went through Playwright frame waits during navigation (the empty child wait measured about 10.4 seconds); - the real verifier accepted any error as cancellation and did not close the provider it created. The fix gives `Page.stopLoading` one owner, models target detach as an explicit lifetime, restores the selected main realm through the owned CDP session, skips only child frames without a committed document after removing future-document scripts, and recognizes only Chromium's exact terminal missing-session rejection. Other missing/foreign session responses remain fail-closed. Verifier cancellation now requires the typed cancellation outcome and closes its provider in `finally`. Validation on the latest-main merge head: - focused browser/session and CDP tests: 36/36 passed; - `npm run check:fast`: passed; - ordinary-user Windows Edge production storage→service-worker workflow: passed on head `df7a223f2879d2c7c15fb47f579bf7a4cf0d63a6`; every storage and service-worker assertion was true and cleanup left zero Job processes. After the final main refresh, all affected sources and verifier inputs are unchanged at final head `bfc0545b1b641edf7194909443e7ed300cf4c7df`, where focused 36/36 and `check:fast` passed again. The final r18 workflow used 203.53 seconds of Job wall time, 197.67 sampled family CPU seconds, 1,098,883,072-byte peak Job commit, 1,364,963,328-byte sampled working-set peak, and zero remaining Job processes. The host lane was limited to two logical CPUs, BelowNormal priority, and 2 GiB Job memory. A pre-refresh pass also completed, but host variance does not support a speedup claim. The implementation is on branch `fix/windows-edge-service-worker-navigation-3816`; the PR will link this issue.
Author
Owner

@N0zoM1z0 commented on GitHub (Oct 15, 2026):

PR review found and fixed three directly related cleanup-boundary gaps on top of the initial implementation:

  1. A storage owner was treated as proof that Page.stopLoading succeeded. Stop ownership now transfers to the policy owner only after failed storage cleanup has ended; the successful path still has exactly one owner.
  2. Cleanup replayed a historical policy setup rejection before removing an init script acquired earlier in setup. Restore now waits for setup/detach to settle without re-reporting that error, then cleans the effects actually acquired.
  3. The detached-session parser exception lacked near-miss tests. Foreign sessionId, wrong code, wrong message, and simultaneous result/error cases now prove the connection still fails closed.

Final head b775324ead8f8fb216a9c2ffa0f852803183fc05 passes focused browser/CDP tests 42/42 and npm run check:fast. The complete ordinary-user Windows Edge storage→service-worker workflow also passed on that exact head: 213.09 s Job wall, 174.50 sampled family CPU seconds, 1,185,759,232-byte peak Job commit, 1,481,318,400-byte sampled working-set peak, and zero remaining Job processes under the 2 CPU/BelowNormal/2 GiB limits. Every production assertion was true.

<!-- gh-comment-id:6187203805 --> @N0zoM1z0 commented on GitHub (Oct 15, 2026): PR review found and fixed three directly related cleanup-boundary gaps on top of the initial implementation: 1. A storage owner was treated as proof that `Page.stopLoading` succeeded. Stop ownership now transfers to the policy owner only after failed storage cleanup has ended; the successful path still has exactly one owner. 2. Cleanup replayed a historical policy setup rejection before removing an init script acquired earlier in setup. Restore now waits for setup/detach to settle without re-reporting that error, then cleans the effects actually acquired. 3. The detached-session parser exception lacked near-miss tests. Foreign `sessionId`, wrong code, wrong message, and simultaneous result/error cases now prove the connection still fails closed. Final head `b775324ead8f8fb216a9c2ffa0f852803183fc05` passes focused browser/CDP tests 42/42 and `npm run check:fast`. The complete ordinary-user Windows Edge storage→service-worker workflow also passed on that exact head: 213.09 s Job wall, 174.50 sampled family CPU seconds, 1,185,759,232-byte peak Job commit, 1,481,318,400-byte sampled working-set peak, and zero remaining Job processes under the 2 CPU/BelowNormal/2 GiB limits. Every production assertion was true.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Thanks for the deep root-cause follow-up. Making Page.stopLoading single-owner and treating Chromium's exact -32001 missing-session rejection as the only terminal case (while keeping every other session failure fail-closed) makes the cleanup boundary much more predictable. 42/42 focused tests plus the full ordinary-user Windows Edge workflow passing on the final head is solid evidence. One suggestion: since the aggregate 1s window proved too narrow under Windows scheduling, consider a regression test that simulates cleanup crossing the deadline, so the grace handling stays visible.

<!-- gh-comment-id:6187457570 --> @XinwangTao commented on GitHub (Oct 15, 2026): Thanks for the deep root-cause follow-up. Making Page.stopLoading single-owner and treating Chromium's exact -32001 missing-session rejection as the only terminal case (while keeping every other session failure fail-closed) makes the cleanup boundary much more predictable. 42/42 focused tests plus the full ordinary-user Windows Edge workflow passing on the final head is solid evidence. One suggestion: since the aggregate 1s window proved too narrow under Windows scheduling, consider a regression test that simulates cleanup crossing the deadline, so the grace handling stays visible.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1474
No description provided.