Coverage Report

Created: 2026-07-31 00:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/operator/data_queue.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/operator/data_queue.h"
19
20
#include <glog/logging.h>
21
22
#include <algorithm>
23
#include <utility>
24
25
#include "common/thread_safety_annotations.h"
26
#include "core/block/block.h"
27
#include "exec/pipeline/dependency.h"
28
#include "fmt/format.h"
29
30
namespace doris {
31
32
11.3k
void SubQueue::try_pop(std::unique_ptr<Block>* output_block) {
33
11.3k
    LockGuard l(queue_lock);
34
11.3k
    if (!blocks.empty()) {
35
11.3k
        *output_block = std::move(blocks.front());
36
11.3k
        blocks.pop_front();
37
11.3k
        bytes_in_queue -= (*output_block)->allocated_bytes();
38
11.3k
        blocks_in_queue -= 1;
39
11.3k
        if (blocks.empty()) {
40
10.3k
            sink_dependency->set_ready();
41
10.3k
        }
42
11.3k
    }
43
11.3k
}
44
45
11.4k
bool SubQueue::try_push(std::unique_ptr<Block> block, std::atomic_uint32_t& total_counter) {
46
11.4k
    LockGuard l(queue_lock);
47
11.4k
    if (is_finished) {
48
73
        return false;
49
73
    }
50
11.3k
    total_counter++;
51
11.3k
    bytes_in_queue += block->allocated_bytes();
52
11.3k
    blocks.emplace_back(std::move(block));
53
11.3k
    blocks_in_queue += 1;
54
11.3k
    if (static_cast<int64_t>(blocks.size()) > max_blocks_in_queue.load()) {
55
1.04k
        sink_dependency->block();
56
1.04k
    }
57
11.3k
    return true;
58
11.4k
}
59
60
bool SubQueue::mark_finished(std::atomic_uint32_t& unfinished_counter,
61
16.4k
                             std::atomic_bool& all_finished) {
62
16.4k
    LockGuard l(queue_lock);
63
16.4k
    if (is_finished) {
64
8.15k
        return false;
65
8.15k
    }
66
8.27k
    is_finished = true;
67
8.27k
    if (unfinished_counter.fetch_sub(1) == 1) {
68
3.83k
        all_finished = true;
69
3.83k
    }
70
8.27k
    return true;
71
16.4k
}
72
73
8.23k
void SubQueue::clear_blocks() {
74
8.23k
    bool need_set_always_ready = false;
75
8.23k
    {
76
8.23k
        LockGuard l(queue_lock);
77
8.23k
        if (!blocks.empty()) {
78
32
            blocks.clear();
79
32
            bytes_in_queue = 0;
80
32
            blocks_in_queue = 0;
81
32
            need_set_always_ready = true;
82
32
        }
83
8.23k
    }
84
    // Notify outside of queue_lock to keep lock ordering simple.
85
8.23k
    if (need_set_always_ready) {
86
32
        sink_dependency->set_always_ready();
87
32
    }
88
8.23k
}
89
90
3.84k
DataQueue::DataQueue(int child_count) : _sub_queues(child_count), _child_count(child_count) {
91
8.29k
    for (auto& sub : _sub_queues) {
92
8.29k
        sub = std::make_unique<SubQueue>();
93
8.29k
    }
94
3.84k
    _un_finished_counter = child_count;
95
3.84k
}
96
97
4.94k
bool DataQueue::has_more_data() const {
98
4.94k
    return _cur_blocks_total_nums.load() > 0;
99
4.94k
}
100
101
5
std::string DataQueue::debug_string() const {
102
5
    return fmt::format("(is_all_finish = {}, has_data = {})", is_all_finish(), has_more_data());
103
5
}
104
105
void DataQueue::set_source_dependency(std::shared_ptr<Dependency> source_dependency)
106
3.82k
        NO_THREAD_SAFETY_ANALYSIS {
107
3.82k
    _source_dependency = std::move(source_dependency);
108
3.82k
}
109
110
8.20k
void DataQueue::set_sink_dependency(Dependency* sink_dependency, int child_idx) {
111
8.20k
    _sub_queues[child_idx]->sink_dependency = sink_dependency;
112
8.20k
}
113
114
8.25k
void DataQueue::set_max_blocks_in_sub_queue(int64_t max_blocks) {
115
21.7k
    for (auto& sub : _sub_queues) {
116
21.7k
        sub->max_blocks_in_queue = max_blocks;
117
21.7k
    }
118
8.25k
}
119
120
1
void DataQueue::set_low_memory_mode() {
121
1
    _is_low_memory_mode = true;
122
3
    for (auto& sub : _sub_queues) {
123
3
        sub->max_blocks_in_queue = 1;
124
3
    }
125
1
    clear_free_blocks();
126
1
}
127
128
11.2k
std::unique_ptr<Block> DataQueue::get_free_block(int child_idx) {
129
11.2k
    auto& sub = *_sub_queues[child_idx];
130
11.2k
    {
131
11.2k
        LockGuard l(sub.free_lock);
132
11.2k
        if (!sub.free_blocks.empty()) {
133
2.08k
            auto block = std::move(sub.free_blocks.front());
134
2.08k
            sub.free_blocks.pop_front();
135
2.08k
            return block;
136
2.08k
        }
137
11.2k
    }
138
139
9.21k
    return Block::create_unique();
140
11.2k
}
141
142
11.1k
void DataQueue::push_free_block(DataQueueBlock&& queue_block) {
143
11.1k
    if (!queue_block.block) {
144
0
        return;
145
0
    }
146
11.1k
    DCHECK(queue_block.block->rows() == 0);
147
148
11.1k
    if (!_is_low_memory_mode) {
149
11.1k
        auto& sub = *_sub_queues[queue_block.child_idx];
150
11.1k
        LockGuard l(sub.free_lock);
151
11.1k
        sub.free_blocks.emplace_back(std::move(queue_block.block));
152
11.1k
    }
153
11.1k
}
154
155
3.79k
void DataQueue::clear_free_blocks() {
156
8.23k
    for (auto& sub : _sub_queues) {
157
8.23k
        LockGuard l(sub->free_lock);
158
8.23k
        std::deque<std::unique_ptr<Block>> tmp_queue;
159
8.23k
        sub->free_blocks.swap(tmp_queue);
160
8.23k
    }
161
3.79k
}
162
163
3.79k
void DataQueue::terminate() {
164
12.0k
    for (int i = 0; i < _child_count; ++i) {
165
8.23k
        mark_finish(i);
166
8.23k
        _sub_queues[i]->clear_blocks();
167
8.23k
    }
168
3.79k
    _cur_blocks_total_nums = 0;
169
3.79k
    clear_free_blocks();
170
3.79k
    set_source_always_ready();
171
3.79k
}
172
173
11.4k
Result<DataQueueBlock> DataQueue::get_block_from_queue() {
174
11.4k
    DataQueueBlock result;
175
11.4k
    const int start_idx = (_flag_queue_idx + 1) % _child_count;
176
18.3k
    for (int offset = 0; offset < _child_count; ++offset) {
177
18.2k
        const int idx = (start_idx + offset) % _child_count;
178
18.2k
        if (_sub_queues[idx]->blocks_in_queue.load() == 0) {
179
6.89k
            continue;
180
6.89k
        }
181
182
11.3k
        auto& sub = *_sub_queues[idx];
183
11.3k
        sub.try_pop(&result.block);
184
11.3k
        if (!result.block) {
185
0
            continue;
186
0
        }
187
11.3k
        result.child_idx = idx;
188
11.3k
        _flag_queue_idx = idx;
189
11.3k
        auto old_total = _cur_blocks_total_nums.fetch_sub(1);
190
11.3k
        if (old_total == 1) {
191
9.31k
            set_source_block();
192
9.31k
        }
193
11.3k
        break;
194
11.3k
    }
195
196
    // A producer enqueues its final block before marking the child finished. Observe completion
197
    // first, then recheck queued data to avoid reporting EOS while the final block is still queued.
198
11.4k
    result.eos = is_all_finish() && !has_more_data();
199
11.4k
    return result;
200
11.4k
}
201
202
11.4k
Status DataQueue::push_block(std::unique_ptr<Block> block, int child_idx, bool eos) {
203
11.4k
    DCHECK(block || eos);
204
11.4k
    if (!block && !eos) {
205
0
        return Status::OK();
206
0
    }
207
208
11.4k
    if (block) {
209
11.4k
        auto& sub = *_sub_queues[child_idx];
210
        // total_counter is incremented inside try_push under queue_lock, only when the
211
        // block is actually enqueued. This ensures get_block_from_queue() always observes
212
        // _cur_blocks_total_nums >= 1 when it successfully pops a block, with no risk of
213
        // underflow or the need for a rollback on failure.
214
11.4k
        if (!sub.try_push(std::move(block), _cur_blocks_total_nums)) {
215
72
            return Status::EndOfFile("SubQueue already finished");
216
72
        }
217
11.4k
    }
218
219
11.4k
    if (eos) {
220
8.20k
        mark_finish(child_idx);
221
8.20k
    }
222
11.4k
    set_source_ready();
223
11.4k
    return Status::OK();
224
11.4k
}
225
226
16.4k
void DataQueue::mark_finish(int child_idx) {
227
16.4k
    auto& sub = *_sub_queues[child_idx];
228
16.4k
    if (!sub.mark_finished(_un_finished_counter, _is_all_finished)) {
229
8.15k
        return;
230
8.15k
    }
231
16.4k
}
232
233
20.7k
bool DataQueue::is_all_finish() const {
234
20.7k
    return _is_all_finished;
235
20.7k
}
236
237
11.4k
void DataQueue::set_source_ready() {
238
11.4k
    LockGuard lc(_source_lock);
239
11.4k
    if (_source_dependency) {
240
11.4k
        _source_dependency->set_ready();
241
11.4k
    }
242
11.4k
}
243
244
3.80k
void DataQueue::set_source_always_ready() {
245
3.80k
    LockGuard lc(_source_lock);
246
3.80k
    if (_source_dependency) {
247
3.80k
        _source_dependency->set_always_ready();
248
3.80k
    }
249
3.80k
}
250
251
9.31k
void DataQueue::set_source_block() {
252
    // Re-check under _source_lock to avoid blocking the source when a concurrent push
253
    // has already added new blocks (or all children have finished) since we observed
254
    // the counter drop to zero.
255
9.31k
    LockGuard lc(_source_lock);
256
9.31k
    if (_source_dependency && _cur_blocks_total_nums == 0 && !is_all_finish()) {
257
5.55k
        _source_dependency->block();
258
5.55k
    }
259
9.31k
}
260
261
} // namespace doris