Absolute sleep
[mirrors/Programs.git] / c / pthread_pause.c
index 7d6ed6af01a57daef4c91d417ef3eb08df15dc04..6e46ea525f88cacb7ea12ec4e4694be9754040d2 100644 (file)
 //I've wrote this code as excercise to solve following stack overflow question:
 // https://stackoverflow.com/questions/9397068/how-to-pause-a-pthread-any-time-i-want/68119116#68119116
 
+#define _GNU_SOURCE //pthread_yield() needs this
 #include <signal.h>
 #include <pthread.h>
 //#include <pthread_extra.h>
+#include <semaphore.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <assert.h>
 #include <unistd.h>
 #include <errno.h>
 #include <sys/resource.h>
+#include <time.h>
 
 #define PTHREAD_XSIG_STOP (SIGRTMIN+0)
 #define PTHREAD_XSIG_CONT (SIGRTMIN+1)
 #define PTHREAD_XSIGRTMIN (SIGRTMIN+2) //First unused RT signal
 
+pthread_t main_thread;
+sem_t pthread_pause_sem;
+pthread_once_t pthread_pause_once_ctrl = PTHREAD_ONCE_INIT;
+
+void pthread_pause_once(void) {
+       sem_init(&pthread_pause_sem, 0, 1);
+}
+
+#define pthread_pause_init() (pthread_once(&pthread_pause_once_ctrl, &pthread_pause_once))
+
+#define NSEC_PER_SEC (1000*1000*1000)
+// timespec_normalise() from https://github.com/solemnwarning/timespec/
+struct timespec timespec_normalise(struct timespec ts)
+{
+       while(ts.tv_nsec >= NSEC_PER_SEC) {
+               ++(ts.tv_sec); ts.tv_nsec -= NSEC_PER_SEC;
+       }
+       while(ts.tv_nsec <= -NSEC_PER_SEC) {
+               --(ts.tv_sec); ts.tv_nsec += NSEC_PER_SEC;
+       }
+       if(ts.tv_nsec < 0) { // Negative nanoseconds isn't valid according to POSIX.
+               --(ts.tv_sec); ts.tv_nsec = (NSEC_PER_SEC + ts.tv_nsec);
+       }
+       return ts;
+}
+
+void pthread_nanosleep(struct timespec t) {
+       //Sleep calls on Linux get interrupted by signals, causing premature wake
+       //Pthread (un)pause is built using signals
+       //Therefore we need self-restarting sleep implementation
+       //IO timeouts are restarted by SA_RESTART, but sleeps do need explicit restart
+       //We also need to sleep using absolute time, because relative time is paused
+       //You should use this in any thread that gets (un)paused
+
+       struct timespec wake;
+       clock_gettime(CLOCK_MONOTONIC, &wake);
+
+       t = timespec_normalise(t);
+       wake.tv_sec += t.tv_sec;
+       wake.tv_nsec += t.tv_nsec;
+       wake = timespec_normalise(wake);
+
+       while(clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &wake, NULL)) if(errno!=EINTR) break;
+       return;
+}
+void pthread_nsleep(time_t s, long ns) {
+       struct timespec t;
+       t.tv_sec = s;
+       t.tv_nsec = ns;
+       pthread_nanosleep(t);
+}
+
+void pthread_sleep(time_t s) {
+       pthread_nsleep(s, 0);
+}
+
+void pthread_pause_yield() {
+       //Call this to give other threads chance to run
+       //Wait until last (un)pause action gets finished
+       sem_wait(&pthread_pause_sem);
+       sem_post(&pthread_pause_sem);
+        //usleep(0);
+       //nanosleep(&((const struct timespec){.tv_sec=0,.tv_nsec=1}), NULL);
+       //pthread_nsleep(0,1); //pthread_yield() is not enough, so we use sleep
+       pthread_yield();
+}
+
 void pthread_pause_handler(int signal) {
     //Do nothing when there are more signals pending (to cleanup the queue)
+    //This is no longer needed, since we use semaphore to limit pending signals
+    /*
     sigset_t pending;
     sigpending(&pending);
     if(sigismember(&pending, PTHREAD_XSIG_STOP)) return;
     if(sigismember(&pending, PTHREAD_XSIG_CONT)) return;
+    */
 
+    //Post semaphore to confirm that signal is handled
+    sem_post(&pthread_pause_sem);
+    //Suspend if needed
     if(signal == PTHREAD_XSIG_STOP) {
        sigset_t sigset;
        sigfillset(&sigset);
@@ -41,18 +117,32 @@ void pthread_pause_enable() {
     //It can be limited using RLIMIT_SIGPENDING
     //You can get runtime SigQ stats using following command:
     //grep -i sig /proc/$(pgrep binary)/status
-    struct rlimit sigq = {.rlim_cur = 32, .rlim_max=32};
-    setrlimit(RLIMIT_SIGPENDING, &sigq);
+    //This is no longer needed, since we use semaphores
+    //struct rlimit sigq = {.rlim_cur = 32, .rlim_max=32};
+    //setrlimit(RLIMIT_SIGPENDING, &sigq);
 
-    //Register signal handlers
-    signal(PTHREAD_XSIG_STOP, pthread_pause_handler);
-    signal(PTHREAD_XSIG_CONT, pthread_pause_handler);
+    pthread_pause_init();
 
-    //UnBlock signals
+    //Prepare sigset
     sigset_t sigset;
     sigemptyset(&sigset);
     sigaddset(&sigset, PTHREAD_XSIG_STOP);
     sigaddset(&sigset, PTHREAD_XSIG_CONT);
+
+    //Register signal handlers
+    //signal(PTHREAD_XSIG_STOP, pthread_pause_handler);
+    //signal(PTHREAD_XSIG_CONT, pthread_pause_handler);
+    //We now use sigaction() instead of signal(), because it supports SA_RESTART
+    const struct sigaction pause_sa = {
+               .sa_handler = pthread_pause_handler,
+               .sa_mask = sigset,
+               .sa_flags = SA_RESTART,
+               .sa_restorer = NULL
+    };
+    sigaction(PTHREAD_XSIG_STOP, &pause_sa, NULL);
+    sigaction(PTHREAD_XSIG_CONT, &pause_sa, NULL);
+
+    //UnBlock signals
     pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
 }
 
@@ -61,24 +151,35 @@ void pthread_pause_disable() {
        //Eg.: locking mutex, calling printf() which has internal mutex, etc...
        //After unlocking mutex, you can enable pause again.
 
+       pthread_pause_init();
+
+       //Make sure all signals are dispatched before we block them
+       sem_wait(&pthread_pause_sem);
+
        //Block signals
        sigset_t sigset;
        sigemptyset(&sigset);
        sigaddset(&sigset, PTHREAD_XSIG_STOP);
        sigaddset(&sigset, PTHREAD_XSIG_CONT);
        pthread_sigmask(SIG_BLOCK, &sigset, NULL);
+
+       sem_post(&pthread_pause_sem);
 }
 
 
 int pthread_pause(pthread_t thread) {
+       sem_wait(&pthread_pause_sem);
        //If signal queue is full, we keep retrying
        while(pthread_kill(thread, PTHREAD_XSIG_STOP) == EAGAIN) usleep(1000);
+       pthread_pause_yield();
        return 0;
 }
 
 int pthread_unpause(pthread_t thread) {
+       sem_wait(&pthread_pause_sem);
        //If signal queue is full, we keep retrying
        while(pthread_kill(thread, PTHREAD_XSIG_CONT) == EAGAIN) usleep(1000);
+       pthread_pause_yield();
        return 0;
 }
 
@@ -86,7 +187,6 @@ void *thread_test() {
     //Whole process dies if you kill thread immediately before it is pausable
     //pthread_pause_enable();
     while(1) {
-        usleep(1000*300);
        //Printf() is not async signal safe (because it holds internal mutex),
        //you should call it only with pause disabled!
        //Will throw helgrind warnings anyway, not sure why...
@@ -94,24 +194,43 @@ void *thread_test() {
        pthread_pause_disable();
         printf("Running!\n");
        pthread_pause_enable();
+
+       //Pausing main thread should not cause deadlock
+       //We pause main thread here just to test it is OK
+       pthread_pause(main_thread);
+       //pthread_nsleep(0, 1000*1000);
+       pthread_unpause(main_thread);
+
+       //Wait for a while
+       //pthread_nsleep(0, 1000*1000*100);
+       pthread_unpause(main_thread);
     }
 }
 
 int main() {
     pthread_t t;
+    main_thread = pthread_self();
     pthread_pause_enable(); //Will get inherited by all threads from now on
     //you need to call pthread_pause_enable (or disable) before creating threads,
-    //otherwise first signal will kill whole process
+    //otherwise first (un)pause signal will kill whole process
     pthread_create(&t, NULL, thread_test, NULL);
 
     while(1) {
         pthread_pause(t);
         printf("PAUSED\n");
-        sleep(3);
+        pthread_sleep(3);
 
         printf("UNPAUSED\n");
         pthread_unpause(t);
-        sleep(1);
+        pthread_sleep(1);
+
+       /*
+       pthread_pause_disable();
+        printf("RUNNING!\n");
+       pthread_pause_enable();
+       */
+       pthread_pause(t);
+       pthread_unpause(t);
     }
 
     pthread_join(t, NULL);
This page took 0.175684 seconds and 4 git commands to generate.