NAME

Fugu::Timeout - run something under a time limit

SYNOPSIS

use Fugu::Timeout;

my $result = Fugu::Timeout::bounded(10, sub { $client->connect });

Fugu::Timeout::wait_until(60, 0.5, sub { -S $socket })
    or die "the daemon never came up\n";

DESCRIPTION

Fugu::Timeout holds the two ways to run something under a time limit: bounded() sets an alarm, and wait_until() polls. An alarm interrupts a blocking system call that a poll loop cannot reach, and a poll loop leaves a signal handler alone. Neither can do the other's job, so both are here.

The two are plain functions, not methods. They keep no state and they never log.

bounded

bounded($seconds, $code) runs $code under a hard wall-clock deadline. A blocked call inside $code cannot stall the caller for longer than $seconds.

The guard is an alarm(3), so it interrupts a blocking system call that a poll loop cannot reach. That is why it exists next to wait_until(), which cannot do that.

A die inside $code propagates, with the alarm already cleared. Thus a real failure keeps its own message and is not reported as a timeout.

wait_until

wait_until($timeout, $interval, $code) calls $code until it returns true, or until $timeout seconds pass. $interval is the pause between calls; its default is a quarter of a second.

$code runs at least once, even with a timeout of 0. A caller that asks "is it ready" always gets an answer about now.

The loop also stops when a signal arrives, through Fugu::Signal. A poll loop must not outlive the interrupt that told the program to stop.

RETURN VALUES

bounded() returns what $code returned. It returns undef when the deadline elapsed.

wait_until() returns the first true value that $code gave. It returns undef on the timeout, and undef when a signal arrived.

EXAMPLES

This example bounds a guest interaction that can block for ever:

my $out = Fugu::Timeout::bounded(15, sub {
    return $ssh->run_command('sync; sync; sync');
});
warn "the guest did not answer\n" unless defined $out;

This example waits for a port to open, and stops early on a SIGINT:

Fugu::Timeout::wait_until(120, 1, sub {
    IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => $port);
}) or die "port $port never opened\n";

ERRORS

bounded() lets a die from $code through unchanged. It has no error of its own: a deadline is a return value, not an exception.

wait_until() does not die.

SEE ALSO

alarm(3), Fugu::Process, Fugu::Signal

AUTHORS

Dick Olsson <hi@senzilla.io>

CAVEATS

bounded() uses alarm(3), which is process-global and has one pending deadline. A nested bounded() replaces the outer deadline and does not restore it. Do not nest the two.

An alarm interrupts a system call in progress. A caller whose $code writes to a file can therefore leave a partial write behind. Bound a read, a connect or a wait, and not a write that must be whole.

wait_until() cannot interrupt a $code that blocks. The timeout bounds the loop, not one call inside it. Use bounded() for that.