mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-24 16:41:30 +02:00
On network changes the client restarted the whole engine. That is heavy-handed and slow: it tears down working state to recover from a transition the engine could handle itself. This replaces the restart with proper network event handling. Suspend the retry loops while no network is available. Instead of burning through backoff intervals against an unreachable network, the reconnection loops park until the OS reports a usable network again. Reconnect immediately on a network switch. When the OS hands us a new network, connections bound to the old one are swept and re-dialed right away, rather than waiting for a timeout to notice they are dead.
171 lines
3.4 KiB
Go
171 lines
3.4 KiB
Go
package netstate
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNewStateIsOnline(t *testing.T) {
|
|
assert.True(t, New().IsOnline(), "a fresh State should start online")
|
|
}
|
|
|
|
func TestSetTogglesOnlineState(t *testing.T) {
|
|
s := New()
|
|
|
|
s.Set(false)
|
|
assert.False(t, s.IsOnline(), "state should be offline after Set(false)")
|
|
|
|
s.Set(true)
|
|
assert.True(t, s.IsOnline(), "state should be online after Set(true)")
|
|
}
|
|
|
|
func TestWaitReturnsImmediatelyWhenOnline(t *testing.T) {
|
|
s := New()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
|
|
waited, err := s.Wait(ctx)
|
|
require.NoError(t, err)
|
|
assert.False(t, waited, "Wait should not block when the network is online")
|
|
}
|
|
|
|
func TestWaitBlocksUntilOnline(t *testing.T) {
|
|
s := New()
|
|
s.Set(false)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
result := make(chan bool, 1)
|
|
go func() {
|
|
waited, err := s.Wait(ctx)
|
|
if err != nil {
|
|
result <- false
|
|
return
|
|
}
|
|
result <- waited
|
|
}()
|
|
|
|
// Verify Wait is actually blocking while offline
|
|
select {
|
|
case <-result:
|
|
t.Fatal("Wait should block while the network is offline")
|
|
case <-time.After(100 * time.Millisecond):
|
|
}
|
|
|
|
s.Set(true)
|
|
|
|
select {
|
|
case waited := <-result:
|
|
assert.True(t, waited, "Wait should report that it had to wait for the network")
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Wait should return promptly after the network becomes available")
|
|
}
|
|
}
|
|
|
|
func TestWaitReturnsOnContextCancel(t *testing.T) {
|
|
s := New()
|
|
s.Set(false)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
result := make(chan error, 1)
|
|
go func() {
|
|
_, err := s.Wait(ctx)
|
|
result <- err
|
|
}()
|
|
|
|
cancel()
|
|
|
|
select {
|
|
case err := <-result:
|
|
assert.ErrorIs(t, err, context.Canceled)
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Wait should return promptly after context cancellation")
|
|
}
|
|
}
|
|
|
|
func TestWaitWakesAllWaiters(t *testing.T) {
|
|
s := New()
|
|
s.Set(false)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
const waiters = 10
|
|
var wg sync.WaitGroup
|
|
results := make(chan bool, waiters)
|
|
for i := 0; i < waiters; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
waited, err := s.Wait(ctx)
|
|
if err != nil {
|
|
results <- false
|
|
return
|
|
}
|
|
results <- waited
|
|
}()
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
s.Set(true)
|
|
wg.Wait()
|
|
|
|
close(results)
|
|
count := 0
|
|
for waited := range results {
|
|
assert.True(t, waited, "every waiter should report that it waited")
|
|
count++
|
|
}
|
|
assert.Equal(t, waiters, count, "all waiters should have returned")
|
|
}
|
|
|
|
func TestNilStateReadsAreNoops(t *testing.T) {
|
|
var s *State
|
|
|
|
assert.True(t, s.IsOnline(), "nil State should report online")
|
|
|
|
waited, err := s.Wait(context.Background())
|
|
require.NoError(t, err)
|
|
assert.False(t, waited, "nil State's Wait should not block")
|
|
}
|
|
|
|
func TestConcurrentSetAndWait(t *testing.T) {
|
|
s := New()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 4; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < 100; j++ {
|
|
s.Set(j%2 == 0)
|
|
s.IsOnline()
|
|
}
|
|
}()
|
|
}
|
|
for i := 0; i < 4; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for j := 0; j < 100; j++ {
|
|
if _, err := s.Wait(ctx); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|