1 #include "pthread_impl.h"
9 #include "../dirent/__dirent.h"
15 sem_t target_sem, caller_sem;
18 static volatile int synccall_lock[1];
19 static volatile int target_tid;
20 static void (*callback)(void *), *context;
21 static volatile int dummy = 0;
22 weak_alias(dummy, __block_new_threads);
24 static void handler(int sig)
27 int old_errno = errno;
29 sem_init(&ch.target_sem, 0, 0);
30 sem_init(&ch.caller_sem, 0, 0);
32 ch.tid = __syscall(SYS_gettid);
35 while (a_cas_p(&head, ch.next, &ch) != ch.next);
37 if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000))
38 __syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE);
40 sem_wait(&ch.target_sem);
42 sem_post(&ch.caller_sem);
43 sem_wait(&ch.target_sem);
48 void __synccall(void (*func)(void *), void *ctx)
51 int cs, i, r, pid, self;;
54 struct sigaction sa = { .sa_flags = SA_RESTART, .sa_handler = handler };
55 struct chain *cp, *next;
58 /* Blocking signals in two steps, first only app-level signals
59 * before taking the lock, then all signals after taking the lock,
60 * is necessary to achieve AS-safety. Blocking them all first would
61 * deadlock if multiple threads called __synccall. Waiting to block
62 * any until after the lock would allow re-entry in the same thread
63 * with the lock already held. */
64 __block_app_sigs(&oldmask);
67 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
71 if (!libc.threaded) goto single_threaded;
76 /* This atomic store ensures that any signaled threads will see the
77 * above stores, and prevents more than a bounded number of threads,
78 * those already in pthread_create, from creating new threads until
79 * the value is cleared to zero again. */
80 a_store(&__block_new_threads, 1);
82 /* Block even implementation-internal signals, so that nothing
83 * interrupts the SIGSYNCCALL handlers. The main possible source
84 * of trouble is asynchronous cancellation. */
85 memset(&sa.sa_mask, -1, sizeof sa.sa_mask);
86 __libc_sigaction(SIGSYNCCALL, &sa, 0);
88 pid = __syscall(SYS_getpid);
89 self = __syscall(SYS_gettid);
91 /* Since opendir is not AS-safe, the DIR needs to be setup manually
92 * in automatic storage. Thankfully this is easy. */
93 dir.fd = open("/proc/self/task", O_RDONLY|O_DIRECTORY|O_CLOEXEC);
94 if (dir.fd < 0) goto out;
96 /* Initially send one signal per counted thread. But since we can't
97 * synchronize with thread creation/exit here, there could be too
98 * few signals. This initial signaling is just an optimization, not
99 * part of the logic. */
100 for (i=libc.threads_minus_1; i; i--)
101 __syscall(SYS_kill, pid, SIGSYNCCALL);
103 /* Loop scanning the kernel-provided thread list until it shows no
104 * threads that have not already replied to the signal. */
107 while ((de = readdir(&dir))) {
108 if (!isdigit(de->d_name[0])) continue;
109 int tid = atoi(de->d_name);
110 if (tid == self || !tid) continue;
112 /* Set the target thread as the PI futex owner before
113 * checking if it's in the list of caught threads. If it
114 * adds itself to the list after we check for it, then
115 * it will see its own tid in the PI futex and perform
116 * the unlock operation. */
117 a_store(&target_tid, tid);
119 /* Thread-already-caught is a success condition. */
120 for (cp = head; cp && cp->tid != tid; cp=cp->next);
123 r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL);
125 /* Target thread exit is a success condition. */
126 if (r == ESRCH) continue;
128 /* The FUTEX_LOCK_PI operation is used to loan priority
129 * to the target thread, which otherwise may be unable
130 * to run. Timeout is necessary because there is a race
131 * condition where the tid may be reused by a different
133 clock_gettime(CLOCK_REALTIME, &ts);
134 ts.tv_nsec += 10000000;
135 if (ts.tv_nsec >= 1000000000) {
137 ts.tv_nsec -= 1000000000;
139 r = -__syscall(SYS_futex, &target_tid,
140 FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts);
142 /* Obtaining the lock means the thread responded. ESRCH
143 * means the target thread exited, which is okay too. */
144 if (!r || r == ESRCH) continue;
148 if (!miss_cnt) break;
153 /* Serialize execution of callback in caught threads. */
154 for (cp=head; cp; cp=cp->next) {
155 sem_post(&cp->target_sem);
156 sem_wait(&cp->caller_sem);
159 sa.sa_handler = SIG_IGN;
160 __libc_sigaction(SIGSYNCCALL, &sa, 0);
165 /* Only release the caught threads once all threads, including the
166 * caller, have returned from the callback function. */
167 for (cp=head; cp; cp=next) {
169 sem_post(&cp->target_sem);
173 a_store(&__block_new_threads, 0);
174 __wake(&__block_new_threads, -1, 1);
176 pthread_setcancelstate(cs, 0);
177 UNLOCK(synccall_lock);
178 __restore_sigs(&oldmask);