fix: 修复raceDial竞态条件导致daemon死锁无法连接
raceDial在首个连接成功后cancel剩余拨号goroutine时,dial goroutine 的select有两个同时就绪的case(缓冲channel可发送 vs raceCtx.Done()), Go随机选择可能导致结果未发送,使drain循环永久阻塞,进而导致daemon 持有d.mu死锁,所有IPC命令(stats/stop/新start)全部阻塞。 DNS解析返回IPv4+IPv6双地址时触发此bug,约50%概率死锁。 修复:移除select,直接发送到缓冲channel(容量=len(ips)永不阻塞), 多余连接由drain循环负责关闭。
This commit is contained in:
@@ -99,13 +99,13 @@ func raceDial(ctx context.Context, network string, ips []string, port string) (n
|
||||
go func(target string) {
|
||||
d := &net.Dialer{}
|
||||
c, err := d.DialContext(raceCtx, network, net.JoinHostPort(target, port))
|
||||
select {
|
||||
case resultCh <- result{conn: c, err: err}:
|
||||
case <-raceCtx.Done():
|
||||
if c != nil {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
// Always send the result: resultCh is buffered (len(ips)),
|
||||
// so this never blocks. Do NOT use a select with
|
||||
// <-raceCtx.Done() here - when the context is cancelled
|
||||
// both cases would be ready and Go's random select pick
|
||||
// could skip the send, causing the drain loop below to
|
||||
// block forever.
|
||||
resultCh <- result{conn: c, err: err}
|
||||
}(ip)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,8 @@ func raceDial(ctx context.Context, network string, ips []string, port string) (n
|
||||
// any late successful connections.
|
||||
cancel()
|
||||
for j := i + 1; j < len(ips); j++ {
|
||||
if late := <-resultCh; late.conn != nil {
|
||||
late := <-resultCh
|
||||
if late.conn != nil {
|
||||
late.conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user