diff options
Diffstat (limited to 'src/thread')
-rw-r--r-- | src/thread/pthread_create.c | 4 | ||||
-rw-r--r-- | src/thread/synccall.c | 178 |
2 files changed, 59 insertions, 123 deletions
diff --git a/src/thread/pthread_create.c b/src/thread/pthread_create.c index 03cdea0a..cec82157 100644 --- a/src/thread/pthread_create.c +++ b/src/thread/pthread_create.c @@ -201,8 +201,6 @@ weak_alias(dummy, __pthread_tsd_size); static void *dummy_tsd[1] = { 0 }; weak_alias(dummy_tsd, __pthread_tsd_main); -volatile int __block_new_threads = 0; - static FILE *volatile dummy_file = 0; weak_alias(dummy_file, __stdin_used); weak_alias(dummy_file, __stdout_used); @@ -247,8 +245,6 @@ int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict att attr._a_guardsize = __default_guardsize; } - if (__block_new_threads) __wait(&__block_new_threads, 0, 1, 1); - if (attr._a_stackaddr) { size_t need = libc.tls_size + __pthread_tsd_size; size = attr._a_stacksize; diff --git a/src/thread/synccall.c b/src/thread/synccall.c index cc66bd24..648a6ad4 100644 --- a/src/thread/synccall.c +++ b/src/thread/synccall.c @@ -1,46 +1,42 @@ #include "pthread_impl.h" #include <semaphore.h> -#include <unistd.h> -#include <dirent.h> #include <string.h> -#include <ctype.h> -#include "futex.h" -#include "atomic.h" -#include "../dirent/__dirent.h" -#include "lock.h" - -static struct chain { - struct chain *next; - int tid; - sem_t target_sem, caller_sem; -} *volatile head; - -static volatile int synccall_lock[1]; -static volatile int target_tid; + +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 volatile int dummy = 0; -weak_alias(dummy, __block_new_threads); +static sem_t target_sem, caller_sem; + +static void dummy(void *p) +{ +} static void handler(int sig) { - struct chain ch; - int old_errno = errno; + if (__pthread_self()->tid != target_tid) return; - sem_init(&ch.target_sem, 0, 0); - sem_init(&ch.caller_sem, 0, 0); + int old_errno = errno; - ch.tid = __syscall(SYS_gettid); + /* 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); - do ch.next = head; - while (a_cas_p(&head, ch.next, &ch) != ch.next); + callback(context); - if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000)) - __syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE); + /* 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); - sem_wait(&ch.target_sem); - callback(context); - sem_post(&ch.caller_sem); - sem_wait(&ch.target_sem); + /* Inform caller we are returning and state is destroyable. */ + sem_post(&caller_sem); errno = old_errno; } @@ -48,12 +44,10 @@ static void handler(int sig) void __synccall(void (*func)(void *), void *ctx) { sigset_t oldmask; - int cs, i, r, pid, self;; - DIR dir = {0}; - struct dirent *de; + int cs, i, r; struct sigaction sa = { .sa_flags = SA_RESTART, .sa_handler = handler }; - struct chain *cp, *next; - struct timespec ts; + 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, @@ -62,98 +56,45 @@ void __synccall(void (*func)(void *), void *ctx) * any until after the lock would allow re-entry in the same thread * with the lock already held. */ __block_app_sigs(&oldmask); - LOCK(synccall_lock); + __tl_lock(); __block_all_sigs(0); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); - head = 0; + sem_init(&target_sem, 0, 0); + sem_init(&caller_sem, 0, 0); - if (!libc.threaded) goto single_threaded; + if (!libc.threads_minus_1) goto single_threaded; callback = func; context = ctx; - /* This atomic store ensures that any signaled threads will see the - * above stores, and prevents more than a bounded number of threads, - * those already in pthread_create, from creating new threads until - * the value is cleared to zero again. */ - a_store(&__block_new_threads, 1); - /* 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); - pid = __syscall(SYS_getpid); - self = __syscall(SYS_gettid); - - /* Since opendir is not AS-safe, the DIR needs to be setup manually - * in automatic storage. Thankfully this is easy. */ - dir.fd = open("/proc/self/task", O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (dir.fd < 0) goto out; - - /* Initially send one signal per counted thread. But since we can't - * synchronize with thread creation/exit here, there could be too - * few signals. This initial signaling is just an optimization, not - * part of the logic. */ - for (i=libc.threads_minus_1; i; i--) - __syscall(SYS_kill, pid, SIGSYNCCALL); - - /* Loop scanning the kernel-provided thread list until it shows no - * threads that have not already replied to the signal. */ - for (;;) { - int miss_cnt = 0; - while ((de = readdir(&dir))) { - if (!isdigit(de->d_name[0])) continue; - int tid = atoi(de->d_name); - if (tid == self || !tid) continue; - - /* Set the target thread as the PI futex owner before - * checking if it's in the list of caught threads. If it - * adds itself to the list after we check for it, then - * it will see its own tid in the PI futex and perform - * the unlock operation. */ - a_store(&target_tid, tid); - - /* Thread-already-caught is a success condition. */ - for (cp = head; cp && cp->tid != tid; cp=cp->next); - if (cp) continue; - - r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL); - - /* Target thread exit is a success condition. */ - if (r == ESRCH) continue; - - /* The FUTEX_LOCK_PI operation is used to loan priority - * to the target thread, which otherwise may be unable - * to run. Timeout is necessary because there is a race - * condition where the tid may be reused by a different - * process. */ - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_nsec += 10000000; - if (ts.tv_nsec >= 1000000000) { - ts.tv_sec++; - ts.tv_nsec -= 1000000000; - } - r = -__syscall(SYS_futex, &target_tid, - FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts); - - /* Obtaining the lock means the thread responded. ESRCH - * means the target thread exited, which is okay too. */ - if (!r || r == ESRCH) continue; - - miss_cnt++; + + 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; } - if (!miss_cnt) break; - rewinddir(&dir); + sem_wait(&caller_sem); + count++; } - close(dir.fd); + target_tid = 0; - /* Serialize execution of callback in caught threads. */ - for (cp=head; cp; cp=cp->next) { - sem_post(&cp->target_sem); - sem_wait(&cp->caller_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_handler = SIG_IGN; @@ -164,16 +105,15 @@ single_threaded: /* Only release the caught threads once all threads, including the * caller, have returned from the callback function. */ - for (cp=head; cp; cp=next) { - next = cp->next; - sem_post(&cp->target_sem); - } + for (i=0; i<count; i++) + sem_post(&target_sem); + for (i=0; i<count; i++) + sem_wait(&caller_sem); -out: - a_store(&__block_new_threads, 0); - __wake(&__block_new_threads, -1, 1); + sem_destroy(&caller_sem); + sem_destroy(&target_sem); pthread_setcancelstate(cs, 0); - UNLOCK(synccall_lock); + __tl_unlock(); __restore_sigs(&oldmask); } |