From f995eecf649d535d2d33aad1e7ca988533647c64 Mon Sep 17 00:00:00 2001 From: Arjen Baart Date: Sat, 18 Jul 2020 10:21:40 +0200 Subject: [PATCH] Properly handle sleeps interrupted by receiving a message --- .gitignore | 7 + doc/design.xml | 34 ++- doc/tachyon_class.svg | 577 +++++++++++++++++++++++++++++++++++++++++ src/Tachyon.cpp | 218 ++++++++++++++-- src/Tachyon.h | 16 +- test/Makefile.am | 5 +- test/accellerate | 10 +- test/accellerate_float | 6 +- test/accellerate_multi | 28 ++ test/timespec.cpp | 86 ++++++ 10 files changed, 957 insertions(+), 30 deletions(-) create mode 100644 doc/tachyon_class.svg create mode 100755 test/accellerate_multi create mode 100644 test/timespec.cpp diff --git a/.gitignore b/.gitignore index b4dc9a3..00ba685 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ *.[oa] Makefile.in +.*.swp +autom4te.cache +config.h.in +aclocal.m4 +configure +missing +m4 diff --git a/doc/design.xml b/doc/design.xml index ee4c1c0..eb12e28 100644 --- a/doc/design.xml +++ b/doc/design.xml @@ -80,7 +80,7 @@ while (std::cin >> report) - + The virtual time is calculated by using an offset o and an acceleration factor a in a simple linear function: @@ -94,6 +94,32 @@ The virtual time is calculated by using an offset o and an acceleration factor a T0 is the time at the moment of changing the accelleration. By default, the acceleration is 1.0 and the offset is 0, rendering the virtual time equal to the actual time. +When the acceleration is unequal to 1.0, the virtual time starts to deviate from the actual time, i.e. run slower or faster. +The difference or 'offset' will gruadually change as time progresses. At any point in time, Ta, the offset can be calculated with: + +
+  o' = Tv - Ta = (1 - a) * T0 - (1 - a) * Ta + o
+
+ +If the acceleration, a, is changed, the offset is recalculated to reflect the change in acceleration and +T0 is set to the time at which the acceleration is changed. +
+ + +The nanosleep method will sleep for an interval of time, ts, in virtual time. +This is a period of ts / a in actual time, unless the the accelleration or offset are changed during the sleep. +If either of these parameters is changed, the time to sleep is recalculated from the moment of change to the +moment the sleep period is supposed to end in virtual time. +Receiving a message to change the accelleration or the virtual time interrupts a sleeping process. +At that moment, the sleep will resume with a recalculated period in actual time. +This is the diffrence bewteen at which the sleep ends and the current virtual time divided by the accelleration. + + + +The nanosleep method calculates the virtual time at which the sleep ends and call the sleep_until method to perform +the actual sleep by suspending the process. +Interruptions of the sleep are handled by recalculating the sleep period from the possibly changed parameters +and resuming the sleep if needed. @@ -126,7 +152,11 @@ Return the name of the IPC resource that can be used to control this object. -Return the virtual in second since the Epoch. See also time(2). +Return the virtual in seconds since the Epoch. See also time(2). + + + +Return the virtual in seconds and nanoseconds since the Epoch. See also clock_gettime(2). diff --git a/doc/tachyon_class.svg b/doc/tachyon_class.svg new file mode 100644 index 0000000..93e06c0 --- /dev/null +++ b/doc/tachyon_class.svg @@ -0,0 +1,577 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + queue_name + + + + msg_queue + + + + ~Tachyon + + + + + name + + + + + + Tachyon + + + + + message_notification + + + + expect_message + + + + receive_message + + + + + + + accellerate + + + + + nanosleep + + + + + sleep_until + + + + + accelleration + + + + + offset + + + + T0 + + + + + time + + + + settime + + + + + gettime + + + + + + + + + diff --git a/src/Tachyon.cpp b/src/Tachyon.cpp index da2af54..b9adaa4 100644 --- a/src/Tachyon.cpp +++ b/src/Tachyon.cpp @@ -2,30 +2,109 @@ #include #include +#include #include #include #include #include +#include #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. diff --git a/src/Tachyon.h b/src/Tachyon.h index 04eb354..c073506 100644 --- a/src/Tachyon.h +++ b/src/Tachyon.h @@ -10,8 +10,8 @@ class Tachyon char queue_name[255]; mqd_t msg_queue; - time_t offset; - time_t T0; + timespec offset; + timespec T0; double accelleration; @@ -23,13 +23,23 @@ public: ~Tachyon(); const char * name(void); + void expect_message(void); void receive_message(void); - time_t time(void); + time_t time(void); + timespec gettime(void); + int nanosleep(struct timespec req); int nanosleep(float req); + int sleep_until(timespec ends_at); void settime(time_t sec); void accellerate(double factor); }; +// Operations on timespec structures + +timespec operator + (timespec a, timespec b); +timespec operator - (timespec a, timespec b); + +bool operator < (timespec a, timespec b); diff --git a/test/Makefile.am b/test/Makefile.am index d5e36d4..6801cf9 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,12 +1,13 @@ -TESTS = create sleep accellerate sleep_float accellerate_float +TESTS = create timespec sleep accellerate accellerate_multi sleep_float accellerate_float AM_CPPFLAGS = -I../src LDADD = ../src/.libs/libTachyon.la -lrt -lm -check_PROGRAMS = create sleep sleep_float +check_PROGRAMS = create timespec sleep sleep_float create_SOURCES = create.cpp +timespec_SOURCES = timespec.cpp sleep_SOURCES = sleep.cpp sleep_float_SOURCES = sleep_float.cpp diff --git a/test/accellerate b/test/accellerate index 537d329..f20aabb 100755 --- a/test/accellerate +++ b/test/accellerate @@ -1,5 +1,7 @@ #!/bin/bash - +# +# Test accellerating the virtual time once +# set -m PATH=../src:$PATH @@ -13,9 +15,11 @@ fg END_TIME=`date +%s` duration=$(($END_TIME - $START_TIME)) echo "Elapsed time is $duration" -if [[ $duration -eq 15 ]] || [[ $duration -eq 14 ]] +cat sleep.tmp + +if [[ $duration -eq 5 ]] || [[ $duration -eq 6 ]] then - echo "Elapsed time within 15 seconds" + echo "Elapsed time within 6 seconds" exit 0 fi echo "Elapsed time $duration seconds is unexpected." diff --git a/test/accellerate_float b/test/accellerate_float index f41e818..8ff7325 100755 --- a/test/accellerate_float +++ b/test/accellerate_float @@ -13,9 +13,11 @@ fg END_TIME=`date +%s` duration=$(($END_TIME - $START_TIME)) echo "Elapsed time is $duration" -if [[ $duration -eq 6 ]] || [[ $duration -eq 5 ]] +cat sleep.tmp + +if [[ $duration -eq 1 ]] || [[ $duration -eq 2 ]] then - echo "Elapsed time within 7 seconds" + echo "Elapsed time within 2 seconds" exit 0 fi echo "Elapsed time $duration seconds is unexpected." diff --git a/test/accellerate_multi b/test/accellerate_multi new file mode 100755 index 0000000..9e999b3 --- /dev/null +++ b/test/accellerate_multi @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Test accellerating the virtual time multiple times +# +set -m + +PATH=../src:$PATH + +START_TIME=`date +%s` +./sleep >sleep.tmp & +sleep 1 +read a b c TACHYON_NAME +#include "Tachyon.h" +#include "assert.h" + +/* test timespec operators */ + +bool operator_test(timespec a, timespec b, timespec exp_add, timespec exp_sub, bool exp_lt) +{ + bool ok; + + ok = true; + + timespec add = a + b; + timespec sub = a - b; + bool lt = a < b; + + std::cout << a.tv_sec << "." << a.tv_nsec << " + " << b.tv_sec << "." << b.tv_nsec; + std::cout << " = " << add.tv_sec << "." << add.tv_nsec << "\n"; + std::cout << a.tv_sec << "." << a.tv_nsec << " - " << b.tv_sec << "." << b.tv_nsec; + std::cout << " = " << sub.tv_sec << "." << sub.tv_nsec << "\n"; + std::cout << a.tv_sec << "." << a.tv_nsec << " < " << b.tv_sec << "." << b.tv_nsec; + std::cout << " = " << lt << "\n\n"; + std::cout.flush(); + + ok = ok && add.tv_sec == exp_add.tv_sec && add.tv_nsec == exp_add.tv_nsec; + ok = ok && sub.tv_sec == exp_sub.tv_sec && sub.tv_nsec == exp_sub.tv_nsec; + ok = ok && lt == exp_lt; + + return ok; +} + +int main() +{ + timespec t_add, t_sub; // The expected values + + timespec ts0, ts1; + + ts0.tv_sec = 10; + ts0.tv_nsec = 600000000; + ts1.tv_sec = 2; + ts1.tv_nsec = 500000000; + + t_add.tv_sec = 13; + t_add.tv_nsec = 100000000; + t_sub.tv_sec = 8; + t_sub.tv_nsec = 100000000; + + assert(operator_test(ts0, ts1, t_add, t_sub, false)); + + t_sub.tv_sec = -8; + t_sub.tv_nsec = -100000000; + assert(operator_test(ts1, ts0, t_add, t_sub, true)); + + timespec ts3, ts4; + + ts3.tv_sec = 10; + ts3.tv_nsec = 600000000; + ts4.tv_sec = 2; + ts4.tv_nsec = 700000000; + + t_add.tv_sec = 13; + t_add.tv_nsec = 300000000; + t_sub.tv_sec = 7; + t_sub.tv_nsec = 900000000; + assert(operator_test(ts3, ts4, t_add, t_sub, false)); + + t_sub.tv_sec = -7; + t_sub.tv_nsec = -900000000; + assert(operator_test(ts4, ts3, t_add, t_sub, true)); + + + + timespec ts6, ts7; + + ts6.tv_sec = 10; + ts6.tv_nsec = 600000000; + ts7.tv_sec = 10; + ts7.tv_nsec = 600000999; + + t_add.tv_sec = 21; + t_add.tv_nsec = 200000999; + t_sub.tv_sec = 0; + t_sub.tv_nsec = -999; + assert(operator_test(ts6, ts7, t_add, t_sub, true)); + +} -- 2.20.1