Skip to content

Commit 7aab9b6

Browse files
jmachowinskiJanosch Machowinski
andauthored
Shutdown deadlock fix jazzy (#2887)
* fix: Don't deadlock if removing shutdown callbacks in a shutdown callback Signed-off-by: Janosch Machowinski <[email protected]> * refactor: Made fix API compatible Signed-off-by: Janosch Machowinski <[email protected]> --------- Signed-off-by: Janosch Machowinski <[email protected]> Co-authored-by: Janosch Machowinski <[email protected]>
1 parent 7860a8c commit 7aab9b6

File tree

1 file changed

+83
-15
lines changed

1 file changed

+83
-15
lines changed

rclcpp/src/rclcpp/context.cpp

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "rclcpp/context.hpp"
1616

17+
#include <map>
1718
#include <memory>
1819
#include <mutex>
1920
#include <sstream>
@@ -142,11 +143,52 @@ rclcpp_logging_output_handler(
142143
}
143144
} // extern "C"
144145

146+
/**
147+
* Global storage for pre and post shutdown recursive mutexes.
148+
* Note, this is a ABI compatibility hack.
149+
*/
150+
class MutexLookup
151+
{
152+
std::mutex m;
153+
154+
struct MutexHolder
155+
{
156+
std::recursive_mutex on_shutdown_callbacks_mutex_;
157+
std::recursive_mutex pre_shutdown_callbacks_mutex_;
158+
};
159+
160+
std::map<const Context *, std::unique_ptr<MutexHolder>> mutexMap;
161+
162+
public:
163+
MutexHolder & getMutexes(const Context *forContext)
164+
{
165+
auto it = mutexMap.find(forContext);
166+
if(it == mutexMap.end()) {
167+
it = mutexMap.emplace(forContext, std::make_unique<MutexHolder>()).first;
168+
}
169+
170+
return *(it->second);
171+
}
172+
173+
/**
174+
* Only supposed to be called on deletion of context
175+
*/
176+
void removeMutexes(const Context *forContext)
177+
{
178+
mutexMap.erase(forContext);
179+
}
180+
};
181+
182+
MutexLookup mutexStorage;
183+
145184
Context::Context()
146185
: rcl_context_(nullptr),
147186
shutdown_reason_(""),
148187
logging_mutex_(nullptr)
149-
{}
188+
{
189+
// allocate mutexes
190+
mutexStorage.getMutexes(this);
191+
}
150192

151193
Context::~Context()
152194
{
@@ -165,6 +207,9 @@ Context::~Context()
165207
} catch (...) {
166208
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "unhandled exception in ~Context()");
167209
}
210+
211+
// delete mutexes
212+
mutexStorage.removeMutexes(this);
168213
}
169214

170215
RCLCPP_LOCAL
@@ -311,9 +356,17 @@ Context::shutdown(const std::string & reason)
311356

