2019-02-06 14:49:11 +00:00
|
|
|
# Chronos Test Suite
|
|
|
|
# (c) Copyright 2018-Present
|
2018-05-22 08:51:11 +00:00
|
|
|
# Status Research & Development GmbH
|
|
|
|
#
|
|
|
|
# Licensed under either of
|
|
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
|
|
# MIT license (LICENSE-MIT)
|
exception tracking (#166)
* exception tracking
This PR adds minimal exception tracking to chronos, moving the goalpost
one step further.
In particular, it becomes invalid to raise exceptions from `callSoon`
callbacks: this is critical for writing correct error handling because
there's no reasonable way that a user of chronos can possibly _reason_
about exceptions coming out of there: the event loop will be in an
indeterminite state when the loop is executing an _random_ callback.
As expected, there are several issues in the error handling of chronos:
in particular, it will end up in an inconsistent internal state whenever
the selector loop operations fail, because the internal state update
functions are not written in an exception-safe way. This PR turns this
into a Defect, which probably is not the optimal way of handling things
- expect more work to be done here.
Some API have no way of reporting back errors to callers - for example,
when something fails in the accept loop, there's not much it can do, and
no way to report it back to the user of the API - this has been fixed
with the new accept flow - the old one should be deprecated.
Finally, there is information loss in the API: in composite operations
like `poll` and `waitFor` there's no way to differentiate internal
errors from user-level errors originating from callbacks.
* store `CatchableError` in future
* annotate proc's with correct raises information
* `selectors2` to avoid non-CatchableError IOSelectorsException
* `$` should never raise
* remove unnecessary gcsafe annotations
* fix exceptions leaking out of timer waits
* fix some imports
* functions must signal raising the union of all exceptions across all
platforms to enable cross-platform code
* switch to unittest2
* add `selectors2` which supercedes the std library version and fixes
several exception handling issues in there
* fixes
* docs, platform-independent eh specifiers for some functions
* add feature flag for strict exception mode
also bump version to 3.0.0 - _most_ existing code should be compatible
with this version of exception handling but some things might need
fixing - callbacks, existing raises specifications etc.
* fix AsyncCheck for non-void T
2021-03-24 09:08:33 +00:00
|
|
|
import unittest2
|
2019-02-06 14:49:11 +00:00
|
|
|
import ../chronos
|
2018-05-22 08:51:11 +00:00
|
|
|
|
2023-03-31 05:35:04 +00:00
|
|
|
{.used.}
|
2019-10-24 13:01:57 +00:00
|
|
|
|
2019-03-30 22:31:10 +00:00
|
|
|
suite "Asynchronous sync primitives test suite":
|
2020-03-10 14:33:09 +00:00
|
|
|
var testLockResult {.threadvar.}: string
|
|
|
|
var testEventResult {.threadvar.}: string
|
2019-03-30 22:31:10 +00:00
|
|
|
var testQueue1Result = 0
|
|
|
|
var testQueue2Result = 0
|
|
|
|
var testQueue3Result = 0
|
|
|
|
|
|
|
|
proc testLock(n: int, lock: AsyncLock) {.async.} =
|
|
|
|
await lock.acquire()
|
|
|
|
testLockResult = testLockResult & $n
|
|
|
|
lock.release()
|
|
|
|
|
|
|
|
proc test1(): string =
|
|
|
|
var lock = newAsyncLock()
|
2019-07-17 13:12:31 +00:00
|
|
|
waitFor lock.acquire()
|
2019-03-30 22:31:10 +00:00
|
|
|
discard testLock(0, lock)
|
|
|
|
discard testLock(1, lock)
|
|
|
|
discard testLock(2, lock)
|
|
|
|
discard testLock(3, lock)
|
|
|
|
discard testLock(4, lock)
|
|
|
|
discard testLock(5, lock)
|
|
|
|
discard testLock(6, lock)
|
|
|
|
discard testLock(7, lock)
|
|
|
|
discard testLock(8, lock)
|
|
|
|
discard testLock(9, lock)
|
|
|
|
lock.release()
|
|
|
|
## There must be exactly 20 poll() calls
|
|
|
|
for i in 0..<20:
|
|
|
|
poll()
|
|
|
|
result = testLockResult
|
|
|
|
|
2020-09-10 20:28:20 +00:00
|
|
|
proc testFlag(): Future[bool] {.async.} =
|
|
|
|
var lock = newAsyncLock()
|
|
|
|
var futs: array[4, Future[void]]
|
|
|
|
futs[0] = lock.acquire()
|
|
|
|
futs[1] = lock.acquire()
|
|
|
|
futs[2] = lock.acquire()
|
|
|
|
futs[3] = lock.acquire()
|
|
|
|
|
|
|
|
proc checkFlags(b0, b1, b2, b3, b4: bool): bool =
|
|
|
|
(lock.locked == b0) and
|
|
|
|
(futs[0].finished == b1) and (futs[1].finished == b2) and
|
|
|
|
(futs[2].finished == b3) and (futs[3].finished == b4)
|
|
|
|
|
|
|
|
if not(checkFlags(true, true, false, false ,false)):
|
|
|
|
return false
|
|
|
|
|
|
|
|
lock.release()
|
|
|
|
if not(checkFlags(true, true, false, false, false)):
|
|
|
|
return false
|
|
|
|
await sleepAsync(10.milliseconds)
|
|
|
|
if not(checkFlags(true, true, true, false, false)):
|
|
|
|
return false
|
|
|
|
|
|
|
|
lock.release()
|
|
|
|
if not(checkFlags(true, true, true, false, false)):
|
|
|
|
return false
|
|
|
|
await sleepAsync(10.milliseconds)
|
|
|
|
if not(checkFlags(true, true, true, true, false)):
|
|
|
|
return false
|
|
|
|
|
|
|
|
lock.release()
|
|
|
|
if not(checkFlags(true, true, true, true, false)):
|
|
|
|
return false
|
|
|
|
await sleepAsync(10.milliseconds)
|
|
|
|
if not(checkFlags(true, true, true, true, true)):
|
|
|
|
return false
|
|
|
|
|
|
|
|
lock.release()
|
|
|
|
if not(checkFlags(false, true, true, true, true)):
|
|
|
|
return false
|
|
|
|
await sleepAsync(10.milliseconds)
|
|
|
|
if not(checkFlags(false, true, true, true, true)):
|
|
|
|
return false
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
proc testNoAcquiredRelease(): Future[bool] {.async.} =
|
|
|
|
var lock = newAsyncLock()
|
|
|
|
var res = false
|
|
|
|
try:
|
|
|
|
lock.release()
|
|
|
|
except AsyncLockError:
|
|
|
|
res = true
|
|
|
|
return res
|
|
|
|
|
|
|
|
proc testDoubleRelease(): Future[bool] {.async.} =
|
|
|
|
var lock = newAsyncLock()
|
|
|
|
var fut0 = lock.acquire()
|
|
|
|
var fut1 = lock.acquire()
|
|
|
|
var res = false
|
|
|
|
asyncSpawn fut0
|
|
|
|
asyncSpawn fut1
|
|
|
|
lock.release()
|
|
|
|
try:
|
|
|
|
lock.release()
|
|
|
|
except AsyncLockError:
|
|
|
|
res = true
|
|
|
|
return res
|
|
|
|
|
2019-07-17 13:12:31 +00:00
|
|
|
proc testBehaviorLock(n1, n2, n3: Duration): Future[seq[int]] {.async.} =
|
|
|
|
var stripe: seq[int]
|
|
|
|
|
|
|
|
proc task(lock: AsyncLock, n: int, timeout: Duration) {.async.} =
|
|
|
|
await lock.acquire()
|
|
|
|
stripe.add(n * 10)
|
|
|
|
await sleepAsync(timeout)
|
|
|
|
lock.release()
|
|
|
|
await lock.acquire()
|
|
|
|
stripe.add(n * 10 + 1)
|
|
|
|
await sleepAsync(timeout)
|
|
|
|
lock.release()
|
|
|
|
|
|
|
|
var lock = newAsyncLock()
|
|
|
|
var fut1 = task(lock, 1, n1)
|
|
|
|
var fut2 = task(lock, 2, n2)
|
|
|
|
var fut3 = task(lock, 3, n3)
|
|
|
|
await allFutures(fut1, fut2, fut3)
|
|
|
|
result = stripe
|
|
|
|
|
|
|
|
proc testCancelLock(n1, n2, n3: Duration,
|
|
|
|
cancelIndex: int): Future[seq[int]] {.async.} =
|
|
|
|
var stripe: seq[int]
|
|
|
|
|
|
|
|
proc task(lock: AsyncLock, n: int, timeout: Duration) {.async.} =
|
|
|
|
await lock.acquire()
|
|
|
|
stripe.add(n * 10)
|
|
|
|
await sleepAsync(timeout)
|
|
|
|
lock.release()
|
2020-09-10 20:28:20 +00:00
|
|
|
|
2019-07-17 13:12:31 +00:00
|
|
|
await lock.acquire()
|
|
|
|
stripe.add(n * 10 + 1)
|
|
|
|
await sleepAsync(timeout)
|
|
|
|
lock.release()
|
|
|
|
|
|
|
|
var lock = newAsyncLock()
|
|
|
|
var fut1 = task(lock, 1, n1)
|
|
|
|
var fut2 = task(lock, 2, n2)
|
|
|
|
var fut3 = task(lock, 3, n3)
|
|
|
|
if cancelIndex == 2:
|
2023-09-15 16:38:39 +00:00
|
|
|
fut2.cancelSoon()
|
2019-07-17 13:12:31 +00:00
|
|
|
else:
|
2023-09-15 16:38:39 +00:00
|
|
|
fut3.cancelSoon()
|
2019-07-17 13:12:31 +00:00
|
|
|
await allFutures(fut1, fut2, fut3)
|
|
|
|
result = stripe
|
|
|
|
|
|
|
|
|
2019-03-30 22:31:10 +00:00
|
|
|
proc testEvent(n: int, ev: AsyncEvent) {.async.} =
|
|
|
|
await ev.wait()
|
|
|
|
testEventResult = testEventResult & $n
|
|
|
|
|
|
|
|
proc test2(): string =
|
|
|
|
var event = newAsyncEvent()
|
|
|
|
event.clear()
|
|
|
|
discard testEvent(0, event)
|
|
|
|
discard testEvent(1, event)
|
|
|
|
discard testEvent(2, event)
|
|
|
|
discard testEvent(3, event)
|
|
|
|
discard testEvent(4, event)
|
|
|
|
discard testEvent(5, event)
|
|
|
|
discard testEvent(6, event)
|
|
|
|
discard testEvent(7, event)
|
|
|
|
discard testEvent(8, event)
|
|
|
|
discard testEvent(9, event)
|
|
|
|
event.fire()
|
2020-09-10 08:39:10 +00:00
|
|
|
## There must be exactly 1 poll() call
|
2019-03-30 22:31:10 +00:00
|
|
|
poll()
|
|
|
|
result = testEventResult
|
|
|
|
|
|
|
|
proc task1(aq: AsyncQueue[int]) {.async.} =
|
|
|
|
var item1 = await aq.get()
|
|
|
|
var item2 = await aq.get()
|
|
|
|
testQueue1Result = item1 + item2
|
|
|
|
|
|
|
|
proc task2(aq: AsyncQueue[int]) {.async.} =
|
|
|
|
await aq.put(1000)
|
|
|
|
await aq.put(2000)
|
|
|
|
|
|
|
|
proc test3(): int =
|
|
|
|
var queue = newAsyncQueue[int](1)
|
|
|
|
discard task1(queue)
|
|
|
|
discard task2(queue)
|
|
|
|
## There must be exactly 2 poll() calls
|
|
|
|
poll()
|
|
|
|
poll()
|
|
|
|
result = testQueue1Result
|
|
|
|
|
|
|
|
const testsCount = 1000
|
|
|
|
const queueSize = 10
|
|
|
|
|
|
|
|
proc task3(aq: AsyncQueue[int]) {.async.} =
|
|
|
|
for i in 1..testsCount:
|
|
|
|
var item = await aq.get()
|
|
|
|
testQueue2Result -= item
|
|
|
|
|
|
|
|
proc task4(aq: AsyncQueue[int]) {.async.} =
|
|
|
|
for i in 1..testsCount:
|
|
|
|
await aq.put(i)
|
|
|
|
testQueue2Result += i
|
|
|
|
|
|
|
|
proc test4(): int =
|
|
|
|
var queue = newAsyncQueue[int](queueSize)
|
2019-07-04 12:13:39 +00:00
|
|
|
waitFor(allFutures(task3(queue), task4(queue)))
|
2019-03-30 22:31:10 +00:00
|
|
|
result = testQueue2Result
|
|
|
|
|
|
|
|
proc task51(aq: AsyncQueue[int]) {.async.} =
|
|
|
|
var item1 = await aq.popFirst()
|
|
|
|
var item2 = await aq.popLast()
|
|
|
|
var item3 = await aq.get()
|
|
|
|
testQueue3Result = item1 - item2 + item3
|
|
|
|
|
|
|
|
proc task52(aq: AsyncQueue[int]) {.async.} =
|
|
|
|
await aq.put(100)
|
|
|
|
await aq.addLast(1000)
|
|
|
|
await aq.addFirst(2000)
|
|
|
|
|
|
|
|
proc test5(): int =
|
|
|
|
var queue = newAsyncQueue[int](3)
|
|
|
|
discard task51(queue)
|
|
|
|
discard task52(queue)
|
|
|
|
poll()
|
2018-05-22 08:51:11 +00:00
|
|
|
poll()
|
2019-03-30 22:31:10 +00:00
|
|
|
result = testQueue3Result
|
|
|
|
|
|
|
|
proc test6(): bool =
|
|
|
|
var queue = newAsyncQueue[int]()
|
|
|
|
queue.putNoWait(1)
|
|
|
|
queue.putNoWait(2)
|
|
|
|
queue.putNoWait(3)
|
|
|
|
queue.putNoWait(4)
|
|
|
|
queue.putNoWait(5)
|
|
|
|
queue.clear()
|
|
|
|
result = (len(queue) == 0)
|
|
|
|
|
|
|
|
proc test7(): bool =
|
|
|
|
var queue = newAsyncQueue[int]()
|
|
|
|
var arr1 = @[1, 2, 3, 4, 5]
|
|
|
|
var arr2 = @[2, 2, 2, 2, 2]
|
|
|
|
var arr3 = @[1, 2, 3, 4, 5]
|
|
|
|
queue.putNoWait(1)
|
|
|
|
queue.putNoWait(2)
|
|
|
|
queue.putNoWait(3)
|
|
|
|
queue.putNoWait(4)
|
|
|
|
queue.putNoWait(5)
|
|
|
|
var index = 0
|
|
|
|
for item in queue.items():
|
|
|
|
result = (item == arr1[index])
|
|
|
|
inc(index)
|
|
|
|
|
|
|
|
if not result: return
|
|
|
|
|
|
|
|
queue[0] = 2
|
|
|
|
|
|
|
|
result = (queue[0] == 2)
|
|
|
|
|
|
|
|
if not result: return
|
|
|
|
|
|
|
|
for item in queue.mitems():
|
|
|
|
item = 2
|
|
|
|
|
|
|
|
index = 0
|
|
|
|
for item in queue.items():
|
|
|
|
result = (item == arr2[index])
|
|
|
|
inc(index)
|
|
|
|
|
|
|
|
if not result: return
|
|
|
|
|
|
|
|
queue[0] = 1
|
|
|
|
queue[1] = 2
|
|
|
|
queue[2] = 3
|
|
|
|
queue[3] = 4
|
|
|
|
queue[^1] = 5
|
|
|
|
|
|
|
|
for i, item in queue.pairs():
|
|
|
|
result = (item == arr3[i])
|
|
|
|
|
|
|
|
proc test8(): bool =
|
|
|
|
var q0 = newAsyncQueue[int]()
|
|
|
|
q0.putNoWait(1)
|
|
|
|
q0.putNoWait(2)
|
|
|
|
q0.putNoWait(3)
|
|
|
|
q0.putNoWait(4)
|
|
|
|
q0.putNoWait(5)
|
|
|
|
result = ($q0 == "[1, 2, 3, 4, 5]")
|
|
|
|
if not result: return
|
|
|
|
|
|
|
|
var q1 = newAsyncQueue[string]()
|
|
|
|
q1.putNoWait("1")
|
|
|
|
q1.putNoWait("2")
|
|
|
|
q1.putNoWait("3")
|
|
|
|
q1.putNoWait("4")
|
|
|
|
q1.putNoWait("5")
|
|
|
|
result = ($q1 == "[\"1\", \"2\", \"3\", \"4\", \"5\"]")
|
|
|
|
|
|
|
|
proc test9(): bool =
|
|
|
|
var q = newAsyncQueue[int]()
|
|
|
|
q.putNoWait(1)
|
|
|
|
q.putNoWait(2)
|
|
|
|
q.putNoWait(3)
|
|
|
|
q.putNoWait(4)
|
|
|
|
q.putNoWait(5)
|
|
|
|
result = (5 in q and not(6 in q))
|
|
|
|
|
|
|
|
test "AsyncLock() behavior test":
|
2019-07-17 13:12:31 +00:00
|
|
|
check:
|
|
|
|
test1() == "0123456789"
|
|
|
|
waitFor(testBehaviorLock(10.milliseconds,
|
|
|
|
20.milliseconds,
|
|
|
|
50.milliseconds)) == @[10, 20, 30, 11, 21, 31]
|
|
|
|
waitFor(testBehaviorLock(50.milliseconds,
|
|
|
|
20.milliseconds,
|
|
|
|
10.milliseconds)) == @[10, 20, 30, 11, 21, 31]
|
2020-09-10 20:28:20 +00:00
|
|
|
test "AsyncLock() cancellation test":
|
|
|
|
check:
|
2019-07-17 13:12:31 +00:00
|
|
|
waitFor(testCancelLock(10.milliseconds,
|
|
|
|
20.milliseconds,
|
|
|
|
50.milliseconds, 2)) == @[10, 30, 11, 31]
|
|
|
|
waitFor(testCancelLock(50.milliseconds,
|
|
|
|
20.milliseconds,
|
|
|
|
10.milliseconds, 3)) == @[10, 20, 11, 21]
|
2020-09-10 20:28:20 +00:00
|
|
|
test "AsyncLock() flag consistency test":
|
|
|
|
check waitFor(testFlag()) == true
|
|
|
|
test "AsyncLock() double release test":
|
|
|
|
check waitFor(testDoubleRelease()) == true
|
|
|
|
test "AsyncLock() non-acquired release test":
|
|
|
|
check waitFor(testNoAcquiredRelease()) == true
|
2019-03-30 22:31:10 +00:00
|
|
|
test "AsyncEvent() behavior test":
|
|
|
|
check test2() == "0123456789"
|
|
|
|
test "AsyncQueue() behavior test":
|
|
|
|
check test3() == 3000
|
|
|
|
test "AsyncQueue() many iterations test":
|
|
|
|
check test4() == 0
|
|
|
|
test "AsyncQueue() addLast/addFirst/popLast/popFirst test":
|
|
|
|
check test5() == 1100
|
|
|
|
test "AsyncQueue() clear test":
|
|
|
|
check test6() == true
|
|
|
|
test "AsyncQueue() iterators/assignments test":
|
|
|
|
check test7() == true
|
|
|
|
test "AsyncQueue() representation test":
|
|
|
|
check test8() == true
|
|
|
|
test "AsyncQueue() contains test":
|
|
|
|
check test9() == true
|
2021-09-15 13:55:15 +00:00
|
|
|
|
2022-06-15 21:51:21 +00:00
|
|
|
test "AsyncEventQueue() behavior test":
|
|
|
|
let eventQueue = newAsyncEventQueue[int]()
|
|
|
|
let key = eventQueue.register()
|
|
|
|
eventQueue.emit(100)
|
|
|
|
eventQueue.emit(200)
|
|
|
|
eventQueue.emit(300)
|
|
|
|
|
|
|
|
proc test1() =
|
|
|
|
let dataFut = eventQueue.waitEvents(key)
|
2021-09-15 13:55:15 +00:00
|
|
|
check:
|
2022-06-15 21:51:21 +00:00
|
|
|
dataFut.finished() == true
|
|
|
|
dataFut.read() == @[100, 200, 300]
|
|
|
|
|
|
|
|
proc test2() =
|
|
|
|
let dataFut = eventQueue.waitEvents(key)
|
|
|
|
check:
|
|
|
|
dataFut.finished() == false
|
|
|
|
eventQueue.emit(400)
|
|
|
|
eventQueue.emit(500)
|
|
|
|
poll()
|
|
|
|
check:
|
|
|
|
dataFut.finished() == true
|
|
|
|
dataFut.read() == @[400, 500]
|
|
|
|
|
|
|
|
test1()
|
|
|
|
test2()
|
|
|
|
waitFor eventQueue.closeWait()
|
|
|
|
|
|
|
|
test "AsyncEventQueue() concurrency test":
|
|
|
|
let eventQueue = newAsyncEventQueue[int]()
|
|
|
|
let key0 = eventQueue.register()
|
|
|
|
let key1 = eventQueue.register()
|
|
|
|
eventQueue.emit(100)
|
|
|
|
let key2 = eventQueue.register()
|
|
|
|
eventQueue.emit(200)
|
|
|
|
eventQueue.emit(300)
|
|
|
|
let key3 = eventQueue.register()
|
|
|
|
eventQueue.emit(400)
|
|
|
|
eventQueue.emit(500)
|
|
|
|
eventQueue.emit(600)
|
|
|
|
let key4 = eventQueue.register()
|
|
|
|
eventQueue.emit(700)
|
|
|
|
eventQueue.emit(800)
|
|
|
|
eventQueue.emit(900)
|
|
|
|
eventQueue.emit(1000)
|
|
|
|
let key5 = eventQueue.register()
|
|
|
|
let key6 = eventQueue.register()
|
|
|
|
|
|
|
|
let dataFut1 = eventQueue.waitEvents(key1)
|
|
|
|
let dataFut2 = eventQueue.waitEvents(key2)
|
|
|
|
let dataFut3 = eventQueue.waitEvents(key3)
|
|
|
|
let dataFut4 = eventQueue.waitEvents(key4)
|
|
|
|
let dataFut5 = eventQueue.waitEvents(key5)
|
|
|
|
let dataFut6 = eventQueue.waitEvents(key6)
|
2021-09-15 13:55:15 +00:00
|
|
|
check:
|
2022-06-15 21:51:21 +00:00
|
|
|
dataFut1.finished() == true
|
|
|
|
dataFut1.read() == @[100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]
|
|
|
|
dataFut2.finished() == true
|
|
|
|
dataFut2.read() == @[200, 300, 400, 500, 600, 700, 800, 900, 1000]
|
|
|
|
dataFut3.finished() == true
|
|
|
|
dataFut3.read() == @[400, 500, 600, 700, 800, 900, 1000]
|
|
|
|
dataFut4.finished() == true
|
|
|
|
dataFut4.read() == @[700, 800, 900, 1000]
|
|
|
|
dataFut5.finished() == false
|
|
|
|
dataFut6.finished() == false
|
|
|
|
|
|
|
|
eventQueue.emit(2000)
|
|
|
|
poll()
|
|
|
|
let dataFut0 = eventQueue.waitEvents(key0)
|
2021-09-15 13:55:15 +00:00
|
|
|
check:
|
2022-06-15 21:51:21 +00:00
|
|
|
dataFut5.finished() == true
|
|
|
|
dataFut5.read() == @[2000]
|
|
|
|
dataFut6.finished() == true
|
|
|
|
dataFut6.read() == @[2000]
|
|
|
|
dataFut0.finished() == true
|
|
|
|
dataFut0.read() == @[100, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
|
|
|
|
2000]
|
|
|
|
|
|
|
|
waitFor eventQueue.closeWait()
|
|
|
|
|
|
|
|
test "AsyncEventQueue() specific number test":
|
|
|
|
let eventQueue = newAsyncEventQueue[int]()
|
|
|
|
let key = eventQueue.register()
|
|
|
|
|
|
|
|
let dataFut1 = eventQueue.waitEvents(key, 1)
|
|
|
|
eventQueue.emit(100)
|
|
|
|
eventQueue.emit(200)
|
|
|
|
eventQueue.emit(300)
|
|
|
|
eventQueue.emit(400)
|
|
|
|
check dataFut1.finished() == false
|
|
|
|
poll()
|
2021-09-15 13:55:15 +00:00
|
|
|
check:
|
2022-06-15 21:51:21 +00:00
|
|
|
dataFut1.finished() == true
|
|
|
|
dataFut1.read() == @[100]
|
2021-09-15 13:55:15 +00:00
|
|
|
|
2022-06-15 21:51:21 +00:00
|
|
|
let dataFut2 = eventQueue.waitEvents(key, 2)
|
|
|
|
check:
|
|
|
|
dataFut2.finished() == true
|
|
|
|
dataFut2.read() == @[200, 300]
|
|
|
|
|
|
|
|
let dataFut3 = eventQueue.waitEvents(key, 5)
|
|
|
|
check dataFut3.finished() == false
|
|
|
|
eventQueue.emit(500)
|
|
|
|
eventQueue.emit(600)
|
|
|
|
eventQueue.emit(700)
|
|
|
|
eventQueue.emit(800)
|
|
|
|
check dataFut3.finished() == false
|
|
|
|
poll()
|
|
|
|
check:
|
|
|
|
dataFut3.finished() == true
|
|
|
|
dataFut3.read() == @[400, 500, 600, 700, 800]
|
|
|
|
|
|
|
|
let dataFut4 = eventQueue.waitEvents(key, -1)
|
|
|
|
check dataFut4.finished() == false
|
|
|
|
eventQueue.emit(900)
|
|
|
|
eventQueue.emit(1000)
|
|
|
|
eventQueue.emit(1100)
|
|
|
|
eventQueue.emit(1200)
|
|
|
|
eventQueue.emit(1300)
|
|
|
|
eventQueue.emit(1400)
|
|
|
|
eventQueue.emit(1500)
|
|
|
|
eventQueue.emit(1600)
|
|
|
|
check dataFut4.finished() == false
|
|
|
|
poll()
|
|
|
|
check:
|
|
|
|
dataFut4.finished() == true
|
|
|
|
dataFut4.read() == @[900, 1000, 1100, 1200, 1300, 1400, 1500, 1600]
|
2021-09-15 13:55:15 +00:00
|
|
|
|
2022-06-15 21:51:21 +00:00
|
|
|
waitFor eventQueue.closeWait()
|
2021-09-15 13:55:15 +00:00
|
|
|
|
2022-06-15 21:51:21 +00:00
|
|
|
test "AsyncEventQueue() register()/unregister() test":
|
|
|
|
var emptySeq: seq[int]
|
|
|
|
let eventQueue = newAsyncEventQueue[int]()
|
|
|
|
let key1 = eventQueue.register()
|
2021-09-15 13:55:15 +00:00
|
|
|
|
2022-06-15 21:51:21 +00:00
|
|
|
let dataFut1 = eventQueue.waitEvents(key1, 1)
|
|
|
|
check dataFut1.finished() == false
|
|
|
|
eventQueue.unregister(key1)
|
|
|
|
check dataFut1.finished() == false
|
|
|
|
poll()
|
|
|
|
check:
|
|
|
|
dataFut1.finished() == true
|
|
|
|
dataFut1.read() == emptySeq
|
|
|
|
|
|
|
|
let key2 = eventQueue.register()
|
|
|
|
let dataFut2 = eventQueue.waitEvents(key2, 5)
|
|
|
|
check dataFut2.finished() == false
|
|
|
|
eventQueue.emit(100)
|
|
|
|
eventQueue.emit(200)
|
|
|
|
eventQueue.emit(300)
|
|
|
|
eventQueue.emit(400)
|
|
|
|
eventQueue.emit(500)
|
|
|
|
check dataFut2.finished() == false
|
|
|
|
eventQueue.unregister(key2)
|
|
|
|
poll()
|
|
|
|
check:
|
|
|
|
dataFut2.finished() == true
|
|
|
|
dataFut2.read() == emptySeq
|
|
|
|
|
|
|
|
let key3 = eventQueue.register()
|
|
|
|
let dataFut3 = eventQueue.waitEvents(key3, 5)
|
|
|
|
check dataFut3.finished() == false
|
|
|
|
eventQueue.emit(100)
|
|
|
|
eventQueue.emit(200)
|
|
|
|
eventQueue.emit(300)
|
|
|
|
check dataFut3.finished() == false
|
|
|
|
poll()
|
|
|
|
eventQueue.unregister(key3)
|
|
|
|
eventQueue.emit(400)
|
|
|
|
check dataFut3.finished() == false
|
|
|
|
poll()
|
|
|
|
check:
|
|
|
|
dataFut3.finished() == true
|
|
|
|
dataFut3.read() == @[100, 200, 300]
|
|
|
|
|
|
|
|
waitFor eventQueue.closeWait()
|
|
|
|
|
|
|
|
test "AsyncEventQueue() garbage collection test":
|
|
|
|
let eventQueue = newAsyncEventQueue[int]()
|
|
|
|
let key1 = eventQueue.register()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.emit(100)
|
|
|
|
eventQueue.emit(200)
|
|
|
|
eventQueue.emit(300)
|
|
|
|
check len(eventQueue) == 3
|
|
|
|
let key2 = eventQueue.register()
|
|
|
|
eventQueue.emit(400)
|
|
|
|
eventQueue.emit(500)
|
|
|
|
eventQueue.emit(600)
|
|
|
|
eventQueue.emit(700)
|
|
|
|
check len(eventQueue) == 7
|
|
|
|
let key3 = eventQueue.register()
|
|
|
|
eventQueue.emit(800)
|
|
|
|
eventQueue.emit(900)
|
|
|
|
eventQueue.emit(1000)
|
|
|
|
eventQueue.emit(1100)
|
|
|
|
eventQueue.emit(1200)
|
|
|
|
check len(eventQueue) == 12
|
|
|
|
let dataFut1 = eventQueue.waitEvents(key1)
|
|
|
|
check:
|
|
|
|
dataFut1.finished() == true
|
|
|
|
dataFut1.read() == @[100, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
|
|
|
|
1100, 1200]
|
|
|
|
len(eventQueue) == 9
|
2022-05-20 09:57:37 +00:00
|
|
|
|
2022-06-15 21:51:21 +00:00
|
|
|
let dataFut3 = eventQueue.waitEvents(key3)
|
|
|
|
check:
|
|
|
|
dataFut3.finished() == true
|
|
|
|
dataFut3.read() == @[800, 900, 1000, 1100, 1200]
|
|
|
|
len(eventQueue) == 9
|
2022-05-20 09:57:37 +00:00
|
|
|
|
2022-06-15 21:51:21 +00:00
|
|
|
let dataFut2 = eventQueue.waitEvents(key2)
|
|
|
|
check:
|
|
|
|
dataFut2.finished() == true
|
|
|
|
dataFut2.read() == @[400, 500, 600, 700, 800, 900, 1000, 1100, 1200]
|
|
|
|
len(eventQueue) == 0
|
2022-05-20 09:57:37 +00:00
|
|
|
|
2022-06-15 21:51:21 +00:00
|
|
|
waitFor eventQueue.closeWait()
|
2022-05-20 09:57:37 +00:00
|
|
|
|
2022-06-15 21:51:21 +00:00
|
|
|
test "AsyncEventQueue() 1,000,000 of events to 10 clients test":
|
2022-05-20 09:57:37 +00:00
|
|
|
proc test() {.async.} =
|
2022-06-15 21:51:21 +00:00
|
|
|
let eventQueue = newAsyncEventQueue[int]()
|
|
|
|
var keys = @[
|
|
|
|
eventQueue.register(), eventQueue.register(),
|
|
|
|
eventQueue.register(), eventQueue.register(),
|
|
|
|
eventQueue.register(), eventQueue.register(),
|
|
|
|
eventQueue.register(), eventQueue.register(),
|
|
|
|
eventQueue.register(), eventQueue.register()
|
|
|
|
]
|
|
|
|
|
|
|
|
proc clientTask(queue: AsyncEventQueue[int],
|
|
|
|
key: EventQueueKey): Future[seq[int]] {.async.} =
|
|
|
|
var events: seq[int]
|
|
|
|
while true:
|
|
|
|
let res = await queue.waitEvents(key)
|
|
|
|
if len(res) == 0:
|
|
|
|
break
|
|
|
|
events.add(res)
|
|
|
|
queue.unregister(key)
|
|
|
|
return events
|
|
|
|
|
|
|
|
var futs = @[
|
|
|
|
clientTask(eventQueue, keys[0]), clientTask(eventQueue, keys[1]),
|
|
|
|
clientTask(eventQueue, keys[2]), clientTask(eventQueue, keys[3]),
|
|
|
|
clientTask(eventQueue, keys[4]), clientTask(eventQueue, keys[5]),
|
|
|
|
clientTask(eventQueue, keys[6]), clientTask(eventQueue, keys[7]),
|
|
|
|
clientTask(eventQueue, keys[8]), clientTask(eventQueue, keys[9])
|
|
|
|
]
|
|
|
|
|
|
|
|
for i in 1 .. 1_000_000:
|
|
|
|
if (i mod 1000) == 0:
|
|
|
|
# Give some CPU for clients.
|
|
|
|
await sleepAsync(0.milliseconds)
|
|
|
|
eventQueue.emit(i)
|
|
|
|
|
|
|
|
await eventQueue.closeWait()
|
|
|
|
|
|
|
|
await allFutures(futs)
|
|
|
|
for index in 0 ..< len(futs):
|
|
|
|
let fut = futs[index]
|
|
|
|
check fut.finished() == true
|
|
|
|
let data = fut.read()
|
|
|
|
var counter = 1
|
|
|
|
for item in data:
|
|
|
|
check item == counter
|
|
|
|
inc(counter)
|
|
|
|
futs[index] = nil
|
|
|
|
|
|
|
|
waitFor test()
|
|
|
|
|
|
|
|
test "AsyncEventQueue() one consumer limits test":
|
|
|
|
proc test() {.async.} =
|
|
|
|
let eventQueue = newAsyncEventQueue[int](4)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.emit(100)
|
|
|
|
eventQueue.emit(200)
|
|
|
|
eventQueue.emit(300)
|
|
|
|
eventQueue.emit(400)
|
|
|
|
# There no consumers, so all the items should be discarded
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
let key1 = eventQueue.register()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.emit(500)
|
|
|
|
eventQueue.emit(600)
|
|
|
|
eventQueue.emit(700)
|
|
|
|
eventQueue.emit(800)
|
|
|
|
# So exact `limit` number of items added, consumer should receive all of
|
|
|
|
# them.
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
let dataFut1 = eventQueue.waitEvents(key1)
|
|
|
|
check:
|
|
|
|
dataFut1.finished() == true
|
|
|
|
dataFut1.read() == @[500, 600, 700, 800]
|
|
|
|
len(eventQueue) == 0
|
|
|
|
|
|
|
|
eventQueue.emit(900)
|
|
|
|
eventQueue.emit(1000)
|
|
|
|
eventQueue.emit(1100)
|
|
|
|
eventQueue.emit(1200)
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
# Overfilling queue
|
|
|
|
eventQueue.emit(1300)
|
|
|
|
# Because overfill for single consumer happend, whole queue should become
|
|
|
|
# empty.
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.emit(1400)
|
|
|
|
eventQueue.emit(1500)
|
|
|
|
eventQueue.emit(1600)
|
|
|
|
eventQueue.emit(1700)
|
|
|
|
eventQueue.emit(1800)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
let errorFut1 = eventQueue.waitEvents(key1)
|
|
|
|
check errorFut1.finished() == true
|
|
|
|
let checkException =
|
|
|
|
try:
|
|
|
|
let res {.used.} = await errorFut1
|
|
|
|
false
|
|
|
|
except AsyncEventQueueFullError:
|
|
|
|
true
|
|
|
|
except CatchableError:
|
|
|
|
false
|
|
|
|
check checkException == true
|
|
|
|
# There should be no items because consumer was overflowed.
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key1)
|
|
|
|
# All items should be garbage collected after unregister.
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
await eventQueue.closeWait()
|
|
|
|
|
|
|
|
waitFor test()
|
|
|
|
|
|
|
|
test "AsyncEventQueue() many consumers limits test":
|
|
|
|
proc test() {.async.} =
|
|
|
|
let eventQueue = newAsyncEventQueue[int](4)
|
|
|
|
block:
|
|
|
|
let key1 = eventQueue.register()
|
|
|
|
eventQueue.emit(100)
|
|
|
|
check len(eventQueue) == 1
|
|
|
|
let key2 = eventQueue.register()
|
|
|
|
eventQueue.emit(200)
|
|
|
|
check len(eventQueue) == 2
|
|
|
|
let key3 = eventQueue.register()
|
|
|
|
eventQueue.emit(300)
|
|
|
|
check len(eventQueue) == 3
|
|
|
|
let key4 = eventQueue.register()
|
|
|
|
eventQueue.emit(400)
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
let key5 = eventQueue.register()
|
|
|
|
eventQueue.emit(500)
|
|
|
|
# At this point consumer with `key1` is overfilled, so after `emit()`
|
|
|
|
# queue length should be decreased by one item.
|
|
|
|
# So queue should look like this: [200, 300, 400, 500]
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
eventQueue.emit(600)
|
|
|
|
# At this point consumers with `key2` is overfilled, so after `emit()`
|
|
|
|
# queue length should be decreased by one item.
|
|
|
|
# So queue should look like this: [300, 400, 500, 600]
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
eventQueue.emit(700)
|
|
|
|
# At this point consumers with `key3` is overfilled, so after `emit()`
|
|
|
|
# queue length should be decreased by one item.
|
|
|
|
# So queue should look like this: [400, 500, 600, 700]
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
eventQueue.emit(800)
|
|
|
|
# At this point consumers with `key4` is overfilled, so after `emit()`
|
|
|
|
# queue length should be decreased by one item.
|
|
|
|
# So queue should look like this: [500, 600, 700, 800]
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
# Consumer with key5 is not overfilled.
|
|
|
|
let dataFut5 = eventQueue.waitEvents(key5)
|
|
|
|
check:
|
|
|
|
dataFut5.finished() == true
|
|
|
|
dataFut5.read() == @[500, 600, 700, 800]
|
|
|
|
# No more items should be left because all other consumers are overfilled.
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key5)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
let dataFut2 = eventQueue.waitEvents(key2)
|
|
|
|
check dataFut2.finished() == true
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = dataFut2.read()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key2)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
let dataFut4 = eventQueue.waitEvents(key4)
|
|
|
|
check dataFut4.finished() == true
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = dataFut4.read()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key4)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
let dataFut3 = eventQueue.waitEvents(key3)
|
|
|
|
check dataFut3.finished() == true
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = dataFut3.read()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key3)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
let dataFut1 = eventQueue.waitEvents(key1)
|
|
|
|
check dataFut1.finished() == true
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = dataFut1.read()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key1)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
block:
|
|
|
|
let key1 = eventQueue.register()
|
|
|
|
eventQueue.emit(100)
|
|
|
|
check len(eventQueue) == 1
|
|
|
|
let key2 = eventQueue.register()
|
|
|
|
eventQueue.emit(200)
|
|
|
|
check len(eventQueue) == 2
|
|
|
|
let key3 = eventQueue.register()
|
|
|
|
eventQueue.emit(300)
|
|
|
|
check len(eventQueue) == 3
|
|
|
|
let key4 = eventQueue.register()
|
|
|
|
eventQueue.emit(400)
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
let key5 = eventQueue.register()
|
|
|
|
eventQueue.emit(500)
|
|
|
|
# At this point consumer with `key1` is overfilled, so after `emit()`
|
|
|
|
# queue length should be decreased by one item.
|
|
|
|
# So queue should look like this: [200, 300, 400, 500]
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
eventQueue.emit(600)
|
|
|
|
# At this point consumer with `key2` is overfilled, so after `emit()`
|
|
|
|
# queue length should be decreased by one item.
|
|
|
|
# So queue should look like this: [300, 400, 500, 600]
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
eventQueue.emit(700)
|
|
|
|
# At this point consumer with `key3` is overfilled, so after `emit()`
|
|
|
|
# queue length should be decreased by one item.
|
|
|
|
# So queue should look like this: [400, 500, 600, 700]
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
eventQueue.emit(800)
|
|
|
|
# At this point consumer with `key4` is overfilled, so after `emit()`
|
|
|
|
# queue length should be decreased by one item.
|
|
|
|
# So queue should look like this: [500, 600, 700, 800]
|
|
|
|
check len(eventQueue) == 4
|
|
|
|
eventQueue.emit(900)
|
|
|
|
# At this point all consumers are overfilled, so after `emit()`
|
|
|
|
# queue length should become 0.
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.emit(1000)
|
|
|
|
eventQueue.emit(1100)
|
|
|
|
eventQueue.emit(1200)
|
|
|
|
eventQueue.emit(1300)
|
|
|
|
eventQueue.emit(1400)
|
|
|
|
eventQueue.emit(1500)
|
|
|
|
eventQueue.emit(1600)
|
|
|
|
eventQueue.emit(1700)
|
|
|
|
eventQueue.emit(1800)
|
|
|
|
eventQueue.emit(1900)
|
|
|
|
# No more events should be accepted.
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
let dataFut1 = eventQueue.waitEvents(key1)
|
|
|
|
check dataFut1.finished() == true
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = dataFut1.read()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key1)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
let dataFut2 = eventQueue.waitEvents(key2)
|
|
|
|
check dataFut2.finished() == true
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = dataFut2.read()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key2)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
let dataFut3 = eventQueue.waitEvents(key3)
|
|
|
|
check dataFut3.finished() == true
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = dataFut3.read()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key3)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
let dataFut4 = eventQueue.waitEvents(key4)
|
|
|
|
check dataFut4.finished() == true
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = dataFut4.read()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key4)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
|
|
|
|
let dataFut5 = eventQueue.waitEvents(key5)
|
|
|
|
check dataFut5.finished() == true
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = dataFut5.read()
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(key5)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
await eventQueue.closeWait()
|
|
|
|
|
|
|
|
waitFor test()
|
|
|
|
|
|
|
|
test "AsyncEventQueue() slow and fast consumer test":
|
|
|
|
proc test() {.async.} =
|
|
|
|
let eventQueue = newAsyncEventQueue[int](1)
|
|
|
|
let
|
|
|
|
fastConsumer = eventQueue.register()
|
|
|
|
slowConsumer = eventQueue.register()
|
|
|
|
slowFut = eventQueue.waitEvents(slowConsumer)
|
|
|
|
|
|
|
|
for i in 0 ..< 1000:
|
|
|
|
eventQueue.emit(i)
|
|
|
|
let fastData {.used.} = await eventQueue.waitEvents(fastConsumer)
|
|
|
|
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
await allFutures(slowFut)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
expect AsyncEventQueueFullError:
|
|
|
|
let res {.used.} = slowFut.read()
|
|
|
|
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(fastConsumer)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
eventQueue.unregister(slowConsumer)
|
|
|
|
check len(eventQueue) == 0
|
|
|
|
await eventQueue.closeWait()
|
|
|
|
|
|
|
|
waitFor test()
|