summary refs log tree commit diff stats
path: root/tests
diff options
context:
space:
mode:
authorDominik Picheta <dominikpicheta@googlemail.com>2019-06-12 16:07:05 +0100
committerAndreas Rumpf <rumpf_a@web.de>2019-06-12 17:07:05 +0200
commit73c44c6e9b55f7fb2f5b902c78086f46c1b118dd (patch)
tree10e8705024436760a3d8a853982e748375edd865 /tests
parentda035e9c8385be59449d13d1355aba4f9f97a6b4 (diff)
downloadNim-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.nim99
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()
+