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 | ||
9 | #include <signal.h> | |
10 | #include <pthread.h> | |
11 | //#include <pthread_extra.h> | |
12 | #include <stdio.h> | |
13 | #include <stdlib.h> | |
14 | #include <assert.h> | |
15 | #include <unistd.h> | |
16 | #include <errno.h> | |
17 | #include <sys/resource.h> | |
18 | ||
19 | #define PTHREAD_XSIG_STOP (SIGRTMIN+0) | |
20 | #define PTHREAD_XSIG_CONT (SIGRTMIN+1) | |
21 | #define PTHREAD_XSIGRTMIN (SIGRTMIN+2) //First unused RT signal | |
22 | ||
128ba9ae TM |
23 | pthread_t main_thread; |
24 | ||
d0c03bf1 TM |
25 | void pthread_pause_handler(int signal) { |
26 | //Do nothing when there are more signals pending (to cleanup the queue) | |
27 | sigset_t pending; | |
28 | sigpending(&pending); | |
29 | if(sigismember(&pending, PTHREAD_XSIG_STOP)) return; | |
30 | if(sigismember(&pending, PTHREAD_XSIG_CONT)) return; | |
31 | ||
32 | if(signal == PTHREAD_XSIG_STOP) { | |
33 | sigset_t sigset; | |
34 | sigfillset(&sigset); | |
35 | sigdelset(&sigset, PTHREAD_XSIG_STOP); | |
36 | sigdelset(&sigset, PTHREAD_XSIG_CONT); | |
37 | sigsuspend(&sigset); //Wait for next signal | |
38 | } else return; | |
39 | } | |
40 | ||
41 | void pthread_pause_enable() { | |
42 | //Having signal queue too deep might not be necessary | |
43 | //It can be limited using RLIMIT_SIGPENDING | |
44 | //You can get runtime SigQ stats using following command: | |
45 | //grep -i sig /proc/$(pgrep binary)/status | |
46 | struct rlimit sigq = {.rlim_cur = 32, .rlim_max=32}; | |
47 | setrlimit(RLIMIT_SIGPENDING, &sigq); | |
48 | ||
49 | //Register signal handlers | |
50 | signal(PTHREAD_XSIG_STOP, pthread_pause_handler); | |
51 | signal(PTHREAD_XSIG_CONT, pthread_pause_handler); | |
52 | ||
53 | //UnBlock signals | |
54 | sigset_t sigset; | |
55 | sigemptyset(&sigset); | |
56 | sigaddset(&sigset, PTHREAD_XSIG_STOP); | |
57 | sigaddset(&sigset, PTHREAD_XSIG_CONT); | |
58 | pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); | |
59 | } | |
60 | ||
61 | void pthread_pause_disable() { | |
62 | //This is important for when you want to do some signal unsafe stuff | |
63 | //Eg.: locking mutex, calling printf() which has internal mutex, etc... | |
64 | //After unlocking mutex, you can enable pause again. | |
65 | ||
66 | //Block signals | |
67 | sigset_t sigset; | |
68 | sigemptyset(&sigset); | |
69 | sigaddset(&sigset, PTHREAD_XSIG_STOP); | |
70 | sigaddset(&sigset, PTHREAD_XSIG_CONT); | |
71 | pthread_sigmask(SIG_BLOCK, &sigset, NULL); | |
72 | } | |
73 | ||
74 | ||
75 | int pthread_pause(pthread_t thread) { | |
76 | //If signal queue is full, we keep retrying | |
77 | while(pthread_kill(thread, PTHREAD_XSIG_STOP) == EAGAIN) usleep(1000); | |
78 | return 0; | |
79 | } | |
80 | ||
81 | int pthread_unpause(pthread_t thread) { | |
82 | //If signal queue is full, we keep retrying | |
83 | while(pthread_kill(thread, PTHREAD_XSIG_CONT) == EAGAIN) usleep(1000); | |
84 | return 0; | |
85 | } | |
86 | ||
87 | void *thread_test() { | |
88 | //Whole process dies if you kill thread immediately before it is pausable | |
89 | //pthread_pause_enable(); | |
90 | while(1) { | |
91 | usleep(1000*300); | |
92 | //Printf() is not async signal safe (because it holds internal mutex), | |
93 | //you should call it only with pause disabled! | |
94 | //Will throw helgrind warnings anyway, not sure why... | |
95 | //See: man 7 signal-safety | |
96 | pthread_pause_disable(); | |
97 | printf("Running!\n"); | |
98 | pthread_pause_enable(); | |
128ba9ae TM |
99 | //pthread_pause(main_thread); |
100 | //pthread_unpause(main_thread); | |
d0c03bf1 TM |
101 | } |
102 | } | |
103 | ||
104 | int main() { | |
105 | pthread_t t; | |
128ba9ae | 106 | main_thread = pthread_self(); |
d0c03bf1 TM |
107 | pthread_pause_enable(); //Will get inherited by all threads from now on |
108 | //you need to call pthread_pause_enable (or disable) before creating threads, | |
109 | //otherwise first signal will kill whole process | |
110 | pthread_create(&t, NULL, thread_test, NULL); | |
111 | ||
112 | while(1) { | |
113 | pthread_pause(t); | |
114 | printf("PAUSED\n"); | |
115 | sleep(3); | |
116 | ||
117 | printf("UNPAUSED\n"); | |
118 | pthread_unpause(t); | |
119 | sleep(1); | |
120 | } | |
121 | ||
122 | pthread_join(t, NULL); | |
123 | printf("DIEDED!\n"); | |
124 | } |