pthread_pause now more robust with sleep restart, SA_RESTART and semaphores
[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
TM
27sem_t pthread_pause_sem;
28
29void 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
38void 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
45void pthread_sleep(time_t s) {
46 pthread_nsleep(s, 0);
47}
48
49void 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
59void 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
79void 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
110void 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
129int 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
137int 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
145void *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
169int 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}
This page took 0.301241 seconds and 4 git commands to generate.