Skip to content

Instantly share code, notes, and snippets.

@capocasa
Last active March 10, 2026 11:24
Show Gist options
  • Select an option

  • Save capocasa/52b994bf592bc40212ac9e152941763c to your computer and use it in GitHub Desktop.

Select an option

Save capocasa/52b994bf592bc40212ac9e152941763c to your computer and use it in GitHub Desktop.
Thread-safety corruption tests for Nim stdlib (non-reentrant C calls)
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 ""
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"
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"
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"
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"
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"
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