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)
     {