event: prevent potential deadlock
In the `obtain_lock<true>()` path there is a race in which another
thread can run after the `run_condition.signal()` call before the
mutex lock call is executed. This other thread could run a significant
amount of code, including coming around to be back in the `run_one`
path.
To prevent this behavior, add a stage to the lock so that there is an
ordering such that the signalling thread is ensured to get the primary
lock before the signalled thread.
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: Ia464a95ed55246a67ed87601275a6e1fa8b16ba3
diff --git a/include/sdbusplus/event.hpp b/include/sdbusplus/event.hpp
index 4a59756..951db43 100644
--- a/include/sdbusplus/event.hpp
+++ b/include/sdbusplus/event.hpp
@@ -136,6 +136,11 @@
// Safely get the lock, possibly signaling the running 'run_one' to exit.
template <bool Signal = true>
std::unique_lock<std::recursive_mutex> obtain_lock();
+ // When obtain_lock signals 'run_one' to exit, we want a priority of
+ // obtaining the lock so that the 'run_one' task doesn't run and reclaim
+ // the lock before the signaller can run. This stage is first obtained
+ // prior to getting the primary lock in order to set an order.
+ std::mutex obtain_lock_stage{};
};
} // namespace event
diff --git a/src/event.cpp b/src/event.cpp
index 209bac6..a16f25a 100644
--- a/src/event.cpp
+++ b/src/event.cpp
@@ -176,6 +176,8 @@
template <bool Signal>
std::unique_lock<std::recursive_mutex> event::obtain_lock()
{
+ std::unique_lock stage{this->obtain_lock_stage};
+
std::unique_lock<std::recursive_mutex> l{this->lock, std::defer_lock_t()};
if constexpr (Signal)
{