remove LFS64 symbol aliases; replace with dynamic linker remapping
[musl] / src / thread / synccall.c
index 91ac5eb..a6b177c 100644 (file)
 #include "pthread_impl.h"
 #include <semaphore.h>
+#include <string.h>
 
-static struct chain {
-       struct chain *next;
-       sem_t sem, sem2;
-} *head;
+static void dummy_0(void)
+{
+}
+
+weak_alias(dummy_0, __tl_lock);
+weak_alias(dummy_0, __tl_unlock);
 
+static int target_tid;
 static void (*callback)(void *), *context;
-static int chainlen;
-static sem_t chainlock, chaindone;
-static pthread_rwlock_t lock = PTHREAD_RWLOCK_INITIALIZER;
+static sem_t target_sem, caller_sem;
 
-static void handler(int sig, siginfo_t *si, void *ctx)
+static void dummy(void *p)
 {
-       struct chain ch;
-       pthread_t self = __pthread_self();
-       int old_errno = errno;
+}
 
-       if (chainlen == libc.threads_minus_1) return;
+static void handler(int sig)
+{
+       if (__pthread_self()->tid != target_tid) return;
 
-       sigqueue(self->pid, SIGSYNCCALL, (union sigval){0});
+       int old_errno = errno;
 
-       /* Threads which have already decremented themselves from the
-        * thread count must not act. Block further receipt of signals
-        * and return. */
-       if (self->dead) {
-               memset(&((ucontext_t *)ctx)->uc_sigmask, -1, 8);
-               errno = old_errno;
-               return;
-       }
+       /* Inform caller we have received signal and wait for
+        * the caller to let us make the callback. */
+       sem_post(&caller_sem);
+       sem_wait(&target_sem);
 
-       sem_init(&ch.sem, 0, 0);
-       sem_init(&ch.sem2, 0, 0);
+       callback(context);
 
-       while (sem_wait(&chainlock));
-       ch.next = head;
-       head = &ch;
-       if (++chainlen == libc.threads_minus_1) sem_post(&chaindone);
-       sem_post(&chainlock);
+       /* Inform caller we've complered the callback and wait
+        * for the caller to release us to return. */
+       sem_post(&caller_sem);
+       sem_wait(&target_sem);
 
-       while (sem_wait(&ch.sem));
-       callback(context);
-       sem_post(&ch.sem2);
-       while (sem_wait(&ch.sem));
+       /* Inform caller we are returning and state is destroyable. */
+       sem_post(&caller_sem);
 
        errno = old_errno;
 }
 
 void __synccall(void (*func)(void *), void *ctx)
 {
-       pthread_t self;
-       struct sigaction sa;
-       struct chain *cur, *next;
-       uint64_t oldmask;
-
-       if (!libc.threads_minus_1) {
-               func(ctx);
-               return;
-       }
-
-       pthread_rwlock_wrlock(&lock);
-
-       __syscall(SYS_rt_sigprocmask, SIG_BLOCK, (uint64_t[]){-1}, &oldmask, 8);
+       sigset_t oldmask;
+       int cs, i, r;
+       struct sigaction sa = { .sa_flags = SA_RESTART | SA_ONSTACK, .sa_handler = handler };
+       pthread_t self = __pthread_self(), td;
+       int count = 0;
+
+       /* Blocking signals in two steps, first only app-level signals
+        * before taking the lock, then all signals after taking the lock,
+        * is necessary to achieve AS-safety. Blocking them all first would
+        * deadlock if multiple threads called __synccall. Waiting to block
+        * any until after the lock would allow re-entry in the same thread
+        * with the lock already held. */
+       __block_app_sigs(&oldmask);
+       __tl_lock();
+       __block_all_sigs(0);
+       pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
+
+       sem_init(&target_sem, 0, 0);
+       sem_init(&caller_sem, 0, 0);
+
+       if (!libc.threads_minus_1 || __syscall(SYS_gettid) != self->tid)
+               goto single_threaded;
 
-       sem_init(&chaindone, 0, 0);
-       sem_init(&chainlock, 0, 1);
-       chainlen = 0;
        callback = func;
        context = ctx;
 
-       sa.sa_flags = SA_SIGINFO | SA_RESTART;
-       sa.sa_sigaction = handler;
-       sigfillset(&sa.sa_mask);
+       /* Block even implementation-internal signals, so that nothing
+        * interrupts the SIGSYNCCALL handlers. The main possible source
+        * of trouble is asynchronous cancellation. */
+       memset(&sa.sa_mask, -1, sizeof sa.sa_mask);
        __libc_sigaction(SIGSYNCCALL, &sa, 0);
 
-       self = __pthread_self();
-       sigqueue(self->pid, SIGSYNCCALL, (union sigval){0});
-       while (sem_wait(&chaindone));
 
-       for (cur=head; cur; cur=cur->next) {
-               sem_post(&cur->sem);
-               while (sem_wait(&cur->sem2));
+       for (td=self->next; td!=self; td=td->next) {
+               target_tid = td->tid;
+               while ((r = -__syscall(SYS_tkill, td->tid, SIGSYNCCALL)) == EAGAIN);
+               if (r) {
+                       /* If we failed to signal any thread, nop out the
+                        * callback to abort the synccall and just release
+                        * any threads already caught. */
+                       callback = func = dummy;
+                       break;
+               }
+               sem_wait(&caller_sem);
+               count++;
        }
-       func(ctx);
+       target_tid = 0;
 
-       for (cur=head; cur; cur=next) {
-               next = cur->next;
-               sem_post(&cur->sem);
+       /* Serialize execution of callback in caught threads, or just
+        * release them all if synccall is being aborted. */
+       for (i=0; i<count; i++) {
+               sem_post(&target_sem);
+               sem_wait(&caller_sem);
        }
 
-       sa.sa_flags = 0;
        sa.sa_handler = SIG_IGN;
        __libc_sigaction(SIGSYNCCALL, &sa, 0);
 
-       __syscall(SYS_rt_sigprocmask, SIG_SETMASK, &oldmask, 0, 8);
+single_threaded:
+       func(ctx);
 
-       pthread_rwlock_unlock(&lock);
-}
+       /* Only release the caught threads once all threads, including the
+        * caller, have returned from the callback function. */
+       for (i=0; i<count; i++)
+               sem_post(&target_sem);
+       for (i=0; i<count; i++)
+               sem_wait(&caller_sem);
 
-void __synccall_lock()
-{
-       pthread_rwlock_rdlock(&lock);
-}
+       sem_destroy(&caller_sem);
+       sem_destroy(&target_sem);
 
-void __synccall_unlock()
-{
-       pthread_rwlock_unlock(&lock);
+       pthread_setcancelstate(cs, 0);
+       __tl_unlock();
+       __restore_sigs(&oldmask);
 }