From 41759b95f6678df9270e9de2507e7805b01ebdfc Mon Sep 17 00:00:00 2001 From: "Brandon McGinty (chatgpt)" Date: Sun, 9 Aug 2026 14:30:31 -0400 Subject: [PATCH] Stop FIFO reader after terminal errors --- client_notification_test.go | 19 +++++++++++++++++++ fix.txt | 2 +- main.go | 27 ++++++++++++++++++--------- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/client_notification_test.go b/client_notification_test.go index 501525f..d664756 100644 --- a/client_notification_test.go +++ b/client_notification_test.go @@ -1,6 +1,8 @@ package main import ( + "io" + "strings" "testing" "git.stormux.org/storm/barnard/gumble/gumble" @@ -15,6 +17,23 @@ func TestEscRemovesTerminalControlSequences(t *testing.T) { } } +type testReadCloser struct{ io.Reader } + +func (testReadCloser) Close() error { return nil } + +// Regression: an EOF from the FIFO was ignored and caused an unbounded busy +// loop. The reader must deliver a final command then close its output. +func TestReadFIFOStopsOnEOF(t *testing.T) { + out := make(chan string) + go readFIFO(testReadCloser{strings.NewReader("command\n")}, out) + if got := <-out; got != "command" { + t.Fatalf("got %q", got) + } + if _, ok := <-out; ok { + t.Fatal("FIFO output remained open after EOF") + } +} + func TestUserChangeNotification(t *testing.T) { current := &gumble.Channel{ID: 1, Name: "Current"} other := &gumble.Channel{ID: 2, Name: "Other"} diff --git a/fix.txt b/fix.txt index 6208750..d6fadf1 100644 --- a/fix.txt +++ b/fix.txt @@ -225,7 +225,7 @@ Priority 3: configuration, UI, and binding hardening temporary file/directory before rename. Avoid broad unrelated formatting changes while fixing this. -29. FIFO reader spins after an error +[x] 29. FIFO reader spins after an error File: main.go setup_fifo ignores all ReadBytes errors and retries immediately. It also never closes the FIFO descriptor. Exit the reader on terminal errors, diff --git a/main.go b/main.go index 15c54e1..29b8dbc 100644 --- a/main.go +++ b/main.go @@ -88,18 +88,27 @@ func setup_fifo(fn string) (chan string, error) { if err != nil { return t, err } - go func(fh io.Reader, out chan string) { - reader := bufio.NewReader(fh) - for { - line, err := reader.ReadBytes('\n') - if err == nil { - out <- strings.TrimSpace(string(line)) - } - } - }(file, t) + go readFIFO(file, t) return t, nil } +// readFIFO forwards complete commands and terminates on EOF or any read +// failure. Retrying an unrecoverable FIFO error used to spin a CPU forever. +func readFIFO(fh io.ReadCloser, out chan<- string) { + defer fh.Close() + defer close(out) + reader := bufio.NewReader(fh) + for { + line, err := reader.ReadBytes('\n') + if len(line) != 0 { + out <- strings.TrimSpace(string(line)) + } + if err != nil { + return + } + } +} + func main() { // Command line flags server := flag.String("server", "localhost:64738", "the server to connect to")