Coverage Report

Created: 2026-08-07 10:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/sink/writer/async_result_writer.cpp
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
#include "exec/sink/writer/async_result_writer.h"
19
20
#include "common/status.h"
21
#include "core/block/block.h"
22
#include "core/block/materialize_block.h"
23
#include "exec/pipeline/dependency.h"
24
#include "exprs/vexpr_context.h"
25
#include "runtime/exec_env.h"
26
#include "runtime/fragment_mgr.h"
27
#include "runtime/runtime_state.h"
28
#include "runtime/workload_group/workload_group.h"
29
30
namespace doris {
31
class ObjectPool;
32
class RowDescriptor;
33
class TExpr;
34
35
AsyncResultWriter::AsyncResultWriter(const doris::VExprContextSPtrs& output_expr_ctxs,
36
                                     std::shared_ptr<Dependency> dep,
37
                                     std::shared_ptr<Dependency> fin_dep)
38
71
        : _vec_output_expr_ctxs(output_expr_ctxs), _dependency(dep), _finish_dependency(fin_dep) {}
39
40
0
Status AsyncResultWriter::sink(Block* block, bool eos) {
41
0
    auto rows = block->rows();
42
0
    std::unique_ptr<Block> add_block;
43
0
    if (rows) {
44
0
        add_block = _get_free_block(block, rows);
45
0
    }
46
47
0
    std::lock_guard l(_m);
48
    // if io task failed, just return error status to
49
    // end the query
50
0
    if (!_writer_status.ok()) {
51
0
        return _writer_status.status();
52
0
    }
53
54
0
    DCHECK(_dependency);
55
0
    if (_is_finished()) {
56
0
        _dependency->set_ready();
57
0
    }
58
0
    if (rows) {
59
0
        _memory_used_counter->update(add_block->allocated_bytes());
60
0
        _data_queue.emplace_back(std::move(add_block));
61
0
        if (!_data_queue_is_available() && !_is_finished()) {
62
0
            _dependency->block();
63
0
        }
64
0
    }
65
    // in 'process block' we check _eos first and _data_queue second so here
66
    // in the lock. must modify the _eos after change _data_queue to make sure
67
    // not lead the logic error in multi thread
68
0
    _eos = eos;
69
70
0
    _cv.notify_one();
71
0
    return Status::OK();
72
0
}
73
74
0
std::unique_ptr<Block> AsyncResultWriter::_get_block_from_queue() {
75
0
    std::lock_guard l(_m);
76
0
    DCHECK(!_data_queue.empty());
77
0
    auto block = std::move(_data_queue.front());
78
0
    _data_queue.pop_front();
79
0
    DCHECK(_dependency);
80
0
    if (_data_queue_is_available()) {
81
0
        _dependency->set_ready();
82
0
    }
83
0
    _memory_used_counter->update(-block->allocated_bytes());
84
0
    return block;
85
0
}
86
87
0
Status AsyncResultWriter::start_writer(RuntimeState* state, RuntimeProfile* operator_profile) {
88
    // Attention!!!
89
    // AsyncResultWriter::open is called asynchronously,
90
    // so we need to setupt the operator_profile and memory counter here,
91
    // or else the counter can be nullptr when AsyncResultWriter::sink is called.
92
0
    _operator_profile = operator_profile;
93
0
    DCHECK(_operator_profile->get_child("CommonCounters") != nullptr);
94
0
    _memory_used_counter =
95
0
            _operator_profile->get_child("CommonCounters")->get_counter("MemoryUsage");
96
0
    DCHECK(_memory_used_counter != nullptr);
97
    // Should set to false here, to
98
0
    DCHECK(_finish_dependency);
99
0
    _finish_dependency->block();
100
    // This is a async thread, should lock the task ctx, to make sure runtimestate and operator_profile
101
    // not deconstructed before the thread exit.
102
0
    auto task_ctx = state->get_task_execution_context();
103
0
    RETURN_IF_ERROR(ExecEnv::GetInstance()->fragment_mgr()->get_thread_pool()->submit_func(
104
0
            [this, state, operator_profile, task_ctx]() {
105
0
                SCOPED_ATTACH_TASK(state);
106
0
                auto task_lock = task_ctx.lock();
107
0
                if (task_lock == nullptr) {
108
0
                    return;
109
0
                }
110
0
                this->process_block(state, operator_profile);
111
0
                task_lock.reset();
112
0
            }));
113
0
    return Status::OK();
114
0
}
115
116
0
void AsyncResultWriter::process_block(RuntimeState* state, RuntimeProfile* operator_profile) {
117
0
    if (auto status = open(state, operator_profile); !status.ok()) {
118
0
        force_close(status);
119
0
    }
120
121
0
    if (state && state->get_query_ctx() && state->get_query_ctx()->workload_group()) {
122
0
        if (auto cg_ctl_sptr =
123
0
                    state->get_query_ctx()->workload_group()->get_cgroup_cpu_ctl_wptr().lock()) {
124
0
            Status ret = cg_ctl_sptr->add_thread_to_cgroup();
125
0
            if (ret.ok()) {
126
0
                std::string wg_tname =
127
0
                        "asyc_wr_" + state->get_query_ctx()->workload_group()->name();
128
0
                Thread::set_self_name(wg_tname);
129
0
            }
130
0
        }
131
0
    }
132
133
0
    DCHECK(_dependency);
134
0
    while (_writer_status.ok()) {
135
0
        ThreadCpuStopWatch cpu_time_stop_watch;
136
0
        cpu_time_stop_watch.start();
137
0
        Defer defer {[&]() {
138
0
            if (state && state->get_query_ctx()) {
139
0
                state->get_query_ctx()->resource_ctx()->cpu_context()->update_cpu_cost_ms(
140
0
                        cpu_time_stop_watch.elapsed_time());
141
0
            }
142
0
        }};
143
144
        //1) wait scan operator write data
145
0
        {
146
0
            std::unique_lock l(_m);
147
            // When the query is cancelled, _writer_status may be set to error status in force_close method.
148
            // When the BE process is exit gracefully, the fragment mgr's thread pool will be shutdown,
149
            // and the async thread will be exit.
150
0
            while (!_eos && _data_queue.empty() && _writer_status.ok() &&
151
0
                   !ExecEnv::GetInstance()->fragment_mgr()->shutting_down()) {
152
                // Add 1s to check to avoid lost signal
153
0
                _cv.wait_for(l, std::chrono::seconds(1));
154
0
            }
155
            // If writer status is not ok, then we should not change its status to avoid lost the actual error status.
156
0
            if (ExecEnv::GetInstance()->fragment_mgr()->shutting_down() && _writer_status.ok()) {
157
0
                _writer_status.update(Status::InternalError<false>("FragmentMgr is shutting down"));
158
0
            }
159
160
            //check if eos or writer error
161
0
            if ((_eos && _data_queue.empty()) || !_writer_status.ok()) {
162
0
                _data_queue.clear();
163
0
                break;
164
0
            }
165
0
        }
166
167
        //2) get the block from  data queue and write to downstream
168
0
        auto block = _get_block_from_queue();
169
0
        auto status = write(state, *block);
170
0
        if (!status.ok()) [[unlikely]] {
171
0
            std::unique_lock l(_m);
172
0
            _writer_status.update(status);
173
0
            if (_is_finished()) {
174
0
                _dependency->set_ready();
175
0
            }
176
0
            break;
177
0
        }
178
179
0
        _return_free_block(std::move(block));
180
0
    }
181
182
0
    bool need_finish = false;
183
0
    {
184
        // If the last block is sent successfuly, then call finish to clear the buffer or commit
185
        // transactions.
186
        // Using lock to make sure the writer status is not modified
187
        // There is a unique ptr err_msg in Status, if it is modified, the unique ptr
188
        // maybe released. And it will core because use after free.
189
0
        std::lock_guard l(_m);
190
0
        if (_writer_status.ok() && _eos) {
191
0
            need_finish = true;
192
0
        }
193
0
    }
194
    // eos only means the last block is input to the queue and there is no more block to be added,
195
    // it is not sure that the block is written to stream.
196
0
    if (need_finish) {
197
        // Should not call finish in lock because it may hang, and it will lock _m too long.
198
        // And get_writer_status will also need this lock, it will block pipeline exec thread.
199
0
        Status st = finish(state);
200
0
        _writer_status.update(st);
201
0
    }
202
0
    Status st = Status::OK();
203
0
    { st = _writer_status.status(); }
204
205
0
    Status close_st = close(st);
206
0
    {
207
        // If it is already failed before, then not update the write status so that we could get
208
        // the real reason.
209
0
        std::lock_guard l(_m);
210
0
        if (_writer_status.ok()) {
211
0
            _writer_status.update(close_st);
212
0
        }
213
0
    }
214
    // should set _finish_dependency first, as close function maybe blocked by wait_close of execution_timeout
215
0
    _set_ready_to_finish();
216
0
}
217
218
0
void AsyncResultWriter::_set_ready_to_finish() {
219
0
    DCHECK(_finish_dependency);
220
0
    _finish_dependency->set_ready();
221
0
}
222
223
74
Status AsyncResultWriter::_projection_block(doris::Block& input_block, doris::Block* output_block) {
224
74
    Status status = Status::OK();
225
74
    if (input_block.rows() == 0) {
226
0
        return status;
227
0
    }
228
74
    RETURN_IF_ERROR(VExprContext::get_output_block_after_execute_exprs(_vec_output_expr_ctxs,
229
74
                                                                       input_block, output_block));
230
74
    materialize_block_inplace(*output_block);
231
74
    return status;
232
74
}
233
234
0
void AsyncResultWriter::force_close(Status s) {
235
0
    std::lock_guard l(_m);
236
0
    _writer_status.update(s);
237
0
    DCHECK(_dependency);
238
0
    if (_is_finished()) {
239
0
        _dependency->set_ready();
240
0
    }
241
0
    _cv.notify_one();
242
0
}
243
244
0
void AsyncResultWriter::_return_free_block(std::unique_ptr<Block> b) {
245
0
    if (_low_memory_mode) {
246
0
        return;
247
0
    }
248
249
0
    const auto allocated_bytes = b->allocated_bytes();
250
0
    if (_free_blocks.enqueue(std::move(b))) {
251
0
        _memory_used_counter->update(allocated_bytes);
252
0
    }
253
0
}
254
255
0
std::unique_ptr<Block> AsyncResultWriter::_get_free_block(doris::Block* block, size_t rows) {
256
0
    std::unique_ptr<Block> b;
257
0
    if (!_free_blocks.try_dequeue(b)) {
258
0
        b = block->create_same_struct_block(rows, true);
259
0
    } else {
260
0
        _memory_used_counter->update(-b->allocated_bytes());
261
0
    }
262
0
    b->swap(*block);
263
0
    return b;
264
0
}
265
266
template <typename T>
267
void clear_blocks(moodycamel::ConcurrentQueue<T>& blocks,
268
                  RuntimeProfile::Counter* memory_used_counter = nullptr);
269
0
void AsyncResultWriter::set_low_memory_mode() {
270
0
    _low_memory_mode = true;
271
0
    clear_blocks(_free_blocks, _memory_used_counter);
272
0
}
273
} // namespace doris