diff options
author | Dominik Picheta <dominikpicheta@googlemail.com> | 2019-06-12 16:07:05 +0100 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2019-06-12 17:07:05 +0200 |
commit | 73c44c6e9b55f7fb2f5b902c78086f46c1b118dd (patch) | |
tree | 10e8705024436760a3d8a853982e748375edd865 /tests | |
parent | da035e9c8385be59449d13d1355aba4f9f97a6b4 (diff) | |
download | Nim-73c44c6e9b55f7fb2f5b902c78086f46c1b118dd.tar.gz |
[bugfix] Fixes async IO operations stalling even after socket is closed. (#11232)
Diffstat (limited to 'tests')
-rw-r--r-- | tests/async/tasyncclosestall.nim | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/tests/async/tasyncclosestall.nim b/tests/async/tasyncclosestall.nim new file mode 100644 index 000000000..e10e23074 --- /dev/null +++ b/tests/async/tasyncclosestall.nim @@ -0,0 +1,99 @@ +discard """ + outputsub: "send has errored. As expected. All good!" + exitcode: 0 +""" +import asyncdispatch, asyncnet + +when defined(windows): + from winlean import ERROR_NETNAME_DELETED +else: + from posix import EBADF + +# This reproduces a case where a socket remains stuck waiting for writes +# even when the socket is closed. +const + port = Port(50726) + timeout = 5000 + +var sent = 0 + +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): + # 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. + echo("Socket has stalled, closing it") + c.close() + + let timeoutFut = withTimeout(sendFut, timeout) + yield timeoutFut + if timeoutFut.failed: + let errCode = ((ref OSError)(timeoutFut.error)).errorCode + # The behaviour differs across platforms. On Windows ERROR_NETNAME_DELETED + # 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 + # 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) + 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) + + # The write shouldn't succeed and also shouldn't be stalled. + if timeoutFut.read(): + raise newException(ValueError, "Test failed. Send was expected to fail.") + else: + raise newException(ValueError, "Test failed. Send future is still stalled.") + sent.inc(1) + +proc startClient() {.async.} = + let client = newAsyncSocket() + await client.connect("localhost", port) + echo("Connected") + + let firstLine = await client.recvLine() + echo("Received first line as a client: ", firstLine) + echo("Now not reading anymore") + while true: await sleepAsync(1000) + +proc debug() {.async.} = + while true: + echo("Sent ", sent) + await sleepAsync(1000) + +proc server() {.async.} = + var s = newAsyncSocket() + s.setSockOpt(OptReuseAddr, true) + s.bindAddr(port) + s.listen() + + # We're now ready to accept connections, so start the client + asyncCheck startClient() + asyncCheck debug() + + while true: + let client = await accept(s) + asyncCheck keepSendingTo(client) + +when isMainModule: + waitFor server() + |