tasyncclosestall.nim 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. discard """
  2. disabled: "windows"
  3. outputsub: "send has errored. As expected. All good!"
  4. exitcode: 0
  5. """
  6. import asyncdispatch, asyncnet
  7. when defined(windows):
  8. from winlean import ERROR_NETNAME_DELETED
  9. else:
  10. from posix import EBADF
  11. # This reproduces a case where a socket remains stuck waiting for writes
  12. # even when the socket is closed.
  13. const
  14. timeout = 8000
  15. var port = Port(0)
  16. var sent = 0
  17. proc keepSendingTo(c: AsyncSocket) {.async.} =
  18. while true:
  19. # This write will eventually get stuck because the client is not reading
  20. # its messages.
  21. let sendFut = c.send("Foobar" & $sent & "\n", flags = {})
  22. if not await withTimeout(sendFut, timeout):
  23. # The write is stuck. Let's simulate a scenario where the socket
  24. # does not respond to PING messages, and we close it. The above future
  25. # should complete after the socket is closed, not continue stalling.
  26. echo("Socket has stalled, closing it")
  27. c.close()
  28. let timeoutFut = withTimeout(sendFut, timeout)
  29. yield timeoutFut
  30. if timeoutFut.failed:
  31. let errCode = ((ref OSError)(timeoutFut.error)).errorCode
  32. # The behaviour differs across platforms. On Windows ERROR_NETNAME_DELETED
  33. # is raised which we classif as a "diconnection error", hence we overwrite
  34. # the flags above in the `send` call so that this error is raised.
  35. #
  36. # On Linux the EBADF error code is raised, this is because the socket
  37. # is closed.
  38. #
  39. # This means that by default the behaviours will differ between Windows
  40. # and Linux. I think this is fine though, it makes sense mainly because
  41. # Windows doesn't use a IO readiness model. We can fix this later if
  42. # necessary to reclassify ERROR_NETNAME_DELETED as not a "disconnection
  43. # error" (TODO)
  44. when defined(windows):
  45. if errCode == ERROR_NETNAME_DELETED:
  46. echo("send has errored. As expected. All good!")
  47. quit(QuitSuccess)
  48. else:
  49. raise newException(ValueError, "Test failed. Send failed with code " & $errCode)
  50. else:
  51. if errCode == EBADF:
  52. echo("send has errored. As expected. All good!")
  53. quit(QuitSuccess)
  54. else:
  55. raise newException(ValueError, "Test failed. Send failed with code " & $errCode)
  56. # The write shouldn't succeed and also shouldn't be stalled.
  57. if timeoutFut.read():
  58. raise newException(ValueError, "Test failed. Send was expected to fail.")
  59. else:
  60. raise newException(ValueError, "Test failed. Send future is still stalled.")
  61. sent.inc(1)
  62. proc startClient() {.async.} =
  63. let client = newAsyncSocket()
  64. await client.connect("localhost", port)
  65. echo("Connected")
  66. let firstLine = await client.recvLine()
  67. echo("Received first line as a client: ", firstLine)
  68. echo("Now not reading anymore")
  69. while true: await sleepAsync(1000)
  70. proc debug() {.async.} =
  71. while true:
  72. echo("Sent ", sent)
  73. await sleepAsync(1000)
  74. proc server() {.async.} =
  75. var s = newAsyncSocket()
  76. s.setSockOpt(OptReuseAddr, true)
  77. s.bindAddr(port)
  78. s.listen()
  79. let (addr2, port2) = s.getLocalAddr
  80. port = port2
  81. # We're now ready to accept connections, so start the client
  82. asyncCheck startClient()
  83. asyncCheck debug()
  84. while true:
  85. let client = await accept(s)
  86. asyncCheck keepSendingTo(client)
  87. when isMainModule:
  88. waitFor server()