1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
#include "pthread_impl.h"
#include <semaphore.h>
#include <string.h>
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 sem_t target_sem, caller_sem, exit_sem;
static void dummy(void *p)
{
}
static void handler(int sig)
{
if (__pthread_self()->tid != target_tid) return;
int old_errno = errno;
/* 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);
callback(context);
/* Inform caller we've complered the callback and wait
* for the caller to release us to return. */
sem_post(&caller_sem);
sem_wait(&exit_sem);
/* Inform caller we are returning and state is destroyable. */
sem_post(&caller_sem);
errno = old_errno;
}
void __synccall(void (*func)(void *), void *ctx)
{
sigset_t oldmask;
int cs, i, r;
struct sigaction sa = { .sa_flags = SA_RESTART | SA_ONSTACK, .sa_handler = handler };
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,
* is necessary to achieve AS-safety. Blocking them all first would
* deadlock if multiple threads called __synccall. Waiting to block
* any until after the lock would allow re-entry in the same thread
* with the lock already held. */
__block_app_sigs(&oldmask);
__tl_lock();
__block_all_sigs(0);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
sem_init(&target_sem, 0, 0);
sem_init(&caller_sem, 0, 0);
sem_init(&exit_sem, 0, 0);
if (!libc.threads_minus_1 || __syscall(SYS_gettid) != self->tid)
goto single_threaded;
callback = func;
context = ctx;
/* 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);
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;
}
sem_wait(&caller_sem);
count++;
}
target_tid = 0;
/* 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;
__libc_sigaction(SIGSYNCCALL, &sa, 0);
single_threaded:
func(ctx);
/* Only release the caught threads once all threads, including the
* caller, have returned from the callback function. */
for (i=0; i<count; i++)
sem_post(&exit_sem);
for (i=0; i<count; i++)
sem_wait(&caller_sem);
sem_destroy(&caller_sem);
sem_destroy(&target_sem);
sem_destroy(&exit_sem);
pthread_setcancelstate(cs, 0);
__tl_unlock();
__restore_sigs(&oldmask);
}
#ifdef TEST
int
main(void) {
return 0;
}
#endif
|