Skip to content

Commit

Permalink
Fix cancelAndWait() which now could perform multiple attempts to canc…
Browse files Browse the repository at this point in the history
…el target Future (mustCancel behavior).
  • Loading branch information
cheatfate committed Sep 9, 2023
1 parent 9c63541 commit 772a7d0
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 1 deletion.
19 changes: 18 additions & 1 deletion chronos/asyncfutures2.nim
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,17 @@ proc cancelAndWait*(fut: FutureBase): Future[void] =
let retFuture = newFuture[void]("chronos.cancelAndWait(FutureBase)",
{FutureFlag.OwnCancelSchedule})

proc checktick(udata: pointer) {.gcsafe.} =
# We trying to cancel Future on more time, and if `cancel()` succeeds we
# return early.
if cancel(fut, getSrcLocation()):
return
# Cancellation signal was not delivered, so we trying to deliver it one
# more time after one tick. But we need to check situation when child
# future was finished but our completion callback is not yet invoked.
if not(fut.finished()):
callTick(checktick, nil)

proc continuation(udata: pointer) {.gcsafe.} =
retFuture.complete()

Expand All @@ -817,7 +828,13 @@ proc cancelAndWait*(fut: FutureBase): Future[void] =
fut.addCallback(continuation)
retFuture.cancelCallback = cancellation
# Initiate cancellation process.
fut.cancel()
if not(cancel(fut, getSrcLocation())):
# Cancellation signal was not delivered, so we trying to deliver it one
# more time after async tick. But we need to check case, when future was
# finished but our completion callback is not yet invoked.
if not(fut.finished()):
callTick(checktick, nil)

retFuture

proc checkedCancelAndWait*(fut: FutureBase): Future[bool] =
Expand Down
76 changes: 76 additions & 0 deletions tests/testfut.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1939,3 +1939,79 @@ suite "Future[T] behavior test suite":
testres == "ABCDE"

check test() == true

asyncTest "cancelAndWait() should be able to cancel test":
proc test1() {.async.} =
await noCancelWait sleepAsync(100.milliseconds)
await noCancelWait sleepAsync(100.milliseconds)
await sleepAsync(100.milliseconds)

proc test2() {.async.} =
await noCancelWait sleepAsync(100.milliseconds)
await sleepAsync(100.milliseconds)
await noCancelWait sleepAsync(100.milliseconds)

proc test3() {.async.} =
await sleepAsync(100.milliseconds)
await noCancelWait sleepAsync(100.milliseconds)
await noCancelWait sleepAsync(100.milliseconds)

proc test4() {.async.} =
while true:
await noCancelWait sleepAsync(50.milliseconds)
await sleepAsync(0.milliseconds)

proc test5() {.async.} =
while true:
await sleepAsync(0.milliseconds)
await noCancelWait sleepAsync(50.milliseconds)

block:
let future1 = test1()
await cancelAndWait(future1)
let future2 = test1()
await sleepAsync(10.milliseconds)
await cancelAndWait(future2)
check:
future1.cancelled() == true
future2.cancelled() == true

block:
let future1 = test2()
await cancelAndWait(future1)
let future2 = test2()
await sleepAsync(10.milliseconds)
await cancelAndWait(future2)
check:
future1.cancelled() == true
future2.cancelled() == true

block:
let future1 = test3()
await cancelAndWait(future1)
let future2 = test3()
await sleepAsync(10.milliseconds)
await cancelAndWait(future2)
check:
future1.cancelled() == true
future2.cancelled() == true

block:
let future1 = test4()
await cancelAndWait(future1)
let future2 = test4()
await sleepAsync(333.milliseconds)
await cancelAndWait(future2)
check:
future1.cancelled() == true
future2.cancelled() == true

block:
let future1 = test5()
await cancelAndWait(future1)
let future2 = test5()
await sleepAsync(333.milliseconds)
await cancelAndWait(future2)
check:
future1.cancelled() == true
future2.cancelled() == true

0 comments on commit 772a7d0

Please sign in to comment.