mirror of
https://github.com/kerberos-io/onvif.git
synced 2026-08-23 15:08:33 +00:00
PullMessages is a long-poll: the camera holds the connection open for up to PullTimeout waiting for an event. http.Client.Timeout bounds the whole exchange — dial, write, wait-for-headers — and starts before the camera has parsed the request, so a client ceiling equal to or below PullTimeout expires first on every interval with no event. The failure mode is quiet and easy to misread. Pulls fail continuously, but the stream stays alive because ReconnectAfterFailures recreates the subscription, and each recreate makes the camera replay its full property state. Events keep arriving, in bursts, on the reconnect cadence rather than when they happen — so it reads as a slow camera rather than a misconfiguration. Observed in the field with both values at 5s: every pull timed out, recovery landed after exactly 3 failures, and ~90 property-state events were replayed every 18s. Validated in NewStream, before the subscription call, since the config can only fail. A zero client timeout stays legal — unbounded is safe because the pull loop is already bounded by ctx.
45 lines
1.5 KiB
Go
45 lines
1.5 KiB
Go
package stream
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestValidateClientTimeout — PullMessages is a long-poll: the camera
|
|
// holds the connection open for PullTimeout waiting for an event. An
|
|
// http.Client.Timeout covers the whole exchange and starts before the
|
|
// camera has even parsed the request, so a client ceiling at or below
|
|
// PullTimeout loses the race on every quiet interval and the pull can
|
|
// only ever fail. This shipped once (both were 5s) and presented as a
|
|
// slow camera rather than a misconfiguration.
|
|
func TestValidateClientTimeout(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
client time.Duration
|
|
pull time.Duration
|
|
wantErr bool
|
|
}{
|
|
{"unbounded client is fine", 0, 30 * time.Second, false},
|
|
{"comfortable headroom", 40 * time.Second, 30 * time.Second, false},
|
|
{"strictly greater is accepted", 30*time.Second + time.Millisecond, 30 * time.Second, false},
|
|
{"equal timeouts always lose", 5 * time.Second, 5 * time.Second, true},
|
|
{"client below pull", 4 * time.Second, 30 * time.Second, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := validateClientTimeout(tt.client, tt.pull)
|
|
if tt.wantErr {
|
|
require.Error(t, err, "client=%s pull=%s must be rejected", tt.client, tt.pull)
|
|
assert.Contains(t, err.Error(), "PullTimeout",
|
|
"the error must name the option the caller has to change")
|
|
return
|
|
}
|
|
assert.NoError(t, err, "client=%s pull=%s must be accepted", tt.client, tt.pull)
|
|
})
|
|
}
|
|
}
|