Skip to content

Fix: wake L3 scheduler on root ready tasks#1258

Merged
ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:fix/l3-scheduler-ready-wakeup
Jul 2, 2026
Merged

Fix: wake L3 scheduler on root ready tasks#1258
ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:fix/l3-scheduler-ready-wakeup

Conversation

@ChaoWao

@ChaoWao ChaoWao commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Wake the L3 Scheduler when Orchestrator publishes a root ready task.
  • Include ready queues in the Scheduler wait predicate.
  • Replace the previous 1 ms wait_for polling fallback with event-driven wait.

Why

Root L3 tasks were pushed into ReadyQueue, but the Scheduler waited on its
own completion_cv_. Since the wait predicate only checked completions and
stop, root ready tasks could sit until the 1 ms timeout fired before dispatching
to the L2 child.

Measured on pypto-serving Qwen3-14B decode, the L3-to-L2 median gap dropped
from 1.627 ms to 0.602 ms. The pre-L2 startup portion dropped from 1.340 ms to
0.471 ms.

Testing

  • pip install --no-build-isolation -e .
  • python -m pytest tests/ut/py/test_worker/test_host_worker.py tests/ut/py/test_worker/test_group_task.py tests/ut/py/test_worker/test_error_propagation.py -q
  • python -m pytest tests/st/a2a3/tensormap_and_ringbuffer/test_l3_dependency.py tests/st/a2a3/tensormap_and_ringbuffer/test_l3_group.py --platform a2a3sim --device 0-1 -q
  • pypto-serving Qwen3-14B decode smoke/perf run through task-submit, 16 tokens

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces the timed wait in the scheduler loop with an event-driven notification mechanism using a callback from the orchestrator. A critical race condition was identified in Scheduler::notify_ready(), where a lost wakeup could occur because the notification is triggered without holding the completion_mu_ mutex. To prevent this, the mutex should be acquired before calling completion_cv_.notify_one().

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/common/hierarchical/scheduler.cpp Outdated
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3a40559d-8437-4d90-a7e3-f5761650a379

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The Orchestrator now accepts a ready-notification callback invoked when a submitted task becomes immediately ready. The Scheduler exposes a notify_ready() method and replaces its timed polling wait with a predicate-based wait. ReadyQueue gains a thread-safe empty() accessor, and Worker wires these together.

Changes

Ready-Notification Wiring

Layer / File(s) Summary
Orchestrator callback parameter and invocation
src/common/hierarchical/orchestrator.h, src/common/hierarchical/orchestrator.cpp
Orchestrator::init accepts a new std::function<void()> ready_notify_cb (default empty) stored as ready_notify_cb_; submit_impl invokes it when a task has zero live fan-ins and becomes READY.
Scheduler notify_ready and predicate wait
src/common/hierarchical/scheduler.h, src/common/hierarchical/scheduler.cpp
Adds public Scheduler::notify_ready() that signals completion_cv_; Scheduler::run() replaces timed wait_for polling with a predicate-based wait triggered by completions, non-empty ready queues, or shutdown.
ReadyQueue empty() accessor
src/common/hierarchical/types.h, src/common/hierarchical/types.cpp
Adds thread-safe ReadyQueue::empty() const, requiring the internal mutex to become mutable.
Worker wiring
src/common/hierarchical/worker.cpp
Worker::init() passes a lambda invoking scheduler_.notify_ready() into orchestrator_.init(...).

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Orchestrator
  participant ReadyQueue
  participant Scheduler
  participant Worker

  Worker->>Orchestrator: init(..., ready_notify_cb = notify_ready lambda)
  Orchestrator->>ReadyQueue: push slot (task READY, live_fanins == 0)
  Orchestrator->>Scheduler: ready_notify_cb_() -> notify_ready()
  Scheduler->>Scheduler: completion_cv_.notify_one()
  Scheduler->>Scheduler: wait wakes on predicate (queue non-empty)
  Scheduler->>ReadyQueue: check empty()
  ReadyQueue-->>Scheduler: false
  Scheduler->>Scheduler: process ready task
Loading

Poem

A callback hops where polling used to plod,
No more waiting on a timer's nod,
Ready queues whisper "not empty, come see!"
Scheduler wakes with a hop of glee,
Mutable mutex, empty() in tow —
This bunny's build runs swift and low! 🐇⚡

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: waking the L3 scheduler for root ready tasks.
Description check ✅ Passed The description is directly related to the changeset and matches the scheduler wakeup behavior changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ChaoWao
ChaoWao force-pushed the fix/l3-scheduler-ready-wakeup branch from 42d92b3 to 94429d1 Compare July 2, 2026 08:38
Root L3 tasks were pushed into ReadyQueue without waking the
Scheduler's own condition variable. The scheduler only noticed them
after its 1 ms wait timeout, adding avoidable latency before L2
dispatch.

