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 | 8.31k | void SubQueue::try_pop(std::unique_ptr<Block>* output_block) { |
33 | 8.31k | LockGuard l(queue_lock); |
34 | 8.31k | if (!blocks.empty()) { |
35 | 8.31k | *output_block = std::move(blocks.front()); |
36 | 8.31k | blocks.pop_front(); |
37 | 8.31k | bytes_in_queue -= (*output_block)->allocated_bytes(); |
38 | 8.31k | blocks_in_queue -= 1; |
39 | 8.31k | if (blocks.empty()) { |
40 | 7.29k | sink_dependency->set_ready(); |
41 | 7.29k | } |
42 | 8.31k | } |
43 | 8.31k | } |
44 | | |
45 | 8.39k | bool SubQueue::try_push(std::unique_ptr<Block> block, std::atomic_uint32_t& total_counter) { |
46 | 8.39k | LockGuard l(queue_lock); |
47 | 8.39k | if (is_finished) { |
48 | 41 | return false; |
49 | 41 | } |
50 | 8.35k | total_counter++; |
51 | 8.35k | bytes_in_queue += block->allocated_bytes(); |
52 | 8.35k | blocks.emplace_back(std::move(block)); |
53 | 8.35k | blocks_in_queue += 1; |
54 | 8.35k | if (static_cast<int64_t>(blocks.size()) > max_blocks_in_queue.load()) { |
55 | 1.03k | sink_dependency->block(); |
56 | 1.03k | } |
57 | 8.35k | return true; |
58 | 8.39k | } |
59 | | |
60 | | bool SubQueue::mark_finished(std::atomic_uint32_t& unfinished_counter, |
61 | 11.0k | std::atomic_bool& all_finished) { |
62 | 11.0k | LockGuard l(queue_lock); |
63 | 11.0k | if (is_finished) { |
64 | 5.47k | return false; |
65 | 5.47k | } |
66 | 5.55k | is_finished = true; |
67 | 5.55k | if (unfinished_counter.fetch_sub(1) == 1) { |
68 | 2.65k | all_finished = true; |
69 | 2.65k | } |
70 | 5.55k | return true; |
71 | 11.0k | } |
72 | | |
73 | 5.52k | void SubQueue::clear_blocks() { |
74 | 5.52k | bool need_set_always_ready = false; |
75 | 5.52k | { |
76 | 5.52k | LockGuard l(queue_lock); |
77 | 5.52k | if (!blocks.empty()) { |
78 | 25 | blocks.clear(); |
79 | 25 | bytes_in_queue = 0; |
80 | 25 | blocks_in_queue = 0; |
81 | 25 | need_set_always_ready = true; |
82 | 25 | } |
83 | 5.52k | } |
84 | | // Notify outside of queue_lock to keep lock ordering simple. |
85 | 5.52k | if (need_set_always_ready) { |
86 | 25 | sink_dependency->set_always_ready(); |
87 | 25 | } |
88 | 5.52k | } |
89 | | |
90 | 2.65k | DataQueue::DataQueue(int child_count) : _sub_queues(child_count), _child_count(child_count) { |
91 | 5.57k | for (auto& sub : _sub_queues) { |
92 | 5.57k | sub = std::make_unique<SubQueue>(); |
93 | 5.57k | } |
94 | 2.65k | _un_finished_counter = child_count; |
95 | 2.65k | } |
96 | | |
97 | 3.59k | bool DataQueue::has_more_data() const { |
98 | 3.59k | return _cur_blocks_total_nums.load() > 0; |
99 | 3.59k | } |
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 | 2.65k | NO_THREAD_SAFETY_ANALYSIS { |
107 | 2.65k | _source_dependency = std::move(source_dependency); |
108 | 2.65k | } |
109 | | |
110 | 5.55k | void DataQueue::set_sink_dependency(Dependency* sink_dependency, int child_idx) { |
111 | 5.55k | _sub_queues[child_idx]->sink_dependency = sink_dependency; |
112 | 5.55k | } |
113 | | |
114 | 5.53k | void DataQueue::set_max_blocks_in_sub_queue(int64_t max_blocks) { |
115 | 12.6k | for (auto& sub : _sub_queues) { |
116 | 12.6k | sub->max_blocks_in_queue = max_blocks; |
117 | 12.6k | } |
118 | 5.53k | } |
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 | 8.22k | std::unique_ptr<Block> DataQueue::get_free_block(int child_idx) { |
129 | 8.22k | auto& sub = *_sub_queues[child_idx]; |
130 | 8.22k | { |
131 | 8.22k | LockGuard l(sub.free_lock); |
132 | 8.22k | if (!sub.free_blocks.empty()) { |
133 | 1.73k | auto block = std::move(sub.free_blocks.front()); |
134 | 1.73k | sub.free_blocks.pop_front(); |
135 | 1.73k | return block; |
136 | 1.73k | } |
137 | 8.22k | } |
138 | | |
139 | 6.48k | return Block::create_unique(); |
140 | 8.22k | } |
141 | | |
142 | 8.15k | void DataQueue::push_free_block(DataQueueBlock&& queue_block) { |
143 | 8.15k | if (!queue_block.block) { |
144 | 0 | return; |
145 | 0 | } |
146 | 8.15k | DCHECK(queue_block.block->rows() == 0); |
147 | | |
148 | 8.15k | if (!_is_low_memory_mode) { |
149 | 8.15k | auto& sub = *_sub_queues[queue_block.child_idx]; |
150 | 8.15k | LockGuard l(sub.free_lock); |
151 | 8.15k | sub.free_blocks.emplace_back(std::move(queue_block.block)); |
152 | 8.15k | } |
153 | 8.15k | } |
154 | | |
155 | 2.63k | void DataQueue::clear_free_blocks() { |
156 | 5.52k | for (auto& sub : _sub_queues) { |
157 | 5.52k | LockGuard l(sub->free_lock); |
158 | 5.52k | std::deque<std::unique_ptr<Block>> tmp_queue; |
159 | 5.52k | sub->free_blocks.swap(tmp_queue); |
160 | 5.52k | } |
161 | 2.63k | } |
162 | | |
163 | 2.63k | void DataQueue::terminate() { |
164 | 8.15k | for (int i = 0; i < _child_count; ++i) { |
165 | 5.52k | mark_finish(i); |
166 | 5.52k | _sub_queues[i]->clear_blocks(); |
167 | 5.52k | } |
168 | 2.63k | _cur_blocks_total_nums = 0; |
169 | 2.63k | clear_free_blocks(); |
170 | 2.63k | set_source_always_ready(); |
171 | 2.63k | } |
172 | | |
173 | 123k | Result<DataQueueBlock> DataQueue::get_block_from_queue() { |
174 | 123k | DataQueueBlock result; |
175 | 123k | const int start_idx = (_flag_queue_idx + 1) % _child_count; |
176 | 472k | for (int offset = 0; offset < _child_count; ++offset) { |
177 | 357k | const int idx = (start_idx + offset) % _child_count; |
178 | 357k | if (_sub_queues[idx]->blocks_in_queue.load() == 0) { |
179 | 349k | continue; |
180 | 349k | } |
181 | | |
182 | 8.31k | auto& sub = *_sub_queues[idx]; |
183 | 8.31k | sub.try_pop(&result.block); |
184 | 8.31k | if (!result.block) { |
185 | 0 | continue; |
186 | 0 | } |
187 | 8.31k | result.child_idx = idx; |
188 | 8.31k | _flag_queue_idx = idx; |
189 | 8.31k | auto old_total = _cur_blocks_total_nums.fetch_sub(1); |
190 | 8.31k | if (old_total == 1) { |
191 | 6.43k | set_source_block(); |
192 | 6.43k | } |
193 | 8.31k | break; |
194 | 8.31k | } |
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 | 123k | result.eos = is_all_finish() && !has_more_data(); |
199 | 123k | return result; |
200 | 123k | } |
201 | | |
202 | 8.40k | Status DataQueue::push_block(std::unique_ptr<Block> block, int child_idx, bool eos) { |
203 | 8.40k | DCHECK(block || eos); |
204 | 8.40k | if (!block && !eos) { |
205 | 0 | return Status::OK(); |
206 | 0 | } |
207 | | |
208 | 8.40k | if (block) { |
209 | 8.38k | 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 | 8.38k | if (!sub.try_push(std::move(block), _cur_blocks_total_nums)) { |
215 | 40 | return Status::EndOfFile("SubQueue already finished"); |
216 | 40 | } |
217 | 8.38k | } |
218 | | |
219 | 8.36k | if (eos) { |
220 | 5.50k | mark_finish(child_idx); |
221 | 5.50k | } |
222 | 8.36k | set_source_ready(); |
223 | 8.36k | return Status::OK(); |
224 | 8.40k | } |
225 | | |
226 | 11.0k | void DataQueue::mark_finish(int child_idx) { |
227 | 11.0k | auto& sub = *_sub_queues[child_idx]; |
228 | 11.0k | if (!sub.mark_finished(_un_finished_counter, _is_all_finished)) { |
229 | 5.47k | return; |
230 | 5.47k | } |
231 | 11.0k | } |
232 | | |
233 | 130k | bool DataQueue::is_all_finish() const { |
234 | 130k | return _is_all_finished; |
235 | 130k | } |
236 | | |
237 | 8.37k | void DataQueue::set_source_ready() { |
238 | 8.37k | LockGuard lc(_source_lock); |
239 | 8.37k | if (_source_dependency) { |
240 | 8.37k | _source_dependency->set_ready(); |
241 | 8.37k | } |
242 | 8.37k | } |
243 | | |
244 | 2.63k | void DataQueue::set_source_always_ready() { |
245 | 2.63k | LockGuard lc(_source_lock); |
246 | 2.63k | if (_source_dependency) { |
247 | 2.63k | _source_dependency->set_always_ready(); |
248 | 2.63k | } |
249 | 2.63k | } |
250 | | |
251 | 6.43k | 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 | 6.43k | LockGuard lc(_source_lock); |
256 | 6.43k | if (_source_dependency && _cur_blocks_total_nums == 0 && !is_all_finish()) { |
257 | 3.83k | _source_dependency->block(); |
258 | 3.83k | } |
259 | 6.43k | } |
260 | | |
261 | | } // namespace doris |