From 0411d191b864035b4e214cbd9388dcf7caf9ea1a Mon Sep 17 00:00:00 2001 From: Rob Fisher Date: Thu, 27 Aug 2026 14:25:03 +0000 Subject: [PATCH 1/2] Only rescan the subtree that changed Rescanning from the root while openDAQ holds the changed component's lock deadlocks against anything holding another device's lock. --- .../websocket_streaming/ws_streaming_server.h | 2 ++ .../src/ws_streaming_server.cpp | 32 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_server.h b/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_server.h index 11c0f72..339ada2 100644 --- a/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_server.h +++ b/shared/libraries/websocket_streaming/include/websocket_streaming/ws_streaming_server.h @@ -106,6 +106,8 @@ class WsStreamingServer : public Server CoreEventArgsPtr& args); void rescan(); + void rescan(FolderPtr folder); + void pruneRemovedSignals(); private: diff --git a/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp b/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp index f9c36ac..76c910a 100644 --- a/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp @@ -194,6 +194,10 @@ void WsStreamingServer::addCapability() void WsStreamingServer::createListener(const SignalPtr& signal) { + // Without a descriptor there is no way to know which kind of handler to create. + if (!signal.getDescriptor().assigned()) + return; + SignalPtr domainSignal = signal.getDomainSignal(); if (domainSignal.assigned()) @@ -313,31 +317,36 @@ void WsStreamingServer::onComponentAdded( ComponentPtr& component, CoreEventArgsPtr& args) { - rescan(); + // openDAQ holds the changed component's lock while core event handlers run, so scanning + // from the root here deadlocks against a thread holding any other device's lock. + if (auto folder = component.asPtrOrNull(); folder.assigned()) + rescan(folder); } void WsStreamingServer::onComponentRemoved( ComponentPtr& component, CoreEventArgsPtr& args) { - rescan(); + pruneRemovedSignals(); } void WsStreamingServer::onComponentUpdateEnd( ComponentPtr& component, CoreEventArgsPtr& args) { - rescan(); + if (auto signal = component.asPtrOrNull(); signal.assigned()) + createListener(signal); } void WsStreamingServer::onAttributeChanged( ComponentPtr& component, CoreEventArgsPtr& args) { - rescan(); + if (auto signal = component.asPtrOrNull(); signal.assigned()) + createListener(signal); } -void WsStreamingServer::rescan() +void WsStreamingServer::pruneRemovedSignals() { auto it = _localSignals.begin(); while (it != _localSignals.end()) @@ -352,10 +361,19 @@ void WsStreamingServer::rescan() else ++it; } +} + +void WsStreamingServer::rescan() +{ + pruneRemovedSignals(); + rescan(_rootDevice); +} - auto items = _rootDevice.getItems(search::Recursive(search::Any())); +void WsStreamingServer::rescan(FolderPtr folder) +{ + auto items = folder.getItems(search::Recursive(search::Any())); for (const auto& item : items) - if (auto signal = item.asPtrOrNull(); signal.assigned() && signal.getDescriptor().assigned()) + if (auto signal = item.asPtrOrNull(); signal.assigned()) createListener(signal); } From 2b4fc87a0dc71cc7cbfad0ba7648ec29e59fcb60 Mon Sep 17 00:00:00 2001 From: Rob Fisher Date: Wed, 23 Sep 2026 11:37:16 +0000 Subject: [PATCH 2/2] Addresses comment left by copilot --- .../websocket_streaming/src/ws_streaming_server.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp b/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp index 76c910a..8340edc 100644 --- a/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp +++ b/shared/libraries/websocket_streaming/src/ws_streaming_server.cpp @@ -319,6 +319,7 @@ void WsStreamingServer::onComponentAdded( { // openDAQ holds the changed component's lock while core event handlers run, so scanning // from the root here deadlocks against a thread holding any other device's lock. + // The sender is the parent the component was added to, so its subtree covers the new child. if (auto folder = component.asPtrOrNull(); folder.assigned()) rescan(folder); } @@ -334,7 +335,16 @@ void WsStreamingServer::onComponentUpdateEnd( ComponentPtr& component, CoreEventArgsPtr& args) { - if (auto signal = component.asPtrOrNull(); signal.assigned()) + // Core events are muted for the whole subtree while it updates, so this is the only + // notification that anything below the component changed. An update both adds and + // removes signals. + if (auto folder = component.asPtrOrNull(); folder.assigned()) + { + pruneRemovedSignals(); + rescan(folder); + } + + else if (auto signal = component.asPtrOrNull(); signal.assigned()) createListener(signal); }