Coverage Report

Created: 2026-08-31 16:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/scan/scanner_scheduler.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <atomic>
21
#include <memory>
22
23
#include "common/be_mock_util.h"
24
#include "common/status.h"
25
#include "exec/scan/scanner_context.h"
26
#include "exec/scan/task_executor/listenable_future.h"
27
#include "exec/scan/task_executor/ticker.h"
28
#include "exec/scan/task_executor/time_sharing/time_sharing_task_executor.h"
29
#include "util/threadpool.h"
30
31
namespace doris {
32
class ExecEnv;
33
34
class Scanner;
35
class Block;
36
37
template <typename T>
38
class BlockingQueue;
39
} // namespace doris
40
41
namespace doris {
42
class ScannerDelegate;
43
class ScanTask;
44
class ScannerContext;
45
class ScannerScheduler;
46
47
struct SimplifiedScanTask {
48
    SimplifiedScanTask() = default;
49
    SimplifiedScanTask(std::function<bool()> scan_func,
50
                       std::shared_ptr<ScannerContext> scanner_context,
51
13
                       std::shared_ptr<ScanTask> scan_task) {
52
13
        this->scan_func = scan_func;
53
13
        this->scanner_context = scanner_context;
54
13
        this->scan_task = scan_task;
55
13
    }
56
57
    std::function<bool()> scan_func;
58
    std::shared_ptr<ScannerContext> scanner_context = nullptr;
59
    std::shared_ptr<ScanTask> scan_task = nullptr;
60
};
61
62
class ScannerSplitRunner : public SplitRunner {
63
public:
64
    ScannerSplitRunner(std::string name, std::function<bool()> scan_func)
65
0
            : _name(std::move(name)), _scan_func(std::move(scan_func)), _started(false) {}
66
67
0
    Status init() override { return Status::OK(); }
68
69
    Result<SharedListenableFuture<Void>> process_for(std::chrono::nanoseconds) override;
70
71
0
    void close(const Status& status) override {}
72
73
0
    std::string get_info() const override { return ""; }
74
75
    bool is_finished() override;
76
77
    Status finished_status() override;
78
79
    bool is_started() const;
80
81
    bool is_auto_reschedule() const override;
82
83
private:
84
    std::string _name;
85
    std::function<bool()> _scan_func;
86
87
    std::atomic<bool> _started;
88
    SharedListenableFuture<Void> _completion_future;
89
};
90
91
// Abstract interface for scan scheduler
92
93
// Responsible for the scheduling and execution of all Scanners of a BE node.
94
// Execution thread pool
95
//     When a ScannerContext is launched, it will submit the running scanners to this scheduler.
96
//     The scheduling thread will submit the running scanner and its ScannerContext
97
//     to the execution thread pool to do the actual scan task.
98
//     Each Scanner will act as a producer, read the next block and put it into
99
//     the corresponding block queue.
100
//     The corresponding ScanNode will act as a consumer to consume blocks from the block queue.
101
//     After the block is consumed, the unfinished scanner will resubmit to this scheduler.
102
103
class ScannerScheduler {
104
public:
105
66
    virtual ~ScannerScheduler() {}
106
107
    Status submit(std::shared_ptr<ScannerContext> ctx, std::shared_ptr<ScanTask> scan_task);
108
109
    static int default_local_scan_thread_num();
110
111
    static int default_remote_scan_thread_num();
112
113
    static int get_remote_scan_thread_queue_size();
114
115
    static int default_min_active_scan_threads();
116
117
    static int default_min_active_file_scan_threads();
118
119
    virtual Status start(int max_thread_num, int min_thread_num, int queue_size,
120
                         int min_active_scan_threads) = 0;
121
    virtual void stop() = 0;
122
    virtual Status submit_scan_task(SimplifiedScanTask scan_task) = 0;
123
    virtual Status submit_scan_task(SimplifiedScanTask scan_task,
124
                                    const std::string& task_id_string) = 0;
125
126
    virtual void reset_thread_num(int new_max_thread_num, int new_min_thread_num,
127
                                  int min_active_scan_threads) = 0;
128
0
    int get_min_active_scan_threads() const { return _min_active_scan_threads; }
129
130
    virtual int get_queue_size() = 0;
131
    virtual int get_active_threads() = 0;
132
    virtual std::vector<int> thread_debug_info() = 0;
133
134
    virtual Status schedule_scan_task(std::shared_ptr<ScannerContext> scanner_ctx,
135
                                      std::shared_ptr<ScanTask> current_scan_task,
136
                                      std::unique_lock<std::mutex>& transfer_lock) = 0;
137
138
protected:
139
    int _min_active_scan_threads;
140
141
    // Execute one admitted task for both scheduler implementations. The return value is consumed
142
    // by TaskExecutor to distinguish terminal EOS/error tasks from scanners that remain runnable.
143
    static bool execute_scan_task(const std::shared_ptr<ScannerContext>& ctx,
144
                                  const std::shared_ptr<ScanTask>& scan_task);
145
146
    static void _scanner_scan(std::shared_ptr<ScannerContext> ctx,
147
                              std::shared_ptr<ScanTask> scan_task);
148
149
    static void _make_sure_virtual_col_is_materialized(const std::shared_ptr<Scanner>& scanner,
150
                                                       Block* block);
151
};
152
153
class ThreadPoolSimplifiedScanScheduler MOCK_REMOVE(final) : public ScannerScheduler {
154
public:
155
    ThreadPoolSimplifiedScanScheduler(std::string sched_name,
156
                                      std::shared_ptr<CgroupCpuCtl> cgroup_cpu_ctl,
157
                                      std::string workload_group = "system")
158
63
            : _is_stop(false),
159
63
              _cgroup_cpu_ctl(cgroup_cpu_ctl),
160
63
              _sched_name(sched_name),
161
63
              _workload_group(workload_group) {}
162
163
63
    ~ThreadPoolSimplifiedScanScheduler() override {
164
#ifndef BE_TEST
165
        stop();
166
#endif
167
63
        LOG(INFO) << "Scanner sche " << _sched_name << " shutdown";
168
63
    }
169
170
7
    void stop() override {
171
7
        if (_is_stop.exchange(true)) {
172
0
            return;
173
0
        }
174
7
        _scan_thread_pool->shutdown();
175
7
        _scan_thread_pool->wait();
176
7
    }
177
178
    Status start(int max_thread_num, int min_thread_num, int queue_size,
179
7
                 int min_active_scan_threads) override {
180
7
        _min_active_scan_threads = min_active_scan_threads;
181
7
        RETURN_IF_ERROR(ThreadPoolBuilder(_sched_name, _workload_group)
182
7
                                .set_min_threads(min_thread_num)
183
7
                                .set_max_threads(max_thread_num)
184
7
                                .set_max_queue_size(queue_size)
185
7
                                .set_cgroup_cpu_ctl(_cgroup_cpu_ctl)
186
7
                                .build(&_scan_thread_pool));
187
7
        return Status::OK();
188
7
    }
189
190
4
    Status submit_scan_task(SimplifiedScanTask scan_task) override {
191
4
        if (!_is_stop) {
192
4
            return _scan_thread_pool->submit_func([scan_task] { scan_task.scan_func(); });
193
4
        } else {
194
0
            return Status::InternalError<false>("scanner pool {} is shutdown.", _sched_name);
195
0
        }
196
4
    }
197
198
    Status submit_scan_task(SimplifiedScanTask scan_task,
199
0
                            const std::string& task_id_string) override {
200
0
        return submit_scan_task(scan_task);
201
0
    }
202
203
    void reset_thread_num(int new_max_thread_num, int new_min_thread_num,
204
0
                          int min_active_scan_threads) override {
205
0
        _min_active_scan_threads = min_active_scan_threads;
206
0
        int cur_max_thread_num = _scan_thread_pool->max_threads();
207
0
        int cur_min_thread_num = _scan_thread_pool->min_threads();
208
0
        if (cur_max_thread_num == new_max_thread_num && cur_min_thread_num == new_min_thread_num) {
209
0
            return;
210
0
        }
211
0
        if (new_max_thread_num >= cur_max_thread_num) {
212
0
            Status st_max = _scan_thread_pool->set_max_threads(new_max_thread_num);
213
0
            if (!st_max.ok()) {
214
0
                LOG(WARNING) << "Failed to set max threads for scan thread pool: "
215
0
                             << st_max.to_string();
216
0
            }
217
0
            Status st_min = _scan_thread_pool->set_min_threads(new_min_thread_num);
218
0
            if (!st_min.ok()) {
219
0
                LOG(WARNING) << "Failed to set min threads for scan thread pool: "
220
0
                             << st_min.to_string();
221
0
            }
222
0
        } else {
223
0
            Status st_min = _scan_thread_pool->set_min_threads(new_min_thread_num);
224
0
            if (!st_min.ok()) {
225
0
                LOG(WARNING) << "Failed to set min threads for scan thread pool: "
226
0
                             << st_min.to_string();
227
0
            }
228
0
            Status st_max = _scan_thread_pool->set_max_threads(new_max_thread_num);
229
0
            if (!st_max.ok()) {
230
0
                LOG(WARNING) << "Failed to set max threads for scan thread pool: "
231
0
                             << st_max.to_string();
232
0
            }
233
0
        }
234
0
    }
235
236
84
    int get_queue_size() override { return _scan_thread_pool->get_queue_size(); }
237
238
56
    int get_active_threads() override { return _scan_thread_pool->num_active_threads(); }
239
240
0
    std::vector<int> thread_debug_info() override { return _scan_thread_pool->debug_info(); }
241
242
    Status schedule_scan_task(std::shared_ptr<ScannerContext> scanner_ctx,
243
                              std::shared_ptr<ScanTask> current_scan_task,
244
                              std::unique_lock<std::mutex>& transfer_lock) override;
245
246
private:
247
    void _run_context(std::shared_ptr<ScannerContext> scanner_ctx);
248
249
    std::unique_ptr<ThreadPool> _scan_thread_pool;
250
    std::atomic<bool> _is_stop;
251
    std::weak_ptr<CgroupCpuCtl> _cgroup_cpu_ctl;
252
    std::string _sched_name;
253
    std::string _workload_group;
254
};
255
256
class TaskExecutorSimplifiedScanScheduler final : public ScannerScheduler {
257
public:
258
    TaskExecutorSimplifiedScanScheduler(std::string sched_name,
259
                                        std::shared_ptr<CgroupCpuCtl> cgroup_cpu_ctl,
260
                                        std::string workload_group = "system")
261
0
            : _is_stop(false),
262
0
              _cgroup_cpu_ctl(cgroup_cpu_ctl),
263
0
              _sched_name(sched_name),
264
0
              _workload_group(workload_group) {}
265
266
0
    ~TaskExecutorSimplifiedScanScheduler() override {
267
#ifndef BE_TEST
268
        stop();
269
#endif
270
0
        LOG(INFO) << "Scanner sche " << _sched_name << " shutdown";
271
0
    }
272
273
0
    void stop() override {
274
0
        if (_is_stop.exchange(true)) {
275
0
            return;
276
0
        }
277
0
        _task_executor->stop();
278
0
        _task_executor->wait();
279
0
    }
280
281
    Status start(int max_thread_num, int min_thread_num, int queue_size,
282
0
                 int min_active_scan_threads) override {
283
0
        _min_active_scan_threads = min_active_scan_threads;
284
0
        TimeSharingTaskExecutor::ThreadConfig thread_config;
285
0
        thread_config.thread_name = _sched_name;
286
0
        thread_config.workload_group = _workload_group;
287
0
        thread_config.max_thread_num = max_thread_num;
288
0
        thread_config.min_thread_num = min_thread_num;
289
0
        thread_config.max_queue_size = queue_size;
290
0
        thread_config.cgroup_cpu_ctl = _cgroup_cpu_ctl;
291
0
        _task_executor = TimeSharingTaskExecutor::create_shared(
292
0
                thread_config, max_thread_num * 2, config::task_executor_min_concurrency_per_task,
293
0
                config::task_executor_max_concurrency_per_task > 0
294
0
                        ? config::task_executor_max_concurrency_per_task
295
0
                        : std::numeric_limits<int>::max(),
296
0
                std::make_shared<SystemTicker>(), nullptr, false);
297
0
        RETURN_IF_ERROR(_task_executor->init());
298
0
        RETURN_IF_ERROR(_task_executor->start());
299
0
        return Status::OK();
300
0
    }
301
302
0
    Status submit_scan_task(SimplifiedScanTask scan_task) override {
303
0
        if (!_is_stop) {
304
0
            if (scan_task.scanner_context == nullptr) {
305
0
                return Status::InternalError<false>("scanner pool {} got null scanner context.",
306
0
                                                    _sched_name);
307
0
            }
308
0
            if (scan_task.scan_task == nullptr) {
309
0
                return Status::InternalError<false>("scanner pool {} got null scan task.",
310
0
                                                    _sched_name);
311
0
            }
312
0
            auto task_handle = scan_task.scanner_context->task_handle();
313
0
            if (task_handle == nullptr) {
314
0
                return Status::InternalError<false>(
315
0
                        "scanner pool {} got null task handle, scan task first schedule: {}, "
316
0
                        "scanner context: {}",
317
0
                        _sched_name, scan_task.scan_task->is_first_schedule,
318
0
                        scan_task.scanner_context->debug_string());
319
0
            }
320
0
            std::shared_ptr<SplitRunner> split_runner;
321
0
            if (scan_task.scan_task->is_first_schedule) {
322
0
                split_runner = std::make_shared<ScannerSplitRunner>("scanner_split_runner",
323
0
                                                                    scan_task.scan_func);
324
0
                RETURN_IF_ERROR(split_runner->init());
325
0
                auto result = _task_executor->enqueue_splits(task_handle, false, {split_runner});
326
0
                if (!result.has_value()) {
327
0
                    LOG(WARNING) << "enqueue_splits failed: " << result.error();
328
0
                    return result.error();
329
0
                }
330
0
                scan_task.scan_task->is_first_schedule = false;
331
0
            } else {
332
0
                split_runner = scan_task.scan_task->split_runner.lock();
333
0
                if (split_runner == nullptr) {
334
0
                    return Status::OK();
335
0
                }
336
0
                RETURN_IF_ERROR(_task_executor->re_enqueue_split(task_handle, false, split_runner));
337
0
            }
338
0
            scan_task.scan_task->split_runner = split_runner;
339
0
            return Status::OK();
340
0
        } else {
341
0
            return Status::InternalError<false>("scanner pool {} is shutdown.", _sched_name);
342
0
        }
343
0
    }
344
345
    // A task has only one split. When the split is created, the task is created according to the task_id,
346
    // and the task is automatically removed when the split ends.
347
    // Now it is only for PInternalService::multiget_data_v2 used by TopN materialization.
348
    Status submit_scan_task(SimplifiedScanTask scan_task,
349
0
                            const std::string& task_id_string) override {
350
0
        if (!_is_stop) {
351
0
            TaskId task_id(task_id_string);
352
0
            std::shared_ptr<TaskHandle> task_handle = DORIS_TRY(_task_executor->create_task(
353
0
                    task_id, []() { return 0.0; },
354
0
                    config::task_executor_initial_max_concurrency_per_task > 0
355
0
                            ? config::task_executor_initial_max_concurrency_per_task
356
0
                            : std::max(48, CpuInfo::num_cores() * 2),
357
0
                    std::chrono::milliseconds(100), std::nullopt));
358
359
0
            std::weak_ptr<TaskExecutor> task_executor = _task_executor;
360
0
            auto wrapped_scan_func = [task_executor, task_handle,
361
0
                                      scan_func = scan_task.scan_func]() {
362
0
                bool result = scan_func();
363
0
                if (result) {
364
0
                    if (auto executor = task_executor.lock()) {
365
0
                        static_cast<void>(executor->remove_task(task_handle));
366
0
                    }
367
0
                }
368
0
                return result;
369
0
            };
370
371
0
            auto split_runner =
372
0
                    std::make_shared<ScannerSplitRunner>("scanner_split_runner", wrapped_scan_func);
373
0
            RETURN_IF_ERROR(split_runner->init());
374
375
0
            auto result = _task_executor->enqueue_splits(task_handle, false, {split_runner});
376
0
            if (!result.has_value()) {
377
0
                LOG(WARNING) << "enqueue_splits failed: " << result.error();
378
0
                return result.error();
379
0
            }
380
0
            return Status::OK();
381
0
        } else {
382
0
            return Status::InternalError<false>("scanner pool {} is shutdown.", _sched_name);
383
0
        }
384
0
    }
385
386
    void reset_thread_num(int new_max_thread_num, int new_min_thread_num,
387
0
                          int min_active_scan_threads) override {
388
0
        _min_active_scan_threads = min_active_scan_threads;
389
0
        auto task_executor =
390
0
                std::dynamic_pointer_cast<doris::TimeSharingTaskExecutor>(_task_executor);
391
0
        int cur_max_thread_num = task_executor->max_threads();
392
0
        int cur_min_thread_num = task_executor->min_threads();
393
0
        if (cur_max_thread_num == new_max_thread_num && cur_min_thread_num == new_min_thread_num) {
394
0
            return;
395
0
        }
396
0
        if (new_max_thread_num >= cur_max_thread_num) {
397
0
            Status st_max = task_executor->set_max_threads(new_max_thread_num);
398
0
            if (!st_max.ok()) {
399
0
                LOG(WARNING) << "Failed to set max threads for scan thread pool: "
400
0
                             << st_max.to_string();
401
0
            }
402
0
            Status st_min = task_executor->set_min_threads(new_min_thread_num);
403
0
            if (!st_min.ok()) {
404
0
                LOG(WARNING) << "Failed to set min threads for scan thread pool: "
405
0
                             << st_min.to_string();
406
0
            }
407
0
        } else {
408
0
            Status st_min = task_executor->set_min_threads(new_min_thread_num);
409
0
            if (!st_min.ok()) {
410
0
                LOG(WARNING) << "Failed to set min threads for scan thread pool: "
411
0
                             << st_min.to_string();
412
0
            }
413
0
            Status st_max = task_executor->set_max_threads(new_max_thread_num);
414
0
            if (!st_max.ok()) {
415
0
                LOG(WARNING) << "Failed to set max threads for scan thread pool: "
416
0
                             << st_max.to_string();
417
0
            }
418
0
        }
419
0
    }
420
421
0
    int get_queue_size() override {
422
0
        auto task_executor =
423
0
                std::dynamic_pointer_cast<doris::TimeSharingTaskExecutor>(_task_executor);
424
0
        return task_executor->get_queue_size();
425
0
    }
426
427
0
    int get_active_threads() override {
428
0
        auto task_executor =
429
0
                std::dynamic_pointer_cast<doris::TimeSharingTaskExecutor>(_task_executor);
430
0
        return task_executor->num_active_threads();
431
0
    }
432
433
0
    std::vector<int> thread_debug_info() override {
434
0
        auto task_executor =
435
0
                std::dynamic_pointer_cast<doris::TimeSharingTaskExecutor>(_task_executor);
436
0
        return task_executor->debug_info();
437
0
    }
438
439
0
    std::shared_ptr<TaskExecutor> task_executor() const { return _task_executor; }
440
441
    Status schedule_scan_task(std::shared_ptr<ScannerContext> scanner_ctx,
442
                              std::shared_ptr<ScanTask> current_scan_task,
443
                              std::unique_lock<std::mutex>& transfer_lock) override;
444
445
private:
446
    std::atomic<bool> _is_stop;
447
    std::weak_ptr<CgroupCpuCtl> _cgroup_cpu_ctl;
448
    std::string _sched_name;
449
    std::string _workload_group;
450
    std::shared_mutex _lock;
451
    std::shared_ptr<TaskExecutor> _task_executor = nullptr;
452
};
453
454
} // namespace doris