v / vlib / sync / sync_freebsd.c.v
339 lines · 302 sloc · 10.1 KB · 11f7d7c7ac379b36bb859f68d904f5de694c8f7b
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module sync
5
6import time
7
8// There's no additional linking (-lpthread) needed for Android.
9// See https://stackoverflow.com/a/31277163/1904615
10$if !android {
11 #flag -lpthread
12}
13
14#include <semaphore.h>
15#include <pthread_np.h>
16#include <errno.h>
17
18fn C.pthread_getthreadid_np() i32
19
20@[trusted]
21fn C.pthread_mutex_init(voidptr, voidptr) i32
22fn C.pthread_mutex_lock(voidptr) i32
23fn C.pthread_mutex_trylock(voidptr) i32
24fn C.pthread_mutex_unlock(voidptr) i32
25fn C.pthread_mutex_destroy(voidptr) i32
26fn C.pthread_rwlockattr_init(voidptr) i32
27fn C.pthread_rwlockattr_setkind_np(voidptr, i32) i32
28fn C.pthread_rwlockattr_destroy(voidptr) i32
29fn C.pthread_rwlock_init(voidptr, voidptr) i32
30fn C.pthread_rwlock_rdlock(voidptr) i32
31fn C.pthread_rwlock_wrlock(voidptr) i32
32fn C.pthread_rwlock_tryrdlock(voidptr) i32
33fn C.pthread_rwlock_trywrlock(voidptr) i32
34fn C.pthread_rwlock_unlock(voidptr) i32
35fn C.pthread_rwlock_destroy(voidptr) i32
36fn C.sem_init(voidptr, i32, u32) i32
37fn C.sem_post(voidptr) i32
38fn C.sem_wait(voidptr) i32
39fn C.sem_trywait(voidptr) i32
40fn C.sem_timedwait(voidptr, voidptr) i32
41fn C.sem_destroy(voidptr) i32
42
43pub struct C.pthread_mutex {}
44
45pub struct C.pthread_rwlock {}
46
47@[typedef]
48pub struct C.pthread_rwlockattr_t {}
49
50@[typedef]
51pub struct C.sem_t {}
52
53// [init_with=new_mutex] // TODO: implement support for this struct attribute, and disallow Mutex{} from outside the sync.new_mutex() function.
54@[heap]
55pub struct Mutex {
56 mutex &C.pthread_mutex = unsafe { nil }
57}
58
59@[heap]
60pub struct RwMutex {
61 mutex &C.pthread_rwlock = unsafe { nil }
62 inited u32
63 writer u32 // tid of current write-lock holder, 0 when none; used for self-deadlock detection
64}
65
66struct RwMutexAttr {
67 attr C.pthread_rwlockattr_t
68}
69
70@[heap]
71pub struct Semaphore {
72 sem C.sem_t
73}
74
75// new_mutex creates and initialises a new mutex instance on the heap, then returns a pointer to it.
76pub fn new_mutex() &Mutex {
77 mut m := &Mutex{}
78 m.init()
79 return m
80}
81
82// init initialises the mutex. It should be called once before the mutex is used,
83// since it creates the associated resources needed for the mutex to work properly.
84@[inline]
85pub fn (mut m Mutex) init() {
86 C.pthread_mutex_init(&m.mutex, C.NULL)
87}
88
89// new_rwmutex creates a new read/write mutex instance on the heap, and returns a pointer to it.
90pub fn new_rwmutex() &RwMutex {
91 mut m := &RwMutex{}
92 m.init()
93 return m
94}
95
96// init initialises the RwMutex instance. It should be called once before the rw mutex is used,
97// since it creates the associated resources needed for the mutex to work properly.
98pub fn (mut m RwMutex) init() {
99 a := RwMutexAttr{}
100 C.pthread_rwlockattr_init(&a.attr)
101 // Give writer priority over readers
102 C.pthread_rwlockattr_setkind_np(&a.attr, C.PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP)
103 C.pthread_rwlock_init(&m.mutex, &a.attr)
104 C.pthread_rwlockattr_destroy(&a.attr) // destroy the attr when done
105 C.atomic_store_u32(&m.inited, 1)
106}
107
108fn (mut m RwMutex) lazy_init() {
109 if C.atomic_load_u32(&m.inited) == 0 {
110 mut expected := u32(0)
111 if C.atomic_compare_exchange_strong_u32(&m.inited, &expected, 1) {
112 a := RwMutexAttr{}
113 C.pthread_rwlockattr_init(&a.attr)
114 C.pthread_rwlockattr_setkind_np(&a.attr, C.PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP)
115 C.pthread_rwlock_init(&m.mutex, &a.attr)
116 C.pthread_rwlockattr_destroy(&a.attr)
117 }
118 }
119}
120
121// lock locks the mutex instance (`lock` is a keyword).
122// If the mutex was already locked, it will block, till it is unlocked.
123@[inline]
124pub fn (mut m Mutex) lock() {
125 C.pthread_mutex_lock(&m.mutex)
126}
127
128// try_lock try to lock the mutex instance and return immediately.
129// If the mutex was already locked, it will return false.
130@[inline]
131pub fn (mut m Mutex) try_lock() bool {
132 return C.pthread_mutex_trylock(&m.mutex) == 0
133}
134
135// unlock unlocks the mutex instance. The mutex is released, and one of
136// the other threads, that were blocked, because they called lock can continue.
137@[inline]
138pub fn (mut m Mutex) unlock() {
139 C.pthread_mutex_unlock(&m.mutex)
140}
141
142// destroy frees the resources associated with the mutex instance.
143// Note: the mutex itself is not freed.
144pub fn (mut m Mutex) destroy() {
145 should_be_zero(C.pthread_mutex_destroy(&m.mutex))
146}
147
148// rlock locks the given RwMutex instance for reading.
149// If the mutex was already locked, it will block, and will try to get the lock,
150// once the lock is released by another thread calling unlock.
151// Once it succeds, it returns.
152// Note: there may be several threads that are waiting for the same lock.
153// Note: RwMutex has separate read and write locks.
154@[inline]
155pub fn (mut m RwMutex) rlock() {
156 m.lazy_init()
157 // FreeBSD libthr does not return EDEADLK on self-deadlock, it hangs forever.
158 // Match the Linux glibc nonrecursive-writer behavior by failing fast.
159 tid := u32(C.pthread_getthreadid_np())
160 if C.atomic_load_u32(&m.writer) == tid {
161 cpanic(C.EDEADLK)
162 }
163 should_be_zero(C.pthread_rwlock_rdlock(&m.mutex))
164}
165
166// lock locks the given RwMutex instance for writing.
167// If the mutex was already locked, it will block, till it is unlocked,
168// then it will try to get the lock, and if it can, it will return, otherwise
169// it will continue waiting for the mutex to become unlocked.
170// Note: there may be several threads that are waiting for the same lock.
171// Note: RwMutex has separate read and write locks.
172@[inline]
173pub fn (mut m RwMutex) lock() {
174 m.lazy_init()
175 tid := u32(C.pthread_getthreadid_np())
176 if C.atomic_load_u32(&m.writer) == tid {
177 cpanic(C.EDEADLK)
178 }
179 should_be_zero(C.pthread_rwlock_wrlock(&m.mutex))
180 C.atomic_store_u32(&m.writer, tid)
181}
182
183// try_rlock try to lock the given RwMutex instance for reading and return immediately.
184// If the mutex was already locked, it will return false.
185@[inline]
186pub fn (mut m RwMutex) try_rlock() bool {
187 return C.pthread_rwlock_tryrdlock(&m.mutex) == 0
188}
189
190// try_wlock try to lock the given RwMutex instance for writing and return immediately.
191// If the mutex was already locked, it will return false.
192@[inline]
193pub fn (mut m RwMutex) try_wlock() bool {
194 if C.pthread_rwlock_trywrlock(&m.mutex) == 0 {
195 C.atomic_store_u32(&m.writer, u32(C.pthread_getthreadid_np()))
196 return true
197 }
198 return false
199}
200
201// destroy frees the resources associated with the rwmutex instance.
202// Note: the mutex itself is not freed.
203pub fn (mut m RwMutex) destroy() {
204 should_be_zero(C.pthread_rwlock_destroy(&m.mutex))
205}
206
207// runlock unlocks the RwMutex instance, locked for reading.
208// Note: Windows SRWLocks have different function to unlocking.
209// To have a common portable API, there are two methods for
210// unlocking here as well, even though that they do the same
211// on !windows platforms.
212@[inline]
213pub fn (mut m RwMutex) runlock() {
214 C.pthread_rwlock_unlock(&m.mutex)
215}
216
217// unlock unlocks the RwMutex instance, locked for writing.
218// Note: Windows SRWLocks have different function to unlocking.
219// To have a common portable API, there are two methods for
220// unlocking here as well, even though that they do the same
221// on !windows platforms.
222@[inline]
223pub fn (mut m RwMutex) unlock() {
224 C.atomic_store_u32(&m.writer, 0)
225 C.pthread_rwlock_unlock(&m.mutex)
226}
227
228// new_semaphore creates a new initialised Semaphore instance on the heap, and returns a pointer to it.
229// The initial counter value of the semaphore is 0.
230@[inline]
231pub fn new_semaphore() &Semaphore {
232 return new_semaphore_init(0)
233}
234
235// new_semaphore_init creates a new initialised Semaphore instance on the heap, and returns a pointer to it.
236// The `n` parameter can be used to set the initial counter value of the semaphore.
237pub fn new_semaphore_init(n u32) &Semaphore {
238 mut sem := &Semaphore{}
239 sem.init(n)
240 return sem
241}
242
243// init initialises the Semaphore instance with `n` as its initial counter value.
244// It should be called once before the semaphore is used, since it creates the associated
245// resources needed for the semaphore to work properly.
246@[inline]
247pub fn (mut sem Semaphore) init(n u32) {
248 C.sem_init(&sem.sem, 0, n)
249}
250
251// post increases/unlocks the counter of the semaphore by 1.
252// If the resulting counter value is > 0, and if there is another thread waiting
253// on the semaphore, the waiting thread will decrement the counter by 1
254// (locking the semaphore), and then will continue running. See also .wait() .
255@[inline]
256pub fn (mut sem Semaphore) post() {
257 C.sem_post(&sem.sem)
258}
259
260// wait will just decrement the semaphore count, if it was positive.
261// It it was not positive, it will waits for the semaphore count to reach a positive number.
262// When that happens, it will decrease the semaphore count (lock the semaphore), and will return.
263// In effect, it allows you to block threads, until the semaphore, is posted by another thread.
264// See also .post() .
265pub fn (mut sem Semaphore) wait() {
266 for {
267 if C.sem_wait(&sem.sem) == 0 {
268 return
269 }
270 e := C.errno
271 match e {
272 C.EINTR {
273 continue // interrupted by signal
274 }
275 else {
276 cpanic_errno()
277 }
278 }
279 }
280}
281
282// try_wait tries to decrease the semaphore count by 1, if it was positive.
283// If it succeeds in that, it returns true, otherwise it returns false.
284// try_wait should return as fast as possible so error handling is only
285// done when debugging
286pub fn (mut sem Semaphore) try_wait() bool {
287 $if !debug {
288 return C.sem_trywait(&sem.sem) == 0
289 } $else {
290 if C.sem_trywait(&sem.sem) != 0 {
291 e := C.errno
292 match e {
293 C.EAGAIN {
294 return false
295 }
296 else {
297 cpanic_errno()
298 }
299 }
300 }
301 return true
302 }
303}
304
305// timed_wait is similar to .wait(), but it also accepts a timeout duration,
306// thus it can return false early, if the timeout passed before the semaphore was posted.
307pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
308 $if macos {
309 time.sleep(timeout)
310 return true
311 }
312 t_spec := timeout.timespec()
313 for {
314 $if !macos {
315 if C.sem_timedwait(&sem.sem, &t_spec) == 0 {
316 return true
317 }
318 }
319 e := C.errno
320 match e {
321 C.EINTR {
322 continue // interrupted by signal
323 }
324 C.ETIMEDOUT {
325 break
326 }
327 else {
328 cpanic(e)
329 }
330 }
331 }
332 return false
333}
334
335// destroy frees the resources associated with the Semaphore instance.
336// Note: the semaphore instance itself is not freed.
337pub fn (mut sem Semaphore) destroy() {
338 should_be_zero(C.sem_destroy(&sem.sem))
339}
340