package relaysvc import ( "context" "sync" "github.com/libp2p/go-libp2p/core/event" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/p2p/host/eventbus" relayv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/relay" ) type RelayManager struct { host host.Host mutex sync.Mutex relay *relayv2.Relay opts []relayv2.Option refCount sync.WaitGroup ctxCancel context.CancelFunc } func NewRelayManager(host host.Host, opts ...relayv2.Option) *RelayManager { ctx, cancel := context.WithCancel(context.Background()) m := &RelayManager{ host: host, opts: opts, ctxCancel: cancel, } m.refCount.Add(1) go m.background(ctx) return m } func (m *RelayManager) background(ctx context.Context) { defer m.refCount.Done() defer func() { m.mutex.Lock() if m.relay != nil { m.relay.Close() } m.mutex.Unlock() }() subReachability, _ := m.host.EventBus().Subscribe(new(event.EvtLocalReachabilityChanged), eventbus.Name("relaysvc")) defer subReachability.Close() for { select { case <-ctx.Done(): return case ev, ok := <-subReachability.Out(): if !ok { return } if err := m.reachabilityChanged(ev.(event.EvtLocalReachabilityChanged).Reachability); err != nil { return } } } } func (m *RelayManager) reachabilityChanged(r network.Reachability) error { switch r { case network.ReachabilityPublic: m.mutex.Lock() defer m.mutex.Unlock() // This could happen if two consecutive EvtLocalReachabilityChanged report the same reachability. // This shouldn't happen, but it's safer to double-check. if m.relay != nil { return nil } relay, err := relayv2.New(m.host, m.opts...) if err != nil { return err } m.relay = relay default: m.mutex.Lock() defer m.mutex.Unlock() if m.relay != nil { err := m.relay.Close() m.relay = nil return err } } return nil } func (m *RelayManager) Close() error { m.ctxCancel() m.refCount.Wait() return nil }