[client] Avoid Signal reconnect listener shutdown deadlock (#8257)

* [client] Reproduce Signal reconnect listener shutdown deadlock

* [client] Invoke Signal reconnect listeners outside the client mutex

Capture the listener with the registration state, then release the mutex before calling it. Watcher shutdown can now unregister a listener while an in-flight callback waits for the watcher lock.

Keep callback invocation synchronous and document that unregistration does not drain an already captured callback. Record callback completion inside the regression test's external lock.
This commit is contained in:
Maycon Santos
2026-10-19 18:47:25 +02:00
committed by GitHub
parent 0bb9854704
commit 36fe467aba
2 changed files with 105 additions and 3 deletions
+7 -3
View File
@@ -299,7 +299,6 @@ func (c *GrpcClient) notifyStreamDisconnected() {
func (c *GrpcClient) notifyStreamConnected() {
c.mux.Lock()
defer c.mux.Unlock()
c.status = StreamConnected
if c.connectedCh != nil {
@@ -308,8 +307,12 @@ func (c *GrpcClient) notifyStreamConnected() {
c.connectedCh = nil
}
if c.onReconnectedListenerFn != nil {
c.onReconnectedListenerFn()
listener := c.onReconnectedListenerFn
c.mux.Unlock()
// Listeners can hold their own locks while unregistering from this client.
if listener != nil {
listener()
}
}
@@ -409,6 +412,7 @@ func (c *GrpcClient) WaitStreamConnected(ctx context.Context) {
}
}
// SetOnReconnectedListener replaces the listener without waiting for an in-flight callback.
func (c *GrpcClient) SetOnReconnectedListener(fn func()) {
c.mux.Lock()
defer c.mux.Unlock()