feat(stovepipe): migrate process and buildsignal waits to the hold primitive - #488
Draft
behinddwalls wants to merge 1 commit into
Draft
feat(stovepipe): migrate process and buildsignal waits to the hold primitive#488behinddwalls wants to merge 1 commit into
behinddwalls wants to merge 1 commit into
Conversation
…imitive ## Summary ### Why? The process stage's build-slot wait and buildsignal's status poll loop were built on ack-and-republish: each wait tick acked the delivery and PublishAfter'd a fresh copy of the same message to the controller's own topic. That forced both controllers to be publishers to their own topics, required per-stage message-id minting (a wall-clock suffix in process, a deterministic generation counter in buildsignal) to dodge the queue's (topic, partition_key, id) dedup, wrote a new log row per tick, and hung each loop's liveness on a publish succeeding. The hold primitive (doc/rfc/consumer-hold.md) is the framework-owned replacement. ### What? process: the budget-full branch now records a hold for GateWaitDelayMs and returns success; the delivery is threaded through processAccepted/admitLatestHead, rescheduleProcess and its wall-clock message-id minting are deleted, and the positive-delay config guard moves into holdForBuildSlot. buildsignal: a non-terminal status holds the delivery for the per-status poll delay; nextPollMessageID, the /poll/ generation scheme, and publishBuildSignal are deleted, and publish loses its now-unused delay parameter. The held message is a partition barrier that redelivers in order without counting toward the retry limit, so the DLQ budget stays reserved for real failures, and a failed postpone write lapses into a normal visibility-timeout redelivery instead of killing the loop. Docs updated to the decided mechanism: process.md's "Waiting for a slot" section replaces the previously-deferred Option 1/Option 2 comparison with hold (the primitive that deferral named), buildsignal.md's polling-primitive and algorithm sections describe hold, GateWaitDelayMs is re-documented as a redelivery delay, and the e2e slow-build test's obsolete dedup rationale is rewritten. ## Test Plan ✅ `bazel test //stovepipe/...` — reworked unit tests assert Hold(delay) is recorded on the gate-wait and poll paths and that no Hold happens on terminal/error paths. ✅ `bazel test //test/e2e/stovepipe/...` — the slow-build scenario reaches a terminal build through multiple hold-driven poll ticks and releases the build slot. ✅ `make fmt`.
This was referenced Aug 1, 2026
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.
Summary
Why?
The process stage's build-slot wait and buildsignal's status poll loop were built on ack-and-republish: each wait tick acked the delivery and PublishAfter'd a fresh copy of the same message to the controller's own topic. That forced both controllers to be publishers to their own topics, required per-stage message-id minting (a wall-clock suffix in process, a deterministic generation counter in buildsignal) to dodge the queue's (topic, partition_key, id) dedup, wrote a new log row per tick, and hung each loop's liveness on a publish succeeding. The hold primitive (doc/rfc/consumer-hold.md) is the framework-owned replacement.
What?
process: the budget-full branch now records a hold for GateWaitDelayMs and returns success; the delivery is threaded through processAccepted/admitLatestHead, rescheduleProcess and its wall-clock message-id minting are deleted, and the positive-delay config guard moves into holdForBuildSlot. buildsignal: a non-terminal status holds the delivery for the per-status poll delay; nextPollMessageID, the /poll/ generation scheme, and publishBuildSignal are deleted, and publish loses its now-unused delay parameter. The held message is a partition barrier that redelivers in order without counting toward the retry limit, so the DLQ budget stays reserved for real failures, and a failed postpone write lapses into a normal visibility-timeout redelivery instead of killing the loop.
Docs updated to the decided mechanism: process.md's "Waiting for a slot" section replaces the previously-deferred Option 1/Option 2 comparison with hold (the primitive that deferral named), buildsignal.md's polling-primitive and algorithm sections describe hold, GateWaitDelayMs is re-documented as a redelivery delay, and the e2e slow-build test's obsolete dedup rationale is rewritten.
Test Plan
✅
bazel test //stovepipe/...— reworked unit tests assert Hold(delay) is recorded on the gate-wait and poll paths and that no Hold happens on terminal/error paths. ✅bazel test //test/e2e/stovepipe/...— the slow-build scenario reaches a terminal build through multiple hold-driven poll ticks and releases the build slot. ✅make fmt.Stack