Visitar URL original
Add `Silk::FiberTaskTracker` and keep the scheduler thread name when a fiber joins a thread group by kolsha · Pull Request #131272 · ClickHouse/ClickHouse · GitHub
Skip to content

Add Silk::FiberTaskTracker and keep the scheduler thread name when a fiber joins a thread group - #131272

Draft
kolsha wants to merge 2 commits into
masterfrom
silk-fiber-task-tracker
Draft

kolsha wants to merge 2 commits into
masterfrom
silk-fiber-task-tracker

Conversation

@kolsha

@kolsha kolsha commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Adds Silk::FiberTaskTracker, which runs tasks in Silk fibers, at most max_inflight at once, for a waiting fiber or thread, and stops ThreadGroupSwitcher from renaming the scheduler thread when a fiber joins a thread group.

TaskTracker runs parallel work on a thread pool, so every task holds a pool thread while it waits for I/O. Running such work in fibers needs an executor with the same calling pattern that stays correct on every failure path. Each fiber task joins the caller's thread group, which exposed that ThreadGroupSwitcher treated a fiber's ThreadStatus as if it owned the OS thread it ran on.

  • Failures leave nothing behind. Slots are allocated in the constructor, as in ThreadPoolTaskTracker.cpp, so a caller can create the tracker before it starts work that has to be undone. Only started tasks are tracked and waited for; a failed Silk::spawn and a fiber that ends without running its task are reported as CANNOT_SCHEDULE_TASK, and a tracker without slots is a LOGICAL_ERROR.
  • Waiting follows TaskTracker. The interface is a subset of it: add, waitAll, safeWaitAll. When every slot is busy, add waits for any finished task through silk::FiberFuture::waitForMultiple and rethrows its exception. A task releases its callback and thread group before the waiter resumes, as in threadPoolCallbackRunner.h.
  • Joining a thread group keeps the scheduler thread's name. A fiber's ThreadStatus has no OS thread of its own, so ThreadGroupSwitcher renamed whichever scheduler thread the fiber ran on and wrote the wrong name back after the fiber migrated. It now skips the rename and its restore for such statuses, as attach and detach already do (ThreadStatusExt.cpp); ThreadStatus::boundToOSThread becomes public for it.

No caller in this repository uses the tracker yet. Tests: gtests for thread group accounting in a real fiber, the first error, the inflight limit, both start failures through the new failpoints silk_fiber_task_tracker_spawn_failure and silk_fiber_main_failure, and the scheduler thread name in a fiber, both when the switcher attaches and when it borrows a status that owns a group.

Changelog category (leave one):

  • Not for changelog (changelog entry is not required)

Workflow [PR]
Sync PR [sync-upstream/pr/131272]

…roup

A fiber has a `ThreadStatus` without an OS thread of its own, so `setThreadName` in `ThreadGroupSwitcher` renamed the Silk scheduler thread it happened to run on, and the restore wrote the wrong name back after the fiber migrated. Attach and detach already skip OS-thread work for such statuses (`ThreadStatusExt.cpp`); the switcher now skips the rename and its restore the same way, and `ThreadStatus::boundToOSThread` becomes public for it.

Test: inside a fiber the scheduler thread keeps its name while a `ThreadGroupSwitcher` attaches the fiber to a group, also when it borrows a status that owns a group.
`Silk::FiberTaskTracker` runs tasks in Silk fibers, at most `max_inflight` at once, for a waiting fiber or thread, with a subset of the interface of `TaskTracker` (`add`, `waitAll`, `safeWaitAll`).

Its slots are allocated up front, as in `ThreadPoolTaskTracker.cpp`, so that a caller can create it before it starts work that has to be undone on failure. It waits for any finished task through `silk::FiberFuture::waitForMultiple`, tracks only started tasks, and releases a task's callback and thread group before the waiter resumes, as in `threadPoolCallbackRunner.h`. A failed `Silk::spawn` and a fiber that ends without running its task are reported as `CANNOT_SCHEDULE_TASK`, and a tracker without slots is a `LOGICAL_ERROR`.

Tests: gtests for thread group accounting in a real fiber, the first error, the inflight limit, and both start failures through the new failpoints `silk_fiber_task_tracker_spawn_failure` and `silk_fiber_main_failure`.
@clickhouse-gh

clickhouse-gh Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [e62bbe7]

Summary: ⏳

job_name test_name status info comment
Performance Comparison (arm_release, master_head, 1/6) ERROR
Performance Comparison (arm_release, master_head, 2/6) ERROR
Performance Comparison (arm_release, master_head, 3/6) ERROR
Performance Comparison (arm_release, master_head, 4/6) ERROR
Performance Comparison (arm_release, master_head, 5/6) ERROR
Performance Comparison (arm_release, master_head, 6/6) ERROR

AI Review

Summary

Adds Silk::FiberTaskTracker with bounded concurrency, exception propagation, and task cleanup, and prevents ThreadGroupSwitcher from renaming a fiber's scheduler thread. ❌ Block: the real startup-failure path can still dereference a destroyed ThreadStatus; query-group attachment also leaves jemalloc profiling state shared between fibers.

Missing context / blind spots
  • ⚠️ No configured build or test binary is available locally, so this review used source tracing, including the pinned Silk implementation. Run SilkFiberTaskTrackerTest.* and ThreadGroupSwitcher.* with USE_SILK=1 to complete runtime validation.
Findings

❌ Blockers

  • src/Common/Silk/FiberScheduler.cpp:66–70: A partially failed ThreadStatus constructor leaves current_thread dangling. ThreadStatus.cpp:199 publishes the status before the first-use alt_stack construction at line 215, which can throw in non-sanitized builds. Member destruction runs without ~ThreadStatus, so the catch's logging and onFiberSuspend can access the destroyed status. This violates the promised safe handling of tasks that fail before execution. Add constructor rollback before member destruction and test a failure after publication; the new failpoint runs too early to cover this path.

