123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- discard """
- disabled: "windows"
- 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
- timeout = 8000
- var port = Port(0)
- 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()
- let (addr2, port2) = s.getLocalAddr
- port = port2
- # 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()
|