From cd93b9ad44603ef6508f986364ddc43a94286028 Mon Sep 17 00:00:00 2001 From: Madana Gopal Date: Tue, 28 Jul 2026 08:40:08 +0530 Subject: [PATCH 1/2] RDKEMW-21185: Add exitcode on container stopped event --- AppInfrastructure/Public/Dobby/IDobbyProxy.h | 2 +- client/lib/include/DobbyProxy.h | 3 ++- client/lib/source/DobbyProxy.cpp | 20 +++++++++++++------- client/tool/source/Main.cpp | 1 + 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/AppInfrastructure/Public/Dobby/IDobbyProxy.h b/AppInfrastructure/Public/Dobby/IDobbyProxy.h index 13b3c851..89a9855c 100644 --- a/AppInfrastructure/Public/Dobby/IDobbyProxy.h +++ b/AppInfrastructure/Public/Dobby/IDobbyProxy.h @@ -192,7 +192,7 @@ class IDobbyProxy : public AICommon::Notifier } public: - typedef std::function StateChangeListener; + typedef std::function StateChangeListener; virtual int registerListener(const StateChangeListener &listener, const void* cbParams) = 0; diff --git a/client/lib/include/DobbyProxy.h b/client/lib/include/DobbyProxy.h index 72fa0a24..1bf51c81 100644 --- a/client/lib/include/DobbyProxy.h +++ b/client/lib/include/DobbyProxy.h @@ -188,12 +188,13 @@ class DobbyProxy : public IDobbyProxy { } 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; diff --git a/client/lib/source/DobbyProxy.cpp b/client/lib/source/DobbyProxy.cpp index a98ce860..44a720c1 100644 --- a/client/lib/source/DobbyProxy.cpp +++ b/client/lib/source/DobbyProxy.cpp @@ -27,6 +27,7 @@ #include +#include #include @@ -89,7 +90,8 @@ 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); - const AI_IPC::Signal stoppedSignal(objectName, DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_STOPPED); + // Subscribe to stopped-with-status — carries exit code directly alongside descriptor and id. + const AI_IPC::Signal stoppedSignal(objectName, DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_STOPPED_WITH_STATUS); const AI_IPC::SignalHandler stoppedHandler(std::bind(&DobbyProxy::onContainerStoppedEvent, this, std::placeholders::_1)); mContainerStoppedSignal = mIpcService->registerSignalHandler(stoppedSignal, stoppedHandler); @@ -244,20 +246,24 @@ void DobbyProxy::onContainerStoppedEvent(const AI_IPC::VariantList& args) { AI_LOG_FN_ENTRY(); - // the event should container two args; container descriptor and id + // 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)) + 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); + DOBBY_CTRL_INTERFACE, DOBBY_CTRL_EVENT_STOPPED_WITH_STATUS); } else { - // ping off an event + const int32_t exitCode = WIFEXITED(rawStatus) ? WEXITSTATUS(rawStatus) : -1; + std::lock_guard locker(mStateChangeLock); - mStateChangeQueue.emplace_back(StateChangeEvent::ContainerStopped, descriptor, id); + StateChangeEvent ev(StateChangeEvent::ContainerStopped, descriptor, id); + ev.exitCode = exitCode; + mStateChangeQueue.push_back(ev); mStateChangeCond.notify_all(); } @@ -1507,7 +1513,7 @@ void DobbyProxy::containerStateChangeThread() const StateChangeListener& callback = handler.second.first; const void* cbParams = handler.second.second; if (callback) - callback(event.descriptor, event.name, state, cbParams); + callback(event.descriptor, event.name, state, event.exitCode, cbParams); } } diff --git a/client/tool/source/Main.cpp b/client/tool/source/Main.cpp index 8ab34425..e49a9392 100644 --- a/client/tool/source/Main.cpp +++ b/client/tool/source/Main.cpp @@ -92,6 +92,7 @@ std::promise promise; */ void containerStopCallback(int32_t cd, const std::string &containerId, IDobbyProxyEvents::ContainerState state, + int32_t exitCode, const void *params) { const std::string *id = static_cast(params); From 2d4a3f643f81db5b57d7f72568de3b110c8d52cc Mon Sep 17 00:00:00 2001 From: Madana Gopal Date: Tue, 28 Jul 2026 08:40:08 +0530 Subject: [PATCH 2/2] RDKEMW-21185: Add exitcode on container stopped event --- AppInfrastructure/Public/Dobby/IDobbyProxy.h | 8 +- client/lib/include/DobbyProxy.h | 15 ++- client/lib/source/DobbyProxy.cpp | 101 ++++++++++++++++--- 3 files changed, 109 insertions(+), 15 deletions(-) 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..1323c7ca 100644 --- a/client/lib/include/DobbyProxy.h +++ b/client/lib/include/DobbyProxy.h @@ -125,6 +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; + + int registerListenerWithStatus(const StateChangeWithStatusListener &listener, const void* cbParams) override; + void unregisterListenerWithStatus(int id) override; void unregisterListener(int tag) override; @@ -155,6 +159,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 +178,7 @@ class DobbyProxy : public IDobbyProxy private: std::string mContainerStartedSignal; std::string mContainerStoppedSignal; + std::string mContainerStoppedWithStatusSignal; private: std::thread mStateChangeThread; @@ -181,19 +187,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 +209,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); + } } }