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 "load/channel/load_stream.h" |
19 | | |
20 | | #include <errno.h> |
21 | | |
22 | | #include <brpc/stream.h> |
23 | | #include <bthread/bthread.h> |
24 | | #include <bthread/condition_variable.h> |
25 | | #include <bthread/mutex.h> |
26 | | |
27 | | #include <memory> |
28 | | #include <sstream> |
29 | | |
30 | | #include "bvar/bvar.h" |
31 | | #include "cloud/config.h" |
32 | | #include "common/signal_handler.h" |
33 | | #include "load/channel/load_channel.h" |
34 | | #include "load/channel/load_stream_mgr.h" |
35 | | #include "load/channel/load_stream_writer.h" |
36 | | #include "load/delta_writer/delta_writer.h" |
37 | | #include "runtime/exec_env.h" |
38 | | #include "runtime/fragment_mgr.h" |
39 | | #include "runtime/runtime_profile.h" |
40 | | #include "runtime/workload_group/workload_group_manager.h" |
41 | | #include "storage/rowset/rowset_factory.h" |
42 | | #include "storage/rowset/rowset_meta.h" |
43 | | #include "storage/storage_engine.h" |
44 | | #include "storage/tablet/tablet.h" |
45 | | #include "storage/tablet/tablet_fwd.h" |
46 | | #include "storage/tablet/tablet_manager.h" |
47 | | #include "storage/tablet/tablet_schema.h" |
48 | | #include "storage/tablet_info.h" |
49 | | #include "util/debug_points.h" |
50 | | #include "util/defer_op.h" |
51 | | #include "util/thrift_util.h" |
52 | | #include "util/uid_util.h" |
53 | | |
54 | | #define UNKNOWN_ID_FOR_TEST 0x7c00 |
55 | | |
56 | | namespace doris { |
57 | | |
58 | | bvar::Adder<int64_t> g_load_stream_cnt("load_stream_count"); |
59 | | bvar::LatencyRecorder g_load_stream_flush_wait_ms("load_stream_flush_wait_ms"); |
60 | | bvar::Adder<int> g_load_stream_flush_running_threads("load_stream_flush_wait_threads"); |
61 | | |
62 | | TabletStream::TabletStream(const PUniqueId& load_id, int64_t id, int64_t txn_id, |
63 | | LoadStreamMgr* load_stream_mgr, RuntimeProfile* profile) |
64 | 15 | : _id(id), |
65 | 15 | _next_segid(0), |
66 | 15 | _load_id(load_id), |
67 | 15 | _txn_id(txn_id), |
68 | 15 | _load_stream_mgr(load_stream_mgr) { |
69 | 15 | load_stream_mgr->create_token(_flush_token); |
70 | 15 | _profile = profile->create_child(fmt::format("TabletStream {}", id), true, true); |
71 | 15 | _append_data_timer = ADD_TIMER(_profile, "AppendDataTime"); |
72 | 15 | _add_segment_timer = ADD_TIMER(_profile, "AddSegmentTime"); |
73 | 15 | _close_wait_timer = ADD_TIMER(_profile, "CloseWaitTime"); |
74 | 15 | } |
75 | | |
76 | 10 | inline std::ostream& operator<<(std::ostream& ostr, const TabletStream& tablet_stream) { |
77 | 10 | ostr << "load_id=" << print_id(tablet_stream._load_id) << ", txn_id=" << tablet_stream._txn_id |
78 | 10 | << ", tablet_id=" << tablet_stream._id << ", status=" << tablet_stream._status.status(); |
79 | 10 | return ostr; |
80 | 10 | } |
81 | | |
82 | 26 | Status TabletStream::_wait_for_task_slot(size_t max_tasks, int64_t timeout_ms) { |
83 | 26 | DCHECK_GT(timeout_ms, 0); |
84 | 26 | std::unique_lock<bthread::Mutex> lock(_flush_task_lock); |
85 | | |
86 | 26 | while (_pending_flush_tasks >= max_tasks) { |
87 | 0 | int ret = _flush_task_cv.wait_for(lock, timeout_ms * 1000); |
88 | |
|
89 | 0 | if (ret == ETIMEDOUT) { |
90 | 0 | return Status::Error<true>( |
91 | 0 | ErrorCode::INTERNAL_ERROR, |
92 | 0 | "wait flush token back pressure time is more than " |
93 | 0 | "load_stream_max_wait_flush_token_time {}, pending_flush_tasks={}, " |
94 | 0 | "flush_token_max_tasks={}", |
95 | 0 | timeout_ms, _pending_flush_tasks, max_tasks); |
96 | 0 | } |
97 | 0 | if (ret != 0) { |
98 | 0 | return Status::Error<true>(ErrorCode::INTERNAL_ERROR, |
99 | 0 | "wait flush task slot failed, ret={}", ret); |
100 | 0 | } |
101 | 0 | } |
102 | 26 | ++_pending_flush_tasks; |
103 | 26 | return Status::OK(); |
104 | 26 | } |
105 | | |
106 | 26 | void TabletStream::_release_task_slot() { |
107 | 26 | DCHECK_GT(_pending_flush_tasks, 0); |
108 | | |
109 | 26 | std::lock_guard<bthread::Mutex> lock(_flush_task_lock); |
110 | 26 | --_pending_flush_tasks; |
111 | 26 | _flush_task_cv.notify_one(); |
112 | 26 | } |
113 | | |
114 | | Status TabletStream::init(std::shared_ptr<OlapTableSchemaParam> schema, int64_t index_id, |
115 | 15 | int64_t partition_id) { |
116 | 15 | WriteRequest req { |
117 | 15 | .tablet_id = _id, |
118 | 15 | .txn_id = _txn_id, |
119 | 15 | .index_id = index_id, |
120 | 15 | .partition_id = partition_id, |
121 | 15 | .load_id = _load_id, |
122 | 15 | .table_schema_param = schema, |
123 | | // TODO(plat1ko): write_file_cache |
124 | 15 | .storage_vault_id {}, |
125 | 15 | }; |
126 | | |
127 | 15 | _load_stream_writer = std::make_shared<LoadStreamWriter>(&req, _profile); |
128 | 15 | DBUG_EXECUTE_IF("TabletStream.init.uninited_writer", { |
129 | 15 | _status.update(Status::Uninitialized("fault injection")); |
130 | 15 | return _status.status(); |
131 | 15 | }); |
132 | 15 | _status.update(_load_stream_writer->init()); |
133 | 15 | if (!_status.ok()) { |
134 | 1 | LOG(INFO) << "failed to init rowset builder due to " << *this; |
135 | 1 | } |
136 | 15 | return _status.status(); |
137 | 15 | } |
138 | | |
139 | 27 | Status TabletStream::append_data(const PStreamHeader& header, butil::IOBuf* data) { |
140 | 27 | if (!_status.ok()) { |
141 | 1 | return _status.status(); |
142 | 1 | } |
143 | | |
144 | | // dispatch add_segment request |
145 | 26 | if (header.opcode() == PStreamHeader::ADD_SEGMENT) { |
146 | 0 | return add_segment(header, data); |
147 | 0 | } |
148 | | |
149 | 26 | SCOPED_TIMER(_append_data_timer); |
150 | | |
151 | 26 | int64_t src_id = header.src_id(); |
152 | 26 | uint32_t segid = header.segment_id(); |
153 | | // Ensure there are enough space and mapping are built. |
154 | 26 | SegIdMapping* mapping = nullptr; |
155 | 26 | { |
156 | 26 | std::lock_guard lock_guard(_lock); |
157 | 26 | if (!_segids_mapping.contains(src_id)) { |
158 | 14 | _segids_mapping[src_id] = std::make_unique<SegIdMapping>(); |
159 | 14 | } |
160 | 26 | mapping = _segids_mapping[src_id].get(); |
161 | 26 | } |
162 | 26 | if (segid + 1 > mapping->size()) { |
163 | | // TODO: Each sender lock is enough. |
164 | 15 | std::lock_guard lock_guard(_lock); |
165 | 15 | ssize_t origin_size = mapping->size(); |
166 | 15 | if (segid + 1 > origin_size) { |
167 | 15 | mapping->resize(segid + 1, std::numeric_limits<uint32_t>::max()); |
168 | 39 | for (size_t index = origin_size; index <= segid; index++) { |
169 | 24 | mapping->at(index) = _next_segid; |
170 | 24 | _next_segid++; |
171 | 24 | VLOG_DEBUG << "src_id=" << src_id << ", segid=" << index << " to " |
172 | 0 | << " segid=" << _next_segid - 1 << ", " << *this; |
173 | 24 | } |
174 | 15 | } |
175 | 15 | } |
176 | | |
177 | | // Each sender sends data in one segment sequential, so we also do not |
178 | | // need a lock here. |
179 | 26 | bool eos = header.segment_eos(); |
180 | 26 | FileType file_type = header.file_type(); |
181 | 26 | uint32_t new_segid = mapping->at(segid); |
182 | 26 | DCHECK(new_segid != std::numeric_limits<uint32_t>::max()); |
183 | 26 | butil::IOBuf buf = data->movable(); |
184 | 26 | auto flush_func = [this, new_segid, eos, buf, header, file_type]() mutable { |
185 | 26 | signal::set_signal_task_id(_load_id); |
186 | 26 | g_load_stream_flush_running_threads << -1; |
187 | 26 | Defer defer {[this]() { |
188 | 26 | _release_task_slot(); |
189 | 26 | }}; |
190 | 26 | auto st = _load_stream_writer->append_data(new_segid, header.offset(), buf, file_type); |
191 | 26 | if (!st.ok() && !config::is_cloud_mode()) { |
192 | 1 | auto res = ExecEnv::get_tablet(_id); |
193 | 1 | TabletSharedPtr tablet = |
194 | 1 | res.has_value() ? std::dynamic_pointer_cast<Tablet>(res.value()) : nullptr; |
195 | 1 | if (tablet) { |
196 | 1 | tablet->report_error(st); |
197 | 1 | } |
198 | 1 | } |
199 | 26 | if (eos && st.ok()) { |
200 | 21 | DBUG_EXECUTE_IF("TabletStream.append_data.unknown_file_type", |
201 | 21 | { file_type = static_cast<FileType>(-1); }); |
202 | 21 | if (file_type == FileType::SEGMENT_FILE || file_type == FileType::INVERTED_INDEX_FILE) { |
203 | 21 | st = _load_stream_writer->close_writer(new_segid, file_type); |
204 | 21 | } else { |
205 | 0 | st = Status::InternalError( |
206 | 0 | "appent data failed, file type error, file type = {}, " |
207 | 0 | "segment_id={}", |
208 | 0 | file_type, new_segid); |
209 | 0 | } |
210 | 21 | } |
211 | 26 | DBUG_EXECUTE_IF("TabletStream.append_data.append_failed", |
212 | 26 | { st = Status::InternalError("fault injection"); }); |
213 | 26 | if (!st.ok()) { |
214 | 2 | _status.update(st); |
215 | 2 | LOG(WARNING) << "write data failed " << st << ", " << *this; |
216 | 2 | } |
217 | 26 | }; |
218 | 26 | auto load_stream_flush_token_max_tasks = config::load_stream_flush_token_max_tasks; |
219 | 26 | auto load_stream_max_wait_flush_token_time_ms = |
220 | 26 | config::load_stream_max_wait_flush_token_time_ms; |
221 | 26 | DBUG_EXECUTE_IF("TabletStream.append_data.long_wait", { |
222 | 26 | load_stream_flush_token_max_tasks = 0; |
223 | 26 | load_stream_max_wait_flush_token_time_ms = 1000; |
224 | 26 | }); |
225 | 26 | MonotonicStopWatch timer; |
226 | 26 | timer.start(); |
227 | 26 | auto wait_st = _wait_for_task_slot(load_stream_flush_token_max_tasks, |
228 | 26 | load_stream_max_wait_flush_token_time_ms); |
229 | 26 | if (!wait_st.ok()) { |
230 | 0 | _status.update(wait_st); |
231 | 0 | return _status.status(); |
232 | 0 | } |
233 | 26 | timer.stop(); |
234 | 26 | int64_t time_ms = timer.elapsed_time() / 1000 / 1000; |
235 | 26 | g_load_stream_flush_wait_ms << time_ms; |
236 | 26 | g_load_stream_flush_running_threads << 1; |
237 | 26 | Status st = Status::OK(); |
238 | 26 | DBUG_EXECUTE_IF("TabletStream.append_data.submit_func_failed", |
239 | 26 | { st = Status::InternalError("fault injection"); }); |
240 | 26 | if (st.ok()) { |
241 | 26 | st = _flush_token->submit_func(flush_func); |
242 | 26 | } |
243 | 26 | if (!st.ok()) { |
244 | 0 | _release_task_slot(); |
245 | 0 | g_load_stream_flush_running_threads << -1; |
246 | 0 | _status.update(st); |
247 | 0 | } |
248 | 26 | return _status.status(); |
249 | 26 | } |
250 | | |
251 | 0 | Status TabletStream::add_segment(const PStreamHeader& header, butil::IOBuf* data) { |
252 | 0 | if (!_status.ok()) { |
253 | 0 | return _status.status(); |
254 | 0 | } |
255 | | |
256 | 0 | SCOPED_TIMER(_add_segment_timer); |
257 | 0 | DCHECK(header.has_segment_statistics()); |
258 | 0 | SegmentStatistics stat(header.segment_statistics()); |
259 | |
|
260 | 0 | int64_t src_id = header.src_id(); |
261 | 0 | uint32_t segid = header.segment_id(); |
262 | 0 | uint32_t new_segid; |
263 | 0 | DBUG_EXECUTE_IF("TabletStream.add_segment.unknown_segid", { segid = UNKNOWN_ID_FOR_TEST; }); |
264 | 0 | { |
265 | 0 | std::lock_guard lock_guard(_lock); |
266 | 0 | if (!_segids_mapping.contains(src_id)) { |
267 | 0 | _status.update(Status::InternalError( |
268 | 0 | "add segment failed, no segment written by this src be yet, src_id={}, " |
269 | 0 | "segment_id={}", |
270 | 0 | src_id, segid)); |
271 | 0 | return _status.status(); |
272 | 0 | } |
273 | 0 | DBUG_EXECUTE_IF("TabletStream.add_segment.segid_never_written", |
274 | 0 | { segid = static_cast<uint32_t>(_segids_mapping[src_id]->size()); }); |
275 | 0 | if (segid >= _segids_mapping[src_id]->size()) { |
276 | 0 | _status.update(Status::InternalError( |
277 | 0 | "add segment failed, segment is never written, src_id={}, segment_id={}", |
278 | 0 | src_id, segid)); |
279 | 0 | return _status.status(); |
280 | 0 | } |
281 | 0 | new_segid = _segids_mapping[src_id]->at(segid); |
282 | 0 | } |
283 | 0 | DCHECK(new_segid != std::numeric_limits<uint32_t>::max()); |
284 | |
|
285 | 0 | auto add_segment_func = [this, new_segid, stat]() { |
286 | 0 | signal::set_signal_task_id(_load_id); |
287 | 0 | Defer defer {[this]() { _release_task_slot(); }}; |
288 | 0 | auto st = _load_stream_writer->add_segment(new_segid, stat); |
289 | 0 | DBUG_EXECUTE_IF("TabletStream.add_segment.add_segment_failed", |
290 | 0 | { st = Status::InternalError("fault injection"); }); |
291 | 0 | if (!st.ok()) { |
292 | 0 | _status.update(st); |
293 | 0 | LOG(INFO) << "add segment failed " << *this; |
294 | 0 | } |
295 | 0 | }; |
296 | 0 | auto wait_st = _wait_for_task_slot(config::load_stream_flush_token_max_tasks, |
297 | 0 | config::load_stream_max_wait_flush_token_time_ms); |
298 | 0 | if (!wait_st.ok()) { |
299 | 0 | _status.update(wait_st); |
300 | 0 | return _status.status(); |
301 | 0 | } |
302 | 0 | Status st = Status::OK(); |
303 | 0 | DBUG_EXECUTE_IF("TabletStream.add_segment.submit_func_failed", |
304 | 0 | { st = Status::InternalError("fault injection"); }); |
305 | 0 | if (st.ok()) { |
306 | 0 | st = _flush_token->submit_func(add_segment_func); |
307 | 0 | } |
308 | 0 | if (!st.ok()) { |
309 | 0 | _release_task_slot(); |
310 | 0 | _status.update(st); |
311 | 0 | } |
312 | 0 | return _status.status(); |
313 | 0 | } |
314 | | |
315 | 33 | Status TabletStream::_run_in_heavy_work_pool(std::function<Status()> fn) { |
316 | 33 | bthread::Mutex mu; |
317 | 33 | std::unique_lock<bthread::Mutex> lock(mu); |
318 | 33 | bthread::ConditionVariable cv; |
319 | 33 | auto st = Status::OK(); |
320 | 33 | auto func = [this, &mu, &cv, &st, &fn] { |
321 | 33 | signal::set_signal_task_id(_load_id); |
322 | 33 | st = fn(); |
323 | 33 | std::lock_guard<bthread::Mutex> lock(mu); |
324 | 33 | cv.notify_one(); |
325 | 33 | }; |
326 | 33 | bool ret = _load_stream_mgr->heavy_work_pool()->try_offer(func); |
327 | 33 | if (!ret) { |
328 | 0 | return Status::Error<ErrorCode::INTERNAL_ERROR>( |
329 | 0 | "there is not enough thread resource for close load"); |
330 | 0 | } |
331 | 33 | cv.wait(lock); |
332 | 33 | return st; |
333 | 33 | } |
334 | | |
335 | 30 | void TabletStream::wait_for_flush_tasks() { |
336 | 30 | { |
337 | 30 | std::lock_guard lock_guard(_lock); |
338 | 30 | if (_flush_tasks_done) { |
339 | 15 | return; |
340 | 15 | } |
341 | 15 | _flush_tasks_done = true; |
342 | 15 | } |
343 | | |
344 | 15 | if (!_status.ok()) { |
345 | 1 | _flush_token->shutdown(); |
346 | 1 | return; |
347 | 1 | } |
348 | | |
349 | | // Use heavy_work_pool to avoid blocking bthread |
350 | 14 | auto st = _run_in_heavy_work_pool([this]() { |
351 | 14 | _flush_token->wait(); |
352 | 14 | return Status::OK(); |
353 | 14 | }); |
354 | 14 | if (!st.ok()) { |
355 | | // If heavy_work_pool is unavailable, fall back to shutdown |
356 | | // which will cancel pending tasks and wait for running tasks |
357 | 0 | _flush_token->shutdown(); |
358 | 0 | _status.update(st); |
359 | 0 | } |
360 | 14 | } |
361 | | |
362 | 15 | void TabletStream::pre_close() { |
363 | 15 | SCOPED_TIMER(_close_wait_timer); |
364 | 15 | wait_for_flush_tasks(); |
365 | | |
366 | 15 | if (!_status.ok()) { |
367 | 3 | return; |
368 | 3 | } |
369 | | |
370 | 12 | DBUG_EXECUTE_IF("TabletStream.close.segment_num_mismatch", { _num_segments++; }); |
371 | 12 | if (_check_num_segments && (_next_segid.load() != _num_segments)) { |
372 | 2 | _status.update(Status::Corruption( |
373 | 2 | "segment num mismatch in tablet {}, expected: {}, actual: {}, load_id: {}", _id, |
374 | 2 | _num_segments, _next_segid.load(), print_id(_load_id))); |
375 | 2 | return; |
376 | 2 | } |
377 | | |
378 | 10 | _status.update(_run_in_heavy_work_pool([this]() { return _load_stream_writer->pre_close(); })); |
379 | 10 | } |
380 | | |
381 | 15 | Status TabletStream::close() { |
382 | 15 | if (!_status.ok()) { |
383 | 6 | return _status.status(); |
384 | 6 | } |
385 | | |
386 | 9 | SCOPED_TIMER(_close_wait_timer); |
387 | 9 | _status.update(_run_in_heavy_work_pool([this]() { return _load_stream_writer->close(); })); |
388 | 9 | return _status.status(); |
389 | 15 | } |
390 | | |
391 | | IndexStream::IndexStream(const PUniqueId& load_id, int64_t id, int64_t txn_id, |
392 | | std::shared_ptr<OlapTableSchemaParam> schema, |
393 | | LoadStreamMgr* load_stream_mgr, RuntimeProfile* profile) |
394 | 30 | : _id(id), |
395 | 30 | _load_id(load_id), |
396 | 30 | _txn_id(txn_id), |
397 | 30 | _schema(schema), |
398 | 30 | _load_stream_mgr(load_stream_mgr) { |
399 | 30 | _profile = profile->create_child(fmt::format("IndexStream {}", id), true, true); |
400 | 30 | _append_data_timer = ADD_TIMER(_profile, "AppendDataTime"); |
401 | 30 | _close_wait_timer = ADD_TIMER(_profile, "CloseWaitTime"); |
402 | 30 | } |
403 | | |
404 | 30 | IndexStream::~IndexStream() { |
405 | | // Ensure all TabletStreams have their flush tokens properly handled before destruction. |
406 | | // In normal flow, close() should have called pre_close() on all tablet streams. |
407 | | // But if IndexStream is destroyed without close() being called (e.g., on_idle_timeout), |
408 | | // we need to wait for flush tasks here to ensure flush tokens are properly shut down. |
409 | 30 | for (auto& [_, tablet_stream] : _tablet_streams_map) { |
410 | 15 | tablet_stream->wait_for_flush_tasks(); |
411 | 15 | } |
412 | 30 | } |
413 | | |
414 | 27 | Status IndexStream::append_data(const PStreamHeader& header, butil::IOBuf* data) { |
415 | 27 | SCOPED_TIMER(_append_data_timer); |
416 | 27 | int64_t tablet_id = header.tablet_id(); |
417 | 27 | TabletStreamSharedPtr tablet_stream; |
418 | 27 | { |
419 | 27 | std::lock_guard lock_guard(_lock); |
420 | 27 | auto it = _tablet_streams_map.find(tablet_id); |
421 | 27 | if (it == _tablet_streams_map.end()) { |
422 | 13 | _init_tablet_stream(tablet_stream, tablet_id, header.partition_id()); |
423 | 14 | } else { |
424 | 14 | tablet_stream = it->second; |
425 | 14 | } |
426 | 27 | } |
427 | | |
428 | 27 | return tablet_stream->append_data(header, data); |
429 | 27 | } |
430 | | |
431 | | void IndexStream::_init_tablet_stream(TabletStreamSharedPtr& tablet_stream, int64_t tablet_id, |
432 | 15 | int64_t partition_id) { |
433 | 15 | tablet_stream = std::make_shared<TabletStream>(_load_id, tablet_id, _txn_id, _load_stream_mgr, |
434 | 15 | _profile); |
435 | 15 | _tablet_streams_map[tablet_id] = tablet_stream; |
436 | 15 | auto st = tablet_stream->init(_schema, _id, partition_id); |
437 | 15 | if (!st.ok()) { |
438 | 1 | LOG(WARNING) << "tablet stream init failed " << *tablet_stream; |
439 | 1 | } |
440 | 15 | } |
441 | | |
442 | 0 | void IndexStream::get_all_write_tablet_ids(std::vector<int64_t>* tablet_ids) { |
443 | 0 | std::lock_guard lock_guard(_lock); |
444 | 0 | for (const auto& [tablet_id, _] : _tablet_streams_map) { |
445 | 0 | tablet_ids->push_back(tablet_id); |
446 | 0 | } |
447 | 0 | } |
448 | | |
449 | | void IndexStream::close(const std::vector<PTabletID>& tablets_to_commit, |
450 | 30 | std::vector<int64_t>* success_tablet_ids, FailedTablets* failed_tablets) { |
451 | 30 | std::lock_guard lock_guard(_lock); |
452 | 30 | SCOPED_TIMER(_close_wait_timer); |
453 | | // open all need commit tablets |
454 | 40 | for (const auto& tablet : tablets_to_commit) { |
455 | 40 | if (_id != tablet.index_id()) { |
456 | 21 | continue; |
457 | 21 | } |
458 | 19 | TabletStreamSharedPtr tablet_stream; |
459 | 19 | auto it = _tablet_streams_map.find(tablet.tablet_id()); |
460 | 19 | if (it == _tablet_streams_map.end()) { |
461 | 2 | _init_tablet_stream(tablet_stream, tablet.tablet_id(), tablet.partition_id()); |
462 | 17 | } else { |
463 | 17 | tablet_stream = it->second; |
464 | 17 | } |
465 | 19 | if (tablet.has_num_segments()) { |
466 | 16 | tablet_stream->add_num_segments(tablet.num_segments()); |
467 | 16 | } else { |
468 | | // for compatibility reasons (sink from old version BE) |
469 | 3 | tablet_stream->disable_num_segments_check(); |
470 | 3 | } |
471 | 19 | } |
472 | | |
473 | 30 | for (auto& [_, tablet_stream] : _tablet_streams_map) { |
474 | 15 | tablet_stream->pre_close(); |
475 | 15 | } |
476 | | |
477 | 30 | for (auto& [_, tablet_stream] : _tablet_streams_map) { |
478 | 15 | auto st = tablet_stream->close(); |
479 | 15 | if (st.ok()) { |
480 | 9 | success_tablet_ids->push_back(tablet_stream->id()); |
481 | 9 | } else { |
482 | 6 | LOG(INFO) << "close tablet stream " << *tablet_stream << ", status=" << st; |
483 | 6 | failed_tablets->emplace_back(tablet_stream->id(), st); |
484 | 6 | } |
485 | 15 | } |
486 | 30 | } |
487 | | |
488 | | // TODO: Profile is temporary disabled, because: |
489 | | // 1. It's not being processed by the upstream for now |
490 | | // 2. There are some problems in _profile->to_thrift() |
491 | | LoadStream::LoadStream(const PUniqueId& load_id, LoadStreamMgr* load_stream_mgr, |
492 | | bool enable_profile) |
493 | 15 | : _load_id(load_id), _enable_profile(false), _load_stream_mgr(load_stream_mgr) { |
494 | 15 | g_load_stream_cnt << 1; |
495 | 15 | _profile = std::make_unique<RuntimeProfile>("LoadStream"); |
496 | 15 | _append_data_timer = ADD_TIMER(_profile, "AppendDataTime"); |
497 | 15 | _close_wait_timer = ADD_TIMER(_profile, "CloseWaitTime"); |
498 | 15 | TUniqueId load_tid = ((UniqueId)load_id).to_thrift(); |
499 | | #ifndef BE_TEST |
500 | | std::shared_ptr<QueryContext> query_context = |
501 | | ExecEnv::GetInstance()->fragment_mgr()->get_query_ctx(load_tid); |
502 | | if (query_context != nullptr) { |
503 | | _resource_ctx = query_context->resource_ctx(); |
504 | | } else { |
505 | | _resource_ctx = ResourceContext::create_shared(); |
506 | | _resource_ctx->task_controller()->set_task_id(load_tid); |
507 | | std::shared_ptr<MemTrackerLimiter> mem_tracker = MemTrackerLimiter::create_shared( |
508 | | MemTrackerLimiter::Type::LOAD, |
509 | | fmt::format("(FromLoadStream)Load#Id={}", ((UniqueId)load_id).to_string())); |
510 | | _resource_ctx->memory_context()->set_mem_tracker(mem_tracker); |
511 | | } |
512 | | #else |
513 | 15 | _resource_ctx = ResourceContext::create_shared(); |
514 | 15 | _resource_ctx->task_controller()->set_task_id(load_tid); |
515 | 15 | std::shared_ptr<MemTrackerLimiter> mem_tracker = MemTrackerLimiter::create_shared( |
516 | 15 | MemTrackerLimiter::Type::LOAD, |
517 | 15 | fmt::format("(FromLoadStream)Load#Id={}", ((UniqueId)load_id).to_string())); |
518 | 15 | _resource_ctx->memory_context()->set_mem_tracker(mem_tracker); |
519 | 15 | #endif |
520 | 15 | } |
521 | | |
522 | 15 | LoadStream::~LoadStream() { |
523 | 15 | g_load_stream_cnt << -1; |
524 | 15 | LOG(INFO) << "load stream is deconstructed " << *this; |
525 | 15 | } |
526 | | |
527 | 15 | Status LoadStream::init(const POpenLoadStreamRequest* request) { |
528 | 15 | _txn_id = request->txn_id(); |
529 | 15 | _total_streams = static_cast<int32_t>(request->total_streams()); |
530 | 15 | _is_incremental = (_total_streams == 0); |
531 | | |
532 | 15 | _schema = std::make_shared<OlapTableSchemaParam>(); |
533 | 15 | RETURN_IF_ERROR(_schema->init(request->schema())); |
534 | 30 | for (auto& index : request->schema().indexes()) { |
535 | 30 | _index_streams_map[index.id()] = std::make_shared<IndexStream>( |
536 | 30 | _load_id, index.id(), _txn_id, _schema, _load_stream_mgr, _profile.get()); |
537 | 30 | } |
538 | 15 | LOG(INFO) << "succeed to init load stream " << *this; |
539 | 15 | return Status::OK(); |
540 | 15 | } |
541 | | |
542 | | bool LoadStream::close(int64_t src_id, const std::vector<PTabletID>& tablets_to_commit, |
543 | 19 | std::vector<int64_t>* success_tablet_ids, FailedTablets* failed_tablets) { |
544 | 19 | std::lock_guard<bthread::Mutex> lock_guard(_lock); |
545 | 19 | SCOPED_TIMER(_close_wait_timer); |
546 | | |
547 | | // we do nothing until recv CLOSE_LOAD from all stream to ensure all data are handled before ack |
548 | 19 | _open_streams[src_id]--; |
549 | 19 | if (_open_streams[src_id] == 0) { |
550 | 19 | _open_streams.erase(src_id); |
551 | 19 | } |
552 | 19 | _close_load_cnt++; |
553 | 19 | LOG(INFO) << "received CLOSE_LOAD from sender " << src_id << ", remaining " |
554 | 19 | << _total_streams - _close_load_cnt << " senders, " << *this; |
555 | | |
556 | 19 | _tablets_to_commit.insert(_tablets_to_commit.end(), tablets_to_commit.begin(), |
557 | 19 | tablets_to_commit.end()); |
558 | | |
559 | 19 | if (_close_load_cnt < _total_streams) { |
560 | | // do not return commit info if there is remaining streams. |
561 | 4 | return false; |
562 | 4 | } |
563 | | |
564 | 30 | for (auto& [_, index_stream] : _index_streams_map) { |
565 | 30 | index_stream->close(_tablets_to_commit, success_tablet_ids, failed_tablets); |
566 | 30 | } |
567 | 15 | LOG(INFO) << "close load " << *this << ", success_tablet_num=" << success_tablet_ids->size() |
568 | 15 | << ", failed_tablet_num=" << failed_tablets->size(); |
569 | 15 | return true; |
570 | 19 | } |
571 | | |
572 | 19 | std::vector<int64_t> LoadStream::mark_eos_sent_and_collect(int64_t stream_id, bool is_incremental) { |
573 | 19 | std::lock_guard<bthread::Mutex> lock_guard(_lock); |
574 | 19 | std::vector<int64_t> to_close; |
575 | | // A non-incremental stream is closed as soon as its own CLOSE_LOAD (and EOS) |
576 | | // is handled -- this is the first batch of streams, known up front, not subject |
577 | | // to fencing. Closing it promptly also means a duplicate/late CLOSE_LOAD lands |
578 | | // on an already-closed stream and is dropped, instead of being counted again. |
579 | | // An incremental stream must be deferred (fencing #56120: it may only close once |
580 | | // every non-incremental stream is closed), so it is parked in _eos_sent_stream_ids |
581 | | // until all CLOSE_LOADs have been received. |
582 | 19 | if (is_incremental) { |
583 | | // Parked only after the caller sent this stream's EOS via _report_result, |
584 | | // so every parked id is safe to close. |
585 | 3 | _eos_sent_stream_ids.push_back(stream_id); |
586 | 16 | } else { |
587 | 16 | to_close.push_back(stream_id); |
588 | 16 | } |
589 | | // `_close_load_cnt == _total_streams` means every CLOSE_LOAD has been counted by |
590 | | // close(). Latch it so that any thread reaching here afterwards also drains the |
591 | | // parked incremental streams, guaranteeing none is left un-closed regardless of |
592 | | // thread interleaving (fixes the split-lock leak race). |
593 | 19 | if (_close_load_cnt >= _total_streams) { |
594 | 17 | _all_close_load_received = true; |
595 | 17 | } |
596 | 19 | if (_all_close_load_received) { |
597 | 17 | for (const auto& parked_id : _eos_sent_stream_ids) { |
598 | 3 | to_close.push_back(parked_id); |
599 | 3 | } |
600 | 17 | _eos_sent_stream_ids.clear(); |
601 | 17 | } |
602 | 19 | return to_close; |
603 | 19 | } |
604 | | |
605 | | void LoadStream::_report_result(StreamId stream, const Status& status, |
606 | | const std::vector<int64_t>& success_tablet_ids, |
607 | 39 | const FailedTablets& failed_tablets, bool eos) { |
608 | 39 | LOG(INFO) << "report result " << *this << ", success tablet num " << success_tablet_ids.size() |
609 | 39 | << ", failed tablet num " << failed_tablets.size(); |
610 | 39 | butil::IOBuf buf; |
611 | 39 | PLoadStreamResponse response; |
612 | 39 | response.set_eos(eos); |
613 | 39 | status.to_protobuf(response.mutable_status()); |
614 | 39 | for (auto& id : success_tablet_ids) { |
615 | 9 | response.add_success_tablet_ids(id); |
616 | 9 | } |
617 | 39 | for (auto& [id, st] : failed_tablets) { |
618 | 10 | auto pb = response.add_failed_tablets(); |
619 | 10 | pb->set_id(id); |
620 | 10 | st.to_protobuf(pb->mutable_status()); |
621 | 10 | } |
622 | | |
623 | 39 | if (_enable_profile && _close_load_cnt == _total_streams) { |
624 | 0 | TRuntimeProfileTree tprofile; |
625 | 0 | ThriftSerializer ser(false, 4096); |
626 | 0 | uint8_t* profile_buf = nullptr; |
627 | 0 | uint32_t len = 0; |
628 | 0 | std::unique_lock<bthread::Mutex> l(_lock); |
629 | |
|
630 | 0 | _profile->to_thrift(&tprofile); |
631 | 0 | auto st = ser.serialize(&tprofile, &len, &profile_buf); |
632 | 0 | if (st.ok()) { |
633 | 0 | response.set_load_stream_profile(profile_buf, len); |
634 | 0 | } else { |
635 | 0 | LOG(WARNING) << "TRuntimeProfileTree serialize failed, errmsg=" << st << ", " << *this; |
636 | 0 | } |
637 | 0 | } |
638 | | |
639 | 39 | buf.append(response.SerializeAsString()); |
640 | 39 | auto wst = _write_stream(stream, buf); |
641 | 39 | if (!wst.ok()) { |
642 | 0 | LOG(WARNING) << " report result failed with " << wst << ", " << *this; |
643 | 0 | } |
644 | 39 | } |
645 | | |
646 | 0 | void LoadStream::_report_schema(StreamId stream, const PStreamHeader& hdr) { |
647 | 0 | butil::IOBuf buf; |
648 | 0 | PLoadStreamResponse response; |
649 | 0 | Status st = Status::OK(); |
650 | 0 | for (const auto& req : hdr.tablets()) { |
651 | 0 | BaseTabletSPtr tablet; |
652 | 0 | if (auto res = ExecEnv::get_tablet(req.tablet_id()); res.has_value()) { |
653 | 0 | tablet = std::move(res).value(); |
654 | 0 | } else { |
655 | 0 | st = std::move(res).error(); |
656 | 0 | break; |
657 | 0 | } |
658 | 0 | auto* resp = response.add_tablet_schemas(); |
659 | 0 | resp->set_index_id(req.index_id()); |
660 | 0 | resp->set_enable_unique_key_merge_on_write(tablet->enable_unique_key_merge_on_write()); |
661 | 0 | tablet->tablet_schema()->to_schema_pb(resp->mutable_tablet_schema()); |
662 | 0 | } |
663 | 0 | st.to_protobuf(response.mutable_status()); |
664 | |
|
665 | 0 | buf.append(response.SerializeAsString()); |
666 | 0 | auto wst = _write_stream(stream, buf); |
667 | 0 | if (!wst.ok()) { |
668 | 0 | LOG(WARNING) << " report result failed with " << wst << ", " << *this; |
669 | 0 | } |
670 | 0 | } |
671 | | |
672 | 0 | void LoadStream::_report_tablet_load_info(StreamId stream, int64_t index_id) { |
673 | 0 | std::vector<int64_t> write_tablet_ids; |
674 | 0 | auto it = _index_streams_map.find(index_id); |
675 | 0 | if (it != _index_streams_map.end()) { |
676 | 0 | it->second->get_all_write_tablet_ids(&write_tablet_ids); |
677 | 0 | } |
678 | |
|
679 | 0 | if (!write_tablet_ids.empty()) { |
680 | 0 | butil::IOBuf buf; |
681 | 0 | PLoadStreamResponse response; |
682 | 0 | auto* tablet_load_infos = response.mutable_tablet_load_rowset_num_infos(); |
683 | 0 | _collect_tablet_load_info_from_tablets(write_tablet_ids, tablet_load_infos); |
684 | 0 | if (tablet_load_infos->empty()) { |
685 | 0 | return; |
686 | 0 | } |
687 | 0 | buf.append(response.SerializeAsString()); |
688 | 0 | auto wst = _write_stream(stream, buf); |
689 | 0 | if (!wst.ok()) { |
690 | 0 | LOG(WARNING) << "report tablet load info failed with " << wst << ", " << *this; |
691 | 0 | } |
692 | 0 | } |
693 | 0 | } |
694 | | |
695 | | void LoadStream::_collect_tablet_load_info_from_tablets( |
696 | | const std::vector<int64_t>& tablet_ids, |
697 | 0 | google::protobuf::RepeatedPtrField<PTabletLoadRowsetInfo>* tablet_load_infos) { |
698 | 0 | for (auto tablet_id : tablet_ids) { |
699 | 0 | BaseTabletSPtr tablet; |
700 | 0 | if (auto res = ExecEnv::get_tablet(tablet_id); res.has_value()) { |
701 | 0 | tablet = std::move(res).value(); |
702 | 0 | } else { |
703 | 0 | continue; |
704 | 0 | } |
705 | 0 | BaseDeltaWriter::collect_tablet_load_rowset_num_info(tablet.get(), tablet_load_infos); |
706 | 0 | } |
707 | 0 | } |
708 | | |
709 | 39 | Status LoadStream::_write_stream(StreamId stream, butil::IOBuf& buf) { |
710 | 39 | for (;;) { |
711 | 39 | int ret = 0; |
712 | 39 | DBUG_EXECUTE_IF("LoadStream._write_stream.EAGAIN", { ret = EAGAIN; }); |
713 | 39 | if (ret == 0) { |
714 | 39 | ret = brpc::StreamWrite(stream, buf); |
715 | 39 | } |
716 | 39 | switch (ret) { |
717 | 39 | case 0: |
718 | 39 | return Status::OK(); |
719 | 0 | case EAGAIN: { |
720 | 0 | const timespec time = butil::seconds_from_now(config::load_stream_eagain_wait_seconds); |
721 | 0 | int wait_ret = brpc::StreamWait(stream, &time); |
722 | 0 | if (wait_ret != 0) { |
723 | 0 | return Status::InternalError("StreamWait failed, err={}", wait_ret); |
724 | 0 | } |
725 | 0 | break; |
726 | 0 | } |
727 | 0 | default: |
728 | 0 | return Status::InternalError("StreamWrite failed, err={}", ret); |
729 | 39 | } |
730 | 39 | } |
731 | 0 | return Status::OK(); |
732 | 39 | } |
733 | | |
734 | 65 | void LoadStream::_parse_header(butil::IOBuf* const message, PStreamHeader& hdr) { |
735 | 65 | butil::IOBufAsZeroCopyInputStream wrapper(*message); |
736 | 65 | hdr.ParseFromZeroCopyStream(&wrapper); |
737 | 65 | VLOG_DEBUG << "header parse result: " << hdr.DebugString(); |
738 | 65 | } |
739 | | |
740 | 28 | Status LoadStream::_append_data(const PStreamHeader& header, butil::IOBuf* data) { |
741 | 28 | SCOPED_TIMER(_append_data_timer); |
742 | 28 | IndexStreamSharedPtr index_stream; |
743 | | |
744 | 28 | int64_t index_id = header.index_id(); |
745 | 28 | DBUG_EXECUTE_IF("TabletStream._append_data.unknown_indexid", |
746 | 28 | { index_id = UNKNOWN_ID_FOR_TEST; }); |
747 | 28 | auto it = _index_streams_map.find(index_id); |
748 | 28 | if (it == _index_streams_map.end()) { |
749 | 1 | return Status::Error<ErrorCode::INVALID_ARGUMENT>("unknown index_id {}", index_id); |
750 | 27 | } else { |
751 | 27 | index_stream = it->second; |
752 | 27 | } |
753 | | |
754 | 27 | return index_stream->append_data(header, data); |
755 | 28 | } |
756 | | |
757 | 52 | int LoadStream::on_received_messages(StreamId id, butil::IOBuf* const messages[], size_t size) { |
758 | 52 | VLOG_DEBUG << "on_received_messages " << id << " " << size; |
759 | 117 | for (size_t i = 0; i < size; ++i) { |
760 | 130 | while (messages[i]->size() > 0) { |
761 | | // step 1: parse header |
762 | 65 | size_t hdr_len = 0; |
763 | 65 | messages[i]->cutn((void*)&hdr_len, sizeof(size_t)); |
764 | 65 | butil::IOBuf hdr_buf; |
765 | 65 | PStreamHeader hdr; |
766 | 65 | messages[i]->cutn(&hdr_buf, hdr_len); |
767 | 65 | _parse_header(&hdr_buf, hdr); |
768 | | |
769 | | // step 2: cut data |
770 | 65 | size_t data_len = 0; |
771 | 65 | messages[i]->cutn((void*)&data_len, sizeof(size_t)); |
772 | 65 | butil::IOBuf data_buf; |
773 | 65 | PStreamHeader data; |
774 | 65 | messages[i]->cutn(&data_buf, data_len); |
775 | | |
776 | | // step 3: dispatch |
777 | 65 | _dispatch(id, hdr, &data_buf); |
778 | 65 | } |
779 | 65 | } |
780 | 52 | return 0; |
781 | 52 | } |
782 | | |
783 | 65 | void LoadStream::_dispatch(StreamId id, const PStreamHeader& hdr, butil::IOBuf* data) { |
784 | 65 | VLOG_DEBUG << PStreamHeader_Opcode_Name(hdr.opcode()) << " from " << hdr.src_id() |
785 | 0 | << " with tablet " << hdr.tablet_id(); |
786 | 65 | SCOPED_ATTACH_TASK(_resource_ctx); |
787 | | // CLOSE_LOAD message should not be fault injected, |
788 | | // otherwise the message will be ignored and causing close wait timeout |
789 | 65 | if (hdr.opcode() != PStreamHeader::CLOSE_LOAD) { |
790 | 30 | DBUG_EXECUTE_IF("LoadStream._dispatch.unknown_loadid", { |
791 | 30 | PStreamHeader& t_hdr = const_cast<PStreamHeader&>(hdr); |
792 | 30 | PUniqueId* load_id = t_hdr.mutable_load_id(); |
793 | 30 | load_id->set_hi(UNKNOWN_ID_FOR_TEST); |
794 | 30 | load_id->set_lo(UNKNOWN_ID_FOR_TEST); |
795 | 30 | }); |
796 | 30 | DBUG_EXECUTE_IF("LoadStream._dispatch.unknown_srcid", { |
797 | 30 | PStreamHeader& t_hdr = const_cast<PStreamHeader&>(hdr); |
798 | 30 | t_hdr.set_src_id(UNKNOWN_ID_FOR_TEST); |
799 | 30 | }); |
800 | 30 | } |
801 | 65 | if (UniqueId(hdr.load_id()) != UniqueId(_load_id)) { |
802 | 1 | Status st = Status::Error<ErrorCode::INVALID_ARGUMENT>( |
803 | 1 | "invalid load id {}, expected {}", print_id(hdr.load_id()), print_id(_load_id)); |
804 | 1 | _report_failure(id, st, hdr); |
805 | 1 | return; |
806 | 1 | } |
807 | | |
808 | 64 | { |
809 | 64 | std::lock_guard lock_guard(_lock); |
810 | 64 | if (!_open_streams.contains(hdr.src_id())) { |
811 | 17 | Status st = Status::Error<ErrorCode::INVALID_ARGUMENT>("no open stream from source {}", |
812 | 17 | hdr.src_id()); |
813 | 17 | _report_failure(id, st, hdr); |
814 | 17 | return; |
815 | 17 | } |
816 | 64 | } |
817 | | |
818 | 47 | switch (hdr.opcode()) { |
819 | 0 | case PStreamHeader::ADD_SEGMENT: { |
820 | 0 | auto st = _append_data(hdr, data); |
821 | 0 | if (!st.ok()) { |
822 | 0 | _report_failure(id, st, hdr); |
823 | 0 | } else { |
824 | | // Report tablet load info only on ADD_SEGMENT to reduce frequency. |
825 | | // ADD_SEGMENT is sent once per segment, while APPEND_DATA is sent |
826 | | // for every data batch. This reduces unnecessary writes and avoids |
827 | | // potential stream write failures when the sender is closing. |
828 | 0 | _report_tablet_load_info(id, hdr.index_id()); |
829 | 0 | } |
830 | 0 | } break; |
831 | 28 | case PStreamHeader::APPEND_DATA: { |
832 | 28 | auto st = _append_data(hdr, data); |
833 | 28 | if (!st.ok()) { |
834 | 2 | _report_failure(id, st, hdr); |
835 | 2 | } |
836 | 28 | } break; |
837 | 19 | case PStreamHeader::CLOSE_LOAD: { |
838 | 19 | DBUG_EXECUTE_IF("LoadStream.close_load.block", DBUG_BLOCK); |
839 | 19 | std::vector<int64_t> success_tablet_ids; |
840 | 19 | FailedTablets failed_tablets; |
841 | 19 | std::vector<PTabletID> tablets_to_commit(hdr.tablets().begin(), hdr.tablets().end()); |
842 | | // Step 1: count this CLOSE_LOAD and, if this is the last one, commit. Under _lock. |
843 | 19 | bool all_received = |
844 | 19 | close(hdr.src_id(), tablets_to_commit, &success_tablet_ids, &failed_tablets); |
845 | | // Step 2: send THIS stream's EOS (network IO, must be outside _lock). A stream |
846 | | // must not be StreamClose'd before its own EOS is delivered, otherwise the |
847 | | // sender sees on_closed without EOS and reports "Stream closed without EOS". |
848 | 19 | _report_result(id, Status::OK(), success_tablet_ids, failed_tablets, true); |
849 | 19 | bool is_incremental = |
850 | 19 | hdr.has_num_incremental_streams() && hdr.num_incremental_streams() > 0; |
851 | | // Test-only: delay every incremental stream except the one that made |
852 | | // all_received, so a non-last incremental stream parks after the last |
853 | | // stream drained the list. On the buggy code this orphans it and the |
854 | | // load hangs; on the fix the latch drains the late registration under |
855 | | // the same lock. Inert unless enable_debug_points=true. |
856 | 19 | if (is_incremental && !all_received) { |
857 | 2 | DBUG_EXECUTE_IF("LoadStream.close_load.delay_incremental_register", |
858 | 2 | { bthread_usleep(3000000); }); |
859 | 2 | } |
860 | | // Step 3: close this stream (non-incremental) or park it for deferred close |
861 | | // (incremental, fencing), then collect everything that is now safe to close. |
862 | | // Registration happens only after step 2, so a collected stream already had |
863 | | // its EOS delivered (fixes the close-before-EOS race); the all-received latch |
864 | | // inside makes any late thread drain the parked streams (fixes the leak race). |
865 | 19 | auto streams_to_close = mark_eos_sent_and_collect(id, is_incremental); |
866 | 19 | for (auto& closing_id : streams_to_close) { |
867 | 19 | brpc::StreamClose(closing_id); |
868 | 19 | } |
869 | 19 | } break; |
870 | 0 | case PStreamHeader::GET_SCHEMA: { |
871 | 0 | _report_schema(id, hdr); |
872 | 0 | } break; |
873 | 0 | default: |
874 | 0 | LOG(WARNING) << "unexpected stream message " << hdr.opcode() << ", " << *this; |
875 | 0 | DCHECK(false); |
876 | 47 | } |
877 | 47 | } |
878 | | |
879 | 0 | void LoadStream::on_idle_timeout(StreamId id) { |
880 | 0 | LOG(WARNING) << "closing load stream on idle timeout, " << *this; |
881 | 0 | brpc::StreamClose(id); |
882 | 0 | } |
883 | | |
884 | 19 | void LoadStream::on_closed(StreamId id) { |
885 | | // `this` may be freed by other threads after increasing `_close_rpc_cnt`, |
886 | | // format string first to prevent use-after-free |
887 | 19 | std::stringstream ss; |
888 | 19 | ss << *this; |
889 | 19 | auto remaining_streams = _total_streams - _close_rpc_cnt.fetch_add(1) - 1; |
890 | 19 | LOG(INFO) << "stream " << id << " on_closed, remaining streams = " << remaining_streams << ", " |
891 | 19 | << ss.str(); |
892 | 19 | if (remaining_streams == 0) { |
893 | 15 | _load_stream_mgr->clear_load(_load_id); |
894 | 15 | } |
895 | 19 | } |
896 | | |
897 | 122 | inline std::ostream& operator<<(std::ostream& ostr, const LoadStream& load_stream) { |
898 | 122 | ostr << "load_id=" << print_id(load_stream._load_id) << ", txn_id=" << load_stream._txn_id; |
899 | 122 | return ostr; |
900 | 122 | } |
901 | | |
902 | | } // namespace doris |