Kmontemayor/graph store event wake#720
Draft
kmontemayor2-sc wants to merge 8 commits into
Draft
Conversation
Add a per-channel in-flight submission cap to the shared graph-store sampling worker's scheduler so the single scheduler thread stays wait-free at high fan-out. - worker_concurrency loop-local mirrors the sampler's bounded semaphore. - _is_channel_parked_locked: a channel with worker_concurrency batches submitted-but-not-completed is "parked". - _submit_one_batch PARKs (returns False, no re-enqueue) instead of issuing a submit that could block the scheduler on a saturated output channel; re-enqueue now goes through the membership-guarded helper to avoid a duplicate round-robin turn. - _on_batch_done WAKEs a parked channel by re-enqueueing it once an in-flight slot frees -- the sole wake path. - Phase 3 idle wait, both worker/backend signatures, and the plain round-robin pump body are unchanged from main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the GLT-order-faithful regression tests that guard the per-channel in-flight cap against the cross-rank deadlock it fixes. The shared harness (_BoundedBlockingChannel, _GltOrderFakeSampler, _CountingTaskQueue) drives the real _shared_sampling_worker_loop with a paused consumer whose channel saturates and whose coroutines wedge in send, holding the sampler semaphore. StallFixWorkerLoopTest then asserts the co-located active channel keeps completing epochs and commands keep draining, and that a parked channel idles on Phase 3's timed wait instead of busy-spinning. Also give the existing worker-loop base test a real int worker_concurrency so the parked-channel check does not compare against a MagicMock. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The per-server scheduler's Phase-3 idle wait blocked on task_queue.get(timeout); a batch completion re-enqueued a parked channel but could not interrupt that sleep, so a freed sampler slot idled up to one SCHEDULER_TICK_SECS (~50ms, mean ~25ms). Add a loop-local threading.Event: _on_batch_done set()s it (under state_lock, non-blocking, never task_queue.put) right after the WAKE re-enqueue; Phase 3 waits on the event with the existing tick as fallback, then loops back so Phase 1 drains commands via get_nowait(). The Event is level-triggered and set() happens under the same state_lock hold as the WAKE re-enqueue, so no wakeup is lost. Command handling moves entirely to Phase 1; worst-case command latency is one fallback tick, negligible at per-epoch cadence. PARK/WAKE/in-flight-cap and the sampler-call-outside-state_lock invariant are unchanged; the stall fix is not reopened. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3 now waits on the completion wake_event instead of task_queue, so a parked scheduler issues ZERO blocking task_queue.get calls. Flip test_unregister_while_parked_tears_down_cleanly's Phase-3 assertion to the event-wake form (blocking_gets == 0; still a handful of get_nowait, not a busy-spin) and reframe the neighboring comment. Add test_completion_event_wakes_parked_channel_without_tick: with SCHEDULER_TICK_SECS patched to 30s and worker_concurrency=1, a withheld-callback sampler parks the single channel; firing the completion must refill the freed slot within 5s via the event (far under the 30s fallback tick), with task_queue.blocking_get_calls == 0 throughout. Non-vacuous only with the event-wake: without wake_event.set() the second submit waits the full 30s and times out. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix a shutdown hang reachable once event-wake moved command handling into Phase 1. A parked channel's in-flight batch could complete (re-enqueue + wake_event.set()), a STOP arrive, Phase 3 wake, Phase 1 drain STOP, and Phase 2 still submit a fresh batch on the unparked channel whose channel.send blocks on a full output channel at teardown — wedging finally's wait_all(). Add an early `if not keep_running: break` immediately after the Phase-1 drain and before Phase 2 so the STOP iteration never pumps again. Teardown is handled by the finally block and the separate unregister protocol, so no final pump is needed. Only the STOP path is affected (keep_running only goes False on STOP). Strengthen test_completion_event_wakes_parked_channel_without_tick: the fake sampler now tracks submit_count, and the test asserts exactly 2 submits after shutdown (no 3rd batch after STOP). This fails without the break (3 != 2). Update the stale _CountingTaskQueue docstring to describe PR-2's wake_event Phase-3 wait instead of the old blocking task_queue.get. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collaborator
Author
|
/all_test |
Contributor
GiGL Automation@ 18:25:39UTC : 🔄 |
Contributor
GiGL Automation@ 18:25:39UTC : 🔄 @ 18:33:26UTC : ✅ Workflow completed successfully. |
Contributor
GiGL Automation@ 18:27:33UTC : 🔄 @ 18:38:54UTC : ✅ Workflow completed successfully. |
Contributor
GiGL Automation@ 18:33:47UTC : 🔄 |
Contributor
GiGL Automation@ 18:36:14UTC : 🔄 @ 18:38:05UTC : ✅ Workflow completed successfully. |
Contributor
GiGL Automation@ 18:38:07UTC : 🔄 @ 19:45:11UTC : ✅ Workflow completed successfully. |
Apply comment/docstring review feedback (humanify): reword the top in-flight-cap block to a standing property (drop the change-story "core of the stall fix"), remove the "(fix step 4)" plan reference from the finalization TODO, and cut the semaphore-block rationale that was restated in the PARK inline comment and the _pump_runnable_channel_ids docstring down to pointers at the canonical top block. No logic change. Add test_parked_channel_resumes_and_completes_when_consumer_drains (ace-it): the existing tests only park a channel; none resumes one. This pins the WAKE path directly -- park a channel by pausing its consumer, then resume and assert the epoch completes (EPOCH_DONE + every batch drained). Verified non-vacuous: with the _on_batch_done re-enqueue disabled the test fails at the epoch-done get (10s timeout), since the pump never revisits a channel it parked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EPOCH_DONE fires when the last batch is sent (buffered), not when the consumer has received it, so asserting channel.total_received == total_batches immediately after the epoch-done event races the consumer draining the tail. Poll for the count instead. submit_count == total_batches stays an immediate assertion -- it is deterministic once EPOCH_DONE fires (all batches completed => all submitted). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ph-store-event-wake # Conflicts: # tests/unit/distributed/graph_store/shared_dist_sampling_producer_test.py
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.
Scope of work done
Where is the documentation for this feature?: N/A
Did you add automated tests or write a test plan?
Updated Changelog.md? NO
Ready for code review?: NO