1
0

udp_linux_test.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. //go:build linux && !android && !e2e_testing
  2. package udp
  3. import (
  4. "errors"
  5. "fmt"
  6. "net"
  7. "net/netip"
  8. "runtime"
  9. "sync/atomic"
  10. "testing"
  11. "time"
  12. "golang.org/x/sys/unix"
  13. )
  14. // TestShutdownWakesAfterRx_Mechanism exercises the kernel quirk our teardown
  15. // relies on: once a socket has received a packet, shutdown(2) wakes a blocked
  16. // recvmmsg with n>=1/Len==0 (not n==0). recvmmsg must turn that into net.ErrClosed
  17. // once Close set closed, so a parked reader exits instead of spinning.
  18. func TestShutdownWakesAfterRx_Mechanism(t *testing.T) {
  19. udpSettings := Settings{
  20. Listen: netip.MustParseAddrPort("127.0.0.1:0"),
  21. Multi: true,
  22. Batch: 64,
  23. Offloads: true,
  24. }
  25. c, err := NewListener(testLogger(), udpSettings)
  26. if err != nil {
  27. t.Fatalf("NewListener: %v", err)
  28. }
  29. sc := c.(*StdConn)
  30. addr, err := sc.LocalAddr()
  31. if err != nil {
  32. t.Fatalf("LocalAddr: %v", err)
  33. }
  34. msgs, _, _, _ := prepareRawMessages(sc.batch, 0xffff, 16)
  35. // Receive a real packet so the socket has carried data.
  36. send, err := net.Dial("udp", addr.String())
  37. if err != nil {
  38. t.Fatalf("dial: %v", err)
  39. }
  40. if _, err := send.Write([]byte("hello")); err != nil {
  41. t.Fatalf("write: %v", err)
  42. }
  43. time.Sleep(50 * time.Millisecond)
  44. n, err := sc.recvmmsg(msgs)
  45. t.Logf("drain of real packet: n=%d err=%v msgs[0].Len=%d", n, err, msgs[0].Len)
  46. _ = send.Close()
  47. // Block a reader on the now-empty queue, then tear down as Close() does.
  48. // recvmmsg must return net.ErrClosed (not hang, not spin) even post-rx.
  49. done := make(chan error, 1)
  50. go func() {
  51. _, err := sc.recvmmsg(msgs)
  52. done <- err
  53. }()
  54. time.Sleep(150 * time.Millisecond) // let it park in recvmmsg
  55. sc.closed.Store(true)
  56. if serr := unix.Shutdown(sc.sysFd, unix.SHUT_RDWR); serr != nil {
  57. t.Logf("shutdown returned %v (expected ENOTCONN on unconnected UDP)", serr)
  58. }
  59. select {
  60. case err := <-done:
  61. if !errors.Is(err, net.ErrClosed) {
  62. t.Errorf("recvmmsg after post-rx shutdown returned %v, want net.ErrClosed", err)
  63. }
  64. case <-time.After(2 * time.Second):
  65. t.Fatalf("HANG: recvmmsg did not return after shutdown following a received packet")
  66. }
  67. _ = unix.Close(sc.sysFd)
  68. }
  69. // TestListenOutTeardown_TrafficPatterns reproduces the field report: a blocking
  70. // reader must tear down cleanly on Close() regardless of what the socket has
  71. // carried. The three cases the report called out:
  72. //
  73. // no traffic ever -> works (shutdown wakes recvmmsg with n==0)
  74. // ping once, then idle -> historically HUNG: once the socket has received a
  75. // packet, shutdown(2) wakes recvmmsg with n>=1/Len==0,
  76. // which an n==0-only teardown check misses
  77. // continuous traffic -> works (a real packet is always arriving)
  78. //
  79. // All three must return within the deadline; a hang dumps goroutines so the
  80. // stuck reader is visible.
  81. func TestListenOutTeardown_TrafficPatterns(t *testing.T) {
  82. cases := []struct {
  83. name string
  84. traffic func(send net.Conn, stop <-chan struct{})
  85. }{
  86. {"no_traffic_ever", func(net.Conn, <-chan struct{}) {}},
  87. {"ping_once_then_idle", func(send net.Conn, _ <-chan struct{}) {
  88. _, _ = send.Write([]byte("hello"))
  89. }},
  90. {"continuous", func(send net.Conn, stop <-chan struct{}) {
  91. for {
  92. select {
  93. case <-stop:
  94. return
  95. default:
  96. _, _ = send.Write([]byte("hello"))
  97. time.Sleep(2 * time.Millisecond)
  98. }
  99. }
  100. }},
  101. }
  102. // batch 1 exercises single-message reads, batch 64 a full recvmmsg batch;
  103. // both must tear down cleanly.
  104. for _, batch := range []int{1, 64} {
  105. for _, tc := range cases {
  106. t.Run(fmt.Sprintf("batch%d/%s", batch, tc.name), func(t *testing.T) {
  107. runTeardownCase(t, batch, tc.name, tc.traffic)
  108. })
  109. }
  110. }
  111. }
  112. func runTeardownCase(t *testing.T, batch int, name string, traffic func(send net.Conn, stop <-chan struct{})) {
  113. udpSettings := Settings{
  114. Listen: netip.MustParseAddrPort("127.0.0.1:0"),
  115. Multi: true,
  116. Batch: batch,
  117. Offloads: true,
  118. }
  119. c, err := NewListener(testLogger(), udpSettings)
  120. if err != nil {
  121. t.Fatalf("NewListener: %v", err)
  122. }
  123. sc := c.(*StdConn)
  124. addr, err := sc.LocalAddr()
  125. if err != nil {
  126. t.Fatalf("LocalAddr: %v", err)
  127. }
  128. var received atomic.Int64
  129. loopDone := make(chan error, 1)
  130. go func() {
  131. loopDone <- sc.ListenOut(func(netip.AddrPort, []byte) {
  132. received.Add(1)
  133. }, func(bufs [][]byte) [][]byte { return bufs })
  134. }()
  135. send, err := net.Dial("udp", addr.String())
  136. if err != nil {
  137. t.Fatalf("dial: %v", err)
  138. }
  139. defer send.Close()
  140. stop := make(chan struct{})
  141. trafficDone := make(chan struct{})
  142. go func() {
  143. traffic(send, stop)
  144. close(trafficDone)
  145. }()
  146. // Let the pattern run and, for the idle case, the reader park again on an
  147. // empty queue with the socket already having received a packet.
  148. time.Sleep(500 * time.Millisecond)
  149. start := time.Now()
  150. if err := sc.Close(); err != nil {
  151. t.Fatalf("Close: %v", err)
  152. }
  153. close(stop)
  154. select {
  155. case err := <-loopDone:
  156. // Clean teardown surfaces as net.ErrClosed (propagated like the other
  157. // platforms); the caller absorbs it via its closed flag.
  158. if err != nil && !errors.Is(err, net.ErrClosed) {
  159. t.Fatalf("%s: ListenOut returned unexpected error on teardown: %v", name, err)
  160. }
  161. t.Logf("%s: closed in %v (received %d packets)", name, time.Since(start), received.Load())
  162. case <-time.After(3 * time.Second):
  163. buf := make([]byte, 1<<20)
  164. n := runtime.Stack(buf, true)
  165. t.Fatalf("%s: HANG, ListenOut did not return within 3s of Close\n%s", name, buf[:n])
  166. }
  167. <-trafficDone
  168. }