From 1eb834a2f98c31677e899ffcc80259a10d78cfe7 Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Tue, 5 Mar 2024 18:33:46 +0200 Subject: [PATCH] Fix `or` deadlock issue. (#517) * Fix `or` should not create future with OwnCancelSchedule flag set. * Fix `CancelledError` missing from raises list when both futures has empty raises list. * Fix macros tests. --- chronos/internal/asyncfutures.nim | 9 ++++----- tests/testbugs.nim | 13 +++++++++++++ tests/testmacro.nim | 2 +- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/chronos/internal/asyncfutures.nim b/chronos/internal/asyncfutures.nim index 1a2be757..de6debaf 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) @@ -1665,10 +1665,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 = diff --git a/tests/testbugs.nim b/tests/testbugs.nim index fc4af3a4..3f2f4e42 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/testmacro.nim b/tests/testmacro.nim index 9b19c689..d646303a 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: [].} =