mirror of
https://github.com/apache/doris.git
synced 2026-11-04 16:03:23 +00:00
### What problem does this PR solve? The SyncPoint singleton was being destroyed during program exit while background bthread timers were still accessing it, causing heap-use-after-free. Changed get_instance() to return a never-destructed heap-allocated pointer instead of a stack-allocated static object to prevent destruction order issues. This is a standard pattern for singletons accessed by background threads. ``` 11:47:40 #0 0x5582a963153d in operator new(unsigned long) (/root/doris/cloud/ut_build_ASAN/test/recycler_test+0x122b53d) (BuildId: 1191affa24589f52) 11:47:40 #1 0x5582ab0aa52f in doris::SyncPoint::SyncPoint() /root/doris/common/cpp/sync_point.cpp:77:9 11:47:40 #2 0x5582ab0aa46b in doris::SyncPoint::get_instance() /root/doris/common/cpp/sync_point.cpp:73:20 11:47:40 #3 0x5582aae33ee8 in doris::cloud::memkv::Transaction::get(std::basic_string_view<char, std::char_traits<char>>, std::basic_string_view<char, std::char_traits<char>>, std::unique_ptr<doris::cloud::RangeGetIterator, std::default_delete<doris::cloud::RangeGetIterator>>*, bool, int) /root/doris/cloud/src/meta-store/mem_txn_kv.cpp:276:5 11:47:40 #4 0x5582a968f49f in doris::cloud::txn_get(doris::cloud::TxnKv*, std::basic_string_view<char, std::char_traits<char>>, std::basic_string_view<char, std::char_traits<char>>, std::unique_ptr<doris::cloud::RangeGetIterator, std::default_delete<doris::cloud::RangeGetIterator>>&) /root/doris/cloud/src/recycler/recycler.cpp:99:18 11:47:40 #5 0x5582a9657090 in doris::cloud::InstanceRecycler::scan_and_recycle(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>, std::basic_string_view<char, std::char_traits<char>>, std::function<int (std::basic_string_view<char, std::char_traits<char>>, std::basic_string_view<char, std::char_traits<char>>)>, std::function<int ()>) /root/doris/cloud/src/recycler/recycler.cpp:2976:23 11:47:40 #6 0x5582a9686153 in doris::cloud::InstanceRecycler::recycle_rowsets() /root/doris/cloud/src/recycler/recycler.cpp:2587:15 11:47:40 #7 0x5582a96a8d47 in doris::cloud::RecyclerTest_recycle_empty_Test::TestBody() /root/doris/cloud/test/recycler_test.cpp:1136:5 ```
261 lines
8.3 KiB
C++
261 lines
8.3 KiB
C++
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
// Most code of this file is copied from rocksdb SyncPoint.
|
|
// https://github.com/facebook/rocksdb
|
|
|
|
// clang-format off
|
|
#include "sync_point.h"
|
|
|
|
#include <atomic>
|
|
#include <condition_variable>
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <random>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
|
|
namespace doris {
|
|
|
|
struct SyncPoint::Data { // impl
|
|
public:
|
|
Data() : enabled_(false) { }
|
|
virtual ~Data() {}
|
|
void process(const std::string& point, std::vector<std::any>&& cb_args);
|
|
void load_dependency(const std::vector<SyncPointPair>& dependencies);
|
|
void load_dependency_and_markers(
|
|
const std::vector<SyncPointPair>& dependencies,
|
|
const std::vector<SyncPointPair>& markers);
|
|
bool predecessors_all_cleared(const std::string& point);
|
|
void set_call_back(const std::string& point,
|
|
const std::function<void(std::vector<std::any>&&)>& callback, CallbackGuard*);
|
|
void clear_call_back(const std::string& point);
|
|
void clear_all_call_backs();
|
|
void enable_processing();
|
|
void disable_processing();
|
|
void clear_trace();
|
|
bool has_point(const std::string& point);
|
|
bool get_enable();
|
|
private:
|
|
bool disable_by_marker(const std::string& point, std::thread::id thread_id);
|
|
private:
|
|
// successor/predecessor map loaded from load_dependency
|
|
std::unordered_map<std::string, std::vector<std::string>> successors_;
|
|
std::unordered_map<std::string, std::vector<std::string>> predecessors_;
|
|
std::unordered_map<std::string, std::function<void(std::vector<std::any>&&)>> callbacks_;
|
|
std::unordered_map<std::string, std::vector<std::string>> markers_;
|
|
std::unordered_map<std::string, std::thread::id> marked_thread_id_;
|
|
std::mutex mutex_;
|
|
std::condition_variable cv_;
|
|
// sync points that have been passed through
|
|
std::unordered_set<std::string> cleared_points_;
|
|
std::atomic<bool> enabled_;
|
|
int num_callbacks_running_ = 0;
|
|
};
|
|
|
|
SyncPoint* SyncPoint::get_instance() {
|
|
static auto* sync_point = new SyncPoint();
|
|
return sync_point;
|
|
}
|
|
SyncPoint::SyncPoint() :
|
|
impl_(new Data) {
|
|
}
|
|
SyncPoint:: ~SyncPoint() {
|
|
delete impl_;
|
|
}
|
|
void SyncPoint::load_dependency(const std::vector<SyncPointPair>& dependencies) {
|
|
impl_->load_dependency(dependencies);
|
|
}
|
|
void SyncPoint::load_dependency_and_markers(
|
|
const std::vector<SyncPointPair>& dependencies,
|
|
const std::vector<SyncPointPair>& markers) {
|
|
impl_->load_dependency_and_markers(dependencies, markers);
|
|
}
|
|
void SyncPoint::set_call_back(const std::string& point,
|
|
const std::function<void(std::vector<std::any>&&)>& callback, CallbackGuard* guard) {
|
|
impl_->set_call_back(point, callback, guard);
|
|
}
|
|
void SyncPoint::clear_call_back(const std::string& point) {
|
|
impl_->clear_call_back(point);
|
|
}
|
|
void SyncPoint::clear_all_call_backs() {
|
|
impl_->clear_all_call_backs();
|
|
}
|
|
void SyncPoint::enable_processing() {
|
|
impl_->enable_processing();
|
|
}
|
|
void SyncPoint::disable_processing() {
|
|
impl_->disable_processing();
|
|
}
|
|
void SyncPoint::clear_trace() {
|
|
impl_->clear_trace();
|
|
}
|
|
void SyncPoint::process(const std::string& point, std::vector<std::any>&& cb_arg) {
|
|
impl_->process(point, std::move(cb_arg));
|
|
}
|
|
bool SyncPoint::has_point(const std::string& point) {
|
|
return impl_->has_point(point);
|
|
}
|
|
|
|
bool SyncPoint::get_enable() {
|
|
return impl_->get_enable();
|
|
}
|
|
|
|
// =============================================================================
|
|
// SyncPoint implementation
|
|
// =============================================================================
|
|
|
|
void SyncPoint::Data::load_dependency(
|
|
const std::vector<SyncPointPair>& dependencies) {
|
|
std::lock_guard lock(mutex_);
|
|
successors_.clear();
|
|
predecessors_.clear();
|
|
cleared_points_.clear();
|
|
for (const auto& dependency : dependencies) {
|
|
successors_[dependency.predecessor].push_back(dependency.successor);
|
|
predecessors_[dependency.successor].push_back(dependency.predecessor);
|
|
}
|
|
cv_.notify_all();
|
|
}
|
|
|
|
/**
|
|
* Markers are also dependency descriptions
|
|
*/
|
|
void SyncPoint::Data::load_dependency_and_markers(
|
|
const std::vector<SyncPointPair>& dependencies,
|
|
const std::vector<SyncPointPair>& markers) {
|
|
std::lock_guard lock(mutex_);
|
|
successors_.clear();
|
|
predecessors_.clear();
|
|
cleared_points_.clear();
|
|
markers_.clear();
|
|
marked_thread_id_.clear();
|
|
for (const auto& dependency : dependencies) {
|
|
successors_[dependency.predecessor].push_back(dependency.successor);
|
|
predecessors_[dependency.successor].push_back(dependency.predecessor);
|
|
}
|
|
for (const auto& marker : markers) {
|
|
successors_[marker.predecessor].push_back(marker.successor);
|
|
predecessors_[marker.successor].push_back(marker.predecessor);
|
|
markers_[marker.predecessor].push_back(marker.successor);
|
|
}
|
|
cv_.notify_all();
|
|
}
|
|
|
|
bool SyncPoint::Data::predecessors_all_cleared(const std::string& point) {
|
|
for (const auto& pred : predecessors_[point]) {
|
|
if (cleared_points_.count(pred) == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void SyncPoint::Data::clear_call_back(const std::string& point) {
|
|
std::unique_lock lock(mutex_);
|
|
callbacks_.erase(point);
|
|
}
|
|
|
|
void SyncPoint::Data::clear_all_call_backs() {
|
|
std::unique_lock lock(mutex_);
|
|
callbacks_.clear();
|
|
}
|
|
|
|
void SyncPoint::Data::process(const std::string& point, std::vector<std::any>&& cb_arg) {
|
|
if (!enabled_) {
|
|
return;
|
|
}
|
|
std::unique_lock lock(mutex_);
|
|
auto thread_id = std::this_thread::get_id();
|
|
auto marker_iter = markers_.find(point);
|
|
// if current sync point is a marker
|
|
// record it in marked_thread_id_ for all its successors
|
|
if (marker_iter != markers_.end()) {
|
|
for (auto& marked_point : marker_iter->second) {
|
|
marked_thread_id_.emplace(marked_point, thread_id);
|
|
}
|
|
}
|
|
// if current point is a marker's successor
|
|
if (disable_by_marker(point, thread_id)) {
|
|
return;
|
|
}
|
|
while (!predecessors_all_cleared(point)) {
|
|
cv_.wait(lock);
|
|
if (disable_by_marker(point, thread_id)) {
|
|
return;
|
|
}
|
|
}
|
|
auto callback_pair = callbacks_.find(point);
|
|
if (callback_pair != callbacks_.end()) {
|
|
num_callbacks_running_++;
|
|
auto callback = callback_pair->second;
|
|
mutex_.unlock();
|
|
callback(std::move(cb_arg));
|
|
mutex_.lock();
|
|
num_callbacks_running_--;
|
|
}
|
|
cleared_points_.insert(point);
|
|
cv_.notify_all();
|
|
}
|
|
|
|
bool SyncPoint::Data::disable_by_marker(const std::string& point,
|
|
std::thread::id thread_id) {
|
|
auto marked_point_iter = marked_thread_id_.find(point);
|
|
return marked_point_iter != marked_thread_id_.end() // is a successor
|
|
&& thread_id != marked_point_iter->second;
|
|
}
|
|
|
|
void SyncPoint::Data::set_call_back(const std::string& point,
|
|
const std::function<void(std::vector<std::any>&&)>& callback, CallbackGuard* guard) {
|
|
{
|
|
std::lock_guard lock(mutex_);
|
|
callbacks_[point] = callback;
|
|
}
|
|
|
|
if (guard != nullptr) {
|
|
*guard = CallbackGuard(point);
|
|
}
|
|
}
|
|
|
|
void SyncPoint::Data::clear_trace() {
|
|
std::lock_guard lock(mutex_);
|
|
cleared_points_.clear();
|
|
}
|
|
|
|
void SyncPoint::Data::enable_processing() {
|
|
enabled_ = true;
|
|
}
|
|
|
|
void SyncPoint::Data::disable_processing() {
|
|
enabled_ = false;
|
|
}
|
|
|
|
bool SyncPoint::Data::has_point(const std::string& point) {
|
|
std::unique_lock lock(mutex_);
|
|
return callbacks_.find(point) != callbacks_.end();
|
|
}
|
|
|
|
bool SyncPoint::Data::get_enable() {
|
|
return enabled_;
|
|
}
|
|
|
|
} // namespace doris
|
|
// clang-format on
|
|
// vim: et tw=80 ts=2 sw=2 cc=80:
|