diff --git a/chronos/internal/asyncfutures.nim b/chronos/internal/asyncfutures.nim index 1a2be7577..d08425202 100644 --- a/chronos/internal/asyncfutures.nim +++ b/chronos/internal/asyncfutures.nim @@ -786,7 +786,7 @@ template orImpl*[T, Y](fut1: Future[T], fut2: Future[Y]): untyped = fut2.addCallback(cb) retFuture.cancelCallback = cancellation - return retFuture + retFuture proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] = ## Returns a future which will complete once either ``fut1`` or ``fut2`` @@ -801,7 +801,7 @@ proc `or`*[T, Y](fut1: Future[T], fut2: Future[Y]): Future[void] = ## completed, the result future will also be completed. ## ## If cancelled, ``fut1`` and ``fut2`` futures WILL NOT BE cancelled. - var retFuture = newFuture[void]("chronos.or") + var retFuture = newFuture[void]("chronos.or()") orImpl(fut1, fut2) @@ -1410,6 +1410,8 @@ proc withTimeout*[T](fut: Future[T], timeout: Duration): Future[bool] {. var retFuture = newFuture[bool]("chronos.withTimeout", {FutureFlag.OwnCancelSchedule}) + # We set `OwnCancelSchedule` flag, because we going to cancel `retFuture` + # manually at proper time. moment: Moment timer: TimerCallback timeouted = false @@ -1536,6 +1538,8 @@ proc wait*[T](fut: Future[T], timeout = InfiniteDuration): Future[T] = ## should return, because it can't be cancelled too. var retFuture = newFuture[T]("chronos.wait()", {FutureFlag.OwnCancelSchedule}) + # We set `OwnCancelSchedule` flag, because we going to cancel `retFuture` + # manually at proper time. waitImpl(fut, retFuture, timeout) @@ -1665,10 +1669,9 @@ proc `or`*[T, Y, E1, E2]( fut1: InternalRaisesFuture[T, E1], fut2: InternalRaisesFuture[Y, E2]): auto = type - InternalRaisesFutureRaises = union(E1, E2) + InternalRaisesFutureRaises = union(E1, E2).union((CancelledError,)) - let - retFuture = newFuture[void]("chronos.wait()", {FutureFlag.OwnCancelSchedule}) + let retFuture = newFuture[void]("chronos.or()", {}) orImpl(fut1, fut2) proc wait*(fut: InternalRaisesFuture, timeout = InfiniteDuration): auto = @@ -1678,6 +1681,8 @@ proc wait*(fut: InternalRaisesFuture, timeout = InfiniteDuration): auto = InternalRaisesFutureRaises = E.prepend(CancelledError, AsyncTimeoutError) let - retFuture = newFuture[T]("chronos.wait()", {FutureFlag.OwnCancelSchedule}) + retFuture = newFuture[T]("chronos.wait()", {OwnCancelSchedule}) + # We set `OwnCancelSchedule` flag, because we going to cancel `retFuture` + # manually at proper time. waitImpl(fut, retFuture, timeout) diff --git a/chronos/internal/raisesfutures.nim b/chronos/internal/raisesfutures.nim index 5b91f4152..ed85c036e 100644 --- a/chronos/internal/raisesfutures.nim +++ b/chronos/internal/raisesfutures.nim @@ -142,7 +142,7 @@ macro union*(tup0: typedesc, tup1: typedesc): typedesc = if not found: result.add err - for err2 in getType(getTypeInst(tup1)[1])[1..^1]: + for err2 in tup1.members(): result.add err2 if result.len == 0: result = makeNoRaises() diff --git a/tests/testbugs.nim b/tests/testbugs.nim index fc4af3a45..3f2f4e420 100644 --- a/tests/testbugs.nim +++ b/tests/testbugs.nim @@ -135,6 +135,16 @@ suite "Asynchronous issues test suite": await server.closeWait() return true + proc testOrDeadlock(): Future[bool] {.async.} = + proc f(): Future[void] {.async.} = + await sleepAsync(2.seconds) or sleepAsync(1.seconds) + let fx = f() + try: + await fx.cancelAndWait().wait(2.seconds) + except AsyncTimeoutError: + return false + true + test "Issue #6": check waitFor(issue6()) == true @@ -152,3 +162,6 @@ suite "Asynchronous issues test suite": test "IndexError crash test": check waitFor(testIndexError()) == true + + test "`or` deadlock [#516] test": + check waitFor(testOrDeadlock()) == true diff --git a/tests/testfut.nim b/tests/testfut.nim index fc2401d04..aee3b1537 100644 --- a/tests/testfut.nim +++ b/tests/testfut.nim @@ -1594,6 +1594,19 @@ suite "Future[T] behavior test suite": discard someFut.tryCancel() await someFut + asyncTest "wait() should allow cancellation test (depends on race())": + proc testFoo(): Future[bool] {.async.} = + let + resFut = sleepAsync(2.seconds).wait(3.seconds) + timeFut = sleepAsync(1.seconds) + cancelFut = cancelAndWait(resFut) + discard await race(cancelFut, timeFut) + if cancelFut.finished(): + return (resFut.cancelled() and cancelFut.completed()) + false + + check (await testFoo()) == true + asyncTest "withTimeout() cancellation undefined behavior test #1": proc testInnerFoo(fooFut: Future[void]): Future[TestFooConnection] {. async.} = @@ -1654,6 +1667,19 @@ suite "Future[T] behavior test suite": discard someFut.tryCancel() await someFut + asyncTest "withTimeout() should allow cancellation test (depends on race())": + proc testFoo(): Future[bool] {.async.} = + let + resFut = sleepAsync(2.seconds).withTimeout(3.seconds) + timeFut = sleepAsync(1.seconds) + cancelFut = cancelAndWait(resFut) + discard await race(cancelFut, timeFut) + if cancelFut.finished(): + return (resFut.cancelled() and cancelFut.completed()) + false + + check (await testFoo()) == true + asyncTest "Cancellation behavior test": proc testInnerFoo(fooFut: Future[void]) {.async.} = await fooFut diff --git a/tests/testmacro.nim b/tests/testmacro.nim index 9b19c6891..d646303a3 100644 --- a/tests/testmacro.nim +++ b/tests/testmacro.nim @@ -491,7 +491,7 @@ suite "Exceptions tracking": proc testit2 {.async: (raises: [IOError]).} = raise (ref IOError)() - proc test {.async: (raises: [ValueError, IOError]).} = + proc test {.async: (raises: [CancelledError, ValueError, IOError]).} = await testit() or testit2() proc noraises() {.raises: [].} =