Coverage Report

Created: 2026-05-23 05:15

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
29
namespace doris {
30
31
10.6k
void SubQueue::try_pop(std::unique_ptr<Block>* output_block) {
32
10.6k
    LockGuard l(queue_lock);
33
10.6k
    if (!blocks.empty()) {
34
6.07k
        *output_block = std::move(blocks.front());
35
6.07k
        blocks.pop_front();
36
6.07k
        bytes_in_queue -= (*output_block)->allocated_bytes();
37
6.07k
        blocks_in_queue -= 1;
38
6.07k
        if (blocks.empty()) {
39
6.01k
            sink_dependency->set_ready();
40
6.01k
        }
41
6.07k
    }
42
10.6k
}
43
44
6.09k
bool SubQueue::try_push(std::unique_ptr<Block> block, std::atomic_uint32_t& total_counter) {
45
6.09k
    LockGuard l(queue_lock);
46
6.09k
    if (is_finished) {
47
4
        return false;
48
4
    }
49
6.08k
    total_counter++;
50
6.08k
    bytes_in_queue += block->allocated_bytes();
51
6.08k
    blocks.emplace_back(std::move(block));
52
6.08k
    blocks_in_queue += 1;
53
6.08k
    if (static_cast<int64_t>(blocks.size()) > max_blocks_in_queue.load()) {
54
71
        sink_dependency->block();
55
71
    }
56
6.08k
    return true;
57
6.09k
}
58
59
bool SubQueue::mark_finished(std::atomic_uint32_t& unfinished_counter,
60
11.8k
                             std::atomic_bool& all_finished) {
61
11.8k
    LockGuard l(queue_lock);
62
11.8k
    if (is_finished) {
63
5.90k
        return false;
64
5.90k
    }
65
5.95k
    is_finished = true;
66
5.95k
    if (unfinished_counter.fetch_sub(1) == 1) {
67
2.85k
        all_finished = true;
68
2.85k
    }
69
5.95k
    return true;
70
11.8k
}
71
72
5.92k
void SubQueue::clear_blocks() {
73
5.92k
    bool need_set_always_ready = false;
74
5.92k
    {
75
5.92k
        LockGuard l(queue_lock);
76
5.92k
        if (!blocks.empty()) {
77
11
            blocks.clear();
78
11
            bytes_in_queue = 0;
79
11
            blocks_in_queue = 0;
80
11
            need_set_always_ready = true;
81
11
        }
82
5.92k
    }
83
    // Notify outside of queue_lock to keep lock ordering simple.
84
5.92k
    if (need_set_always_ready) {
85
11
        sink_dependency->set_always_ready();
86
11
    }
87
5.92k
}
88
89
2.85k
DataQueue::DataQueue(int child_count) : _sub_queues(child_count), _child_count(child_count) {
90
5.97k
    for (auto& sub : _sub_queues) {
91
5.97k
        sub = std::make_unique<SubQueue>();
92
5.97k
    }
93
2.85k
    _un_finished_counter = child_count;
94
2.85k
}
95
96
3
bool DataQueue::has_more_data() const {
97
3
    return _cur_blocks_total_nums.load() > 0;
98
3
}
99
100
void DataQueue::set_source_dependency(std::shared_ptr<Dependency> source_dependency)
101
2.84k
        NO_THREAD_SAFETY_ANALYSIS {
102
2.84k
    _source_dependency = std::move(source_dependency);
103
2.84k
}
104
105
5.95k
void DataQueue::set_sink_dependency(Dependency* sink_dependency, int child_idx) {
106
5.95k
    _sub_queues[child_idx]->sink_dependency = sink_dependency;
107
5.95k
}
108
109
5.94k
void DataQueue::set_max_blocks_in_sub_queue(int64_t max_blocks) {
110
13.6k
    for (auto& sub : _sub_queues) {
111
13.6k
        sub->max_blocks_in_queue = max_blocks;
112
13.6k
    }
113
5.94k
}
114
115
1
void DataQueue::set_low_memory_mode() {
116
1
    _is_low_memory_mode = true;
117
3
    for (auto& sub : _sub_queues) {
118
3
        sub->max_blocks_in_queue = 1;
119
3
    }
120
1
    clear_free_blocks();
121
1
}
122
123
5.92k
std::unique_ptr<Block> DataQueue::get_free_block(int child_idx) {
124
5.92k
    auto& sub = *_sub_queues[child_idx];
125
5.92k
    {
126
5.92k
        LockGuard l(sub.free_lock);
127
5.92k
        if (!sub.free_blocks.empty()) {
128
5
            auto block = std::move(sub.free_blocks.front());
129
5
            sub.free_blocks.pop_front();
130
5
            return block;
131
5
        }
132
5.92k
    }
133
134
5.92k
    return Block::create_unique();
135
5.92k
}
136
137
5.91k
void DataQueue::push_free_block(std::unique_ptr<Block> block, int child_idx) {
138
5.91k
    DCHECK(block->rows() == 0);
139
140
5.91k
    if (!_is_low_memory_mode) {
141
5.91k
        auto& sub = *_sub_queues[child_idx];
142
5.91k
        LockGuard l(sub.free_lock);
143
5.91k
        sub.free_blocks.emplace_back(std::move(block));
144
5.91k
    }
145
5.91k
}
146
147
2.82k
void DataQueue::clear_free_blocks() {
148
5.93k
    for (auto& sub : _sub_queues) {
149
5.93k
        LockGuard l(sub->free_lock);
150
5.93k
        std::deque<std::unique_ptr<Block>> tmp_queue;
151
5.93k
        sub->free_blocks.swap(tmp_queue);
152
5.93k
    }
153
2.82k
}
154
155
2.82k
void DataQueue::terminate() {
156
8.75k
    for (int i = 0; i < _child_count; ++i) {
157
5.92k
        set_finish(i);
158
5.92k
        _sub_queues[i]->clear_blocks();
159
5.92k
    }
160
2.82k
    clear_free_blocks();
161
2.82k
}
162
163
//check which queue have data, and save the idx in _flag_queue_idx,
164
//so next loop, will check the record idx + 1 first
165
//maybe it's useful with many queue, others maybe always 0
166
16.2k
bool DataQueue::remaining_has_data() {
167
16.2k
    int count = _child_count;
168
40.5k
    while (--count >= 0) {
169
30.3k
        _flag_queue_idx++;
170
30.3k
        if (_flag_queue_idx == _child_count) {
171
13.2k
            _flag_queue_idx = 0;
172
13.2k
        }
173
30.3k
        if (_sub_queues[_flag_queue_idx]->blocks_in_queue.load() > 0) {
174
6.06k
            return true;
175
6.06k
        }
176
30.3k
    }
177
10.2k
    return false;
178
16.2k
}
179
180
//the _flag_queue_idx indicate which queue has data, and in check can_read
181
//will be set idx in remaining_has_data function
182
10.6k
Status DataQueue::get_block_from_queue(std::unique_ptr<Block>* output_block, int* child_idx) {
183
10.6k
    const int idx = _flag_queue_idx;
184
10.6k
    auto& sub = *_sub_queues[idx];
185
186
10.6k
    sub.try_pop(output_block);
187
10.6k
    if (*output_block) {
188
6.07k
        if (child_idx) {
189
6.07k
            *child_idx = idx;
190
6.07k
        }
191
6.07k
        auto old_total = _cur_blocks_total_nums.fetch_sub(1);
192
6.07k
        if (old_total == 1) {
193
5.63k
            set_source_block();
194
5.63k
        }
195
6.07k
    }
196
10.6k
    return Status::OK();
197
10.6k
}
198
199
6.07k
Status DataQueue::push_block(std::unique_ptr<Block> block, int child_idx) {
200
6.07k
    if (!block) {
201
0
        return Status::OK();
202
0
    }
203
6.07k
    auto& sub = *_sub_queues[child_idx];
204
    // total_counter is incremented inside try_push under queue_lock, only when the
205
    // block is actually enqueued. This ensures get_block_from_queue() always observes
206
    // _cur_blocks_total_nums >= 1 when it successfully pops a block, with no risk of
207
    // underflow or the need for a rollback on failure.
208
6.07k
    if (!sub.try_push(std::move(block), _cur_blocks_total_nums)) {
209
3
        return Status::EndOfFile("SubQueue already finished");
210
3
    }
211
6.07k
    set_source_ready();
212
6.07k
    return Status::OK();
213
6.07k
}
214
215
11.8k
void DataQueue::set_finish(int child_idx) {
216
11.8k
    auto& sub = *_sub_queues[child_idx];
217
11.8k
    if (!sub.mark_finished(_un_finished_counter, _is_all_finished)) {
218
5.90k
        return;
219
5.90k
    }
220
5.95k
    set_source_ready();
221
5.95k
}
222
223
13.0k
bool DataQueue::is_all_finish() {
224
13.0k
    return _is_all_finished;
225
13.0k
}
226
227
12.0k
void DataQueue::set_source_ready() {
228
12.0k
    LockGuard lc(_source_lock);
229
12.0k
    if (_source_dependency) {
230
12.0k
        _source_dependency->set_ready();
231
12.0k
    }
232
12.0k
}
233
234
5.63k
void DataQueue::set_source_block() {
235
    // Re-check under _source_lock to avoid blocking the source when a concurrent push
236
    // has already added new blocks (or all children have finished) since we observed
237
    // the counter drop to zero.
238
5.63k
    LockGuard lc(_source_lock);
239
5.63k
    if (_source_dependency && _cur_blocks_total_nums == 0 && !is_all_finish()) {
240
2.82k
        _source_dependency->block();
241
2.82k
    }
242
5.63k
}
243
244
} // namespace doris