| 576 | } |
| 577 | |
| 578 | void PipelineExecutor::initializeExecution(size_t num_threads, bool concurrency_control) |
| 579 | { |
| 580 | is_execution_initialized = true; |
| 581 | tryUpdateExecutionStatus(ExecutionStatus::NotStarted, ExecutionStatus::Executing); |
| 582 | |
| 583 | /// Capture the ceiling so the upscaling block can cap `setMax` at this value. |
| 584 | max_pipeline_threads = num_threads; |
| 585 | |
| 586 | /// Read the flag ONCE. A config reload mid-initialization would otherwise split a |
| 587 | /// single query between lazy and eager strategies (e.g. allocate lazy with max=1 but |
| 588 | /// then initialize desired_threads=num_threads, stranding subsequent setMax as a no-op). |
| 589 | const bool lazy_allocation = ConcurrencyControl::instance().getLazyAllocation(); |
| 590 | |
| 591 | cpu_slots = allocateCPU(num_threads, concurrency_control, lazy_allocation); |
| 592 | |
| 593 | /// If rollback flag is off, we used eager allocate(1, num_threads) and will not call |
| 594 | /// setMax in the upscaling block (nothing to grow). Initialize desired_threads |
| 595 | /// to the full ceiling so the growth check in the block becomes a no-op. |
| 596 | desired_threads = lazy_allocation ? 1 : num_threads; |
| 597 | |
| 598 | Queue queue; |
| 599 | Queue async_queue; |
| 600 | graph->initializeExecution(queue, async_queue); |
| 601 | |
| 602 | /// use_threads should reflect number of thread spawned and can grow with tasks.upscale(...). |
| 603 | /// Starting from 1 instead of 0 is to tackle the single thread scenario, where no upscale() will |
| 604 | /// be invoked but actually 1 thread used. |
| 605 | tasks.init(num_threads, 1, cpu_slots, profile_processors, trace_processors, read_progress_callback.get()); |
| 606 | const size_t initial_parallel = tasks.fill(queue, async_queue); |
| 607 | |
| 608 | /// Initial queued parallelism never routes through `pushTasks`, so size setMax here to |
| 609 | /// cover it. For multi-source pipelines (e.g. UNION ALL of N subqueries) this prevents |
| 610 | /// the pipeline from being stuck single-threaded if the master's first task happens not |
| 611 | /// to push more work. Only matters on the lazy path; the eager path already has the full |
| 612 | /// ceiling. `initial_parallel` is the total target — the master thread is one of its |
| 613 | /// consumers, no +1 needed. |
| 614 | if (lazy_allocation && initial_parallel > 1) |
| 615 | { |
| 616 | std::lock_guard lock(spawn_mutex); |
| 617 | const size_t target = std::min<size_t>(max_pipeline_threads, initial_parallel); |
| 618 | if (target > desired_threads) |
| 619 | { |
| 620 | desired_threads = target; |
| 621 | cpu_slots->setMax(target); |
| 622 | } |
| 623 | } |
| 624 | |
| 625 | if (num_threads > 1) |
| 626 | pool = std::make_unique<ThreadPool>(CurrentMetrics::QueryPipelineExecutorThreads, CurrentMetrics::QueryPipelineExecutorThreadsActive, CurrentMetrics::QueryPipelineExecutorThreadsScheduled, num_threads); |
| 627 | } |
| 628 | |
| 629 | void PipelineExecutor::spawnThreads(AcquiredSlotPtr slot) |
| 630 | { |
nothing calls this directly
no test coverage detected