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