Repository navigation
fix(net): per-route coordinator body caps and counted baseline skips - #50
Merged
Merged
Conversation
Follow-ups from the PR #49 review: - Per-route request-body caps under the PW_MAX_BODY_BYTES ceiling (4 MiB default): /tasks/{id}/deliver is capped at 2,416,384 bytes (a 200k-char deliverable fully JSON-escaped, plus signature fields), field-capped small bodies (/users, /nodes/register, heartbeat, progress, accept, feedback, rate, /admin/enroll) and non-POST requests at 64 KiB. The free-form routes (/result dicts, /jobs, blob uploads) keep the ceiling because they have no documented smaller bound. A lower ceiling still lowers every cap. - A baseline skipped because every PW_BASELINE_WORKERS slot is busy is now counted (baseline_skipped_busy on GET /metrics) and the log line names the running total. - Tests: an exact-cap /deliver body passes and cap+1 is 413, small-route caps (declared and chunked), the route-to-cap mapping, and the skip counter. README documents PW_MAX_BODY_BYTES and PW_BASELINE_WORKERS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 11, 2026
wikithoughts
added a commit
that referenced
this pull request
Oct 11, 2026
…ttled baseline-skip log (#52) Follow-ups from the PR #50 review: - Blob uploads get their own route cap, _BLOB_BODY_CAP = _BLOB_CAP + 16 KiB slack, derived from the handler's chunk constant (now defined with the other caps), instead of the 4 MiB ceiling. - _body_cap_for matches the app-relative path: a whole-segment root_path prefix (uvicorn --root-path puts it in scope["path"]) and trailing slashes are stripped first, as Starlette's router does, so /pw/users and /users/ get /users's 64 KiB cap instead of the ceiling. - The "baseline skipped" log line is printed for the first skip and every 100th; the /metrics counter stays exact. - README cap wording fixed (blob uploads are no longer "at the ceiling"). - Tests: exact-cap passes and cap+1 is 413 for /users, /result, /jobs and blobs; GET with a >64 KiB body is 413; trailing-slash and root_path-prefixed paths get the route cap; the log throttle keeps an exact count. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Follow-ups from the PR #49 review (
claude-review-pw-49.md: findings 1, 2 and 4). #49 merged as 722caf1.What changed
passiveworkers/net/coordinator_app.py,BodySizeLimitvia the new_body_cap_for).PW_MAX_BODY_BYTES(default 4 MiB) is now the ceiling. Routes with a documented smaller bound get that bound:POST /tasks/{id}/deliver:_DELIVER_BODY_CAP= 2,416,384 bytes. That is 200k chars × 12 bytes (an astral char JSON-escaped as a surrogate pair) plus 16 KiB forsignature/signer_pub/keys.POST /users,/nodes/register,/nodes/heartbeat,/admin/enroll,/tasks/{id}/progress|acceptand/jobs/{id}/feedback|rate, plus every non-POST request: 64 KiB./tasks/{id}/result(bare dict),/jobs(free-formrequires/then, 200 items) and blob uploads (which keep their own 512 KiB check) keep the ceiling. None of them has a documented smaller bound, and the largest real node result is unknown, so a tighter cap could drop legitimate results.PW_MAX_BODY_BYTESstill lowers every cap._start_baselinenow counts each skip. The count is exposed asbaseline_skipped_busyonGET /metrics, and the log line now gives the running total:[baseline] skipped for job X: all N slots busy (PW_BASELINE_WORKERS); K skipped since start. Thebaseline.pydocstring no longer says "one thread per job" and describes the bounded slots.PW_MAX_BODY_BYTESand the per-route caps, plusPW_BASELINE_WORKERSand its skip behaviour. Neither variable was documented before. This PR adds no new env knob.Findings addressed
POST /userscould make the server buffer 4 MiB. It now gets 64 KiB, and/delivergets its exact derived cap./resultis unchanged; see "Unverified"./deliverbody passes. New test: a worst-case 200k-astral-char deliverable, padded to exactly the cap, reaches the endpoint (200), and cap+1 gets a 413 without running the endpoint.Tests (
tests/test_coordinator_limits.py, +13)/deliver: a body exactly at the cap passes, and cap+1 gets a 413./metricskey.coordinator_app.pywith the new tests):12 failed, 12 passed. The only new test that passes on old code is the ceiling-preservation test, which guards unchanged behaviour by design.Verify
ruff check . && pytest tests/ -q --cov=passiveworkers --cov-fail-under=75→ exit 0:All checks passed!,799 passed, 1 skipped in 42.43s,Total coverage: 83.61%python -m py_compile $(git ls-files '*.py')→ exit 0/metricsgains a key;app.py's only consumer reads named fields), so no browser check was run.Unverified / limitations
/resultpayload is still unknown, so/resultkeeps the 4 MiB ceiling rather than a guessed smaller cap. Bounding the result dict per field would need changes in the owner-reviewed store/ledger files, which are out of scope here.scope["path"]. If the coordinator is mounted under aroot_pathprefix, the small/deliver patterns don't match and those routes fall back to the ceiling, which is the pre-PR behaviour. Nothing is broken, but nothing is tightened either.ratelimit.py) is outside this lane's file ownership and was left as-is; the review rated it acceptable.🤖 Generated with Claude Code