Properly handle sleeps interrupted by receiving a message
[Tachyon.git] / src / Tachyon.cpp
index da2af54..b9adaa4 100644 (file)
 
 #include <sys/types.h>
 #include <unistd.h>
+#include <signal.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <math.h>
+#include <errno.h>
 
 #include "Tachyon.h"
 
 const int MAX_MESSAGE_SIZE=100;
 
-static void message_notification(union sigval note)
+/*
+   Functions to calculate timespec structures
+*/
+
+//  Calculate a + b
+timespec timespec_add(timespec a, timespec b)
+{
+   timespec  sum;
+
+   sum.tv_sec   = a.tv_sec  +  b.tv_sec;
+   sum.tv_nsec  = a.tv_nsec + b.tv_nsec;
+
+   if (sum.tv_nsec >= 1000000000)
+   {
+      sum.tv_sec++;
+      sum.tv_nsec -= 1000000000;
+   }
+   return sum;
+}
+
+timespec operator + (timespec a, timespec b)
+{
+   return timespec_add(a, b);
+}
+
+//  Calculate a - b
+timespec timespec_subtract(timespec a, timespec b)
 {
-   Tachyon *tp = (Tachyon *) note.sival_ptr;
+   timespec  dif;
+
+   dif.tv_sec  = a.tv_sec  - b.tv_sec;
+   dif.tv_nsec = a.tv_nsec - b.tv_nsec;
+   if (dif.tv_nsec < 0 && dif.tv_sec > 0)
+   {
+      dif.tv_sec--;
+      dif.tv_nsec += 1000000000;
+   }
+   if (dif.tv_nsec > 0 && dif.tv_sec < 0)
+   {
+      dif.tv_sec++;
+      dif.tv_nsec -= 1000000000;
+   }
+
+   return dif;
+}
+
+timespec operator - (timespec a, timespec b)
+{
+   return timespec_subtract(a, b);
+}
+
+// Compare a and b. Return < 0 if a < b
+int timespec_compare(timespec a, timespec b)
+{
+   int cmp = 0;
+
+   if (a.tv_sec == b.tv_sec)
+   {
+      cmp = a.tv_nsec - b.tv_nsec;
+   }
+   else
+   {
+      cmp = a.tv_sec - b.tv_sec;
+   }
+
+   return cmp;
+}
+
+bool operator < (timespec a, timespec b)
+{
+   return timespec_compare(a, b) < 0;
+}
+
+static void message_notification(int signum, siginfo_t *si, void *notused)
+{
+   Tachyon *tp = (Tachyon *) si->si_value.sival_ptr;
    tp->receive_message();
+   tp->expect_message();
 }
 
 Tachyon::Tachyon()
 {
-   offset = 0;
-   T0     = 0;
+   offset.tv_sec  = 0;
+   offset.tv_nsec = 0;
+   clock_gettime(CLOCK_REALTIME, &T0);
 
    accelleration = 1.0;
 
    struct mq_attr  queue_attributes;
    struct sigevent sev;
+   struct sigaction act;
 
    sprintf(queue_name, "/Tachyon.%d", getpid());
    queue_attributes.mq_maxmsg = 2;
@@ -33,17 +112,15 @@ Tachyon::Tachyon()
 
    msg_queue = mq_open(queue_name, O_CREAT | O_RDONLY, S_IRWXU, &queue_attributes);
 
-   sev.sigev_notify = SIGEV_THREAD;
-   sev.sigev_notify_function = message_notification;
-   sev.sigev_notify_attributes = NULL;
-   sev.sigev_value.sival_ptr = this;  
-   mq_notify(msg_queue, &sev);
+   //  Setup the signal handler for message arrival
+   expect_message();
 }
 
 Tachyon::Tachyon(const char *name)
 {
-   offset = 0;
-   T0     = 0;
+   offset.tv_sec  = 0;
+   offset.tv_nsec = 0;
+   clock_gettime(CLOCK_REALTIME, &T0);
 
    accelleration = 1.0;
 
@@ -65,6 +142,26 @@ const char * Tachyon::name(void)
    return queue_name;
 }
 
