Skip to content

feat(consumergate): runtime stop/start of queue controllers#375

Open
sbalabanov wants to merge 1 commit into
mainfrom
consumer-gate-impl
Open

feat(consumergate): runtime stop/start of queue controllers#375
sbalabanov wants to merge 1 commit into
mainfrom
consumer-gate-impl

Conversation

@sbalabanov

@sbalabanov sbalabanov commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Implements the consumer-gate RFC (#374) and showcases a Request Cancellation e2e test using it.

Summary

  • Adds platform/extension/consumergate with the Gate, Entry, Admin, Factory, and configuration contracts, plus generated mocks.
  • Adds file-backed and no-op implementations. The file backend reads gate state directly from shared files for every delivery and uses atomic temp-file-plus-rename writes; it does not cache snapshots or verdicts.
  • Clears every queue delivery through its consumer-group/partition gate before controller processing. Blocked deliveries remain in flight through visibility extensions without consuming retry attempts or breaking partition ordering.
  • Fails open when gate reads, monitoring, or visibility extensions fail. Consumer shutdown instead leaves a blocked delivery unacked for normal redelivery.
  • Keeps parked records and payload files only while a delivery is actively blocked.
  • Uses polling as the portable convergence mechanism. Filesystem events may be added later as an optimization alongside polling because event delivery varies across operating systems, bind mounts, overlay/network filesystems, rootless Docker, and Docker Desktop.
  • Wires the shared file gate into gateway, orchestrator, and runway. Stovepipe uses the no-op implementation.
  • Runs application containers under the host test UID/GID with rootful Docker, while using 0:0 under rootless Docker so bind-mounted gate files remain readable by the host test process.

Deterministic cancellation E2E coverage

TestCancel_CaughtPreBatch_NeverLands closes the runway merge-conflict-check gate for the test queue before landing a request. The test observes the exact parked delivery, cancels the request to its terminal state, opens the gate, waits for the parked record to disappear, and uses a sentinel request to prove the stale message was consumed without reviving or landing the cancelled request.

Testing

  • make test — passed (76/76 Bazel test targets).
  • //test/integration/... — passed (8/8 integration targets).
  • //test/e2e/... — passed (2/2 E2E targets).
  • git diff --check — passed.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Comment thread platform/consumer/consumer.go Outdated
Comment thread service/submitqueue/orchestrator/server/main.go Outdated
Comment thread platform/extension/consumergate/file/store.go Outdated
Comment thread platform/consumer/gate.go Outdated
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch from d04bf53 to fb4fde8 Compare July 16, 2026 00:38
Comment thread platform/extension/consumergate/consumergate.go Outdated
Comment thread platform/extension/consumergate/README.md Outdated
Comment thread platform/extension/consumergate/README.md Outdated
Comment thread platform/consumer/gate.go Outdated
Comment thread platform/consumer/gate.go Outdated
Comment thread platform/consumer/gate.go Outdated
Comment thread platform/consumer/gate.go Outdated
Comment thread platform/consumer/gate.go Outdated
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch from fb4fde8 to 0b06f08 Compare July 16, 2026 20:43
@behinddwalls
behinddwalls changed the base branch from rfc-consumer-gate to main July 16, 2026 22:00
Comment thread platform/consumer/consumer.go Outdated
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch 2 times, most recently from 4e229a3 to 2db191e Compare July 16, 2026 22:55
Comment thread platform/consumer/consumer.go Outdated
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch 2 times, most recently from 617595b to 8c7078c Compare July 21, 2026 00:10
@sbalabanov
sbalabanov marked this pull request as ready for review July 21, 2026 00:10
@sbalabanov
sbalabanov requested review from a team and behinddwalls as code owners July 21, 2026 00:10
@sbalabanov
sbalabanov marked this pull request as draft July 21, 2026 00:11
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch from 8c7078c to b17ed5a Compare July 21, 2026 00:13
@sbalabanov
sbalabanov marked this pull request as ready for review July 21, 2026 00:13
@sbalabanov
sbalabanov marked this pull request as draft July 21, 2026 00:13
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch from b17ed5a to 40d682f Compare July 21, 2026 00:27
@sbalabanov
sbalabanov marked this pull request as ready for review July 21, 2026 00:27
@sbalabanov
sbalabanov marked this pull request as draft July 21, 2026 00:27
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch from 40d682f to 357d933 Compare July 21, 2026 02:27
@sbalabanov
sbalabanov marked this pull request as ready for review July 21, 2026 16:09
@sbalabanov
sbalabanov marked this pull request as draft July 21, 2026 16:09
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch from 357d933 to dd2a358 Compare July 22, 2026 01:29
@sbalabanov sbalabanov changed the title feat(consumergate): runtime stop/start of queue controllers + deterministic e2e cancel test feat(consumergate): runtime stop/start of queue controllers Jul 22, 2026
@sbalabanov
sbalabanov marked this pull request as ready for review July 22, 2026 01:30
@sbalabanov
sbalabanov marked this pull request as draft July 22, 2026 01:30
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch 2 times, most recently from 7622a3a to ea4e622 Compare July 22, 2026 03:20
Implement the consumer-gate RFC with a file-backed gate shared by gateway,
orchestrator, and runway consumers.

- clear deliveries through a consumer-group/partition gate before controller
  processing while extending visibility for blocked deliveries
- expose caller-owned delivery descriptors and let gate implementations stamp
  gate-owned parked-record fields
- keep parked payload files only while deliveries are actively blocked and
  remove them on every terminal watch path
- fail open on gate or visibility-extension failures and leave blocked
  deliveries unacked during shutdown for normal redelivery
- run service containers with the host test UID/GID under rootful Docker while
  preserving rootless Docker ownership mapping
- make the cancellation E2E scenario deterministic by parking the runway
  merge-conflict-check delivery until cancellation reaches a terminal state
- consolidate consumer white-box and behavioral unit tests in the consumer
  package

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sbalabanov
sbalabanov force-pushed the consumer-gate-impl branch from ea4e622 to bf2e709 Compare July 22, 2026 17:01
@sbalabanov
sbalabanov marked this pull request as ready for review July 22, 2026 17:01
@albertywu

albertywu commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Could you clarify what happens in the situation where there's 10 buffered messages awaiting controller X and the controller is paused?

Codex is telling me that the first message would wait at the closed gate and have visibility continually extended but the trailing 9 would remain buffered without visibility extension. Every 60 seconds, those 9 expire and are redelivered, consuming retry attempts and creating duplicate buffered delivery. And after 3 attempts they move to DLQ.

Assuming this is true, wouldn't this cause the undesirable impact of dropping the first 9 buffered messages while controller X is in a prolonged paused state?

@albertywu

albertywu commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

It seems that with this design that uses file-based marking of gated controllers, the gating would not survive node crashes.

For example, let's say:

  1. Controller X is paused on Node A (so marker is left on Node A on the filesystem)
  2. Node A crashes and is replaced with Node B with fresh filesystem
  3. Controller X is unpaused now (pausing does not survive node crashes)

Similarly, if the fleet has 10 nodes, I don't think we fan-out the file write to all 10 nodes -- so the pause behavior would only impact the single node that the request landed on. Basically this design seems to work with a single-node only and assumes the filesystem is stateful and persistent across the lifetime of gate usage.

We could fix this by moving the gating implementation from file-based to MySQL?

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.

4 participants