summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/asyncdispatch.nim20
-rw-r--r--tests/async/tasyncconnect.nim33
-rw-r--r--tests/async/tasynceverror.nim65
3 files changed, 112 insertions, 6 deletions
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim
index 2ce31b4e8..7523b29d5 100644
--- a/lib/pure/asyncdispatch.nim
+++ b/lib/pure/asyncdispatch.nim
@@ -972,9 +972,9 @@ else:
       let data = PData(info.key.data)
       assert data.fd == info.key.fd.AsyncFD
       #echo("In poll ", data.fd.cint)
-      if EvError in info.events:
-        closeSocket(data.fd)
-        continue
+      # There may be EvError here, but we handle them in callbacks,
+      # so that exceptions can be raised from `send(...)` and
+      # `recv(...)` routines.
 
       if EvRead in info.events:
         # Callback may add items to ``data.readCBs`` which causes issues if
@@ -1013,9 +1013,17 @@ else:
     var retFuture = newFuture[void]("connect")
 
     proc cb(fd: AsyncFD): bool =
-      # We have connected.
-      retFuture.complete()
-      return true
+      var ret = SocketHandle(fd).getSockOptInt(cint(SOL_SOCKET), cint(SO_ERROR))
+      if ret == 0:
+          # We have connected.
+          retFuture.complete()
+          return true
+      elif ret == EINTR:
+          # interrupted, keep waiting
+          return false
+      else:
+          retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret))))
+          return true
 
     assert getSockDomain(socket.SocketHandle) == domain
     var aiList = getAddrInfo(address, port, domain)
diff --git a/tests/async/tasyncconnect.nim b/tests/async/tasyncconnect.nim
new file mode 100644
index 000000000..bc63b8e82
--- /dev/null
+++ b/tests/async/tasyncconnect.nim
@@ -0,0 +1,33 @@
+discard """
+  file: "tasyncconnect.nim"
+  exitcode: 1
+  outputsub: "Error: unhandled exception: Connection refused [Exception]"
+"""
+
+import
+    asyncdispatch,
+    posix
+
+
+const
+    testHost = "127.0.0.1"
+    testPort = Port(17357)
+
+
+when defined(windows) or defined(nimdoc):
+    discard
+else:
+    proc testAsyncConnect() {.async.} =
+        var s = newAsyncRawSocket()
+
+        await s.connect(testHost, testPort)
+
+        var peerAddr: SockAddr
+        var addrSize = Socklen(sizeof(peerAddr))
+        var ret = SocketHandle(s).getpeername(addr(peerAddr), addr(addrSize))
+
+        if ret < 0:
+            echo("`connect(...)` failed but no exception was raised.")
+            quit(2)
+
+    waitFor(testAsyncConnect())
diff --git a/tests/async/tasynceverror.nim b/tests/async/tasynceverror.nim
new file mode 100644
index 000000000..3b81680cb
--- /dev/null
+++ b/tests/async/tasynceverror.nim
@@ -0,0 +1,65 @@
+discard """
+  file: "tasynceverror.nim"
+  exitcode: 1
+  outputsub: "Error: unhandled exception: Connection reset by peer [Exception]"
+"""
+
+import
+    asyncdispatch,
+    asyncnet,
+    rawsockets,
+    os
+
+
+const
+    testHost = "127.0.0.1"
+    testPort = Port(17357)
+
+
+when defined(windows) or defined(nimdoc):
+    discard
+else:
+    proc createListenSocket(host: string, port: Port): TAsyncFD =
+        result = newAsyncRawSocket()
+
+        SocketHandle(result).setSockOptInt(SOL_SOCKET, SO_REUSEADDR, 1)
+
+        var aiList = getAddrInfo(host, port, AF_INET)
+        if SocketHandle(result).bindAddr(aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
+          dealloc(aiList)
+          raiseOSError(osLastError())
+        dealloc(aiList)
+
+        if SocketHandle(result).listen(1) < 0'i32:
+            raiseOSError(osLastError())
+
+
+    proc testAsyncSend() {.async.} =
+        var
+            ls = createListenSocket(testHost, testPort)
+            s = newAsyncSocket()
+
+        await s.connect(testHost, testPort)
+        
+        var ps = await ls.accept()
+        SocketHandle(ls).close()
+
+        await ps.send("test 1", flags={})
+        s.close()
+        # This send should raise EPIPE
+        await ps.send("test 2", flags={})
+        SocketHandle(ps).close()
+
+
+    # The bug was, when the poll function handled EvError for us,
+    # our callbacks may never get executed, thus making the event
+    # loop block indefinitely. This is a timer to keep everything
+    # rolling. 400 ms is an arbitrary value, should be enough though.
+    proc timer() {.async.} =
+        await sleepAsync(400)
+        echo("Timer expired.")
+        quit(2)
+
+
+    asyncCheck(testAsyncSend())
+    waitFor(timer())