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 | 27 | sem_t pthread_pause_sem; |
3b8f487a TM |
28 | pthread_once_t pthread_pause_once_ctrl = PTHREAD_ONCE_INIT; |
29 | ||
30 | void pthread_pause_once(void) { | |
31 | sem_init(&pthread_pause_sem, 0, 1); | |
32 | } | |
33 | ||
34 | #define pthread_pause_init() (pthread_once(&pthread_pause_once_ctrl, &pthread_pause_once)) | |
35 | ||
36 | #define NSEC_PER_SEC (1000*1000*1000) | |
37 | // timespec_normalise() from https://github.com/solemnwarning/timespec/ | |
38 | struct timespec timespec_normalise(struct timespec ts) | |
39 | { | |
40 | while(ts.tv_nsec >= NSEC_PER_SEC) { | |
41 | ++(ts.tv_sec); ts.tv_nsec -= NSEC_PER_SEC; | |
42 | } | |
43 | while(ts.tv_nsec <= -NSEC_PER_SEC) { | |
44 | --(ts.tv_sec); ts.tv_nsec += NSEC_PER_SEC; | |
45 | } | |
46 | if(ts.tv_nsec < 0) { // Negative nanoseconds isn't valid according to POSIX. | |
47 | --(ts.tv_sec); ts.tv_nsec = (NSEC_PER_SEC + ts.tv_nsec); | |
48 | } | |
49 | return ts; | |
50 | } | |
75e24564 TM |
51 | |
52 | void pthread_nanosleep(struct timespec t) { | |
53 | //Sleep calls on Linux get interrupted by signals, causing premature wake | |
54 | //Pthread (un)pause is built using signals | |
55 | //Therefore we need self-restarting sleep implementation | |
56 | //IO timeouts are restarted by SA_RESTART, but sleeps do need explicit restart | |
3b8f487a TM |
57 | //We also need to sleep using absolute time, because relative time is paused |
58 | //You should use this in any thread that gets (un)paused | |
59 | ||
60 | struct timespec wake; | |
61 | clock_gettime(CLOCK_MONOTONIC, &wake); | |
62 | ||
63 | t = timespec_normalise(t); | |
64 | wake.tv_sec += t.tv_sec; | |
65 | wake.tv_nsec += t.tv_nsec; | |
66 | wake = timespec_normalise(wake); | |
67 | ||
68 | while(clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &wake, NULL)) if(errno!=EINTR) break; | |
75e24564 TM |
69 | return; |
70 | } | |
75e24564 TM |
71 | void pthread_nsleep(time_t s, long ns) { |
72 | struct timespec t; | |
73 | t.tv_sec = s; | |
74 | t.tv_nsec = ns; | |
75 | pthread_nanosleep(t); | |
76 | } | |
77 | ||
78 | void pthread_sleep(time_t s) { | |
79 | pthread_nsleep(s, 0); | |
80 | } | |
81 | ||
82 | void pthread_pause_yield() { | |
83 | //Call this to give other threads chance to run | |
3b8f487a | 84 | //Wait until last (un)pause action gets finished |
75e24564 TM |
85 | sem_wait(&pthread_pause_sem); |
86 | sem_post(&pthread_pause_sem); | |
87 | //usleep(0); | |
88 | //nanosleep(&((const struct timespec){.tv_sec=0,.tv_nsec=1}), NULL); | |
3b8f487a | 89 | //pthread_nsleep(0,1); //pthread_yield() is not enough, so we use sleep |
75e24564 TM |
90 | pthread_yield(); |
91 | } | |
128ba9ae | 92 | |
d0c03bf1 TM |
93 | void pthread_pause_handler(int signal) { |
94 | //Do nothing when there are more signals pending (to cleanup the queue) | |
75e24564 TM |
95 | //This is no longer needed, since we use semaphore to limit pending signals |
96 | /* | |
d0c03bf1 TM |
97 | sigset_t pending; |
98 | sigpending(&pending); | |
99 | if(sigismember(&pending, PTHREAD_XSIG_STOP)) return; | |
100 | if(sigismember(&pending, PTHREAD_XSIG_CONT)) return; | |
75e24564 | 101 | */ |
d0c03bf1 | 102 | |
3b8f487a | 103 | //Post semaphore to confirm that signal is handled |
75e24564 | 104 | sem_post(&pthread_pause_sem); |
3b8f487a | 105 | //Suspend if needed |
d0c03bf1 TM |
106 | if(signal == PTHREAD_XSIG_STOP) { |
107 | sigset_t sigset; | |
108 | sigfillset(&sigset); | |
109 | sigdelset(&sigset, PTHREAD_XSIG_STOP); | |
110 | sigdelset(&sigset, PTHREAD_XSIG_CONT); | |
111 | sigsuspend(&sigset); //Wait for next signal | |
112 | } else return; | |
113 | } | |
114 | ||
115 | void pthread_pause_enable() { | |
116 | //Having signal queue too deep might not be necessary | |
117 | //It can be limited using RLIMIT_SIGPENDING | |
118 | //You can get runtime SigQ stats using following command: | |
119 | //grep -i sig /proc/$(pgrep binary)/status | |
3b8f487a TM |
120 | //This is no longer needed, since we use semaphores |
121 | //struct rlimit sigq = {.rlim_cur = 32, .rlim_max=32}; | |
122 | //setrlimit(RLIMIT_SIGPENDING, &sigq); | |
123 | ||
124 | pthread_pause_init(); | |
d0c03bf1 | 125 | |
75e24564 | 126 | //Prepare sigset |
d0c03bf1 TM |
127 | sigset_t sigset; |
128 | sigemptyset(&sigset); | |
129 | sigaddset(&sigset, PTHREAD_XSIG_STOP); | |
130 | sigaddset(&sigset, PTHREAD_XSIG_CONT); | |
75e24564 TM |
131 | |
132 | //Register signal handlers | |
133 | //signal(PTHREAD_XSIG_STOP, pthread_pause_handler); | |
134 | //signal(PTHREAD_XSIG_CONT, pthread_pause_handler); | |
135 | //We now use sigaction() instead of signal(), because it supports SA_RESTART | |
136 | const struct sigaction pause_sa = { | |
137 | .sa_handler = pthread_pause_handler, | |
138 | .sa_mask = sigset, | |
139 | .sa_flags = SA_RESTART, | |
140 | .sa_restorer = NULL | |
141 | }; | |
142 | sigaction(PTHREAD_XSIG_STOP, &pause_sa, NULL); | |
143 | sigaction(PTHREAD_XSIG_CONT, &pause_sa, NULL); | |
144 | ||
145 | //UnBlock signals | |
d0c03bf1 TM |
146 | pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); |
147 | } | |
148 | ||
149 | void pthread_pause_disable() { | |
150 | //This is important for when you want to do some signal unsafe stuff | |
151 | //Eg.: locking mutex, calling printf() which has internal mutex, etc... | |
152 | //After unlocking mutex, you can enable pause again. | |
153 | ||
3b8f487a TM |
154 | pthread_pause_init(); |
155 | ||
75e24564 TM |
156 | //Make sure all signals are dispatched before we block them |
157 | sem_wait(&pthread_pause_sem); | |
158 | ||
d0c03bf1 TM |
159 | //Block signals |
160 | sigset_t sigset; | |
161 | sigemptyset(&sigset); | |
162 | sigaddset(&sigset, PTHREAD_XSIG_STOP); | |
163 | sigaddset(&sigset, PTHREAD_XSIG_CONT); | |
164 | pthread_sigmask(SIG_BLOCK, &sigset, NULL); | |
75e24564 TM |
165 | |
166 | sem_post(&pthread_pause_sem); | |
d0c03bf1 TM |
167 | } |
168 | ||
169 | ||
170 | int pthread_pause(pthread_t thread) { | |
75e24564 | 171 | sem_wait(&pthread_pause_sem); |
d0c03bf1 TM |
172 | //If signal queue is full, we keep retrying |
173 | while(pthread_kill(thread, PTHREAD_XSIG_STOP) == EAGAIN) usleep(1000); | |
75e24564 | 174 | pthread_pause_yield(); |
d0c03bf1 TM |
175 | return 0; |
176 | } | |
177 | ||
178 | int pthread_unpause(pthread_t thread) { | |
75e24564 | 179 | sem_wait(&pthread_pause_sem); |
d0c03bf1 TM |
180 | //If signal queue is full, we keep retrying |
181 | while(pthread_kill(thread, PTHREAD_XSIG_CONT) == EAGAIN) usleep(1000); | |
75e24564 | 182 | pthread_pause_yield(); |
d0c03bf1 TM |
183 | return 0; |
184 | } | |
185 | ||
186 | void *thread_test() { | |
187 | //Whole process dies if you kill thread immediately before it is pausable | |
188 | //pthread_pause_enable(); | |
189 | while(1) { | |
d0c03bf1 TM |
190 | //Printf() is not async signal safe (because it holds internal mutex), |
191 | //you should call it only with pause disabled! | |
192 | //Will throw helgrind warnings anyway, not sure why... | |
193 | //See: man 7 signal-safety | |
194 | pthread_pause_disable(); | |
195 | printf("Running!\n"); | |
196 | pthread_pause_enable(); | |
75e24564 TM |
197 | |
198 | //Pausing main thread should not cause deadlock | |
199 | //We pause main thread here just to test it is OK | |
200 | pthread_pause(main_thread); | |
3b8f487a | 201 | //pthread_nsleep(0, 1000*1000); |
75e24564 TM |
202 | pthread_unpause(main_thread); |
203 | ||
204 | //Wait for a while | |
3b8f487a | 205 | //pthread_nsleep(0, 1000*1000*100); |
75e24564 | 206 | pthread_unpause(main_thread); |
d0c03bf1 TM |
207 | } |
208 | } | |
209 | ||
210 | int main() { | |
211 | pthread_t t; | |
128ba9ae | 212 | main_thread = pthread_self(); |
d0c03bf1 TM |
213 | pthread_pause_enable(); //Will get inherited by all threads from now on |
214 | //you need to call pthread_pause_enable (or disable) before creating threads, | |
75e24564 | 215 | //otherwise first (un)pause signal will kill whole process |
d0c03bf1 TM |
216 | pthread_create(&t, NULL, thread_test, NULL); |
217 | ||
218 | while(1) { | |
219 | pthread_pause(t); | |
220 | printf("PAUSED\n"); | |
75e24564 | 221 | pthread_sleep(3); |
d0c03bf1 TM |
222 | |
223 | printf("UNPAUSED\n"); | |
224 | pthread_unpause(t); | |
75e24564 TM |
225 | pthread_sleep(1); |
226 | ||
227 | /* | |
228 | pthread_pause_disable(); | |
229 | printf("RUNNING!\n"); | |
230 | pthread_pause_enable(); | |
231 | */ | |
232 | pthread_pause(t); | |
233 | pthread_unpause(t); | |
d0c03bf1 TM |
234 | } |
235 | ||
236 | pthread_join(t, NULL); | |
237 | printf("DIEDED!\n"); | |
238 | } |