reject sub-10ns ping intervals and clamp pinger jitter
This commit is contained in:
@@ -13,7 +13,7 @@ All configuration uses option functions applied at construction time. Validation
|
||||
| Session | `ReconnectDelay` | 2s | `0` | 0 means reconnect immediately |
|
||||
| Session | Retry | enabled | `WithRetryDisabled()` | Skips RetryConfig validation when disabled |
|
||||
| Connection | `WriteTimeout` | 30s | `0` | Per-message write deadline |
|
||||
| Connection | `PingInterval` | 20s | `0` | ±10% jitter applied per interval |
|
||||
| Connection | `PingInterval` | 20s | `0` | ±10% jitter applied per interval; positive minimum 10ns |
|
||||
| Connection | `IncomingBufferSize` | 100 | — | Must be positive |
|
||||
| Connection | `ErrorsBufferSize` | 10 | — | Must be positive |
|
||||
| Connection | `ReadLimit` | nil | 0 | Does nothing when unset, 0 = unlimited |
|
||||
@@ -56,7 +56,7 @@ These are passed to `transport.NewConnectionConfig`. Import `git.wisehodl.dev/ja
|
||||
|
||||
**`WithWriteTimeout(duration)`** — sets a per-message write deadline. Applied before every `WriteMessage` call. Set to zero to disable. Must not be negative.
|
||||
|
||||
**`WithPingInterval(duration)`** — sets the interval between WebSocket ping frames. A ±10% jitter is applied to each interval. Set to zero to disable pings entirely. Must not be negative.
|
||||
**`WithPingInterval(duration)`** — sets the interval between WebSocket ping frames. A ±10% jitter is applied to each interval. Set to zero to disable pings entirely. A positive interval must be at least 10ns; smaller positive values leave no room for the jitter window and are rejected.
|
||||
|
||||
**`WithIncomingBufferSize(int)`** — sets the capacity of the channel buffering inbound messages. Must be at least 1.
|
||||
|
||||
|
||||
@@ -115,6 +115,10 @@ func ValidateConnectionConfig(c ConnectionConfig) error {
|
||||
if c.PingInterval < 0 {
|
||||
return fmt.Errorf("invalid ping interval: %v", c.PingInterval)
|
||||
}
|
||||
if c.PingInterval > 0 && c.PingInterval < 10*time.Nanosecond {
|
||||
return fmt.Errorf("invalid ping interval: %v "+
|
||||
"(positive minimum is 10ns)", c.PingInterval)
|
||||
}
|
||||
if c.IncomingBufferSize < 1 {
|
||||
return fmt.Errorf("invalid incoming buffer size: %d",
|
||||
c.IncomingBufferSize)
|
||||
@@ -380,8 +384,12 @@ func (c *Connection) setupPongHandler() {
|
||||
func (c *Connection) startPinger() {
|
||||
defer c.shutdownInternal()
|
||||
|
||||
// Calculate 10% jitter window
|
||||
// Calculate 10% jitter window, clamped so Int63n can never panic
|
||||
// even if config validation is bypassed
|
||||
jitter := c.config.PingInterval / 10
|
||||
if jitter < 1 {
|
||||
jitter = 1
|
||||
}
|
||||
|
||||
for {
|
||||
offset := time.Duration(rand.Int63n(int64(jitter*2))) - jitter
|
||||
|
||||
@@ -322,6 +322,37 @@ func TestConnection_Heartbeat(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestStartPingerSubMinimumInterval(t *testing.T) {
|
||||
// Construct directly to bypass NewConnectionConfig validation; the
|
||||
// jitter clamp alone must prevent the Int63n(0) panic.
|
||||
conn := &Connection{
|
||||
socket: honeybeetest.NewMockSocket(),
|
||||
config: ConnectionConfig{PingInterval: 5 * time.Nanosecond},
|
||||
incoming: make(chan []byte, 1),
|
||||
errors: make(chan error, 1),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
|
||||
finished := make(chan struct{})
|
||||
go func() {
|
||||
defer close(finished)
|
||||
conn.startPinger()
|
||||
}()
|
||||
|
||||
// let several jittered intervals elapse
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
conn.Close()
|
||||
|
||||
honeybeetest.Eventually(t, func() bool {
|
||||
select {
|
||||
case <-finished:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}, "pinger should exit cleanly after Close")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Send
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user