mirror of
https://github.com/kerberos-io/onvif.git
synced 2026-08-23 15:08:33 +00:00
Two gaps in the previous commit's guard. Strict inequality was not enough. A client timeout one millisecond above PullTimeout passed, and the test pinned that as valid — but the client ceiling also has to cover dial, TLS and the response transfer on top of the poll it outlasts, which on a cellular bearer is hundreds of milliseconds. Require minClientHeadroom (5s) above PullTimeout. The error was a bare fmt.Errorf, so callers could not tell it from the transient pull/renew/recreate failures they retry. A consumer that retries this one loops forever on a configuration that can never succeed. ErrInvalidOptions is a sentinel they can short-circuit on. Zero stays accepted: it is the SDK's default when a caller passes no client, so rejecting it would break every default consumer. The comment no longer claims that is safe — the caller interface documents that ctx cannot interrupt an in-flight SOAP call, so an unbounded client is the one case nothing can unwedge.
52 lines
1.8 KiB
Go
52 lines
1.8 KiB
Go
package stream
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/kerberos-io/onvif"
|
|
)
|
|
|
|
// ErrInvalidOptions marks a configuration that cannot succeed. Callers
|
|
// retry the pull/renew/recreate errors; retrying this one never helps,
|
|
// so it is a distinct sentinel they can short-circuit on.
|
|
var ErrInvalidOptions = errors.New("stream: invalid options")
|
|
|
|
// minClientHeadroom is how far http.Client.Timeout must exceed
|
|
// PullTimeout. The client ceiling covers dial, TLS and the response
|
|
// transfer on top of the poll it has to outlast, and starts before the
|
|
// camera has parsed the request; on a cellular bearer that overhead
|
|
// runs to hundreds of milliseconds.
|
|
const minClientHeadroom = 5 * time.Second
|
|
|
|
// validateClientTimeout rejects a client ceiling that cannot outlast
|
|
// the PullMessages long-poll plus minClientHeadroom.
|
|
//
|
|
// Zero means unbounded and is accepted: it is the SDK's default when a
|
|
// caller passes no client, so rejecting it would break every default
|
|
// consumer. Note it is not risk-free — the caller interface documents
|
|
// that ctx cannot interrupt an in-flight SOAP call, so only the client
|
|
// timeout can unwedge a stalled camera.
|
|
func validateClientTimeout(clientTimeout, pullTimeout time.Duration) error {
|
|
if clientTimeout == 0 || clientTimeout >= pullTimeout+minClientHeadroom {
|
|
return nil
|
|
}
|
|
return fmt.Errorf(
|
|
"%w: http.Client.Timeout (%s) must exceed PullTimeout (%s) by at least %s; PullMessages is a long-poll and the client would abort every quiet pull",
|
|
ErrInvalidOptions, clientTimeout, pullTimeout, minClientHeadroom)
|
|
}
|
|
|
|
// clientTimeoutOf reports the device's HTTP client ceiling, or 0 when
|
|
// the SDK is using its own default (unbounded) client.
|
|
func clientTimeoutOf(dev *onvif.Device) time.Duration {
|
|
if dev == nil {
|
|
return 0
|
|
}
|
|
c := dev.GetDeviceParams().HttpClient
|
|
if c == nil {
|
|
return 0
|
|
}
|
|
return c.Timeout
|
|
}
|