aboutsummaryrefslogtreecommitdiff
path: root/core/sync/sync_unix.odin
diff options
context:
space:
mode:
Diffstat (limited to 'core/sync/sync_unix.odin')
-rw-r--r--core/sync/sync_unix.odin99
1 files changed, 99 insertions, 0 deletions
diff --git a/core/sync/sync_unix.odin b/core/sync/sync_unix.odin
new file mode 100644
index 000000000..71ba54128
--- /dev/null
+++ b/core/sync/sync_unix.odin
@@ -0,0 +1,99 @@
+// +build linux, darwin
+package sync
+
+import "core:sys/unix"
+
+// A lock that can only be held by one thread at once.
+Mutex :: struct {
+ handle: unix.pthread_mutex_t,
+}
+
+// Blocks until signalled, and then lets past exactly
+// one thread.
+Condition :: struct {
+ handle: unix.pthread_cond_t,
+
+ // NOTE(tetra, 2019-11-11): Used to mimic the more sane behavior of Windows' AutoResetEvent.
+ // This means that you may signal the condition before anyone is waiting to cause the
+ // next thread that tries to wait to just pass by uninterrupted, without sleeping.
+ // Without this, signalling a condition will only wake up a thread which is already waiting,
+ // but not one that is about to wait, which can cause your program to become out of sync in
+ // ways that are hard to debug or fix.
+ flag: bool, // atomically mutated
+
+ mutex: Mutex,
+}
+
+
+
+mutex_init :: proc(m: ^Mutex) {
+ // NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the mutex.
+ attrs: unix.pthread_mutexattr_t;
+ assert(unix.pthread_mutexattr_init(&attrs) == 0);
+ defer unix.pthread_mutexattr_destroy(&attrs); // ignores destruction error
+
+ assert(unix.pthread_mutex_init(&m.handle, &attrs) == 0);
+}
+
+mutex_destroy :: proc(m: ^Mutex) {
+ assert(unix.pthread_mutex_destroy(&m.handle) == 0);
+ m.handle = {};
+}
+
+mutex_lock :: proc(m: ^Mutex) {
+ assert(unix.pthread_mutex_lock(&m.handle) == 0);
+}
+
+// Returns false if someone else holds the lock.
+mutex_try_lock :: proc(m: ^Mutex) -> bool {
+ return unix.pthread_mutex_trylock(&m.handle) == 0;
+}
+
+mutex_unlock :: proc(m: ^Mutex) {
+ assert(unix.pthread_mutex_unlock(&m.handle) == 0);
+}
+
+
+condition_init :: proc(c: ^Condition) {
+ // NOTE(tetra, 2019-11-01): POSIX OOM if we cannot init the attrs or the condition.
+ attrs: unix.pthread_condattr_t;
+ assert(unix.pthread_condattr_init(&attrs) == 0);
+ defer unix.pthread_condattr_destroy(&attrs); // ignores destruction error
+
+ assert(unix.pthread_cond_init(&c.handle, &attrs) == 0);
+
+ mutex_init(&c.mutex);
+ c.flag = false;
+}
+
+condition_destroy :: proc(c: ^Condition) {
+ assert(unix.pthread_cond_destroy(&c.handle) == 0);
+ mutex_destroy(&c.mutex);
+ c.handle = {};
+}
+
+// Awaken exactly one thread who is waiting on the condition.
+condition_signal :: proc(c: ^Condition) {
+ mutex_lock(&c.mutex);
+ defer mutex_unlock(&c.mutex);
+ atomic_swap(&c.flag, true, .Sequentially_Consistent);
+ assert(unix.pthread_cond_signal(&c.handle) == 0);
+}
+
+// Wait for the condition to be signalled.
+// Does not block if the condition has been signalled and no one
+// has waited on it yet.
+condition_wait_for :: proc(c: ^Condition) {
+ mutex_lock(&c.mutex);
+ defer mutex_unlock(&c.mutex);
+ // NOTE(tetra): If a thread comes by and steals the flag immediately after the signal occurs,
+ // the thread that gets signalled and wakes up, discovers that the flag was taken and goes
+ // back to sleep.
+ // Though this overall behavior is the most sane, there may be a better way to do this that means that
+ // the first thread to wait, gets the flag first.
+ if atomic_swap(&c.flag, false, .Sequentially_Consistent) do return;
+ for {
+ assert(unix.pthread_cond_wait(&c.handle, &c.mutex.handle) == 0);
+ if atomic_swap(&c.flag, false, .Sequentially_Consistent) do break;
+ }
+}