Skip to content

Commit 3d7e1e1

Browse files
committed
fix thread safety issues
1 parent 24f3d93 commit 3d7e1e1

File tree

5 files changed

+48
-30
lines changed

5 files changed

+48
-30
lines changed

cmake/sanitizers.cmake

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@ if(BTCPP_ENABLE_ASAN)
1515
message(STATUS "Address Sanitizer enabled")
1616
add_compile_options(-fsanitize=address)
1717
add_link_options(-fsanitize=address)
18-
endif()
18+
endif()
1919

20-
if(BTCPP_ENABLE_UBSAN)
20+
if(BTCPP_ENABLE_UBSAN)
2121
message(STATUS "Undefined Behavior Sanitizer enabled")
2222
add_compile_options(-fsanitize=undefined)
2323
add_link_options(-fsanitize=undefined)
24-
endif()
24+
endif()
2525

26-
if(BTCPP_ENABLE_TSAN)
26+
if(BTCPP_ENABLE_TSAN)
2727
message(STATUS "Thread Sanitizer enabled")
2828
add_compile_options(-fsanitize=thread)
2929
add_link_options(-fsanitize=thread)
30+
add_compile_definitions(USE_SANITIZE_THREAD)
3031
endif()

include/behaviortree_cpp/blackboard.h

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class Blackboard
141141
const Blackboard* rootBlackboard() const;
142142

143143
private:
144-
mutable std::mutex mutex_;
144+
mutable std::mutex storage_mutex_;
145145
mutable std::recursive_mutex entry_mutex_;
146146
std::unordered_map<std::string, std::shared_ptr<Entry>> storage_;
147147
std::weak_ptr<Blackboard> parent_bb_;
@@ -186,7 +186,7 @@ inline T Blackboard::get(const std::string& key) const
186186

187187
inline void Blackboard::unset(const std::string& key)
188188
{
189-
std::unique_lock lock(mutex_);
189+
std::unique_lock storage_lock(storage_mutex_);
190190

191191
// check local storage
192192
auto it = storage_.find(key);
@@ -207,15 +207,15 @@ inline void Blackboard::set(const std::string& key, const T& value)
207207
rootBlackboard()->set(key.substr(1, key.size() - 1), value);
208208
return;
209209
}
210-
std::unique_lock lock(mutex_);
210+
std::unique_lock storage_lock(storage_mutex_);
211211

212212
// check local storage
213213
auto it = storage_.find(key);
214214
if(it == storage_.end())
215215
{
216216
// create a new entry
217217
Any new_value(value);
218-
lock.unlock();
218+
storage_lock.unlock();
219219
std::shared_ptr<Blackboard::Entry> entry;
220220
// if a new generic port is created with a string, it's type should be AnyTypeAllowed
221221
if constexpr(std::is_same_v<std::string, T>)
@@ -228,7 +228,7 @@ inline void Blackboard::set(const std::string& key, const T& value)
228228
GetAnyFromStringFunctor<T>());
229229
entry = createEntryImpl(key, new_port);
230230
}
231-
lock.lock();
231+
storage_lock.lock();
232232

233233
entry->value = new_value;
234234
entry->sequence_id++;
@@ -239,6 +239,8 @@ inline void Blackboard::set(const std::string& key, const T& value)
239239
// this is not the first time we set this entry, we need to check
240240
// if the type is the same or not.
241241
Entry& entry = *it->second;
242+
storage_lock.unlock();
243+
242244
std::scoped_lock scoped_lock(entry.entry_mutex);
243245

244246
Any& previous_any = entry.value;

include/behaviortree_cpp/utils/timer_queue.h

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ class Semaphore
2323

2424
void notify()
2525
{
26-
{
27-
std::lock_guard<std::mutex> lock(m_mtx);
28-
m_count++;
29-
}
26+
m_count.fetch_add(1);
3027
m_cv.notify_one();
3128
}
3229

@@ -38,8 +35,15 @@ class Semaphore
3835
{
3936
return false;
4037
}
41-
m_count--;
38+
// Only decrement if there is a real count. If we woke because of manualUnlock,
39+
// m_count may be zero and we must not decrement it.
40+
if(m_count > 0)
41+
{
42+
m_count.fetch_sub(1);
43+
}
44+
// Clear the manual unlock flag
4245
m_unlock = false;
46+
4347
return true;
4448
}
4549

