udp_linux_test.go 5.1 KB

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