mirror of
https://github.com/logos-storage/nim-chronos.git
synced 2026-02-20 05:23:06 +00:00
Fix behavior of wait() and withTimeout() calls to cancel and wait for result of cancelled Future[T].
Add tests.
This commit is contained in:
parent
7fd1a0f635
commit
493cb1dbfd
@ -847,19 +847,23 @@ proc withTimeout*[T](fut: Future[T], timeout: Duration): Future[bool] =
|
||||
var retFuture = newFuture[bool]("chronos.`withTimeout`")
|
||||
var moment: Moment
|
||||
var timer: TimerCallback
|
||||
var cancelling = false
|
||||
|
||||
proc continuation(udata: pointer) {.gcsafe.} =
|
||||
if not(retFuture.finished()):
|
||||
if not(fut.finished()):
|
||||
# Timer exceeded first.
|
||||
fut.removeCallback(continuation)
|
||||
fut.cancel()
|
||||
retFuture.complete(false)
|
||||
if not(cancelling):
|
||||
if not(fut.finished()):
|
||||
# Timer exceeded first, we going to cancel `fut` and wait until it
|
||||
# not completes.
|
||||
cancelling = true
|
||||
fut.cancel()
|
||||
else:
|
||||
# Future `fut` completed/failed/cancelled first.
|
||||
if not(isNil(timer)):
|
||||
clearTimer(timer)
|
||||
retFuture.complete(true)
|
||||
else:
|
||||
# Future `fut` completed/failed/cancelled first.
|
||||
if not isNil(timer):
|
||||
clearTimer(timer)
|
||||
retFuture.complete(true)
|
||||
retFuture.complete(false)
|
||||
|
||||
proc cancellation(udata: pointer) {.gcsafe.} =
|
||||
if not isNil(timer):
|
||||
@ -900,26 +904,29 @@ proc wait*[T](fut: Future[T], timeout = InfiniteDuration): Future[T] =
|
||||
var retFuture = newFuture[T]("chronos.wait()")
|
||||
var moment: Moment
|
||||
var timer: TimerCallback
|
||||
var cancelling = false
|
||||
|
||||
proc continuation(udata: pointer) {.gcsafe.} =
|
||||
if not(retFuture.finished()):
|
||||
if not(fut.finished()):
|
||||
# Timer exceeded first.
|
||||
fut.removeCallback(continuation)
|
||||
fut.cancel()
|
||||
retFuture.fail(newException(AsyncTimeoutError, "Timeout exceeded!"))
|
||||
else:
|
||||
# Future `fut` completed/failed/cancelled first.
|
||||
if not isNil(timer):
|
||||
clearTimer(timer)
|
||||
|
||||
if fut.failed():
|
||||
retFuture.fail(fut.error)
|
||||
if not(cancelling):
|
||||
if not(fut.finished()):
|
||||
# Timer exceeded first.
|
||||
cancelling = true
|
||||
fut.cancel()
|
||||
else:
|
||||
when T is void:
|
||||
retFuture.complete()
|
||||
# Future `fut` completed/failed/cancelled first.
|
||||
if not isNil(timer):
|
||||
clearTimer(timer)
|
||||
|
||||
if fut.failed():
|
||||
retFuture.fail(fut.error)
|
||||
else:
|
||||
retFuture.complete(fut.read())
|
||||
when T is void:
|
||||
retFuture.complete()
|
||||
else:
|
||||
retFuture.complete(fut.read())
|
||||
else:
|
||||
retFuture.fail(newException(AsyncTimeoutError, "Timeout exceeded!"))
|
||||
|
||||
proc cancellation(udata: pointer) {.gcsafe.} =
|
||||
if not isNil(timer):
|
||||
|
||||
@ -1067,6 +1067,52 @@ suite "Future[T] behavior test suite":
|
||||
|
||||
r10 and r11 and r20 and r21 and r30 and r31
|
||||
|
||||
proc testWithTimeoutCancelAndWait(): bool =
|
||||
proc futureNeverEnds(): Future[void] =
|
||||
newFuture[void]("neverending.future")
|
||||
|
||||
proc futureOneLevelMore() {.async.} =
|
||||
await futureNeverEnds()
|
||||
|
||||
proc testWithTimeout(): Future[bool] {.async.} =
|
||||
var fut = futureOneLevelMore()
|
||||
try:
|
||||
let res = await withTimeout(fut, 100.milliseconds)
|
||||
# Because `fut` is never-ending Future[T], `withTimeout` should return
|
||||
# `false` but it also has to wait until `fut` is cancelled.
|
||||
if not(res) and fut.cancelled():
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
except CatchableError:
|
||||
return false
|
||||
|
||||
waitFor testWithTimeout()
|
||||
|
||||
proc testWaitCancelAndWait(): bool =
|
||||
proc futureNeverEnds(): Future[void] =
|
||||
newFuture[void]("neverending.future")
|
||||
|
||||
proc futureOneLevelMore() {.async.} =
|
||||
await futureNeverEnds()
|
||||
|
||||
proc testWait(): Future[bool] {.async.} =
|
||||
var fut = futureOneLevelMore()
|
||||
try:
|
||||
await wait(fut, 100.milliseconds)
|
||||
return false
|
||||
except AsyncTimeoutError:
|
||||
# Because `fut` is never-ending Future[T], `wait` should raise
|
||||
# `AsyncTimeoutError`, but only after `fut` is cancelled.
|
||||
if fut.cancelled():
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
except CatchableError:
|
||||
return false
|
||||
|
||||
waitFor testWait()
|
||||
|
||||
test "Async undefined behavior (#7758) test":
|
||||
check test1() == true
|
||||
test "Immediately completed asynchronous procedure test":
|
||||
@ -1126,3 +1172,9 @@ suite "Future[T] behavior test suite":
|
||||
|
||||
test "location test":
|
||||
check testSrcLocation() == true
|
||||
|
||||
test "withTimeout(fut) should wait cancellation test":
|
||||
check testWithTimeoutCancelAndWait()
|
||||
|
||||
test "wait(fut) should wait cancellation test":
|
||||
check testWaitCancelAndWait()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user