package discovery import ( "context" "sync" "codeberg.org/eduVPN/eduvpn-common/internal/log" ) // Manager is the discovery struct that is cached // with some bookkeeping to avoid race conditions type Manager struct { disco *Discovery cancel context.CancelFunc mu sync.RWMutex wait sync.WaitGroup } // NewManager creates a new Discovery manager func NewManager(disco *Discovery) *Manager { return &Manager{disco: disco} } func (m *Manager) lock(write bool) { if write { m.mu.Lock() return } m.mu.RLock() } func (m *Manager) unlock(write bool) { if write { m.mu.Unlock() return } m.mu.RUnlock() } // Discovery gets the cached discovery // `write` is true if discovery will be written to func (m *Manager) Discovery(write bool) (*Discovery, func()) { if write { m.wait.Wait() } m.lock(write) return m.disco, func() { m.unlock(write) } } // Cancel aborts the running discovery startup process func (m *Manager) Cancel() { if m.cancel != nil { m.cancel() } m.wait.Wait() } // Startup handles the discovery process in the background // It's called Startup because it's called when the lib is initialised func (m *Manager) Startup(ctx context.Context, cb func()) { ctx, cancel := context.WithCancel(ctx) m.cancel = cancel m.wait.Add(1) go func() { m.lock(false) discoCopy, err := m.disco.Copy() if err != nil { log.Logger.Warningf("internal error, failed to clone discovery, %v", err) return } m.unlock(false) // we already log the warning discoCopy.Servers(ctx) //nolint:errcheck m.lock(true) m.disco.UpdateServers(discoCopy) m.unlock(true) m.wait.Done() select { case <-ctx.Done(): return default: if cb == nil { return } cb() } }() }