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