diff --git a/AppInfrastructure/Public/Dobby/IDobbyProxy.h b/AppInfrastructure/Public/Dobby/IDobbyProxy.h index 13b3c851..6369711c 100644 --- a/AppInfrastructure/Public/Dobby/IDobbyProxy.h +++ b/AppInfrastructure/Public/Dobby/IDobbyProxy.h @@ -192,12 +192,18 @@ class IDobbyProxy : public AICommon::Notifier } public: + // Listener for STOPPED events — no exit code, backward-compatible. typedef std::function StateChangeListener; - virtual int registerListener(const StateChangeListener &listener, const void* cbParams) = 0; + // Listener for STOPPED_WITH_STATUS events — includes exit code. + typedef std::function StateChangeWithStatusListener; + virtual int registerListener(const StateChangeListener &listener, const void* cbParams) = 0; virtual void unregisterListener(int tag) = 0; + virtual int registerListenerWithStatus(const StateChangeWithStatusListener &listener, const void* cbParams) = 0; + virtual void unregisterListenerWithStatus(int tag) = 0; + #if (AI_BUILD_TYPE == AI_DEBUG) diff --git a/client/lib/include/DobbyProxy.h b/client/lib/include/DobbyProxy.h index 72fa0a24..6885750b 100644 --- a/client/lib/include/DobbyProxy.h +++ b/client/lib/include/DobbyProxy.h @@ -125,8 +125,10 @@ class DobbyProxy : public IDobbyProxy int getContainerState(int32_t cd) const override; int registerListener(const StateChangeListener &listener, const void* cbParams) override; + void unregisterListener(int id) override; - void unregisterListener(int tag) override; + int registerListenerWithStatus(const StateChangeWithStatusListener &listener, const void* cbParams) override; + void unregisterListenerWithStatus(int id) override; std::string getContainerInfo(int32_t descriptor) const override; @@ -155,6 +157,7 @@ class DobbyProxy : public IDobbyProxy private: void onContainerStartedEvent(const AI_IPC::VariantList& args); void onContainerStoppedEvent(const AI_IPC::VariantList& args); + void onContainerStoppedWithStatusEvent(const AI_IPC::VariantList& args); void onContainerHibernatedEvent(const AI_IPC::VariantList& args); void onContainerAwokenEvent(const AI_IPC::VariantList& args); @@ -173,6 +176,7 @@ class DobbyProxy : public IDobbyProxy private: std::string mContainerStartedSignal; std::string mContainerStoppedSignal; + std::string mContainerStoppedWithStatusSignal; private: std::thread mStateChangeThread; @@ -181,19 +185,20 @@ class DobbyProxy : public IDobbyProxy struct StateChangeEvent { - enum Type { Terminate, ContainerStarted, ContainerStopped, ContainerHibernated, ContainerAwoken }; + enum Type { Terminate, ContainerStarted, ContainerStopped, ContainerStoppedWithStatus, ContainerHibernated, ContainerAwoken }; explicit StateChangeEvent(Type type_) : type(type_), descriptor(-1) { } StateChangeEvent(Type type_, int32_t descriptor_, const std::string& name_) - : type(type_), descriptor(descriptor_), name(name_) + : type(type_), descriptor(descriptor_), name(name_), exitCode(-1) { } Type type; int32_t descriptor; std::string name; + int32_t exitCode; }; std::deque mStateChangeQueue; @@ -202,6 +207,10 @@ class DobbyProxy : public IDobbyProxy AICommon::IDGenerator<8> mListenerIdGen; std::map> mListeners; + std::mutex mStatusListenersLock; + AICommon::IDGenerator<8> mStatusListenerIdGen; + std::map> mStatusListeners; + }; diff --git a/client/lib/source/DobbyProxy.cpp b/client/lib/source/DobbyProxy.cpp index a98ce860..b5619280 100644 --- a/client/lib/source/DobbyProxy.cpp +++ b/client/lib/source/DobbyProxy.cpp @@ -27,6 +27,7 @@ #include +#include #include @@ -89,10 +90,16 @@ DobbyProxy::DobbyProxy(const std::shared_ptr& ipcService, const AI_IPC::SignalHandler startedHandler(std::bind(&DobbyProxy::onContainerStartedEvent, this, std::placeholders::_1)); mContainerStartedSignal = mIpcService->registerSignalHandler(startedSignal, startedHandler); + // Subscribe to plain STOPPED to serve StateChangeListener clients (no exit code). const AI_IPC::Signal stoppedSignal(objectName, DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_STOPPED); const AI_IPC::SignalHandler stoppedHandler(std::bind(&DobbyProxy::onContainerStoppedEvent, this, std::placeholders::_1)); mContainerStoppedSignal = mIpcService->registerSignalHandler(stoppedSignal, stoppedHandler); + // Subscribe to STOPPED_WITH_STATUS to serve StateChangeWithStatusListener clients (with exit code). + const AI_IPC::Signal stoppedWithStatusSignal(objectName, DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_STOPPED_WITH_STATUS); + const AI_IPC::SignalHandler stoppedWithStatusHandler(std::bind(&DobbyProxy::onContainerStoppedWithStatusEvent, this, std::placeholders::_1)); + mContainerStoppedWithStatusSignal = mIpcService->registerSignalHandler(stoppedWithStatusSignal, stoppedWithStatusHandler); + const AI_IPC::Signal hibernatedSignal(objectName, DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_HIBERNATED); const AI_IPC::SignalHandler hibernatedHandler(std::bind(&DobbyProxy::onContainerHibernatedEvent, this, std::placeholders::_1)); mContainerStartedSignal = mIpcService->registerSignalHandler(hibernatedSignal, hibernatedHandler); @@ -101,7 +108,7 @@ DobbyProxy::DobbyProxy(const std::shared_ptr& ipcService, const AI_IPC::SignalHandler awokenHandler(std::bind(&DobbyProxy::onContainerAwokenEvent, this, std::placeholders::_1)); mContainerStartedSignal = mIpcService->registerSignalHandler(awokenSignal, awokenHandler); - if (mContainerStartedSignal.empty() || mContainerStoppedSignal.empty()) + if (mContainerStartedSignal.empty() || mContainerStoppedSignal.empty() || mContainerStoppedWithStatusSignal.empty()) { AI_LOG_ERROR("failed to register dbus signal listeners"); } @@ -127,6 +134,9 @@ DobbyProxy::~DobbyProxy() if (!mContainerStoppedSignal.empty()) mIpcService->unregisterHandler(mContainerStoppedSignal); + if (!mContainerStoppedWithStatusSignal.empty()) + mIpcService->unregisterHandler(mContainerStoppedWithStatusSignal); + // flush the ipc service to guarantee the signal handlers aren't going to // be called after we're done mIpcService->flush(); @@ -196,6 +206,36 @@ void DobbyProxy::unregisterListener(int id) mListenerIdGen.put(id); } +int DobbyProxy::registerListenerWithStatus(const StateChangeWithStatusListener &listener, const void* cbParams) +{ + std::lock_guard locker(mStatusListenersLock); + + int id = mStatusListenerIdGen.get(); + if (id < 0) + { + AI_LOG_ERROR("too many status listeners installed"); + return -1; + } + + mStatusListeners.emplace(id, std::make_pair(listener, cbParams)); + return id; +} + +void DobbyProxy::unregisterListenerWithStatus(int id) +{ + std::lock_guard locker(mStatusListenersLock); + + auto it = mStatusListeners.find(id); + if (it == mStatusListeners.end()) + { + AI_LOG_ERROR("no status listener installed with id %d", id); + return; + } + + mStatusListeners.erase(it); + mStatusListenerIdGen.put(id); +} + // ----------------------------------------------------------------------------- /** * @brief Called when a org.rdk.dobby.ctrl1.Started event is received from @@ -244,7 +284,7 @@ void DobbyProxy::onContainerStoppedEvent(const AI_IPC::VariantList& args) { AI_LOG_FN_ENTRY(); - // the event should container two args; container descriptor and id + // STOPPED carries two args: container descriptor and id (no exit code). int32_t descriptor; std::string id; @@ -255,7 +295,6 @@ void DobbyProxy::onContainerStoppedEvent(const AI_IPC::VariantList& args) } else { - // ping off an event std::lock_guard locker(mStateChangeLock); mStateChangeQueue.emplace_back(StateChangeEvent::ContainerStopped, descriptor, id); mStateChangeCond.notify_all(); @@ -264,6 +303,33 @@ void DobbyProxy::onContainerStoppedEvent(const AI_IPC::VariantList& args) AI_LOG_FN_EXIT(); } +void DobbyProxy::onContainerStoppedWithStatusEvent(const AI_IPC::VariantList& args) +{ + AI_LOG_FN_ENTRY(); + + // STOPPED_WITH_STATUS carries three args: descriptor, id, raw waitpid status. + int32_t descriptor; + std::string id; + int32_t rawStatus; + + if (!AI_IPC::parseVariantList(args, &descriptor, &id, &rawStatus)) + { + AI_LOG_ERROR("failed to read all args from %s.%s signal", + DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_STOPPED_WITH_STATUS); + } + else + { + StateChangeEvent ev(StateChangeEvent::ContainerStoppedWithStatus, descriptor, id); + ev.exitCode = WIFEXITED(rawStatus) ? WEXITSTATUS(rawStatus) : -1; + + std::lock_guard locker(mStateChangeLock); + mStateChangeQueue.push_back(ev); + mStateChangeCond.notify_all(); + } + + AI_LOG_FN_EXIT(); +} + // ----------------------------------------------------------------------------- /** * @brief Called when a org.rdk.dobby.ctrl1.Hibernated event is received from @@ -1498,16 +1564,27 @@ void DobbyProxy::containerStateChangeThread() notify(&IDobbyProxyEvents::containerStateChanged, event.descriptor, event.name, state); - // need to hold the lock before searching - std::lock_guard listenerLocker(mListenersLock); - - // check if we have any listener interested in this service - for (const std::pair>& handler : mListeners) + if (event.type == StateChangeEvent::ContainerStoppedWithStatus) + { + // Notify status listeners (include exit code) + std::lock_guard listenerLocker(mStatusListenersLock); + for (const auto& handler : mStatusListeners) + { + const StateChangeWithStatusListener& cb = handler.second.first; + if (cb) + cb(event.descriptor, event.name, state, event.exitCode, handler.second.second); + } + } + else { - const StateChangeListener& callback = handler.second.first; - const void* cbParams = handler.second.second; - if (callback) - callback(event.descriptor, event.name, state, cbParams); + // Notify standard listeners (no exit code) + std::lock_guard listenerLocker(mListenersLock); + for (const auto& handler : mListeners) + { + const StateChangeListener& cb = handler.second.first; + if (cb) + cb(event.descriptor, event.name, state, handler.second.second); + } } }