v2 / vlib / sync / sync.c.v
124 lines · 110 sloc · 3.33 KB · d1d43abf5caea56be93f4189766e060785c744d3
Raw
1module sync
2
3import time
4
5@[noreturn]
6fn cpanic(res int) {
7 panic(unsafe { tos_clone(&u8(C.strerror(res))) })
8}
9
10@[noreturn]
11fn cpanic_errno() {
12 cpanic(C.errno)
13}
14
15fn should_be_zero(res int) {
16 if res != 0 {
17 cpanic(res)
18 }
19}
20
21// SpinLock is a mutual exclusion lock that busy-waits (spins) when locked.
22// When one thread holds the lock, any other thread attempting to acquire it
23// will loop repeatedly until the lock becomes available.
24@[noinit]
25pub struct SpinLock {
26mut:
27 locked u8 // Lock state: 0 = unlocked, 1 = locked
28 padding [63]u8 // Cache line padding (fills to 64 bytes total)
29}
30
31// new_spin_lock creates and returns a new SpinLock instance initialized to unlocked state.
32pub fn new_spin_lock() &SpinLock {
33 mut the_lock := &SpinLock{
34 locked: 0
35 }
36 // Ensure initialization visibility across threads
37 C.atomic_thread_fence(C.memory_order_release)
38 $if valgrind ? {
39 C.ANNOTATE_RWLOCK_CREATE(&the_lock.locked)
40 }
41 return the_lock
42}
43
44// lock acquires the spin lock. If the lock is currently held by another thread,
45// this function will spin (busy-wait) until the lock becomes available.
46@[inline]
47pub fn (s &SpinLock) lock() {
48 // Expected value starts as unlocked (0)
49 mut expected := u8(0)
50 mut spin_count := 0
51 max_spins := 100
52 base_delay := 100 // nanosecond
53 max_delay := 10000 // nanoseconds (10μs)
54
55 // Busy-wait until lock is acquired
56 for {
57 // Attempt atomic compare-and-swap:
58 // Succeeds if current value matches expected (0),
59 // then swaps to locked (1)
60 if C.atomic_compare_exchange_weak_byte(&s.locked, &expected, 1) {
61 $if valgrind ? {
62 C.ANNOTATE_RWLOCK_ACQUIRED(&s.locked, 1) // 1 = write lock
63 }
64 // Prevent critical section reordering
65 C.atomic_thread_fence(C.memory_order_acquire)
66 return
67 }
68
69 spin_count++
70 // Exponential backoff after max_spins
71 if spin_count > max_spins {
72 // Calculate delay with cap: 100ns to 10μs
73 exponent := int_min(spin_count / max_spins, 10)
74 delay := int_min(base_delay * (1 << exponent), max_delay)
75 time.sleep(delay * time.nanosecond)
76 } else {
77 // Reduce power/bus contention during spinning
78 C.cpu_relax()
79 }
80
81 expected = 0
82 }
83}
84
85// try_lock try to lock the spin lock instance and return immediately.
86// If the spin lock was already locked, it will return false.
87@[inline]
88pub fn (s &SpinLock) try_lock() bool {
89 // First do a relaxed load to check if lock is free in order to prevent
90 // unnecessary cache misses if someone does while(!try_lock())
91 // TODO: make a `relaxed` load
92 if C.atomic_load_byte(&s.locked) == 0 {
93 mut expected := u8(0)
94 if C.atomic_compare_exchange_weak_byte(&s.locked, &expected, 1) {
95 $if valgrind ? {
96 C.ANNOTATE_RWLOCK_ACQUIRED(&s.locked, 1)
97 }
98 C.atomic_thread_fence(C.memory_order_acquire)
99 return true
100 }
101 }
102 return false
103}
104
105// unlock releases the spin lock, making it available to other threads.
106// IMPORTANT: Must only be called by the thread that currently holds the lock.
107@[inline]
108pub fn (s &SpinLock) unlock() {
109 $if valgrind ? {
110 C.ANNOTATE_RWLOCK_RELEASED(&s.locked, 1) // 1 = write lock
111 }
112 // Ensure critical section completes before release
113 C.atomic_thread_fence(C.memory_order_release)
114
115 // Atomically reset to unlocked state
116 C.atomic_store_byte(&s.locked, 0)
117}
118
119// destroy frees the resources associated with the spin lock instance.
120pub fn (s &SpinLock) destroy() {
121 $if valgrind ? {
122 C.ANNOTATE_RWLOCK_DESTROY(&s.locked)
123 }
124}
125