+// Prepare notification and signal handling for ther next message
+
+void Tachyon::expect_message(void)
+{
+   struct sigevent sev;
+   struct sigaction act;
+
+   //  Setup the signal handler for message arrival
+
+   act.sa_sigaction = message_notification;
+   act.sa_flags     = SA_SIGINFO;
+   sigaction(SIGUSR1, &act, NULL);
+
+   sev.sigev_notify = SIGEV_SIGNAL;
+   sev.sigev_signo  = SIGUSR1;
+   sev.sigev_notify_attributes = NULL;
+   sev.sigev_value.sival_ptr = this;  
+   mq_notify(msg_queue, &sev);
+}
+
 void Tachyon::receive_message(void)
 {
    char message[MAX_MESSAGE_SIZE];
@@ -91,18 +188,53 @@ time_t Tachyon::time(void)
 {
 }
 
+// calculate the currect virtual time
+
+timespec Tachyon::gettime(void)
+{
+   timespec actual_time;
+   timespec virtual_time;
+   timespec difference;
+
+   double seconds;
+   double nanoseconds;
+   double overflow;
+
+   clock_gettime(CLOCK_REALTIME, &actual_time);
+   //printf("  Actual time: %d,%d, T0 = %d,%d\n", actual_time.tv_sec, actual_time.tv_nsec, T0.tv_sec, T0.tv_nsec);
+
+   //  Tvirtual = T0 + acc * (Tactual - T0) + offset
+
+   difference = timespec_subtract(actual_time, T0);
+   //printf("  Ta - T0 = %d,%d\n", difference.tv_sec, difference.tv_nsec);
+
+   seconds     = difference.tv_sec  * accelleration;
+   nanoseconds = difference.tv_nsec * accelleration;
+   overflow    = trunc(nanoseconds * 1.0e-9);
+   seconds     += overflow;
+   nanoseconds -= overflow * 1.0e9;
+
+   difference.tv_sec  = trunc(seconds);
+   difference.tv_nsec = trunc(nanoseconds);
+   //printf("  a * (Ta - T0) = %d,%d\n", difference.tv_sec, difference.tv_nsec);
+
+   virtual_time   = timespec_add(T0, difference);
+   virtual_time   = timespec_add(virtual_time, offset);
+   return virtual_time;
+}
+
 int Tachyon::nanosleep(struct timespec req)
 {
    double seconds;
    double nanoseconds;
+   int    sleep_return;
 
-   seconds     = req.tv_sec  / accelleration;
-   nanoseconds = req.tv_nsec / accelleration;
-   nanoseconds += (seconds - trunc(seconds)) * 1.0e9;
-   req.tv_sec  = trunc(seconds);
-   req.tv_nsec = trunc(nanoseconds);
+   timespec virt_time;
 
-   ::nanosleep(&req, NULL);
+   virt_time = gettime();
+
+   return sleep_until(timespec_add(virt_time, req));
 }
 
 int Tachyon::nanosleep(float req)
@@ -110,6 +242,11 @@ int Tachyon::nanosleep(float req)
    double seconds;
    double nanoseconds;
    struct timespec req_t;
+   int    sleep_return;
+
+   timespec virt_time;
+
+   virt_time = gettime();
 
    seconds     = req / accelleration;
    nanoseconds = (seconds - trunc(seconds)) * 1.0e9;
@@ -117,7 +254,44 @@ int Tachyon::nanosleep(float req)
    req_t.tv_sec  = trunc(seconds);
    req_t.tv_nsec = trunc(nanoseconds);
 
-   ::nanosleep(&req_t, NULL);
+   return sleep_until(virt_time + req_t);
+}
+
+int Tachyon::sleep_until(timespec ends_at)
+{
+   double seconds;
+   double nanoseconds;
+   int    sleep_return;
+   bool   error;
+
+   timespec  virt_time;
+   timespec  remaining;
+
+   virt_time = gettime();
+   while (!error && virt_time < ends_at)
+   {
+      timespec period;
+
+      period      = ends_at - virt_time;
+      //printf("Tv = %d,%d.  sleep end at %d,%d\n", virt_time.tv_sec, virt_time.tv_nsec, ends_at.tv_sec, ends_at.tv_nsec);
+      //printf("Virtual period = %d,%d\n", period.tv_sec, period.tv_nsec);
+
+      seconds     = period.tv_sec  / accelleration;
+      nanoseconds = period.tv_nsec / accelleration;
+      nanoseconds += (seconds - trunc(seconds)) * 1.0e9;
+      period.tv_sec  = trunc(seconds);
+      period.tv_nsec = trunc(nanoseconds);
+
+      //printf("Sleeping for : %d,%d\n", period.tv_sec, period.tv_nsec);
+      errno = 0;
+      sleep_return = ::nanosleep(&period, &remaining);
+      //printf("nanosleep returns %d, error = %s\n", sleep_return, strerror(errno));
+
+      error = sleep_return == -1 && errno != EINTR;
+      virt_time = gettime();
+   }
+
+   return sleep_return;
 }
 
 void Tachyon::settime(time_t sec)
@@ -126,6 +300,14 @@ void Tachyon::settime(time_t sec)
 
 void Tachyon::accellerate(double factor)
 {
+   timespec   actual_time, virtual_time;
+
+   clock_gettime(CLOCK_REALTIME, &actual_time);
+   virtual_time = gettime();
+
+   T0 = actual_time;
+   offset = virtual_time - actual_time;
+
    accelleration = factor;
 
    //  send to the Tachyon object in another process.