chore(maven): shed a refused upload-session open as 503 (upload-session-concurrency-cap plan: 5/12)
What
The Maven artifact PUT answered 500 internal_server_error for every refused
upload-session open, saturation included. A refused-because-saturated open now
answers 503 with service_unavailable, its canonical detail, and a jittered
Retry-After. Every other refused open keeps the 500.
writeSessionOpenError classifies at the call site rather than in a mapper,
because internal/format/maven/upload.go handed the raw error to
writeStorageError, which writes 500 unconditionally with no classification.
Why
This is Step 5 of the upload-session concurrency cap.
storage.ErrUploadSessionsBusy landed in !2408 (merged) with no handler arm on the
Maven deploy route, so a shed would surface as a server fault and tell the
client nothing about when to come back.
Nothing changes on main today. The gate's Acquire is unwired until Step 11,
so this arm is unreachable and no request can reach the 503 yet.
The Retry-After window
The header renders 28 to 42 seconds: a base of 35 s with a one-fifth
half-width drawn around it.
The base is priced on the precedent inlineBuildTimeoutRetryAfter sets
(rebuildTimeout plus five seconds) rather than reusing that constant, whose
doc prices it against a rebuild worker's lifetime. It is held as a literal, so
a reprice of the rebuild cannot move a client-visible retry. The comment says
so, because a comment claiming the derivation would go stale in silence.
The window matches the npm publish arm of the same plan, so a reader can carry
the number between the two packages. Tests assert bounds rather than an exact
value, per the plan's rule for every rendering step, and also assert a render
below the base. Without that second assertion the bounds admit a one-sided
draw whole, and a revert to base + rand.N(jitter) would pass green.
Spec
docs/specs/S10-maven-hosted.md now records the 503 at all four sites that
enumerate this route's statuses: the PUT response-status table, the Step 4c
failure-modes summary, the format-specific error-code catalog, and Error Cases.
The plan's Step 5 file list does not name the spec. It is here because a reader
of any one of those tables would otherwise conclude the route cannot answer
503. The GET table keeps its 500: the gate is consulted when a session
opens, and only PUT opens one.
Test plan
TestStreamAndCommitPrimary_RefusedOpenAnswersItsCausepairs the two refusals a session open takes. The sentinel gets503,service_unavailableand aRetry-After; every other error keeps500,internal_server_errorand advertises noRetry-After. Both arms assert the cause stays out of the body, and thatrequest_idechoes the real correlation ID.TestStreamAndCommitPrimary_ShedRetryAfterCarriesTheJitterchecks the spread reaches the header, and that at least one render falls below the base.TestStreamAndCommitPrimary_ShedStillBoundsTheDrainWindowkeeps the shed on the refused-open return, where the unread body's drain bound is armed.TestMavenDeadlineOpenFailStore_NeedsAnErrorreaches the refusal store's own guard, which no case that setserrcan.
Each new guard was mutation-checked: dropping the jitter, reverting to the one-sided draw, and widening the sentinel narrowing each redden a case.
Guardrails
- Merge order. Depends on !2408 (merged), which is merged. Step 11 depends on this
step and edits the same file for its
defer release(), so this lands first. - e2e scenarios. None added. The plan states that Steps 1 to 10 add no
scenario, because none of them changes observable behavior, and that Step 11
adds the concurrent-push-past-the-cap scenario. That is where the client's
reaction to the
503gets established rather than assumed. - Conformance. Not run. The arm is unreachable until Step 11 wires the acquire, so no conformance case can reach it.
- Size. 276 reviewable LOC across 6 files: 50 source, 221 test, 5 docs. Under the 500 ceiling.
- ADRs. ADR-003 is the governing one and is met rather than deviated from. ADR-004's per-user and per-namespace upload-session limits are a different control from this global memory bound.
Stack
| Step | MR | State |
|---|---|---|
| 2. admission gate primitive and shed sentinel | !2408 (merged) | merged |
| 5. Maven publish shed arm | this MR | open |
Steps 1, 3, 4, and 6 to 12 have no MR open yet. This step depends only on Step 2, so those do not block it.
Related to #1035