From fa9569fdd928135579833f9e9ab704a58a4eba36 Mon Sep 17 00:00:00 2001 From: Urs Ritzmann Date: Thu, 3 Jun 2021 18:53:38 +0200 Subject: [PATCH] Issue 133 race in proxy destruct (#177) * fix construct/destruct order of mutex/data in AsnycCalls Destroying a mutex which might still be owned leads to undefined behaviour. It wasn't the case here but using the right order is more resistant to future bugs. * proxy AsnycCalls: don't leave map in unspecified state * fix 133: use thread-safe container for proxy interfaces list * proxy callback payload uses concrete signalData * proxy: revert adding the wrapper class InterfaceContainer It's no longer required and simplifies the locking logic. * Proxy: avoid additional lambda wrapper around signal callbacks As proposed in https://github.com/Kistler-Group/sdbus-cpp/pull/177#issuecomment-834439707 option 3. Still TODO: Avoid relying on std::map's non-invalidatable by adding just std::unique_ptr to the container. * proxy: add missing underscore prefix * proxy: put InterfaceData::SignalData into a unique_ptr This ways, we avoid relying on std::map's non-invalidatable references. * proxy: code style: get raw pointer directly * style: fix code style Co-authored-by: Stanislav Angelovic --- src/Proxy.cpp | 33 ++++++++++++++------------------- src/Proxy.h | 19 +++++++++++++++---- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Proxy.cpp b/src/Proxy.cpp index d7e5225..156205c 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -34,7 +34,7 @@ #include #include #include -#include +#include namespace sdbus::internal { @@ -160,7 +160,7 @@ void Proxy::registerSignalHandler( const std::string& interfaceName auto& interface = interfaces_[interfaceName]; - InterfaceData::SignalData signalData{std::move(signalHandler), nullptr}; + auto signalData = std::make_unique(*this, std::move(signalHandler), nullptr); auto insertionResult = interface.signals_.emplace(signalName, std::move(signalData)); auto inserted = insertionResult.second; @@ -182,13 +182,13 @@ void Proxy::registerSignalHandlers(sdbus::internal::IConnection& connection) for (auto& signalItem : signalsOnInterface) { const auto& signalName = signalItem.first; - auto& slot = signalItem.second.slot_; - slot = connection.registerSignalHandler( destination_ - , objectPath_ - , interfaceName - , signalName - , &Proxy::sdbus_signal_handler - , this ); + auto* signalData = signalItem.second.get(); + signalData->slot = connection.registerSignalHandler( destination_ + , objectPath_ + , interfaceName + , signalName + , &Proxy::sdbus_signal_handler + , signalData); } } } @@ -245,17 +245,12 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) { - auto* proxy = static_cast(userData); - assert(proxy != nullptr); - - auto message = Message::Factory::create(sdbusMessage, &proxy->connection_->getSdBusInterface()); - - // Note: The lookup can be optimized by using sorted vectors instead of associative containers - auto& callback = proxy->interfaces_[message.getInterfaceName()].signals_[message.getMemberName()].callback_; - assert(callback); - - callback(message); + auto* signalData = static_cast(userData); + assert(signalData != nullptr); + assert(signalData->callback); + auto message = Message::Factory::create(sdbusMessage, &signalData->proxy.connection_->getSdBusInterface()); + signalData->callback(message); return 0; } diff --git a/src/Proxy.h b/src/Proxy.h index 4b74882..ec004f6 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -98,10 +98,20 @@ namespace sdbus::internal { using SignalName = std::string; struct SignalData { - signal_handler callback_; - SlotPtr slot_; + SignalData(Proxy& proxy, signal_handler callback, SlotPtr slot) + : proxy(proxy) + , callback(std::move(callback)) + , slot(std::move(slot)) + {} + Proxy& proxy; + signal_handler callback; + // slot_ must be listed after callback_ to ensure that slot_ is destructed first. + // Destructing the slot_ will sd_bus_slot_unref() the callback. + // Only after sd_bus_slot_unref(), we can safely delete the callback. The bus mutex (SdBus::sdbusMutex_) + // ensures that sd_bus_slot_unref() and the callback execute sequentially. + SlotPtr slot; }; - std::map signals_; + std::map> signals_; }; std::map interfaces_; @@ -150,6 +160,7 @@ namespace sdbus::internal { { std::unique_lock lock(mutex_); auto asyncCallSlots = std::move(calls_); + calls_ = {}; lock.unlock(); // Releasing call slot pointer acquires global sd-bus mutex. We have to perform the release @@ -159,8 +170,8 @@ namespace sdbus::internal { } private: - std::unordered_map> calls_; std::mutex mutex_; + std::unordered_map> calls_; } pendingAsyncCalls_; };