| 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 | |
| 29 | void 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 | |
| 38 | void 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 | |
| 45 | void pthread_sleep(time_t s) { |
| 46 | pthread_nsleep(s, 0); |
| 47 | } |
| 48 | |
| 49 | void 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 | } |
| 58 | |
| 59 | void pthread_pause_handler(int signal) { |
| 60 | //Do nothing when there are more signals pending (to cleanup the queue) |
| 61 | //This is no longer needed, since we use semaphore to limit pending signals |
| 62 | /* |
| 63 | sigset_t pending; |
| 64 | sigpending(&pending); |
| 65 | if(sigismember(&pending, PTHREAD_XSIG_STOP)) return; |
| 66 | if(sigismember(&pending, PTHREAD_XSIG_CONT)) return; |
| 67 | */ |
| 68 | |
| 69 | sem_post(&pthread_pause_sem); |
| 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 | |
| 79 | void 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 | |
| 87 | //Prepare sigset |
| 88 | sigset_t sigset; |
| 89 | sigemptyset(&sigset); |
| 90 | sigaddset(&sigset, PTHREAD_XSIG_STOP); |
| 91 | sigaddset(&sigset, PTHREAD_XSIG_CONT); |
| 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 |
| 107 | pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); |
| 108 | } |
| 109 | |
| 110 | void 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 | |
| 115 | //Make sure all signals are dispatched before we block them |
| 116 | sem_wait(&pthread_pause_sem); |
| 117 | |
| 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); |
| 124 | |
| 125 | sem_post(&pthread_pause_sem); |
| 126 | } |
| 127 | |
| 128 | |
| 129 | int pthread_pause(pthread_t thread) { |
| 130 | sem_wait(&pthread_pause_sem); |
| 131 | //If signal queue is full, we keep retrying |
| 132 | while(pthread_kill(thread, PTHREAD_XSIG_STOP) == EAGAIN) usleep(1000); |
| 133 | pthread_pause_yield(); |
| 134 | return 0; |
| 135 | } |
| 136 | |
| 137 | int pthread_unpause(pthread_t thread) { |
| 138 | sem_wait(&pthread_pause_sem); |
| 139 | //If signal queue is full, we keep retrying |
| 140 | while(pthread_kill(thread, PTHREAD_XSIG_CONT) == EAGAIN) usleep(1000); |
| 141 | pthread_pause_yield(); |
| 142 | return 0; |
| 143 | } |
| 144 | |
| 145 | void *thread_test() { |
| 146 | //Whole process dies if you kill thread immediately before it is pausable |
| 147 | //pthread_pause_enable(); |
| 148 | while(1) { |
| 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(); |
| 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); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | int main() { |
| 170 | pthread_t t; |
| 171 | sem_init(&pthread_pause_sem, 0, 1); //TODO: hide somewhere using pthred_once() |
| 172 | main_thread = pthread_self(); |
| 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, |
| 175 | //otherwise first (un)pause signal will kill whole process |
| 176 | pthread_create(&t, NULL, thread_test, NULL); |
| 177 | |
| 178 | while(1) { |
| 179 | pthread_pause(t); |
| 180 | printf("PAUSED\n"); |
| 181 | pthread_sleep(3); |
| 182 | |
| 183 | printf("UNPAUSED\n"); |
| 184 | pthread_unpause(t); |
| 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); |
| 194 | } |
| 195 | |
| 196 | pthread_join(t, NULL); |
| 197 | printf("DIEDED!\n"); |
| 198 | } |