1+ package netstack
2+
3+ import (
4+ "os"
5+ "sync"
6+ "testing"
7+ "time"
8+ )
9+
10+ // TestWaitGroupRaceCondition tests that the WaitGroup reuse issue is fixed.
11+ // This test reproduces the scenario where an endpoint is swapped while
12+ // another goroutine is waiting on the old endpoint.
13+ func TestWaitGroupRaceCondition (t * testing.T ) {
14+ // Create a temp file to simulate a TUN device
15+ tmpFile , err := os .CreateTemp ("" , "test_tun" )
16+ if err != nil {
17+ t .Skip ("Cannot create temp file for test" )
18+ }
19+ defer os .Remove (tmpFile .Name ())
20+ defer tmpFile .Close ()
21+
22+ fd := int (tmpFile .Fd ())
23+
24+ // Create a magiclink endpoint
25+ endpoint , err := NewEndpoint (fd , 1500 , & testSink {})
26+ if err != nil {
27+ t .Fatalf ("Failed to create endpoint: %v" , err )
28+ }
29+ defer endpoint .Dispose ()
30+
31+ magicLink , ok := endpoint .(* magiclink )
32+ if ! ok {
33+ t .Fatalf ("Expected magiclink, got %T" , endpoint )
34+ }
35+
36+ // Start multiple goroutines that will call Wait() on the endpoint
37+ // while we swap endpoints in the background
38+ var wg sync.WaitGroup
39+ errors := make (chan error , 10 )
40+
41+ for i := 0 ; i < 5 ; i ++ {
42+ wg .Add (1 )
43+ go func (id int ) {
44+ defer wg .Done ()
45+ defer func () {
46+ if r := recover (); r != nil {
47+ errors <- r .(error )
48+ }
49+ }()
50+
51+ // Call Wait() multiple times to increase chance of race condition
52+ for j := 0 ; j < 10 ; j ++ {
53+ magicLink .Wait ()
54+ time .Sleep (time .Millisecond )
55+ }
56+ }(i )
57+ }
58+
59+ // Swap endpoints multiple times while Wait() is being called
60+ go func () {
61+ for i := 0 ; i < 5 ; i ++ {
62+ // Create another temp file for swapping
63+ tmpFile2 , err := os .CreateTemp ("" , "test_tun2" )
64+ if err != nil {
65+ continue
66+ }
67+ fd2 := int (tmpFile2 .Fd ())
68+
69+ // Swap to new fd
70+ magicLink .Swap (fd2 , 1500 )
71+ time .Sleep (time .Millisecond * 5 )
72+
73+ tmpFile2 .Close ()
74+ os .Remove (tmpFile2 .Name ())
75+ }
76+ }()
77+
78+ // Wait for all goroutines to complete
79+ done := make (chan struct {})
80+ go func () {
81+ wg .Wait ()
82+ close (done )
83+ }()
84+
85+ select {
86+ case <- done :
87+ // Check if any errors occurred
88+ select {
89+ case err := <- errors :
90+ t .Fatalf ("WaitGroup reuse panic occurred: %v" , err )
91+ default :
92+ // Success - no panic occurred
93+ }
94+ case <- time .After (time .Second * 10 ):
95+ t .Fatal ("Test timed out" )
96+ }
97+ }
98+
99+ // testSink is a simple implementation of io.WriteCloser for testing
100+ type testSink struct {}
101+
102+ func (ts * testSink ) Write (p []byte ) (n int , err error ) {
103+ return len (p ), nil
104+ }
105+
106+ func (ts * testSink ) Close () error {
107+ return nil
108+ }
0 commit comments