TestCloseConnInvokesOnRemove guards against a regression where (*ConnPool).CloseConn removed a connection from the pool without notifying pool hooks via OnRemove, causing per-connection state (e.g. streaming credentials listeners) to leak for every stale-conn close, idle/lifetime expiry, or health-c
(t *testing.T)
| 235 | // causing per-connection state (e.g. streaming credentials listeners) to leak |
| 236 | // for every stale-conn close, idle/lifetime expiry, or health-check failure. |
| 237 | func TestCloseConnInvokesOnRemove(t *testing.T) { |
| 238 | opt := &Options{ |
| 239 | Dialer: func(ctx context.Context) (net.Conn, error) { |
| 240 | return &net.TCPConn{}, nil |
| 241 | }, |
| 242 | PoolSize: 1, |
| 243 | MaxConcurrentDials: 1, |
| 244 | DialTimeout: time.Second, |
| 245 | } |
| 246 | |
| 247 | p := NewConnPool(opt) |
| 248 | defer p.Close() |
| 249 | |
| 250 | hook := &TestHook{ShouldPool: true, ShouldAccept: true} |
| 251 | p.AddPoolHook(hook) |
| 252 | |
| 253 | ctx := context.Background() |
| 254 | cn, err := p.NewConn(ctx) |
| 255 | if err != nil { |
| 256 | t.Fatalf("NewConn failed: %v", err) |
| 257 | } |
| 258 | |
| 259 | // The underlying net.TCPConn{} is a zero value, so cn.Close() may report |
| 260 | // "invalid argument" — we don't care about the close error, only about |
| 261 | // whether the OnRemove hook fired before the close. |
| 262 | _ = p.CloseConn(ctx, cn, CloseReasonStale, MetricStateIdle) |
| 263 | |
| 264 | if hook.OnRemoveCalled != 1 { |
| 265 | t.Errorf("Expected OnRemove to fire once for CloseConn, got %d", hook.OnRemoveCalled) |
| 266 | } |
| 267 | } |
nothing calls this directly
no test coverage detected