- Notify the Scheduler when Orchestrator publishes a root ready task
- Include ready queues in the Scheduler wait predicate
- Switch the loop to event-driven wait instead of wait_for polling
@ChaoWao
ChaoWao merged commit 2593759 into hw-native-sys:main Jul 2, 2026
4 of 9 checks passed
@ChaoWao
ChaoWao deleted the fix/l3-scheduler-ready-wakeup branch July 2, 2026 09:37
ChaoWao added a commit to ChaoWao/simpler-fork that referenced this pull request Jul 3, 2026
…-sys#1260)

hw-native-sys#1258 moved the Scheduler off the ReadyQueue's condition variable onto its
own completion_cv_, and added a `ready_notify_cb` parameter to
`Orchestrator::init` so `submit()` can wake the scheduler when a root task
goes READY. Production wires it (worker.cpp: `[this]{ scheduler_.notify_ready(); }`),
but the four `test_scheduler` fixtures were not updated and relied on the
parameter's `{}` default — so submissions pushed to the ready queue but
never notified the scheduler, which blocked forever on completion_cv_.
test_scheduler then hung into the PR `ut (ubuntu)`/`ut (macos)` 15-min cap
(issue hw-native-sys#1260), affecting every PR that runs that job.

Pass `[this/&sched]{ sched.notify_ready(); }` at all four Orchestrator::init
call sites in test_scheduler.cpp (3 TEST_F SetUp methods capture `this`;
the one plain TEST captures the local `sched` by reference).

Verified: test_scheduler 17/17 pass in 91 ms (was: hang); full hierarchical
cpput suite 52/52.
ChaoWao added a commit that referenced this pull request Jul 3, 2026
* Refactor: remove orphaned WorkerManager idle-picker methods, rename survivor

#1009 (remote endpoint eligibility scheduling) migrated the scheduler's
worker selection from pick_idle_excluding to pick_idle_excluding_eligible
but left the old entry points behind. Git history confirms these are
migration residue, not in-progress scaffolding:

- pick_idle_excluding — the scheduler's only call site was rewritten to
  pick_idle_excluding_eligible in #1009; the old method was kept as a
  thin forwarding shim (unconstrained eligible list) that nothing calls.
- pick_idle / pick_n_idle — base variants with no call site anywhere in
  the tree's history (only ever declared/defined since #587).
- shutdown_children — WorkerManager method with zero call sites (distinct
  from the live WorkerThread::shutdown_child, which it wrapped).

All four are zero-call, host-only, non-virtual, and not part of any wire
protocol or serialized struct.

With its distinguishing siblings gone, the survivor's
`_excluding_eligible` suffix is vestigial naming baggage — rename
pick_idle_excluding_eligible -> pick_idle (reusing the freed name; the
old pick_idle(type) had zero callers so there is no collision). Update
the scheduler.h data-flow comment and the worker-manager.md / scheduler.md
docs to match.

Deliberately kept: pick_idle (the renamed live selector) and
WorkerThread::shutdown_child (8 live call sites).

Verified: both runtimes build+link clean (-Wall -Wextra, no warnings);
WorkerManager python test passes. (The pre-existing gtest ABI link error
in the local cpput tree reproduces on clean upstream/main and is
unrelated; CI builds cpput in a clean container.)

* Fix: wire ready_notify_cb in test_scheduler fixtures (fixes #1260)

#1258 moved the Scheduler off the ReadyQueue's condition variable onto its
own completion_cv_, and added a `ready_notify_cb` parameter to
`Orchestrator::init` so `submit()` can wake the scheduler when a root task
goes READY. Production wires it (worker.cpp: `[this]{ scheduler_.notify_ready(); }`),
but the four `test_scheduler` fixtures were not updated and relied on the
parameter's `{}` default — so submissions pushed to the ready queue but
never notified the scheduler, which blocked forever on completion_cv_.
test_scheduler then hung into the PR `ut (ubuntu)`/`ut (macos)` 15-min cap
(issue #1260), affecting every PR that runs that job.

Pass `[this/&sched]{ sched.notify_ready(); }` at all four Orchestrator::init
call sites in test_scheduler.cpp (3 TEST_F SetUp methods capture `this`;
the one plain TEST captures the local `sched` by reference).

Verified: test_scheduler 17/17 pass in 91 ms (was: hang); full hierarchical
cpput suite 52/52.
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.

1 participant