| 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 | #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 | } |