From bdae156e2242743cdf7094f42f1ae9c2885cf837 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Wed, 12 Aug 2026 06:08:49 +0000 Subject: [PATCH 1/2] fix(coderd/x/agenthooks/dispatch): deflake TestDispatcherTimeoutNoRetry --- .../dispatch/dispatcher_internal_test.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/coderd/x/agenthooks/dispatch/dispatcher_internal_test.go b/coderd/x/agenthooks/dispatch/dispatcher_internal_test.go index 1ba11e41a68..d9dd44cf304 100644 --- a/coderd/x/agenthooks/dispatch/dispatcher_internal_test.go +++ b/coderd/x/agenthooks/dispatch/dispatcher_internal_test.go @@ -197,19 +197,18 @@ func TestDispatcherTimeoutNoRetry(t *testing.T) { event := newTestEvent(t, agenthooks.EventStop, agenthooks.StopData{}) var requests atomic.Int32 - release := make(chan struct{}) - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + // A real server cannot guarantee the handler runs before the short + // dispatch deadline on a loaded machine, so the transport records the + // attempt synchronously and blocks until the deadline expires. + client := &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { requests.Add(1) - w.WriteHeader(http.StatusOK) - assert.NoError(t, http.NewResponseController(w).Flush()) - <-release - })) - t.Cleanup(server.Close) + <-req.Context().Done() + return nil, req.Context().Err() + })} - _, _, err := newTestDispatcher(t, server.Client(), server.URL, 50*time.Millisecond).Dispatch( + _, _, err := newTestDispatcher(t, client, "https://hooks.example.com/coder", 50*time.Millisecond).Dispatch( testutil.Context(t, testutil.WaitLong), event, ) - close(release) assertDispatchErrorClass(t, err, ResultTimeout) require.Equal(t, int32(1), requests.Load()) } From ecf0af5c7c60cecda0662b23a21b8c0f2af4833d Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Wed, 12 Aug 2026 06:20:01 +0000 Subject: [PATCH 2/2] fix(coderd/x/agenthooks/dispatch): keep read-path timeout coverage in deflaked test --- .../dispatch/dispatcher_internal_test.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/coderd/x/agenthooks/dispatch/dispatcher_internal_test.go b/coderd/x/agenthooks/dispatch/dispatcher_internal_test.go index d9dd44cf304..03d291e2a74 100644 --- a/coderd/x/agenthooks/dispatch/dispatcher_internal_test.go +++ b/coderd/x/agenthooks/dispatch/dispatcher_internal_test.go @@ -199,11 +199,15 @@ func TestDispatcherTimeoutNoRetry(t *testing.T) { var requests atomic.Int32 // A real server cannot guarantee the handler runs before the short // dispatch deadline on a loaded machine, so the transport records the - // attempt synchronously and blocks until the deadline expires. + // attempt synchronously. The successful response with a body that blocks + // until the deadline expires keeps the read-path timeout branch covered. client := &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { requests.Add(1) - <-req.Context().Done() - return nil, req.Context().Err() + return &http.Response{ + StatusCode: http.StatusOK, + Body: contextBlockedBody{ctx: req.Context()}, + Request: req, + }, nil })} _, _, err := newTestDispatcher(t, client, "https://hooks.example.com/coder", 50*time.Millisecond).Dispatch( @@ -708,6 +712,15 @@ func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) } +type contextBlockedBody struct{ ctx context.Context } + +func (b contextBlockedBody) Read([]byte) (int, error) { + <-b.ctx.Done() + return 0, b.ctx.Err() +} + +func (contextBlockedBody) Close() error { return nil } + func TestDispatcherCapacityClassRequired(t *testing.T) { t.Parallel()