Real-time aware sleep() call?
Asked Answered
B

1

11

I've written a utility to talk to TomTom GPS watches over Bluetooth, and am trying to make it run nicely as a run-and-forget background daemon.

The program periodically communicates with the GPS device and then sleeps for a while until it's needed again.

I've noticed that sleep() interacts strangely with system suspend: when I enter system suspend (laptop running Linux 3.16.0 kernel) and then wake the computer back up, sleep doesn't appear to notice the suspend time. For example, take the following sleep.c:

#include <time.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char**argv)
{
    time_t t=time(NULL);
    printf("sleep at: %s", ctime(&t));

    sleep(atoi(argv[1]));

    t=time(NULL);
    printf("wake at: %s", ctime(&t));
}
dlenski@dlenski-ultra:~$ 

Compile, run, and sleep halfway through;

$ gcc sleep.c -o sleep
$ ./sleep 30
sleep at: Fri Aug 21 21:05:36 2015
<suspend computer for 17 seconds>
wake at: Fri Aug 21 21:06:23 2015

Is there a correct way to suspend program execution in a way that's clock-time-aware rather than system-uptime-aware?

(I've also tried usleep, nanosleep, and alarm and found that they behave similarly.)

UPDATE: setitimer, as suggested by @HuStmpHrrr, sounded quite promising... but appears to have the same problem.

This version pauses for more than the requested 30s when I suspend in the middle of it...

#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>

void sighandler() {}

int main(int argc, char**argv)
{
    time_t t=time(NULL);
    printf("sleep at: %s", ctime(&t));

    signal(SIGALRM, sighandler);
    struct itimerval timer = {.it_interval={0,0},
                              .it_value={atoi(argv[1]),0}};
    setitimer(ITIMER_REAL, &timer, NULL);
    pause();

    t=time(NULL);
    printf("wake at: %s", ctime(&t));
}
Bibliophage answered 22/8, 2015 at 4:22 Comment(10)
what about alarm? alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds. reads promising.Bitartrate
setitimer provides more control. you can pass in ITIMER_REAL to achieve what you want.Bitartrate
setitimer looked very promising but appears to have the same issue in that it doesn't count "downtime." Polling time seems like quite an inelegant solution but possibly the right one :(Bibliophage
I don't know of one, and after a short search I suspect the most reliable method would be to register with the power management system to be notified on suspend/resume. Then you'll just have to check your timers on resume (unless you want them to wake the system from suspended state, which is harder).Guillema
For Linux, it's probably worth looking at pselect. You can set a timeout for periodic returns, wait on 'interesting' events like file (descriptor) activity, as well as signal events. Even if you don't think you need all these features now - you get a much more flexible solution - and your utility looks like something interesting enough that you'll want more features and better response time in the future.Bullard
Thanks all. For the time being, chopping a long interval into 30s chunks and repeatedly sleeping for min((real_time-real_start_time), 30s) seems like a reasonable solution.Bibliophage
Why not use timer_create(CLOCK_MONOTONIC,..), and set the next expiry in absolute time using timer_settime(...,TIMER_ABSTIME,...)? (Or even CLOCK_REALTIME, really.) Such a timer should expire immediately after wake-up if the timer expired during suspend. These are all POSIX.1 too, so no Linux-specific/desktop environment shenanigans needed, either.Breen
@NominalAnimal: Many thanks, that works… if I use both CLOCK_REALTIME and TIMER_ABSTIME, per the docs: "If the value of the CLOCK_REALTIME clock is adjusted while an absolute timer based on that clock is armed, then the expiration of the timer will be appropriately adjusted. Adjustments to the CLOCK_REALTIME clock have no effect on relative timers based on that clock." If you want to write an answer, I'll gladly accept it, otherwise I'll add your solution to my answer.Bibliophage
@DanLenski: Note that CLOCK_MONOTONIC isn't adjusted, ever, and its zero epoch is arbitrary, otherwise it's similar to CLOCK_REALTIME; that's why the passage you quoted does not apply to it. Because it's never adjusted. However, I haven't verified that CLOCK_MONOTONIC progresses during suspend, and that both kinds of timers get triggered immediately after resume, so that's why I used conditionals in my comment. You verified it, you answered it. :)Breen
You're right! CLOCK_MONOTONIC appears to behave similarly to CLOCK_REALTIME with system suspend. Since it doesn't actually simplify the code, and the documentation isn't clear that this behavior is guaranteed, I'll stick with CLOCK_REALTIME for now.Bibliophage
B
7

