From 90b7517656d4fb8aba15139711fdc5f5dd7e4a62 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Wed, 17 Dec 2025 20:25:51 +0100 Subject: [PATCH] =?UTF-8?q?refs=20https://github.com/nim-lang/Nim/pull/253?= =?UTF-8?q?53=20make=20tasyncclosestall=E2=80=A6=20(#25366)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ….nim less flaky (cherry picked from commit 334ac3f58860584f1e24b9164054007348b7f0eb) --- tests/async/tasyncclosestall.nim | 49 +++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/tests/async/tasyncclosestall.nim b/tests/async/tasyncclosestall.nim index d1c7a5fbae..566523a726 100644 --- a/tests/async/tasyncclosestall.nim +++ b/tests/async/tasyncclosestall.nim @@ -8,7 +8,7 @@ import asyncdispatch, asyncnet when defined(windows): from winlean import ERROR_NETNAME_DELETED else: - from posix import EBADF + from posix import EBADF, ECONNRESET, EPIPE # This reproduces a case where a socket remains stuck waiting for writes # even when the socket is closed. @@ -18,12 +18,37 @@ var port = Port(0) var sent = 0 +proc isExpectedDisconnectionError(errCode: int32): bool = + ## Check if an error code indicates an expected disconnection. + ## On POSIX systems, the error code depends on timing: + ## - EBADF: Socket was closed locally before kernel detected remote state + ## - ECONNRESET: Remote peer sent RST packet (detected first) + ## - EPIPE: Socket is no longer connected (broken pipe) + ## All three are valid disconnection errors for this test scenario. + when defined(windows): + errCode == ERROR_NETNAME_DELETED + else: + errCode == EBADF or errCode == ECONNRESET or errCode == EPIPE + proc keepSendingTo(c: AsyncSocket) {.async.} = while true: # This write will eventually get stuck because the client is not reading # its messages. let sendFut = c.send("Foobar" & $sent & "\n", flags = {}) - if not await withTimeout(sendFut, timeout): + var sendTimedOut = false + try: + # On some platforms (notably macOS ARM64), the kernel may return + # ECONNRESET immediately when detecting a non-responsive connection, + # rather than letting the send stall. We catch this case here. + sendTimedOut = not await withTimeout(sendFut, timeout) + except OSError as e: + if isExpectedDisconnectionError(e.errorCode): + echo("send has errored. As expected. All good!") + quit(QuitSuccess) + else: + raise + + if sendTimedOut: # The write is stuck. Let's simulate a scenario where the socket # does not respond to PING messages, and we close it. The above future # should complete after the socket is closed, not continue stalling. @@ -38,26 +63,16 @@ proc keepSendingTo(c: AsyncSocket) {.async.} = # is raised which we classif as a "diconnection error", hence we overwrite # the flags above in the `send` call so that this error is raised. # - # On Linux the EBADF error code is raised, this is because the socket - # is closed. - # # This means that by default the behaviours will differ between Windows - # and Linux. I think this is fine though, it makes sense mainly because + # and POSIX. I think this is fine though, it makes sense mainly because # Windows doesn't use a IO readiness model. We can fix this later if # necessary to reclassify ERROR_NETNAME_DELETED as not a "disconnection # error" (TODO) - when defined(windows): - if errCode == ERROR_NETNAME_DELETED: - echo("send has errored. As expected. All good!") - quit(QuitSuccess) - else: - raise newException(ValueError, "Test failed. Send failed with code " & $errCode) + if isExpectedDisconnectionError(errCode): + echo("send has errored. As expected. All good!") + quit(QuitSuccess) else: - if errCode == EBADF: - echo("send has errored. As expected. All good!") - quit(QuitSuccess) - else: - raise newException(ValueError, "Test failed. Send failed with code " & $errCode) + raise newException(ValueError, "Test failed. Send failed with code " & $errCode) # The write shouldn't succeed and also shouldn't be stalled. if timeoutFut.read():