@@ -52,7 +56,7 @@ class Semaphore
5256
private:
5357
std::mutex m_mtx;
5458
std::condition_variable m_cv;
55-
unsigned m_count = 0;
59+
std::atomic_uint m_count = 0;
5660
std::atomic_bool m_unlock = false;
5761
};
5862
} // namespace details
@@ -74,15 +78,19 @@ class TimerQueue
7478
public:
7579
TimerQueue()
7680
{
77-
m_th = std::thread([this] { run(); });
81+
m_finish.store(false);
82+
m_thread = std::thread([this]() { run(); });
7883
}
7984

8085
~TimerQueue()
8186
{
82-
m_finish = true;
87+
m_finish.store(true);
8388
cancelAll();
84-
m_checkWork.manualUnlock();
85-
m_th.join();
89+
90+
if(m_thread.joinable())
91+
{
92+
m_thread.join();
93+
}
8694
}
8795

8896
//! Adds a new timer
@@ -174,7 +182,7 @@ class TimerQueue
174182

175183
void run()
176184
{
177-
while(!m_finish)
185+
while(!m_finish.load())
178186
{
179187
auto end = calcWaitTime();
180188
if(end.first)
@@ -239,8 +247,8 @@ class TimerQueue
239247
}
240248

241249
details::Semaphore m_checkWork;
242-
std::thread m_th;
243-
bool m_finish = false;
250+
std::thread m_thread;
251+
std::atomic_bool m_finish = false;
244252
uint64_t m_idcounter = 0;
245253

246254
struct WorkItem

src/blackboard.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ Blackboard::getEntry(const std::string& key) const
5252
return rootBlackboard()->getEntry(key.substr(1, key.size() - 1));
5353
}
5454

55-
std::unique_lock<std::mutex> lock(mutex_);
56-
auto it = storage_.find(key);
57-
if(it != storage_.end())
5855
{
59-
return it->second;
56+
std::unique_lock<std::mutex> storage_lock(storage_mutex_);
57+
auto it = storage_.find(key);
58+
if(it != storage_.end())
59+
{
60+
return it->second;
61+
}
6062
}
6163
// not found. Try autoremapping
6264
if(auto parent = parent_bb_.lock())
@@ -130,7 +132,7 @@ std::vector<StringView> Blackboard::getKeys() const
130132

131133
void Blackboard::clear()
132134
{
133-
std::unique_lock<std::mutex> lock(mutex_);
135+
std::unique_lock<std::mutex> storage_lock(storage_mutex_);
134136
storage_.clear();
135137
}
136138

@@ -157,8 +159,10 @@ void Blackboard::createEntry(const std::string& key, const TypeInfo& info)
157159

158160
void Blackboard::cloneInto(Blackboard& dst) const
159161
{
160-
std::unique_lock lk1(mutex_);
161-
std::unique_lock lk2(dst.mutex_);
162+
// Lock both mutexes without risking lock-order inversion.
163+
std::unique_lock<std::mutex> lk1(storage_mutex_, std::defer_lock);
164+
std::unique_lock<std::mutex> lk2(dst.storage_mutex_, std::defer_lock);
165+
std::lock(lk1, lk2);
162166

163167
// keys that are not updated must be removed.
164168
std::unordered_set<std::string> keys_to_remove;
@@ -212,7 +216,7 @@ Blackboard::Ptr Blackboard::parent()
212216
std::shared_ptr<Blackboard::Entry> Blackboard::createEntryImpl(const std::string& key,
213217
const TypeInfo& info)
214218
{
215-
std::unique_lock<std::mutex> lock(mutex_);
219+
std::unique_lock<std::mutex> storage_lock(storage_mutex_);
216220
// This function might be called recursively, when we do remapping, because we move
217221
// to the top scope to find already existing entries
218222

tests/gtest_blackboard.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@ TEST(BlackboardTest, CheckTypeSafety)
294294
ASSERT_TRUE(is);
295295
}
296296

297+
#ifndef USE_SANITIZE_THREAD
298+
297299
TEST(BlackboardTest, AnyPtrLocked)
298300
{
299301
auto blackboard = Blackboard::create();
@@ -346,6 +348,7 @@ TEST(BlackboardTest, AnyPtrLocked)
346348
ASSERT_NE(cycles, value);
347349
}
348350
}
351+
#endif
349352

350353
TEST(BlackboardTest, SetStringView)
351354
{

0 commit comments

Comments
 (0)