Wire pool-level Retire closure; emit EventRetired on terminal dial failure

This commit is contained in:
Jay
2026-06-06 19:50:58 -04:00
parent 7657de160e
commit 35428a4dad
2 changed files with 74 additions and 0 deletions
+9
View File
@@ -348,6 +348,15 @@ func (p *Pool) Connect(id string, opts ...ConnectOption) error {
Inbox: p.inbox, Inbox: p.inbox,
Events: p.events, Events: p.events,
InboxCounter: p.inboxCounter, InboxCounter: p.inboxCounter,
Retire: func(err error) {
p.mu.Lock()
defer p.mu.Unlock()
if p.closed {
return
}
delete(p.peers, id)
p.events <- PoolEvent{ID: id, Kind: EventRetired, Err: err, At: time.Now()}
},
} }
p.wg.Go(func() { p.wg.Go(func() {
+65
View File
@@ -2,13 +2,16 @@ package honeybee
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"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/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"net/http" "net/http"
"slices"
"testing" "testing"
"time"
) )
// Helpers // Helpers
@@ -237,6 +240,68 @@ func TestPoolDialFailed(t *testing.T) {
}) })
} }
func TestPoolRetire(t *testing.T) {
t.Run("peer absent and EventRetired emitted after self-retire", func(t *testing.T) {
wc, _ := NewWorkerConfig(WithRetryDisabled())
poolCfg, _ := NewPoolConfig(WithWorkerConfig(*wc))
pool, _ := NewPool(context.Background(), poolCfg, nil)
pool.dialer = &honeybeetest.MockDialer{
DialContextFunc: func(
context.Context, string, http.Header,
) (types.Socket, *http.Response, error) {
return nil, nil, fmt.Errorf("connection refused")
},
}
err := pool.Connect("wss://test")
assert.NoError(t, err)
honeybeetest.Eventually(t, func() bool {
return !slices.Contains(pool.Peers(), "wss://test")
}, "expected peer to be absent after self-retire")
honeybeetest.Eventually(t, func() bool {
select {
case e := <-pool.Events():
return e.Kind == EventRetired && e.ID == "wss://test"
default:
return false
}
}, "expected EventRetired on pool.Events()")
assert.ErrorIs(t, pool.Remove("wss://test"), ErrPeerNotFound)
})
t.Run("concurrent Remove and self-retire is safe", func(t *testing.T) {
wc, _ := NewWorkerConfig(WithRetryDisabled())
poolCfg, _ := NewPoolConfig(WithWorkerConfig(*wc))
pool, _ := NewPool(context.Background(), poolCfg, nil)
pool.dialer = &honeybeetest.MockDialer{
DialContextFunc: func(
ctx context.Context, _ string, _ http.Header,
) (types.Socket, *http.Response, error) {
select {
case <-ctx.Done():
return nil, nil, ctx.Err()
case <-time.After(5 * time.Millisecond):
return nil, nil, fmt.Errorf("connection refused")
}
},
}
err := pool.Connect("wss://test")
assert.NoError(t, err)
removeErr := pool.Remove("wss://test")
assert.True(t,
removeErr == nil || errors.Is(removeErr, ErrPeerNotFound),
"expected nil or ErrPeerNotFound, got: %v", removeErr,
)
})
}
func TestPoolSend(t *testing.T) { func TestPoolSend(t *testing.T) {
mockSocket := honeybeetest.NewMockSocket() mockSocket := honeybeetest.NewMockSocket()
outgoingData := make(chan honeybeetest.MockOutgoingData, 10) outgoingData := make(chan honeybeetest.MockOutgoingData, 10)