v / vlib / sync
Raw file | 290 loc (258 sloc) | 4.24 KB | Latest commit hash de136f6ba
1// Copyright (c) 2019-2023 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
16[trusted]
17fn C.pthread_mutex_init(voidptr, voidptr) int
18fn C.pthread_mutex_lock(voidptr) int
19fn C.pthread_mutex_unlock(voidptr) int
20fn C.pthread_mutex_destroy(voidptr) int
21fn C.pthread_rwlockattr_init(voidptr) int
22fn C.pthread_rwlockattr_setkind_np(voidptr, int) int
23fn C.pthread_rwlockattr_setpshared(voidptr, int) int
24fn C.pthread_rwlockattr_destroy(voidptr) int
25fn C.pthread_rwlock_init(voidptr, voidptr) int
26fn C.pthread_rwlock_rdlock(voidptr) int
27fn C.pthread_rwlock_wrlock(voidptr) int
28fn C.pthread_rwlock_unlock(voidptr) int
29fn C.pthread_rwlock_destroy(voidptr) int
30fn C.sem_init(voidptr, int, u32) int
31fn C.sem_post(voidptr) int
32fn C.sem_wait(voidptr) int
33fn C.sem_trywait(voidptr) int
34fn C.sem_timedwait(voidptr, voidptr) int
35fn C.sem_destroy(voidptr) int
36
37[typedef]
38struct C.pthread_mutex_t {}
39
40[typedef]
41struct C.pthread_rwlock_t {}
42
43[typedef]
44struct C.pthread_rwlockattr_t {}
45
46[typedef]
47struct C.sem_t {}
48
49// [init_with=new_mutex] // TODO: implement support for this struct attribute, and disallow Mutex{} from outside the sync.new_mutex() function.
50[heap]
51pub struct Mutex {
52 mutex C.pthread_mutex_t
53}
54
55[heap]
56pub struct RwMutex {
57 mutex C.pthread_rwlock_t
58}
59
60struct RwMutexAttr {
61 attr C.pthread_rwlockattr_t
62}
63
64[heap]
65pub struct Semaphore {
66 sem C.sem_t
67}
68
69// new_mutex creates and initialises a new mutex instance on the heap, then returns a pointer to it.
70pub fn new_mutex() &Mutex {
71 mut m := &Mutex{}
72 m.init()
73 return m
74}
75
76// init initialises the mutex. It should be called once before the mutex is used,
77// since it creates the associated resources needed for the mutex to work properly.
78[inline]
79pub fn (mut m Mutex) init() {
80 C.pthread_mutex_init(&m.mutex, C.NULL)
81}
82
83// new_rwmutex creates a new read/write mutex instance on the heap, and returns a pointer to it.
84pub fn new_rwmutex() &RwMutex {
85 mut m := &RwMutex{}
86 m.init()
87 return m
88}
89
90// init initialises the RwMutex instance. It should be called once before the rw mutex is used,
91// since it creates the associated resources needed for the mutex to work properly.
92pub fn (mut m RwMutex) init() {
93 a := RwMutexAttr{}
94 C.pthread_rwlockattr_init(&a.attr)
95 // Give writer priority over readers
96 C.pthread_rwlockattr_setkind_np(&a.attr, C.PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP)
97 C.pthread_rwlockattr_setpshared(&a.attr, C.PTHREAD_PROCESS_PRIVATE)
98 C.pthread_rwlock_init(&m.mutex, &a.attr)
99 C.pthread_rwlockattr_destroy(&a.attr) // destroy the attr when done
100}
101
102// @lock locks the mutex instance (`lock` is a keyword).
103// If the mutex was already locked, it will block, till it is unlocked.
104[inline]
105pub fn (mut m Mutex) @lock() {
106 C.pthread_mutex_lock(&m.mutex)
107}
108
109// unlock unlocks the mutex instance. The mutex is released, and one of
110// the other threads, that were blocked, because they called @lock can continue.
111[inline]
112pub fn (mut m Mutex) unlock() {
113 C.pthread_mutex_unlock(&m.mutex)
114}
115
116// destroy frees the resources associated with the mutex instance.
117// Note: the mutex itself is not freed.
118pub fn (mut m Mutex) destroy() {
119 res := C.pthread_mutex_destroy(&m.mutex)
120 if res != 0 {
121 cpanic(res)
122 }
123}
124
125// @rlock locks the given RwMutex instance for reading.
126// If the mutex was already locked, it will block, and will try to get the lock,
127// once the lock is released by another thread calling unlock.
128// Once it succeds, it returns.
129// Note: there may be several threads that are waiting for the same lock.
130// Note: RwMutex has separate read and write locks.
131[inline]
132pub fn (mut m RwMutex) @rlock() {
133 C.pthread_rwlock_rdlock(&m.mutex)
134}
135
136// @lock locks the given RwMutex instance for writing.
137// If the mutex was already locked, it will block, till it is unlocked,
138// then it will try to get the lock, and if it can, it will return, otherwise
139// it will continue waiting for the mutex to become unlocked.
140// Note: there may be several threads that are waiting for the same lock.
141// Note: RwMutex has separate read and write locks.
142[inline]
143pub fn (mut m RwMutex) @lock() {
144 C.pthread_rwlock_wrlock(&m.mutex)
145}
146
147// destroy frees the resources associated with the rwmutex instance.
148// Note: the mutex itself is not freed.
149pub fn (mut m RwMutex) destroy() {
150 res := C.pthread_rwlock_destroy(&m.mutex)
151 if res != 0 {
152 cpanic(res)
153 }
154}
155
156// runlock unlocks the RwMutex instance, locked for reading.
157// Note: Windows SRWLocks have different function to unlocking.
158// To have a common portable API, there are two methods for
159// unlocking here as well, even though that they do the same
160// on !windows platforms.
161[inline]
162pub fn (mut m RwMutex) runlock() {
163 C.pthread_rwlock_unlock(&m.mutex)
164}
165
166// unlock unlocks the RwMutex instance, locked for writing.
167// Note: Windows SRWLocks have different function to unlocking.
168// To have a common portable API, there are two methods for
169// unlocking here as well, even though that they do the same
170// on !windows platforms.
171[inline]
172pub fn (mut m RwMutex) unlock() {
173 C.pthread_rwlock_unlock(&m.mutex)
174}
175
176// new_semaphore creates a new initialised Semaphore instance on the heap, and returns a pointer to it.
177// The initial counter value of the semaphore is 0.
178[inline]
179pub fn new_semaphore() &Semaphore {
180 return new_semaphore_init(0)
181}
182
183// new_semaphore_init creates a new initialised Semaphore instance on the heap, and returns a pointer to it.
184// The `n` parameter can be used to set the initial counter value of the semaphore.
185pub fn new_semaphore_init(n u32) &Semaphore {
186 mut sem := &Semaphore{}
187 sem.init(n)
188 return sem
189}
190
191// init initialises the Semaphore instance with `n` as its initial counter value.
192// It should be called once before the semaphore is used, since it creates the associated
193// resources needed for the semaphore to work properly.
194[inline]
195pub fn (mut sem Semaphore) init(n u32) {
196 C.sem_init(&sem.sem, 0, n)
197}
198
199// post increases/unlocks the counter of the semaphore by 1.
200// If the resulting counter value is > 0, and if there is another thread waiting
201// on the semaphore, the waiting thread will decrement the counter by 1
202// (locking the semaphore), and then will continue running. See also .wait() .
203[inline]
204pub fn (mut sem Semaphore) post() {
205 C.sem_post(&sem.sem)
206}
207
208// wait will just decrement the semaphore count, if it was positive.
209// It it was not positive, it will waits for the semaphore count to reach a positive number.
210// When that happens, it will decrease the semaphore count (lock the semaphore), and will return.
211// In effect, it allows you to block threads, until the semaphore, is posted by another thread.
212// See also .post() .
213pub fn (mut sem Semaphore) wait() {
214 for {
215 if C.sem_wait(&sem.sem) == 0 {
216 return
217 }
218 e := C.errno
219 match e {
220 C.EINTR {
221 continue // interrupted by signal
222 }
223 else {
224 cpanic_errno()
225 }
226 }
227 }
228}
229
230// try_wait tries to decrease the semaphore count by 1, if it was positive.
231// If it succeeds in that, it returns true, otherwise it returns false.
232// try_wait should return as fast as possible so error handling is only
233// done when debugging
234pub fn (mut sem Semaphore) try_wait() bool {
235 $if !debug {
236 return C.sem_trywait(&sem.sem) == 0
237 } $else {
238 if C.sem_trywait(&sem.sem) != 0 {
239 e := C.errno
240 match e {
241 C.EAGAIN {
242 return false
243 }
244 else {
245 cpanic_errno()
246 }
247 }
248 }
249 return true
250 }
251}
252
253// timed_wait is similar to .wait(), but it also accepts a timeout duration,
254// thus it can return false early, if the timeout passed before the semaphore was posted.
255pub fn (mut sem Semaphore) timed_wait(timeout time.Duration) bool {
256 $if macos {
257 time.sleep(timeout)
258 return true
259 }
260 t_spec := timeout.timespec()
261 for {
262 $if !macos {
263 if C.sem_timedwait(&sem.sem, &t_spec) == 0 {
264 return true
265 }
266 }
267 e := C.errno
268 match e {
269 C.EINTR {
270 continue // interrupted by signal
271 }
272 C.ETIMEDOUT {
273 break
274 }
275 else {
276 cpanic(e)
277 }
278 }
279 }
280 return false
281}
282
283// destroy frees the resources associated with the Semaphore instance.
284// Note: the semaphore instance itself is not freed.
285pub fn (mut sem Semaphore) destroy() {
286 res := C.sem_destroy(&sem.sem)
287 if res != 0 {
288 cpanic(res)
289 }
290}