312357
// call each pre-shutdown callback
313358
{
314-
std::lock_guard<std::mutex> lock{pre_shutdown_callbacks_mutex_};
315-
for (const auto & callback : pre_shutdown_callbacks_) {
316-
(*callback)();
359+
std::lock_guard<std::recursive_mutex> lock{mutexStorage.getMutexes(
360+
this).pre_shutdown_callbacks_mutex_};
361+
// callbacks may delete other callbacks during the execution,
362+
// therefore we need to save a copy and check before execution
363+
// if the next callback is still present
364+
auto cpy = pre_shutdown_callbacks_;
365+
for (const auto & callback : cpy) {
366+
auto it = std::find(pre_shutdown_callbacks_.begin(), pre_shutdown_callbacks_.end(), callback);
367+
if(it != pre_shutdown_callbacks_.end()) {
368+
(*callback)();
369+
}
317370
}
318371
}
319372

@@ -326,9 +379,17 @@ Context::shutdown(const std::string & reason)
326379
shutdown_reason_ = reason;
327380
// call each shutdown callback
328381
{
329-
std::lock_guard<std::mutex> lock(on_shutdown_callbacks_mutex_);
330-
for (const auto & callback : on_shutdown_callbacks_) {
331-
(*callback)();
382+
std::lock_guard<std::recursive_mutex> lock(mutexStorage.getMutexes(
383+
this).on_shutdown_callbacks_mutex_);
384+
// callbacks may delete other callbacks during the execution,
385+
// therefore we need to save a copy and check before execution
386+
// if the next callback is still present
387+
auto cpy = on_shutdown_callbacks_;
388+
for (const auto & callback : cpy) {
389+
auto it = std::find(on_shutdown_callbacks_.begin(), on_shutdown_callbacks_.end(), callback);
390+
if(it != on_shutdown_callbacks_.end()) {
391+
(*callback)();
392+
}
332393
}
333394
}
334395

@@ -399,10 +460,12 @@ Context::add_shutdown_callback(
399460
shutdown_type == ShutdownType::pre_shutdown || shutdown_type == ShutdownType::on_shutdown);
400461

401462
if constexpr (shutdown_type == ShutdownType::pre_shutdown) {
402-
std::lock_guard<std::mutex> lock(pre_shutdown_callbacks_mutex_);
463+
std::lock_guard<std::recursive_mutex> lock(mutexStorage.getMutexes(
464+
this).pre_shutdown_callbacks_mutex_);
403465
pre_shutdown_callbacks_.emplace_back(callback_shared_ptr);
404466
} else {
405-
std::lock_guard<std::mutex> lock(on_shutdown_callbacks_mutex_);
467+
std::lock_guard<std::recursive_mutex> lock(mutexStorage.getMutexes(
468+
this).on_shutdown_callbacks_mutex_);
406469
on_shutdown_callbacks_.emplace_back(callback_shared_ptr);
407470
}
408471

@@ -422,7 +485,7 @@ Context::remove_shutdown_callback(
422485
}
423486

424487
const auto remove_callback = [&callback_shared_ptr](auto & mutex, auto & callback_vector) {
425-
const std::lock_guard<std::mutex> lock(mutex);
488+
const std::lock_guard<std::recursive_mutex> lock(mutex);
426489
auto iter = callback_vector.begin();
427490
for (; iter != callback_vector.end(); iter++) {
428491
if ((*iter).get() == callback_shared_ptr.get()) {
@@ -433,16 +496,19 @@ Context::remove_shutdown_callback(
433496
return false;
434497
}
435498
callback_vector.erase(iter);
499+
436500
return true;
437501
};
438502

439503
static_assert(
440504
shutdown_type == ShutdownType::pre_shutdown || shutdown_type == ShutdownType::on_shutdown);
441505

442506
if constexpr (shutdown_type == ShutdownType::pre_shutdown) {
443-
return remove_callback(pre_shutdown_callbacks_mutex_, pre_shutdown_callbacks_);
507+
return remove_callback(mutexStorage.getMutexes(this).pre_shutdown_callbacks_mutex_,
508+
pre_shutdown_callbacks_);
444509
} else {
445-
return remove_callback(on_shutdown_callbacks_mutex_, on_shutdown_callbacks_);
510+
return remove_callback(mutexStorage.getMutexes(this).on_shutdown_callbacks_mutex_,
511+
on_shutdown_callbacks_);
446512
}
447513
}
448514

@@ -463,7 +529,7 @@ std::vector<rclcpp::Context::ShutdownCallback>
463529
Context::get_shutdown_callback() const
464530
{
465531
const auto get_callback_vector = [](auto & mutex, auto & callback_set) {
466-
const std::lock_guard<std::mutex> lock(mutex);
532+
const std::lock_guard<std::recursive_mutex> lock(mutex);
467533
std::vector<rclcpp::Context::ShutdownCallback> callbacks;
468534
for (auto & callback : callback_set) {
469535
callbacks.push_back(*callback);
@@ -475,9 +541,11 @@ Context::get_shutdown_callback() const
475541
shutdown_type == ShutdownType::pre_shutdown || shutdown_type == ShutdownType::on_shutdown);
476542

477543
if constexpr (shutdown_type == ShutdownType::pre_shutdown) {
478-
return get_callback_vector(pre_shutdown_callbacks_mutex_, pre_shutdown_callbacks_);
544+
return get_callback_vector(mutexStorage.getMutexes(this).pre_shutdown_callbacks_mutex_,
545+
pre_shutdown_callbacks_);
479546
} else {
480-
return get_callback_vector(on_shutdown_callbacks_mutex_, on_shutdown_callbacks_);
547+
return get_callback_vector(mutexStorage.getMutexes(this).on_shutdown_callbacks_mutex_,
548+
on_shutdown_callbacks_);
481549
}
482550
}
483551

0 commit comments

Comments
 (0)