Coverage Report

Created: 2026-07-27 17:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/be/src/pipeline/pipeline_task.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 <cstdint>
21
#include <memory>
22
#include <mutex>
23
#include <string>
24
#include <vector>
25
26
#include "common/status.h"
27
#include "pipeline/dependency.h"
28
#include "pipeline/exec/operator.h"
29
#include "pipeline/exec/spill_utils.h"
30
#include "pipeline/pipeline.h"
31
#include "util/runtime_profile.h"
32
#include "util/stopwatch.hpp"
33
#include "vec/core/block.h"
34
35
namespace doris {
36
class QueryContext;
37
class RuntimeState;
38
namespace pipeline {
39
class PipelineFragmentContext;
40
} // namespace pipeline
41
} // namespace doris
42
43
namespace doris::pipeline {
44
45
class MultiCoreTaskQueue;
46
class PriorityTaskQueue;
47
class Dependency;
48
49
class PipelineTask : public std::enable_shared_from_this<PipelineTask> {
50
public:
51
    PipelineTask(PipelinePtr& pipeline, uint32_t task_id, RuntimeState* state,
52
                 std::shared_ptr<PipelineFragmentContext> fragment_context,
53
                 RuntimeProfile* parent_profile,
54
                 std::map<int, std::pair<std::shared_ptr<BasicSharedState>,
55
                                         std::vector<std::shared_ptr<Dependency>>>>
56
                         shared_state_map,
57
                 int task_idx);
58
59
    virtual ~PipelineTask();
60
61
    Status prepare(const std::vector<TScanRangeParams>& scan_range, const int sender_id,
62
                   const TDataSink& tsink);
63
64
    virtual Status execute(bool* done);
65
66
    // if the pipeline create a bunch of pipeline task
67
    // must be call after all pipeline task is finish to release resource
68
    virtual Status close(Status exec_status, bool close_sink = true);
69
70
0
    virtual std::weak_ptr<PipelineFragmentContext>& fragment_context() { return _fragment_context; }
71
72
29
    int get_thread_id(int num_threads) const {
73
29
        return _thread_id == -1 ? _thread_id : _thread_id % num_threads;
74
29
    }
75
76
0
    virtual PipelineTask& set_thread_id(int thread_id) {
77
0
        _thread_id = thread_id;
78
0
        if (thread_id != _thread_id) {
79
0
            COUNTER_UPDATE(_core_change_times, 1);
80
0
        }
81
0
        return *this;
82
0
    }
83
84
    virtual Status finalize();
85
86
    std::string debug_string();
87
88
0
    std::shared_ptr<BasicSharedState> get_source_shared_state() {
89
0
        return _op_shared_states.contains(_source->operator_id())
90
0
                       ? _op_shared_states[_source->operator_id()]
91
0
                       : nullptr;
92
0
    }
93
94
    /**
95
     * Pipeline task is blockable means it will be blocked in the next run. So we should put it into
96
     * the blocking task scheduler.
97
     */
98
    virtual bool is_blockable() const;
99
100
    /**
101
     * `shared_state` is shared by different pipeline tasks. This function aims to establish
102
     * connections across related tasks.
103
     *
104
     * There are 2 kinds of relationships to share state by tasks.
105
     * 1. For regular operators, for example, Aggregation, we use the AggSinkOperator to create a
106
     *    shared state and then inject it into downstream task which contains the corresponding
107
     *    AggSourceOperator.
108
     * 2. For multiple-sink-single-source operator, for example, Set operations, the shared state is
109
     *    created once and shared by multiple sink operators and single source operator. For this
110
     *    case, we use the first sink operator create shared state and then inject into all of other
111
     *    tasks.
112
     */
113
    bool inject_shared_state(std::shared_ptr<BasicSharedState> shared_state);
114
115
21
    std::shared_ptr<BasicSharedState> get_sink_shared_state() { return _sink_shared_state; }
116
117
21
    BasicSharedState* get_op_shared_state(int id) {
118
21
        if (!_op_shared_states.contains(id)) {
119
19
            return nullptr;
120
19
        }
121
2
        return _op_shared_states[id].get();
122
21
    }
123
124
    Status wake_up(Dependency* dep);
125
126
0
    DataSinkOperatorPtr sink() const { return _sink; }
127
128
0
    int task_id() const { return _index; };
129
104k
    virtual bool is_finalized() const { return _exec_state == State::FINALIZED; }
130
131
6
    void set_wake_up_early(PipelineId wake_by = -1) {
132
6
        _wake_up_early = true;
133
6
        _wake_by = wake_by;
134
6
    }
135
136
    // Execution phase should be terminated. This is called if this task is canceled or waken up early.
137
    void terminate();
138
139
    // 1 used for update priority queue
140
    // note(wb) an ugly implementation, need refactor later
141
    // 1.1 pipeline task
142
0
    void inc_runtime_ns(uint64_t delta_time) { this->_runtime += delta_time; }
143
29
    uint64_t get_runtime_ns() const { return this->_runtime; }
144
145
    // 1.2 priority queue's queue level
146
10
    void update_queue_level(int queue_level) { this->_queue_level = queue_level; }
147
0
    int get_queue_level() const { return this->_queue_level; }
148
149
29
    void put_in_runnable_queue() {
150
29
        _schedule_time++;
151
29
        _wait_worker_watcher.start();
152
29
    }
153
154
10
    void pop_out_runnable_queue() { _wait_worker_watcher.stop(); }
155
156
35.0k
    bool is_running() { return _running.load(); }
157
0
    virtual bool set_running(bool running) {
158
0
        bool old_value = !running;
159
0
        _running.compare_exchange_weak(old_value, running);
160
0
        return old_value;
161
0
    }
162
163
0
    virtual RuntimeState* runtime_state() const { return _state; }
164
165
0
    virtual std::string task_name() const {
166
0
        return fmt::format("task{}({})", _index, _pipeline->_name);
167
0
    }
168
169
    [[nodiscard]] Status do_revoke_memory(const std::shared_ptr<SpillContext>& spill_context);
170
171
    // TODO: Maybe we do not need this safe code anymore
172
    void stop_if_finished();
173
174
0
    virtual PipelineId pipeline_id() const { return _pipeline->id(); }
175
    [[nodiscard]] size_t get_revocable_size() const;
176
    [[nodiscard]] Status revoke_memory(const std::shared_ptr<SpillContext>& spill_context);
177
178
25
    Status blocked(Dependency* dependency) {
179
25
        DCHECK_EQ(_blocked_dep, nullptr) << "task: " << debug_string();
180
25
        _blocked_dep = dependency;
181
25
        return _state_transition(PipelineTask::State::BLOCKED);
182
25
    }
183
184
protected:
185
    // Only used for RevokableTask
186
0
    PipelineTask() : _index(0) {}
187
188
private:
189
    friend class HybridTaskScheduler;
190
191
    void _stop_accepting_submit();
192
193
    // Whether this task is blocked before execution (FE 2-phase commit trigger, runtime filters)
194
    bool _wait_to_start();
195
    // Whether this task is blocked during execution (read dependency, write dependency)
196
    bool _is_blocked();
197
    // Whether this task is blocked after execution (pending finish dependency)
198
    bool _is_pending_finish();
199
200
    Status _extract_dependencies();
201
    void _init_profile();
202
    void _fresh_profile_counter();
203
    Status _open();
204
    Status _prepare();
205
206
    // Operator `op` try to reserve memory before executing. Return false if reserve failed
207
    // otherwise return true.
208
    bool _try_to_reserve_memory(const size_t reserve_size, OperatorBase* op);
209
210
    const TUniqueId _query_id;
211
    const uint32_t _index;
212
    PipelinePtr _pipeline;
213
    bool _opened;
214
    RuntimeState* _state = nullptr;
215
    int _thread_id = -1;
216
    uint32_t _schedule_time = 0;
217
    std::unique_ptr<vectorized::Block> _block;
218
219
    std::weak_ptr<PipelineFragmentContext> _fragment_context;
220
221
    // used for priority queue
222
    // it may be visited by different thread but there is no race condition
223
    // so no need to add lock
224
    uint64_t _runtime = 0;
225
    // it's visited in one thread, so no need to thread synchronization
226
    // 1 get task, (set _queue_level/_core_id)
227
    // 2 exe task
228
    // 3 update task statistics(update _queue_level/_core_id)
229
    int _queue_level = 0;
230
231
    RuntimeProfile* _parent_profile = nullptr;
232
    std::unique_ptr<RuntimeProfile> _task_profile;
233
    RuntimeProfile::Counter* _task_cpu_timer = nullptr;
234
    RuntimeProfile::Counter* _prepare_timer = nullptr;
235
    RuntimeProfile::Counter* _open_timer = nullptr;
236
    RuntimeProfile::Counter* _exec_timer = nullptr;
237
    RuntimeProfile::Counter* _get_block_timer = nullptr;
238
    RuntimeProfile::Counter* _get_block_counter = nullptr;
239
    RuntimeProfile::Counter* _sink_timer = nullptr;
240
    RuntimeProfile::Counter* _close_timer = nullptr;
241
    RuntimeProfile::Counter* _schedule_counts = nullptr;
242
    MonotonicStopWatch _wait_worker_watcher;
243
    RuntimeProfile::Counter* _wait_worker_timer = nullptr;
244
    // TODO we should calculate the time between when really runnable and runnable
245
    RuntimeProfile::Counter* _yield_counts = nullptr;
246
    RuntimeProfile::Counter* _core_change_times = nullptr;
247
    RuntimeProfile::Counter* _memory_reserve_times = nullptr;
248
    RuntimeProfile::Counter* _memory_reserve_failed_times = nullptr;
249
250
    Operators _operators; // left is _source, right is _root
251
    OperatorXBase* _source;
252
    OperatorXBase* _root;
253
    DataSinkOperatorPtr _sink;
254
255
    // `_read_dependencies` is stored as same order as `_operators`
256
    std::vector<std::vector<Dependency*>> _read_dependencies;
257
    std::vector<Dependency*> _write_dependencies;
258
    std::vector<Dependency*> _finish_dependencies;
259
    std::vector<Dependency*> _execution_dependencies;
260
261
    // All shared states of this pipeline task.
262
    std::map<int, std::shared_ptr<BasicSharedState>> _op_shared_states;
263
    std::shared_ptr<BasicSharedState> _sink_shared_state;
264
    std::vector<TScanRangeParams> _scan_ranges;
265
    std::map<int,
266
             std::pair<std::shared_ptr<BasicSharedState>, std::vector<std::shared_ptr<Dependency>>>>
267
            _shared_state_map;
268
    int _task_idx;
269
    bool _dry_run = false;
270
    MOCK_REMOVE(const)
271
    unsigned long long _exec_time_slice = config::pipeline_task_exec_time_slice * NANOS_PER_MILLIS;
272
    Dependency* _blocked_dep = nullptr;
273
274
    Dependency* _memory_sufficient_dependency;
275
    std::mutex _dependency_lock;
276
    // Guards _accept_submit and keeps HybridTaskScheduler::submit() from reading _sink/_operators
277
    // in is_blockable() while terminal close/finalize is closing the submit gate.
278
    std::mutex _blockable_check_lock;
279
    bool _accept_submit = true;
280
281
    std::atomic<bool> _running {false};
282
    std::atomic<bool> _eos {false};
283
    std::atomic<bool> _wake_up_early {false};
284
    // PipelineTask maybe hold by TaskQueue
285
    std::shared_ptr<MemTrackerLimiter> _query_mem_tracker;
286
287
    /**
288
         *
289
         * INITED -----> RUNNABLE -------------------------+----> FINISHED ---+---> FINALIZED
290
         *                   ^                             |                  |
291
         *                   |                             |                  |
292
         *                   +----------- BLOCKED <--------+------------------+
293
         */
294
    enum class State : int {
295
        INITED,
296
        RUNNABLE,
297
        BLOCKED,
298
        FINISHED,
299
        FINALIZED,
300
    };
301
    const std::vector<std::set<State>> LEGAL_STATE_TRANSITION = {
302
            {},                                               // Target state is INITED
303
            {State::INITED, State::RUNNABLE, State::BLOCKED}, // Target state is RUNNABLE
304
            {State::RUNNABLE, State::FINISHED},               // Target state is BLOCKED
305
            {State::RUNNABLE},                                // Target state is FINISHED
306
            {State::INITED, State::FINISHED}};                // Target state is FINALIZED
307
308
35.1k
    std::string _to_string(State state) const {
309
35.1k
        switch (state) {
310
17
        case State::INITED:
311
17
            return "INITED";
312
35.0k
        case State::RUNNABLE:
313
35.0k
            return "RUNNABLE";
314
41
        case State::BLOCKED:
315
41
            return "BLOCKED";
316
23
        case State::FINISHED:
317
23
            return "FINISHED";
318
21
        case State::FINALIZED:
319
21
            return "FINALIZED";
320
0
        default:
321
0
            __builtin_unreachable();
322
35.1k
        }
323
35.1k
    }
324
325
    Status _state_transition(State new_state);
326
    std::atomic<State> _exec_state = State::INITED;
327
    MonotonicStopWatch _state_change_watcher;
328
    std::atomic<bool> _spilling = false;
329
    const std::string _pipeline_name;
330
    int _wake_by = -1;
331
};
332
333
using PipelineTaskSPtr = std::shared_ptr<PipelineTask>;
334
335
} // namespace doris::pipeline