Start returns before the first sync response necessarily arrives, so the
disabled case could pass against a client that had simply not synced yet.
Wait for the engine to apply a sync, then require the persistence error
rather than any error, so a not-started engine no longer satisfies it.
Also document that PeerState is a read-only snapshot which shares its mutex
and route map with the recorder.
Running several embedded clients in one process is already how the proxy
works, and both of the costs below scale with the number of clients.
Every engine retains its latest management sync response so that
GetLatestSyncResponse can read it back. Retaining it pins a decoded copy of
the whole network map for the lifetime of the client, roughly 680 KB per
client against a 2000 peer network map. A process holding many clients that
never read the response back pays that for nothing, so
DisableSyncResponsePersistence lets a caller opt out. Persistence stays on
by default.
Status runs health probes against every STUN and TURN server and takes the
engine lock, which is too expensive to poll at high frequency or across many
clients. StatusSnapshot returns the same recorder state without the probes.
Reading that state from another module also needs the peer status constants
and the per-peer state type, which were only partly exported.
- Engine.Start takes syncMsgMux with a deferred unlock (engine.go:445) and parks in receiveSignalEvents → WaitStreamConnected (engine.go:1762), which only wakes on
signal-stream connect or client-context cancellation.
- When signal never connects, the 30s startup timeout fires and embed.Client.Start's rollback (embed.go:281) called client.Stop() → Engine.Stop, which blocks acquiring
syncMsgMux (engine.go:318). The cancel() that would unpark Start was deferred until Start returned — permanent cycle. RemovePeer calls (g43/g385) then queue behind the
lifecycle mutex.
- Notably, embed.Client.Stop and the daemon's cleanupConnection both cancel before stopping — the startup rollback was the only path that didn't.
- Engine.Start takes syncMsgMux with a deferred unlock (engine.go:445) and parks in receiveSignalEvents → WaitStreamConnected (engine.go:1762), which only wakes on
signal-stream connect or client-context cancellation.
- When signal never connects, the 30s startup timeout fires and embed.Client.Start's rollback (embed.go:281) called client.Stop() → Engine.Stop, which blocks acquiring
syncMsgMux (engine.go:318). The cancel() that would unpark Start was deferred until Start returned — permanent cycle. RemovePeer calls (g43/g385) then queue behind the
lifecycle mutex.
- Notably, embed.Client.Stop and the daemon's cleanupConnection both cancel before stopping — the startup rollback was the only path that didn't.