various improvements

This commit is contained in:
Jay
2026-04-24 10:59:33 -04:00
parent e32bbc99d8
commit 4ac2c488ad
14 changed files with 41 additions and 32 deletions
+5 -3
View File
@@ -59,15 +59,17 @@ type InboundPoolEventKind = inbound.PoolEventKind
const ( const (
InboundEventDisconnected = inbound.EventDisconnected InboundEventDisconnected = inbound.EventDisconnected
InboundEventDropped = inbound.EventDropped InboundEventDroppedClose = inbound.EventDroppedClose
InboundEventEvicted = inbound.EventEvicted InboundEventDroppedError = inbound.EventDroppedError
InboundEventEvictedPolicy = inbound.EventEvictedPolicy
) )
// Inbound Worker exit kinds // Inbound Worker exit kinds
const ( const (
InboundExitDisconnected = inbound.ExitDisconnected InboundExitDisconnected = inbound.ExitDisconnected
InboundExitError = inbound.ExitError InboundExitUnexpectedClose = inbound.ExitUnexpectedClose
InboundExitReadError = inbound.ExitReadError
InboundExitPolicy = inbound.ExitPolicy InboundExitPolicy = inbound.ExitPolicy
) )
-1
View File
@@ -1,4 +1,3 @@
// responderpool/config.go
package inbound package inbound
import ( import (
+6 -4
View File
@@ -17,14 +17,16 @@ type PoolEventKind string
const ( const (
EventDisconnected PoolEventKind = "disconnected" EventDisconnected PoolEventKind = "disconnected"
EventDropped PoolEventKind = "dropped" EventDroppedClose PoolEventKind = "dropped_close"
EventEvicted PoolEventKind = "evicted" EventDroppedError PoolEventKind = "dropped_error"
EventEvictedPolicy PoolEventKind = "evicted_policy"
) )
var workerToPoolEvent = map[WorkerExitKind]PoolEventKind{ var workerToPoolEvent = map[WorkerExitKind]PoolEventKind{
ExitDisconnected: EventDisconnected, ExitDisconnected: EventDisconnected,
ExitError: EventDropped, ExitUnexpectedClose: EventDroppedClose,
ExitPolicy: EventEvicted, ExitReadError: EventDroppedError,
ExitPolicy: EventEvictedPolicy,
} }
type OnExitFunction func(kind WorkerExitKind) type OnExitFunction func(kind WorkerExitKind)
+2 -2
View File
@@ -333,7 +333,7 @@ func TestPoolEvents(t *testing.T) {
Err: &websocket.CloseError{Code: websocket.CloseProtocolError}, Err: &websocket.CloseError{Code: websocket.CloseProtocolError},
} }
expectEvent(t, pool.Events(), "peer-1", EventDropped) expectEvent(t, pool.Events(), "peer-1", EventDroppedClose)
honeybeetest.Eventually(t, func() bool { honeybeetest.Eventually(t, func() bool {
return !slices.Contains(pool.Peers(), "peer-1") return !slices.Contains(pool.Peers(), "peer-1")
@@ -353,7 +353,7 @@ func TestPoolEvents(t *testing.T) {
socket, _, _ := honeybeetest.SetupTestSocket(t) socket, _, _ := honeybeetest.SetupTestSocket(t)
pool.Add("peer-1", socket) pool.Add("peer-1", socket)
expectEvent(t, pool.Events(), "peer-1", EventEvicted) expectEvent(t, pool.Events(), "peer-1", EventEvictedPolicy)
honeybeetest.Eventually(t, func() bool { honeybeetest.Eventually(t, func() bool {
return !slices.Contains(pool.Peers(), "peer-1") return !slices.Contains(pool.Peers(), "peer-1")
+11 -4
View File
@@ -21,7 +21,8 @@ type WorkerExitKind string
const ( const (
ExitDisconnected WorkerExitKind = "disconnected" ExitDisconnected WorkerExitKind = "disconnected"
ExitError WorkerExitKind = "error" ExitUnexpectedClose WorkerExitKind = "unexpected_close"
ExitReadError WorkerExitKind = "read_error"
ExitPolicy WorkerExitKind = "policy" ExitPolicy WorkerExitKind = "policy"
) )
@@ -147,7 +148,7 @@ func RunReader(
var err error var err error
// determine exit kind // determine exit kind
// by default, the peer dropped unexpectedly // by default, the peer dropped unexpectedly
kind := ExitError kind := ExitUnexpectedClose
select { select {
// the peer-side error is sent before the connection is closed, // the peer-side error is sent before the connection is closed,
// so a non-blocking call here is correct // so a non-blocking call here is correct
@@ -156,11 +157,17 @@ func RunReader(
if errors.Is(err, transport.ErrPeerClosedClean) { if errors.Is(err, transport.ErrPeerClosedClean) {
kind = ExitDisconnected kind = ExitDisconnected
} }
if errors.Is(err, transport.ErrPeerClosedUnexpected) {
kind = ExitUnexpectedClose
}
if errors.Is(err, transport.ErrReadError) {
kind = ExitReadError
}
default: default:
} }
if logger != nil { if logger != nil {
if kind == ExitError { if kind == ExitUnexpectedClose || kind == ExitReadError {
logger.Error("reader: peer dropped", "event", kind, "error", err) logger.Error("reader: peer dropped", "event", kind, "error", err)
} else { } else {
logger.Info("reader: peer disconnected", "event", kind) logger.Info("reader: peer disconnected", "event", kind)
@@ -280,7 +287,7 @@ func RunWatchdog(
case <-timer.C: case <-timer.C:
// signal peer is inactive // signal peer is inactive
if logger != nil { if logger != nil {
logger.Info("watchdog: peer is inactive") logger.Info("watchdog: no activity observed")
} }
onInactive(ExitPolicy) onInactive(ExitPolicy)
return return
+2 -2
View File
@@ -140,7 +140,7 @@ func TestRunReader(t *testing.T) {
} }
}, "expected onPeerClose") }, "expected onPeerClose")
assert.Equal(t, ExitError, gotKind) assert.Equal(t, ExitUnexpectedClose, gotKind)
}) })
t.Run("read error calls onPeerClose with ExitUnexpectedDrop", func(t *testing.T) { t.Run("read error calls onPeerClose with ExitUnexpectedDrop", func(t *testing.T) {
@@ -177,7 +177,7 @@ func TestRunReader(t *testing.T) {
} }
}, "expected onPeerClose") }, "expected onPeerClose")
assert.Equal(t, ExitError, gotKind) assert.Equal(t, ExitReadError, gotKind)
}) })
t.Run("ctx.Done exits without calling onPeerClose", func(t *testing.T) { t.Run("ctx.Done exits without calling onPeerClose", func(t *testing.T) {
+1 -1
View File
@@ -115,7 +115,7 @@ func TestWorkerStart(t *testing.T) {
honeybeetest.Eventually(t, func() bool { honeybeetest.Eventually(t, func() bool {
val := v.exitKind.Load() val := v.exitKind.Load()
return val != nil && val.(WorkerExitKind) == ExitError return val != nil && val.(WorkerExitKind) == ExitUnexpectedClose
}, "expected ExitUnexpectedDrop") }, "expected ExitUnexpectedDrop")
}) })
+1 -1
View File
@@ -228,7 +228,7 @@ func (p *Pool) Connect(id string) error {
p.peers[id] = &Peer{id: id, worker: worker} p.peers[id] = &Peer{id: id, worker: worker}
if p.logger != nil { if p.logger != nil {
p.logger.Info("connected to peer", "peer", id) p.logger.Info("registered peer", "peer", id)
} }
return nil return nil
+1 -1
View File
@@ -416,7 +416,7 @@ func RunKeepalive(
case <-timer.C: case <-timer.C:
// send keepalive signal, then reset the timer // send keepalive signal, then reset the timer
if logger != nil { if logger != nil {
logger.Info("keepalive: peer is inactive") logger.Info("keepalive: no activity observed")
} }
select { select {
case keepalive <- struct{}{}: case keepalive <- struct{}{}:
+1 -1
View File
@@ -56,7 +56,7 @@ func GetDefaultRetryConfig() *RetryConfig {
MaxRetries: 0, // Infinite retries MaxRetries: 0, // Infinite retries
InitialDelay: 1 * time.Second, InitialDelay: 1 * time.Second,
MaxDelay: 60 * time.Second, MaxDelay: 60 * time.Second,
JitterFactor: 0.5, JitterFactor: 0.2,
} }
} }
+1 -1
View File
@@ -56,7 +56,7 @@ func TestDefaultRetryConnectionConfig(t *testing.T) {
MaxRetries: 0, MaxRetries: 0,
InitialDelay: 1 * time.Second, InitialDelay: 1 * time.Second,
MaxDelay: 60 * time.Second, MaxDelay: 60 * time.Second,
JitterFactor: 0.5, JitterFactor: 0.2,
}) })
} }
-1
View File
@@ -399,7 +399,6 @@ func (c *Connection) Send(data []byte) error {
if c.logger != nil { if c.logger != nil {
c.logger.Error("write deadline error", "error", err) c.logger.Error("write deadline error", "error", err)
} }
c.shutdownExternal()
return NewConnectionError(fmt.Errorf("%w: %w", ErrFailedWriteDeadline, err)) return NewConnectionError(fmt.Errorf("%w: %w", ErrFailedWriteDeadline, err))
} }
} }
+2 -2
View File
@@ -215,9 +215,9 @@ func TestConnectionSend(t *testing.T) {
err = conn.Send([]byte("test")) err = conn.Send([]byte("test"))
assert.ErrorIs(t, err, ErrFailedWriteDeadline) assert.ErrorIs(t, err, ErrFailedWriteDeadline)
honeybeetest.Eventually(t, func() bool { honeybeetest.Never(t, func() bool {
return conn.State() == StateClosed return conn.State() == StateClosed
}, "expected closed state") }, "write error does not close connection")
}) })
t.Run("send fails on socket write error", func(t *testing.T) { t.Run("send fails on socket write error", func(t *testing.T) {
+1 -1
View File
@@ -14,7 +14,7 @@ type RetryManager struct {
func NewRetryManager(config *RetryConfig) *RetryManager { func NewRetryManager(config *RetryConfig) *RetryManager {
// saturationCount: retry count at which base delay meets or exceeds MaxDelay. // saturationCount: retry count at which base delay meets or exceeds MaxDelay.
// Conservative by one to preserve jitter variance near the boundary. // Conservative by two to preserve jitter variance near the boundary.
saturation := 0 saturation := 0
if config != nil && if config != nil &&
config.InitialDelay > 0 && config.InitialDelay > 0 &&