OSDN Git Service

cc66bd2491089afb2b2dc3eb5ff50b8b7f89d3e8
[android-x86/external-musl-libc.git] / src / thread / synccall.c
1 #include "pthread_impl.h"
2 #include <semaphore.h>
3 #include <unistd.h>
4 #include <dirent.h>
5 #include <string.h>
6 #include <ctype.h>
7 #include "futex.h"
8 #include "atomic.h"
9 #include "../dirent/__dirent.h"
10 #include "lock.h"
11
12 static struct chain {
13         struct chain *next;
14         int tid;
15         sem_t target_sem, caller_sem;
16 } *volatile head;
17
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);
23
24 static void handler(int sig)
25 {
26         struct chain ch;
27         int old_errno = errno;
28
29         sem_init(&ch.target_sem, 0, 0);
30         sem_init(&ch.caller_sem, 0, 0);
31
32         ch.tid = __syscall(SYS_gettid);
33
34         do ch.next = head;
35         while (a_cas_p(&head, ch.next, &ch) != ch.next);
36
37         if (a_cas(&target_tid, ch.tid, 0) == (ch.tid | 0x80000000))
38                 __syscall(SYS_futex, &target_tid, FUTEX_UNLOCK_PI|FUTEX_PRIVATE);
39
40         sem_wait(&ch.target_sem);
41         callback(context);
42         sem_post(&ch.caller_sem);
43         sem_wait(&ch.target_sem);
44
45         errno = old_errno;
46 }
47
48 void __synccall(void (*func)(void *), void *ctx)
49 {
50         sigset_t oldmask;
51         int cs, i, r, pid, self;;
52         DIR dir = {0};
53         struct dirent *de;
54         struct sigaction sa = { .sa_flags = SA_RESTART, .sa_handler = handler };
55         struct chain *cp, *next;
56         struct timespec ts;
57
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);
65         LOCK(synccall_lock);
66         __block_all_sigs(0);
67         pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
68
69         head = 0;
70
71         if (!libc.threaded) goto single_threaded;
72
73         callback = func;
74         context = ctx;
75
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);
81
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);
87
88         pid = __syscall(SYS_getpid);
89         self = __syscall(SYS_gettid);
90
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;
95
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);
102
103         /* Loop scanning the kernel-provided thread list until it shows no
104          * threads that have not already replied to the signal. */
105         for (;;) {
106                 int miss_cnt = 0;
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;
111
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);
118
119                         /* Thread-already-caught is a success condition. */
120                         for (cp = head; cp && cp->tid != tid; cp=cp->next);
121                         if (cp) continue;
122
123                         r = -__syscall(SYS_tgkill, pid, tid, SIGSYNCCALL);
124
125                         /* Target thread exit is a success condition. */
126                         if (r == ESRCH) continue;
127
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
132                          * process. */
133                         clock_gettime(CLOCK_REALTIME, &ts);
134                         ts.tv_nsec += 10000000;
135                         if (ts.tv_nsec >= 1000000000) {
136                                 ts.tv_sec++;
137                                 ts.tv_nsec -= 1000000000;
138                         }
139                         r = -__syscall(SYS_futex, &target_tid,
140                                 FUTEX_LOCK_PI|FUTEX_PRIVATE, 0, &ts);
141
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;
145
146                         miss_cnt++;
147                 }
148                 if (!miss_cnt) break;
149                 rewinddir(&dir);
150         }
151         close(dir.fd);
152
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);
157         }
158
159         sa.sa_handler = SIG_IGN;
160         __libc_sigaction(SIGSYNCCALL, &sa, 0);
161
162 single_threaded:
163         func(ctx);
164
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) {
168                 next = cp->next;
169                 sem_post(&cp->target_sem);
170         }
171
172 out:
173         a_store(&__block_new_threads, 0);
174         __wake(&__block_new_threads, -1, 1);
175
176         pthread_setcancelstate(cs, 0);
177         UNLOCK(synccall_lock);
178         __restore_sigs(&oldmask);
179 }