github.com/afumu/libc@v0.0.6/musl/src/thread/synccall.c (about) 1 #include "pthread_impl.h" 2 #include <semaphore.h> 3 #include <string.h> 4 5 static void dummy_0(void) 6 { 7 } 8 9 weak_alias(dummy_0, __tl_lock); 10 weak_alias(dummy_0, __tl_unlock); 11 12 static int target_tid; 13 static void (*callback)(void *), *context; 14 static sem_t target_sem, caller_sem; 15 16 static void dummy(void *p) 17 { 18 } 19 20 static void handler(int sig) 21 { 22 if (__pthread_self()->tid != target_tid) return; 23 24 int old_errno = errno; 25 26 /* Inform caller we have received signal and wait for 27 * the caller to let us make the callback. */ 28 sem_post(&caller_sem); 29 sem_wait(&target_sem); 30 31 callback(context); 32 33 /* Inform caller we've complered the callback and wait 34 * for the caller to release us to return. */ 35 sem_post(&caller_sem); 36 sem_wait(&target_sem); 37 38 /* Inform caller we are returning and state is destroyable. */ 39 sem_post(&caller_sem); 40 41 errno = old_errno; 42 } 43 44 void __synccall(void (*func)(void *), void *ctx) 45 { 46 sigset_t oldmask; 47 int cs, i, r; 48 struct sigaction sa = { .sa_flags = SA_RESTART, .sa_handler = handler }; 49 pthread_t self = __pthread_self(), td; 50 int count = 0; 51 52 /* Blocking signals in two steps, first only app-level signals 53 * before taking the lock, then all signals after taking the lock, 54 * is necessary to achieve AS-safety. Blocking them all first would 55 * deadlock if multiple threads called __synccall. Waiting to block 56 * any until after the lock would allow re-entry in the same thread 57 * with the lock already held. */ 58 __block_app_sigs(&oldmask); 59 __tl_lock(); 60 __block_all_sigs(0); 61 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); 62 63 sem_init(&target_sem, 0, 0); 64 sem_init(&caller_sem, 0, 0); 65 66 if (!libc.threads_minus_1) goto single_threaded; 67 68 callback = func; 69 context = ctx; 70 71 /* Block even implementation-internal signals, so that nothing 72 * interrupts the SIGSYNCCALL handlers. The main possible source 73 * of trouble is asynchronous cancellation. */ 74 memset(&sa.sa_mask, -1, sizeof sa.sa_mask); 75 __libc_sigaction(SIGSYNCCALL, &sa, 0); 76 77 78 for (td=self->next; td!=self; td=td->next) { 79 target_tid = td->tid; 80 while ((r = -__syscall(SYS_tkill, td->tid, SIGSYNCCALL)) == EAGAIN); 81 if (r) { 82 /* If we failed to signal any thread, nop out the 83 * callback to abort the synccall and just release 84 * any threads already caught. */ 85 callback = func = dummy; 86 break; 87 } 88 sem_wait(&caller_sem); 89 count++; 90 } 91 target_tid = 0; 92 93 /* Serialize execution of callback in caught threads, or just 94 * release them all if synccall is being aborted. */ 95 for (i=0; i<count; i++) { 96 sem_post(&target_sem); 97 sem_wait(&caller_sem); 98 } 99 100 sa.sa_handler = SIG_IGN; 101 __libc_sigaction(SIGSYNCCALL, &sa, 0); 102 103 single_threaded: 104 func(ctx); 105 106 /* Only release the caught threads once all threads, including the 107 * caller, have returned from the callback function. */ 108 for (i=0; i<count; i++) 109 sem_post(&target_sem); 110 for (i=0; i<count; i++) 111 sem_wait(&caller_sem); 112 113 sem_destroy(&caller_sem); 114 sem_destroy(&target_sem); 115 116 pthread_setcancelstate(cs, 0); 117 __tl_unlock(); 118 __restore_sigs(&oldmask); 119 }