Properly handle sleeps interrupted by receiving a message
[Tachyon.git] / src / Tachyon.cpp
1
2
3 #include <sys/types.h>
4 #include <unistd.h>
5 #include <signal.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <stdlib.h>
9 #include <math.h>
10 #include <errno.h>
11
12 #include "Tachyon.h"
13
14 const int MAX_MESSAGE_SIZE=100;
15
16 /*
17    Functions to calculate timespec structures
18 */
19
20 //  Calculate a + b
21 timespec timespec_add(timespec a, timespec b)
22 {
23    timespec  sum;
24
25    sum.tv_sec   = a.tv_sec  +  b.tv_sec;
26    sum.tv_nsec  = a.tv_nsec + b.tv_nsec;
27
28    if (sum.tv_nsec >= 1000000000)
29    {
30       sum.tv_sec++;
31       sum.tv_nsec -= 1000000000;
32    }
33  
34    return sum;
35 }
36
37 timespec operator + (timespec a, timespec b)
38 {
39    return timespec_add(a, b);
40 }
41
42 //  Calculate a - b
43 timespec timespec_subtract(timespec a, timespec b)
44 {
45    timespec  dif;
46
47    dif.tv_sec  = a.tv_sec  - b.tv_sec;
48    dif.tv_nsec = a.tv_nsec - b.tv_nsec;
49    if (dif.tv_nsec < 0 && dif.tv_sec > 0)
50    {
51       dif.tv_sec--;
52       dif.tv_nsec += 1000000000;
53    }
54    if (dif.tv_nsec > 0 && dif.tv_sec < 0)
55    {
56       dif.tv_sec++;
57       dif.tv_nsec -= 1000000000;
58    }
59
60    return dif;
61 }
62
63 timespec operator - (timespec a, timespec b)
64 {
65    return timespec_subtract(a, b);
66 }
67
68 // Compare a and b. Return < 0 if a < b
69 int timespec_compare(timespec a, timespec b)
70 {
71    int cmp = 0;
72
73    if (a.tv_sec == b.tv_sec)
74    {
75       cmp = a.tv_nsec - b.tv_nsec;
76    }
77    else
78    {
79       cmp = a.tv_sec - b.tv_sec;
80    }
81
82    return cmp;
83 }
84
85 bool operator < (timespec a, timespec b)
86 {
87    return timespec_compare(a, b) < 0;
88 }
89
90 static void message_notification(int signum, siginfo_t *si, void *notused)
91 {
92    Tachyon *tp = (Tachyon *) si->si_value.sival_ptr;
93    tp->receive_message();
94    tp->expect_message();
95 }
96
97 Tachyon::Tachyon()
98 {
99    offset.tv_sec  = 0;
100    offset.tv_nsec = 0;
101    clock_gettime(CLOCK_REALTIME, &T0);
102
103    accelleration = 1.0;
104
105    struct mq_attr  queue_attributes;
106    struct sigevent sev;
107    struct sigaction act;
108
109    sprintf(queue_name, "/Tachyon.%d", getpid());
110    queue_attributes.mq_maxmsg = 2;
111    queue_attributes.mq_msgsize = MAX_MESSAGE_SIZE;
112
113    msg_queue = mq_open(queue_name, O_CREAT | O_RDONLY, S_IRWXU, &queue_attributes);
114
115    //  Setup the signal handler for message arrival
116    expect_message();
117 }
118
119 Tachyon::Tachyon(const char *name)
120 {
121    offset.tv_sec  = 0;
122    offset.tv_nsec = 0;
123    clock_gettime(CLOCK_REALTIME, &T0);
124
125    accelleration = 1.0;
126
127    queue_name[0] = '\0';
128    msg_queue = mq_open(name, O_WRONLY);
129 }
130
131 Tachyon::~Tachyon()
132 {
133    mq_close(msg_queue);
134    if (queue_name[0] != '\0')
135    {
136       mq_unlink(queue_name);
137    }
138 }
139
140 const char * Tachyon::name(void)
141 {
142    return queue_name;
143 }
144
145 // Prepare notification and signal handling for ther next message
146
147 void Tachyon::expect_message(void)
148 {
149    struct sigevent sev;
150    struct sigaction act;
151
152    //  Setup the signal handler for message arrival
153
154    act.sa_sigaction = message_notification;
155    act.sa_flags     = SA_SIGINFO;
156    sigaction(SIGUSR1, &act, NULL);
157
158    sev.sigev_notify = SIGEV_SIGNAL;
159    sev.sigev_signo  = SIGUSR1;
160    sev.sigev_notify_attributes = NULL;
161    sev.sigev_value.sival_ptr = this;  
162    mq_notify(msg_queue, &sev);
163 }
164
165 void Tachyon::receive_message(void)
166 {
167    char message[MAX_MESSAGE_SIZE];
168    int  message_size;
169
170    //printf("Receiving message.\n");
171    message_size = mq_receive(msg_queue, message, MAX_MESSAGE_SIZE, NULL);
172    message[message_size] = '\0';
173
174    //printf("The message is %s.\n", message);
175    switch (message[0])
176    {
177    case 'A':
178       accellerate(atof(message+1));
179       break;
180
181    default:
182       // Invalid message.
183       break;
184    }
185 }
186
187 time_t Tachyon::time(void)
188 {
189 }
190
191 // calculate the currect virtual time
192
193 timespec Tachyon::gettime(void)
194 {
195    timespec actual_time;
196    timespec virtual_time;
197    timespec difference;
198
199    double seconds;
200    double nanoseconds;
201    double overflow;
202
203    clock_gettime(CLOCK_REALTIME, &actual_time);
204    //printf("  Actual time: %d,%d, T0 = %d,%d\n", actual_time.tv_sec, actual_time.tv_nsec, T0.tv_sec, T0.tv_nsec);
205
206    //  Tvirtual = T0 + acc * (Tactual - T0) + offset
207
208    difference = timespec_subtract(actual_time, T0);
209    //printf("  Ta - T0 = %d,%d\n", difference.tv_sec, difference.tv_nsec);
210
211    seconds     = difference.tv_sec  * accelleration;
212    nanoseconds = difference.tv_nsec * accelleration;
213    overflow    = trunc(nanoseconds * 1.0e-9);
214    seconds     += overflow;
215    nanoseconds -= overflow * 1.0e9;
216
217    difference.tv_sec  = trunc(seconds);
218    difference.tv_nsec = trunc(nanoseconds);
219    //printf("  a * (Ta - T0) = %d,%d\n", difference.tv_sec, difference.tv_nsec);
220
221    virtual_time   = timespec_add(T0, difference);
222    virtual_time   = timespec_add(virtual_time, offset);
223  
224    return virtual_time;
225 }
226
227 int Tachyon::nanosleep(struct timespec req)
228 {
229    double seconds;
230    double nanoseconds;
231    int    sleep_return;
232
233    timespec virt_time;
234
235    virt_time = gettime();
236
237    return sleep_until(timespec_add(virt_time, req));
238 }
239
240 int Tachyon::nanosleep(float req)
241 {
242    double seconds;
243    double nanoseconds;
244    struct timespec req_t;
245    int    sleep_return;
246
247    timespec virt_time;
248
249    virt_time = gettime();
250
251    seconds     = req / accelleration;
252    nanoseconds = (seconds - trunc(seconds)) * 1.0e9;
253
254    req_t.tv_sec  = trunc(seconds);
255    req_t.tv_nsec = trunc(nanoseconds);
256
257    return sleep_until(virt_time + req_t);
258 }
259
260 int Tachyon::sleep_until(timespec ends_at)
261 {
262    double seconds;
263    double nanoseconds;
264    int    sleep_return;
265    bool   error;
266
267    timespec  virt_time;
268    timespec  remaining;
269
270    virt_time = gettime();
271    while (!error && virt_time < ends_at)
272    {
273       timespec period;
274
275       period      = ends_at - virt_time;
276       //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);
277       //printf("Virtual period = %d,%d\n", period.tv_sec, period.tv_nsec);
278
279       seconds     = period.tv_sec  / accelleration;
280       nanoseconds = period.tv_nsec / accelleration;
281       nanoseconds += (seconds - trunc(seconds)) * 1.0e9;
282       period.tv_sec  = trunc(seconds);
283       period.tv_nsec = trunc(nanoseconds);
284
285       //printf("Sleeping for : %d,%d\n", period.tv_sec, period.tv_nsec);
286       errno = 0;
287       sleep_return = ::nanosleep(&period, &remaining);
288       //printf("nanosleep returns %d, error = %s\n", sleep_return, strerror(errno));
289
290       error = sleep_return == -1 && errno != EINTR;
291       virt_time = gettime();
292    }
293
294    return sleep_return;
295 }
296
297 void Tachyon::settime(time_t sec)
298 {
299 }
300
301 void Tachyon::accellerate(double factor)
302 {
303    timespec   actual_time, virtual_time;
304
305    clock_gettime(CLOCK_REALTIME, &actual_time);
306    virtual_time = gettime();
307
308    T0 = actual_time;
309    offset = virtual_time - actual_time;
310
311    accelleration = factor;
312
313    //  send to the Tachyon object in another process.
314    if (queue_name[0] == '\0')
315    {
316       char message[MAX_MESSAGE_SIZE];
317
318       sprintf(message, "A%f", accelleration);
319       mq_send(msg_queue, message, strlen(message), 100);
320    }
321 }