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.
111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
// Package netstate tracks OS-reported network availability for the client.
|
|
//
|
|
// A State instance is owned by the platform integration (e.g. the Android or
|
|
// iOS bindings, fed from ConnectivityManager callbacks or NWPathMonitor) and
|
|
// is injected into the connection retry loops (management, signal, relay,
|
|
// peer guards and the top-level connect loop), which consult it to avoid
|
|
// burning CPU and battery on reconnect attempts while the device has no
|
|
// network at all (e.g. airplane mode), and to reset their backoff as soon as
|
|
// the network returns.
|
|
//
|
|
// Consumers hold a *State that may be nil — every non-mobile platform leaves
|
|
// it unset. The read methods are safe on a nil receiver: they report online
|
|
// and never block, so consumers behave as if this package did not exist.
|
|
package netstate
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// State holds the OS-reported network availability. The zero value is not
|
|
// usable; create instances with New.
|
|
type State struct {
|
|
mu sync.Mutex
|
|
online bool
|
|
changed chan struct{}
|
|
}
|
|
|
|
// New creates a State that starts online. Platforms without network tracking
|
|
// pass a nil *State instead: the read methods treat nil as always online and
|
|
// never block, so consumers need no nil guards.
|
|
func New() *State {
|
|
return &State{
|
|
online: true,
|
|
changed: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Set records whether the OS reports any usable network. Transitions wake up
|
|
// all Wait callers immediately. Unlike the read methods, Set is not nil-safe:
|
|
// it is only for the platform owner that created the State with New.
|
|
func (s *State) Set(online bool) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if s.online == online {
|
|
return
|
|
}
|
|
s.online = online
|
|
close(s.changed)
|
|
s.changed = make(chan struct{})
|
|
log.Infof("OS network availability changed: online=%t", online)
|
|
}
|
|
|
|
// IsOnline reports whether the OS reports at least one usable network. On a
|
|
// nil receiver — no State injected — it reports online.
|
|
func (s *State) IsOnline() bool {
|
|
if s == nil {
|
|
return true
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.online
|
|
}
|
|
|
|
// Changed returns a channel closed on the next availability transition, for
|
|
// callers that already own a select loop and cannot block in Wait. Re-read it
|
|
// after every fire: each transition installs a fresh channel. On a nil
|
|
// receiver — no State injected — it returns nil, which blocks forever in a
|
|
// select, so the caller simply never observes a transition.
|
|
func (s *State) Changed() <-chan struct{} {
|
|
if s == nil {
|
|
return nil
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
return s.changed
|
|
}
|
|
|
|
// Wait blocks while the network is offline. It reports whether it had to
|
|
// wait, so callers can reset their backoff after an outage. It returns early
|
|
// with the context error when ctx is done. On a nil receiver — no State
|
|
// injected — it returns immediately.
|
|
func (s *State) Wait(ctx context.Context) (bool, error) {
|
|
if s == nil {
|
|
return false, nil
|
|
}
|
|
waited := false
|
|
for {
|
|
s.mu.Lock()
|
|
if s.online {
|
|
s.mu.Unlock()
|
|
return waited, nil
|
|
}
|
|
ch := s.changed
|
|
s.mu.Unlock()
|
|
|
|
if !waited {
|
|
waited = true
|
|
log.Debugf("network is offline, pausing connection attempts")
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return waited, ctx.Err()
|
|
case <-ch:
|
|
}
|
|
}
|
|
}
|