Skip to content

docs(portmap): correct the recycle-check cost - it is elevation-bound, not ~180 ms#42

Merged
donislawdev merged 1 commit into
masterfrom
perf/resolver-recycle-batch
Jul 24, 2026
Merged

docs(portmap): correct the recycle-check cost - it is elevation-bound, not ~180 ms#42
donislawdev merged 1 commit into
masterfrom
perf/resolver-recycle-batch

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

What and why

The socket-layer work left a deferred perf follow-up: "resolver refresh is ~180 ms/rebuild (create_time recycle check)", with a plan to batch the create-times in one syscall. Before building that, I measured it — and the premise does not hold under the conditions that matter.

Measured 2026-07-24 (Win11, CPython 3.14, Chrome open), elevated and non-elevated (runas /trustlevel:0x20000):

elevated (every real session) non-elevated (--simulate on non-admin)
socket PIDs the recycle check is slow on 0 of 27 16 of 27
warm refresh (the recycle check) 0.16 ms ~90–180 ms
cold resolve('chrome') 36 ms 381 ms

The cost of create_time() is decided by one thing: whether OpenProcess succeeds — ~0.005 ms when it does, ~5.7 ms when it's denied (psutil then scans the whole system for that one PID). Denial is an elevation question, not the "hardened process / Chrome renderer" one the old note assumed — renderers don't own sockets, so they never reach this loop. The handoff's ~365/180 ms was measured non-elevated; my 381 ms non-elevated cold matches it.

Real impairment always runs elevated — WinDivert won't open without admin, so a non-admin START fails outright. So the recycle check is already cheap (0.16 ms warm / 36 ms cold) in every real session. The only non-elevated path that runs the resolver at all is --simulate (synthetic packets; the cost sits on the resolver thread, never the capture one).

Two docstrings had drifted apart — _psutil_created claimed 0.005 ms, info claimed ~5 ms — and each was right for a different elevation. This states it once, accurately.

Changes

  • Docs only, no behaviour change. Corrected the _psutil_created and PortTable.info docstrings in beantester/portmap.py, and the matching PROJECT_NOTES line (in the private Doc repo, not this PR).
  • Recorded the decision: batching the denied PIDs via NtQuerySystemInformation was measured and rejected — it speeds only the non-elevated --simulate warm check, does nothing for the elevated hot path, and nothing for the cold resolve (which name resolution dominates, not create_time). Written down so the next session doesn't re-open it.

Reviewer notes

  • No CHANGELOG.md line: nothing a user notices (pure prose). Documented in CHANGELOG-INTERNAL.md, which also closes the "known perf follow-up" notes in two earlier entries.
  • No new test: this changes no code path. The existing guard (tests/test_processes.py, the recycle-protection suite) still passes (31/31), and no test pinned the old prose.

Checklist

  • python -m pytest tests passes locally. (tests/test_processes.py 31/31; docs-only change, no code path touched.)
  • New behaviour has tests (see tests/ for the style). (No behaviour change — docstring/notes correction only.)
  • UI text goes through i18n keys, with both lang/en.json and lang/pl.json updated. (No UI text.)
  • User-facing changes noted in CHANGELOG.md; technical ones and new tests in CHANGELOG-INTERNAL.md, under [Unreleased]. (Internal-only, nothing a user notices.)
  • Commits follow Conventional Commits (type(scope): summary).
  • No version bump — the owner closes a version via VERSION.txt.

🤖 Generated with Claude Code

The deferred "resolver refresh is ~180 ms/rebuild" follow-up did not reproduce
elevated. Measured 2026-07-24 (Win11, CPython 3.14, Chrome open), elevated and
non-elevated (runas /trustlevel): create_time() is ~0.005 ms per PID when
OpenProcess succeeds and ~5.7 ms when it is DENIED (psutil then scans the whole
system for that one PID). Denial is an elevation question, not the "hardened
process / Chrome renderer" one the older note assumed - renderers do not own
sockets, so they never reach this loop.

- elevated: 0 of 27 socket PIDs denied, warm recycle check ~0.16 ms, cold
  resolve('chrome') ~36 ms
- non-elevated: 16 of 27 denied, ~90-180 ms warm, ~381 ms cold (this is where the
  handoff's numbers came from)

Real impairment always runs elevated (WinDivert needs admin), so the check is
cheap in every real session; the only non-elevated path that runs the resolver is
--simulate. Batching the denied PIDs via NtQuerySystemInformation was measured and
rejected: it speeds only the non-elevated demo path and nothing else. Two docstrings
had drifted apart (_psutil_created said 0.005 ms, info said ~5 ms) - both were right
for a different elevation; now stated once, accurately. Docs only, no behaviour
change, so CHANGELOG.md is untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@donislawdev
donislawdev merged commit bed6ae8 into master Jul 24, 2026
8 checks passed
@donislawdev
donislawdev deleted the perf/resolver-recycle-batch branch July 24, 2026 21:39
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