1145519Sdarrenr#include "pthread_impl.h" 2145519Sdarrenr#include <semaphore.h> 331183Speter#include <unistd.h> 431183Speter#include <dirent.h> 5255332Scy#include <string.h> 631183Speter#include <ctype.h> 7145519Sdarrenr#include "futex.h" 831183Speter#include "atomic.h" 9161357Sguido#include "../dirent/__dirent.h" 1063537Sarchie 1131183Speterstatic struct chain { 1253024Sguido struct chain *next; 1331183Speter int tid; 1431183Speter sem_t target_sem, caller_sem; 1531183Speter} *volatile head; 1631183Speter 17170268Sdarrenrstatic volatile int synccall_lock[2]; 1831183Speterstatic volatile int target_tid; 19170268Sdarrenrstatic void (*callback)(void *), *context; 2031183Speterstatic volatile int dummy = 0; 2131183Speterweak_alias(dummy, __block_new_threads); 2231183Speter 2331183Speterstatic void handler(int sig) 2431183Speter{ 2531183Speter struct chain ch; 2631183Speter int old_errno = errno; 2731183Speter 2831183Speter sem_init(&ch.target_sem, 0, 0); 29255332Scy sem_init(&ch.caller_sem, 0, 0); 3031183Speter 3131183Speter ch.tid = __syscall(SYS_gettid); 3231183Speter 3331183Speter do ch.next = head; 34170268Sdarrenr while (a_cas_p(&head, ch.next, &ch) != ch.next); 35255332Scy 36170268Sdarrenr if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000)) 3731183Speter __syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE); 3831183Speter 3931183Speter sem_wait(&ch.target_sem); 4031183Speter callback(context); 4131183Speter sem_post(&ch.caller_sem); 4231183Speter sem_wait(&ch.target_sem); 4331183Speter 4431183Speter errno = old_errno; 4531183Speter} 4631183Speter 4731183Spetervoid __synccall(void (*func)(void *), void *ctx) 4864591Sdarrenr{ 49145519Sdarrenr sigset_t oldmask; 5031183Speter int cs, i, r, pid, self;; 5137074Speter DIR dir = {0}; 5231183Speter struct dirent *de; 5331183Speter struct sigaction sa = { .sa_flags = 0, .sa_handler = handler }; 5431183Speter struct chain *cp, *next; 5531183Speter struct timespec ts; 5631183Speter 5731183Speter /* Blocking signals in two steps, first only app-level signals 5831183Speter * before taking the lock, then all signals after taking the lock, 5931183Speter * is necessary to achieve AS-safety. Blocking them all first would 6031183Speter * deadlock if multiple threads called __synccall. Waiting to block 6131183Speter * any until after the lock would allow re-entry in the same thread 6231183Speter * with the lock already held. */ 6331183Speter __block_app_sigs(&oldmask); 6431183Speter LOCK(synccall_lock); 6531183Speter __block_all_sigs(0); 6631183Speter pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); 6731183Speter 6831183Speter head = 0; 6931183Speter 7031183Speter if (!libc.threaded) goto single_threaded; 7131183Speter 7231183Speter callback = func; 7331183Speter context = ctx; 7431183Speter 7531183Speter /* This atomic store ensures that any signaled threads will see the 7631183Speter * above stores, and prevents more than a bounded number of threads, 7731183Speter * those already in pthread_create, from creating new threads until 7831183Speter * the value is cleared to zero again. */ 7931183Speter a_store(&__block_new_threads, 1); 8031183Speter 8131183Speter /* Block even implementation-internal signals, so that nothing 8231183Speter * interrupts the SIGSYNCCALL handlers. The main possible source 8331183Speter * of trouble is asynchronous cancellation. */ 8431183Speter memset(&sa.sa_mask, -1, sizeof sa.sa_mask); 8531183Speter __libc_sigaction(SIGSYNCCALL, &sa, 0); 8631183Speter 8731183Speter pid = __syscall(SYS_getpid); 8831183Speter self = __syscall(SYS_gettid); 8931183Speter 9031183Speter /* Since opendir is not AS-safe, the DIR needs to be setup manually 9131183Speter * in automatic storage. Thankfully this is easy. */ 9231183Speter dir.fd = open("/proc/self/task", O_RDONLY|O_DIRECTORY|O_CLOEXEC); 9331183Speter if (dir.fd < 0) goto out; 9431183Speter 9531183Speter /* Initially send one signal per counted thread. But since we can't 9631183Speter * synchronize with thread creation/exit here, there could be too 9731183Speter * few signals. This initial signaling is just an optimization, not 9831183Speter * part of the logic. */ 9931183Speter for (i=libc.threads_minus_1; i; i--) 10031183Speter __syscall(SYS_kill, pid, SIGSYNCCALL); 10131183Speter 10231183Speter /* Loop scanning the kernel-provided thread list until it shows no 10331183Speter * threads that have not already replied to the signal. */ 10431183Speter for (;;) { 10531183Speter int miss_cnt = 0; 10631183Speter while ((de = readdir(&dir))) { 10731183Speter if (!isdigit(de->d_name[0])) continue; 10831183Speter int tid = atoi(de->d_name); 10931183Speter if (tid == self || !tid) continue; 11031183Speter 11131183Speter /* Set the target thread as the PI futex owner before 11231183Speter * checking if it's in the list of caught threads. If it 11331183Speter * adds itself to the list after we check for it, then 11431183Speter * it will see its own tid in the PI futex and perform 11531183Speter * the unlock operation. */ 11631183Speter a_store(&target_tid, tid); 11731183Speter 11831183Speter /* Thread-already-caught is a success condition. */ 11931183Speter for (cp = head; cp && cp->tid != tid; cp=cp->next); 12031183Speter if (cp) continue; 12131183Speter 12231183Speter r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL); 12331183Speter 12431183Speter /* Target thread exit is a success condition. */ 12531183Speter if (r == ESRCH) continue; 12631183Speter 12731183Speter /* The FUTEX_LOCK_PI operation is used to loan priority 12831183Speter * to the target thread, which otherwise may be unable 12931183Speter * to run. Timeout is necessary because there is a race 13031183Speter * condition where the tid may be reused by a different 13131183Speter * process. */ 13231183Speter clock_gettime(CLOCK_REALTIME, &ts); 13331183Speter ts.tv_nsec += 10000000; 13431183Speter if (ts.tv_nsec >= 1000000000) { 13531183Speter ts.tv_sec++; 13631183Speter ts.tv_nsec -= 1000000000; 13731183Speter } 13831183Speter r = -__syscall(SYS_futex, &target_tid, 13931183Speter FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts); 14031183Speter 14131183Speter /* Obtaining the lock means the thread responded. ESRCH 14231183Speter * means the target thread exited, which is okay too. */ 14331183Speter if (!r || r == ESRCH) continue; 14431183Speter 14531183Speter miss_cnt++; 14631183Speter } 14731183Speter if (!miss_cnt) break; 14831183Speter rewinddir(&dir); 14931183Speter } 15031183Speter close(dir.fd); 15131183Speter 15231183Speter /* Serialize execution of callback in caught threads. */ 15331183Speter for (cp=head; cp; cp=cp->next) { 15431183Speter sem_post(&cp->target_sem); 15531183Speter sem_wait(&cp->caller_sem); 15631183Speter } 15731183Speter 15831183Speter sa.sa_handler = SIG_IGN; 15931183Speter __libc_sigaction(SIGSYNCCALL, &sa, 0); 16031183Speter 16131183Spetersingle_threaded: 16231183Speter func(ctx); 16331183Speter 16431183Speter /* Only release the caught threads once all threads, including the 16555924Sguido * caller, have returned from the callback function. */ 16631183Speter for (cp=head; cp; cp=next) { 16731183Speter next = cp->next; 16831183Speter sem_post(&cp->target_sem); 16931183Speter } 17031183Speter 17131183Speterout: 17231183Speter a_store(&__block_new_threads, 0); 17331183Speter __wake(&__block_new_threads, -1, 1); 17431183Speter 17531183Speter pthread_setcancelstate(cs, 0); 17631183Speter UNLOCK(synccall_lock); 17731183Speter __restore_sigs(&oldmask); 17831183Speter} 17931183Speter