nim-chronos/tests/testfut.nim
Eugene Kabanov b55e2816eb
Allow cancelAndWait() accept multiple Futures for cancellation. (#572)
* Allow cancelAndWait accept multiple Futures for cancellation.
Add tests.

* Add 2 more tests.

* Update test name.

* Fix sporadic test.
2025-03-21 14:46:18 +02:00

2919 lines
80 KiB
Nim

# Chronos Test Suite
# (c) Copyright 2018-Present
# Status Research & Development GmbH
#
# Licensed under either of
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
import unittest2
import results
import ../chronos, ../chronos/unittest2/asynctests
{.used.}
type
TestFooConnection* = ref object
id*: int
suite "Future[T] behavior test suite":
proc testFuture1(): Future[int] {.async.} =
await sleepAsync(0.milliseconds)
proc testFuture2(): Future[int] {.async.} =
return 1
proc testFuture3(): Future[int] {.async.} =
result = await testFuture2()
proc testFuture100(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
test "Async undefined behavior (#7758) test":
var fut = testFuture1()
poll()
poll()
if not fut.finished:
poll()
check: fut.finished
test "Immediately completed asynchronous procedure test":
var fut = testFuture3()
check: fut.finished
test "Future[T] callbacks are invoked in reverse order (#7197) test":
var testResult = ""
var fut = testFuture1()
fut.addCallback proc(udata: pointer) =
testResult &= "1"
fut.addCallback proc(udata: pointer) =
testResult &= "2"
fut.addCallback proc(udata: pointer) =
testResult &= "3"
fut.addCallback proc(udata: pointer) =
testResult &= "4"
fut.addCallback proc(udata: pointer) =
testResult &= "5"
discard waitFor(fut)
check:
fut.finished
testResult == "12345"
test "Future[T] callbacks not changing order after removeCallback()":
var testResult = ""
var fut = testFuture1()
proc cb1(udata: pointer) =
testResult &= "1"
proc cb2(udata: pointer) =
testResult &= "2"
proc cb3(udata: pointer) =
testResult &= "3"
proc cb4(udata: pointer) =
testResult &= "4"
proc cb5(udata: pointer) =
testResult &= "5"
fut.addCallback cb1
fut.addCallback cb2
fut.addCallback cb3
fut.addCallback cb4
fut.addCallback cb5
fut.removeCallback cb3
discard waitFor(fut)
check:
fut.finished
testResult == "1245"
asyncTest "wait(duration) test":
block:
## Test for not immediately completed future and timeout = -1
let res =
try:
discard await wait(testFuture1(), InfiniteDuration)
true
except CatchableError:
false
check res
block:
## Test for immediately completed future and timeout = -1
let res =
try:
discard await wait(testFuture2(), InfiniteDuration)
true
except CatchableError:
false
check res
block:
## Test for not immediately completed future and timeout = 0
let res =
try:
discard await wait(testFuture1(), 0.milliseconds)
false
except AsyncTimeoutError:
true
except CatchableError:
false
check res
block:
## Test for immediately completed future and timeout = 0
let res =
try:
discard await wait(testFuture2(), 0.milliseconds)
true
except CatchableError:
false
check res
block:
## Test for future which cannot be completed in timeout period
let res =
try:
discard await wait(testFuture100(), 50.milliseconds)
false
except AsyncTimeoutError:
true
except CatchableError:
false
check res
block:
## Test for future which will be completed before timeout exceeded.
let res =
try:
discard await wait(testFuture100(), 500.milliseconds)
true
except CatchableError:
false
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":
var completedFutures = 0
proc client1() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
proc client2() {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
proc client3() {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
proc client4() {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
proc client5() {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
proc client1f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client2f() {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client3f() {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client4f() {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client5f() {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
discard client1()
discard client1f()
discard client2()
discard client2f()
discard client3()
discard client3f()
discard client4()
discard client4f()
discard client5()
discard client5f()
await sleepAsync(1.seconds)
check completedFutures == 10
test "allFutures(zero) test":
var tseq = newSeq[Future[int]]()
var fut = allFutures(tseq)
check:
fut.finished
asyncTest "allFutures(varargs) test":
var completedFutures = 0
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
proc vlient4() {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
proc vlient5() {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
proc vlient1f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient2f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient3f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient4f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient5f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
result = 1
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
result = 1
proc client3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
result = 1
proc client4(): Future[int] {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
result = 1
proc client5(): Future[int] {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
result = 1
proc client1f(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client2f(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client3f(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client4f(): Future[int] {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client5f(): Future[int] {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
await allFutures(vlient1(), vlient2(), vlient3(), vlient4(), vlient5())
check completedFutures == 5
completedFutures = 0
await allFutures(vlient1(), vlient1f(), vlient2(), vlient2f(), vlient3(),
vlient3f(), vlient4(), vlient4f(), vlient5(), vlient5f())
check completedFutures == 10
completedFutures = 0
await allFutures(client1(), client2(), client3(), client4(), client5())
check completedFutures == 5
completedFutures = 0
await allFutures(client1(), client1f(), client2(), client2f(), client3(),
client3f(), client4(), client4f(), client5(), client5f())
check completedFutures == 10
asyncTest "allFutures(varargs) test":
var completedFutures = 0
var vfutures = newSeq[Future[void]]()
var nfutures = newSeq[Future[int]]()
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
proc vlient4() {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
proc vlient5() {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
proc vlient1f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient2f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient3f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient4f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc vlient5f() {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
result = 1
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
result = 1
proc client3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
result = 1
proc client4(): Future[int] {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
result = 1
proc client5(): Future[int] {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
result = 1
proc client1f(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client2f(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client3f(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client4f(): Future[int] {.async.} =
await sleepAsync(400.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
proc client5f(): Future[int] {.async.} =
await sleepAsync(500.milliseconds)
inc(completedFutures)
if true:
raise newException(ValueError, "")
vfutures.setLen(0)
for i in 0..<10:
vfutures.add(vlient1())
vfutures.add(vlient2())
vfutures.add(vlient3())
vfutures.add(vlient4())
vfutures.add(vlient5())
await allFutures(vfutures)
# 5 * 10 completed futures = 50
check completedFutures == 50
completedFutures = 0
vfutures.setLen(0)
for i in 0..<10:
vfutures.add(vlient1())
vfutures.add(vlient1f())
vfutures.add(vlient2())
vfutures.add(vlient2f())
vfutures.add(vlient3())
vfutures.add(vlient3f())
vfutures.add(vlient4())
vfutures.add(vlient4f())
vfutures.add(vlient5())
vfutures.add(vlient5f())
await allFutures(vfutures)
# 10 * 10 completed futures = 100
check completedFutures == 100
completedFutures = 0
nfutures.setLen(0)
for i in 0..<10:
nfutures.add(client1())
nfutures.add(client2())
nfutures.add(client3())
nfutures.add(client4())
nfutures.add(client5())
await allFutures(nfutures)
# 5 * 10 completed futures = 50
check completedFutures == 50
completedFutures = 0
nfutures.setLen(0)
for i in 0..<10:
nfutures.add(client1())
nfutures.add(client1f())
nfutures.add(client2())
nfutures.add(client2f())
nfutures.add(client3())
nfutures.add(client3f())
nfutures.add(client4())
nfutures.add(client4f())
nfutures.add(client5())
nfutures.add(client5f())
await allFutures(nfutures)
# 10 * 10 completed futures = 100
check completedFutures == 100
test "allFutures() already completed test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2(): Future[int] {.async.} =
if true:
raise newException(ValueError, "")
var fut = allFutures(client1(), client2())
check:
fut.finished()
not(fut.failed())
test "allFinished() already completed test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2(): Future[int] {.async.} =
if true:
raise newException(ValueError, "")
var fut = allFinished(client1(), client2())
check:
fut.finished()
not(fut.failed())
len(fut.read()) == 2
test "one(zero) test":
var tseq = newSeq[Future[int]]()
var fut = one(tseq)
check: fut.finished and fut.failed
asyncTest "one(varargs) test":
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
proc client1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 10
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
result = 20
proc client3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
result = 30
var fut11 = vlient1()
var fut12 = vlient2()
var fut13 = vlient3()
var res1 = await one(fut11, fut12, fut13)
var fut21 = vlient2()
var fut22 = vlient1()
var fut23 = vlient3()
var res2 = await one(fut21, fut22, fut23)
var fut31 = vlient3()
var fut32 = vlient2()
var fut33 = vlient1()
var res3 = await one(fut31, fut32, fut33)
check:
fut11 == res1
fut22 == res2
fut33 == res3
var cut11 = client1()
var cut12 = client2()
var cut13 = client3()
var res4 = await one(cut11, cut12, cut13)
var cut21 = client2()
var cut22 = client1()
var cut23 = client3()
var res5 = await one(cut21, cut22, cut23)
var cut31 = client3()
var cut32 = client2()
var cut33 = client1()
var res6 = await one(cut31, cut32, cut33)
check:
cut11 == res4
cut22 == res5
cut33 == res6
asyncTest "one(seq) test":
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
proc client1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 10
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
result = 20
proc client3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
result = 30
var v10 = vlient1()
var v11 = vlient2()
var v12 = vlient3()
var res1 = await one(@[v10, v11, v12])
var v20 = vlient2()
var v21 = vlient1()
var v22 = vlient3()
var res2 = await one(@[v20, v21, v22])
var v30 = vlient3()
var v31 = vlient2()
var v32 = vlient1()
var res3 = await one(@[v30, v31, v32])
check:
res1 == v10
res2 == v21
res3 == v32
var c10 = client1()
var c11 = client2()
var c12 = client3()
var res4 = await one(@[c10, c11, c12])
var c20 = client2()
var c21 = client1()
var c22 = client3()
var res5 = await one(@[c20, c21, c22])
var c30 = client3()
var c31 = client2()
var c32 = client1()
var res6 = await one(@[c30, c31, c32])
check:
res4 == c10
res5 == c21
res6 == c32
test "one(completed) test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2(): Future[int] {.async.} =
if true:
raise newException(ValueError, "")
proc client3(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 3
var f10 = client1()
var f20 = client2()
var f30 = client3()
var fut1 = one(f30, f10, f20)
var f11 = client1()
var f21 = client2()
var f31 = client3()
var fut2 = one(f31, f21, f11)
check:
fut1.finished()
not(fut1.failed())
fut1.read() == f10
fut2.finished()
not(fut2.failed())
fut2.read() == f21
asyncTest "one() exception effect":
proc checkraises() {.async: (raises: [CancelledError]).} =
let f = Future[void].Raising([CancelledError]).init()
f.complete()
one(f).cancelSoon()
await checkraises()
asyncTest "or() test":
proc client1() {.async.} =
await sleepAsync(200.milliseconds)
proc client2() {.async.} =
await sleepAsync(300.milliseconds)
proc client3() {.async.} =
await sleepAsync(100.milliseconds)
if true:
raise newException(ValueError, "")
proc client4() {.async.} =
await sleepAsync(400.milliseconds)
if true:
raise newException(IOError, "")
proc client5() {.async.} =
discard
proc client6() {.async.} =
if true:
raise newException(ValueError, "")
proc client7() {.async.} =
if true:
raise newException(IOError, "")
block:
let res =
try:
await client1() or client2()
true
except CatchableError:
false
check res
block:
let res =
try:
await client2() or client1()
true
except CatchableError:
false
check res
block:
let res =
try:
await client1() or client4()
true
except IOError:
false
except CatchableError:
false
check res
block:
let res =
try:
await client2() or client4()
true
except IOError:
false
except CatchableError:
false
check res
block:
let res =
try:
await client4() or client2()
true
except IOError:
false
except CatchableError:
false
check res
block:
let res =
try:
await client1() or client3()
false
except ValueError:
true
except CatchableError:
false
check res
block:
let res =
try:
await client3() or client1()
false
except ValueError:
true
except CatchableError:
false
check res
block:
let res =
try:
await client3() or client4()
false
except ValueError:
true
except CatchableError:
false
check res
block:
let res =
try:
await client4() or client3()
false
except ValueError:
true
except CatchableError:
false
check res
block:
let res =
try:
await client5() or client6()
true
except ValueError:
false
except CatchableError:
false
check res
block:
let res =
try:
await client6() or client5()
false
except ValueError:
true
except CatchableError:
false
check res
block:
let res =
try:
await client6() or client7()
false
except ValueError:
true
except IOError:
false
except CatchableError:
false
check res
block:
let res =
try:
await client7() or client6()
false
except ValueError:
false
except IOError:
true
except CatchableError:
false
check res
asyncTest "or() already completed test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2(): Future[int] {.async.} =
if true:
raise newException(ValueError, "")
proc client3(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 3
block:
let res =
try:
await client1() or client2()
true
except ValueError:
false
except CatchableError:
false
check res
block:
var fut1 = client1()
var fut2 = client3()
let res =
try:
await fut1 or fut2
true
except ValueError:
false
except CatchableError:
false
let discarded {.used.} = await fut2
check res
block:
var fut1 = client2()
var fut2 = client3()
let res =
try:
await fut1 or fut2
false
except ValueError:
true
except CatchableError:
false
let discarded {.used.} = await fut2
check res
block:
let res =
try:
await client2() or client1()
false
except ValueError:
true
except CatchableError:
false
check res
block:
var fut1 = client3()
var fut2 = client1()
let res =
try:
await fut1 or fut2
true
except ValueError:
false
except CatchableError:
false
let discarded {.used.} = await fut1
check res
block:
var fut1 = client3()
var fut2 = client2()
let res =
try:
await fut1 or fut2
false
except ValueError:
true
except CatchableError:
false
let discarded {.used.} = await fut1
check res
asyncTest "tryCancel() async procedure test":
var completed = 0
proc client1() {.async.} =
await sleepAsync(1.seconds)
inc(completed)
proc client2() {.async.} =
await client1()
inc(completed)
proc client3() {.async.} =
await client2()
inc(completed)
proc client4() {.async.} =
await client3()
inc(completed)
var fut = client4()
discard fut.tryCancel()
# Future must not be cancelled immediately, because it has many nested
# futures.
check:
not fut.cancelled()
expect(CancelledError):
await fut
check completed == 0
asyncTest "cancelAndWait() test":
var completed = 0
proc client1() {.async.} =
await sleepAsync(1.seconds)
inc(completed)
proc client2() {.async.} =
await client1()
inc(completed)
proc client3() {.async.} =
await client2()
inc(completed)
proc client4() {.async.} =
await client3()
inc(completed)
var fut = client4()
await cancelAndWait(fut)
check fut.cancelled()
asyncTest "Break cancellation propagation test":
var completed = 0
proc client1() {.async.} =
await sleepAsync(1.seconds)
inc(completed)
proc client2() {.async.} =
try:
await client1()
except CancelledError:
discard
inc(completed)
var fut1 = client2()
var fut2 = client2()
discard fut1.tryCancel()
await fut1
await cancelAndWait(fut2)
check:
not fut1.cancelled()
not fut2.cancelled()
completed == 2
asyncTest "Cancellation callback test":
var completed = 0
var cancelled = 0
proc client1(duration: Duration): Future[void] =
## Suspends the execution of the current async procedure for the next
## ``duration`` time.
var retFuture = newFuture[void]()
let moment = Moment.fromNow(duration)
proc completion(data: pointer) {.gcsafe.} =
inc(completed)
if not(retFuture.finished()):
retFuture.complete()
proc cancellation(udata: pointer) {.gcsafe.} =
inc(cancelled)
if not(retFuture.finished()):
removeTimer(moment, completion, cast[pointer](retFuture))
retFuture.cancelCallback = cancellation
discard setTimer(moment, completion, cast[pointer](retFuture))
return retFuture
var fut = client1(100.milliseconds)
discard fut.tryCancel()
await sleepAsync(500.milliseconds)
check:
fut.cancelled()
completed == 0
cancelled == 1
asyncTest "Cancellation wait(duration) 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.} =
try:
await wait(neverEndingProc(), 100.milliseconds)
except CancelledError:
waitProc1 = true
except CatchableError:
doAssert(false)
finally:
waitProc2 = true
var fut = waitProc()
await cancelAndWait(fut)
check:
fut.state == FutureState.Completed
neverFlag1 and neverFlag2 and neverFlag3 and waitProc1 and waitProc2
asyncTest "Cancellation withTimeout() 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
neverFlag1 = true
res
proc withTimeoutProc() {.async.} =
try:
discard await withTimeout(neverEndingProc(), 100.milliseconds)
doAssert(false)
except CancelledError:
waitProc1 = true
except CatchableError:
doAssert(false)
finally:
waitProc2 = true
var fut = withTimeoutProc()
await cancelAndWait(fut)
check:
fut.state == FutureState.Completed
neverFlag1 and neverFlag2 and neverFlag3 and waitProc1 and waitProc2
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]()
proc raceProc(): Future[void] {.async.} =
await someFut
var raceFut1 = raceProc()
someFut.complete()
await cancelAndWait(raceFut1)
someFut = newFuture[void]()
var raceFut2 = raceProc()
someFut.fail(newException(ValueError, ""))
await cancelAndWait(raceFut2)
someFut = newFuture[void]()
var raceFut3 = raceProc()
discard someFut.tryCancel()
await cancelAndWait(raceFut3)
check:
raceFut1.state == FutureState.Completed
raceFut2.state == FutureState.Failed
raceFut3.state == FutureState.Cancelled
asyncTest "asyncSpawn() test":
proc completeTask1() {.async.} =
discard
proc completeTask2() {.async.} =
await sleepAsync(100.milliseconds)
proc errorTask() {.async.} =
if true:
raise newException(ValueError, "")
proc cancelTask() {.async.} =
await sleepAsync(10.seconds)
block:
let res =
try:
var fut1 = completeTask1()
var fut2 = completeTask2()
asyncSpawn fut1
asyncSpawn fut2
await sleepAsync(200.milliseconds)
if not(fut1.finished()) or not(fut2.finished()):
false
else:
if fut1.failed() or fut1.cancelled() or fut2.failed() or
fut2.cancelled():
false
else:
true
except CatchableError:
false
check res
block:
let res =
try:
asyncSpawn errorTask()
false
except FutureDefect:
true
except CatchableError:
false
check res
block:
let res =
try:
var fut = cancelTask()
await cancelAndWait(fut)
asyncSpawn fut
false
except FutureDefect:
true
except CatchableError:
false
check res
test "location test":
# WARNING: This test is very sensitive to line numbers and module name.
template start(): int =
instantiationInfo().line
const first = start()
proc macroFuture() {.async.} =
let someVar {.used.} = 5 # LINE POSITION 1
let someOtherVar {.used.} = 4
if true:
let otherVar {.used.} = 3 # LINE POSITION 2
template templateFuture(): untyped =
newFuture[void]("template")
proc procFuture(): Future[void] =
newFuture[void]("procedure") # LINE POSITION 5
var fut1 = macroFuture()
var fut2 = templateFuture() # LINE POSITION 3
var fut3 = procFuture()
fut2.complete() # LINE POSITION 4
fut3.complete() # LINE POSITION 6
{.push warning[Deprecated]: off.} # testing backwards compatibility interface
let loc10 = fut1.location[0]
let loc11 = fut1.location[1]
let loc20 = fut2.location[0]
let loc21 = fut2.location[1]
let loc30 = fut3.location[0]
let loc31 = fut3.location[1]
{.pop.}
proc chk(loc: ptr SrcLoc, file: string, line: int,
procedure: string): bool =
if len(procedure) == 0:
(loc.line == line) and ($loc.file == file)
else:
(loc.line == line) and ($loc.file == file) and
(loc.procedure == procedure)
check:
chk(loc10, "testfut.nim", first + 2, "macroFuture")
chk(loc11, "testfut.nim", first + 5, "")
chk(loc20, "testfut.nim", first + 14, "template")
chk(loc21, "testfut.nim", first + 17, "")
chk(loc30, "testfut.nim", first + 11, "procedure")
chk(loc31, "testfut.nim", first + 18, "")
asyncTest "withTimeout(fut) should wait cancellation test":
proc futureNeverEnds(): Future[void] =
newFuture[void]("neverending.future")
proc futureOneLevelMore() {.async.} =
await futureNeverEnds()
let res =
block:
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():
true
else:
false
except CatchableError:
false
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, 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
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":
var tseq = newSeq[FutureBase]()
var fut1 = race(tseq)
check:
# https://github.com/nim-lang/Nim/issues/22964
not compiles(block:
var fut2 = race())
not compiles(block:
var fut3 = race([]))
check:
fut1.failed()
# fut2.failed()
# fut3.failed()
asyncTest "race(varargs) test":
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
proc ilient1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 10
proc ilient2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
result = 20
proc ilient3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
result = 30
proc slient1(): Future[string] {.async.} =
await sleepAsync(100.milliseconds)
result = "sclient1"
proc slient2(): Future[string] {.async.} =
await sleepAsync(200.milliseconds)
result = "sclient2"
proc slient3(): Future[string] {.async.} =
await sleepAsync(300.milliseconds)
result = "sclient3"
var fut11 = vlient1()
var fut12 = ilient2()
var fut13 = slient3()
var res1 = await race(fut11, fut12, fut13)
check FutureBase(fut11) == res1
await allFutures(fut12, fut13)
var fut21 = vlient2()
var fut22 = ilient1()
var fut23 = slient3()
var res2 = await race(fut21, fut22, fut23)
check FutureBase(fut22) == res2
await allFutures(fut21, fut23)
var fut31 = vlient3()
var fut32 = ilient2()
var fut33 = slient1()
var res3 = await race(fut31, fut32, fut33)
check FutureBase(fut33) == res3
await allFutures(fut31, fut32)
var fut41 = vlient1()
var fut42 = slient2()
var fut43 = ilient3()
var res4 = await race(fut41, fut42, fut43)
check FutureBase(fut41) == res4
await allFutures(fut42, fut43)
asyncTest "race(seq) test":
proc vlient1() {.async.} =
await sleepAsync(100.milliseconds)
proc vlient2() {.async.} =
await sleepAsync(200.milliseconds)
proc vlient3() {.async.} =
await sleepAsync(300.milliseconds)
proc ilient1(): Future[int] {.async.} =
await sleepAsync(100.milliseconds)
result = 10
proc ilient2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
result = 20
proc ilient3(): Future[int] {.async.} =
await sleepAsync(300.milliseconds)
result = 30
proc slient1(): Future[string] {.async.} =
await sleepAsync(100.milliseconds)
result = "slient1"
proc slient2(): Future[string] {.async.} =
await sleepAsync(200.milliseconds)
result = "slient2"
proc slient3(): Future[string] {.async.} =
await sleepAsync(300.milliseconds)
result = "slient3"
var v10 = vlient1()
var v11 = ilient2()
var v12 = slient3()
var res1 = await race(@[FutureBase(v10), FutureBase(v11), FutureBase(v12)])
check res1 == FutureBase(v10)
await allFutures(v11, v12)
var v20 = vlient2()
var v21 = ilient1()
var v22 = slient3()
var res2 = await race(@[FutureBase(v20), FutureBase(v21), FutureBase(v22)])
check res2 == FutureBase(v21)
await allFutures(v20, v22)
var v30 = vlient3()
var v31 = ilient2()
var v32 = slient1()
var res3 = await race(@[FutureBase(v30), FutureBase(v31), FutureBase(v32)])
check res3 == FutureBase(v32)
await allFutures(v30, v31)
var v40 = vlient1()
var v41 = slient2()
var v42 = ilient3()
var res4 = await race(@[FutureBase(v40), FutureBase(v41), FutureBase(v42)])
check res4 == FutureBase(v40)
await allFutures(v41, v42)
asyncTest "race() already completed test":
proc client1(): Future[int] {.async.} =
result = 1
proc client2() {.async.} =
if true:
raise newException(ValueError, "")
proc client3(): Future[string] {.async.} =
await sleepAsync(100.milliseconds)
result = "client3"
var f10 = client1()
var f20 = client2()
var f30 = client3()
var fut1 = race(f30, f10, f20)
var f11 = client1()
var f21 = client2()
var f31 = client3()
var fut2 = race(f31, f21, f11)
check:
fut1.completed() and fut1.read() == FutureBase(f10)
fut2.completed() and fut2.read() == FutureBase(f21)
await allFutures(f20, f30, f11, f31)
asyncTest "race() cancellation test":
proc client1() {.async.} =
await sleepAsync(100.milliseconds)
proc client2(): Future[int] {.async.} =
await sleepAsync(200.milliseconds)
return 10
proc client3(): Future[string] {.async.} =
await sleepAsync(300.milliseconds)
return "client3"
var f1 = client1()
var f2 = client2()
var f3 = client3()
var fut = race(f1, f2, f3)
await cancelAndWait(fut)
check:
not(f1.finished())
not(f2.finished())
not(f3.finished())
await sleepAsync(500.milliseconds)
check:
f1.finished()
f2.finished()
f3.finished()
asyncTest "race() exception effect":
proc checkraises() {.async: (raises: [CancelledError]).} =
let f = Future[void].Raising([CancelledError]).init()
f.complete()
race(f).cancelSoon()
await checkraises()
test "Unsigned integer overflow test":
check:
0xFFFF_FFFF_FFFF_FFFF'u64 + 1'u64 == 0'u64
0xFFFF_FFFF'u32 + 1'u32 == 0'u32
when sizeof(uint) == 8:
check 0xFFFF_FFFF_FFFF_FFFF'u + 1'u == 0'u
else:
check 0xFFFF_FFFF'u + 1'u == 0'u
var v1_64 = 0xFFFF_FFFF_FFFF_FFFF'u64
var v2_64 = 0xFFFF_FFFF_FFFF_FFFF'u64
var v1_32 = 0xFFFF_FFFF'u32
var v2_32 = 0xFFFF_FFFF'u32
inc(v1_64)
inc(v1_32)
check:
v1_64 == 0'u64
v2_64 + 1'u64 == 0'u64
v1_32 == 0'u32
v2_32 + 1'u32 == 0'u32
when sizeof(uint) == 8:
var v1_u = 0xFFFF_FFFF_FFFF_FFFF'u
var v2_u = 0xFFFF_FFFF_FFFF_FFFF'u
inc(v1_u)
check:
v1_u == 0'u
v2_u + 1'u == 0'u
else:
var v1_u = 0xFFFF_FFFF'u
var v2_u = 0xFFFF_FFFF'u
inc(v1_u)
check:
v1_u == 0'u
v2_u + 1'u == 0'u
asyncTest "wait(duration) cancellation undefined behavior test #1":
proc testInnerFoo(fooFut: Future[void]): Future[TestFooConnection] {.
async.} =
await fooFut
return TestFooConnection()
proc testFoo(fooFut: Future[void]) {.async.} =
let connection =
try:
let res = await testInnerFoo(fooFut).wait(10.seconds)
Result[TestFooConnection, int].ok(res)
except CancelledError:
Result[TestFooConnection, int].err(0)
except CatchableError:
Result[TestFooConnection, int].err(1)
check connection.isOk()
var future = newFuture[void]("last.child.future")
var someFut = testFoo(future)
future.complete()
discard someFut.tryCancel()
await someFut
asyncTest "wait(duration) 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 connection =
try:
let res = await testMiddleFoo(fooFut).wait(10.seconds)
Result[TestFooConnection, int].ok(res)
except CancelledError:
Result[TestFooConnection, int].err(0)
except CatchableError:
Result[TestFooConnection, int].err(1)
check connection.isOk()
var future = newFuture[void]("last.child.future")
var someFut = testFoo(future)
future.complete()
discard someFut.tryCancel()
await someFut
asyncTest "wait(duration) 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.} =
await fooFut
return TestFooConnection()
proc testFoo(fooFut: Future[void]) {.async.} =
let connection =
try:
let
checkFut = testInnerFoo(fooFut)
res = await withTimeout(checkFut, 10.seconds)
if res:
Result[TestFooConnection, int].ok(checkFut.value)
else:
Result[TestFooConnection, int].err(0)
except CancelledError:
Result[TestFooConnection, int].err(1)
except CatchableError:
Result[TestFooConnection, int].err(2)
check connection.isOk()
var future = newFuture[void]("last.child.future")
var someFut = testFoo(future)
future.complete()
discard someFut.tryCancel()
await someFut
asyncTest "withTimeout() 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 connection =
try:
let
checkFut = testMiddleFoo(fooFut)
res = await withTimeout(checkFut, 10.seconds)
if res:
Result[TestFooConnection, int].ok(checkFut.value)
else:
Result[TestFooConnection, int].err(0)
except CancelledError:
Result[TestFooConnection, int].err(1)
except CatchableError:
Result[TestFooConnection, int].err(2)
check connection.isOk()
var future = newFuture[void]("last.child.future")
var someFut = testFoo(future)
future.complete()
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 "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":
proc testInnerFoo(fooFut: Future[void]) {.async.} =
await fooFut
proc testMiddleFoo(fooFut: Future[void]) {.async.} =
await testInnerFoo(fooFut)
proc testOuterFoo(fooFut: Future[void]) {.async.} =
await testMiddleFoo(fooFut)
block:
# Cancellation of pending Future
let future = newFuture[void]("last.child.pending.future")
await cancelAndWait(future)
check:
future.cancelled() == true
block:
# Cancellation of completed Future
let future = newFuture[void]("last.child.completed.future")
future.complete()
await cancelAndWait(future)
check:
future.cancelled() == false
future.completed() == true
block:
# Cancellation of failed Future
let future = newFuture[void]("last.child.failed.future")
future.fail(newException(ValueError, "ABCD"))
await cancelAndWait(future)
check:
future.cancelled() == false
future.failed() == true
block:
# Cancellation of already cancelled Future
let future = newFuture[void]("last.child.cancelled.future")
future.cancelAndSchedule()
await cancelAndWait(future)
check:
future.cancelled() == true
block:
# Cancellation of Pending->Pending->Pending->Pending sequence
let future = newFuture[void]("last.child.pending.future")
let testFut = testOuterFoo(future)
await cancelAndWait(testFut)
check:
testFut.cancelled() == true
block:
# Cancellation of Pending->Pending->Pending->Completed sequence
let future = newFuture[void]("last.child.completed.future")
let testFut = testOuterFoo(future)
future.complete()
await cancelAndWait(testFut)
check:
testFut.cancelled() == false
testFut.completed() == true
block:
# Cancellation of Pending->Pending->Pending->Failed sequence
let future = newFuture[void]("last.child.failed.future")
let testFut = testOuterFoo(future)
future.fail(newException(ValueError, "ABCD"))
await cancelAndWait(testFut)
check:
testFut.cancelled() == false
testFut.failed() == true
block:
# Cancellation of Pending->Pending->Pending->Cancelled sequence
let future = newFuture[void]("last.child.cancelled.future")
let testFut = testOuterFoo(future)
future.cancelAndSchedule()
await cancelAndWait(testFut)
check:
testFut.cancelled() == true
block:
# Cancellation of pending Future, when automatic scheduling disabled
let future = newFuture[void]("last.child.pending.future",
{FutureFlag.OwnCancelSchedule})
proc cancellation(udata: pointer) {.gcsafe.} =
discard
future.cancelCallback = cancellation
# Note, future will never be finished in such case, until we manually not
# finish it
let cancelFut = cancelAndWait(future)
await sleepAsync(100.milliseconds)
check:
cancelFut.finished() == false
future.cancelled() == false
# Now we manually changing Future's state, so `cancelAndWait` could
# finish
future.complete()
await cancelFut
check:
cancelFut.finished() == true
future.cancelled() == false
future.finished() == true
block:
# Cancellation of pending Future, which will fail Future on cancellation,
# when automatic scheduling disabled
let future = newFuture[void]("last.child.completed.future",
{FutureFlag.OwnCancelSchedule})
proc cancellation(udata: pointer) {.gcsafe.} =
future.complete()
future.cancelCallback = cancellation
# Note, future will never be finished in such case, until we manually not
# finish it
await cancelAndWait(future)
check:
future.cancelled() == false
future.completed() == true
block:
# Cancellation of pending Future, which will fail Future on cancellation,
# when automatic scheduling disabled
let future = newFuture[void]("last.child.failed.future",
{FutureFlag.OwnCancelSchedule})
proc cancellation(udata: pointer) {.gcsafe.} =
future.fail(newException(ValueError, "ABCD"))
future.cancelCallback = cancellation
# Note, future will never be finished in such case, until we manually not
# finish it
await cancelAndWait(future)
check:
future.cancelled() == false
future.failed() == true
block:
# Cancellation of pending Future, which will fail Future on cancellation,
# when automatic scheduling disabled
let future = newFuture[void]("last.child.cancelled.future",
{FutureFlag.OwnCancelSchedule})
proc cancellation(udata: pointer) {.gcsafe.} =
future.cancelAndSchedule()
future.cancelCallback = cancellation
# Note, future will never be finished in such case, until we manually not
# finish it
await cancelAndWait(future)
check:
future.cancelled() == true
block:
# Cancellation of pending Pending->Pending->Pending->Pending, when
# automatic scheduling disabled and Future do nothing in cancellation
# callback
let future = newFuture[void]("last.child.pending.future",
{FutureFlag.OwnCancelSchedule})
proc cancellation(udata: pointer) {.gcsafe.} =
discard
future.cancelCallback = cancellation
# Note, future will never be finished in such case, until we manually not
# finish it
let testFut = testOuterFoo(future)
let cancelFut = cancelAndWait(testFut)
await sleepAsync(100.milliseconds)
check:
cancelFut.finished() == false
testFut.cancelled() == false
future.cancelled() == false
# Now we manually changing Future's state, so `cancelAndWait` could
# finish
future.complete()
await cancelFut
check:
cancelFut.finished() == true
future.cancelled() == false
future.finished() == true
testFut.cancelled() == false
testFut.finished() == true
block:
# Cancellation of pending Pending->Pending->Pending->Pending, when
# automatic scheduling disabled and Future completes in cancellation
# callback
let future = newFuture[void]("last.child.pending.future",
{FutureFlag.OwnCancelSchedule})
proc cancellation(udata: pointer) {.gcsafe.} =
future.complete()
future.cancelCallback = cancellation
# Note, future will never be finished in such case, until we manually not
# finish it
let testFut = testOuterFoo(future)
await cancelAndWait(testFut)
await sleepAsync(100.milliseconds)
check:
testFut.cancelled() == false
testFut.finished() == true
future.cancelled() == false
future.finished() == true
block:
# Cancellation of pending Pending->Pending->Pending->Pending, when
# automatic scheduling disabled and Future fails in cancellation callback
let future = newFuture[void]("last.child.pending.future",
{FutureFlag.OwnCancelSchedule})
proc cancellation(udata: pointer) {.gcsafe.} =
future.fail(newException(ValueError, "ABCD"))
future.cancelCallback = cancellation
# Note, future will never be finished in such case, until we manually not
# finish it
let testFut = testOuterFoo(future)
await cancelAndWait(testFut)
await sleepAsync(100.milliseconds)
check:
testFut.cancelled() == false
testFut.failed() == true
future.cancelled() == false
future.failed() == true
block:
# Cancellation of pending Pending->Pending->Pending->Pending, when
# automatic scheduling disabled and Future fails in cancellation callback
let future = newFuture[void]("last.child.pending.future",
{FutureFlag.OwnCancelSchedule})
proc cancellation(udata: pointer) {.gcsafe.} =
future.cancelAndSchedule()
future.cancelCallback = cancellation
# Note, future will never be finished in such case, until we manually not
# finish it
let testFut = testOuterFoo(future)
await cancelAndWait(testFut)
await sleepAsync(100.milliseconds)
check:
testFut.cancelled() == true
future.cancelled() == true
test "Issue #334 test":
proc test(): bool =
var testres = ""
proc a() {.async.} =
try:
await sleepAsync(seconds(1))
except CatchableError as exc:
testres.add("A")
raise exc
proc b() {.async.} =
try:
await a()
except CatchableError as exc:
testres.add("B")
raise exc
proc c() {.async.} =
try:
echo $(await b().withTimeout(seconds(2)))
except CatchableError as exc:
testres.add("C")
raise exc
let x = c()
x.cancelSoon()
try:
waitFor x
except CatchableError:
testres.add("D")
testres.add("E")
waitFor sleepAsync(milliseconds(100))
testres == "ABCDE"
check test() == true
asyncTest "cancelAndWait() should be able to cancel test":
proc test1() {.async.} =
await noCancel sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
await sleepAsync(100.milliseconds)
proc test2() {.async.} =
await noCancel sleepAsync(100.milliseconds)
await sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
proc test3() {.async.} =
await sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
proc test4() {.async.} =
while true:
await noCancel sleepAsync(50.milliseconds)
await sleepAsync(0.milliseconds)
proc test5() {.async.} =
while true:
await sleepAsync(0.milliseconds)
await noCancel 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
asyncTest "cancelAndWait(varargs) should be able to cancel test":
proc test01() {.async.} =
await noCancel sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
await sleepAsync(100.milliseconds)
proc test02() {.async.} =
await noCancel sleepAsync(100.milliseconds)
await sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
proc test03() {.async.} =
await sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
proc test04() {.async.} =
while true:
await noCancel sleepAsync(50.milliseconds)
await sleepAsync(0.milliseconds)
proc test05() {.async.} =
while true:
await sleepAsync(0.milliseconds)
await noCancel sleepAsync(50.milliseconds)
proc test11() {.async: (raises: [CancelledError]).} =
await noCancel sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
await sleepAsync(100.milliseconds)
proc test12() {.async: (raises: [CancelledError]).} =
await noCancel sleepAsync(100.milliseconds)
await sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
proc test13() {.async: (raises: [CancelledError]).} =
await sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
await noCancel sleepAsync(100.milliseconds)
proc test14() {.async: (raises: [CancelledError]).} =
while true:
await noCancel sleepAsync(50.milliseconds)
await sleepAsync(0.milliseconds)
proc test15() {.async: (raises: [CancelledError]).} =
while true:
await sleepAsync(0.milliseconds)
await noCancel sleepAsync(50.milliseconds)
template runTest(N1, N2, N3: untyped) =
let
future01 = `test N2 N1`()
future02 = `test N2 N1`()
future03 = `test N2 N1`()
future04 = `test N2 N1`()
future05 = `test N2 N1`()
future06 = `test N2 N1`()
future07 = `test N2 N1`()
future08 = `test N2 N1`()
future09 = `test N2 N1`()
future10 = `test N2 N1`()
future11 = `test N2 N1`()
future12 = `test N2 N1`()
await allFutures(
cancelAndWait(future01, future02),
cancelAndWait(FutureBase(future03), FutureBase(future04)),
cancelAndWait([future05, future06]),
cancelAndWait([FutureBase(future07), FutureBase(future08)]),
cancelAndWait(@[future09, future10]),
cancelAndWait(@[FutureBase(future11), FutureBase(future12)])
)
let
future21 = `test N2 N1`()
future22 = `test N2 N1`()
future23 = `test N2 N1`()
future24 = `test N2 N1`()
future25 = `test N2 N1`()
future26 = `test N2 N1`()
future27 = `test N2 N1`()
future28 = `test N2 N1`()
future29 = `test N2 N1`()
future30 = `test N2 N1`()
future31 = `test N2 N1`()
future32 = `test N2 N1`()
await sleepAsync(`N3`)
await allFutures(
cancelAndWait(future21, future22),
cancelAndWait(FutureBase(future23), FutureBase(future24)),
cancelAndWait([future25, future26]),
cancelAndWait([FutureBase(future27), FutureBase(future28)]),
cancelAndWait(@[future29, future30]),
cancelAndWait(@[FutureBase(future31), FutureBase(future32)])
)
check:
future01.state == FutureState.Cancelled
future02.state == FutureState.Cancelled
future03.state == FutureState.Cancelled
future04.state == FutureState.Cancelled
future05.state == FutureState.Cancelled
future06.state == FutureState.Cancelled
future07.state == FutureState.Cancelled
future08.state == FutureState.Cancelled
future09.state == FutureState.Cancelled
future10.state == FutureState.Cancelled
future11.state == FutureState.Cancelled
future12.state == FutureState.Cancelled
future21.state == FutureState.Cancelled
future22.state == FutureState.Cancelled
future23.state == FutureState.Cancelled
future24.state == FutureState.Cancelled
future25.state == FutureState.Cancelled
future26.state == FutureState.Cancelled
future27.state == FutureState.Cancelled
future28.state == FutureState.Cancelled
future29.state == FutureState.Cancelled
future30.state == FutureState.Cancelled
future31.state == FutureState.Cancelled
future32.state == FutureState.Cancelled
runTest(1, 0, 10.milliseconds)
runTest(1, 1, 10.milliseconds)
runTest(2, 0, 10.milliseconds)
runTest(2, 1, 10.milliseconds)
runTest(3, 0, 10.milliseconds)
runTest(3, 1, 10.milliseconds)
runTest(4, 0, 333.milliseconds)
runTest(4, 1, 333.milliseconds)
runTest(5, 0, 333.milliseconds)
runTest(5, 1, 333.milliseconds)
asyncTest "cancelAndWait([]) on empty set returns completed Future test":
var
a0: array[0, Future[void]]
a1: array[0, Future[void].Raising([CancelledError])]
a2: seq[Future[void].Raising([CancelledError])]
a3: seq[Future[void]]
let
future1 = cancelAndWait()
future2 = cancelAndWait(a0)
future3 = cancelAndWait(a1)
future4 = cancelAndWait(a2)
future5 = cancelAndWait(a3)
check:
future1.finished() == true
future2.finished() == true
future3.finished() == true
future4.finished() == true
future5.finished() == true
asyncTest "cancelAndWait([]) should ignore finished futures test":
let
future0 =
Future[void].Raising([]).init("future0", {OwnCancelSchedule})
future1 =
Future[void].Raising([CancelledError]).init("future1")
future2 =
Future[void].Raising([CancelledError, ValueError]).init("future2")
future3 =
Future[string].Raising([]).init("future3", {OwnCancelSchedule})
future4 =
Future[string].Raising([CancelledError]).init("future4")
future5 =
Future[string].Raising([CancelledError, ValueError]).init("future5")
future6 =
newFuture[void]("future6")
future7 =
newFuture[void]("future7")
future8 =
newFuture[void]("future8")
future9 =
newFuture[string]("future9")
future10 =
newFuture[string]("future10")
future11 =
newFuture[string]("future11")
future0.complete()
check future1.tryCancel() == true
future2.fail(newException(ValueError, "Test Error"))
future3.complete("test")
check future4.tryCancel() == true
future5.fail(newException(ValueError, "Test Error"))
future6.complete()
check future7.tryCancel() == true
future8.fail(newException(ValueError, "Test Error"))
future9.complete("test")
check future10.tryCancel() == true
future11.fail(newException(ValueError, "Test Error"))
check:
cancelAndWait(future0, future1, future2).finished() == true
cancelAndWait(future3, future4, future5).finished() == true
cancelAndWait(future6, future7, future8).finished() == true
cancelAndWait(future9, future10, future11).finished() == true
cancelAndWait(future0, future1, future2,
future3, future4, future5,
future5, future7, future8,
future9, future10, future11).finished() == true
cancelAndWait([future0, future1, future2]).finished() == true
cancelAndWait([future3, future4]).finished() == true
cancelAndWait([future5]).finished() == true
cancelAndWait([future6, future7, future8]).finished() == true
cancelAndWait([future9, future10, future11]).finished() == true
cancelAndWait(@[future0, future1, future2]).finished() == true
cancelAndWait(@[future3, future4]).finished() == true
cancelAndWait(@[future5]).finished() == true
cancelAndWait(@[future6, future7, future8]).finished() == true
cancelAndWait(@[future9, future10, future11]).finished() == true
asyncTest "join() test":
proc joinFoo0(future: FutureBase) {.async.} =
await join(future)
proc joinFoo1(future: Future[void]) {.async.} =
await join(future)
proc joinFoo2(future: Future[void]) {.
async: (raises: [CancelledError]).} =
await join(future)
let
future0 = newFuture[void]()
future1 = newFuture[void]()
future2 = Future[void].Raising([CancelledError]).init()
let
resfut0 = joinFoo0(future0)
resfut1 = joinFoo1(future1)
resfut2 = joinFoo2(future2)
check:
resfut0.finished() == false
resfut1.finished() == false
resfut2.finished() == false
future0.complete()
future1.complete()
future2.complete()
let res =
try:
await noCancel allFutures(resfut0, resfut1, resfut2).wait(1.seconds)
true
except AsyncTimeoutError:
false
check:
res == true
resfut0.finished() == true
resfut1.finished() == true
resfut2.finished() == true
future0.finished() == true
future1.finished() == true
future2.finished() == true
asyncTest "join() cancellation test":
proc joinFoo0(future: FutureBase) {.async.} =
await join(future)
proc joinFoo1(future: Future[void]) {.async.} =
await join(future)
proc joinFoo2(future: Future[void]) {.
async: (raises: [CancelledError]).} =
await join(future)
let
future0 = newFuture[void]()
future1 = newFuture[void]()
future2 = Future[void].Raising([CancelledError]).init()
let
resfut0 = joinFoo0(future0)
resfut1 = joinFoo1(future1)
resfut2 = joinFoo2(future2)
check:
resfut0.finished() == false
resfut1.finished() == false
resfut2.finished() == false
let
cancelfut0 = cancelAndWait(resfut0)
cancelfut1 = cancelAndWait(resfut1)
cancelfut2 = cancelAndWait(resfut2)
let res =
try:
await noCancel allFutures(cancelfut0, cancelfut1,
cancelfut2).wait(1.seconds)
true
except AsyncTimeoutError:
false
check:
res == true
cancelfut0.finished() == true
cancelfut1.finished() == true
cancelfut2.finished() == true
resfut0.cancelled() == true
resfut1.cancelled() == true
resfut2.cancelled() == true
future0.finished() == false
future1.finished() == false
future2.finished() == false
future0.complete()
future1.complete()
future2.complete()
check:
future0.finished() == true
future1.finished() == true
future2.finished() == true
test "Sink with literals":
# https://github.com/nim-lang/Nim/issues/22175
let fut = newFuture[string]()
fut.complete("test")
check:
fut.value() == "test"
test "Raising type matching":
type X[E] = Future[void].Raising(E)
proc f(x: X) = discard
var v: Future[void].Raising([ValueError])
f(v)
type Object = object
# TODO cannot use X[[ValueError]] here..
field: Future[void].Raising([ValueError])
discard Object(field: v)
check:
not compiles(Future[void].Raising([42]))
not compiles(Future[void].Raising(42))
asyncTest "Timeout/cancellation race wait(duration) test":
proc raceTest(T: typedesc, itype: int) {.async.} =
let monitorFuture = newFuture[T]("monitor",
{FutureFlag.OwnCancelSchedule})
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 activation(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()
monitorFuture.cancelCallback = activation
let
testFut = raceProc2(monitorFuture)
waitFut = wait(testFut, 10.milliseconds)
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 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":
proc raceTest(T: typedesc, itype: int) {.async.} =
let monitorFuture = newFuture[T]("monitor",
{FutureFlag.OwnCancelSchedule})
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 activation(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()
monitorFuture.cancelCallback = activation
let
testFut = raceProc2(monitorFuture)
waitFut = withTimeout(testFut, 10.milliseconds)
when T is void:
let waitRes =
try:
await waitFut
except CancelledError:
false
except CatchableError:
false
if itype == 0:
check waitRes == true
elif itype == 1:
check waitRes == true
else:
check waitRes == false
elif T is int:
let waitRes =
try:
await waitFut
except CancelledError:
false
except CatchableError:
false
if itype == 0:
check waitRes == true
elif itype == 1:
check waitRes == true
else:
check waitRes == false
await raceTest(void, 0)
await raceTest(void, 1)
await raceTest(void, 2)
await raceTest(int, 0)
await raceTest(int, 1)
await raceTest(int, 2)