385 lines
12 KiB
Nim
385 lines
12 KiB
Nim
# Chronos Test Suite
|
|
# (c) Copyright 2023-Present
|
|
# Status Research & Development GmbH
|
|
#
|
|
# Licensed under either of
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
# MIT license (LICENSE-MIT)
|
|
import std/[cpuinfo, locks, strutils]
|
|
import ../chronos/unittest2/asynctests
|
|
import ../chronos/threadsync
|
|
|
|
{.used.}
|
|
|
|
type
|
|
ThreadResult = object
|
|
value: int
|
|
|
|
ThreadResultPtr = ptr ThreadResult
|
|
|
|
LockPtr = ptr Lock
|
|
|
|
ThreadArg = object
|
|
signal: ThreadSignalPtr
|
|
retval: ThreadResultPtr
|
|
index: int
|
|
|
|
ThreadArg2 = object
|
|
signal1: ThreadSignalPtr
|
|
signal2: ThreadSignalPtr
|
|
retval: ThreadResultPtr
|
|
|
|
ThreadArg3 = object
|
|
lock: LockPtr
|
|
signal: ThreadSignalPtr
|
|
retval: ThreadResultPtr
|
|
index: int
|
|
|
|
WaitSendKind {.pure.} = enum
|
|
Sync, Async
|
|
|
|
const
|
|
TestsCount = when sizeof(int) == 8: 1000 else: 100
|
|
|
|
suite "Asynchronous multi-threading sync primitives test suite":
|
|
teardown:
|
|
checkLeaks()
|
|
|
|
proc setResult(thr: ThreadResultPtr, value: int) =
|
|
thr[].value = value
|
|
|
|
proc new(t: typedesc[ThreadResultPtr], value: int = 0): ThreadResultPtr =
|
|
var res = cast[ThreadResultPtr](allocShared0(sizeof(ThreadResult)))
|
|
res[].value = value
|
|
res
|
|
|
|
proc free(thr: ThreadResultPtr) =
|
|
doAssert(not(isNil(thr)))
|
|
deallocShared(thr)
|
|
|
|
let numProcs = countProcessors() * 2
|
|
|
|
template threadSignalTest(sendFlag, waitFlag: WaitSendKind) =
|
|
proc testSyncThread(arg: ThreadArg) {.thread.} =
|
|
let res = waitSync(arg.signal, 1500.milliseconds)
|
|
if res.isErr():
|
|
arg.retval.setResult(1)
|
|
else:
|
|
if res.get():
|
|
arg.retval.setResult(2)
|
|
else:
|
|
arg.retval.setResult(3)
|
|
|
|
proc testAsyncThread(arg: ThreadArg) {.thread.} =
|
|
proc testAsyncCode(arg: ThreadArg) {.async.} =
|
|
try:
|
|
await wait(arg.signal).wait(1500.milliseconds)
|
|
arg.retval.setResult(2)
|
|
except AsyncTimeoutError:
|
|
arg.retval.setResult(3)
|
|
except CatchableError:
|
|
arg.retval.setResult(1)
|
|
|
|
waitFor testAsyncCode(arg)
|
|
|
|
let signal = ThreadSignalPtr.new().tryGet()
|
|
var args: seq[ThreadArg]
|
|
var threads = newSeq[Thread[ThreadArg]](numProcs)
|
|
for i in 0 ..< numProcs:
|
|
let
|
|
res = ThreadResultPtr.new()
|
|
arg = ThreadArg(signal: signal, retval: res, index: i)
|
|
args.add(arg)
|
|
case waitFlag
|
|
of WaitSendKind.Sync:
|
|
createThread(threads[i], testSyncThread, arg)
|
|
of WaitSendKind.Async:
|
|
createThread(threads[i], testAsyncThread, arg)
|
|
|
|
await sleepAsync(500.milliseconds)
|
|
case sendFlag
|
|
of WaitSendKind.Sync:
|
|
check signal.fireSync().isOk()
|
|
of WaitSendKind.Async:
|
|
await signal.fire()
|
|
|
|
joinThreads(threads)
|
|
|
|
var ncheck: array[3, int]
|
|
for item in args:
|
|
if item.retval[].value == 1:
|
|
inc(ncheck[0])
|
|
elif item.retval[].value == 2:
|
|
inc(ncheck[1])
|
|
elif item.retval[].value == 3:
|
|
inc(ncheck[2])
|
|
free(item.retval)
|
|
check:
|
|
signal.close().isOk()
|
|
ncheck[0] == 0
|
|
ncheck[1] == 1
|
|
ncheck[2] == numProcs - 1
|
|
|
|
template threadSignalTest2(testsCount: int,
|
|
sendFlag, waitFlag: WaitSendKind) =
|
|
proc testSyncThread(arg: ThreadArg2) {.thread.} =
|
|
for i in 0 ..< testsCount:
|
|
block:
|
|
let res = waitSync(arg.signal1, 1500.milliseconds)
|
|
if res.isErr():
|
|
arg.retval.setResult(-1)
|
|
return
|
|
if not(res.get()):
|
|
arg.retval.setResult(-2)
|
|
return
|
|
|
|
block:
|
|
let res = arg.signal2.fireSync()
|
|
if res.isErr():
|
|
arg.retval.setResult(-3)
|
|
return
|
|
|
|
arg.retval.setResult(i + 1)
|
|
|
|
proc testAsyncThread(arg: ThreadArg2) {.thread.} =
|
|
proc testAsyncCode(arg: ThreadArg2) {.async.} =
|
|
for i in 0 ..< testsCount:
|
|
try:
|
|
await wait(arg.signal1).wait(1500.milliseconds)
|
|
except AsyncTimeoutError:
|
|
arg.retval.setResult(-2)
|
|
return
|
|
except AsyncError:
|
|
arg.retval.setResult(-1)
|
|
return
|
|
except CatchableError:
|
|
arg.retval.setResult(-3)
|
|
return
|
|
|
|
try:
|
|
await arg.signal2.fire()
|
|
except AsyncError:
|
|
arg.retval.setResult(-4)
|
|
return
|
|
except CatchableError:
|
|
arg.retval.setResult(-5)
|
|
return
|
|
|
|
arg.retval.setResult(i + 1)
|
|
|
|
waitFor testAsyncCode(arg)
|
|
|
|
let
|
|
signal1 = ThreadSignalPtr.new().tryGet()
|
|
signal2 = ThreadSignalPtr.new().tryGet()
|
|
retval = ThreadResultPtr.new()
|
|
arg = ThreadArg2(signal1: signal1, signal2: signal2, retval: retval)
|
|
var thread: Thread[ThreadArg2]
|
|
|
|
case waitFlag
|
|
of WaitSendKind.Sync:
|
|
createThread(thread, testSyncThread, arg)
|
|
of WaitSendKind.Async:
|
|
createThread(thread, testAsyncThread, arg)
|
|
|
|
let start = Moment.now()
|
|
for i in 0 ..< testsCount:
|
|
case sendFlag
|
|
of WaitSendKind.Sync:
|
|
block:
|
|
let res = signal1.fireSync()
|
|
check res.isOk()
|
|
block:
|
|
let res = waitSync(arg.signal2, 1500.milliseconds)
|
|
check:
|
|
res.isOk()
|
|
res.get() == true
|
|
of WaitSendKind.Async:
|
|
await arg.signal1.fire()
|
|
await wait(arg.signal2).wait(1500.milliseconds)
|
|
joinThreads(thread)
|
|
let finish = Moment.now()
|
|
let perf = (float64(nanoseconds(1.seconds)) /
|
|
float64(nanoseconds(finish - start))) * float64(testsCount)
|
|
echo "Switches tested: ", testsCount, ", elapsed time: ", (finish - start),
|
|
", performance = ", formatFloat(perf, ffDecimal, 4),
|
|
" switches/second"
|
|
|
|
check:
|
|
arg.retval[].value == testsCount
|
|
|
|
template threadSignalTest3(testsCount: int,
|
|
sendFlag, waitFlag: WaitSendKind) =
|
|
proc testSyncThread(arg: ThreadArg3) {.thread.} =
|
|
withLock(arg.lock[]):
|
|
let res = waitSync(arg.signal, 10.milliseconds)
|
|
if res.isErr():
|
|
arg.retval.setResult(1)
|
|
else:
|
|
if res.get():
|
|
arg.retval.setResult(2)
|
|
else:
|
|
arg.retval.setResult(3)
|
|
|
|
proc testAsyncThread(arg: ThreadArg3) {.thread.} =
|
|
proc testAsyncCode(arg: ThreadArg3) {.async.} =
|
|
withLock(arg.lock[]):
|
|
try:
|
|
await wait(arg.signal).wait(10.milliseconds)
|
|
arg.retval.setResult(2)
|
|
except AsyncTimeoutError:
|
|
arg.retval.setResult(3)
|
|
except CatchableError:
|
|
arg.retval.setResult(1)
|
|
|
|
waitFor testAsyncCode(arg)
|
|
|
|
let signal = ThreadSignalPtr.new().tryGet()
|
|
var args: seq[ThreadArg3]
|
|
var threads = newSeq[Thread[ThreadArg3]](numProcs)
|
|
var lockPtr = cast[LockPtr](allocShared0(sizeof(Lock)))
|
|
initLock(lockPtr[])
|
|
acquire(lockPtr[])
|
|
|
|
for i in 0 ..< numProcs:
|
|
let
|
|
res = ThreadResultPtr.new()
|
|
arg = ThreadArg3(signal: signal, retval: res, index: i, lock: lockPtr)
|
|
args.add(arg)
|
|
case waitFlag
|
|
of WaitSendKind.Sync:
|
|
createThread(threads[i], testSyncThread, arg)
|
|
of WaitSendKind.Async:
|
|
createThread(threads[i], testAsyncThread, arg)
|
|
|
|
await sleepAsync(500.milliseconds)
|
|
case sendFlag
|
|
of WaitSendKind.Sync:
|
|
for i in 0 ..< testsCount:
|
|
check signal.fireSync().isOk()
|
|
of WaitSendKind.Async:
|
|
for i in 0 ..< testsCount:
|
|
await signal.fire()
|
|
|
|
release(lockPtr[])
|
|
joinThreads(threads)
|
|
deinitLock(lockPtr[])
|
|
deallocShared(lockPtr)
|
|
|
|
var ncheck: array[3, int]
|
|
for item in args:
|
|
if item.retval[].value == 1:
|
|
inc(ncheck[0])
|
|
elif item.retval[].value == 2:
|
|
inc(ncheck[1])
|
|
elif item.retval[].value == 3:
|
|
inc(ncheck[2])
|
|
free(item.retval)
|
|
check:
|
|
signal.close().isOk()
|
|
ncheck[0] == 0
|
|
ncheck[1] == 1
|
|
ncheck[2] == numProcs - 1
|
|
|
|
template threadSignalTest4(testsCount: int,
|
|
sendFlag, waitFlag: WaitSendKind) =
|
|
let signal = ThreadSignalPtr.new().tryGet()
|
|
let start = Moment.now()
|
|
for i in 0 ..< testsCount:
|
|
case sendFlag
|
|
of WaitSendKind.Sync:
|
|
check signal.fireSync().isOk()
|
|
of WaitSendKind.Async:
|
|
await signal.fire()
|
|
|
|
case waitFlag
|
|
of WaitSendKind.Sync:
|
|
check waitSync(signal).isOk()
|
|
of WaitSendKind.Async:
|
|
await wait(signal)
|
|
let finish = Moment.now()
|
|
let perf = (float64(nanoseconds(1.seconds)) /
|
|
float64(nanoseconds(finish - start))) * float64(testsCount)
|
|
echo "Switches tested: ", testsCount, ", elapsed time: ", (finish - start),
|
|
", performance = ", formatFloat(perf, ffDecimal, 4),
|
|
" switches/second"
|
|
|
|
check:
|
|
signal.close.isOk()
|
|
|
|
asyncTest "ThreadSignal: Multiple [" & $numProcs &
|
|
"] threads waiting test [sync -> sync]":
|
|
threadSignalTest(WaitSendKind.Sync, WaitSendKind.Sync)
|
|
|
|
asyncTest "ThreadSignal: Multiple [" & $numProcs &
|
|
"] threads waiting test [async -> async]":
|
|
threadSignalTest(WaitSendKind.Async, WaitSendKind.Async)
|
|
|
|
asyncTest "ThreadSignal: Multiple [" & $numProcs &
|
|
"] threads waiting test [async -> sync]":
|
|
threadSignalTest(WaitSendKind.Async, WaitSendKind.Sync)
|
|
|
|
asyncTest "ThreadSignal: Multiple [" & $numProcs &
|
|
"] threads waiting test [sync -> async]":
|
|
threadSignalTest(WaitSendKind.Sync, WaitSendKind.Async)
|
|
|
|
asyncTest "ThreadSignal: Multiple thread switches [" & $TestsCount &
|
|
"] test [sync -> sync]":
|
|
when sizeof(int) == 8:
|
|
threadSignalTest2(TestsCount, WaitSendKind.Sync, WaitSendKind.Sync)
|
|
else:
|
|
skip()
|
|
|
|
asyncTest "ThreadSignal: Multiple thread switches [" & $TestsCount &
|
|
"] test [async -> async]":
|
|
when sizeof(int) == 8:
|
|
threadSignalTest2(TestsCount, WaitSendKind.Async, WaitSendKind.Async)
|
|
else:
|
|
skip()
|
|
|
|
asyncTest "ThreadSignal: Multiple thread switches [" & $TestsCount &
|
|
"] test [sync -> async]":
|
|
when sizeof(int) == 8:
|
|
threadSignalTest2(TestsCount, WaitSendKind.Sync, WaitSendKind.Async)
|
|
else:
|
|
skip()
|
|
|
|
asyncTest "ThreadSignal: Multiple thread switches [" & $TestsCount &
|
|
"] test [async -> sync]":
|
|
when sizeof(int) == 8:
|
|
threadSignalTest2(TestsCount, WaitSendKind.Async, WaitSendKind.Sync)
|
|
else:
|
|
skip()
|
|
|
|
asyncTest "ThreadSignal: Multiple signals [" & $TestsCount &
|
|
"] to multiple threads [" & $numProcs & "] test [sync -> sync]":
|
|
threadSignalTest3(TestsCount, WaitSendKind.Sync, WaitSendKind.Sync)
|
|
|
|
asyncTest "ThreadSignal: Multiple signals [" & $TestsCount &
|
|
"] to multiple threads [" & $numProcs & "] test [async -> async]":
|
|
threadSignalTest3(TestsCount, WaitSendKind.Async, WaitSendKind.Async)
|
|
|
|
asyncTest "ThreadSignal: Multiple signals [" & $TestsCount &
|
|
"] to multiple threads [" & $numProcs & "] test [sync -> async]":
|
|
threadSignalTest3(TestsCount, WaitSendKind.Sync, WaitSendKind.Async)
|
|
|
|
asyncTest "ThreadSignal: Multiple signals [" & $TestsCount &
|
|
"] to multiple threads [" & $numProcs & "] test [async -> sync]":
|
|
threadSignalTest3(TestsCount, WaitSendKind.Async, WaitSendKind.Sync)
|
|
|
|
asyncTest "ThreadSignal: Single threaded switches [" & $TestsCount &
|
|
"] test [sync -> sync]":
|
|
threadSignalTest4(TestsCount, WaitSendKind.Sync, WaitSendKind.Sync)
|
|
|
|
asyncTest "ThreadSignal: Single threaded switches [" & $TestsCount &
|
|
"] test [sync -> sync]":
|
|
threadSignalTest4(TestsCount, WaitSendKind.Async, WaitSendKind.Async)
|
|
|
|
asyncTest "ThreadSignal: Single threaded switches [" & $TestsCount &
|
|
"] test [sync -> async]":
|
|
threadSignalTest4(TestsCount, WaitSendKind.Sync, WaitSendKind.Async)
|
|
|
|
asyncTest "ThreadSignal: Single threaded switches [" & $TestsCount &
|
|
"] test [async -> sync]":
|
|
threadSignalTest4(TestsCount, WaitSendKind.Async, WaitSendKind.Sync)
|