Last active
March 10, 2026 11:24
-
-
Save capocasa/52b994bf592bc40212ac9e152941763c to your computer and use it in GitHub Desktop.
Thread-safety corruption tests for Nim stdlib (non-reentrant C calls)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import std/[os, strutils, sequtils] | |
| let nim = getEnv("NIM", findExe("nim")) | |
| let musl = findExe("musl-gcc") | |
| let hasMusl = musl.len > 0 | |
| let tests = @[ | |
| ("tc_proto", "getProtoByName"), | |
| ("tc_servname", "getServByName"), | |
| ("tc_servport", "getServByPort"), | |
| ("tc_hostname", "getHostByName"), | |
| ("tc_localtime", "localtime"), | |
| ("tc_strerror", "strerror"), | |
| ] | |
| proc parseResult(output: string, exitCode: int): string = | |
| let lines = output.strip.splitLines | |
| if exitCode != 0: | |
| for line in lines: | |
| if "SIGSEGV" in line: return "SIGSEGV" | |
| if "unhandled exception" in line: | |
| return "ERROR" | |
| return "CRASH" | |
| for line in lines: | |
| if "corrupted" in line: | |
| for p in line.split(" "): | |
| if "/" in p: | |
| let nums = p.split("/") | |
| if nums[0] == "0": return "0" | |
| else: return p & " CORRUPTED" | |
| return "???" | |
| proc run(src, label, cc: string): string = | |
| echo " " & label & " (" & cc & ")" | |
| var cmd = nim & " c -r -d:release --threads:on --hints:off" | |
| if cc == "musl": | |
| cmd &= " --gcc.exe:musl-gcc --gcc.linkerexe:musl-gcc --passL:\"-static\"" | |
| cmd &= " " & src | |
| let ret = gorgeEx(cmd) | |
| result = parseResult(ret.output, ret.exitCode) | |
| type Row = object | |
| label, glibc, musl: string | |
| var rows: seq[Row] | |
| for (file, label) in tests: | |
| let src = thisDir() / file & ".nim" | |
| var r = Row(label: label) | |
| r.glibc = run(src, label, "glibc") | |
| if hasMusl: | |
| r.musl = run(src, label, "musl") | |
| else: | |
| r.musl = "-" | |
| rows.add r | |
| # table | |
| let w = rows.mapIt(it.glibc.len).foldl(max(a, b), 5) | |
| let wm = rows.mapIt(it.musl.len).foldl(max(a, b), 4) | |
| echo "" | |
| echo "Thread-safety corruption test (8 threads, 50k iterations each)" | |
| echo "" | |
| echo "Function".alignLeft(18) & "glibc".alignLeft(w + 3) & "musl" | |
| echo "-".repeat(17) & " " & "-".repeat(w + 2) & " " & "-".repeat(wm) | |
| for r in rows: | |
| echo r.label.alignLeft(18) & r.glibc.alignLeft(w + 3) & r.musl | |
| echo "" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import std/[nativesockets, atomics] | |
| const N = 50_000 | |
| var bad: Atomic[int]; var total: Atomic[int] | |
| proc w1(x: int) {.thread.} = | |
| let expected = getHostByName("localhost") | |
| for _ in 1..N: | |
| total.atomicInc() | |
| let he = getHostByName("localhost") | |
| if he.name != expected.name or he.addrList != expected.addrList: bad.atomicInc() | |
| var t: array[8, Thread[int]] | |
| for i in 0..7: createThread(t[i], w1, 0) | |
| joinThreads(t) | |
| echo "getHostByName: ", bad.load(), "/", total.load(), " corrupted" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import std/[times, atomics] | |
| const N = 50_000 | |
| var bad: Atomic[int]; var total: Atomic[int] | |
| proc w1(x: int) {.thread.} = | |
| let t = fromUnix(14515200) # 1970 | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if t.local().year != 1970: bad.atomicInc() | |
| proc w2(x: int) {.thread.} = | |
| let t = fromUnix(1907798400) # 2030 | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if t.local().year != 2030: bad.atomicInc() | |
| var t: array[8, Thread[int]] | |
| for i in 0..7: | |
| if i mod 2 == 0: createThread(t[i], w1, 0) | |
| else: createThread(t[i], w2, 0) | |
| joinThreads(t) | |
| echo "localtime: ", bad.load(), "/", total.load(), " corrupted" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import std/[nativesockets, atomics] | |
| const N = 50_000 | |
| var bad: Atomic[int]; var total: Atomic[int] | |
| proc w1(x: int) {.thread.} = | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if getProtoByName("tcp") != 6: bad.atomicInc() | |
| proc w2(x: int) {.thread.} = | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if getProtoByName("udp") != 17: bad.atomicInc() | |
| var t: array[8, Thread[int]] | |
| for i in 0..7: | |
| if i mod 2 == 0: createThread(t[i], w1, 0) | |
| else: createThread(t[i], w2, 0) | |
| joinThreads(t) | |
| echo "getProtoByName: ", bad.load(), "/", total.load(), " corrupted" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import std/[nativesockets, atomics] | |
| const N = 5_000 # ~10s on musl static | |
| var bad: Atomic[int]; var total: Atomic[int] | |
| proc w1(x: int) {.thread.} = | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if getServByName("http", "tcp").name != "http": bad.atomicInc() | |
| proc w2(x: int) {.thread.} = | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if getServByName("ssh", "tcp").name != "ssh": bad.atomicInc() | |
| var t: array[8, Thread[int]] | |
| for i in 0..7: | |
| if i mod 2 == 0: createThread(t[i], w1, 0) | |
| else: createThread(t[i], w2, 0) | |
| joinThreads(t) | |
| echo "getServByName: ", bad.load(), "/", total.load(), " corrupted" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import std/[nativesockets, atomics] | |
| const N = 1_000 | |
| var bad: Atomic[int]; var total: Atomic[int] | |
| proc w1(x: int) {.thread.} = | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if getServByPort(Port(htons(80)), "tcp").name != "http": bad.atomicInc() | |
| proc w2(x: int) {.thread.} = | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if getServByPort(Port(htons(22)), "tcp").name != "ssh": bad.atomicInc() | |
| var t: array[8, Thread[int]] | |
| for i in 0..7: | |
| if i mod 2 == 0: createThread(t[i], w1, 0) | |
| else: createThread(t[i], w2, 0) | |
| joinThreads(t) | |
| echo "getServByPort: ", bad.load(), "/", total.load(), " corrupted" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import std/[oserrors, atomics] | |
| const N = 50_000 | |
| var bad: Atomic[int]; var total: Atomic[int] | |
| proc w1(x: int) {.thread.} = | |
| let expected = $OSErrorCode(2) | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if $OSErrorCode(2) != expected: bad.atomicInc() | |
| proc w2(x: int) {.thread.} = | |
| let expected = $OSErrorCode(13) | |
| for _ in 1..N: | |
| total.atomicInc() | |
| if $OSErrorCode(13) != expected: bad.atomicInc() | |
| var t: array[8, Thread[int]] | |
| for i in 0..7: | |
| if i mod 2 == 0: createThread(t[i], w1, 0) | |
| else: createThread(t[i], w2, 0) | |
| joinThreads(t) | |
| echo "strerror: ", bad.load(), "/", total.load(), " corrupted" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment