@@ -7,6 +7,7 @@ package reacher_test
77import (
88 "context"
99 "errors"
10+ "sync"
1011 "testing"
1112 "time"
1213
@@ -127,6 +128,167 @@ func TestDisconnected(t *testing.T) {
127128 })
128129}
129130
131+ func TestAddressUpdateOnReconnect (t * testing.T ) {
132+ t .Parallel ()
133+
134+ synctest .Test (t , func (t * testing.T ) {
135+ // Use 1 worker and a known retry duration to make timing deterministic.
136+ options := reacher.Options {
137+ PingTimeout : time .Second * 5 ,
138+ Workers : 1 ,
139+ RetryAfterDuration : time .Minute ,
140+ }
141+
142+ overlay := swarm .RandAddress (t )
143+ oldAddr , _ := ma .NewMultiaddr ("/ip4/127.0.0.1/tcp/7071/p2p/16Uiu2HAmTBuJT9LvNmBiQiNoTsxE5mtNy6YG3paw79m94CRa9sRb" )
144+ newAddr , _ := ma .NewMultiaddr ("/ip4/192.168.1.1/tcp/7072/p2p/16Uiu2HAmTBuJT9LvNmBiQiNoTsxE5mtNy6YG3paw79m94CRa9sRb" )
145+
146+ var pingsMu sync.Mutex
147+ var pings []ma.Multiaddr
148+ pinged := make (chan struct {}, 8 )
149+
150+ pingFunc := func (_ context.Context , a ma.Multiaddr ) (time.Duration , error ) {
151+ pingsMu .Lock ()
152+ pings = append (pings , a )
153+ pingsMu .Unlock ()
154+ pinged <- struct {}{}
155+ return 0 , nil
156+ }
157+
158+ reachableFunc := func (addr swarm.Address , status p2p.ReachabilityStatus ) {}
159+
160+ mock := newMock (pingFunc , reachableFunc )
161+
162+ r := reacher .New (mock , mock , & options , log .Noop )
163+ testutil .CleanupCloser (t , r )
164+
165+ // First connection with old address – triggers initial ping.
166+ r .Connected (overlay , oldAddr )
167+
168+ select {
169+ case <- time .After (time .Second * 10 ):
170+ t .Fatal ("timed out waiting for initial ping" )
171+ case <- pinged :
172+ }
173+
174+ // Verify old address was pinged first.
175+ pingsMu .Lock ()
176+ if len (pings ) != 1 {
177+ t .Fatalf ("expected 1 ping after initial connect, got %d" , len (pings ))
178+ }
179+ if ! pings [0 ].Equal (oldAddr ) {
180+ t .Fatalf ("first ping should use old address, got %s" , pings [0 ])
181+ }
182+ pingsMu .Unlock ()
183+
184+ // Reconnect with a new address — should trigger immediate re-ping.
185+ r .Connected (overlay , newAddr )
186+
187+ select {
188+ case <- time .After (time .Second * 10 ):
189+ t .Fatal ("timed out waiting for reconnect ping" )
190+ case <- pinged :
191+ }
192+
193+ // Verify the reconnect pinged the new address.
194+ pingsMu .Lock ()
195+ if len (pings ) != 2 {
196+ t .Fatalf ("expected 2 pings after reconnect, got %d" , len (pings ))
197+ }
198+ if ! pings [1 ].Equal (newAddr ) {
199+ t .Fatalf ("reconnect ping should use new address, got %s" , pings [1 ])
200+ }
201+ pingsMu .Unlock ()
202+
203+ // Advance time past the retry duration — should trigger a scheduled re-ping.
204+ time .Sleep (options .RetryAfterDuration + time .Second )
205+
206+ select {
207+ case <- time .After (time .Second * 10 ):
208+ t .Fatal ("timed out waiting for scheduled re-ping" )
209+ case <- pinged :
210+ }
211+
212+ // Verify the scheduled re-ping used the new address.
213+ pingsMu .Lock ()
214+ if len (pings ) != 3 {
215+ t .Fatalf ("expected 3 pings after retry duration, got %d" , len (pings ))
216+ }
217+ if ! pings [2 ].Equal (newAddr ) {
218+ t .Fatalf ("scheduled re-ping should use new address, got %s" , pings [2 ])
219+ }
220+ pingsMu .Unlock ()
221+ })
222+ }
223+
224+ func TestHeapOrdering (t * testing.T ) {
225+ t .Parallel ()
226+
227+ synctest .Test (t , func (t * testing.T ) {
228+ // Use single worker to ensure sequential processing
229+ options := reacher.Options {
230+ PingTimeout : time .Second * 5 ,
231+ Workers : 1 ,
232+ RetryAfterDuration : time .Second * 10 ,
233+ }
234+
235+ var pingOrder []swarm.Address
236+ var pingOrderMu sync.Mutex
237+ allPinged := make (chan struct {})
238+
239+ overlay1 := swarm .RandAddress (t )
240+ overlay2 := swarm .RandAddress (t )
241+ overlay3 := swarm .RandAddress (t )
242+ addr , _ := ma .NewMultiaddr ("/ip4/127.0.0.1/tcp/7071/p2p/16Uiu2HAmTBuJT9LvNmBiQiNoTsxE5mtNy6YG3paw79m94CRa9sRb" )
243+
244+ pingFunc := func (_ context.Context , _ ma.Multiaddr ) (time.Duration , error ) {
245+ return 0 , nil
246+ }
247+
248+ reachableFunc := func (overlay swarm.Address , status p2p.ReachabilityStatus ) {
249+ pingOrderMu .Lock ()
250+ pingOrder = append (pingOrder , overlay )
251+ if len (pingOrder ) == 3 {
252+ close (allPinged )
253+ }
254+ pingOrderMu .Unlock ()
255+ }
256+
257+ mock := newMock (pingFunc , reachableFunc )
258+
259+ r := reacher .New (mock , mock , & options , log .Noop )
260+ testutil .CleanupCloser (t , r )
261+
262+ // Add peers - they should all be pinged since retryAfter starts at zero
263+ r .Connected (overlay1 , addr )
264+ r .Connected (overlay2 , addr )
265+ r .Connected (overlay3 , addr )
266+
267+ select {
268+ case <- time .After (time .Second * 5 ):
269+ t .Fatalf ("test timed out, only %d peers pinged" , len (pingOrder ))
270+ case <- allPinged :
271+ }
272+
273+ // Verify all three peers were pinged
274+ pingOrderMu .Lock ()
275+ defer pingOrderMu .Unlock ()
276+
277+ if len (pingOrder ) != 3 {
278+ t .Fatalf ("expected 3 peers pinged, got %d" , len (pingOrder ))
279+ }
280+
281+ // Verify all overlays are present (order may vary due to heap with same retryAfter)
282+ seen := make (map [string ]bool )
283+ for _ , o := range pingOrder {
284+ seen [o .String ()] = true
285+ }
286+ if ! seen [overlay1 .String ()] || ! seen [overlay2 .String ()] || ! seen [overlay3 .String ()] {
287+ t .Fatalf ("not all peers were pinged" )
288+ }
289+ })
290+ }
291+
130292type mock struct {
131293 pingFunc func (context.Context , ma.Multiaddr ) (time.Duration , error )
132294 reachableFunc func (swarm.Address , p2p.ReachabilityStatus )
0 commit comments