fix: prevent panic on close of closed channel in worker client reconnection (#7837)
* fix: prevent panic on close of closed channel in worker client reconnection - Use idiomatic Go pattern of setting channels to nil after closing instead of flags - Extract repeated safe-close logic into safeCloseChannel() helper method - Call safeCloseChannel() in attemptConnection(), reconnect(), and handleDisconnect() - In safeCloseChannel(), check if channel is not nil, close it, and set to nil - Also set streamExit to nil in attemptConnection() when registration fails - This follows Go best practices for channel management and prevents double-close panics - Improved code maintainability by eliminating duplication * fix: prevent panic on close of closed channel in worker client reconnection - Use idiomatic Go pattern of setting channels to nil after closing instead of flags - Extract repeated safe-close logic into safeCloseChannel() helper method - Call safeCloseChannel() in attemptConnection(), reconnect(), and handleDisconnect() - In safeCloseChannel(), check if channel is not nil, close it, and set to nil - Also set streamExit to nil in attemptConnection() when registration fails - Document thread-safety assumptions: function is safe in current usage (serialized in managerLoop) but would need synchronization if used in concurrent contexts - This follows Go best practices for channel management and prevents double-close panics - Improved code maintainability by eliminating duplication
This commit is contained in:
@@ -98,6 +98,17 @@ func NewGrpcAdminClient(adminAddress string, workerID string, dialOption grpc.Di
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// safeCloseChannel safely closes a channel and sets it to nil to prevent double-close panics.
|
||||||
|
// NOTE: This function is NOT thread-safe. It is safe to use in this codebase because all calls
|
||||||
|
// are serialized within the managerLoop goroutine. If this function is used in concurrent contexts
|
||||||
|
// in the future, synchronization (e.g., sync.Mutex) should be added.
|
||||||
|
func (c *GrpcAdminClient) safeCloseChannel(chPtr *chan struct{}) {
|
||||||
|
if *chPtr != nil {
|
||||||
|
close(*chPtr)
|
||||||
|
*chPtr = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *GrpcAdminClient) managerLoop() {
|
func (c *GrpcAdminClient) managerLoop() {
|
||||||
state := &grpcState{shouldReconnect: true}
|
state := &grpcState{shouldReconnect: true}
|
||||||
|
|
||||||
@@ -221,7 +232,7 @@ func (c *GrpcAdminClient) attemptConnection(s *grpcState) error {
|
|||||||
if s.lastWorkerInfo != nil {
|
if s.lastWorkerInfo != nil {
|
||||||
// Send registration via the normal outgoing channel and wait for response via incoming
|
// Send registration via the normal outgoing channel and wait for response via incoming
|
||||||
if err := c.sendRegistration(s.lastWorkerInfo); err != nil {
|
if err := c.sendRegistration(s.lastWorkerInfo); err != nil {
|
||||||
close(s.streamExit)
|
c.safeCloseChannel(&s.streamExit)
|
||||||
s.streamCancel()
|
s.streamCancel()
|
||||||
s.conn.Close()
|
s.conn.Close()
|
||||||
s.connected = false
|
s.connected = false
|
||||||
@@ -240,9 +251,7 @@ func (c *GrpcAdminClient) attemptConnection(s *grpcState) error {
|
|||||||
// reconnect attempts to re-establish the connection
|
// reconnect attempts to re-establish the connection
|
||||||
func (c *GrpcAdminClient) reconnect(s *grpcState) error {
|
func (c *GrpcAdminClient) reconnect(s *grpcState) error {
|
||||||
// Clean up existing connection completely
|
// Clean up existing connection completely
|
||||||
if s.streamExit != nil {
|
c.safeCloseChannel(&s.streamExit)
|
||||||
close(s.streamExit)
|
|
||||||
}
|
|
||||||
if s.streamCancel != nil {
|
if s.streamCancel != nil {
|
||||||
s.streamCancel()
|
s.streamCancel()
|
||||||
}
|
}
|
||||||
@@ -425,7 +434,7 @@ func (c *GrpcAdminClient) handleDisconnect(cmd grpcCommand, s *grpcState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send shutdown signal to stop reconnection loop
|
// Send shutdown signal to stop reconnection loop
|
||||||
close(s.reconnectStop)
|
c.safeCloseChannel(&s.reconnectStop)
|
||||||
|
|
||||||
s.connected = false
|
s.connected = false
|
||||||
s.shouldReconnect = false
|
s.shouldReconnect = false
|
||||||
@@ -450,7 +459,7 @@ func (c *GrpcAdminClient) handleDisconnect(cmd grpcCommand, s *grpcState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send shutdown signal to stop handlers loop
|
// Send shutdown signal to stop handlers loop
|
||||||
close(s.streamExit)
|
c.safeCloseChannel(&s.streamExit)
|
||||||
|
|
||||||
// Cancel stream context
|
// Cancel stream context
|
||||||
if s.streamCancel != nil {
|
if s.streamCancel != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user