Skip to content

emrg: fix Restart Manager scan dead-loop (RmGetList m=0, DeleteFile code 5) - #829

Merged
argszero merged 2 commits into
masterfrom
feature/rm-getlist-deadloop-fix
Aug 17, 2026
Merged

emrg: fix Restart Manager scan dead-loop (RmGetList m=0, DeleteFile code 5)#829
argszero merged 2 commits into
masterfrom
feature/rm-getlist-deadloop-fix

Conversation

@argszero

Copy link
Copy Markdown
Owner

Fixes the Windows installer DeleteFile-code-5 root cause identified in rant 2026-08-17T21:04:32 (v0.2.44 install still failed).

Root cause

The C# RM.Who() in emrg/_stop_all.py passed m=0 (buffer capacity) forever:
RmGetList's pdwProcCount parameter is IN/OUT — input = buffer capacity,
output = count written. With capacity 0 every call returned ERROR_MORE_DATA(234),
so the loop never terminated → zero owners killed → the installer still hit
DeleteFile code 5 (websockets pyd locked by an external browser-harness daemon).
Host evidence: two 60s RM scan timeouts ≈ 120s gap between install start and
stop_all.log.

Changes

  1. RmGetList fixed: preallocate 50 entries (m=50), on 234 resize to n
    and retry, hard-capped at MAX_ATTEMPTS=3 so an abnormal API can never
    dead-loop; result takes Math.Min(n, m) capped by buffer length.
  2. RmRegisterResources return checked (reg_fail counter — a failed batch
    is now visible, not silent).
  3. No more silent scans: PowerShell emits an rm-diag line
    (files / owners / elapsed_ms / reg_fail); Python _print_rm_diag() logs the
    summary in both stop_lock_owners() and _verify_windows().
  4. Tests (893 → 904): parametrized pure-Python model of the RmGetList loop
    (7 scenarios incl. cap termination — never dead-loops), C# template static
    assertions, diag parsing + logging tests.

Acceptance (self-verifiable)

  • pytest 903 passed + 1 skipped; import + CLI green; CI test + test-windows.
  • Host: Windows install with a live browser-harness daemon should now show
    killed file-lock owner: PID ... python.exe | ...browser_harness.daemon
    • hint + rm-scan ... line in stop_all.log, and the installer no longer
      reports DeleteFile code 5.

…ode 5)

Rant 2026-08-17T21:04:32: the C# RM.Who() loop passed m=0 (buffer
capacity) forever — RmGetList's pdwProcCount is in/out, so every call
returned ERROR_MORE_DATA(234) -> infinite loop -> zero owners killed ->
installer still hit DeleteFile code 5 on Windows (websockets pyd locked
by an external browser-harness daemon).

- RmGetList: preallocate 50 entries (m=50), on 234 resize to n and
  retry, hard-capped at MAX_ATTEMPTS=3 so an abnormal API can never
  dead-loop; result takes Math.Min(n, m) capped by buffer length
- RmRegisterResources return value now checked (reg_fail counter)
- New rm-diag line (files/owners/elapsed_ms/reg_fail) + Python
  _print_rm_diag logging in stop_lock_owners() and _verify_windows()
  so a scan can never be silently idle
- Tests: parametrized pure-Python model of the RmGetList loop (7
  scenarios incl. cap termination) + C# template static assertions +
  diag parsing/logging tests (893 -> 904)

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle (1/3)

Verified:

  • C# RmGetList loop fixed: m=50 preallocated capacity, resize to n on 234, hard-capped at MAX_ATTEMPTS=3 — no unbounded do/while(rc==234) remains
  • RmRegisterResources return value checked (reg_fail counter surfaced via rm-diag line)
  • _print_rm_diag() logs files/owners/elapsed/reg_fail in both stop_lock_owners() and _verify_windows() — no silent scans
  • Parametrized pure-Python model covers 7 RM scenarios incl. cap termination (never dead-loops)
  • pytest 903 passed + 1 skipped (904); import + CLI green; CI test + test-windows PASS (run 32033707677)

… 21:06:05/21:06:31)

Companion rants to the RmGetList dead-loop fix (21:04:32), explicitly to be
merged together. Both target emrg/_stop_all.py:

21:06:05 - verify must be INDEPENDENT of Restart Manager + pre-verify the
install dir writability, so a broken detector can never silently pass verify:
- new check_install_writable(): probes install\ files with an exclusive
  open (CreateFileW dwShareMode=0 = FileShare.None — exactly the semantic
  the installer's DeleteFile needs); returns the locked-file list; pure
  _check_locked_files(try_open=...) extractable + testable on POSIX
- verify() now includes a lock-probe category: locked files -> named
  residual -> exit 1 (installer aborts with a clear list, no code-5 dialog)
- kill retry: RM owners killed but locks linger -> re-probe + retry up to
  2x (Try-again semantics); still locked -> verify failure list

21:06:31 - stop_all.py log standard so every run is diagnosable from the log:
- header: build stamp / python version / platform / pid
- per-step '[N/T] name -> done (Xs)' via _step_plan() (steps stay ordered:
  clients first, daemon last, bundled-git+RM Windows-only)
- every step wrapped: exceptions print 'ERROR <step>: <reason> (<class>)'
  and the run continues to the final exit code
- verify per-category summary line (GUI/daemon/cmdline-scan/RM re-scan/
  lock-probe/bundled-git) + 'exit code 0 (clean) / 1 (N residual)' + total
- all lines keep the 'emrg stop: ' prefix

Tests (904 -> 914): probe traversal/collection (injected opener),
check_install_writable branches (posix noop / no install dir / locked /
probe error), verify categories incl. lock-probe + summary, kill-retry loop
(3 lock_owners calls), step-exception logging, clean/residual exit lines.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle (2/3) — re-verified on updated head 70876f5 (companion rants 21:06:05 + 21:06:31 folded in, per host instruction to merge together)

Companion changes verified:

  • check_install_writable(): independent of Restart Manager — probes install\ with CreateFileW dwShareMode=0 (exact installer-DeleteFile semantic); _check_locked_files(try_open=...) pure + POSIX-testable
  • verify() gains lock-probe category → locked files become named residuals → exit 1 (no silent pass)
  • kill retry: re-probe + up to 2 retries (Try-again semantics); still locked → verify failure list
  • stop_all() log standard: header (stamp/python/platform/pid), [N/T] steps via _step_plan() (order preserved: clients → daemon → bundled-git → RM), step exceptions print ERROR and continue, per-category verify summary, exit-code + total elapsed
  • pytest 913 passed + 1 skipped (914); import + CLI green; CI test + test-windows PASS (run 32034581689)

@argszero
argszero merged commit c6e5e81 into master Aug 17, 2026
2 checks passed
@argszero
argszero deleted the feature/rm-getlist-deadloop-fix branch August 17, 2026 13:23
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