Skip to content

[feature] Add Active Task#3232

Open
MalikHou wants to merge 7 commits intoapache:masterfrom
MalikHou:malikhou/add_active_task
Open

[feature] Add Active Task#3232
MalikHou wants to merge 7 commits intoapache:masterfrom
MalikHou:malikhou/add_active_task

Conversation

@MalikHou
Copy link

What problem does this PR solve?

Issue Number: resolve #3212

Problem Summary:

  • For per-worker async file I/O (e.g. io_uring), the existing wakeup path may go through cross-thread routing (_remote_rq) and incur extra bthread switch / scheduling cost.
  • We need a worker-local mechanism so a bthread can wait on a request and be resumed by the same worker’s event/harvest loop, without being stolen or migrated during the wait lifecycle.
  • We also need the active-task runtime to drive completion harvesting in bthread worker loop (idle + busy cases), not only in external threads.

What is changed and the side effects?

Changed:

  • Introduce experimental Active-Task infrastructure in bthread/unstable.h (startup registration, per-worker init/destroy, single harvest hook).
  • Add worker-loop integration for active-task harvesting:
    • idle path with configurable wait interval
    • busy path periodic polling (default poll_every_nswitch=1)
  • Add bthread_butex_wake_within(ctx, butex):
    • hook-only API
    • local wake on current worker
    • strict same-worker invariant check (home_group)
  • Add bthread_butex_wait_local(...):
    • implicit wait-scope local pin (no manual pin API for users)
    • guarantees local resume for this wait lifecycle
  • Add internal pin-aware scheduling:
    • non-stealable _pinned_rq
    • owner-only _pinned_remote_rq
    • pin-aware routing for ready_to_run*, timeout, interruption, and generic butex_wake*
  • Fix butex local fast-path/requeue paths to avoid bypassing pin-aware routing (exchange bypass bug).
  • Add/expand tests:
    • local wait/wake, strict same-worker checks
    • no-steal guarantee before resume
    • timeout / interruption / generic wake routed back to home worker
    • wake_within == 0 competition cases (timeout/interruption/generic wake)
    • repeat/shuffle stability tests
  • Add/refresh docs (docs/cn/bthread_active_task.md) with Plan-A usage (butex_wait_local + harvest + wake_within).

Side effects:

  • Performance effects:

    • When active-task is not used, impact is minimal (extra branches / empty checks only).
    • When active-task is used, busy polling is more aggressive by default (poll_every_nswitch=1), which improves completion->resume latency but may increase CPU usage.
    • Pinned tasks in bthread_butex_wait_local lose steal-based load balancing during the wait lifecycle by design (correctness/locality tradeoff).
  • Breaking backward compatibility:

    • No stable API break.
    • New/changed APIs are under bthread/unstable.h (experimental/UNSTABLE).
    • This PR intentionally avoids exposing manual pin enter/leave APIs to reduce misuse; local pin is managed internally by bthread_butex_wait_local(...).

Check List:

@chenBright
Copy link
Contributor

Please merge or rebase master branch which fixed CI failure.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces an experimental Active Task mechanism for bthread workers to run non-blocking maintenance hooks (e.g. harvesting io_uring completions) and adds a local pin + within-worker wake path so waiters can reliably resume on the same worker without cross-thread routing.

Changes:

  • Add Active Task registration/types in bthread/unstable.h and snapshot/init/destroy integration in TaskControl/TaskGroup.
  • Implement pin-aware scheduling and butex support (bthread_butex_wait_local, bthread_butex_wake_within, pinned runqueues, and strict rejection for generic butex wake APIs).
  • Add a comprehensive unit test suite and Chinese documentation for Active Task usage; link docs from READMEs.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/bthread_active_task_unittest.cpp Adds end-to-end tests for local wait/wake, strict invariants, pinned/no-steal behavior, and competition cases.
