Commit | Line | Data |
---|---|---|
5f138e3d | 1 | //Filename: pthread_pause.c |
d0c03bf1 | 2 | //Author: Tomas 'Harvie' Mudrunka 2021 |
5f138e3d TM |
3 | //Build: CFLAGS=-lpthread make pthread_pause; ./pthread_pause |
4 | //Test: valgrind --tool=helgrind ./pthread_pause | |
d0c03bf1 TM |
5 | |
6 | //I've wrote this code as excercise to solve following stack overflow question: | |
7 | // https://stackoverflow.com/questions/9397068/how-to-pause-a-pthread-any-time-i-want/68119116#68119116 | |
8 | ||
75e24564 | 9 | #define _GNU_SOURCE //pthread_yield() needs this |
d0c03bf1 TM |
10 | #include <signal.h> |
11 | #include <pthread.h> | |
12 | //#include <pthread_extra.h> | |
75e24564 | 13 | #include <semaphore.h> |
d0c03bf1 TM |
14 | #include <stdio.h> |
15 | #include <stdlib.h> | |
16 | #include <assert.h> | |
17 | #include <unistd.h> | |
18 | #include <errno.h> | |
19 | #include <sys/resource.h> | |
75e24564 | 20 | #include <time.h> |
d0c03bf1 TM |
21 | |
22 | #define PTHREAD_XSIG_STOP (SIGRTMIN+0) | |
23 | #define PTHREAD_XSIG_CONT (SIGRTMIN+1) | |
24 | #define PTHREAD_XSIGRTMIN (SIGRTMIN+2) //First unused RT signal | |
25 | ||
128ba9ae | 26 | pthread_t main_thread; |
75e24564 TM |
27 | sem_t pthread_pause_sem; |
28 | ||
29 | void pthread_nanosleep(struct timespec t) { | |
30 | //Sleep calls on Linux get interrupted by signals, causing premature wake | |
31 | //Pthread (un)pause is built using signals | |
32 | //Therefore we need self-restarting sleep implementation | |
33 | //IO timeouts are restarted by SA_RESTART, but sleeps do need explicit restart | |
34 | while(nanosleep(&t, &t)) if(errno!=EINTR) break; | |
35 | return; | |
36 | } | |
37 | ||
38 | void pthread_nsleep(time_t s, long ns) { | |
39 | struct timespec t; | |
40 | t.tv_sec = s; | |
41 | t.tv_nsec = ns; | |
42 | pthread_nanosleep(t); | |
43 | } | |
44 | ||
45 | void pthread_sleep(time_t s) { | |
46 | pthread_nsleep(s, 0); | |
47 | } | |
48 | ||
49 | void pthread_pause_yield() { | |
50 | //Call this to give other threads chance to run | |
51 | sem_wait(&pthread_pause_sem); | |
52 | sem_post(&pthread_pause_sem); | |
53 | //usleep(0); | |
54 | //nanosleep(&((const struct timespec){.tv_sec=0,.tv_nsec=1}), NULL); | |
55 | pthread_nsleep(0,1); //pthread_yield() is not enough, so we use sleep | |
56 | pthread_yield(); | |
57 | } | |
128ba9ae | 58 | |
d0c03bf1 TM |
59 | void pthread_pause_handler(int signal) { |
60 | //Do nothing when there are more signals pending (to cleanup the queue) | |
75e24564 TM |
61 | //This is no longer needed, since we use semaphore to limit pending signals |
62 | /* | |
d0c03bf1 TM |
63 | sigset_t pending; |
64 | sigpending(&pending); | |
65 | if(sigismember(&pending, PTHREAD_XSIG_STOP)) return; | |
66 | if(sigismember(&pending, PTHREAD_XSIG_CONT)) return; | |
75e24564 | 67 | */ |
d0c03bf1 | 68 | |
75e24564 | 69 | sem_post(&pthread_pause_sem); |
d0c03bf1 TM |
70 | if(signal == PTHREAD_XSIG_STOP) { |
71 | sigset_t sigset; | |
72 | sigfillset(&sigset); | |
73 | sigdelset(&sigset, PTHREAD_XSIG_STOP); | |
74 | sigdelset(&sigset, PTHREAD_XSIG_CONT); | |
75 | sigsuspend(&sigset); //Wait for next signal | |
76 | } else return; | |
77 | } | |
78 | ||
79 | void pthread_pause_enable() { | |
80 | //Having signal queue too deep might not be necessary | |
81 | //It can be limited using RLIMIT_SIGPENDING | |
82 | //You can get runtime SigQ stats using following command: | |
83 | //grep -i sig /proc/$(pgrep binary)/status | |
84 | struct rlimit sigq = {.rlim_cur = 32, .rlim_max=32}; | |
85 | setrlimit(RLIMIT_SIGPENDING, &sigq); | |
86 | ||
75e24564 | 87 | //Prepare sigset |
d0c03bf1 TM |
88 | sigset_t sigset; |
89 | sigemptyset(&sigset); | |
90 | sigaddset(&sigset, PTHREAD_XSIG_STOP); | |
91 | sigaddset(&sigset, PTHREAD_XSIG_CONT); | |
75e24564 TM |
92 | |
93 | //Register signal handlers | |
94 | //signal(PTHREAD_XSIG_STOP, pthread_pause_handler); | |
95 | //signal(PTHREAD_XSIG_CONT, pthread_pause_handler); | |
96 | //We now use sigaction() instead of signal(), because it supports SA_RESTART | |
97 | const struct sigaction pause_sa = { | |
98 | .sa_handler = pthread_pause_handler, | |
99 | .sa_mask = sigset, | |
100 | .sa_flags = SA_RESTART, | |
101 | .sa_restorer = NULL | |
102 | }; | |
103 | sigaction(PTHREAD_XSIG_STOP, &pause_sa, NULL); | |
104 | sigaction(PTHREAD_XSIG_CONT, &pause_sa, NULL); | |
105 | ||
106 | //UnBlock signals | |
d0c03bf1 TM |
107 | pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); |
108 | } | |
109 | ||
110 | void pthread_pause_disable() { | |
111 | //This is important for when you want to do some signal unsafe stuff | |
112 | //Eg.: locking mutex, calling printf() which has internal mutex, etc... | |
113 | //After unlocking mutex, you can enable pause again. | |
114 | ||
75e24564 TM |
115 | //Make sure all signals are dispatched before we block them |
116 | sem_wait(&pthread_pause_sem); | |
117 | ||
d0c03bf1 TM |
118 | //Block signals |
119 | sigset_t sigset; | |
120 | sigemptyset(&sigset); | |
121 | sigaddset(&sigset, PTHREAD_XSIG_STOP); | |
122 | sigaddset(&sigset, PTHREAD_XSIG_CONT); | |
123 | pthread_sigmask(SIG_BLOCK, &sigset, NULL); | |
75e24564 TM |
124 | |
125 | sem_post(&pthread_pause_sem); | |
d0c03bf1 TM |
126 | } |
127 | ||
128 | ||
129 | int pthread_pause(pthread_t thread) { | |
75e24564 | 130 | sem_wait(&pthread_pause_sem); |
d0c03bf1 TM |
131 | //If signal queue is full, we keep retrying |
132 | while(pthread_kill(thread, PTHREAD_XSIG_STOP) == EAGAIN) usleep(1000); | |
75e24564 | 133 | pthread_pause_yield(); |
d0c03bf1 TM |
134 | return 0; |
135 | } | |
136 | ||
137 | int pthread_unpause(pthread_t thread) { | |
75e24564 | 138 | sem_wait(&pthread_pause_sem); |
d0c03bf1 TM |
139 | //If signal queue is full, we keep retrying |
140 | while(pthread_kill(thread, PTHREAD_XSIG_CONT) == EAGAIN) usleep(1000); | |
75e24564 | 141 | pthread_pause_yield(); |
d0c03bf1 TM |
142 | return 0; |
143 | } | |
144 | ||
145 | void *thread_test() { | |
146 | //Whole process dies if you kill thread immediately before it is pausable | |
147 | //pthread_pause_enable(); | |
148 | while(1) { | |
d0c03bf1 TM |
149 | //Printf() is not async signal safe (because it holds internal mutex), |
150 | //you should call it only with pause disabled! | |
151 | //Will throw helgrind warnings anyway, not sure why... | |
152 | //See: man 7 signal-safety | |
153 | pthread_pause_disable(); | |
154 | printf("Running!\n"); | |
155 | pthread_pause_enable(); | |
75e24564 TM |
156 | |
157 | //Pausing main thread should not cause deadlock | |
158 | //We pause main thread here just to test it is OK | |
159 | pthread_pause(main_thread); | |
160 | pthread_nsleep(0, 1000*1000); | |
161 | pthread_unpause(main_thread); | |
162 | ||
163 | //Wait for a while | |
164 | pthread_nsleep(0, 1000*1000*100); | |
165 | pthread_unpause(main_thread); | |
d0c03bf1 TM |
166 | } |
167 | } | |
168 | ||
169 | int main() { | |
170 | pthread_t t; | |
75e24564 | 171 | sem_init(&pthread_pause_sem, 0, 1); //TODO: hide somewhere using pthred_once() |
128ba9ae | 172 | main_thread = pthread_self(); |
d0c03bf1 TM |
173 | pthread_pause_enable(); //Will get inherited by all threads from now on |
174 | //you need to call pthread_pause_enable (or disable) before creating threads, | |
75e24564 | 175 | //otherwise first (un)pause signal will kill whole process |
d0c03bf1 TM |
176 | pthread_create(&t, NULL, thread_test, NULL); |
177 | ||
178 | while(1) { | |
179 | pthread_pause(t); | |
180 | printf("PAUSED\n"); | |
75e24564 | 181 | pthread_sleep(3); |
d0c03bf1 TM |
182 | |
183 | printf("UNPAUSED\n"); | |
184 | pthread_unpause(t); | |
75e24564 TM |
185 | pthread_sleep(1); |
186 | ||
187 | /* | |
188 | pthread_pause_disable(); | |
189 | printf("RUNNING!\n"); | |
190 | pthread_pause_enable(); | |
191 | */ | |
192 | pthread_pause(t); | |
193 | pthread_unpause(t); | |
d0c03bf1 TM |
194 | } |
195 | ||
196 | pthread_join(t, NULL); | |
197 | printf("DIEDED!\n"); | |
198 | } |