# 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)