davsclaus commented on code in PR #27910:
URL: https://github.com/apache/camel/pull/27910#discussion_r4286228686


##########
components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java:
##########
@@ -683,6 +697,37 @@ public OpcUaClient getOpcUaClient() {
         }
     }
 
+    /**
+     * Deletes and creates the subscription of the current connection again, 
inside the existing session, and creates
+     * its monitored items again. Does nothing if the subscription no longer 
belongs to the current connection or has
+     * already been recreated since the given id was observed. Falls back to 
rebuilding the connection if this fails.
+     */
+    private void recreateSubscription(final OpcUaSubscription subscription, 
final Optional<UInteger> id) {
+        lock.lock();
+        try {
+            if (this.disposed || this.connected == null || 
this.connected.manager != subscription
+                    || !subscription.getSubscriptionId().equals(id)) {
+                LOG.debug("Subscription {} is stale or already recreated", 
id.orElse(null));
+                return;
+            }
+            try {
+                subscription.delete();
+            } catch (final UaException e) {
+                LOG.debug("Failed to delete subscription {}", id.orElse(null), 
e);
+                subscription.reset();
+            }

Review Comment:
   **`reset()` here can leave a live subscription running on the server.**
   
   When the server no longer has the subscription, `delete()` gets 
`Bad_SubscriptionIdInvalid`. Milo then resets the object itself in 
`applyDeleteResponse` before it throws, so this `reset()` is a no-op in the 
case the PR targets. It only matters for other failures: a request timeout on a 
slow server, or a closed connection. In those cases the subscription may still 
exist on the server. After `reset()` + `create()`, the old one is abandoned in 
the same session, where it keeps its monitored items. It can stay alive as long 
as the session keeps sending Publish requests, and Milo discards its responses. 
PLC servers often have tight subscription limits, so a leak like this matters 
there.
   
   Suggestion: only continue in place when the server confirmed the 
subscription is gone. Otherwise let the outer `catch` fall back to 
`handleConnectionFailure`, which closes the session and the subscriptions on it:
   
   ```suggestion
               try {
                   subscription.delete();
               } catch (final UaException e) {
                   if (e.getStatusCode().getValue() != 
StatusCodes.Bad_SubscriptionIdInvalid) {
                       // the subscription may still exist on the server, 
rebuilding the connection closes its session
                       throw e;
                   }
                   LOG.debug("Subscription {} no longer exists on the server", 
id.orElse(null));
               }
   ```
   
   (For the `Bad_Timeout` path, Milo has already reset the subscription, so 
`delete()` returns without calling the server.)



##########
components/camel-milo/src/main/java/org/apache/camel/component/milo/client/internal/SubscriptionManager.java:
##########
@@ -683,6 +697,37 @@ public OpcUaClient getOpcUaClient() {
         }
     }
 
+    /**
+     * Deletes and creates the subscription of the current connection again, 
inside the existing session, and creates
+     * its monitored items again. Does nothing if the subscription no longer 
belongs to the current connection or has
+     * already been recreated since the given id was observed. Falls back to 
rebuilding the connection if this fails.
+     */
+    private void recreateSubscription(final OpcUaSubscription subscription, 
final Optional<UInteger> id) {
+        lock.lock();
+        try {
+            if (this.disposed || this.connected == null || 
this.connected.manager != subscription
+                    || !subscription.getSubscriptionId().equals(id)) {
+                LOG.debug("Subscription {} is stale or already recreated", 
id.orElse(null));
+                return;
+            }
+            try {
+                subscription.delete();
+            } catch (final UaException e) {
+                LOG.debug("Failed to delete subscription {}", id.orElse(null), 
e);
+                subscription.reset();
+            }
+            subscription.create();
+            if 
(!subscription.createMonitoredItems().stream().allMatch(MonitoredItemServiceOperationResult::isGood))
 {
+                throw new UaException(StatusCodes.Bad_UnexpectedError, "Failed 
to create the monitored items again");
+            }

Review Comment:
   **One item that cannot be created again rebuilds the whole connection.**
   
   `isGood()` needs both the service result and the operation result to be 
good, so one operation-level failure fails the whole check. For example, the PR 
description mentions a server configuration reload: a node removed by that 
reload comes back as `Bad_NodeIdUnknown`. The check then throws, and 
`handleConnectionFailure` closes the session for every consumer and producer on 
the connection. That is the cost the PR sets out to avoid. It also handles this 
case differently from `Connected.putSubscriptions`. There, operation-level 
failures are removed from the subscription, reported once to their consumer as 
a bad `DataValue` (outside the lock), and do not cause a rebuild. Only a bad 
*service* result rebuilds.
   
   Could the recreate reuse that handling, so that only a rejected service call 
falls back to the rebuild? Then the failed item should also be removed from 
`goodSubscriptions`, so that a later `deactivate` does not try to delete it. A 
test that removes one monitored node on the server before the recreate would 
cover it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to