Skip to content

Commit ad245f6

Browse files
committed
fix(broker): wake test broker goroutine with Shutdown, not Close (Linux)
The CLI broker tests deadlocked on linux-amd64 CI (10m timeout) while passing on macOS/Windows: a bare close() does not interrupt a recvmsg blocked on that fd in another goroutine on Linux (it does on darwin/BSD). fakeBroker and TestBrokerHTTPClient_RefusedReturnsError join their control goroutine after teardown, so they hung waiting for a recvmsg that never returned. Use syscall.Shutdown(SHUT_RDWR) to wake the blocked recvmsg portably, then join, then close. Verified: the cross-compiled linux/arm64 test binary runs clean (count=2) in an ubuntu container; native darwin still passes.
1 parent 8703d38 commit ad245f6

1 file changed

Lines changed: 13 additions & 6 deletions

File tree

internal/cli/broker_dial_unix_test.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ func fakeBroker(t *testing.T, parentFD int, upstream string, realKey string) (st
3131
defer close(ctlGone)
3232
buf := make([]byte, 8)
3333
for {
34-
// Blocks until a handshake datagram arrives or parentFD is closed by
35-
// stop() (which unblocks Recvmsg with EBADF/EOF → clean exit, no leak).
34+
// Blocks until a handshake datagram arrives or stop() shuts down
35+
// parentFD (recvmsg then returns EOF → clean exit, no leak). NOTE: a
36+
// bare Close(parentFD) does NOT wake a blocked recvmsg on Linux (only on
37+
// darwin/BSD), so stop() uses Shutdown — see the stop func below.
3638
n, _, _, _, err := syscall.Recvmsg(parentFD, buf, nil, 0)
3739
if err != nil || n == 0 {
3840
return
@@ -64,10 +66,13 @@ func fakeBroker(t *testing.T, parentFD int, upstream string, realKey string) (st
6466
}
6567
}()
6668
return func() {
67-
// Closing parentFD unblocks the control goroutine's Recvmsg so it exits
68-
// instead of leaking across -count iterations.
69-
_ = syscall.Close(parentFD)
69+
// Shutdown (NOT a bare Close) wakes the control goroutine's blocked
70+
// Recvmsg portably — on Linux, closing an fd does not interrupt a recvmsg
71+
// blocked on it in another goroutine; shutdown returns EOF on both Linux
72+
// and darwin. Join, then close.
73+
_ = syscall.Shutdown(parentFD, syscall.SHUT_RDWR)
7074
<-ctlGone
75+
_ = syscall.Close(parentFD)
7176
mu.Lock()
7277
for _, c := range conns {
7378
_ = c.Close()
@@ -231,8 +236,10 @@ func TestBrokerHTTPClient_RefusedReturnsError(t *testing.T) {
231236
if _, err := client.Do(req); err == nil {
232237
t.Fatal("client.Do must fail when broker refuses with 0xFF")
233238
}
234-
_ = syscall.Close(parentFD)
239+
// Shutdown (not bare Close) to wake the goroutine's blocked Recvmsg on Linux.
240+
_ = syscall.Shutdown(parentFD, syscall.SHUT_RDWR)
235241
<-done
242+
_ = syscall.Close(parentFD)
236243
}
237244

238245
// serveProxyConn is a tiny test upstream-proxy used by fakeBroker; the real

0 commit comments

Comments
 (0)