This commit is contained in:
2026-06-15 23:45:14 +02:00
parent 29fd832bcd
commit 22e3bb4a18
32 changed files with 2531 additions and 362 deletions
+20 -5
View File
@@ -57,6 +57,7 @@ type RigState struct {
// Manager owns the active backend and runs the polling loop.
type Manager struct {
mu sync.RWMutex
startMu sync.Mutex // serializes Start/Stop so concurrent calls can't leak a poller
state RigState
emit func(RigState)
backend Backend
@@ -115,7 +116,13 @@ func (m *Manager) State() RigState {
// state.Error rather than returned, so the UI can keep retrying via the
// poll loop on next reconnect attempt.
func (m *Manager) Start(b Backend) {
m.Stop()
// Serialize the whole stop-old-then-start-new sequence. Two concurrent
// Start (or Start+Stop) calls could otherwise interleave and leave the
// previous poll goroutine alive — two pollers then fight, e.g. flipping
// OmniRig Rig1/Rig2 endlessly when the user reselects a rig.
m.startMu.Lock()
defer m.startMu.Unlock()
m.stopLocked()
m.mu.Lock()
m.stopCh = make(chan struct{})
m.doneCh = make(chan struct{})
@@ -134,6 +141,18 @@ func (m *Manager) Start(b Backend) {
// Stop signals the CAT goroutine to disconnect and waits for it to exit.
func (m *Manager) Stop() {
m.startMu.Lock()
defer m.startMu.Unlock()
m.stopLocked()
m.mu.Lock()
m.state = RigState{Enabled: false}
m.mu.Unlock()
m.emitState()
}
// stopLocked tears down any running poller and blocks until it exits. The
// caller must hold startMu so it can't race a concurrent Start.
func (m *Manager) stopLocked() {
m.mu.Lock()
stop := m.stopCh
done := m.doneCh
@@ -148,10 +167,6 @@ func (m *Manager) Stop() {
if done != nil {
<-done
}
m.mu.Lock()
m.state = RigState{Enabled: false}
m.mu.Unlock()
m.emitState()
}
// SetFrequency dispatches a SetFreq call to the CAT goroutine.