Measurements at
Regular Intervals in Perl

I want to trigger measurements at defined intervals from my Perl scripts. Because there are unknown (and variable) delays in getting data from external instruments, a simple loop with a hand-tuned delay via a "sleep" call is hard to calibrate and any error accumulates over time.

Searching the web, I found that Perl implements an "alarm" function that calls a signal handling routine when the timer goes off. There are also a more advanced selection of timer functions in the very useful Time::HiRes module. However, I didn't find any examples showing how the alarm can do something useful (for my definition of "useful", of course). Thanks to help from the folks at Perl Monks, I finally figured it out.

After I learned how the alarm works through a signal handler, my biggest problem was how to make my script wait for the alarm to fire, without using a lot of CPU time in the process. The simple answer is to use a sleep() call within the loop to cause a delay. However, it's apparently not safe to use sleep and a timer at the same time, as one may reset the other. It turned out that the pause function in the in the Perl POSIX module would solve that problem.

So, here's a simple skeleton of what I ended up with. This script will wait until the alarm fires, do the necessary tasks, and then wait until the alarm fires again. The main caveat is that the tasks must take less time than the interval; if the script is not in the pause mode when the alarm fires, when it finally reaches the pause statement it will wait unti the next alarm. As a result you could end up with double (or an even higher factor) the desired delay.

#!/usr/bin/perl -w

use POSIX qw(pause);
use Time::HiRes qw(setitimer ITIMER_REAL);

# how often do we trigger (seconds)?
my $first_interval = 10;
my $interval = 30;

# signal handler is empty
$SIG{ALRM} = sub { };

# first value is the initial wait, second is the wait thereafter
setitimer(ITIMER_REAL, $first_interval, $interval);

while (1) {

        # wait for alarm from timer
        pause;

        # do work that takes less than $interval to complete
	mystuff{};

}
exit 0;