mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-17 20:19:55 +00:00
Replace the mutex-guarded callback model of peer.Conn with a per-peer event loop that exclusively owns all mutable connection state. External callers and transport workers post typed events into a non-blocking, coalescing mailbox instead of contending on conn.mu: - offers/answers coalesce to the newest message, a new offer flushes queued candidates of the superseded session - candidates are applied in arrival order from a bounded FIFO - transport state changes are never dropped - the blocking relay dial runs on a helper goroutine with a single dial in flight; signaling I/O (offer/answer sends) runs off the loop conn.mu now only guards the open/close lifecycle. Close posts a close event and waits for the loop teardown; the loop also tears down on engine context cancellation and releases resources of unprocessed events. Delete the Handshaker listener machinery (Listen loop, unbuffered drop-on-busy channels, AsyncOfferListener with its double-processing of the first offer), the never-wired dispatcher package and the unused ICEMonitor.ReconnectCh. Fix a goroutine leak in the WG watcher test that raced with tests mutating the package-level check timing vars.
117 lines
2.7 KiB
Go
117 lines
2.7 KiB
Go
package peer
|
|
|
|
import (
|
|
"sync"
|
|
)
|
|
|
|
// maxQueuedCandidates bounds the remote candidate queue; on overflow the
|
|
// oldest candidate is dropped. Lost candidates are recovered by the next
|
|
// offer exchange triggered by the guard.
|
|
const maxQueuedCandidates = 128
|
|
|
|
// mailbox is the coalescing inbox of the Conn event loop. Posting never
|
|
// blocks. Per message kind either the latest value wins (offer, answer,
|
|
// guard tick), the values queue in bounded FIFO order (candidates) or in
|
|
// unbounded FIFO order (lifecycle and transport state changes, which are
|
|
// low-volume and must not be lost). A new offer flushes the queued
|
|
// candidates because they belong to the superseded session.
|
|
type mailbox struct {
|
|
mu sync.Mutex
|
|
closed bool
|
|
|
|
lifecycle []event
|
|
transport []event
|
|
offer *evRemoteOffer
|
|
answer *evRemoteAnswer
|
|
candidates []evRemoteCandidate
|
|
guardTick bool
|
|
|
|
wake chan struct{}
|
|
}
|
|
|
|
func newMailbox() *mailbox {
|
|
return &mailbox{
|
|
wake: make(chan struct{}, 1),
|
|
}
|
|
}
|
|
|
|
// post stores the event and wakes the loop. It reports false if the mailbox
|
|
// is already closed and the event was not accepted.
|
|
func (m *mailbox) post(ev event) bool {
|
|
m.mu.Lock()
|
|
if m.closed {
|
|
m.mu.Unlock()
|
|
return false
|
|
}
|
|
|
|
switch e := ev.(type) {
|
|
case evClose:
|
|
m.lifecycle = append(m.lifecycle, e)
|
|
case evRemoteOffer:
|
|
m.offer = &e
|
|
m.candidates = nil
|
|
case evRemoteAnswer:
|
|
m.answer = &e
|
|
case evRemoteCandidate:
|
|
if len(m.candidates) >= maxQueuedCandidates {
|
|
m.candidates = m.candidates[1:]
|
|
}
|
|
m.candidates = append(m.candidates, e)
|
|
case evGuardTick:
|
|
m.guardTick = true
|
|
default:
|
|
m.transport = append(m.transport, ev)
|
|
}
|
|
m.mu.Unlock()
|
|
|
|
select {
|
|
case m.wake <- struct{}{}:
|
|
default:
|
|
}
|
|
return true
|
|
}
|
|
|
|
// drain returns the pending events in processing order: lifecycle first,
|
|
// then transport state changes, the coalesced offer and answer, the queued
|
|
// candidates and finally the guard tick.
|
|
func (m *mailbox) drain() []event {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.drainLocked()
|
|
}
|
|
|
|
// closeAndDrain marks the mailbox closed so further posts are rejected and
|
|
// returns the events that were still pending.
|
|
func (m *mailbox) closeAndDrain() []event {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.closed = true
|
|
return m.drainLocked()
|
|
}
|
|
|
|
func (m *mailbox) drainLocked() []event {
|
|
evs := make([]event, 0, len(m.lifecycle)+len(m.transport)+len(m.candidates)+3)
|
|
evs = append(evs, m.lifecycle...)
|
|
evs = append(evs, m.transport...)
|
|
if m.offer != nil {
|
|
evs = append(evs, *m.offer)
|
|
}
|
|
if m.answer != nil {
|
|
evs = append(evs, *m.answer)
|
|
}
|
|
for _, c := range m.candidates {
|
|
evs = append(evs, c)
|
|
}
|
|
if m.guardTick {
|
|
evs = append(evs, evGuardTick{})
|
|
}
|
|
|
|
m.lifecycle = nil
|
|
m.transport = nil
|
|
m.offer = nil
|
|
m.answer = nil
|
|
m.candidates = nil
|
|
m.guardTick = false
|
|
return evs
|
|
}
|