Coverage Report

Created: 2026-09-01 18:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/pipeline/pipeline_fragment_context.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 <brpc/closure_guard.h>
21
#include <gen_cpp/Partitions_types.h>
22
#include <gen_cpp/Types_types.h>
23
#include <gen_cpp/types.pb.h>
24
25
#include <atomic>
26
#include <cstddef>
27
#include <cstdint>
28
#include <functional>
29
#include <memory>
30
#include <mutex>
31
#include <set>
32
#include <string>
33
#include <vector>
34
35
#include "common/status.h"
36
#include "exec/pipeline/pipeline.h"
37
#include "exec/pipeline/pipeline_task.h"
38
#include "runtime/query_context.h"
39
#include "runtime/runtime_profile.h"
40
#include "runtime/runtime_state.h"
41
#include "runtime/task_execution_context.h"
42
#include "util/stopwatch.hpp"
43
#include "util/uid_util.h"
44
45
namespace doris {
46
struct ReportStatusRequest;
47
class ExecEnv;
48
class RuntimeFilterMergeControllerEntity;
49
class TDataSink;
50
class TPipelineFragmentParams;
51
class QueryCacheRuntime;
52
53
class Dependency;
54
struct LocalExchangeSharedState;
55
56
class PipelineFragmentContext : public TaskExecutionContext {
57
public:
58
    ENABLE_FACTORY_CREATOR(PipelineFragmentContext);
59
    PipelineFragmentContext(TUniqueId query_id, const TPipelineFragmentParams& request,
60
                            std::shared_ptr<QueryContext> query_ctx, ExecEnv* exec_env,
61
                            const std::function<void(RuntimeState*, Status*)>& call_back);
62
63
    ~PipelineFragmentContext() override;
64
65
    void print_profile(const std::string& extra_info);
66
67
    std::vector<std::shared_ptr<TRuntimeProfileTree>> collect_realtime_profile() const;
68
    std::shared_ptr<TRuntimeProfileTree> collect_realtime_load_channel_profile() const;
69
70
    bool is_timeout(timespec now) const;
71
72
18.9k
    uint64_t elapsed_time() const { return _fragment_watcher.elapsed_time(); }
73
74
2
    int timeout_second() const { return _timeout; }
75
76
    PipelinePtr add_pipeline(PipelinePtr parent = nullptr, int idx = -1);
77
78
10.4M
    QueryContext* get_query_ctx() { return _query_ctx.get(); }
79
25.0M
    [[nodiscard]] bool is_canceled() const { return _query_ctx->is_cancelled(); }
80
81
    Status prepare(ThreadPool* thread_pool);
82
83
    Status submit();
84
85
312k
    void set_is_report_success(bool is_report_success) { _is_report_success = is_report_success; }
86
87
    bool notify_close();
88
89
1.54M
    TUniqueId get_query_id() const { return _query_id; }
90
91
6
    [[nodiscard]] int get_fragment_id() const { return _fragment_id; }
92
93
    void decrement_running_task(PipelineId pipeline_id);
94
95
47.3k
    uint32_t rec_cte_stage() const { return _rec_cte_stage; }
96
3.51k
    void set_rec_cte_stage(uint32_t stage) { _rec_cte_stage = stage; }
97
98
    Status send_report(bool);
99
100
    void trigger_report_if_necessary();
101
    void refresh_next_report_time();
102
103
    std::string debug_string();
104
105
592k
    [[nodiscard]] int next_operator_id() { return _operator_id--; }
106
107
3.07M
    [[nodiscard]] int max_operator_id() const { return _operator_id; }
108
109
520k
    [[nodiscard]] int next_sink_operator_id() { return _sink_operator_id--; }
110
111
    [[nodiscard]] size_t get_revocable_size(bool* has_running_task) const;
112
113
    [[nodiscard]] std::vector<PipelineTask*> get_revocable_tasks() const;
114
115
34.0k
    void clear_finished_tasks() {
116
34.0k
        if (_need_notify_close) {
117
122
            return;
118
122
        }
119
132k
        for (size_t j = 0; j < _tasks.size(); j++) {
120
297k
            for (size_t i = 0; i < _tasks[j].size(); i++) {
121
198k
                _tasks[j][i].first->stop_if_finished();
122
198k
            }
123
98.8k
        }
124
33.9k
    }
125
126
    std::string get_load_error_url();
127
    std::string get_first_error_msg();
128
129
    std::set<int> get_deregister_runtime_filter() const;
130
131
    // Store the brpc ClosureGuard so the RPC response is deferred until this PFC is destroyed.
132
    // When need_send_report_on_destruction is true (final_close), send the report immediately
133
    // and do not store the guard (let it fire on return to complete the RPC).
134
    //
135
    // Thread safety: This method is NOT thread-safe. It reads/writes _wait_close_guard without
136
    // synchronization. Currently it is only called from rerun_fragment() which is invoked
137
    // sequentially by RecCTESourceOperatorX (a serial operator) — one opcode at a time per
138
    // fragment. Do NOT call this concurrently from multiple threads.
139
    Status listen_wait_close(const std::shared_ptr<brpc::ClosureGuard>& guard,
140
3.69k
                             bool need_send_report_on_destruction) {
141
3.69k
        if (_wait_close_guard) {
142
0
            return Status::InternalError("Already listening wait close");
143
0
        }
144
3.69k
        if (need_send_report_on_destruction) {
145
176
            return send_report(true);
146
3.51k
        } else {
147
3.51k
            _wait_close_guard = guard;
148
3.51k
        }
149
3.51k
        return Status::OK();
150
3.69k
    }
151
152
private:
153
    // QueryContext is the sole entry point for query cancellation. Keep fragment cancellation
154
    // private so callers cannot bypass QueryContext's first-error-wins guard and repeatedly run
155
    // expensive fragment-local cleanup (for example, timeout diagnostics and task unblocking).
156
    // QueryContext::cancel() calls this method only to propagate the accepted query cancellation
157
    // to each fragment; this method must not call QueryContext::cancel() back.
158
    friend void QueryContext::cancel(Status new_status);
159
    void cancel(const Status reason);
160
161
    void _coordinator_callback(const ReportStatusRequest& req);
162
    void _append_external_file_commit_data(const ReportStatusRequest& req,
163
                                           TReportExecStatusParams* params) const;
164
    std::string _to_http_path(const std::string& file_name) const;
165
166
    void _release_resource();
167
168
    Status _build_and_prepare_full_pipeline(ThreadPool* thread_pool);
169
170
    Status _build_pipelines(ObjectPool* pool, const DescriptorTbl& descs, OperatorPtr* root,
171
                            PipelinePtr cur_pipe);
172
    Status _create_tree_helper(ObjectPool* pool, const std::vector<TPlanNode>& tnodes,
173
                               const DescriptorTbl& descs, OperatorPtr parent, int* node_idx,
174
                               OperatorPtr* root, PipelinePtr& cur_pipe, int child_idx,
175
                               const bool followed_by_shuffled_join,
176
                               const bool require_bucket_distribution);
177
178
    Status _create_operator(ObjectPool* pool, const TPlanNode& tnode, const DescriptorTbl& descs,
179
                            OperatorPtr& op, PipelinePtr& cur_pipe, int parent_idx, int child_idx,
180
                            const bool followed_by_shuffled_join,
181
                            const bool require_bucket_distribution, OperatorPtr& cache_op);
182
    template <bool is_intersect>
183
    Status _build_operators_for_set_operation_node(ObjectPool* pool, const TPlanNode& tnode,
184
                                                   const DescriptorTbl& descs, OperatorPtr& op,
185
                                                   PipelinePtr& cur_pipe,
186
                                                   std::vector<DataSinkOperatorPtr>& sink_ops);
187
188
    Status _create_data_sink(ObjectPool* pool, const TDataSink& thrift_sink,
189
                             const std::vector<TExpr>& output_exprs,
190
                             const TPipelineFragmentParams& params, const RowDescriptor& row_desc,
191
                             RuntimeState* state, DescriptorTbl& desc_tbl,
192
                             PipelineId cur_pipeline_id);
193
    Status _plan_local_exchange(int num_buckets,
194
                                const std::map<int, int>& bucket_seq_to_instance_idx,
195
                                const std::map<int, int>& shuffle_idx_to_instance_idx);
196
    Status _plan_local_exchange(int num_buckets, int pip_idx, PipelinePtr pip,
197
                                const std::map<int, int>& bucket_seq_to_instance_idx,
198
                                const std::map<int, int>& shuffle_idx_to_instance_idx);
199
    void _inherit_pipeline_properties(const DataDistribution& data_distribution,
200
                                      PipelinePtr pipe_with_source, PipelinePtr pipe_with_sink);
201
    Status _add_local_exchange(int pip_idx, int idx, int node_id, ObjectPool* pool,
202
                               PipelinePtr cur_pipe, DataDistribution data_distribution,
203
                               bool* do_local_exchange, int num_buckets,
204
                               const std::map<int, int>& bucket_seq_to_instance_idx,
205
                               const std::map<int, int>& shuffle_idx_to_instance_idx);
206
    Status _add_local_exchange_impl(int idx, ObjectPool* pool, PipelinePtr cur_pipe,
207
                                    PipelinePtr new_pip, DataDistribution data_distribution,
208
                                    bool* do_local_exchange, int num_buckets,
209
                                    const std::map<int, int>& bucket_seq_to_instance_idx,
210
                                    const std::map<int, int>& shuffle_idx_to_instance_idx);
211
212
    Status _build_pipeline_tasks(ThreadPool* thread_pool);
213
    Status _build_pipeline_tasks_for_instance(
214
            int instance_idx,
215
            const std::vector<std::shared_ptr<RuntimeProfile>>& pipeline_id_to_profile);
216
    // Close the fragment instance and return true if the caller should call
217
    // remove_pipeline_context() **after** releasing _task_mutex. This avoids
218
    // holding _task_mutex while acquiring _pipeline_map's shard lock, which
219
    // would create an ABBA deadlock with dump_pipeline_tasks().
220
    bool _close_fragment_instance();
221
    void _init_next_report_time();
222
223
    // Id of this query
224
    TUniqueId _query_id;
225
    int _fragment_id;
226
227
    ExecEnv* _exec_env = nullptr;
228
229
    std::atomic_bool _prepared = false;
230
    bool _submitted = false;
231
232
    Pipelines _pipelines;
233
    PipelineId _next_pipeline_id = 0;
234
    std::mutex _task_mutex;
235
    int _closed_tasks = 0;
236
    // After prepared, `_total_tasks` is equal to the size of `_tasks`.
237
    // When submit fail, `_total_tasks` is equal to the number of tasks submitted.
238
    std::atomic<int> _total_tasks = 0;
239
240
    std::unique_ptr<RuntimeProfile> _fragment_level_profile;
241
    // This is used by loading process to report Fragment exec status to FE, FE need fragment status to
242
    // check if the loading process is finished. And during the report, BE will send the loading message to FE,
243
    // for example the loading error, commit rows num etc.
244
    bool _is_report_success = false;
245
246
    std::unique_ptr<RuntimeState> _runtime_state;
247
248
    std::shared_ptr<QueryContext> _query_ctx;
249
250
    MonotonicStopWatch _fragment_watcher;
251
    RuntimeProfile::Counter* _prepare_timer = nullptr;
252
    RuntimeProfile::Counter* _init_context_timer = nullptr;
253
    RuntimeProfile::Counter* _build_pipelines_timer = nullptr;
254
    RuntimeProfile::Counter* _plan_local_exchanger_timer = nullptr;
255
    RuntimeProfile::Counter* _prepare_all_pipelines_timer = nullptr;
256
    RuntimeProfile::Counter* _build_tasks_timer = nullptr;
257
258
    std::function<void(RuntimeState*, Status*)> _call_back;
259
    std::atomic_bool _is_fragment_instance_closed = false;
260
261
    // 0 indicates reporting is in progress or not required
262
    std::atomic_bool _disable_period_report = true;
263
    std::atomic_uint64_t _previous_report_time = 0;
264
265
    DescriptorTbl* _desc_tbl = nullptr;
266
    int _num_instances = 1;
267
268
    int _timeout = -1;
269
    bool _use_serial_source = false;
270
271
    OperatorPtr _root_op = nullptr;
272
    //
273
    /**
274
     * Matrix stores tasks with local runtime states.
275
     * This is a [n * m] matrix. n is parallelism of pipeline engine and m is the number of pipelines.
276
     *
277
     * 2-D matrix:
278
     * +-------------------------+------------+-------+
279
     * |            | Pipeline 0 | Pipeline 1 |  ...  |
280
     * +------------+------------+------------+-------+
281
     * | Instance 0 |  task 0-0  |  task 0-1  |  ...  |
282
     * +------------+------------+------------+-------+
283
     * | Instance 1 |  task 1-0  |  task 1-1  |  ...  |
284
     * +------------+------------+------------+-------+
285
     * | ...                                          |
286
     * +--------------------------------------+-------+
287
     */
288
    std::vector<
289
            std::vector<std::pair<std::shared_ptr<PipelineTask>, std::unique_ptr<RuntimeState>>>>
290
            _tasks;
291
292
    // TODO: remove the _sink and _multi_cast_stream_sink_senders to set both
293
    // of it in pipeline task not the fragment_context
294
#ifdef __clang__
295
#pragma clang diagnostic push
296
#pragma clang diagnostic ignored "-Wshadow-field"
297
#endif
298
    DataSinkOperatorPtr _sink = nullptr;
299
#ifdef __clang__
300
#pragma clang diagnostic pop
301
#endif
302
303
    // `_dag` manage dependencies between pipelines by pipeline ID. the indices will be blocked by members
304
    std::map<PipelineId, std::vector<PipelineId>> _dag;
305
306
    // We use preorder traversal to create an operator tree. When we meet a join node, we should
307
    // build probe operator and build operator in separate pipelines. To do this, we should build
308
    // ProbeSide first, and use `_pipelines_to_build` to store which pipeline the build operator
309
    // is in, so we can build BuildSide once we complete probe side.
310
    struct pipeline_parent_map {
311
        std::map<int, std::vector<PipelinePtr>> _build_side_pipelines;
312
26.6k
        void push(int parent_node_id, PipelinePtr pipeline) {
313
26.6k
            if (!_build_side_pipelines.contains(parent_node_id)) {
314
13.2k
                _build_side_pipelines.insert({parent_node_id, {pipeline}});
315
13.4k
            } else {
316
13.4k
                _build_side_pipelines[parent_node_id].push_back(pipeline);
317
13.4k
            }
318
26.6k
        }
319
590k
        void pop(PipelinePtr& cur_pipe, int parent_node_id, int child_idx) {
320
590k
            if (!_build_side_pipelines.contains(parent_node_id)) {
321
563k
                return;
322
563k
            }
323
590k
            DCHECK(_build_side_pipelines.contains(parent_node_id));
324
26.3k
            auto& child_pipeline = _build_side_pipelines[parent_node_id];
325
26.3k
            DCHECK(child_idx < child_pipeline.size());
326
26.3k
            cur_pipe = child_pipeline[child_idx];
327
26.3k
        }
328
312k
        void clear() { _build_side_pipelines.clear(); }
329
    } _pipeline_parent_map;
330
331
    std::mutex _state_map_lock;
332
333
    // Start from -1 so all operator IDs are negative. This avoids collision with
334
    // unpaired sinks (OlapTableSink etc.) whose hardcoded dest_id=0 would otherwise
335
    // match the first operator's ID when FE-planned LocalExchangeNode is the root.
336
    int _operator_id = -1;
337
    int _sink_operator_id = -1;
338
    /**
339
     * Some states are shared by tasks in different pipeline task (e.g. local exchange , broadcast join).
340
     *
341
     * local exchange sink 0 ->                               -> local exchange source 0
342
     *                            LocalExchangeSharedState
343
     * local exchange sink 1 ->                               -> local exchange source 1
344
     *
345
     * hash join build sink 0 ->                               -> hash join build source 0
346
     *                              HashJoinSharedState
347
     * hash join build sink 1 ->                               -> hash join build source 1
348
     *
349
     * So we should keep states here.
350
     */
351
    std::map<int,
352
             std::pair<std::shared_ptr<BasicSharedState>, std::vector<std::shared_ptr<Dependency>>>>
353
            _op_id_to_shared_state;
354
355
    std::map<PipelineId, Pipeline*> _pip_id_to_pipeline;
356
    std::vector<std::unique_ptr<RuntimeFilterMgr>> _runtime_filter_mgr_map;
357
358
    // Deferred exchanger creation info for FE-planned local exchanges.
359
    // Exchanger sender count depends on the upstream pipeline's final num_tasks,
360
    // which is only known after the full plan tree is built (child operators like
361
    // serial ExchangeNode may reduce num_tasks). So we defer exchanger creation
362
    // until after _build_pipelines completes.
363
    struct DeferredExchangerInfo {
364
        std::shared_ptr<LocalExchangeSharedState> shared_state;
365
        PipelinePtr upstream_pipe;
366
        TLocalPartitionType::type partition_type;
367
        int num_partitions;
368
        int free_blocks_limit;
369
        int local_exchange_id;
370
        int sink_id;
371
    };
372
    std::vector<DeferredExchangerInfo> _deferred_exchangers;
373
    Status _create_deferred_local_exchangers();
374
    // After _build_pipelines, propagate _num_instances from FE-planned LOCAL_EXCHANGE
375
    // pipelines upward through the DAG to ancestor pipelines that inherited reduced
376
    // num_tasks from a serial operator.
377
    void _propagate_local_exchange_num_tasks();
378
379
    //Here are two types of runtime states:
380
    //    - _runtime state is at the Fragment level.
381
    //    - _task_runtime_states is at the task level, unique to each task.
382
383
    std::vector<TUniqueId> _fragment_instance_ids;
384
385
    // Total instance num running on all BEs
386
    int _total_instances = -1;
387
388
    TPipelineFragmentParams _params;
389
    int32_t _parallel_instances = 0;
390
391
    // Query cache context of this fragment, shared by the olap scan operator
392
    // and the cache source operator so both consume the same per-instance
393
    // cache decision (HIT / INCREMENTAL / MISS). Created lazily when the
394
    // fragment carries a query_cache_param. See QueryCacheRuntime.
395
    std::shared_ptr<QueryCacheRuntime> _query_cache_runtime;
396
397
    std::atomic<bool> _need_notify_close = false;
398
    // Holds the brpc ClosureGuard for async wait-close during recursive CTE rerun.
399
    // When the PFC finishes closing and is destroyed, the shared_ptr destructor fires
400
    // the ClosureGuard, which completes the brpc response to the RecCTESourceOperatorX.
401
    // Only written by listen_wait_close() from a single rerun_fragment RPC thread.
402
    std::shared_ptr<brpc::ClosureGuard> _wait_close_guard = nullptr;
403
404
    // The recursion round number for recursive CTE fragments.
405
    // Incremented each time the fragment is rebuilt via rerun_fragment(rebuild).
406
    // Used to stamp runtime filter RPCs so stale messages from old rounds are discarded.
407
    uint32_t _rec_cte_stage = 0;
408
};
409
} // namespace doris