Index: src/untrusted/pthread/nc_rwlock.c |
diff --git a/src/untrusted/pthread/nc_rwlock.c b/src/untrusted/pthread/nc_rwlock.c |
new file mode 100644 |
index 0000000000000000000000000000000000000000..94a9efd425238987a39977c4e3a3548acc787adb |
--- /dev/null |
+++ b/src/untrusted/pthread/nc_rwlock.c |
@@ -0,0 +1,276 @@ |
+/* |
+ * Copyright (c) 2014 The Native Client Authors. All rights reserved. |
+ * Use of this source code is governed by a BSD-style license that can be |
+ * found in the LICENSE file. |
+ */ |
+ |
+/* |
+ * Native Client mutex implementation |
+ */ |
+ |
+#include <errno.h> |
+#include <limits.h> |
+ |
+#include "native_client/src/untrusted/nacl/nacl_irt.h" |
+#include "native_client/src/untrusted/pthread/pthread.h" |
+#include "native_client/src/untrusted/pthread/pthread_internal.h" |
+#include "native_client/src/untrusted/pthread/pthread_types.h" |
+ |
+/* |
+ * This implemtation is based on the one found in the bionic C library |
+ * which is part of the Android Open Source Project |
+ * (See: libc/bionic/pthread_rwlock.cpp) |
+ * The following steps were used to port it to NaCl: |
+ * - convert from C++ to C (mostly replace 'bool') |
+ * - convert futex calls to __libnacl_irt_futex |
+ * - remove unsuppored features such as PTHREAD_PROCESS_PRIVATE/SHARED. |
+ */ |
+ |
+/* |
+ * |
+ * Technical note: |
+ * |
+ * Possible states of a read/write lock: |
+ * |
+ * - no readers and no writer (unlocked) |
+ * - one or more readers sharing the lock at the same time (read-locked) |
+ * - one writer holding the lock (write-lock) |
+ * |
+ * Additionally: |
+ * - trying to get the write-lock while there are any readers blocks |
+ * - trying to get the read-lock while there is a writer blocks |
+ * - a single thread can acquire the lock multiple times in read mode |
+ * |
+ * - Posix states that behavior is undefined (may deadlock) if a thread tries |
+ * to acquire the lock |
+ * - in write mode while already holding the lock (whether in read or write |
+ * mode) |
+ * - in read mode while already holding the lock in write mode. |
+ * - This implementation will return EDEADLK in "write after write" and "read |
+ * after write" cases and will deadlock in write after read case. |
+ */ |
+ |
+int pthread_rwlockattr_init(pthread_rwlockattr_t *attr) { |
+ *attr = PTHREAD_PROCESS_PRIVATE; |
+ return 0; |
+} |
+ |
+int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr) { |
+ *attr = -1; |
+ return 0; |
+} |
+ |
+int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared) { |
+ switch (pshared) { |
+ case PTHREAD_PROCESS_PRIVATE: |
+ case PTHREAD_PROCESS_SHARED: |
+ *attr = pshared; |
+ return 0; |
+ default: |
+ return EINVAL; |
+ } |
+} |
+ |
+int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, |
+ int *pshared) { |
+ *pshared = *attr; |
+ return 0; |
+} |
+ |
+int pthread_rwlock_init(pthread_rwlock_t *rwlock, |
+ const pthread_rwlockattr_t *attr) { |
+ if (attr != NULL) { |
+ switch (*attr) { |
+ case PTHREAD_PROCESS_SHARED: |
+ case PTHREAD_PROCESS_PRIVATE: |
+ rwlock->attr= *attr; |
+ break; |
+ default: |
+ return EINVAL; |
+ } |
+ } |
+ |
+ rwlock->state = 0; |
+ rwlock->pending_readers = 0; |
+ rwlock->pending_writers = 0; |
+ rwlock->writer_thread_id = 0; |
+ |
+ return 0; |
+} |
+ |
+int pthread_rwlock_destroy(pthread_rwlock_t* rwlock) { |
+ if (rwlock->state != 0) { |
+ return EBUSY; |
+ } |
+ return 0; |
+} |
+ |
+static int __pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, |
+ const struct timespec *abs_timeout) { |
+ if (__predict_false(pthread_self() == rwlock->writer_thread_id)) { |
+ return EDEADLK; |
+ } |
+ |
+ int done = 0; |
+ do { |
+ /* |
+ * This is actually a race read as there's nothing that guarantees the |
+ * atomicity of integer reads / writes. However, in practice this "never" |
+ * happens so until we switch to C++11 this should work fine. The same |
+ * applies in the other places this idiom is used. |
+ */ |
+ int32_t cur_state = rwlock->state; /* C++11 relaxed atomic read */ |
+ if (__predict_true(cur_state >= 0)) { |
+ /* Add as an extra reader. */ |
+ done = __sync_bool_compare_and_swap(&rwlock->state, |
+ cur_state, |
+ cur_state + 1); |
+ } else { |
+ /* |
+ * Owner holds it in write mode, hang up. |
+ * To avoid losing wake ups the pending_readers update and the state read |
+ * should be sequentially consistent. (currently enforced by |
+ * __sync_fetch_and_add which creates a full barrier) |
+ */ |
+ __sync_fetch_and_add(&rwlock->pending_readers, 1); |
+ int ret = __libnacl_irt_futex.futex_wait_abs(&rwlock->state, |
+ cur_state, |
+ abs_timeout); |
+ __sync_fetch_and_sub(&rwlock->pending_readers, 1); |
+ if (ret == -ETIMEDOUT) { |
+ return ETIMEDOUT; |
+ } |
+ } |
+ } while (!done); |
+ |
+ return 0; |
+} |
+ |
+static int __pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock, |
+ const struct timespec *abs_timeout) { |
+ pthread_t self = pthread_self(); |
+ if (__predict_false(self == rwlock->writer_thread_id)) { |
+ return EDEADLK; |
+ } |
+ |
+ int done = 0; |
+ do { |
+ int32_t cur_state = rwlock->state; |
+ if (__predict_true(cur_state == 0)) { |
+ /* Change state from 0 to -1. */ |
+ done = __sync_bool_compare_and_swap(&rwlock->state, |
+ 0 /* cur state */, |
+ -1 /* new state */); |
+ } else { |
+ /* |
+ * Failed to acquire, hang up. |
+ * To avoid losing wake ups the pending_writers update and the state read |
+ * should be sequentially consistent. (currently enforced by |
+ * __sync_fetch_and_add which creates a full barrier) |
+ */ |
+ __sync_fetch_and_add(&rwlock->pending_writers, 1); |
+ int ret = __libnacl_irt_futex.futex_wait_abs(&rwlock->state, |
+ cur_state, |
+ abs_timeout); |
+ __sync_fetch_and_sub(&rwlock->pending_writers, 1); |
+ if (ret == -ETIMEDOUT) { |
+ return ETIMEDOUT; |
+ } |
+ } |
+ } while (!done); |
+ |
+ rwlock->writer_thread_id = self; |
+ return 0; |
+} |
+ |
+int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock) { |
+ return __pthread_rwlock_timedrdlock(rwlock, NULL); |
+} |
+ |
+int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock, |
+ const struct timespec *abs_timeout) { |
+ return __pthread_rwlock_timedrdlock(rwlock, abs_timeout); |
+} |
+ |
+int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock) { |
+ int32_t cur_state = rwlock->state; |
+ if ((cur_state >= 0) && |
+ __sync_bool_compare_and_swap(&rwlock->state, cur_state, cur_state + 1)) { |
+ return 0; |
+ } |
+ return EBUSY; |
+} |
+ |
+int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock) { |
+ return __pthread_rwlock_timedwrlock(rwlock, NULL); |
+} |
+ |
+int pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock, |
+ const struct timespec *abs_timeout) { |
+ return __pthread_rwlock_timedwrlock(rwlock, abs_timeout); |
+} |
+ |
+int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock) { |
+ int32_t cur_state = rwlock->state; |
+ if ((cur_state == 0) && |
+ __sync_bool_compare_and_swap(&rwlock->state, |
+ 0 /* cur state */, |
+ -1 /* new state */)) { |
+ rwlock->writer_thread_id = pthread_self(); |
+ return 0; |
+ } |
+ return EBUSY; |
+} |
+ |
+int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) { |
+ pthread_t self = pthread_self(); |
+ int done = 0; |
+ do { |
+ int32_t cur_state = rwlock->state; |
+ if (cur_state == 0) { |
+ return EPERM; |
+ } |
+ if (cur_state == -1) { |
+ if (rwlock->writer_thread_id != self) { |
+ return EPERM; |
+ } |
+ /* We're no longer the owner. */ |
+ rwlock->writer_thread_id = 0; |
+ /* |
+ * Change state from -1 to 0. |
+ * We use __sync_bool_compare_and_swap to achieve sequential consistency |
+ * of the state store and the following pendingX loads. A simple store |
+ * with memory_order_release semantics is not enough to guarantee that the |
+ * pendingX loads are not reordered before the store (which may lead to a |
+ * lost wakeup). |
+ */ |
+ __sync_bool_compare_and_swap(&rwlock->state, |
+ -1 /* cur state*/, |
+ 0 /* new state */); |
+ |
+ /* Wake any waiters. */ |
+ if (__predict_false(rwlock->pending_readers > 0 |
+ || rwlock->pending_writers > 0)) { |
+ __libnacl_irt_futex.futex_wake(&rwlock->state, INT_MAX, NULL); |
+ } |
+ done = 1; |
+ } else { /* cur_state > 0 */ |
+ /* |
+ * Reduce state by 1. |
+ * See the comment above on why we need __sync_bool_compare_and_swap. |
+ */ |
+ done = __sync_bool_compare_and_swap(&rwlock->state, |
+ cur_state, |
+ cur_state - 1); |
+ if (done && (cur_state - 1) == 0) { |
+ /* There are no more readers, wake any waiters. */ |
+ if (__predict_false(rwlock->pending_readers > 0 |
+ || rwlock->pending_writers > 0)) { |
+ __libnacl_irt_futex.futex_wake(&rwlock->state, INT_MAX, NULL); |
+ } |
+ } |
+ } |
+ } while (!done); |
+ |
+ return 0; |
+} |