Timing Out an Operation (Perl Cookbook, 2nd Edition)

## 16.21. Timing Out an Operation

### 16.21.1. Problem

You want to make sure an operation doesn't take more than a certain amount of time. For instance, you're running filesystem backups and want to abort if it takes longer than an hour. Or, you want to give the user a limited amount of time to respond to a query.

### 16.21.2. Solution

To interrupt a long-running operation, set a `SIGALRM` handler to call `die`, in effect transforming the signal into an exception. Set an alarm with `alarm`, then `eval` your code:

>     eval {
>         local $SIG{ALRM} = sub { die "alarm clock restart" };
>         alarm 10;                   # schedule alarm in 10 seconds
>         eval {
>               ########
>               # long-running operation goes here
>               ########
>         };
>         alarm 0;                    # cancel the alarm
>     };
>     alarm 0;                        # race condition protection
>     die if $@ && $@ !~ /alarm clock restart/; # reraise

### 16.21.3. Discussion

The `alarm` function takes one argument: the integer number of seconds before the kernel sends your process a `SIGALRM`, that is, an alarm signal. It may be delivered after that time in busy time-sharing systems. The default action for `SIGALRM` is to terminate your program, so you should install your own signal handler.

Because this example should work no matter what operation is being timed out, we take some special precautions in case your long-running operation contains a slow syscall. Slow syscalls are those that don't return immediately, but await some external event, such as for I/O to happen or some sort of timer to go off. These external events include `read` (including `readline`, the `<FH>` operator), `write`, and `open` on certain devices, fifos, and sockets, as well as `accept`, `connect`, `send`, `recv`, `flock`, `wait`, `waitpid`, and of course, `sleep`. If the alarm hits while you're in a slow syscall and you simply catch the signal and return, you'll go right back into that syscall. That's because Perl automatically restarts syscalls where it's able to. The only way out of them is to raise an exception through `die` and then let `eval` catch it. (This works because the exception winds up calling the C library's longjmp(3) function, which is what really gets you out of the restarting syscall.)

The nested exception trap is because you cannot be sure that arbitrary code in your long-running operation isn't going to raise some exception of its own. If it did, then that would pop you out of the inner `eval` with the alarm still pending. You need to make sure to clear the alarm anyway. The second `alarm 0` is in case the signal comes in after running the long-running operation, but before getting to the first `alarm 0`. If you don't do that, you would risk a tiny race condition—but size doesn't matter in race conditions; they either exist or they don't. And we prefer that they don't.

You cannot (usefully) give the `alarm` function a fractional number of seconds; if you try, it will be truncated to an integer. For precise timers, see Recipe 3.9.

### 16.21.4. See Also

The "Signals" sections in Chapter 16 of Programming Perl and in perlipc(1); the section on "Handling Race Conditions" in Chapter 23 of Programming Perl; the `alarm` function in Chapter 29 of Programming Perl and in perlfunc(1); Recipe 3.9

Copyright © 2003 O'Reilly & Associates. All rights reserved.