misc post-refactor fixes, refinements

This commit is contained in:
Jay
2026-06-07 09:08:45 -04:00
parent 60de9534dc
commit 4b7e68c8fc
5 changed files with 128 additions and 130 deletions
+22 -21
View File
@@ -64,8 +64,8 @@ type PoolStats struct {
} }
type PeerStats struct { type PeerStats struct {
ID string ID string
Worker WorkerStats Session SessionStats
} }
type PoolPlugin struct { type PoolPlugin struct {
@@ -82,20 +82,20 @@ type PoolPlugin struct {
type PoolConfig struct { type PoolConfig struct {
InboxBufferSize int InboxBufferSize int
EventsBufferSize int EventsBufferSize int
WorkerConfig WorkerConfig SessionConfig SessionConfig
RequestHeader http.Header RequestHeader http.Header
} }
type PoolOption func(*PoolConfig) type PoolOption func(*PoolConfig)
func NewPoolConfig(opts ...PoolOption) (*PoolConfig, error) { func NewPoolConfig(opts ...PoolOption) (*PoolConfig, error) {
workerCfg, _ := NewWorkerConfig() sessionCfg, _ := NewSessionConfig()
defaultHeader := http.Header{} defaultHeader := http.Header{}
defaultHeader.Set("User-Agent", "honeybee/0.1.0") defaultHeader.Set("User-Agent", "honeybee/0.1.0")
cfg := &PoolConfig{ cfg := &PoolConfig{
InboxBufferSize: 256, InboxBufferSize: 256,
EventsBufferSize: 10, EventsBufferSize: 10,
WorkerConfig: *workerCfg, SessionConfig: *sessionCfg,
RequestHeader: defaultHeader, RequestHeader: defaultHeader,
} }
for _, o := range opts { for _, o := range opts {
@@ -119,9 +119,9 @@ func WithEventsBufferSize(value int) PoolOption {
} }
} }
func WithWorkerConfig(wc WorkerConfig) PoolOption { func WithSessionConfig(wc SessionConfig) PoolOption {
return func(c *PoolConfig) { return func(c *PoolConfig) {
c.WorkerConfig = wc c.SessionConfig = wc
} }
} }
@@ -142,7 +142,7 @@ func ValidatePoolConfig(c *PoolConfig) error {
if c.EventsBufferSize < 1 { if c.EventsBufferSize < 1 {
return fmt.Errorf("invalid events buffer size: %d", c.EventsBufferSize) return fmt.Errorf("invalid events buffer size: %d", c.EventsBufferSize)
} }
if err := ValidateWorkerConfig(&c.WorkerConfig); err != nil { if err := ValidateSessionConfig(&c.SessionConfig); err != nil {
return err return err
} }
return nil return nil
@@ -153,8 +153,8 @@ func ValidatePoolConfig(c *PoolConfig) error {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
type Peer struct { type Peer struct {
id string id string
worker Worker session *session
} }
type Pool struct { type Pool struct {
@@ -240,8 +240,8 @@ func (p *Pool) Stats() PoolStats {
peerStats := make([]PeerStats, 0, count) peerStats := make([]PeerStats, 0, count)
for id, peer := range p.peers { for id, peer := range p.peers {
peerStats = append(peerStats, PeerStats{ peerStats = append(peerStats, PeerStats{
ID: id, ID: id,
Worker: peer.worker.Stats(), Session: peer.session.Stats(),
}) })
} }
@@ -267,8 +267,8 @@ func (p *Pool) PeerStats(id string) (PeerStats, error) {
} }
return PeerStats{ return PeerStats{
ID: id, ID: id,
Worker: peer.worker.Stats(), Session: peer.session.Stats(),
}, nil }, nil
} }
@@ -284,7 +284,7 @@ func (p *Pool) Close() {
} }
p.closed = true p.closed = true
p.cancel() // closes all workers p.cancel() // closes all sessions
// remove all peers // remove all peers
p.peers = make(map[string]*Peer) p.peers = make(map[string]*Peer)
@@ -356,8 +356,8 @@ func (p *Pool) Connect(id string, opts ...ConnectOption) error {
dialFn = o.dialFn dialFn = o.dialFn
} }
wc := p.config.WorkerConfig wc := p.config.SessionConfig
worker, err := NewWorker(p.ctx, id, dialFn, &wc, p.handler) session, err := newSession(p.ctx, id, dialFn, &wc, p.handler)
if err != nil { if err != nil {
return err return err
} }
@@ -369,6 +369,7 @@ func (p *Pool) Connect(id string, opts ...ConnectOption) error {
Retire: func(err error) { Retire: func(err error) {
p.mu.Lock() p.mu.Lock()
if p.closed { if p.closed {
p.mu.Unlock()
return return
} }
delete(p.peers, id) delete(p.peers, id)
@@ -378,10 +379,10 @@ func (p *Pool) Connect(id string, opts ...ConnectOption) error {
} }
p.wg.Go(func() { p.wg.Go(func() {
worker.Start(pool) session.Start(pool)
}) })
p.peers[id] = &Peer{id: id, worker: worker} p.peers[id] = &Peer{id: id, session: session}
if p.logger != nil { if p.logger != nil {
p.logger.Debug("registered peer", "peer", id) p.logger.Debug("registered peer", "peer", id)
@@ -413,7 +414,7 @@ func (p *Pool) Remove(id string) error {
} }
delete(p.peers, id) delete(p.peers, id)
peer.worker.Stop() peer.session.Stop()
if p.logger != nil { if p.logger != nil {
p.logger.Debug("disconnected from peer", "peer", id) p.logger.Debug("disconnected from peer", "peer", id)
@@ -440,7 +441,7 @@ func (p *Pool) Send(id string, data []byte) error {
return ErrPeerNotFound return ErrPeerNotFound
} }
err = peer.worker.Send(data) err = peer.session.Send(data)
if err != nil { if err != nil {
return err return err
} }
+6 -6
View File
@@ -185,11 +185,11 @@ func TestPoolRemove(t *testing.T) {
func TestPoolDialFailed(t *testing.T) { func TestPoolDialFailed(t *testing.T) {
t.Run("EventDialFailed received after failed dial", func(t *testing.T) { t.Run("EventDialFailed received after failed dial", func(t *testing.T) {
wc, _ := NewWorkerConfig(WithRetryDisabled()) wc, _ := NewSessionConfig(WithRetryDisabled())
poolCfg, _ := NewPoolConfig( poolCfg, _ := NewPoolConfig(
WithInboxBufferSize(256), WithInboxBufferSize(256),
WithEventsBufferSize(10), WithEventsBufferSize(10),
WithWorkerConfig(*wc), WithSessionConfig(*wc),
) )
pool, err := NewPool(context.Background(), poolCfg, nil) pool, err := NewPool(context.Background(), poolCfg, nil)
assert.NoError(t, err) assert.NoError(t, err)
@@ -242,8 +242,8 @@ func TestPoolDialFailed(t *testing.T) {
func TestPoolRetire(t *testing.T) { func TestPoolRetire(t *testing.T) {
t.Run("peer absent and EventRetired emitted after self-retire", func(t *testing.T) { t.Run("peer absent and EventRetired emitted after self-retire", func(t *testing.T) {
wc, _ := NewWorkerConfig(WithRetryDisabled()) wc, _ := NewSessionConfig(WithRetryDisabled())
poolCfg, _ := NewPoolConfig(WithWorkerConfig(*wc)) poolCfg, _ := NewPoolConfig(WithSessionConfig(*wc))
pool, _ := NewPool(context.Background(), poolCfg, nil) pool, _ := NewPool(context.Background(), poolCfg, nil)
pool.dialer = &honeybeetest.MockDialer{ pool.dialer = &honeybeetest.MockDialer{
@@ -274,8 +274,8 @@ func TestPoolRetire(t *testing.T) {
}) })
t.Run("concurrent Remove and self-retire is safe", func(t *testing.T) { t.Run("concurrent Remove and self-retire is safe", func(t *testing.T) {
wc, _ := NewWorkerConfig(WithRetryDisabled()) wc, _ := NewSessionConfig(WithRetryDisabled())
poolCfg, _ := NewPoolConfig(WithWorkerConfig(*wc)) poolCfg, _ := NewPoolConfig(WithSessionConfig(*wc))
pool, _ := NewPool(context.Background(), poolCfg, nil) pool, _ := NewPool(context.Background(), poolCfg, nil)
pool.dialer = &honeybeetest.MockDialer{ pool.dialer = &honeybeetest.MockDialer{
+37 -44
View File
@@ -24,7 +24,7 @@ var ErrConnectionUnavailable = errors.New("connection unavailable")
// Config // Config
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
type WorkerConfig struct { type SessionConfig struct {
ConnectionConfig transport.ConnectionConfig ConnectionConfig transport.ConnectionConfig
RetryConfig transport.RetryConfig RetryConfig transport.RetryConfig
RetryDisabled bool RetryDisabled bool
@@ -32,12 +32,12 @@ type WorkerConfig struct {
ReconnectDelay time.Duration ReconnectDelay time.Duration
} }
type WorkerOption func(*WorkerConfig) type SessionOption func(*SessionConfig)
func NewWorkerConfig(opts ...WorkerOption) (*WorkerConfig, error) { func NewSessionConfig(opts ...SessionOption) (*SessionConfig, error) {
connCfg, _ := transport.NewConnectionConfig() connCfg, _ := transport.NewConnectionConfig()
retryCfg, _ := transport.NewRetryConfig() retryCfg, _ := transport.NewRetryConfig()
cfg := &WorkerConfig{ cfg := &SessionConfig{
ConnectionConfig: *connCfg, ConnectionConfig: *connCfg,
RetryConfig: retryCfg, RetryConfig: retryCfg,
KeepaliveTimeout: 60 * time.Second, KeepaliveTimeout: 60 * time.Second,
@@ -46,44 +46,44 @@ func NewWorkerConfig(opts ...WorkerOption) (*WorkerConfig, error) {
for _, o := range opts { for _, o := range opts {
o(cfg) o(cfg)
} }
if err := ValidateWorkerConfig(cfg); err != nil { if err := ValidateSessionConfig(cfg); err != nil {
return nil, err return nil, err
} }
return cfg, nil return cfg, nil
} }
func WithConnectionConfig(value transport.ConnectionConfig) WorkerOption { func WithConnectionConfig(value transport.ConnectionConfig) SessionOption {
return func(c *WorkerConfig) { return func(c *SessionConfig) {
c.ConnectionConfig = value c.ConnectionConfig = value
} }
} }
func WithRetryConfig(value transport.RetryConfig) WorkerOption { func WithRetryConfig(value transport.RetryConfig) SessionOption {
return func(c *WorkerConfig) { return func(c *SessionConfig) {
c.RetryConfig = value c.RetryConfig = value
} }
} }
func WithRetryDisabled() WorkerOption { func WithRetryDisabled() SessionOption {
return func(c *WorkerConfig) { return func(c *SessionConfig) {
c.RetryDisabled = true c.RetryDisabled = true
} }
} }
// When KeepaliveTimeout is set to zero, keepalive functionality is disabled. // When KeepaliveTimeout is set to zero, keepalive functionality is disabled.
func WithKeepaliveTimeout(value time.Duration) WorkerOption { func WithKeepaliveTimeout(value time.Duration) SessionOption {
return func(c *WorkerConfig) { return func(c *SessionConfig) {
c.KeepaliveTimeout = value c.KeepaliveTimeout = value
} }
} }
func WithReconnectDelay(value time.Duration) WorkerOption { func WithReconnectDelay(value time.Duration) SessionOption {
return func(c *WorkerConfig) { return func(c *SessionConfig) {
c.ReconnectDelay = value c.ReconnectDelay = value
} }
} }
func ValidateWorkerConfig(c *WorkerConfig) error { func ValidateSessionConfig(c *SessionConfig) error {
if !c.RetryDisabled { if !c.RetryDisabled {
if err := transport.ValidateRetryConfig(c.RetryConfig); err != nil { if err := transport.ValidateRetryConfig(c.RetryConfig); err != nil {
return err return err
@@ -99,17 +99,10 @@ func ValidateWorkerConfig(c *WorkerConfig) error {
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Worker // Session
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
type Worker interface { type SessionStats struct {
Start(pool PoolPlugin)
Stop()
Send(data []byte) error
Stats() WorkerStats
}
type WorkerStats struct {
ConnectionAvailable bool ConnectionAvailable bool
Connection transport.ConnectionStats Connection transport.ConnectionStats
ChanIncoming int ChanIncoming int
@@ -119,7 +112,7 @@ type WorkerStats struct {
TotalRestarts uint64 TotalRestarts uint64
} }
type DefaultWorker struct { type session struct {
url string url string
dialFn func(context.Context) (types.Socket, error) dialFn func(context.Context) (types.Socket, error)
conn atomic.Pointer[transport.Connection] conn atomic.Pointer[transport.Connection]
@@ -128,7 +121,7 @@ type DefaultWorker struct {
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
config WorkerConfig config SessionConfig
handler slog.Handler handler slog.Handler
logger *slog.Logger logger *slog.Logger
@@ -137,28 +130,28 @@ type DefaultWorker struct {
restartCount *atomic.Uint64 restartCount *atomic.Uint64
} }
func NewWorker( func newSession(
ctx context.Context, ctx context.Context,
url string, url string,
dialFn func(context.Context) (types.Socket, error), dialFn func(context.Context) (types.Socket, error),
config *WorkerConfig, config *SessionConfig,
handler slog.Handler, handler slog.Handler,
) (*DefaultWorker, error) { ) (*session, error) {
if config == nil { if config == nil {
config, _ = NewWorkerConfig() config, _ = NewSessionConfig()
} }
if err := ValidateWorkerConfig(config); err != nil { if err := ValidateSessionConfig(config); err != nil {
return nil, err return nil, err
} }
if component.FromContext(ctx) == nil { if component.FromContext(ctx) == nil {
ctx = component.MustNew(ctx, "honeybee", "worker") ctx = component.MustNew(ctx, "honeybee", "session")
} else { } else {
ctx = component.MustExtend(ctx, "worker") ctx = component.MustExtend(ctx, "session")
} }
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
w := &DefaultWorker{ w := &session{
url: url, url: url,
dialFn: dialFn, dialFn: dialFn,
@@ -182,13 +175,13 @@ func NewWorker(
return w, nil return w, nil
} }
func (w *DefaultWorker) Start(pool PoolPlugin) { func (w *session) Start(pool PoolPlugin) {
if w.logger != nil { if w.logger != nil {
w.logger.Debug("starting") w.logger.Debug("starting")
} }
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { w.runSession(w.ctx, pool) }) wg.Go(func() { w.run(w.ctx, pool) })
if w.logger != nil { if w.logger != nil {
w.logger.Debug("started") w.logger.Debug("started")
@@ -201,7 +194,7 @@ func (w *DefaultWorker) Start(pool PoolPlugin) {
} }
} }
func (w *DefaultWorker) runSession(ctx context.Context, pool PoolPlugin) { func (w *session) run(ctx context.Context, pool PoolPlugin) {
for { for {
var retryMgr *transport.RetryManager var retryMgr *transport.RetryManager
if !w.config.RetryDisabled { if !w.config.RetryDisabled {
@@ -298,7 +291,7 @@ func (w *DefaultWorker) runSession(ctx context.Context, pool PoolPlugin) {
w.conn.Store(nil) w.conn.Store(nil)
pool.Events <- PoolEvent{ID: w.url, Kind: EventDisconnected, At: time.Now()} pool.Events <- PoolEvent{ID: w.url, Kind: EventDisconnected, At: time.Now()}
// exit if worker is shutting down // exit if session is shutting down
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
@@ -311,7 +304,7 @@ func (w *DefaultWorker) runSession(ctx context.Context, pool PoolPlugin) {
} }
} }
func (w *DefaultWorker) startKeepalive() ( func (w *session) startKeepalive() (
stop func(), stop func(),
inactive func() <-chan time.Time, inactive func() <-chan time.Time,
heartbeat func(), heartbeat func(),
@@ -354,14 +347,14 @@ func (w *DefaultWorker) startKeepalive() (
return return
} }
func (w *DefaultWorker) Stop() { func (w *session) Stop() {
if w.logger != nil { if w.logger != nil {
w.logger.Info("shutting down") w.logger.Info("shutting down")
} }
w.cancel() w.cancel()
} }
func (w *DefaultWorker) Send(data []byte) error { func (w *session) Send(data []byte) error {
conn := w.conn.Load() conn := w.conn.Load()
if conn == nil { if conn == nil {
// connection not established by session // connection not established by session
@@ -383,7 +376,7 @@ func (w *DefaultWorker) Send(data []byte) error {
return nil return nil
} }
func (w *DefaultWorker) Stats() WorkerStats { func (w *session) Stats() SessionStats {
connectionAvailable := false connectionAvailable := false
incomingLen := 0 incomingLen := 0
connStats := transport.ConnectionStats{} connStats := transport.ConnectionStats{}
@@ -395,7 +388,7 @@ func (w *DefaultWorker) Stats() WorkerStats {
connStats = conn.Stats() connStats = conn.Stats()
} }
return WorkerStats{ return SessionStats{
ConnectionAvailable: connectionAvailable, ConnectionAvailable: connectionAvailable,
Connection: connStats, Connection: connStats,
ChanIncoming: incomingLen, ChanIncoming: incomingLen,
+57 -57
View File
@@ -14,7 +14,7 @@ import (
"time" "time"
) )
func makeWorkerContext(t *testing.T) ( func makeSessionContext(t *testing.T) (
inbox chan types.InboxMessage, inbox chan types.InboxMessage,
events chan PoolEvent, events chan PoolEvent,
pool PoolPlugin, pool PoolPlugin,
@@ -31,19 +31,19 @@ func makeWorkerContext(t *testing.T) (
return return
} }
func makeWorker( func makeSession(
t *testing.T, t *testing.T,
socket *honeybeetest.MockSocket, socket *honeybeetest.MockSocket,
config *WorkerConfig, config *SessionConfig,
ctx context.Context, ctx context.Context,
cancel context.CancelFunc, cancel context.CancelFunc,
) *DefaultWorker { ) *session {
t.Helper() t.Helper()
if config == nil { if config == nil {
config, _ = NewWorkerConfig(WithReconnectDelay(0 * time.Second)) config, _ = NewSessionConfig(WithReconnectDelay(0 * time.Second))
} }
dialFn := func(_ context.Context) (types.Socket, error) { return socket, nil } dialFn := func(_ context.Context) (types.Socket, error) { return socket, nil }
return &DefaultWorker{ return &session{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
url: "wss://test", url: "wss://test",
@@ -56,14 +56,14 @@ func makeWorker(
} }
} }
func TestWorkerSession(t *testing.T) { func TestSession(t *testing.T) {
t.Run("EventConnected emitted after dial succeeds", func(t *testing.T) { t.Run("EventConnected emitted after dial succeeds", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
mockSocket := honeybeetest.NewMockSocket() mockSocket := honeybeetest.NewMockSocket()
w := makeWorker(t, mockSocket, nil, ctx, cancel) w := makeSession(t, mockSocket, nil, ctx, cancel)
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { wg.Go(func() {
@@ -80,16 +80,16 @@ func TestWorkerSession(t *testing.T) {
}, "expected EventConnected") }, "expected EventConnected")
}) })
t.Run("dial failure exhausted - worker exits cleanly, no connected/disconnected events", func(t *testing.T) { t.Run("dial failure exhausted - session exits cleanly, no connected/disconnected events", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
config, _ := NewWorkerConfig(WithRetryDisabled()) config, _ := NewSessionConfig(WithRetryDisabled())
w := makeWorker(t, nil, config, ctx, cancel) w := makeSession(t, nil, config, ctx, cancel)
w.dialFn = func(_ context.Context) (types.Socket, error) { w.dialFn = func(_ context.Context) (types.Socket, error) {
return nil, fmt.Errorf("connection refused") return nil, fmt.Errorf("connection refused")
} }
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { w.Start(pool) }) wg.Go(func() { w.Start(pool) })
@@ -103,7 +103,7 @@ func TestWorkerSession(t *testing.T) {
default: default:
return false return false
} }
}, "expected worker to exit after terminal dial failure") }, "expected session to exit after terminal dial failure")
honeybeetest.Never(t, func() bool { honeybeetest.Never(t, func() bool {
select { select {
@@ -119,12 +119,12 @@ func TestWorkerSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
config, _ := NewWorkerConfig(WithRetryDisabled()) config, _ := NewSessionConfig(WithRetryDisabled())
w := makeWorker(t, nil, config, ctx, cancel) w := makeSession(t, nil, config, ctx, cancel)
w.dialFn = func(_ context.Context) (types.Socket, error) { w.dialFn = func(_ context.Context) (types.Socket, error) {
return nil, fmt.Errorf("connection refused") return nil, fmt.Errorf("connection refused")
} }
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
retired := atomic.Bool{} retired := atomic.Bool{}
var retiredErr atomic.Pointer[error] var retiredErr atomic.Pointer[error]
@@ -148,7 +148,7 @@ func TestWorkerSession(t *testing.T) {
default: default:
return false return false
} }
}, "expected worker to exit cleanly") }, "expected session to exit cleanly")
honeybeetest.Never(t, func() bool { honeybeetest.Never(t, func() bool {
select { select {
@@ -164,12 +164,12 @@ func TestWorkerSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
w := makeWorker(t, nil, nil, ctx, cancel) w := makeSession(t, nil, nil, ctx, cancel)
w.dialFn = func(dialCtx context.Context) (types.Socket, error) { w.dialFn = func(dialCtx context.Context) (types.Socket, error) {
<-dialCtx.Done() <-dialCtx.Done()
return nil, dialCtx.Err() return nil, dialCtx.Err()
} }
_, _, pool := makeWorkerContext(t) _, _, pool := makeSessionContext(t)
retired := atomic.Bool{} retired := atomic.Bool{}
pool.Retire = func(_ error) { retired.Store(true) } pool.Retire = func(_ error) { retired.Store(true) }
@@ -187,7 +187,7 @@ func TestWorkerSession(t *testing.T) {
default: default:
return false return false
} }
}, "expected worker to exit after Stop") }, "expected session to exit after Stop")
assert.False(t, retired.Load(), "expected Retire not called") assert.False(t, retired.Load(), "expected Retire not called")
}) })
@@ -197,8 +197,8 @@ func TestWorkerSession(t *testing.T) {
defer cancel() defer cancel()
mockSocket := honeybeetest.NewMockSocket() mockSocket := honeybeetest.NewMockSocket()
w := makeWorker(t, mockSocket, nil, ctx, cancel) w := makeSession(t, mockSocket, nil, ctx, cancel)
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
retired := atomic.Bool{} retired := atomic.Bool{}
pool.Retire = func(_ error) { retired.Store(true) } pool.Retire = func(_ error) { retired.Store(true) }
@@ -222,12 +222,12 @@ func TestWorkerSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
w := makeWorker(t, nil, nil, ctx, cancel) w := makeSession(t, nil, nil, ctx, cancel)
w.dialFn = func(dialCtx context.Context) (types.Socket, error) { w.dialFn = func(dialCtx context.Context) (types.Socket, error) {
<-dialCtx.Done() <-dialCtx.Done()
return nil, dialCtx.Err() return nil, dialCtx.Err()
} }
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { w.Start(pool) }) wg.Go(func() { w.Start(pool) })
@@ -253,8 +253,8 @@ func TestWorkerSession(t *testing.T) {
defer cancel() defer cancel()
_, mockSocket, _, outgoingData := setupTestConnection(t) _, mockSocket, _, outgoingData := setupTestConnection(t)
w := makeWorker(t, mockSocket, nil, ctx, cancel) w := makeSession(t, mockSocket, nil, ctx, cancel)
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { wg.Go(func() {
@@ -302,8 +302,8 @@ func TestWorkerSession(t *testing.T) {
} }
} }
w := makeWorker(t, mockSocket, nil, ctx, cancel) w := makeSession(t, mockSocket, nil, ctx, cancel)
inbox, events, pool := makeWorkerContext(t) inbox, events, pool := makeSessionContext(t)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { wg.Go(func() {
@@ -343,13 +343,13 @@ func TestWorkerSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
config, _ := NewWorkerConfig( config, _ := NewSessionConfig(
WithReconnectDelay(0), WithReconnectDelay(0),
WithKeepaliveTimeout(60*time.Millisecond), WithKeepaliveTimeout(60*time.Millisecond),
) )
_, mockSocket, incomingData, _ := setupTestConnection(t) _, mockSocket, incomingData, _ := setupTestConnection(t)
w := makeWorker(t, mockSocket, config, ctx, cancel) w := makeSession(t, mockSocket, config, ctx, cancel)
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { w.Start(pool) }) wg.Go(func() { w.Start(pool) })
@@ -400,12 +400,12 @@ func TestWorkerSession(t *testing.T) {
mockSocket, incomingData, _ := honeybeetest.SetupTestSocket(t) mockSocket, incomingData, _ := honeybeetest.SetupTestSocket(t)
mockSocket.SetPongHandlerFunc = func(h func(string) error) { pongHandler = h } mockSocket.SetPongHandlerFunc = func(h func(string) error) { pongHandler = h }
config, _ := NewWorkerConfig( config, _ := NewSessionConfig(
WithReconnectDelay(0), WithReconnectDelay(0),
WithKeepaliveTimeout(60*time.Millisecond), WithKeepaliveTimeout(60*time.Millisecond),
) )
w := makeWorker(t, mockSocket, config, ctx, cancel) w := makeSession(t, mockSocket, config, ctx, cancel)
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { w.Start(pool) }) wg.Go(func() { w.Start(pool) })
@@ -451,13 +451,13 @@ func TestWorkerSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
config, _ := NewWorkerConfig( config, _ := NewSessionConfig(
WithReconnectDelay(0), WithReconnectDelay(0),
WithKeepaliveTimeout(30*time.Millisecond), WithKeepaliveTimeout(30*time.Millisecond),
) )
_, mockSocket, _, _ := setupTestConnection(t) _, mockSocket, _, _ := setupTestConnection(t)
w := makeWorker(t, mockSocket, config, ctx, cancel) w := makeSession(t, mockSocket, config, ctx, cancel)
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { w.Start(pool) }) wg.Go(func() { w.Start(pool) })
@@ -496,9 +496,9 @@ func TestWorkerSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
_, mockSocket, incomingData, _ := setupTestConnection(t) _, mockSocket, incomingData, _ := setupTestConnection(t)
w := makeWorker(t, mockSocket, nil, ctx, cancel) w := makeSession(t, mockSocket, nil, ctx, cancel)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { wg.Go(func() {
@@ -539,9 +539,9 @@ func TestWorkerSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
mockSocket := honeybeetest.NewMockSocket() mockSocket := honeybeetest.NewMockSocket()
w := makeWorker(t, mockSocket, nil, ctx, cancel) w := makeSession(t, mockSocket, nil, ctx, cancel)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { wg.Go(func() {
@@ -582,11 +582,11 @@ func TestWorkerSession(t *testing.T) {
t.Run("parent context cancel exits cleanly and wg drains", func(t *testing.T) { t.Run("parent context cancel exits cleanly and wg drains", func(t *testing.T) {
parentCtx, parentCancel := context.WithCancel(context.Background()) parentCtx, parentCancel := context.WithCancel(context.Background())
workerCtx, workerCancel := context.WithCancel(parentCtx) sessionCtx, sessionCancel := context.WithCancel(parentCtx)
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
mockSocket := honeybeetest.NewMockSocket() mockSocket := honeybeetest.NewMockSocket()
w := makeWorker(t, mockSocket, nil, workerCtx, workerCancel) w := makeSession(t, mockSocket, nil, sessionCtx, sessionCancel)
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Go(func() { wg.Go(func() {
@@ -603,7 +603,7 @@ func TestWorkerSession(t *testing.T) {
}, "expected EventConnected") }, "expected EventConnected")
// drain events after parent cancel — we don't assert what they are, // drain events after parent cancel — we don't assert what they are,
// only that the worker exits // only that the session exits
parentCancel() parentCancel()
done := make(chan struct{}) done := make(chan struct{})
@@ -622,8 +622,8 @@ func TestWorkerSession(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
w := makeWorker(t, nil, nil, ctx, cancel) w := makeSession(t, nil, nil, ctx, cancel)
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
dialErr := errors.New("connection refused") dialErr := errors.New("connection refused")
w.dialFn = func(_ context.Context) (types.Socket, error) { w.dialFn = func(_ context.Context) (types.Socket, error) {
@@ -646,12 +646,12 @@ func TestWorkerSession(t *testing.T) {
}, "expected EventDialFailed") }, "expected EventDialFailed")
}) })
t.Run("no EventDialFailed when worker is stopped mid-dial", func(t *testing.T) { t.Run("no EventDialFailed when session is stopped mid-dial", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
w := makeWorker(t, nil, nil, ctx, cancel) w := makeSession(t, nil, nil, ctx, cancel)
_, events, pool := makeWorkerContext(t) _, events, pool := makeSessionContext(t)
w.dialFn = func(dialCtx context.Context) (types.Socket, error) { w.dialFn = func(dialCtx context.Context) (types.Socket, error) {
<-dialCtx.Done() <-dialCtx.Done()
@@ -672,7 +672,7 @@ func TestWorkerSession(t *testing.T) {
default: default:
return false return false
} }
}, "expected worker to exit after Stop") }, "expected session to exit after Stop")
honeybeetest.Never(t, func() bool { honeybeetest.Never(t, func() bool {
select { select {
@@ -685,7 +685,7 @@ func TestWorkerSession(t *testing.T) {
}) })
} }
func TestWorkerSend(t *testing.T) { func TestSession_Send(t *testing.T) {
t.Run("data sent to mock socket", func(t *testing.T) { t.Run("data sent to mock socket", func(t *testing.T) {
conn, _, _, outgoingData := setupTestConnection(t) conn, _, _, outgoingData := setupTestConnection(t)
defer conn.Close() defer conn.Close()
@@ -695,7 +695,7 @@ func TestWorkerSend(t *testing.T) {
heartbeat := make(chan struct{}) heartbeat := make(chan struct{})
heartbeatCount := atomic.Int32{} heartbeatCount := atomic.Int32{}
w := &DefaultWorker{ w := &session{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
url: "wss://test", url: "wss://test",
@@ -740,7 +740,7 @@ func TestWorkerSend(t *testing.T) {
heartbeat := make(chan struct{}) heartbeat := make(chan struct{})
heartbeatCount := atomic.Int32{} heartbeatCount := atomic.Int32{}
w := &DefaultWorker{ w := &session{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
url: "wss://test", url: "wss://test",
@@ -768,13 +768,13 @@ func TestWorkerSend(t *testing.T) {
}) })
t.Run("returns error if connection is unavailable", func(t *testing.T) { t.Run("returns error if connection is unavailable", func(t *testing.T) {
// no connection available to worker // no connection available to session
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
heartbeat := make(chan struct{}) heartbeat := make(chan struct{})
w := &DefaultWorker{ w := &session{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
url: "wss://test", url: "wss://test",
+6 -2
View File
@@ -23,7 +23,6 @@ var (
ErrConnectionClosed = errors.New("connection closed") ErrConnectionClosed = errors.New("connection closed")
ErrWriteFailed = errors.New("write failed") ErrWriteFailed = errors.New("write failed")
ErrNilSocket = errors.New("socket cannot be nil") ErrNilSocket = errors.New("socket cannot be nil")
ErrSocketExists = errors.New("socket already exists")
ErrFailedWriteDeadline = errors.New("failed to set write deadline") ErrFailedWriteDeadline = errors.New("failed to set write deadline")
ErrPeerClosedClean = errors.New("peer closed connection cleanly") ErrPeerClosedClean = errors.New("peer closed connection cleanly")
ErrPeerClosedUnexpected = errors.New("peer closed connection unexpectedly") ErrPeerClosedUnexpected = errors.New("peer closed connection unexpectedly")
@@ -367,7 +366,12 @@ func (c *Connection) startPinger() {
timer.Stop() timer.Stop()
return return
case <-timer.C: case <-timer.C:
deadline := time.Now().Add(c.config.WriteTimeout) var deadline time.Time
if c.config.WriteTimeout > 0 {
deadline = time.Now().Add(c.config.WriteTimeout)
} else {
deadline = time.Now().Add(15 * time.Second)
}
err := c.socket.WriteControl(websocket.PingMessage, nil, deadline) err := c.socket.WriteControl(websocket.PingMessage, nil, deadline)
if err != nil { if err != nil {
return return