A few solutions and potential solutions:

Sleep in a loop and check the real elapsed time

There is almost certainly a better way to do it—or there should be!—but here's an acceptable solution for my application for the time being:

  • When needing to sleep for a long interval (>30s), I do sleep(30) repeatedly, checking that the real elapsed time (time(NULL)-start_time) is not longer than the total desired pause.

  • This way, if the system is suspended for, say, 3 hours, while the desired program pause is 1 hour, the extra delay caused by the suspend will not exceed 30 seconds.

Code to do it:

void nullhandler(int signal) {}

int isleep(int seconds, int verbose)
{
    if (verbose) {
        fprintf(stderr, "Sleeping for %d seconds...", seconds);
        fflush(stderr);
    }
    signal(SIGALRM, nullhandler);

    // weird workaround for non-realtime-awareness of sleep:
    // https://mcmap.net/q/1023796/-real-time-aware-sleep-call
    int res=0, elapsed=0;
    for (time_t t=time(NULL); (elapsed<seconds) && (res<=0); elapsed=time(NULL)-t)
        res = sleep((seconds-elapsed > 30) ? 30 : seconds-elapsed);

    signal(SIGALRM, SIG_IGN);
    if (res && verbose)
        fprintf(stderr, "%s\n\n", res ? " woken by signal!" : "");
    return (res>0);
}

Wakeup callback

@Speed8ump suggested registering a callback for system-wakeup notifications. This thread over at AskUbuntu shows how to do it. A good solution, but I'd like to avoid tying my relatively-low-level code too strongly to a particular desktop environment for now.

timer_settime

This is the most elegant and correct solution, in my opinion, and was suggested by @NominalAnimal. It uses timer_settime() and friends from librt.

Note that in order to make this work correctly with system suspend, you must use CLOCK_REALTIME with TIMER_ABSTIME, per the timer_settime documentation:

If the value of the CLOCK_REALTIME clock is adjusted while an absolute timer based on that clock is armed, then the expiration of the timer will be appropriately adjusted. Adjustments to the CLOCK_REALTIME clock have no effect on relative timers based on that clock.

Here's a demo (link with librt, e.g. gcc -lrt -o sleep sleep.c):

#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>
#include <stdlib.h>

void sighandler(int signal) {}

void isleep(int seconds)
{
    timer_t timerid;
    struct sigevent sev = { .sigev_notify=SIGEV_SIGNAL,
                            .sigev_signo=SIGALRM,
                            .sigev_value=(union sigval){ .sival_ptr = &timerid } };
    signal(SIGALRM, sighandler);
    timer_create(CLOCK_REALTIME, &sev, &timerid);

    struct itimerspec its = {.it_interval={0,0}};
    clock_gettime(CLOCK_REALTIME, &its.it_value);
    its.it_value.tv_sec += seconds;
    timer_settime(timerid, TIMER_ABSTIME, &its, NULL);
    pause();
    signal(SIGALRM, SIG_IGN);
}

int main(int argc, char**argv)
{
    time_t t=time(NULL);
    printf("sleep at: %s", ctime(&t));

    isleep(atoi(argv[1]));

    t=time(NULL);
    printf("wake at: %s", ctime(&t));
}
Bibliophage answered 22/8, 2015 at 5:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.