mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-24 15:41:43 +00:00
YRC: tests and correctness proof
This commit is contained in:
98
tests/yrc/tyrc_cas_race.nim
Normal file
98
tests/yrc/tyrc_cas_race.nim
Normal file
@@ -0,0 +1,98 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "ok"
|
||||
valgrind: "leaks"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# Test concurrent traversal and mutation of a shared cyclic list under YRC.
|
||||
# Multiple threads race to replace nodes using a lock for synchronization.
|
||||
# This exercises YRC's write barrier and cycle collection under contention.
|
||||
|
||||
import std/locks
|
||||
|
||||
type
|
||||
Node = ref object
|
||||
value: int
|
||||
next: Node
|
||||
|
||||
proc newCycle(start, count: int): Node =
|
||||
result = Node(value: start)
|
||||
var cur = result
|
||||
for i in 1..<count:
|
||||
cur.next = Node(value: start + i)
|
||||
cur = cur.next
|
||||
cur.next = result # close the cycle
|
||||
|
||||
proc sumCycle(head: Node; count: int): int =
|
||||
var cur = head
|
||||
for i in 0..<count:
|
||||
result += cur.value
|
||||
cur = cur.next
|
||||
|
||||
const
|
||||
NumThreads = 4
|
||||
CycleLen = 6
|
||||
Iterations = 50
|
||||
|
||||
var
|
||||
shared: Node
|
||||
sharedLock: Lock
|
||||
threads: array[NumThreads, Thread[int]]
|
||||
wins: array[NumThreads, int]
|
||||
|
||||
proc worker(id: int) {.thread.} =
|
||||
{.cast(gcsafe).}:
|
||||
for iter in 0..<Iterations:
|
||||
# Under the lock, walk the shared list and replace a node's next pointer
|
||||
withLock sharedLock:
|
||||
var cur = shared
|
||||
if cur == nil: continue
|
||||
for step in 0..<CycleLen:
|
||||
let nxt = cur.next
|
||||
if nxt == nil: break
|
||||
# Replace cur.next with a fresh node that points to nxt.next
|
||||
let replacement = Node(value: id * 1000 + iter, next: nxt.next)
|
||||
cur.next = replacement
|
||||
wins[id] += 1
|
||||
cur = cur.next
|
||||
if cur == nil: break
|
||||
|
||||
# Outside the lock, create a local cycle to exercise the collector
|
||||
let local = newCycle(id * 100 + iter, 3)
|
||||
discard sumCycle(local, 3)
|
||||
|
||||
# Create initial shared cyclic list: 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 0
|
||||
initLock(sharedLock)
|
||||
shared = newCycle(0, CycleLen)
|
||||
|
||||
for i in 0..<NumThreads:
|
||||
createThread(threads[i], worker, i)
|
||||
|
||||
for i in 0..<NumThreads:
|
||||
joinThread(threads[i])
|
||||
|
||||
# Verify: the list is still traversable (no crashes, no dangling pointers).
|
||||
var totalWins = 0
|
||||
for i in 0..<NumThreads:
|
||||
totalWins += wins[i]
|
||||
|
||||
# Walk the list to verify it's still a valid cycle (or chain)
|
||||
var cur = shared
|
||||
var seen = 0
|
||||
var maxSteps = CycleLen * 3 # generous bound
|
||||
while cur != nil and seen < maxSteps:
|
||||
seen += 1
|
||||
cur = cur.next
|
||||
if cur == shared: break # completed the cycle
|
||||
|
||||
shared = nil
|
||||
GC_fullCollect()
|
||||
deinitLock(sharedLock)
|
||||
|
||||
if totalWins > 0 and seen > 0:
|
||||
echo "ok"
|
||||
else:
|
||||
echo "FAIL: wins=", totalWins, " seen=", seen
|
||||
74
tests/yrc/tyrc_shared_cycle.nim
Normal file
74
tests/yrc/tyrc_shared_cycle.nim
Normal file
@@ -0,0 +1,74 @@
|
||||
discard """
|
||||
cmd: "nim c --mm:yrc -d:useMalloc --threads:on $file"
|
||||
output: "ok"
|
||||
valgrind: "leaks"
|
||||
disabled: "windows"
|
||||
disabled: "freebsd"
|
||||
disabled: "openbsd"
|
||||
"""
|
||||
|
||||
# Test sharing a cyclic list between threads under YRC.
|
||||
|
||||
type
|
||||
Node = ref object
|
||||
value: int
|
||||
next: Node
|
||||
|
||||
proc newCycle(start, count: int): Node =
|
||||
## Create a cyclic linked list: start -> start+1 -> ... -> start+count-1 -> start
|
||||
result = Node(value: start)
|
||||
var cur = result
|
||||
for i in 1..<count:
|
||||
cur.next = Node(value: start + i)
|
||||
cur = cur.next
|
||||
cur.next = result # close the cycle
|
||||
|
||||
proc sumCycle(head: Node; count: int): int =
|
||||
var cur = head
|
||||
for i in 0..<count:
|
||||
result += cur.value
|
||||
cur = cur.next
|
||||
|
||||
const
|
||||
NumThreads = 4
|
||||
NodesPerCycle = 5
|
||||
|
||||
var
|
||||
shared: Node
|
||||
threads: array[NumThreads, Thread[int]]
|
||||
results: array[NumThreads, int]
|
||||
|
||||
proc worker(id: int) {.thread.} =
|
||||
# Each thread reads the shared cycle and computes a sum.
|
||||
# Also creates its own local cycle to exercise the collector.
|
||||
{.cast(gcsafe).}:
|
||||
let local = newCycle(id * 100, NodesPerCycle)
|
||||
let localSum = sumCycle(local, NodesPerCycle)
|
||||
|
||||
let sharedSum = sumCycle(shared, NodesPerCycle)
|
||||
results[id] = sharedSum + localSum
|
||||
|
||||
# Create a shared cyclic list: 0 -> 1 -> 2 -> 3 -> 4 -> 0
|
||||
shared = newCycle(0, NodesPerCycle)
|
||||
let expectedSharedSum = 0 + 1 + 2 + 3 + 4 # = 10
|
||||
|
||||
for i in 0..<NumThreads:
|
||||
createThread(threads[i], worker, i)
|
||||
|
||||
for i in 0..<NumThreads:
|
||||
joinThread(threads[i])
|
||||
|
||||
var allOk = true
|
||||
for i in 0..<NumThreads:
|
||||
let expectedLocal = i * 100 * NodesPerCycle + (NodesPerCycle * (NodesPerCycle - 1) div 2)
|
||||
# sum of id*100, id*100+1, ..., id*100+4
|
||||
let expected = expectedSharedSum + expectedLocal
|
||||
if results[i] != expected:
|
||||
echo "FAIL thread ", i, ": got ", results[i], " expected ", expected
|
||||
allOk = false
|
||||
|
||||
shared = nil # drop the shared cycle, collector should reclaim it
|
||||
GC_fullCollect()
|
||||
|
||||
if allOk:
|
||||
echo "ok"
|
||||
Reference in New Issue
Block a user