mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-20 13:49:07 +02:00
73 lines
2.7 KiB
Go
73 lines
2.7 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestManager_InProgressDialIsSafeForReadersAndCleanup covers the new state that
|
|
// option 1 introduces: openConnVia now publishes a relay track in the map and
|
|
// releases the map lock BEFORE dialing, so a track can legitimately exist with
|
|
// relayClient == nil and no track lock held while its Connect() is in flight.
|
|
//
|
|
// The status path (RelayStates) and the cleanup loop both touch every track, so
|
|
// both must tolerate that mid-dial state — neither deref the nil relayClient nor
|
|
// evict a track whose dial is still running.
|
|
func TestManager_InProgressDialIsSafeForReadersAndCleanup(t *testing.T) {
|
|
m := &Manager{
|
|
relayClients: make(map[string]*RelayTrack),
|
|
// 0 so the created-time grace does not mask the mid-dial (nil relayClient)
|
|
// guard in cleanUpUnusedRelays.
|
|
keepUnusedServerTime: 0,
|
|
}
|
|
|
|
const addr = "relay.example.com:443"
|
|
rt := NewRelayTrack() // ready open, relayClient nil, unlocked == dial in progress
|
|
m.relayClients[addr] = rt
|
|
|
|
// A status call must not block or panic on a relay still being dialed; it has
|
|
// no state to report yet.
|
|
require.Empty(t, m.RelayStates(), "a relay still being dialed has no state to report")
|
|
|
|
// Cleanup must not deref the nil relayClient, and must not evict an in-flight dial.
|
|
m.cleanUpUnusedRelays()
|
|
m.relayClientsMutex.RLock()
|
|
_, stillTracked := m.relayClients[addr]
|
|
m.relayClientsMutex.RUnlock()
|
|
require.True(t, stillTracked, "an in-progress dial must not be cleaned up")
|
|
}
|
|
|
|
// TestOpenConnOnTrack_ReleasesOnContextCancelDuringDial verifies the core option-1
|
|
// property: a caller that finds a track whose dial is in progress waits on
|
|
// rt.ready, not on the track lock, and can be released by its own context. This
|
|
// is what keeps a slow relay dial from serializing behind the track lock.
|
|
func TestOpenConnOnTrack_ReleasesOnContextCancelDuringDial(t *testing.T) {
|
|
m := &Manager{relayClients: make(map[string]*RelayTrack)}
|
|
|
|
rt := NewRelayTrack() // ready deliberately left open == dial in progress
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
_, err := m.openConnOnTrack(ctx, rt, "peerKey")
|
|
errCh <- err
|
|
}()
|
|
|
|
// While waiting for the dial the caller holds no track lock, so a concurrent
|
|
// reader (e.g. RelayStates) can still take it.
|
|
require.True(t, rt.TryRLock(), "waiter must not hold the track lock while the dial is in progress")
|
|
rt.RUnlock()
|
|
|
|
// Caller gives up: openConnOnTrack must return via ctx rather than hang on the dial.
|
|
cancel()
|
|
select {
|
|
case err := <-errCh:
|
|
require.ErrorIs(t, err, context.Canceled)
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("openConnOnTrack did not return on ctx cancellation while a dial was in progress")
|
|
}
|
|
}
|