|
@@ -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);
|
|
|
}
|