Detach tone-test savers on reconnect

This commit is contained in:
Brandon McGinty (chatgpt)
2026-08-09 23:12:56 -04:00
committed by Brandon McGinty
parent b23afebdb3
commit bad4e8172a
4 changed files with 66 additions and 15 deletions
+16 -4
View File
@@ -64,10 +64,11 @@ type Barnard struct {
FileStreamMutex sync.Mutex
// Added for tone test mode (bypasses all soundcard/OpenAL)
ToneTest bool
ToneTestOutput string
toneTestStop chan struct{}
toneTestSaver *AudioFileSaver
ToneTest bool
ToneTestOutput string
toneTestStop chan struct{}
toneTestSaver *AudioFileSaver
toneTestSaverDetach gumble.Detacher
// Added for recording
RecordingMutex sync.Mutex
@@ -98,6 +99,17 @@ func (b *Barnard) cleanupConnectionAudio() {
b.FileStreamMutex.Unlock()
}
func (b *Barnard) cleanupToneTestAudio() {
if b.toneTestSaverDetach != nil {
b.toneTestSaverDetach.Detach()
b.toneTestSaverDetach = nil
}
if b.toneTestSaver != nil {
b.toneTestSaver.Stop()
b.toneTestSaver = nil
}
}
func (b *Barnard) StopTransmission() {
if b.Tx {
b.Notify("micdown", "me", "")
+2 -5
View File
@@ -68,7 +68,7 @@ func (b *Barnard) connect(reconnect bool) bool {
return false
}
b.toneTestSaver = saver
b.Client.Config.AttachAudio(saver)
b.toneTestSaverDetach = b.Client.Config.AttachAudio(saver)
b.Connected = true
if b.toneTestAutoTransmit() {
@@ -191,10 +191,7 @@ func (b *Barnard) OnDisconnect(e *gumble.DisconnectEvent) {
close(b.toneTestStop)
b.toneTestStop = nil
}
if b.toneTestSaver != nil {
b.toneTestSaver.Stop()
b.toneTestSaver = nil
}
b.cleanupToneTestAudio()
}
b.Notify("disconnect", "me", reason)
+24 -6
View File
@@ -65,10 +65,11 @@ func StartToneGenerator(client *gumble.Client, stop <-chan struct{}) {
// AudioFileSaver implements gumble.AudioListener and writes all incoming PCM
// audio to a single raw 16-bit little-endian stereo 48kHz file.
type AudioFileSaver struct {
file *os.File
stop chan struct{}
mu sync.Mutex
wg sync.WaitGroup
file *os.File
stop chan struct{}
mu sync.Mutex
stopped bool
wg sync.WaitGroup
}
// NewAudioFileSaver creates the output file and returns a configured saver.
@@ -95,15 +96,32 @@ func NewAudioFileSaver(path string) (*AudioFileSaver, error) {
// Stop closes the stop channel, waits for all stream goroutines to finish,
// and closes the output file.
func (s *AudioFileSaver) Stop() {
s.mu.Lock()
if s.stopped {
s.mu.Unlock()
return
}
s.stopped = true
close(s.stop)
s.mu.Unlock()
s.wg.Wait()
s.file.Close()
s.mu.Lock()
_ = s.file.Close()
s.mu.Unlock()
}
// OnAudioStream implements gumble.AudioListener.
func (s *AudioFileSaver) OnAudioStream(e *gumble.AudioStreamEvent) {
fmt.Fprintf(os.Stderr, "tonetest: incoming audio stream from %s\n", e.User.Name)
s.mu.Lock()
if s.stopped {
s.mu.Unlock()
return
}
s.wg.Add(1)
s.mu.Unlock()
fmt.Fprintf(os.Stderr, "tonetest: incoming audio stream from %s\n", e.User.Name)
go func() {
defer s.wg.Done()
+24
View File
@@ -24,3 +24,27 @@ func TestNewAudioFileSaverReportsUnavailableOutputPath(t *testing.T) {
t.Fatalf("got saver=%v err=%v", saver, err)
}
}
type testDetacher struct{ detached bool }
func (d *testDetacher) Detach() { d.detached = true }
// Regression: reconnecting tone-test mode kept prior savers attached to the
// shared audio listener list, causing callbacks to write to closed files.
func TestCleanupToneTestAudioDetachesSaver(t *testing.T) {
saver, err := NewAudioFileSaver(filepath.Join(t.TempDir(), "tone.pcm"))
if err != nil {
t.Fatal(err)
}
detacher := &testDetacher{}
b := &Barnard{toneTestSaver: saver, toneTestSaverDetach: detacher}
b.cleanupToneTestAudio()
if !detacher.detached {
t.Fatal("tone saver listener was not detached")
}
if b.toneTestSaver != nil || b.toneTestSaverDetach != nil {
t.Fatal("tone saver cleanup retained connection state")
}
b.cleanupToneTestAudio()
}