From 3647d3975cc300a86ba8a2fc28efe3235c0ffd9b Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Sun, 8 Feb 2026 10:49:31 -0500 Subject: [PATCH] Fix panic in handleSubscribeHTTP when client disconnects during publish Replace wlock.TryLock() with a proper Lock() + closed flag to prevent writing to a response writer that has been cleaned up after the handler returns. The previous TryLock approach could not guarantee the response writer was still valid when a concurrent Publish goroutine called Flush. --- server/server.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/server/server.go b/server/server.go index 4e03ef5a..9069f3ed 100644 --- a/server/server.go +++ b/server/server.go @@ -1458,12 +1458,15 @@ func (s *Server) handleSubscribeHTTP(w http.ResponseWriter, r *http.Request, v * return err } var wlock sync.Mutex + var closed bool defer func() { - // Hack: This is the fix for a horrible data race that I have not been able to figure out in quite some time. - // It appears to be happening when the Go HTTP code reads from the socket when closing the request (i.e. AFTER - // this function returns), and causes a data race with the ResponseWriter. Locking wlock here silences the - // data race detector. See https://github.com/binwiederhier/ntfy/issues/338#issuecomment-1163425889. - wlock.TryLock() + // This blocks until any in-flight sub() call finishes writing/flushing the response writer, + // then marks the connection as closed so future sub() calls are no-ops. This prevents a panic + // from writing to a response writer that has been cleaned up after the handler returns. + // See https://github.com/binwiederhier/ntfy/issues/338#issuecomment-1163425889. + wlock.Lock() + closed = true + wlock.Unlock() }() sub := func(v *visitor, msg *message) error { if !filters.Pass(msg) { @@ -1475,6 +1478,9 @@ func (s *Server) handleSubscribeHTTP(w http.ResponseWriter, r *http.Request, v * } wlock.Lock() defer wlock.Unlock() + if closed { + return nil + } if _, err := w.Write([]byte(m)); err != nil { return err }