From cc2ce39170fd8c5e7dbd5bd011a75282387824d2 Mon Sep 17 00:00:00 2001 From: Wasabules <39313803+Wasabules@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:50:46 +0200 Subject: [PATCH] fix(server): avoid nil-channel deadlock and data race in Stop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The worker goroutines ran `for fn := range s.workCh`, reading the shared field on start-up. Stop() sets s.workCh to nil (before closing the locally captured channel), so a worker that evaluated the range after that write would range over a nil channel and block forever — wg.Wait() in Stop then never returned, hanging shutdown. The same unsynchronised read also raced with Stop's write, which `go test -race` flagged. Capture the channel under the lock in Start and range over that local in each worker, so the workers never touch s.workCh. submitWork already reads s.workCh under RLock with a nil guard, so message posting is unaffected. Pre-existing issue surfaced by the Start+Stop test added in #4. Verified with `go test -race ./...` (all packages pass). --- internal/syslog/server.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/syslog/server.go b/internal/syslog/server.go index 99f629f..18b3fc8 100644 --- a/internal/syslog/server.go +++ b/internal/syslog/server.go @@ -129,6 +129,13 @@ func (s *SyslogServer) Start(config models.ServerConfig) error { s.cancel = cancel s.running = true s.workCh = make(chan func(), maxWorkers*4) + // Capture the worker channel under the lock. The workers must range over + // this local, never s.workCh directly: Stop() sets s.workCh to nil, and a + // worker that evaluated `range s.workCh` after that write would range over + // a nil channel and block forever, deadlocking shutdown (wg.Wait never + // returns). Reading the shared field from the workers would also race with + // Stop's write. + workCh := s.workCh s.connSem = make(chan struct{}, maxTCPConnections) s.conns = make(map[net.Conn]struct{}) @@ -147,7 +154,7 @@ func (s *SyslogServer) Start(config models.ServerConfig) error { s.wg.Add(1) go func() { defer s.wg.Done() - for fn := range s.workCh { + for fn := range workCh { fn() } }()