Skip to content

Publish StatusBadTimeout on sechanErr forces reconnect #908

Description

@aschiffmann

Idle PublishRequest returning StatusBadTimeout (0x800A0000) is a normal keepalive miss. publish() already ignores it and sends another Publish. The secure-channel dispatcher still copies that ServiceResult onto sechanErr, and monitor treats it as a dead channel (createSecureChannel).

Related: this is what triggers the restoreSession path in #879 / #907 on the server we tested. Closest existing report is #805 (idle subscription, ~1 min, ServiceFault on Publish → reconnect).

What happens

gopcua v0.9.1 (same errch / ServiceResult behavior in v0.8.0), AutoReconnect, one subscription.

Server revised publishing interval 100ms and max keepalive 300 → Publish TimeoutHint 30s.

debug: send *ua.PublishRequest
debug: recv *ua.PublishResponse  ServiceResult=0  NotificationData=[]   // keepalive
debug: send *ua.PublishRequest
debug: err: StatusBadTimeout (0x800A0000)
debug: publish: error: ignoring: StatusBadTimeout
debug: client: monitor: disconnected
debug: client: monitor: auto-reconnecting
debug: client: monitor: action: createSecureChannel

Cycle repeats on the keepalive interval. Client looks fine between blips; any in-flight Read/Write during the ~100ms restore can fail (StatusBadSessionIDInvalid while the session pointer is cleared for ActivateSession).

Root cause

uasc.Receive sets msg.Err from a non-OK ResponseHeader.ServiceResult:

if resp := msg.Response(); resp != nil {
    if status := resp.Header().ServiceResult; status != ua.StatusOK {
        msg.Err = status
        return msg
    }
}

dispatcher always does errch <- msg.Err. monitor only continues on StatusBadNoSubscription, then reconnects.

client_sub.go already documents StatusBadTimeout as ignore-and-continue for the publish loop.

Suggested fix

Skip StatusBadTimeout in monitor the same way as StatusBadNoSubscription (leave it to publish()). Alternatively, do not put Publish BadTimeout on errch.

Repro

  1. Connect with AutoReconnect (default) and one subscription.
  2. Use a server that returns StatusBadTimeout on Publish when TimeoutHint elapses with no notifications (Siemens OPC UA did this with revised keepalive 300 × 100ms).
  3. Leave the subscription idle (no data changes).
  4. Observe publish() ignore BadTimeout, then monitor createSecureChannel.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions