Repository navigation
fix(proxy): sample CPU via os.cpus() on hosts without /proc/stat - #53
Merged
TriDefender merged 1 commit intoSep 27, 2026
Merged
Conversation
The CPU governor read host load from /proc/stat and fell back to os.loadavg() when that file was missing. os.loadavg() is hard-wired to [0,0,0] on Windows (documented in Node, inherited by Bun), so lastCpuPercent stayed at 0 forever and tick() always took the ramp branch: solve concurrency and the pool target climbed to their maxima (poolSizeMax 60) even while the host was saturated. Observed on Windows: a proxy that pinned a core, grew the heap to ~2.8 GB over tens of minutes, and eventually stopped answering every HTTP route (including paths that should 404) while holding 0 outbound connections. Replace the loadavg fallback with a delta over os.cpus() tick counters, which are cumulative per-core milliseconds on every platform and yield the same host-wide busy ratio /proc/stat does. Also track which source produced a sample so a source switch re-baselines instead of diffing two unrelated scales, and reject non-positive or inverted deltas (counter reset, core-count change) instead of feeding the throttle a bogus percentage. Linux keeps the /proc/stat fast path and is unaffected.
4 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On Windows the CPU governor never throttles captcha solving, which lets the
token pool run unattended until the proxy stops serving.
CaptchaCpuGovernorreads host load from/proc/statand, when that file ismissing, falls back to
os.loadavg():os.loadavg()is hard-wired to[0,0,0]on Windows (documented in Node, andBun inherits it), so
lastCpuPercentstays0forever. Everytick()thentakes the ramp branch:
Concurrency and the pool target climb to their maxima (
poolSizeMax, default60) no matter how loaded the host actually is.
Observed behaviour (Windows 11, v4.7.0 exe)
CLOSE_WAITsocketsRestarting clears it; it recurs whenever the pool is allowed to refill.
Fix
Replace the
loadavgfallback with a delta overos.cpus()tick counters,which are cumulative per-core milliseconds on every platform and produce the
same host-wide busy ratio
/proc/statdoes.readProcStatCounters()— Linux fast path, returnsnullon failurereadOsCpusCounters()— new cross-platform fallbackreadCpuCounters()=proc ?? osTwo smaller hardening changes while touching this path:
source: "proc" | "os") so a sourceswitch re-baselines instead of diffing two unrelated scales
and keep the last good reading, rather than feeding the throttle a bogus
percentage
Linux keeps the
/proc/statpath and is unaffected.Verification
bun test— 885 pass, 0 fail (56 files), including the 6 existingCaptchaCpuGovernorcases, which stubsampleCpuPercentand so areunaffected by construction.
cpuLimitPercent: 5, burning one core for3 s between ticks:
Before the change the same scenario reports
cpu = 0and ramps.os.cpus()deltas are scaled correctly on win32: a 2 ssample on a 20-core machine advances
totalby ~40 s of core-time.Notes
The default
CAPTCHA_CPU_LIMIT_PCTis 100, so even with correct sampling thegovernor only engages when the whole host is saturated. Sampling correctly is
still what makes the existing
CAPTCHA_CPU_LIMIT_PCTknob usable at all onWindows — today it does nothing there.