mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-29 11:01:29 +02:00
Losing the last network only flipped the availability state: the dead management, signal and relay sockets stayed silently connected until their own timeouts, so the client kept reporting Connected with no network at all. Introduce client/netevents with a Manager that ties the availability state, the connection sweeper and the status recorder together, and move the netstate and netsweep packages under it (netsweep renamed to sweep). SetNetworkAvailable(false) now also sweeps the registered connections so their owners redial and the listener reaches the NoNetwork state. The Android and iOS bindings own a Manager instance and inject it through the constructors; consumers hold the concrete *Manager whose nil zero value reports always-online and never sweeps, with interfaces kept only as parameter contracts. The relay guard settle wait moved into the Manager as WaitSettled, removing the netevents import from the relay package.
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()
|
|
}
|