udp_linux_test.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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 Shutdown() 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() {})
  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. // Tear down the way Interface.Close does, the fd stays open until the reader is gone
  150. start := time.Now()
  151. if err := sc.Shutdown(); err != nil {
  152. t.Fatalf("Shutdown: %v", err)
  153. }
  154. defer func() { _ = sc.Close() }()
  155. select {
  156. case err := <-loopDone:
  157. // Clean teardown surfaces as net.ErrClosed (propagated like the other
  158. // platforms); the caller absorbs it via its closed flag.
  159. if err != nil && !errors.Is(err, net.ErrClosed) {
  160. t.Fatalf("%s: ListenOut returned unexpected error on teardown: %v", name, err)
  161. }
  162. t.Logf("%s: closed in %v (received %d packets)", name, time.Since(start), received.Load())
  163. case <-time.After(3 * time.Second):
  164. buf := make([]byte, 1<<20)
  165. n := runtime.Stack(buf, true)
  166. t.Fatalf("%s: HANG, ListenOut did not return within 3s of Shutdown\n%s", name, buf[:n])
  167. }
  168. close(stop)
  169. <-trafficDone
  170. }
  171. // A shut down socket still returns queued datagrams, so a reader that is behind has to stop on its own within the
  172. // batch it was working on
  173. func TestListenOutShutdown_BusyReader(t *testing.T) {
  174. const batch = 64
  175. udpSettings := Settings{Listen: netip.MustParseAddrPort("127.0.0.1:0"), Multi: true, Batch: batch}
  176. c, err := NewListener(testLogger(), udpSettings)
  177. if err != nil {
  178. t.Fatalf("NewListener: %v", err)
  179. }
  180. sc := c.(*StdConn)
  181. defer sc.Close()
  182. addr, err := sc.LocalAddr()
  183. if err != nil {
  184. t.Fatalf("LocalAddr: %v", err)
  185. }
  186. stop := make(chan struct{})
  187. defer close(stop)
  188. for range 8 {
  189. send, err := net.Dial("udp", addr.String())
  190. if err != nil {
  191. t.Fatalf("dial: %v", err)
  192. }
  193. defer send.Close()
  194. go func() {
  195. b := make([]byte, 1200)
  196. for {
  197. select {
  198. case <-stop:
  199. return
  200. default:
  201. _, _ = send.Write(b)
  202. }
  203. }
  204. }()
  205. }
  206. var afterShutdown atomic.Int64
  207. loopDone := make(chan error, 1)
  208. go func() {
  209. loopDone <- sc.ListenOut(func(netip.AddrPort, []byte) {
  210. if sc.closed.Load() {
  211. afterShutdown.Add(1)
  212. }
  213. // Slower than the senders so the queue never drains
  214. for start := time.Now(); time.Since(start) < 200*time.Microsecond; {
  215. }
  216. }, func() {})
  217. }()
  218. time.Sleep(200 * time.Millisecond)
  219. if err := sc.Shutdown(); err != nil {
  220. t.Fatalf("Shutdown: %v", err)
  221. }
  222. select {
  223. case err := <-loopDone:
  224. if !errors.Is(err, net.ErrClosed) {
  225. t.Fatalf("ListenOut returned %v, want net.ErrClosed", err)
  226. }
  227. case <-time.After(3 * time.Second):
  228. t.Fatalf("ListenOut still running 3s after Shutdown, delivered %d packets since", afterShutdown.Load())
  229. }
  230. if n := afterShutdown.Load(); n > batch {
  231. t.Fatalf("delivered %d packets after Shutdown, more than the one batch in flight", n)
  232. }
  233. }