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. |
4 | module sync |
5 | |
6 | import 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] |
17 | fn C.pthread_mutex_init(voidptr, voidptr) int |
18 | fn C.pthread_mutex_lock(voidptr) int |
19 | fn C.pthread_mutex_unlock(voidptr) int |
20 | fn C.pthread_mutex_destroy(voidptr) int |
21 | fn C.pthread_rwlockattr_init(voidptr) int |
22 | fn C.pthread_rwlockattr_setkind_np(voidptr, int) int |
23 | fn C.pthread_rwlockattr_setpshared(voidptr, int) int |
24 | fn C.pthread_rwlockattr_destroy(voidptr) int |
25 | fn C.pthread_rwlock_init(voidptr, voidptr) int |
26 | fn C.pthread_rwlock_rdlock(voidptr) int |
27 | fn C.pthread_rwlock_wrlock(voidptr) int |
28 | fn C.pthread_rwlock_unlock(voidptr) int |
29 | fn C.pthread_rwlock_destroy(voidptr) int |
30 | fn C.sem_init(voidptr, int, u32) int |
31 | fn C.sem_post(voidptr) int |
32 | fn C.sem_wait(voidptr) int |
33 | fn C.sem_trywait(voidptr) int |
34 | fn C.sem_timedwait(voidptr, voidptr) int |
35 | fn C.sem_destroy(voidptr) int |
36 | |
37 | [typedef] |
38 | struct C.pthread_mutex_t {} |
39 | |
40 | [typedef] |
41 | struct C.pthread_rwlock_t {} |
42 | |
43 | [typedef] |
44 | struct C.pthread_rwlockattr_t {} |
45 | |
46 | [typedef] |
47 | struct 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] |
51 | pub struct Mutex { |
52 | mutex C.pthread_mutex_t |
53 | } |
54 | |
55 | [heap] |
56 | pub struct RwMutex { |
57 | mutex C.pthread_rwlock_t |
58 | } |
59 | |
60 | struct RwMutexAttr { |
61 | attr C.pthread_rwlockattr_t |
62 | } |
63 | |
64 | [heap] |
65 | pub 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. |
70 | pub 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] |
79 | pub 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. |
84 | pub 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. |
92 | pub 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] |
105 | pub 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] |
112 | pub 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. |
118 | pub 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] |
132 | pub 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] |
143 | pub 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. |
149 | pub 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] |
162 | pub 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] |
172 | pub 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] |
179 | pub 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. |
185 | pub 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] |
195 | pub 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] |
204 | pub 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() . |
213 | pub 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 |
234 | pub 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. |
255 | pub 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. |
285 | pub fn (mut sem Semaphore) destroy() { |
286 | res := C.sem_destroy(&sem.sem) |
287 | if res != 0 { |
288 | cpanic(res) |
289 | } |
290 | } |