Coverage Report

Created: 2026-10-12 12:17

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