Skip to content

atenet-router: take parking-lot slots at the park transition, not at admission - #1229

Open
Omer Yahud (omeryahud) wants to merge 3 commits into
agent-substrate:mainfrom
omeryahud:fix/1081-lot-admission-on-park
Open

atenet-router: take parking-lot slots at the park transition, not at admission#1229
Omer Yahud (omeryahud) wants to merge 3 commits into
agent-substrate:mainfrom
omeryahud:fix/1081-lot-admission-on-park

Conversation

@omeryahud

Copy link
Copy Markdown
Collaborator

Fixes #1081

Context

HandleRequestHeaders admitted every request to the parking lot before its ResumeActor lookup, so a request to an already-RUNNING actor held a slot for the duration of a millisecond-scale control-plane call — and once the lot was full, such requests were shed with 503 "router at capacity" before the lookup even ran. That contradicts the design guarantee in docs/request-parking.md ("a saturated lot cannot starve requests to already-running actors, at any lot size"): the fast-path headroom built into the 2× ext_proc circuit-breaker sizing was nullified by the lot check sitting in front of the resume, and one saturated WorkerPool could black-hole routing for every other pool's healthy actors. TestHandleRequestHeaders_ParkingLotFull pinned the broken behavior as expected.

Change

A caller now occupies a lot slot only from the moment its resume actually parks — the flight's first retryable failure — so the guarantee holds by construction, with no status probe:

  • Flight registry replaces singleflight.Group (resumeFlight{parked, done, result} in a mutex-guarded map, split into flight.go). The registry keeps singleflight's exact semantics — per-actor dedup, forget-on-completion, leader/joiner outcome labels, caller-cancel-doesn't-abort-flight — and adds the one hook singleflight cannot express: a parked channel closed at the first retryable error. A side-map next to Group can't do this race-free, because DoChan never tells a joiner which flight generation it attached to.
  • Two-phase caller wait: slot-free while the flight resolves; on the parked signal the caller must acquire a slot (lot.enter) to keep waiting, and is shed with the existing 503 "router at capacity" when the lot is full — after exactly the one resume attempt that revealed it would have to wait.
Scenario Before After
Full lot + RUNNING actor shed 503, no lookup served — never touches the lot
Full lot + request that must wait shed before any attempt shed after one attempt (per-actor deduped)
Parking disabled (Max=0) unchanged unchanged
parking.active / wait.duration counted every lookup count only genuinely parked requests
  • Tests pass
  • Appropriate changes to documentation are included in the PR

…admission

The lot admitted every request before its resume lookup, so a full lot
shed traffic to already-RUNNING actors — exactly the starvation
docs/request-parking.md rules out (agent-substrate#1081), and
TestHandleRequestHeaders_ParkingLotFull pinned that behavior as expected.

Replace the resumer's singleflight.Group with a per-actor flight
registry whose flights signal their park transition (the first
retryable error). A caller now waits slot-free while its flight
resolves, acquires a slot only once the flight parks, and is shed with
the existing 503 "router at capacity" only at that transition — after
the single attempt that revealed it would have to wait. Requests
resolved on the first attempt never touch the lot, so a saturated lot
cannot starve running-actor traffic, and parking.active /
parking.wait.duration now count only genuinely parked requests.
Review-driven cleanup, no behavior change: resumeFlight and its lifecycle
move to a dedicated file, with the close-once and publish-ordering
invariants enforced by methods instead of comments at the call sites —
park() owns the parked close (the signaled bool moves into the struct),
publish() owns the result-write → registry-delete → done-close order,
and runFlight shrinks to the retry loop plus one publish call, with the
terminal-state classification split into flightResult().
- Drop the fast-path benchmarks: the before/after numbers live in the
  PR description; nothing in CI executes benchmarks, so the file only
  cost maintenance.
- Reformat the tests this change adds: nested proto literals one field
  per line, and the anonymous mock signatures wrapped one parameter per
  line. No behavior change.
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.

atenet-router: requests to running actors consume parking-lot slots

1 participant