| 1 | //Filename: pthread_pause.c |
| 2 | //Author: Tomas 'Harvie' Mudrunka 2021 |
| 3 | //Build: CFLAGS=-lpthread make pthread_pause; ./pthread_pause |
| 4 | //Test: valgrind --tool=helgrind ./pthread_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 | #define _GNU_SOURCE //pthread_yield() needs this |
| 10 | #include <signal.h> |
| 11 | #include <pthread.h> |
| 12 | //#include <pthread_extra.h> |
| 13 | #include <semaphore.h> |
| 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> |
| 20 | #include <time.h> |
| 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 | |
| 26 | pthread_t main_thread; |
| 27 | sem_t pthread_pause_sem; |
| 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 | } |
| 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 |
| 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; |
| 69 | return; |
| 70 | } |
| 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 |
| 84 | //Wait until last (un)pause action gets finished |
| 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); |
| 89 | //pthread_nsleep(0,1); //pthread_yield() is not enough, so we use sleep |
| 90 | pthread_yield(); |
| 91 | } |
| 92 | |
| 93 | void pthread_pause_handler(int signal) { |
| 94 | //Do nothing when there are more signals pending (to cleanup the queue) |
| 95 | //This is no longer needed, since we use semaphore to limit pending signals |
| 96 | /* |
| 97 | sigset_t pending; |
| 98 | sigpending(&pending); |
| 99 | if(sigismember(&pending, PTHREAD_XSIG_STOP)) return; |
| 100 | if(sigismember(&pending, PTHREAD_XSIG_CONT)) return; |
| 101 | */ |
| 102 | |
| 103 | //Post semaphore to confirm that signal is handled |
| 104 | sem_post(&pthread_pause_sem); |
| 105 | //Suspend if needed |
| 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 |
| 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(); |
| 125 | |
| 126 | //Prepare sigset |
| 127 | sigset_t sigset; |
| 128 | sigemptyset(&sigset); |
| 129 | sigaddset(&sigset, PTHREAD_XSIG_STOP); |
| 130 | sigaddset(&sigset, PTHREAD_XSIG_CONT); |
| 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 |
| 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 | |
| 154 | pthread_pause_init(); |
| 155 | |
| 156 | //Make sure all signals are dispatched before we block them |
| 157 | sem_wait(&pthread_pause_sem); |
| 158 | |
| 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); |
| 165 | |
| 166 | sem_post(&pthread_pause_sem); |
| 167 | } |
| 168 | |
| 169 | |
| 170 | int pthread_pause(pthread_t thread) { |
| 171 | sem_wait(&pthread_pause_sem); |
| 172 | //If signal queue is full, we keep retrying |
| 173 | while(pthread_kill(thread, PTHREAD_XSIG_STOP) == EAGAIN) usleep(1000); |
| 174 | pthread_pause_yield(); |
| 175 | return 0; |
| 176 | } |
| 177 | |
| 178 | int pthread_unpause(pthread_t thread) { |
| 179 | sem_wait(&pthread_pause_sem); |
| 180 | //If signal queue is full, we keep retrying |
| 181 | while(pthread_kill(thread, PTHREAD_XSIG_CONT) == EAGAIN) usleep(1000); |
| 182 | pthread_pause_yield(); |
| 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) { |
| 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(); |
| 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); |
| 201 | //pthread_nsleep(0, 1000*1000); |
| 202 | pthread_unpause(main_thread); |
| 203 | |
| 204 | //Wait for a while |
| 205 | //pthread_nsleep(0, 1000*1000*100); |
| 206 | pthread_unpause(main_thread); |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | int main() { |
| 211 | pthread_t t; |
| 212 | main_thread = pthread_self(); |
| 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, |
| 215 | //otherwise first (un)pause signal will kill whole process |
| 216 | pthread_create(&t, NULL, thread_test, NULL); |
| 217 | |
| 218 | while(1) { |
| 219 | pthread_pause(t); |
| 220 | printf("PAUSED\n"); |
| 221 | pthread_sleep(3); |
| 222 | |
| 223 | printf("UNPAUSED\n"); |
| 224 | pthread_unpause(t); |
| 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); |
| 234 | } |
| 235 | |
| 236 | pthread_join(t, NULL); |
| 237 | printf("DIEDED!\n"); |
| 238 | } |