⚠️ Majors

  • src/Common/Silk/FiberTaskTracker.cpp:62: Query-specific jemalloc profiling changes the shared scheduler thread. Group attachment reaches the unguarded jemalloc setters in ThreadStatus::applyQuerySettings; the settings are absent from FiberLocalStorage. A profiled task that suspends enables profiling for unrelated tasks, and migration causes detach to reset a different worker while leaving the original modified. Save/restore the effective profiling state across fiber switches, or explicitly reject these options for fiber tasks.
Tests
  • ⚠️ Inject a construction failure after current_thread is published and verify that the waiter receives the scheduling exception, the callback is released, and the scheduler never accesses a destroyed status.
  • ⚠️ Interleave profiled and unprofiled tasks across suspension and verify that jemalloc profiling state follows the intended task and leaves scheduler workers unchanged afterward.
Final Verdict

❌ Block. Make partial ThreadStatus construction exception-safe and isolate or reject fiber task jemalloc profiling settings, with focused regression coverage. The constructor defect is in an unchanged callee reached by the startup-failure contract; alternatively, explicitly track that pre-existing defect in a linked issue as required by the review instructions.

@clickhouse-gh clickhouse-gh Bot added the pr-not-for-changelog This PR should not be mentioned in the changelog label Nov 13, 2026
Comment on lines +66 to +70
/// Fails as a failed `ThreadStatus` construction would, before the task runs.
fiu_do_on(DB::FailPoints::silk_fiber_main_failure,
{
throw DB::Exception(DB::ErrorCodes::FAULT_INJECTED, "Injected failure before running a fiber task");
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Blocker] Real ThreadStatus construction failures can leave current_thread dangling

This failpoint throws before construction, so it misses a failure after ThreadStatus.cpp:199 publishes current_thread = this. In a non-sanitized build, the subsequent first-use construction of alt_stack at line 215 can throw from aligned_alloc or guard-page installation. The partially constructed status then loses its members without running ~ThreadStatus, leaving current_thread pointing at the destroyed stack object. The catch here logs through that status, and onFiberSuspend subsequently dereferences it in publishUntrackedMemory, causing undefined behavior instead of safely reporting CANNOT_SCHEDULE_TASK.

Please roll back current_thread when construction fails (before member destruction), and exercise a failure after publication. The current pre-construction injection cannot establish the advertised startup-failure guarantee.

try
{
/// Fibers are not renamed, so the thread name is not used.
DB::ThreadGroupSwitcher switcher(std::move(slot.thread_group), DB::ThreadName::UNKNOWN);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Major] Attaching the task still leaks jemalloc profiling state between fibers

ThreadGroupSwitcher reaches ThreadStatus::applyQuerySettings, whose jemalloc section (ThreadStatusExt.cpp:404–412) still changes thread.prof.active and collect_local_profiles_in_trace_log on the scheduler's OS thread. Neither is saved by FiberLocalStorage. With jemalloc_enable_profiler or jemalloc_collect_profile_samples_in_trace_log enabled, a task that waits for I/O leaves profiling enabled for unrelated fibers running on that worker. If it resumes elsewhere, detachFromGroup resets the new worker instead, leaving the original worker modified; another task's detach can also disable profiling underneath the suspended task. Skipping the thread rename does not isolate these settings.

Please make these profiling settings effective per fiber across suspend/resume, or explicitly reject them for fiber tasks. Add focused coverage with interleaved profiled and unprofiled tasks.

@clickhouse-gh clickhouse-gh Bot added the comp-query-execution Runtime execution pipeline, processors, vectorized operators, resource management during execution. label Nov 13, 2026
@clickhouse-gh

clickhouse-gh Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Build profile diff (arm_release)

Comparing e62bbe7f5 with master c65bdd819 (stripped binary size, per-symbol sizes and ThinLTO time; compile times per translation unit against the most recent warmup build that recompiled it).

✅ No significant changes.

Binary sizes

programs/clickhouse-stripped: smaller than the master baseline by the known offset between the two builds, so the difference is not shown. A delta that differs from the offset by more than 50% of it is shown, in either direction.

The official master build is compiled with -g and a pull request build is not, and XRay counts debug instructions towards its instrumentation threshold, so master instruments thousands of functions more and its binary is ~0.4% larger no matter what the pull request does.

Object file sizes

7 object files changed (+74.37 KiB total), 1 added.

Object file Master PR Δ
src/CMakeFiles/clickhouse_common_io.dir/Common/Silk/FiberTaskTracker.cpp.o new 59.95 KiB +59.95 KiB

711 more object files are built by the master warmup baseline only (it builds every object-file target, a pull request build only clickhouse-bundle) and not compared.

Slowest function optimization changes (ThinLTO)

Median per-function time ratio to the master baseline: programs/clickhouse ×0.62, programs/clickhouse-keeper ×0.64 (each binary is linked separately, on a different machine and with different build flags); deltas below are relative to it.

Binary Function Master PR Δ vs median
programs/clickhouse DB::registerFunctionConversion(DB::FunctionFactory&) 5.7 s 13.6 s +10.0 s (+281%)
programs/clickhouse _ZN6detail10static_forITk7is_enumN2DB23AttributeUnderlyingTypeEZNS1_29callOnDictionaryAttributeType… 3.4 s 7.4 s +5.3 s (+249%)
Compile time of recompiled translation units

132 translation units recompiled, 932 s compile time in total, 131 of them have a recent master baseline.

Job report

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp-query-execution Runtime execution pipeline, processors, vectorized operators, resource management during execution. pr-not-for-changelog This PR should not be mentioned in the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant