mirror of
https://github.com/status-im/nim-chronos.git
synced 2025-02-05 16:03:45 +00:00
Add wait(deadline future) implementation. (#535)
* Add waitUntil(deadline) implementation. * Add one more test. * Fix rare race condition and tests for it. * Rename waitUntil() to wait().
This commit is contained in:
parent
d184a92227
commit
0f0ed1d654
@ -1529,6 +1529,60 @@ proc withTimeout*[T](fut: Future[T], timeout: int): Future[bool] {.
|
|||||||
inline, deprecated: "Use withTimeout(Future[T], Duration)".} =
|
inline, deprecated: "Use withTimeout(Future[T], Duration)".} =
|
||||||
withTimeout(fut, timeout.milliseconds())
|
withTimeout(fut, timeout.milliseconds())
|
||||||
|
|
||||||
|
proc waitUntilImpl[F: SomeFuture](fut: F, retFuture: auto,
|
||||||
|
deadline: auto): auto =
|
||||||
|
var timeouted = false
|
||||||
|
|
||||||
|
template completeFuture(fut: untyped, timeout: bool): untyped =
|
||||||
|
if fut.failed():
|
||||||
|
retFuture.fail(fut.error(), warn = false)
|
||||||
|
elif fut.cancelled():
|
||||||
|
if timeout:
|
||||||
|
# Its possible that `future` could be cancelled in some other place. In
|
||||||
|
# such case we can't detect if it was our cancellation due to timeout,
|
||||||
|
# or some other cancellation.
|
||||||
|
retFuture.fail(newException(AsyncTimeoutError, "Timeout exceeded!"))
|
||||||
|
else:
|
||||||
|
retFuture.cancelAndSchedule()
|
||||||
|
else:
|
||||||
|
when type(fut).T is void:
|
||||||
|
retFuture.complete()
|
||||||
|
else:
|
||||||
|
retFuture.complete(fut.value)
|
||||||
|
|
||||||
|
proc continuation(udata: pointer) {.raises: [].} =
|
||||||
|
if not(retFuture.finished()):
|
||||||
|
if timeouted:
|
||||||
|
# When timeout is exceeded and we cancelled future via cancelSoon(),
|
||||||
|
# its possible that future at this moment already has value
|
||||||
|
# and/or error.
|
||||||
|
fut.completeFuture(timeouted)
|
||||||
|
return
|
||||||
|
if not(fut.finished()):
|
||||||
|
timeouted = true
|
||||||
|
fut.cancelSoon()
|
||||||
|
else:
|
||||||
|
fut.completeFuture(false)
|
||||||
|
|
||||||
|
var cancellation: proc(udata: pointer) {.gcsafe, raises: [].}
|
||||||
|
cancellation = proc(udata: pointer) {.gcsafe, raises: [].} =
|
||||||
|
deadline.removeCallback(continuation)
|
||||||
|
if not(fut.finished()):
|
||||||
|
fut.cancelSoon()
|
||||||
|
else:
|
||||||
|
fut.completeFuture(false)
|
||||||
|
|
||||||
|
if fut.finished():
|
||||||
|
fut.completeFuture(false)
|
||||||
|
else:
|
||||||
|
if deadline.finished():
|
||||||
|
retFuture.fail(newException(AsyncTimeoutError, "Timeout exceeded!"))
|
||||||
|
else:
|
||||||
|
retFuture.cancelCallback = cancellation
|
||||||
|
fut.addCallback(continuation)
|
||||||
|
deadline.addCallback(continuation)
|
||||||
|
retFuture
|
||||||
|
|
||||||
proc waitImpl[F: SomeFuture](fut: F, retFuture: auto, timeout: Duration): auto =
|
proc waitImpl[F: SomeFuture](fut: F, retFuture: auto, timeout: Duration): auto =
|
||||||
var
|
var
|
||||||
moment: Moment
|
moment: Moment
|
||||||
@ -1606,7 +1660,8 @@ proc wait*[T](fut: Future[T], timeout = InfiniteDuration): Future[T] =
|
|||||||
## TODO: In case when ``fut`` got cancelled, what result Future[T]
|
## TODO: In case when ``fut`` got cancelled, what result Future[T]
|
||||||
## should return, because it can't be cancelled too.
|
## should return, because it can't be cancelled too.
|
||||||
var
|
var
|
||||||
retFuture = newFuture[T]("chronos.wait()", {FutureFlag.OwnCancelSchedule})
|
retFuture = newFuture[T]("chronos.wait(duration)",
|
||||||
|
{FutureFlag.OwnCancelSchedule})
|
||||||
# We set `OwnCancelSchedule` flag, because we going to cancel `retFuture`
|
# We set `OwnCancelSchedule` flag, because we going to cancel `retFuture`
|
||||||
# manually at proper time.
|
# manually at proper time.
|
||||||
|
|
||||||
@ -1621,6 +1676,28 @@ proc wait*[T](fut: Future[T], timeout = -1): Future[T] {.
|
|||||||
else:
|
else:
|
||||||
wait(fut, timeout.milliseconds())
|
wait(fut, timeout.milliseconds())
|
||||||
|
|
||||||
|
proc wait*[T](fut: Future[T], deadline: SomeFuture): Future[T] =
|
||||||
|
## Returns a future which will complete once future ``fut`` completes
|
||||||
|
## or if ``deadline`` future completes.
|
||||||
|
##
|
||||||
|
## If `deadline` future completes before future `fut` -
|
||||||
|
## `AsyncTimeoutError` exception will be raised.
|
||||||
|
##
|
||||||
|
## Note: `deadline` future will not be cancelled and/or failed.
|
||||||
|
##
|
||||||
|
## Note: While `waitUntil(future)` operation is pending, please avoid any
|
||||||
|
## attempts to cancel future `fut`. If it happens `waitUntil()` could
|
||||||
|
## introduce undefined behavior - it could raise`CancelledError` or
|
||||||
|
## `AsyncTimeoutError`.
|
||||||
|
##
|
||||||
|
## If you need to cancel `future` - cancel `waitUntil(future)` instead.
|
||||||
|
var
|
||||||
|
retFuture = newFuture[T]("chronos.wait(future)",
|
||||||
|
{FutureFlag.OwnCancelSchedule})
|
||||||
|
# We set `OwnCancelSchedule` flag, because we going to cancel `retFuture`
|
||||||
|
# manually at proper time.
|
||||||
|
waitUntilImpl(fut, retFuture, deadline)
|
||||||
|
|
||||||
proc join*(future: FutureBase): Future[void] {.
|
proc join*(future: FutureBase): Future[void] {.
|
||||||
async: (raw: true, raises: [CancelledError]).} =
|
async: (raw: true, raises: [CancelledError]).} =
|
||||||
## Returns a future which will complete once future ``future`` completes.
|
## Returns a future which will complete once future ``future`` completes.
|
||||||
@ -1783,8 +1860,21 @@ proc wait*(fut: InternalRaisesFuture, timeout = InfiniteDuration): auto =
|
|||||||
InternalRaisesFutureRaises = E.prepend(CancelledError, AsyncTimeoutError)
|
InternalRaisesFutureRaises = E.prepend(CancelledError, AsyncTimeoutError)
|
||||||
|
|
||||||
let
|
let
|
||||||
retFuture = newFuture[T]("chronos.wait()", {OwnCancelSchedule})
|
retFuture = newFuture[T]("chronos.wait(duration)", {OwnCancelSchedule})
|
||||||
# We set `OwnCancelSchedule` flag, because we going to cancel `retFuture`
|
# We set `OwnCancelSchedule` flag, because we going to cancel `retFuture`
|
||||||
# manually at proper time.
|
# manually at proper time.
|
||||||
|
|
||||||
waitImpl(fut, retFuture, timeout)
|
waitImpl(fut, retFuture, timeout)
|
||||||
|
|
||||||
|
proc wait*(fut: InternalRaisesFuture, deadline: InternalRaisesFuture): auto =
|
||||||
|
type
|
||||||
|
T = type(fut).T
|
||||||
|
E = type(fut).E
|
||||||
|
InternalRaisesFutureRaises = E.prepend(CancelledError, AsyncTimeoutError)
|
||||||
|
|
||||||
|
let
|
||||||
|
retFuture = newFuture[T]("chronos.wait(future)", {OwnCancelSchedule})
|
||||||
|
# We set `OwnCancelSchedule` flag, because we going to cancel `retFuture`
|
||||||
|
# manually at proper time.
|
||||||
|
|
||||||
|
waitUntilImpl(fut, retFuture, deadline)
|
||||||
|
@ -83,7 +83,7 @@ suite "Future[T] behavior test suite":
|
|||||||
fut.finished
|
fut.finished
|
||||||
testResult == "1245"
|
testResult == "1245"
|
||||||
|
|
||||||
asyncTest "wait[T]() test":
|
asyncTest "wait(duration) test":
|
||||||
block:
|
block:
|
||||||
## Test for not immediately completed future and timeout = -1
|
## Test for not immediately completed future and timeout = -1
|
||||||
let res =
|
let res =
|
||||||
@ -146,6 +146,183 @@ suite "Future[T] behavior test suite":
|
|||||||
false
|
false
|
||||||
check res
|
check res
|
||||||
|
|
||||||
|
asyncTest "wait(future) test":
|
||||||
|
block:
|
||||||
|
## Test for not immediately completed future and deadline which is not
|
||||||
|
## going to be finished
|
||||||
|
let
|
||||||
|
deadline = newFuture[void]()
|
||||||
|
future1 = testFuture1()
|
||||||
|
let res =
|
||||||
|
try:
|
||||||
|
discard await wait(future1, deadline)
|
||||||
|
true
|
||||||
|
except CatchableError:
|
||||||
|
false
|
||||||
|
check:
|
||||||
|
deadline.finished() == false
|
||||||
|
future1.finished() == true
|
||||||
|
res == true
|
||||||
|
|
||||||
|
await deadline.cancelAndWait()
|
||||||
|
|
||||||
|
check deadline.finished() == true
|
||||||
|
block:
|
||||||
|
## Test for immediately completed future and timeout = -1
|
||||||
|
let
|
||||||
|
deadline = newFuture[void]()
|
||||||
|
future2 = testFuture2()
|
||||||
|
let res =
|
||||||
|
try:
|
||||||
|
discard await wait(future2, deadline)
|
||||||
|
true
|
||||||
|
except CatchableError:
|
||||||
|
false
|
||||||
|
check:
|
||||||
|
deadline.finished() == false
|
||||||
|
future2.finished() == true
|
||||||
|
res
|
||||||
|
|
||||||
|
await deadline.cancelAndWait()
|
||||||
|
|
||||||
|
check deadline.finished() == true
|
||||||
|
block:
|
||||||
|
## Test for not immediately completed future and timeout = 0
|
||||||
|
let
|
||||||
|
deadline = newFuture[void]()
|
||||||
|
future1 = testFuture1()
|
||||||
|
deadline.complete()
|
||||||
|
let res =
|
||||||
|
try:
|
||||||
|
discard await wait(future1, deadline)
|
||||||
|
false
|
||||||
|
except AsyncTimeoutError:
|
||||||
|
true
|
||||||
|
except CatchableError:
|
||||||
|
false
|
||||||
|
check:
|
||||||
|
future1.finished() == false
|
||||||
|
deadline.finished() == true
|
||||||
|
res
|
||||||
|
|
||||||
|
block:
|
||||||
|
## Test for immediately completed future and timeout = 0
|
||||||
|
let
|
||||||
|
deadline = newFuture[void]()
|
||||||
|
future2 = testFuture2()
|
||||||
|
deadline.complete()
|
||||||
|
let (res1, res2) =
|
||||||
|
try:
|
||||||
|
let res = await wait(future2, deadline)
|
||||||
|
(true, res)
|
||||||
|
except CatchableError:
|
||||||
|
(false, -1)
|
||||||
|
check:
|
||||||
|
future2.finished() == true
|
||||||
|
deadline.finished() == true
|
||||||
|
res1 == true
|
||||||
|
res2 == 1
|
||||||
|
|
||||||
|
block:
|
||||||
|
## Test for future which cannot be completed in timeout period
|
||||||
|
let
|
||||||
|
deadline = sleepAsync(50.milliseconds)
|
||||||
|
future100 = testFuture100()
|
||||||
|
let res =
|
||||||
|
try:
|
||||||
|
discard await wait(future100, deadline)
|
||||||
|
false
|
||||||
|
except AsyncTimeoutError:
|
||||||
|
true
|
||||||
|
except CatchableError:
|
||||||
|
false
|
||||||
|
check:
|
||||||
|
deadline.finished() == true
|
||||||
|
res
|
||||||
|
await future100.cancelAndWait()
|
||||||
|
check:
|
||||||
|
future100.finished() == true
|
||||||
|
|
||||||
|
block:
|
||||||
|
## Test for future which will be completed before timeout exceeded.
|
||||||
|
let
|
||||||
|
deadline = sleepAsync(500.milliseconds)
|
||||||
|
future100 = testFuture100()
|
||||||
|
let (res1, res2) =
|
||||||
|
try:
|
||||||
|
let res = await wait(future100, deadline)
|
||||||
|
(true, res)
|
||||||
|
except CatchableError:
|
||||||
|
(false, -1)
|
||||||
|
check:
|
||||||
|
future100.finished() == true
|
||||||
|
deadline.finished() == false
|
||||||
|
res1 == true
|
||||||
|
res2 == 0
|
||||||
|
await deadline.cancelAndWait()
|
||||||
|
check:
|
||||||
|
deadline.finished() == true
|
||||||
|
|
||||||
|
asyncTest "wait(future) cancellation behavior test":
|
||||||
|
proc deepTest3(future: Future[void]) {.async.} =
|
||||||
|
await future
|
||||||
|
|
||||||
|
proc deepTest2(future: Future[void]) {.async.} =
|
||||||
|
await deepTest3(future)
|
||||||
|
|
||||||
|
proc deepTest1(future: Future[void]) {.async.} =
|
||||||
|
await deepTest2(future)
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
deadlineFuture = newFuture[void]()
|
||||||
|
|
||||||
|
block:
|
||||||
|
# Cancellation should affect `testFuture` because it is in pending state.
|
||||||
|
let monitorFuture = newFuture[void]()
|
||||||
|
var testFuture = deepTest1(monitorFuture)
|
||||||
|
let waitFut = wait(testFuture, deadlineFuture)
|
||||||
|
await cancelAndWait(waitFut)
|
||||||
|
check:
|
||||||
|
monitorFuture.cancelled() == true
|
||||||
|
testFuture.cancelled() == true
|
||||||
|
waitFut.cancelled() == true
|
||||||
|
deadlineFuture.finished() == false
|
||||||
|
|
||||||
|
block:
|
||||||
|
# Cancellation should not affect `testFuture` because it is completed.
|
||||||
|
let monitorFuture = newFuture[void]()
|
||||||
|
var testFuture = deepTest1(monitorFuture)
|
||||||
|
let waitFut = wait(testFuture, deadlineFuture)
|
||||||
|
monitorFuture.complete()
|
||||||
|
await cancelAndWait(waitFut)
|
||||||
|
check:
|
||||||
|
monitorFuture.completed() == true
|
||||||
|
monitorFuture.cancelled() == false
|
||||||
|
testFuture.completed() == true
|
||||||
|
waitFut.completed() == true
|
||||||
|
deadlineFuture.finished() == false
|
||||||
|
|
||||||
|
block:
|
||||||
|
# Cancellation should not affect `testFuture` because it is failed.
|
||||||
|
let monitorFuture = newFuture[void]()
|
||||||
|
var testFuture = deepTest1(monitorFuture)
|
||||||
|
let waitFut = wait(testFuture, deadlineFuture)
|
||||||
|
monitorFuture.fail(newException(ValueError, "TEST"))
|
||||||
|
await cancelAndWait(waitFut)
|
||||||
|
check:
|
||||||
|
monitorFuture.failed() == true
|
||||||
|
monitorFuture.cancelled() == false
|
||||||
|
testFuture.failed() == true
|
||||||
|
testFuture.cancelled() == false
|
||||||
|
waitFut.failed() == true
|
||||||
|
testFuture.cancelled() == false
|
||||||
|
deadlineFuture.finished() == false
|
||||||
|
|
||||||
|
await cancelAndWait(deadlineFuture)
|
||||||
|
|
||||||
|
check deadlineFuture.finished() == true
|
||||||
|
|
||||||
asyncTest "Discarded result Future[T] test":
|
asyncTest "Discarded result Future[T] test":
|
||||||
var completedFutures = 0
|
var completedFutures = 0
|
||||||
|
|
||||||
@ -1082,7 +1259,7 @@ suite "Future[T] behavior test suite":
|
|||||||
completed == 0
|
completed == 0
|
||||||
cancelled == 1
|
cancelled == 1
|
||||||
|
|
||||||
asyncTest "Cancellation wait() test":
|
asyncTest "Cancellation wait(duration) test":
|
||||||
var neverFlag1, neverFlag2, neverFlag3: bool
|
var neverFlag1, neverFlag2, neverFlag3: bool
|
||||||
var waitProc1, waitProc2: bool
|
var waitProc1, waitProc2: bool
|
||||||
proc neverEndingProc(): Future[void] =
|
proc neverEndingProc(): Future[void] =
|
||||||
@ -1143,7 +1320,39 @@ suite "Future[T] behavior test suite":
|
|||||||
fut.state == FutureState.Completed
|
fut.state == FutureState.Completed
|
||||||
neverFlag1 and neverFlag2 and neverFlag3 and waitProc1 and waitProc2
|
neverFlag1 and neverFlag2 and neverFlag3 and waitProc1 and waitProc2
|
||||||
|
|
||||||
asyncTest "Cancellation race test":
|
asyncTest "Cancellation wait(future) test":
|
||||||
|
var neverFlag1, neverFlag2, neverFlag3: bool
|
||||||
|
var waitProc1, waitProc2: bool
|
||||||
|
proc neverEndingProc(): Future[void] =
|
||||||
|
var res = newFuture[void]()
|
||||||
|
proc continuation(udata: pointer) {.gcsafe.} =
|
||||||
|
neverFlag2 = true
|
||||||
|
proc cancellation(udata: pointer) {.gcsafe.} =
|
||||||
|
neverFlag3 = true
|
||||||
|
res.addCallback(continuation)
|
||||||
|
res.cancelCallback = cancellation
|
||||||
|
result = res
|
||||||
|
neverFlag1 = true
|
||||||
|
|
||||||
|
proc waitProc() {.async.} =
|
||||||
|
let deadline = sleepAsync(100.milliseconds)
|
||||||
|
try:
|
||||||
|
await wait(neverEndingProc(), deadline)
|
||||||
|
except CancelledError:
|
||||||
|
waitProc1 = true
|
||||||
|
except CatchableError:
|
||||||
|
doAssert(false)
|
||||||
|
finally:
|
||||||
|
await cancelAndWait(deadline)
|
||||||
|
waitProc2 = true
|
||||||
|
|
||||||
|
var fut = waitProc()
|
||||||
|
await cancelAndWait(fut)
|
||||||
|
check:
|
||||||
|
fut.state == FutureState.Completed
|
||||||
|
neverFlag1 and neverFlag2 and neverFlag3 and waitProc1 and waitProc2
|
||||||
|
|
||||||
|
asyncTest "Cancellation race() test":
|
||||||
var someFut = newFuture[void]()
|
var someFut = newFuture[void]()
|
||||||
|
|
||||||
proc raceProc(): Future[void] {.async.} =
|
proc raceProc(): Future[void] {.async.} =
|
||||||
@ -1298,7 +1507,7 @@ suite "Future[T] behavior test suite":
|
|||||||
false
|
false
|
||||||
check res
|
check res
|
||||||
|
|
||||||
asyncTest "wait(fut) should wait cancellation test":
|
asyncTest "wait(future) should wait cancellation test":
|
||||||
proc futureNeverEnds(): Future[void] =
|
proc futureNeverEnds(): Future[void] =
|
||||||
newFuture[void]("neverending.future")
|
newFuture[void]("neverending.future")
|
||||||
|
|
||||||
@ -1322,6 +1531,29 @@ suite "Future[T] behavior test suite":
|
|||||||
|
|
||||||
check res
|
check res
|
||||||
|
|
||||||
|
asyncTest "wait(future) should wait cancellation test":
|
||||||
|
proc futureNeverEnds(): Future[void] =
|
||||||
|
newFuture[void]("neverending.future")
|
||||||
|
|
||||||
|
proc futureOneLevelMore() {.async.} =
|
||||||
|
await futureNeverEnds()
|
||||||
|
|
||||||
|
var fut = futureOneLevelMore()
|
||||||
|
let res =
|
||||||
|
try:
|
||||||
|
await wait(fut, sleepAsync(100.milliseconds))
|
||||||
|
false
|
||||||
|
except AsyncTimeoutError:
|
||||||
|
# Because `fut` is never-ending Future[T], `wait` should raise
|
||||||
|
# `AsyncTimeoutError`, but only after `fut` is cancelled.
|
||||||
|
if fut.cancelled():
|
||||||
|
true
|
||||||
|
else:
|
||||||
|
false
|
||||||
|
except CatchableError:
|
||||||
|
false
|
||||||
|
check res
|
||||||
|
|
||||||
test "race(zero) test":
|
test "race(zero) test":
|
||||||
var tseq = newSeq[FutureBase]()
|
var tseq = newSeq[FutureBase]()
|
||||||
var fut1 = race(tseq)
|
var fut1 = race(tseq)
|
||||||
@ -1563,7 +1795,7 @@ suite "Future[T] behavior test suite":
|
|||||||
v1_u == 0'u
|
v1_u == 0'u
|
||||||
v2_u + 1'u == 0'u
|
v2_u + 1'u == 0'u
|
||||||
|
|
||||||
asyncTest "wait() cancellation undefined behavior test #1":
|
asyncTest "wait(duration) cancellation undefined behavior test #1":
|
||||||
proc testInnerFoo(fooFut: Future[void]): Future[TestFooConnection] {.
|
proc testInnerFoo(fooFut: Future[void]): Future[TestFooConnection] {.
|
||||||
async.} =
|
async.} =
|
||||||
await fooFut
|
await fooFut
|
||||||
@ -1586,7 +1818,7 @@ suite "Future[T] behavior test suite":
|
|||||||
discard someFut.tryCancel()
|
discard someFut.tryCancel()
|
||||||
await someFut
|
await someFut
|
||||||
|
|
||||||
asyncTest "wait() cancellation undefined behavior test #2":
|
asyncTest "wait(duration) cancellation undefined behavior test #2":
|
||||||
proc testInnerFoo(fooFut: Future[void]): Future[TestFooConnection] {.
|
proc testInnerFoo(fooFut: Future[void]): Future[TestFooConnection] {.
|
||||||
async.} =
|
async.} =
|
||||||
await fooFut
|
await fooFut
|
||||||
@ -1613,7 +1845,7 @@ suite "Future[T] behavior test suite":
|
|||||||
discard someFut.tryCancel()
|
discard someFut.tryCancel()
|
||||||
await someFut
|
await someFut
|
||||||
|
|
||||||
asyncTest "wait() should allow cancellation test (depends on race())":
|
asyncTest "wait(duration) should allow cancellation test (depends on race())":
|
||||||
proc testFoo(): Future[bool] {.async.} =
|
proc testFoo(): Future[bool] {.async.} =
|
||||||
let
|
let
|
||||||
resFut = sleepAsync(2.seconds).wait(3.seconds)
|
resFut = sleepAsync(2.seconds).wait(3.seconds)
|
||||||
@ -1699,6 +1931,78 @@ suite "Future[T] behavior test suite":
|
|||||||
|
|
||||||
check (await testFoo()) == true
|
check (await testFoo()) == true
|
||||||
|
|
||||||
|
asyncTest "wait(future) cancellation undefined behavior test #1":
|
||||||
|
proc testInnerFoo(fooFut: Future[void]): Future[TestFooConnection] {.
|
||||||
|
async.} =
|
||||||
|
await fooFut
|
||||||
|
return TestFooConnection()
|
||||||
|
|
||||||
|
proc testFoo(fooFut: Future[void]) {.async.} =
|
||||||
|
let deadline = sleepAsync(10.seconds)
|
||||||
|
let connection =
|
||||||
|
try:
|
||||||
|
let res = await testInnerFoo(fooFut).wait(deadline)
|
||||||
|
Result[TestFooConnection, int].ok(res)
|
||||||
|
except CancelledError:
|
||||||
|
Result[TestFooConnection, int].err(0)
|
||||||
|
except CatchableError:
|
||||||
|
Result[TestFooConnection, int].err(1)
|
||||||
|
finally:
|
||||||
|
await deadline.cancelAndWait()
|
||||||
|
|
||||||
|
check connection.isOk()
|
||||||
|
|
||||||
|
var future = newFuture[void]("last.child.future")
|
||||||
|
var someFut = testFoo(future)
|
||||||
|
future.complete()
|
||||||
|
discard someFut.tryCancel()
|
||||||
|
await someFut
|
||||||
|
|
||||||
|
asyncTest "wait(future) cancellation undefined behavior test #2":
|
||||||
|
proc testInnerFoo(fooFut: Future[void]): Future[TestFooConnection] {.
|
||||||
|
async.} =
|
||||||
|
await fooFut
|
||||||
|
return TestFooConnection()
|
||||||
|
|
||||||
|
proc testMiddleFoo(fooFut: Future[void]): Future[TestFooConnection] {.
|
||||||
|
async.} =
|
||||||
|
await testInnerFoo(fooFut)
|
||||||
|
|
||||||
|
proc testFoo(fooFut: Future[void]) {.async.} =
|
||||||
|
let deadline = sleepAsync(10.seconds)
|
||||||
|
let connection =
|
||||||
|
try:
|
||||||
|
let res = await testMiddleFoo(fooFut).wait(deadline)
|
||||||
|
Result[TestFooConnection, int].ok(res)
|
||||||
|
except CancelledError:
|
||||||
|
Result[TestFooConnection, int].err(0)
|
||||||
|
except CatchableError:
|
||||||
|
Result[TestFooConnection, int].err(1)
|
||||||
|
finally:
|
||||||
|
await deadline.cancelAndWait()
|
||||||
|
check connection.isOk()
|
||||||
|
|
||||||
|
var future = newFuture[void]("last.child.future")
|
||||||
|
var someFut = testFoo(future)
|
||||||
|
future.complete()
|
||||||
|
discard someFut.tryCancel()
|
||||||
|
await someFut
|
||||||
|
|
||||||
|
asyncTest "wait(future) should allow cancellation test (depends on race())":
|
||||||
|
proc testFoo(): Future[bool] {.async.} =
|
||||||
|
let
|
||||||
|
deadline = sleepAsync(3.seconds)
|
||||||
|
resFut = sleepAsync(2.seconds).wait(deadline)
|
||||||
|
timeFut = sleepAsync(1.seconds)
|
||||||
|
cancelFut = cancelAndWait(resFut)
|
||||||
|
discard await race(cancelFut, timeFut)
|
||||||
|
await deadline.cancelAndWait()
|
||||||
|
if cancelFut.finished():
|
||||||
|
return (resFut.cancelled() and cancelFut.completed())
|
||||||
|
false
|
||||||
|
|
||||||
|
check (await testFoo()) == true
|
||||||
|
|
||||||
asyncTest "Cancellation behavior test":
|
asyncTest "Cancellation behavior test":
|
||||||
proc testInnerFoo(fooFut: Future[void]) {.async.} =
|
proc testInnerFoo(fooFut: Future[void]) {.async.} =
|
||||||
await fooFut
|
await fooFut
|
||||||
@ -2178,7 +2482,7 @@ suite "Future[T] behavior test suite":
|
|||||||
not compiles(Future[void].Raising([42]))
|
not compiles(Future[void].Raising([42]))
|
||||||
not compiles(Future[void].Raising(42))
|
not compiles(Future[void].Raising(42))
|
||||||
|
|
||||||
asyncTest "Timeout/cancellation race wait() test":
|
asyncTest "Timeout/cancellation race wait(duration) test":
|
||||||
proc raceTest(T: typedesc, itype: int) {.async.} =
|
proc raceTest(T: typedesc, itype: int) {.async.} =
|
||||||
let monitorFuture = newFuture[T]("monitor",
|
let monitorFuture = newFuture[T]("monitor",
|
||||||
{FutureFlag.OwnCancelSchedule})
|
{FutureFlag.OwnCancelSchedule})
|
||||||
@ -2252,6 +2556,83 @@ suite "Future[T] behavior test suite":
|
|||||||
await raceTest(int, 1)
|
await raceTest(int, 1)
|
||||||
await raceTest(int, 2)
|
await raceTest(int, 2)
|
||||||
|
|
||||||
|
asyncTest "Timeout/cancellation race wait(future) test":
|
||||||
|
proc raceTest(T: typedesc, itype: int) {.async.} =
|
||||||
|
let monitorFuture = newFuture[T]()
|
||||||
|
|
||||||
|
proc raceProc0(future: Future[T]): Future[T] {.async.} =
|
||||||
|
await future
|
||||||
|
proc raceProc1(future: Future[T]): Future[T] {.async.} =
|
||||||
|
await raceProc0(future)
|
||||||
|
proc raceProc2(future: Future[T]): Future[T] {.async.} =
|
||||||
|
await raceProc1(future)
|
||||||
|
|
||||||
|
proc continuation(udata: pointer) {.gcsafe.} =
|
||||||
|
if itype == 0:
|
||||||
|
when T is void:
|
||||||
|
monitorFuture.complete()
|
||||||
|
elif T is int:
|
||||||
|
monitorFuture.complete(100)
|
||||||
|
elif itype == 1:
|
||||||
|
monitorFuture.fail(newException(ValueError, "test"))
|
||||||
|
else:
|
||||||
|
monitorFuture.cancelAndSchedule()
|
||||||
|
|
||||||
|
let deadlineFuture = newFuture[void]()
|
||||||
|
deadlineFuture.addCallback continuation
|
||||||
|
|
||||||
|
let
|
||||||
|
testFut = raceProc2(monitorFuture)
|
||||||
|
waitFut = wait(testFut, deadlineFuture)
|
||||||
|
|
||||||
|
deadlineFuture.complete()
|
||||||
|
|
||||||
|
when T is void:
|
||||||
|
let waitRes =
|
||||||
|
try:
|
||||||
|
await waitFut
|
||||||
|
if itype == 0:
|
||||||
|
true
|
||||||
|
else:
|
||||||
|
false
|
||||||
|
except CancelledError:
|
||||||
|
false
|
||||||
|
except CatchableError:
|
||||||
|
if itype != 0:
|
||||||
|
true
|
||||||
|
else:
|
||||||
|
false
|
||||||
|
check waitRes == true
|
||||||
|
elif T is int:
|
||||||
|
let waitRes =
|
||||||
|
try:
|
||||||
|
let res = await waitFut
|
||||||
|
if itype == 0:
|
||||||
|
(true, res)
|
||||||
|
else:
|
||||||
|
(false, -1)
|
||||||
|
except CancelledError:
|
||||||
|
(false, -1)
|
||||||
|
except CatchableError:
|
||||||
|
if itype != 0:
|
||||||
|
(true, 0)
|
||||||
|
else:
|
||||||
|
(false, -1)
|
||||||
|
if itype == 0:
|
||||||
|
check:
|
||||||
|
waitRes[0] == true
|
||||||
|
waitRes[1] == 100
|
||||||
|
else:
|
||||||
|
check:
|
||||||
|
waitRes[0] == true
|
||||||
|
|
||||||
|
await raceTest(void, 0)
|
||||||
|
await raceTest(void, 1)
|
||||||
|
await raceTest(void, 2)
|
||||||
|
await raceTest(int, 0)
|
||||||
|
await raceTest(int, 1)
|
||||||
|
await raceTest(int, 2)
|
||||||
|
|
||||||
asyncTest "Timeout/cancellation race withTimeout() test":
|
asyncTest "Timeout/cancellation race withTimeout() test":
|
||||||
proc raceTest(T: typedesc, itype: int) {.async.} =
|
proc raceTest(T: typedesc, itype: int) {.async.} =
|
||||||
let monitorFuture = newFuture[T]("monitor",
|
let monitorFuture = newFuture[T]("monitor",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user