From a16fabee898e13853196366b77bcbb991fe0b439 Mon Sep 17 00:00:00 2001 From: acud <12988138+acud@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:04:59 -0600 Subject: [PATCH] fix(api): subscribe to gsoc before websocket upgrade Upgrade flushes the 101 response, so the client can trigger a GSOC update before the handler subscribed, silently dropping it. Register the subscription before upgrading and release it if the upgrade fails. Fixes flaky TestGsocWebsocketSingleHandler and TestGsocWebsocketSocFields timeouts in the master coverage job. Co-Authored-By: Claude Opus 5.5 --- pkg/api/gsoc.go | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/pkg/api/gsoc.go b/pkg/api/gsoc.go index 55361938e22..fccf68121e1 100644 --- a/pkg/api/gsoc.go +++ b/pkg/api/gsoc.go @@ -183,20 +183,12 @@ func (s *Service) gsocWsHandler(w http.ResponseWriter, r *http.Request) { CheckOrigin: s.checkOrigin, } - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - logger.Debug("upgrade failed", "error", err) - logger.Error(nil, "upgrade failed") - jsonhttp.InternalServerError(w, "upgrade failed") - return - } - - // Subscribe synchronously, before handing the connection off to its own - // goroutine: Upgrade already flushed the 101 response, so the client can - // start sending GSOC-triggering activity immediately. Subscribing here - // instead of inside the spawned goroutine closes the window in which an - // update could arrive before the handler is registered and be silently - // missed. + // Subscribe before upgrading the connection: Upgrade flushes the 101 + // response, after which the client can immediately start sending + // GSOC-triggering activity. Subscribing only after Upgrade leaves a window + // in which such an update arrives before the handler is registered and is + // silently missed. Updates that arrive before the writer goroutine starts + // are buffered in the queue and picked up through wake. // // Caching the wrapped chunks is a subscription of its own, shared by every // subscriber of this address that asked for it. It is registered first, so @@ -227,6 +219,17 @@ func (s *Service) gsocWsHandler(w http.ResponseWriter, r *http.Request) { } }) + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + cleanup() + releaseCache() + queue.release() + logger.Debug("upgrade failed", "error", err) + logger.Error(nil, "upgrade failed") + jsonhttp.InternalServerError(w, "upgrade failed") + return + } + s.wsWg.Add(1) go s.gsocListeningWs(conn, func() { cleanup()