synccall.c raw

   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 || __syscall(SYS_gettid) != self->tid)
  67  		goto single_threaded;
  68  
  69  	callback = func;
  70  	context = ctx;
  71  
  72  	/* Block even implementation-internal signals, so that nothing
  73  	 * interrupts the SIGSYNCCALL handlers. The main possible source
  74  	 * of trouble is asynchronous cancellation. */
  75  	memset(&sa.sa_mask, -1, sizeof sa.sa_mask);
  76  	__libc_sigaction(SIGSYNCCALL, &sa, 0);
  77  
  78  
  79  	for (td=self->next; td!=self; td=td->next) {
  80  		target_tid = td->tid;
  81  		while ((r = -__syscall(SYS_tkill, td->tid, SIGSYNCCALL)) == EAGAIN);
  82  		if (r) {
  83  			/* If we failed to signal any thread, nop out the
  84  			 * callback to abort the synccall and just release
  85  			 * any threads already caught. */
  86  			callback = func = dummy;
  87  			break;
  88  		}
  89  		sem_wait(&caller_sem);
  90  		count++;
  91  	}
  92  	target_tid = 0;
  93  
  94  	/* Serialize execution of callback in caught threads, or just
  95  	 * release them all if synccall is being aborted. */
  96  	for (i=0; i<count; i++) {
  97  		sem_post(&target_sem);
  98  		sem_wait(&caller_sem);
  99  	}
 100  
 101  	sa.sa_handler = SIG_IGN;
 102  	__libc_sigaction(SIGSYNCCALL, &sa, 0);
 103  
 104  single_threaded:
 105  	func(ctx);
 106  
 107  	/* Only release the caught threads once all threads, including the
 108  	 * caller, have returned from the callback function. */
 109  	for (i=0; i<count; i++)
 110  		sem_post(&target_sem);
 111  	for (i=0; i<count; i++)
 112  		sem_wait(&caller_sem);
 113  
 114  	sem_destroy(&caller_sem);
 115  	sem_destroy(&target_sem);
 116  
 117  	pthread_setcancelstate(cs, 0);
 118  	__tl_unlock();
 119  	__restore_sigs(&oldmask);
 120  }
 121