Gitignore
[mirrors/Programs.git] / c / pthread_pause.c
CommitLineData
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 26pthread_t main_thread;
75e24564 27sem_t pthread_pause_sem;
3b8f487a
TM
28pthread_once_t pthread_pause_once_ctrl = PTHREAD_ONCE_INIT;
29
30void 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/
38struct 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
52void 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
71void 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
78void pthread_sleep(time_t s) {
79 pthread_nsleep(s, 0);
80}
81
82void 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
93void 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
115void 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
149void 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
170int 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
178int 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
186void *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
210int 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}
This page took 0.682555 seconds and 4 git commands to generate.