refactor: phase 3 integrated new dialing pattern into pool

This commit is contained in:
Jay
2026-06-06 18:32:52 -04:00
parent a46e9f7ad8
commit 1977fb385a
5 changed files with 85 additions and 75 deletions
+21 -25
View File
@@ -178,19 +178,12 @@ func NewPool(ctx context.Context, config *PoolConfig, handler slog.Handler,
logger = slog.New(handler).With(slog.Any("component", c)) logger = slog.New(handler).With(slog.Any("component", c))
} }
var dialer types.Dialer
// if config.ConnectionConfig.Dialer != nil {
// dialer = config.ConnectionConfig.Dialer
// } else {
// dialer = transport.NewDialer()
// }
return &Pool{ return &Pool{
peers: make(map[string]*Peer), peers: make(map[string]*Peer),
inbox: make(chan types.InboxMessage, config.InboxBufferSize), inbox: make(chan types.InboxMessage, config.InboxBufferSize),
events: make(chan PoolEvent, config.EventsBufferSize), events: make(chan PoolEvent, config.EventsBufferSize),
dialer: dialer, dialer: transport.NewDialer(),
config: config, config: config,
handler: handler, handler: handler,
logger: logger, logger: logger,
@@ -296,14 +289,14 @@ func (p *Pool) Close() {
type ConnectOption func(*connectOptions) type ConnectOption func(*connectOptions)
type connectOptions struct { type connectOptions struct {
dialer types.Dialer dialFn func(context.Context) (types.Socket, error)
} }
// WithDialer returns a ConnectOption that overrides the pool dialer for this // WithDialFunc returns a ConnectOption that overrides the dial function for
// connection only. // this connection.
func WithDialer(d types.Dialer) ConnectOption { func WithDialFunc(f func(context.Context) (types.Socket, error)) ConnectOption {
return func(o *connectOptions) { return func(o *connectOptions) {
o.dialer = d o.dialFn = f
} }
} }
@@ -328,23 +321,26 @@ func (p *Pool) Connect(id string, opts ...ConnectOption) error {
return ErrPeerExists return ErrPeerExists
} }
wc := p.config.WorkerConfig
worker, err := NewWorker(p.ctx, id, nil, &wc, p.handler)
if err != nil {
return err
}
o := &connectOptions{} o := &connectOptions{}
for _, opt := range opts { for _, opt := range opts {
opt(o) opt(o)
} }
// effectiveDialer := p.dialer
// if o.dialer != nil {
// effectiveDialer = o.dialer
// }
// cc := p.config.ConnectionConfig.Clone() // TODO: pass headers
// cc.Dialer = effectiveDialer dialFn := func(ctx context.Context) (types.Socket, error) {
socket, _, err := p.dialer.DialContext(ctx, id, nil)
return socket, err
}
if o.dialFn != nil {
dialFn = o.dialFn
}
wc := p.config.WorkerConfig
worker, err := NewWorker(p.ctx, id, dialFn, &wc, p.handler)
if err != nil {
return err
}
pool := PoolPlugin{ pool := PoolPlugin{
Inbox: p.inbox, Inbox: p.inbox,
+40 -39
View File
@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"git.wisehodl.dev/jay/go-honeybee/honeybeetest" "git.wisehodl.dev/jay/go-honeybee/honeybeetest"
"git.wisehodl.dev/jay/go-honeybee/transport"
"git.wisehodl.dev/jay/go-honeybee/types" "git.wisehodl.dev/jay/go-honeybee/types"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -17,14 +16,15 @@ import (
func setupPool(t *testing.T) (*Pool, *honeybeetest.MockDialer) { func setupPool(t *testing.T) (*Pool, *honeybeetest.MockDialer) {
t.Helper() t.Helper()
dialer := &honeybeetest.MockDialer{ dialer := &honeybeetest.MockDialer{
DialContextFunc: func(context.Context, string, http.Header) (types.Socket, *http.Response, error) { DialContextFunc: func(
context.Context, string, http.Header,
) (types.Socket, *http.Response, error) {
return honeybeetest.NewMockSocket(), nil, nil return honeybeetest.NewMockSocket(), nil, nil
}, },
} }
cc, _ := transport.NewConnectionConfig(transport.WithConnectionDialer(dialer)) config, _ := NewPoolConfig()
config, _ := NewPoolConfig(WithConnectionConfig(*cc)) pool, _ := NewPool(context.Background(), config, nil)
pool, err := NewPool(context.Background(), config, nil) pool.dialer = dialer
assert.NoError(t, err)
return pool, dialer return pool, dialer
} }
@@ -86,29 +86,28 @@ func TestPoolConnect(t *testing.T) {
} }
func TestPoolConnectWithDialer(t *testing.T) { func TestPoolConnectWithDialer(t *testing.T) {
t.Run("per-call dialer is used instead of pool dialer", func(t *testing.T) { t.Run("per-call dial function is used instead of pool dialer", func(t *testing.T) {
config, _ := NewPoolConfig()
pool, err := NewPool(context.Background(), config, nil)
assert.NoError(t, err)
perCallUsed := false perCallUsed := false
perCallDialer := &honeybeetest.MockDialer{ dialFn := func(_ context.Context) (types.Socket, error) {
DialContextFunc: func(ctx context.Context, url string, h http.Header) (types.Socket, *http.Response, error) { perCallUsed = true
perCallUsed = true return honeybeetest.NewMockSocket(), nil
return honeybeetest.NewMockSocket(), nil, nil
},
} }
// pool dialer should NOT be called // pool dialer should NOT be called
poolDialer := &honeybeetest.MockDialer{ pool.dialer = &honeybeetest.MockDialer{
DialContextFunc: func(context.Context, string, http.Header) (types.Socket, *http.Response, error) { DialContextFunc: func(
context.Context, string, http.Header,
) (types.Socket, *http.Response, error) {
t.Error("pool dialer should not be called when per-call dialer is provided") t.Error("pool dialer should not be called when per-call dialer is provided")
return nil, nil, fmt.Errorf("unexpected call") return nil, nil, fmt.Errorf("unexpected call")
}, },
} }
cc, _ := transport.NewConnectionConfig(transport.WithConnectionDialer(poolDialer)) err = pool.Connect("wss://test", WithDialFunc(dialFn))
config, _ := NewPoolConfig(WithConnectionConfig(*cc))
pool, err := NewPool(context.Background(), config, nil)
assert.NoError(t, err)
err = pool.Connect("wss://test", WithDialer(perCallDialer))
assert.NoError(t, err) assert.NoError(t, err)
honeybeetest.Eventually(t, func() bool { honeybeetest.Eventually(t, func() bool {
@@ -182,25 +181,25 @@ func TestPoolRemove(t *testing.T) {
} }
func TestPoolDialFailed(t *testing.T) { func TestPoolDialFailed(t *testing.T) {
t.Run("EventDialFailed surfaces with correct peer ID, non-nil Err, and non-zero At", func(t *testing.T) { t.Run("EventDialFailed received after failed dial", func(t *testing.T) {
dialErr := fmt.Errorf("connection refused") wc, _ := NewWorkerConfig(WithRetryDisabled())
failingDialer := &honeybeetest.MockDialer{
DialContextFunc: func(context.Context, string, http.Header) (types.Socket, *http.Response, error) {
return nil, nil, dialErr
},
}
cc, _ := transport.NewConnectionConfig(
transport.WithConnectionDialer(failingDialer),
transport.WithRetryDisabled(),
)
poolCfg, _ := NewPoolConfig( poolCfg, _ := NewPoolConfig(
WithInboxBufferSize(256), WithInboxBufferSize(256),
WithEventsBufferSize(10), WithEventsBufferSize(10),
WithConnectionConfig(*cc), WithWorkerConfig(*wc),
) )
pool, err := NewPool(context.Background(), poolCfg, nil) pool, err := NewPool(context.Background(), poolCfg, nil)
assert.NoError(t, err) assert.NoError(t, err)
dialErr := fmt.Errorf("connection refused")
pool.dialer = &honeybeetest.MockDialer{
DialContextFunc: func(
context.Context, string, http.Header,
) (types.Socket, *http.Response, error) {
return nil, nil, dialErr
},
}
err = pool.Connect("wss://test") err = pool.Connect("wss://test")
assert.NoError(t, err) assert.NoError(t, err)
@@ -245,17 +244,19 @@ func TestPoolSend(t *testing.T) {
outgoingData <- honeybeetest.MockOutgoingData{MsgType: msgType, Data: data} outgoingData <- honeybeetest.MockOutgoingData{MsgType: msgType, Data: data}
return nil return nil
} }
mockDialer := &honeybeetest.MockDialer{
DialContextFunc: func(context.Context, string, http.Header) (types.Socket, *http.Response, error) { config, _ := NewPoolConfig()
pool, err := NewPool(context.Background(), config, nil)
assert.NoError(t, err)
pool.dialer = &honeybeetest.MockDialer{
DialContextFunc: func(
context.Context, string, http.Header,
) (types.Socket, *http.Response, error) {
return mockSocket, nil, nil return mockSocket, nil, nil
}, },
} }
cc, _ := transport.NewConnectionConfig(transport.WithConnectionDialer(mockDialer))
config, _ := NewPoolConfig(WithConnectionConfig(*cc))
pool, err := NewPool(context.Background(), config, nil)
assert.NoError(t, err)
err = pool.Connect("wss://test") err = pool.Connect("wss://test")
assert.NoError(t, err) assert.NoError(t, err)
expectEvent(t, pool.events, "wss://test", EventConnected) expectEvent(t, pool.events, "wss://test", EventConnected)
+10 -2
View File
@@ -85,14 +85,22 @@ func DialWithRetry(
for { for {
if logger != nil { if logger != nil {
logger.Debug("dialing", "attempt", mgr.RetryCount()+1) if mgr == nil {
logger.Debug("dialing")
} else {
logger.Debug("dialing", "attempt", mgr.RetryCount()+1)
}
} }
// dial // dial
socket, err := dialFn(ctx) socket, err := dialFn(ctx)
if err == nil { if err == nil {
if logger != nil { if logger != nil {
logger.Debug("dial successful", "attempt", mgr.RetryCount()+1) if mgr == nil {
logger.Debug("dial successful")
} else {
logger.Debug("dial successful", "attempt", mgr.RetryCount()+1)
}
} }
return socket, nil return socket, nil
} }
+11 -1
View File
@@ -7,6 +7,7 @@ import (
"git.wisehodl.dev/jay/go-honeybee/honeybeetest" "git.wisehodl.dev/jay/go-honeybee/honeybeetest"
"git.wisehodl.dev/jay/go-honeybee/types" "git.wisehodl.dev/jay/go-honeybee/types"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"sync"
"sync/atomic" "sync/atomic"
"testing" "testing"
"time" "time"
@@ -252,8 +253,13 @@ func TestDialWithRetry_DialErrors(t *testing.T) {
} }
} }
var mu sync.Mutex
var gotErrors []error var gotErrors []error
onError := func(err error) { gotErrors = append(gotErrors, err) } onError := func(err error) {
mu.Lock()
defer mu.Unlock()
gotErrors = append(gotErrors, err)
}
retryCfg, _ := NewRetryConfig( retryCfg, _ := NewRetryConfig(
WithMaxRetries(3), WithMaxRetries(3),
@@ -269,9 +275,13 @@ func TestDialWithRetry_DialErrors(t *testing.T) {
}() }()
honeybeetest.Eventually(t, func() bool { honeybeetest.Eventually(t, func() bool {
mu.Lock()
defer mu.Unlock()
return len(gotErrors) == 2 return len(gotErrors) == 2
}, "expected errors") }, "expected errors")
mu.Lock()
defer mu.Unlock()
assert.Equal(t, dialErr1, gotErrors[0]) assert.Equal(t, dialErr1, gotErrors[0])
assert.Equal(t, dialErr2, gotErrors[1]) assert.Equal(t, dialErr2, gotErrors[1])
}) })
+3 -8
View File
@@ -64,9 +64,9 @@ func WithRetryConfig(value transport.RetryConfig) WorkerOption {
} }
} }
func WithRetryDisabled(value bool) WorkerOption { func WithRetryDisabled() WorkerOption {
return func(c *WorkerConfig) { return func(c *WorkerConfig) {
c.RetryDisabled = value c.RetryDisabled = true
} }
} }
@@ -224,13 +224,8 @@ func (w *DefaultWorker) runSession(ctx context.Context, pool PoolPlugin) {
} }
// setup new connection // setup new connection
conn, err := transport.NewConnection( conn, _ := transport.NewConnection(
ctx, socket, &w.config.ConnectionConfig, w.handler) ctx, socket, &w.config.ConnectionConfig, w.handler)
if err != nil {
// TODO: cleanup worker? surface error?
return
}
w.conn.Store(conn) w.conn.Store(conn)
pool.Events <- PoolEvent{ID: w.url, Kind: EventConnected, At: time.Now()} pool.Events <- PoolEvent{ID: w.url, Kind: EventConnected, At: time.Now()}