src/bthread/unstable.h Defines the experimental Active Task API surface and callback/context structs.
src/bthread/task_meta.h Extends task metadata with local pin state (home group/control/tag, depth, enabled flag).
src/bthread/task_group_inl.h Adds pinned runqueue push and flushes pinned remote nosignal tasks.
src/bthread/task_group.h Declares active-task worker lifecycle/harvest plumbing and pin-aware routing helpers/queues.
src/bthread/task_group.cpp Implements active-task init/destroy/harvest, worker-loop polling/idle integration, and pin-aware ready-to-run routing.
src/bthread/task_control.h Stores an active-task type snapshot on init; exposes internal snapshot helper.
src/bthread/task_control.cpp Captures active-task registry snapshot during init; initializes/destroys per-worker active tasks; extends signal counting.
src/bthread/parking_lot.h Adds timed wait support (relative timeout) used by active-task idle waiting.
src/bthread/butex.h Documents strict pinned-waiter behavior and exposes butex_wake_to_task_group helper.
src/bthread/butex.cpp Adds within-worker wake helper, strict rejection for pinned waiters, and pin-aware wake routing adjustments.
src/bthread/bthread.cpp Adds active-task type registry + registration API and implements bthread_butex_wait_local/wake_within.
docs/cn/bthread_active_task.md New documentation describing the Active Task model, constraints, and recommended usage pattern.
README_cn.md Links the new Active Task documentation from the Chinese README.
README.md Links the new Active Task documentation from the main README.
.gitignore Adds .cache to ignored paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 399 to 413
// wake_within() runs on target_group's owner worker. For pinned waiters,
// if owner-local pinned runqueue is already full, report EAGAIN and
// keep waiter on butex list for next harvest retry, instead of
// blocking/spinning here.
if (is_pinned_waiter(bw) && target_group->pinned_rq_full()) {
errno = EAGAIN;
return -1;
}
bw->RemoveFromList();
bw->container.store(NULL, butil::memory_order_relaxed);
}

unsleep_if_necessary(bbw, get_global_timer_thread());
target_group->ready_to_run(bbw->task_meta, true);
return 1;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

butex_wake_to_task_group tries to avoid blocking in active-task hooks by checking pinned_rq_full() and returning EAGAIN, but the check isn't atomic with the subsequent enqueue (target_group->ready_to_run(...)). If the pinned runqueue becomes full between the check and ready_to_run, push_pinned_rq may spin/sleep inside the hook, violating the non-blocking intent. Consider adding a non-blocking/try-enqueue path for within-wake (or re-check/return EAGAIN on enqueue failure) so hooks never block.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

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

in this code path, pinned_rq_full() and the following enqueue are executed by the same owner worker, so the TOCTOU case you mentioned does not occur here.

butex_wake_within_active_task() is only allowed from the active-task hook of the current TaskGroup (validated in validate_active_task_hook_ctx). For pinned waiters, ready_to_run() routes to ready_to_run_pinned_local() on the same home worker. Also, _pinned_rq is owner-local (not used by steal path, and remote routing for pinned tasks goes to _pinned_remote_rq), so there is no concurrent producer that can make _pinned_rq become full between the check and enqueue.

so this check is intended as a fast fail (EAGAIN) when already full, not as a cross-thread non-blocking CAS gate.

Comment on lines +305 to +311
bthread::TaskControl& GetSharedSingleWorkerTaskControl() {
return GetSharedTaskControl();
}

bthread::TaskControl& GetSharedTwoWorkerTaskControl() {
return GetSharedTaskControl();
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

These helpers are named as if they return different TaskControl configurations, but both currently return the same shared TaskControl initialized with concurrency=2. This is misleading for readers and can cause incorrect assumptions in future edits; please either rename them to reflect the shared 2-worker instance, or actually initialize distinct single-worker vs two-worker TaskControls (if feasible).

Copilot uses AI. Check for mistakes.
@MalikHou
Copy link
Author

MalikHou commented Mar 2, 2026

截屏2026-03-02 14 09 45

@MalikHou
Copy link
Author

MalikHou commented Mar 2, 2026

截屏2026-03-02 14 12 25

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.

[Feature Request] Run-to-Completion (RTC) Worker Loop Support

3 participants