| #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" |
| |
| 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 (*callback)(void *), *context; |
| static volatile int dummy = 0; |
| weak_alias(dummy, __block_new_threads); |
| |
| static void handler(int sig) |
| { |
| struct chain ch; |
| int old_errno = errno; |
| |
| sem_init(&ch.target_sem, 0, 0); |
| sem_init(&ch.caller_sem, 0, 0); |
| |
| ch.tid = __syscall(SYS_gettid); |
| |
| do ch.next = head; |
| while (a_cas_p(&head, ch.next, &ch) != ch.next); |
| |
| if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000)) |
| __syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE); |
| |
| sem_wait(&ch.target_sem); |
| callback(context); |
| sem_post(&ch.caller_sem); |
| sem_wait(&ch.target_sem); |
| |
| errno = old_errno; |
| } |
| |
| void __synccall(void (*func)(void *), void *ctx) |
| { |
| sigset_t oldmask; |
| int cs, i, r, pid, self;; |
| DIR dir = {0}; |
| struct dirent *de; |
| struct sigaction sa = { .sa_flags = SA_RESTART, .sa_handler = handler }; |
| struct chain *cp, *next; |
| struct timespec ts; |
| |
| /* 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); |
| LOCK(synccall_lock); |
| __block_all_sigs(0); |
| pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); |
| |
| head = 0; |
| |
| if (!libc.threaded) 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++; |
| } |
| if (!miss_cnt) break; |
| rewinddir(&dir); |
| } |
| close(dir.fd); |
| |
| /* Serialize execution of callback in caught threads. */ |
| for (cp=head; cp; cp=cp->next) { |
| sem_post(&cp->target_sem); |
| sem_wait(&cp->caller_sem); |
| } |
| |
| sa.sa_handler = SIG_IGN; |
| __libc_sigaction(SIGSYNCCALL, &sa, 0); |
| |
| single_threaded: |
| func(ctx); |
| |
| /* 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); |
| } |
| |
| out: |
| a_store(&__block_new_threads, 0); |
| __wake(&__block_new_threads, -1, 1); |
| |
| pthread_setcancelstate(cs, 0); |
| UNLOCK(synccall_lock); |
| __restore_sigs(&oldmask); |
| } |