diff options
-rw-r--r-- | lib/pure/asyncdispatch.nim | 63 | ||||
-rw-r--r-- | lib/pure/asyncmacro.nim | 4 | ||||
-rw-r--r-- | lib/upcoming/asyncdispatch.nim | 33 | ||||
-rw-r--r-- | tests/async/tawaitsemantics.nim | 72 |
4 files changed, 130 insertions, 42 deletions
diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 802c40495..06c21e08d 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -105,6 +105,36 @@ export Port, SocketFlag ## be used to await a procedure returning a ``Future[void]``: ## ``await socket.send("foobar")``. ## +## If an awaited future completes with an error, then ``await`` will re-raise +## this error. To avoid this, you can use the ``yield`` keyword instead of +## ``await``. The following section shows different ways that you can handle +## exceptions in async procs. +## +## Handling Exceptions +## ~~~~~~~~~~~~~~~~~~~ +## +## The most reliable way to handle exceptions is to use ``yield`` on a future +## then check the future's ``failed`` property. For example: +## +## .. code-block:: Nim +## var future = sock.recv(100) +## yield future +## if future.failed: +## # Handle exception +## +## The ``async`` procedures also offer limited support for the try statement. +## +## .. code-block:: Nim +## try: +## let data = await sock.recv(100) +## echo("Received ", data) +## except: +## # Handle exception +## +## Unfortunately the semantics of the try statement may not always be correct, +## and occassionally the compilation may fail altogether. +## As such it is better to use the former style when possible. +## ## Discarding futures ## ------------------ ## @@ -339,17 +369,22 @@ proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] = var retFuture = newFuture[void]("asyncdispatch.`and`") fut1.callback = proc () = - if fut2.finished: retFuture.complete() + if not retFuture.finished: + if fut1.failed: retFuture.fail(fut1.error) + elif fut2.finished: retFuture.complete() fut2.callback = proc () = - if fut1.finished: retFuture.complete() + if not retFuture.finished: + if fut2.failed: retFuture.fail(fut2.error) + elif fut1.finished: retFuture.complete() return retFuture proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] = ## Returns a future which will complete once either ``fut1`` or ``fut2`` ## complete. var retFuture = newFuture[void]("asyncdispatch.`or`") - proc cb() = + proc cb(fut: Future[T]) = + if fut.failed: retFuture.fail(fut.error) if not retFuture.finished: retFuture.complete() fut1.callback = cb fut2.callback = cb @@ -374,10 +409,13 @@ proc all*[T](futs: varargs[Future[T]]): auto = for fut in futs: fut.callback = proc(f: Future[T]) = - inc(completedFutures) + if f.failed: + retFuture.fail(f.error) + elif not retFuture.finished: + inc(completedFutures) - if completedFutures == totalFutures: - retFuture.complete() + if completedFutures == totalFutures: + retFuture.complete() return retFuture @@ -390,11 +428,14 @@ proc all*[T](futs: varargs[Future[T]]): auto = for i, fut in futs: proc setCallback(i: int) = fut.callback = proc(f: Future[T]) = - retValues[i] = f.read() - inc(completedFutures) - - if completedFutures == len(retValues): - retFuture.complete(retValues) + if f.failed: + retFuture.fail(f.error) + elif not retFuture.finished: + retValues[i] = f.read() + inc(completedFutures) + + if completedFutures == len(retValues): + retFuture.complete(retValues) setCallback(i) diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index 28e3e2a16..6b565cd3b 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -144,11 +144,9 @@ proc processBody(node, retFutureSym: NimNode, of nnkCommand, nnkCall: if node[0].kind == nnkIdent and node[0].ident == !"await": case node[1].kind - of nnkIdent, nnkInfix, nnkDotExpr: + of nnkIdent, nnkInfix, nnkDotExpr, nnkCall, nnkCommand: # await x # await x or y - result = newNimNode(nnkYieldStmt, node).add(node[1]) # -> yield x - of nnkCall, nnkCommand: # await foo(p, x) # await foo p, x var futureValue: NimNode diff --git a/lib/upcoming/asyncdispatch.nim b/lib/upcoming/asyncdispatch.nim index 689d0272c..460dfbd1a 100644 --- a/lib/upcoming/asyncdispatch.nim +++ b/lib/upcoming/asyncdispatch.nim @@ -339,17 +339,22 @@ proc `and`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] = var retFuture = newFuture[void]("asyncdispatch.`and`") fut1.callback = proc () = - if fut2.finished: retFuture.complete() + if not retFuture.finished: + if fut1.failed: retFuture.fail(fut1.error) + elif fut2.finished: retFuture.complete() fut2.callback = proc () = - if fut1.finished: retFuture.complete() + if not retFuture.finished: + if fut2.failed: retFuture.fail(fut2.error) + elif fut1.finished: retFuture.complete() return retFuture proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] = ## Returns a future which will complete once either ``fut1`` or ``fut2`` ## complete. var retFuture = newFuture[void]("asyncdispatch.`or`") - proc cb() = + proc cb(fut: Future[T]) = + if fut.failed: retFuture.fail(fut.error) if not retFuture.finished: retFuture.complete() fut1.callback = cb fut2.callback = cb @@ -374,10 +379,13 @@ proc all*[T](futs: varargs[Future[T]]): auto = for fut in futs: fut.callback = proc(f: Future[T]) = - inc(completedFutures) + if f.failed: + retFuture.fail(f.error) + elif not retFuture.finished: + inc(completedFutures) - if completedFutures == totalFutures: - retFuture.complete() + if completedFutures == totalFutures: + retFuture.complete() return retFuture @@ -390,11 +398,14 @@ proc all*[T](futs: varargs[Future[T]]): auto = for i, fut in futs: proc setCallback(i: int) = fut.callback = proc(f: Future[T]) = - retValues[i] = f.read() - inc(completedFutures) - - if completedFutures == len(retValues): - retFuture.complete(retValues) + if f.failed: + retFuture.fail(f.error) + elif not retFuture.finished: + retValues[i] = f.read() + inc(completedFutures) + + if completedFutures == len(retValues): + retFuture.complete(retValues) setCallback(i) diff --git a/tests/async/tawaitsemantics.nim b/tests/async/tawaitsemantics.nim index 3e0c3903e..98fb5dfd5 100644 --- a/tests/async/tawaitsemantics.nim +++ b/tests/async/tawaitsemantics.nim @@ -2,17 +2,21 @@ discard """ file: "tawaitsemantics.nim" exitcode: 0 output: ''' -Error caught -Test infix -Test call +Error can be caught using yield +Infix `or` raises +Infix `and` raises +All() raises +Awaiting a async procedure call raises +Awaiting a future raises ''' """ import asyncdispatch # This tests the behaviour of 'await' under different circumstances. -# For example, when awaiting Future variable and this future has failed the -# exception shouldn't be raised as described here +# Specifically, when an awaited future raises an exception then `await` should +# also raise that exception by `read`'ing that future. In cases where you don't +# want this behaviour, you can use `yield`. # https://github.com/nim-lang/Nim/issues/4170 proc thrower(): Future[void] = @@ -23,37 +27,71 @@ proc dummy: Future[void] = result = newFuture[void]() result.complete() -proc testInfix() {.async.} = - # Test the infix operator semantics. +proc testInfixOr() {.async.} = + # Test the infix `or` operator semantics. var fut = thrower() var fut2 = dummy() - await fut or fut2 # Shouldn't raise. - # TODO: what about: await thrower() or fut2? + await fut or fut2 # Should raise! + +proc testInfixAnd() {.async.} = + # Test the infix `and` operator semantics. + var fut = thrower() + var fut2 = dummy() + await fut and fut2 # Should raise! + +proc testAll() {.async.} = + # Test the `all` semantics. + var fut = thrower() + var fut2 = dummy() + await all(fut, fut2) # Should raise! proc testCall() {.async.} = await thrower() +proc testAwaitFut() {.async.} = + var fut = thrower() + await fut # This should raise. + proc tester() {.async.} = # Test that we can handle exceptions without 'try' var fut = thrower() doAssert fut.finished doAssert fut.failed doAssert fut.error.msg == "Test" - await fut # We are awaiting a 'Future', so no `read` occurs. + yield fut # We are yielding a 'Future', so no `read` occurs. doAssert fut.finished doAssert fut.failed doAssert fut.error.msg == "Test" - echo("Error caught") + echo("Error can be caught using yield") + + fut = testInfixOr() + yield fut + doAssert fut.finished + doAssert fut.failed + echo("Infix `or` raises") - fut = testInfix() - await fut + fut = testInfixAnd() + yield fut doAssert fut.finished - doAssert(not fut.failed) - echo("Test infix") + doAssert fut.failed + echo("Infix `and` raises") + + fut = testAll() + yield fut + doAssert fut.finished + doAssert fut.failed + echo("All() raises") fut = testCall() - await fut + yield fut + doAssert fut.failed + echo("Awaiting a async procedure call raises") + + # Test that await will read the future and raise an exception. + fut = testAwaitFut() + yield fut doAssert fut.failed - echo("Test call") + echo("Awaiting a future raises") + waitFor(tester()) |