Repository navigation
Fix livelock when all TaskGroup runqueues are full (#1038) - #3633
two-headBoy wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
4 open issues
_rq.push(...)is called without checking whether it succeeded. In this function we’re explicitly… · New_rq.push(...)is called without checking whether it succeeded. In this function we’re explicitly… · New This enqueuescurrent_tid()onto_rqbefore the current task has actually been scheduled out… · New This enqueuescurrent_tid()onto_rqbefore the current task has actually been scheduled out… · New
What changed in this PR
Addresses a scheduler livelock when all TaskGroup local runqueues are full by making progress (running a queued task inline) instead of sleeping/retrying forever under full-queue conditions.
Changes:
- Replace the “sleep and retry” behavior on
_rqfull with “pop one task, run it in place, then retry push”. - Add special handling for pthread-mode tasks to avoid illegal switches and prevent accidentally dropping popped pthread tasks.
| File | Description |
|---|---|
| src/bthread/task_group_inl.h | Changes TaskGroup::push_rq() to drain work when _rq is full, preventing livelock and handling pthread-mode edge cases. |
Review effort: Lite
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
2 open issues
4 resolved since last review
This enqueuescurrent_tid()onto_rqbefore the current task has actually been scheduled out… This enqueuescurrent_tid()onto_rqbefore the current task has actually been scheduled out…_rq.push(...)is called without checking whether it succeeded. In this function we’re explicitly…_rq.push(...)is called without checking whether it succeeded. In this function we’re explicitly…
Review effort: Lite
| if (is_current_main_task() && | ||
| address_meta(run_tid)->attr.stack_type == BTHREAD_STACKTYPE_PTHREAD) { | ||
| // pthread-mode tasks are run inline on _main_stack by the | ||
| // dispatch loops (run_main_task/task_runner). Switching to one | ||
| // from the main task itself causes no stack jump and would | ||
| // silently drop it, put it back and wait; stealers may run it | ||
| // on other workers. | ||
| _rq.push(run_tid); | ||
| ::usleep(1000); | ||
| continue; | ||
| } |
| // from the main task itself causes no stack jump and would | ||
| // silently drop it, put it back and wait; stealers may run it | ||
| // on other workers. | ||
| _rq.push(run_tid); |


What problem does this PR solve?
Issue Number: Fixes #1038
Problem Summary:
When
_rqis full,push_rq()sleeps the worker thread and retries. Theowner worker is the only consumer of
_rq(other workers only steal), soonce every worker spins there, no one drains any queue and the whole
scheduler livelocks.
What is changed and the side effects?
Changed:
When
_rqis full, pop one pending task and run it in place, then retrythe push. The pop frees one slot, which guarantees the push of the task
being scheduled out succeeds, since
_rqhas a single producer andstealers never push; the original push is then retried until the queue
has room. The task being scheduled out is saved with
current_tid()(remained-callbacks re-enter
push_rq()on the next task's stack).Pthread-mode tasks cannot be switched out, so they fall back to waiting,
and a pthread-mode task popped by the main task is put back instead of
being silently dropped.
Verified with a recursive spawner repro (every task spawns more tasks until stopped):
before the fix all workers froze in the usleep loop with CPU ~0%;
after the fix workers kept draining the queues (CPU ~400%) and the echo_c++ smoke test passes.
Side effects:
Note: this fix guarantees scheduler liveness, not per-task completion
deadlines; rate limiting under sustained overload remains the
application's responsibility. The window where every worker is inside a
pthread-mode task when its queue fills is left as-is, consistent with
the existing stance for pthread-mode tasks in
exchange().Check List:
https://github.com/apache/brpc/blob/master/CODE_OF_CONDUCT.md.