Q: If you come to fix this bug, what are your ideas? List them out so that I can submit them to the official OpcLabs personnel A: EasyUAClient infinite loop bug fixing strategy The two key points generated by infinite loops: InternalConnect() still calls ConnectKeepAlive() when VirtualSession==null → Reset Publishers Top=false → SDK detects stop again → Loop Failed () resets Failed Tick every time called → RetralDelay mechanism fails → session reconnection can never be triggered Solution 1: Modify the execution order of InternalConnect() (recommended) Location: UAClient Subscription Base. InternalConnect() Problem: ConnectKeepAlive() is called before checking VirtualSession Current code logic: protected virtual void InternalConnect() { ConnectKeepAlive(); //← Problem: Unconditional reset of state UAVirtualSession virtualSession = ClientSession.GetVirtualSession(); if (virtualSession == null) { SubscriptionException = UAEngineException.Create(6401); return; //Although returned, the Publishing Top has been reset } // . .. Normal connection logic } Suggested modification: protected virtual void InternalConnect() { UAVirtualSession virtualSession = ClientSession.GetVirtualSession(); if (virtualSession == null) { SubscriptionWarnings.Clear(); SubscriptionException = UAEngineException.Create(6401); return; //Return directly without resetting any status } //Reset state only when VirtualSession exists ConnectKeepAlive(); // ... Normal connection logic } Effect: Disconnect the first step of the loop, and do not reset Publishing Top when the session no longer exists Solution 2: Prevent frequent resetting of Failed Ticks position: RetriableState.Failed() or UAClientSessionState.Failed() Problem: Each call to Failed () resets the Failed Tick, causing RetralDelay to never reach the current code logic: protected virtual void Failed(Exception exception) { FailureTick = _timing.GetTickCount64(); //← Problem: Unconditional reset } Suggested modification plan A - Add minimum reset interval private const long MinFailureResetInterval = 30000; //30 second minimum interval protected virtual void Failed(Exception exception) { long currentTick = _timing.GetTickCount64(); //Reset only when the first attempt fails or when the distance from the last reset exceeds the minimum interval if (FailureTick < 0 || (currentTick - FailureTick) >= MinFailureResetInterval) { FailureTick = currentTick; } } Suggested modification plan B - Reset only when the exception type changes: protected virtual void Failed(Exception exception) { //Reset Failed Tick only when the exception type/error code changes if (!IsSameFailure(_exception, exception)) { FailureTick = _timing.GetTickCount64(); } } private bool IsSameFailure(Exception oldException, Exception newException) { //Compare abnormal core attributes (such as error codes) rather than object references if (oldException == null || newException == null) return false; //Implement specific comparative logic .. } Effect: Ensure that the RetroalDelay mechanism works properly and session level reconnection can be triggered after a specified delay Solution 3: Avoid triggering the same state events repeatedly Location: UAClientSessionBase. Disconnect() Problem: When VirtualSession==null, each call will trigger the Disconnected event, even if the current state is already Disconnected. Current code logic: internal void Disconnect() { lock (base.Serialize) { if (GetVirtualSession() == null) { //Unconditional setting of status (even if already disconnected) ConnectedCondition = MakeConnectedCondition(ConnectionState.Disconnected, SessionException); return; } // ... } } Suggested modification: internal void Disconnect() { lock (base.Serialize) { if (GetVirtualSession() == null) { //Events are triggered only when there is a change in state if (ConnectedCondition.ConnectionState != ConnectionState.Disconnected) { ConnectedCondition = MakeConnectedCondition(ConnectionState.Disconnected, SessionException); } return; } // ... } } Effect: Prevent duplicate Disconnected events and reduce unnecessary event notifications Solution 4: Improve the comparison logic of SessionException Location: UAClientSessionBase. OnSessionExceptionChange() Problem: Currently using object reference comparison, different exception objects with the same error code will be considered as "changes" Suggestion: Implement content-based (error code) comparison instead of reference comparison private bool IsSessionExceptionChanged(Exception oldException, Exception newException) { if (ReferenceEquals(oldException, newException)) return false; if (oldException == null || newException == null) return true; //Compare error codes instead of object references var oldCode = GetErrorCode(oldException); var newCode = GetErrorCode(newException); return oldCode != newCode; } Effect: Avoid duplicate processing of abnormal objects with the same error code Recommended priority Priority repair strategy, impact scope, risk 1. Modify the execution order of InternalConnect() to directly break the loop low 2. Prevent frequent reset and recovery of Failed Tick with low RetralDelay mechanism 3. Avoid triggering the same state events repeatedly and reduce invalid events 4. Improve the comparison logic of SessionException and optimize the event triggering conditions Minimum repair plan (only modify one location) If only one modification is selected, it is recommended to modify InternalConnect(): // UAClientSubscriptionBase.cs - InternalConnect() //Move ConnectKeepAlive() to VirtualSession after checking protected virtual void InternalConnect() { UAVirtualSession virtualSession = ClientSession.GetVirtualSession(); if (virtualSession == null) { SubscriptionWarnings.Clear(); SubscriptionException = UAEngineException.Create(6401); return; } ConnectKeepAlive(); // move to here.. // . The rest of the code remains unchanged } This modification: Minimal modifications Directly breaking the infinite loop Logically, the subscription status needs to be reset only when the session exists Does not affect the normal connection process