From f6971023d4ca6c89af3997369cd0e9a165c133e8 Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Sun, 24 May 2026 13:28:55 +0200 Subject: [PATCH 1/2] Fix race in server-close test that flaked on Windows. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test triggered `server.close` from inside the 10th request handler (before the sleep and response). A flaky Windows CI failure showed the client retrying a request and hitting "actively refused" when the fresh TCP connect raced against the test tearing down the listening socket. Keep the in-flight close behavior — `server.close` still fires while the 10th handler is sleeping, so it still has to wait for the handler to drain — but wait for the client to confirm it received all 10 responses before closing the listening socket. --- tests/server-close-test.toit | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/server-close-test.toit b/tests/server-close-test.toit index acf713c..ae6c982 100644 --- a/tests/server-close-test.toit +++ b/tests/server-close-test.toit @@ -18,14 +18,15 @@ test --request-more/bool=false: network := net.open tcp-socket := network.tcp-listen 0 port := tcp-socket.local-address.port - served-ten := monitor.Latch - closed := monitor.Latch + in-tenth-handler := monitor.Latch + ten-done := monitor.Latch + client-done := monitor.Latch task:: requests := 0 server.listen tcp-socket:: | request/http.Request writer/http.ResponseWriter | requests++ - if requests == 10: served-ten.set true - sleep --ms=100 // Make it more likely to have parallel requests. + if requests == CLOSE-AFTER-REQUESTS: in-tenth-handler.set true + sleep --ms=100 // Stretch the handler so server.close has to wait. if request.path == "/": writer.headers.set "Content-Type" "text/html" writer.out.write "hello world" @@ -36,17 +37,28 @@ test --request-more/bool=false: // 10 requests should be no problem. CLOSE-AFTER-REQUESTS.repeat: client.get --uri="http://localhost:$port/" + ten-done.set true if request-more: // There might still be some requests that made it through, but // now we should soon have issues. e := catch: - with-timeout --ms=1000: + with-timeout --ms=2000: while true: client.get --uri="http://localhost:$port/" sleep --ms=10 expect-not-null e + client-done.set true - served-ten.get + // Trigger close while the 10th handler is still in flight, so we verify + // that server.close waits for in-flight handlers to complete. + in-tenth-handler.get server.close + // server.close has returned, so all handlers are done. But the client + // may still be reading the last response. Wait for the client to + // confirm receipt before tearing down the listening socket, otherwise + // a mid-stream close on Windows can show up as RST and trigger a retry + // that hits the closed listener with "actively refused". + ten-done.get tcp-socket.close + client-done.get From a4727b90f13c203008df89c427b413c98626a634 Mon Sep 17 00:00:00 2001 From: Florian Loitsch Date: Sun, 24 May 2026 13:31:40 +0200 Subject: [PATCH 2/2] Rename latches to avoid baking the request count into the variable name. 'in-tenth-handler' / 'ten-done' would go stale if CLOSE-AFTER-REQUESTS ever changes. --- tests/server-close-test.toit | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/server-close-test.toit b/tests/server-close-test.toit index ae6c982..025398e 100644 --- a/tests/server-close-test.toit +++ b/tests/server-close-test.toit @@ -18,14 +18,14 @@ test --request-more/bool=false: network := net.open tcp-socket := network.tcp-listen 0 port := tcp-socket.local-address.port - in-tenth-handler := monitor.Latch - ten-done := monitor.Latch + close-trigger := monitor.Latch + requests-done := monitor.Latch client-done := monitor.Latch task:: requests := 0 server.listen tcp-socket:: | request/http.Request writer/http.ResponseWriter | requests++ - if requests == CLOSE-AFTER-REQUESTS: in-tenth-handler.set true + if requests == CLOSE-AFTER-REQUESTS: close-trigger.set true sleep --ms=100 // Stretch the handler so server.close has to wait. if request.path == "/": writer.headers.set "Content-Type" "text/html" @@ -37,7 +37,7 @@ test --request-more/bool=false: // 10 requests should be no problem. CLOSE-AFTER-REQUESTS.repeat: client.get --uri="http://localhost:$port/" - ten-done.set true + requests-done.set true if request-more: // There might still be some requests that made it through, but @@ -50,15 +50,15 @@ test --request-more/bool=false: expect-not-null e client-done.set true - // Trigger close while the 10th handler is still in flight, so we verify + // Trigger close while the last handler is still in flight, so we verify // that server.close waits for in-flight handlers to complete. - in-tenth-handler.get + close-trigger.get server.close // server.close has returned, so all handlers are done. But the client // may still be reading the last response. Wait for the client to // confirm receipt before tearing down the listening socket, otherwise // a mid-stream close on Windows can show up as RST and trigger a retry // that hits the closed listener with "actively refused". - ten-done.get + requests-done.get tcp-socket.close client-done.get