nim-chronos/tests/testthreadsync.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)