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
- Connect with AutoReconnect (default) and one subscription.
- Use a server that returns
StatusBadTimeout on Publish when TimeoutHint elapses with no notifications (Siemens OPC UA did this with revised keepalive 300 × 100ms).
- Leave the subscription idle (no data changes).
- Observe
publish() ignore BadTimeout, then monitor createSecureChannel.
Idle
PublishRequestreturningStatusBadTimeout(0x800A0000) is a normal keepalive miss.publish()already ignores it and sends another Publish. The secure-channel dispatcher still copies thatServiceResultontosechanErr, andmonitortreats it as a dead channel (createSecureChannel).Related: this is what triggers the
restoreSessionpath 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/ServiceResultbehavior in v0.8.0), AutoReconnect, one subscription.Server revised publishing interval 100ms and max keepalive 300 → Publish
TimeoutHint30s.Cycle repeats on the keepalive interval. Client looks fine between blips; any in-flight Read/Write during the ~100ms restore can fail (
StatusBadSessionIDInvalidwhile the session pointer is cleared forActivateSession).Root cause
uasc.Receivesetsmsg.Errfrom a non-OKResponseHeader.ServiceResult:dispatcheralways doeserrch <- msg.Err.monitoronly continues onStatusBadNoSubscription, then reconnects.client_sub.goalready documentsStatusBadTimeoutas ignore-and-continue for the publish loop.Suggested fix
Skip
StatusBadTimeoutinmonitorthe same way asStatusBadNoSubscription(leave it topublish()). Alternatively, do not put PublishBadTimeoutonerrch.Repro
StatusBadTimeouton Publish whenTimeoutHintelapses with no notifications (Siemens OPC UA did this with revised keepalive 300 × 100ms).publish()ignoreBadTimeout, thenmonitorcreateSecureChannel.