Coverage Report

Created: 2026-07-09 07:36

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
1.46M
                       std::shared_ptr<ScanTask> scan_task) {
52
1.46M
        this->scan_func = scan_func;
53
1.46M
        this->scanner_context = scanner_context;
54
1.46M
        this->scan_task = scan_task;
55
1.46M
    }
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
1.35M
            : _name(std::move(name)), _scan_func(std::move(scan_func)), _started(false) {}
66
67
2.71M
    Status init() override { return Status::OK(); }
68
69
    Result<SharedListenableFuture<Void>> process_for(std::chrono::nanoseconds) override;
70
71
1.35M
    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
77
    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
312k
    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
private:
142
    static void _scanner_scan(std::shared_ptr<ScannerContext> ctx,
143
                              std::shared_ptr<ScanTask> scan_task);
144
145
    static void _make_sure_virtual_col_is_materialized(const std::shared_ptr<Scanner>& scanner,
146
                                                       Block* block);
147
};
148
149
class ThreadPoolSimplifiedScanScheduler MOCK_REMOVE(final) : public ScannerScheduler {
150
public:
151
    ThreadPoolSimplifiedScanScheduler(std::string sched_name,
152
                                      std::shared_ptr<CgroupCpuCtl> cgroup_cpu_ctl,
153
                                      std::string workload_group = "system")
154
41
            : _is_stop(false),
155
41
              _cgroup_cpu_ctl(cgroup_cpu_ctl),
156
41
              _sched_name(sched_name),
157
41
              _workload_group(workload_group) {}
158
159
41
    ~ThreadPoolSimplifiedScanScheduler() override {
160
41
#ifndef BE_TEST
161
41
        stop();
162
41
#endif
163
41
        LOG(INFO) << "Scanner sche " << _sched_name << " shutdown";
164
41
    }
165
166
0
    void stop() override {
167
0
        if (_is_stop.exchange(true)) {
168
0
            return;
169
0
        }
170
0
        _scan_thread_pool->shutdown();
171
0
        _scan_thread_pool->wait();
172
0
    }
173
174
    Status start(int max_thread_num, int min_thread_num, int queue_size,
175
0
                 int min_active_scan_threads) override {
176
0
        _min_active_scan_threads = min_active_scan_threads;
177
0
        RETURN_IF_ERROR(ThreadPoolBuilder(_sched_name, _workload_group)
178
0
                                .set_min_threads(min_thread_num)
179
0
                                .set_max_threads(max_thread_num)
180
0
                                .set_max_queue_size(queue_size)
181
0
                                .set_cgroup_cpu_ctl(_cgroup_cpu_ctl)
182
0
                                .build(&_scan_thread_pool));
183
0
        return Status::OK();
184
0
    }
185
186
0
    Status submit_scan_task(SimplifiedScanTask scan_task) override {
187
0
        if (!_is_stop) {
188
0
            return _scan_thread_pool->submit_func([scan_task] { scan_task.scan_func(); });
189
0
        } else {
190
0
            return Status::InternalError<false>("scanner pool {} is shutdown.", _sched_name);
191
0
        }
192
0
    }
193
194
    Status submit_scan_task(SimplifiedScanTask scan_task,
195
0
                            const std::string& task_id_string) override {
196
0
        return submit_scan_task(scan_task);
197
0
    }
198
199
    void reset_thread_num(int new_max_thread_num, int new_min_thread_num,
200
0
                          int min_active_scan_threads) override {
201
0
        _min_active_scan_threads = min_active_scan_threads;
202
0
        int cur_max_thread_num = _scan_thread_pool->max_threads();
203
0
        int cur_min_thread_num = _scan_thread_pool->min_threads();
204
0
        if (cur_max_thread_num == new_max_thread_num && cur_min_thread_num == new_min_thread_num) {
205
0
            return;
206
0
        }
207
0
        if (new_max_thread_num >= cur_max_thread_num) {
208
0
            Status st_max = _scan_thread_pool->set_max_threads(new_max_thread_num);
209
0
            if (!st_max.ok()) {
210
0
                LOG(WARNING) << "Failed to set max threads for scan thread pool: "
211
0
                             << st_max.to_string();
212
0
            }
213
0
            Status st_min = _scan_thread_pool->set_min_threads(new_min_thread_num);
214
0
            if (!st_min.ok()) {
215
0
                LOG(WARNING) << "Failed to set min threads for scan thread pool: "
216
0
                             << st_min.to_string();
217
0
            }
218
0
        } else {
219
0
            Status st_min = _scan_thread_pool->set_min_threads(new_min_thread_num);
220
0
            if (!st_min.ok()) {
221
0
                LOG(WARNING) << "Failed to set min threads for scan thread pool: "
222
0
                             << st_min.to_string();
223
0
            }
224
0
            Status st_max = _scan_thread_pool->set_max_threads(new_max_thread_num);
225
0
            if (!st_max.ok()) {
226
0
                LOG(WARNING) << "Failed to set max threads for scan thread pool: "
227
0
                             << st_max.to_string();
228
0
            }
229
0
        }
230
0
    }
231
232
0
    int get_queue_size() override { return _scan_thread_pool->get_queue_size(); }
233
234
0
    int get_active_threads() override { return _scan_thread_pool->num_active_threads(); }
235
236
0
    std::vector<int> thread_debug_info() override { return _scan_thread_pool->debug_info(); }
237
238
    Status schedule_scan_task(std::shared_ptr<ScannerContext> scanner_ctx,
239
                              std::shared_ptr<ScanTask> current_scan_task,
240
                              std::unique_lock<std::mutex>& transfer_lock) override;
241
242
private:
243
    std::unique_ptr<ThreadPool> _scan_thread_pool;
244
    std::atomic<bool> _is_stop;
245
    std::weak_ptr<CgroupCpuCtl> _cgroup_cpu_ctl;
246
    std::string _sched_name;
247
    std::string _workload_group;
248
    std::shared_mutex _lock;
249
};
250
251
class TaskExecutorSimplifiedScanScheduler final : public ScannerScheduler {
252
public:
253
    TaskExecutorSimplifiedScanScheduler(std::string sched_name,
254
                                        std::shared_ptr<CgroupCpuCtl> cgroup_cpu_ctl,
255
                                        std::string workload_group = "system")
256
80
            : _is_stop(false),
257
80
              _cgroup_cpu_ctl(cgroup_cpu_ctl),
258
80
              _sched_name(sched_name),
259
80
              _workload_group(workload_group) {}
260
261
36
    ~TaskExecutorSimplifiedScanScheduler() override {
262
36
#ifndef BE_TEST
263
36
        stop();
264
36
#endif
265
36
        LOG(INFO) << "Scanner sche " << _sched_name << " shutdown";
266
36
    }
267
268
72
    void stop() override {
269
72
        if (_is_stop.exchange(true)) {
270
36
            return;
271
36
        }
272
36
        _task_executor->stop();
273
36
        _task_executor->wait();
274
36
    }
275
276
    Status start(int max_thread_num, int min_thread_num, int queue_size,
277
80
                 int min_active_scan_threads) override {
278
80
        _min_active_scan_threads = min_active_scan_threads;
279
80
        TimeSharingTaskExecutor::ThreadConfig thread_config;
280
80
        thread_config.thread_name = _sched_name;
281
80
        thread_config.workload_group = _workload_group;
282
80
        thread_config.max_thread_num = max_thread_num;
283
80
        thread_config.min_thread_num = min_thread_num;
284
80
        thread_config.max_queue_size = queue_size;
285
80
        thread_config.cgroup_cpu_ctl = _cgroup_cpu_ctl;
286
80
        _task_executor = TimeSharingTaskExecutor::create_shared(
287
80
                thread_config, max_thread_num * 2, config::task_executor_min_concurrency_per_task,
288
80
                config::task_executor_max_concurrency_per_task > 0
289
80
                        ? config::task_executor_max_concurrency_per_task
290
80
                        : std::numeric_limits<int>::max(),
291
80
                std::make_shared<SystemTicker>(), nullptr, false);
292
80
        RETURN_IF_ERROR(_task_executor->init());
293
80
        RETURN_IF_ERROR(_task_executor->start());
294
80
        return Status::OK();
295
80
    }
296
297
1.46M
    Status submit_scan_task(SimplifiedScanTask scan_task) override {
298
1.46M
        if (!_is_stop) {
299
1.46M
            if (scan_task.scanner_context == nullptr) {
300
0
                return Status::InternalError<false>("scanner pool {} got null scanner context.",
301
0
                                                    _sched_name);
302
0
            }
303
1.46M
            if (scan_task.scan_task == nullptr) {
304
0
                return Status::InternalError<false>("scanner pool {} got null scan task.",
305
0
                                                    _sched_name);
306
0
            }
307
1.46M
            auto task_handle = scan_task.scanner_context->task_handle();
308
1.46M
            if (task_handle == nullptr) {
309
0
                return Status::InternalError<false>(
310
0
                        "scanner pool {} got null task handle, scan task first schedule: {}, "
311
0
                        "scanner context: {}",
312
0
                        _sched_name, scan_task.scan_task->is_first_schedule,
313
0
                        scan_task.scanner_context->debug_string());
314
0
            }
315
1.46M
            std::shared_ptr<SplitRunner> split_runner;
316
1.46M
            if (scan_task.scan_task->is_first_schedule) {
317
1.34M
                split_runner = std::make_shared<ScannerSplitRunner>("scanner_split_runner",
318
1.34M
                                                                    scan_task.scan_func);
319
1.34M
                RETURN_IF_ERROR(split_runner->init());
320
1.34M
                auto result = _task_executor->enqueue_splits(task_handle, false, {split_runner});
321
1.34M
                if (!result.has_value()) {
322
0
                    LOG(WARNING) << "enqueue_splits failed: " << result.error();
323
0
                    return result.error();
324
0
                }
325
1.34M
                scan_task.scan_task->is_first_schedule = false;
326
1.34M
            } else {
327
112k
                split_runner = scan_task.scan_task->split_runner.lock();
328
112k
                if (split_runner == nullptr) {
329
0
                    return Status::OK();
330
0
                }
331
112k
                RETURN_IF_ERROR(_task_executor->re_enqueue_split(task_handle, false, split_runner));
332
112k
            }
333
1.46M
            scan_task.scan_task->split_runner = split_runner;
334
1.46M
            return Status::OK();
335
1.46M
        } else {
336
2
            return Status::InternalError<false>("scanner pool {} is shutdown.", _sched_name);
337
2
        }
338
1.46M
    }
339
340
    // A task has only one split. When the split is created, the task is created according to the task_id,
341
    // and the task is automatically removed when the split ends.
342
    // Now it is only for PInternalService::multiget_data_v2 used by TopN materialization.
343
    Status submit_scan_task(SimplifiedScanTask scan_task,
344
6.63k
                            const std::string& task_id_string) override {
345
6.63k
        if (!_is_stop) {
346
6.63k
            TaskId task_id(task_id_string);
347
6.63k
            std::shared_ptr<TaskHandle> task_handle = DORIS_TRY(_task_executor->create_task(
348
6.63k
                    task_id, []() { return 0.0; },
349
6.63k
                    config::task_executor_initial_max_concurrency_per_task > 0
350
6.63k
                            ? config::task_executor_initial_max_concurrency_per_task
351
6.63k
                            : std::max(48, CpuInfo::num_cores() * 2),
352
6.63k
                    std::chrono::milliseconds(100), std::nullopt));
353
354
6.63k
            std::weak_ptr<TaskExecutor> task_executor = _task_executor;
355
6.63k
            auto wrapped_scan_func = [task_executor, task_handle,
356
6.63k
                                      scan_func = scan_task.scan_func]() {
357
6.63k
                bool result = scan_func();
358
6.63k
                if (result) {
359
6.63k
                    if (auto executor = task_executor.lock()) {
360
6.62k
                        static_cast<void>(executor->remove_task(task_handle));
361
6.62k
                    }
362
6.63k
                }
363
6.63k
                return result;
364
6.63k
            };
365
366
6.63k
            auto split_runner =
367
6.63k
                    std::make_shared<ScannerSplitRunner>("scanner_split_runner", wrapped_scan_func);
368
6.63k
            RETURN_IF_ERROR(split_runner->init());
369
370
6.63k
            auto result = _task_executor->enqueue_splits(task_handle, false, {split_runner});
371
6.63k
            if (!result.has_value()) {
372
0
                LOG(WARNING) << "enqueue_splits failed: " << result.error();
373
0
                return result.error();
374
0
            }
375
6.63k
            return Status::OK();
376
6.63k
        } else {
377
0
            return Status::InternalError<false>("scanner pool {} is shutdown.", _sched_name);
378
0
        }
379
6.63k
    }
380
381
    void reset_thread_num(int new_max_thread_num, int new_min_thread_num,
382
14.0k
                          int min_active_scan_threads) override {
383
14.0k
        _min_active_scan_threads = min_active_scan_threads;
384
14.0k
        auto task_executor =
385
14.0k
                std::dynamic_pointer_cast<doris::TimeSharingTaskExecutor>(_task_executor);
386
14.0k
        int cur_max_thread_num = task_executor->max_threads();
387
14.0k
        int cur_min_thread_num = task_executor->min_threads();
388
14.0k
        if (cur_max_thread_num == new_max_thread_num && cur_min_thread_num == new_min_thread_num) {
389
14.0k
            return;
390
14.0k
        }
391
3
        if (new_max_thread_num >= cur_max_thread_num) {
392
0
            Status st_max = task_executor->set_max_threads(new_max_thread_num);
393
0
            if (!st_max.ok()) {
394
0
                LOG(WARNING) << "Failed to set max threads for scan thread pool: "
395
0
                             << st_max.to_string();
396
0
            }
397
0
            Status st_min = task_executor->set_min_threads(new_min_thread_num);
398
0
            if (!st_min.ok()) {
399
0
                LOG(WARNING) << "Failed to set min threads for scan thread pool: "
400
0
                             << st_min.to_string();
401
0
            }
402
3
        } else {
403
3
            Status st_min = task_executor->set_min_threads(new_min_thread_num);
404
3
            if (!st_min.ok()) {
405
0
                LOG(WARNING) << "Failed to set min threads for scan thread pool: "
406
0
                             << st_min.to_string();
407
0
            }
408
3
            Status st_max = task_executor->set_max_threads(new_max_thread_num);
409
3
            if (!st_max.ok()) {
410
0
                LOG(WARNING) << "Failed to set max threads for scan thread pool: "
411
0
                             << st_max.to_string();
412
0
            }
413
3
        }
414
3
    }
415
416
1.77M
    int get_queue_size() override {
417
1.77M
        auto task_executor =
418
1.77M
                std::dynamic_pointer_cast<doris::TimeSharingTaskExecutor>(_task_executor);
419
1.77M
        return task_executor->get_queue_size();
420
1.77M
    }
421
422
1.77M
    int get_active_threads() override {
423
1.77M
        auto task_executor =
424
1.77M
                std::dynamic_pointer_cast<doris::TimeSharingTaskExecutor>(_task_executor);
425
1.77M
        return task_executor->num_active_threads();
426
1.77M
    }
427
428
218
    std::vector<int> thread_debug_info() override {
429
218
        auto task_executor =
430
218
                std::dynamic_pointer_cast<doris::TimeSharingTaskExecutor>(_task_executor);
431
218
        return task_executor->debug_info();
432
218
    }
433
434
316k
    std::shared_ptr<TaskExecutor> task_executor() const { return _task_executor; }
435
436
    Status schedule_scan_task(std::shared_ptr<ScannerContext> scanner_ctx,
437
                              std::shared_ptr<ScanTask> current_scan_task,
438
                              std::unique_lock<std::mutex>& transfer_lock) override;
439
440
private:
441
    std::atomic<bool> _is_stop;
442
    std::weak_ptr<CgroupCpuCtl> _cgroup_cpu_ctl;
443
    std::string _sched_name;
444
    std::string _workload_group;
445
    std::shared_mutex _lock;
446
    std::shared_ptr<TaskExecutor> _task_executor = nullptr;
447
};
448
449
} // namespace doris