The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Catalyst::Plugin::Alarm - call an action with a timeout value

SYNOPSIS

 package MyApp;
 use Catalyst qw( Alarm );
 MyApp->config( alarm => {
    timeout => 60,
    global  => 120,
    handler => sub { # do something if alarm sounds }
 });
 
 sub default : Private {
     my ($self,$c) = @_;
     unless( $c->timeout('foo') ) {
        $c->stash->{error} = "Sorry to keep you waiting. There was a problem.";
        return;
     }
 }
 
 sub foo : Private {
    my ($self,$c) = @_;
    sleep 61;  
 }
 

DESCRIPTION

Catalyst::Plugin::Alarm implements the timeout_call() function of Sys::SigAction for both global and local alarms.

You may set a global timeout value that will trigger alarm if the total processing time of any request exceeds N seconds.

You may call individual actions with timeout values in a manner similar to the standard forward() method.

NOTE: Using alarms in a web application is not without peril, as any number of factors could contribute to legitimately slowing down your application. The Alarm plugin should be used only when you need to catch things that a browser's timeout feature won't catch.

CONFIGURATION

You may set default values in your config() hash, using the alarm key. Timeout values should be indicated in seconds and must be integers. The added float granularity of Time::HiRes is not available for the alarm values due to the way sleep() and alarm() interact (i.e., they do not play together predictably).

timeout N

The default time to wait in the timeout() method.

global N

The default time to wait for the entire request to finish. Default time is three minutes (180 seconds). If your app will legitimately take longer than that to finish a request, you should set it higher.

To disable global timeouts entirely, set N to 0.

handler coderef

Set a handler for timeouts. Will be used in both global timeouts and the timeout() method. The default is to throw() a Catalyst::Exception with a (hopefully) helpful message about the alarm.

coderef can expect to receive the following arguments:

$controller

The current controller object.

\@return or 1

If the alarm is the global alarm, the second value in @_ will be a 1. If the alarm is a local alarm (from timeout() or forward()) then the second value will be a reference to the array returned from your forwarded action.

The on() flag is significant in this case because if false, then the @return value will be returned from the timeout() method. Otherwise, if on() is true, timeout() will return undef.

Thus you can make alarms non-fatal by defining a handler that just notifies you when an alarm went off and resetting the on() flag.

Example:

  __PACKAGE__->config( alarm => {
    handler => sub {
        if (ref $_[1]) {
            $_[0]->log->error(" .... local alarm went off!!");
            $_[1]->[0] = 'some return value';
            $_[0]->alarm->on(0);    # turn 'off' the alarm flag
        }
        else {
            $_[0]->log->error(" .... global alarm went off");
            $_[0]->alarm->on(1);
        }
    }
  });
override

Configure a temporary override of the global timeout value based on a regular expression match against $c->request->path().

Example:

 __PACKAGE__->config( alarm => {
    override => {
        re=> qr{/ajax/}, 
        timeout=> 3
    }
 });
 

Will set the global timeout value to 3 if the request->path matches /ajax. The global timeout value will persist only for the life of that request.

forward

Use forward() directly instead of timeout(). Useful if you want to always call timeout(), as with existing forward() code that you don't want to re-write to use timeout().

Example:

 __PACKAGE__->config( alarm => { 
    forward => 1, 
    timeout => 10 
 });
 

Will automatically call timeout() with a default value of 10 seconds, wherever your code calls forward().

NOTE: You must assign a default timeout value to use the forward feature.

use_native_signals

Default value is false. If set to a true value, Sys::SigAction will not be used and instead the built-in %SIG handlers will be used. This is necessary for the plugin to work under Win32 systems and in some cases with FCGI.

METHODS

alarm

Access the Catalyst::Alarm object.

NOTE: This object won't exist if you do not configure the alarm.

See Catalyst::Alarm METHODS section below.

timeout( stuff_to_forward )

A wrapper around the standard forward() call.

If the stuff_to_forward has not returned before the alarm goes off, timeout() will return undef and an error is set with the error() method.

On success, returns same thing forward() would return.

If you set a default timeout value in config(), you can use timeout() just like forward(). If you want to override any default timeout value, pass either a hashref or an array of key/value pairs. The supported key names are action and timeout.

Examples:

    $c->timeout( 'action' );  # use default timeout (throws exception if not set)

    $c->timeout( 
        action  => 'action',
        timeout => 40,    # override any defaults
    );

    $c->timeout( {  # or as a hashref
        timeout => 40,
        action  => [ qw/MyApp::Controller::Bar snafu/, ['some option'] ],
    });

setup_finalize

Overridden internally.

prepare

Overridden internally.

forward

Overridden internally.

finalize

Overridden internally.

Catalyst::Alarm METHODS

off

The Catalyst::Alarm object has one non-accessor method: off.

The off() method will turn all alarms off, including the global alarm. If you later call timeout() in the same request cycle, the alarm will be reset as indicated in timeout().

An alias for off() is snooze(), which amuses the author. The metaphor collapses in one important way: snooze() turns off the alarm completely for the entire request cycle.

Example:

 __PACKAGE__->config( alarm => {
    override => {
        re      => qr{/foo/},
        timeout => 3 
    }
 });
 
 sub foo : Global {
   my ($self,$c) = @_;
   $c->alarm->off;      # negates the override in config
   $c->alarm->snooze;   # same thing as off()
   $c->timeout('bar');  # but set default alarm for 'bar'
 }

NOTE: The off() method does not set the stop() time.

Alarm object accessors

You probably don't want to muck around with setting anything, but you can get the following values:

timeout

The global timeout value.

sounded

If the global alarm went off, this value is set to a Time::HiRes::gettimeofday() result.

sig_handler

The Sys::SigAction object.

handler

The coderef used in case of alarm.

start

The time alarm was set. A Time::HiRes::gettimeofday() result.

stop

The time alarm was turned off. A Time::HiRes::gettimeofday() result.

total

The total run time the alarm was on. A Time::HiRes::tv_interval() result using start and stop.

failed

An arrayref of the methods where an alarm sounded. If a global alarm sounded, the value of $c->action->name is used.

forward

Whether or not the forward config option was on.

override

If the override config option was used and there was a successful match against the regular expression, this method returns the request path that matched.

on

Flag that indicates whether the alarm sounded or not. True means that the alarm sounded. If you set this flag to 0 (false), then the alarm will be ignored in timeout(). See the handler configuration option for more details about manipulating alarm responses.

NOTE: Because of where stop and total are set in the lifecycle of the request, they are likely not accessible in your View. Thus they are likely useless to you and exist for the amusement of the author, debugging, and perhaps other plugins that may make use of them.

BUGS

Using a global alarm together with the forward config feature can have unforeseen behaviour. Most likely your global alarm will not work at all or may take a lot longer to go off than you expect.

The Time::HiRes alarm() function ought to be used internally instead of the CORE alarm() function, but it behaved unpredictably in the test cases. See the comments in the source for more details.

Win32 systems don't have alarm() or other signal handlers, so use_native_signals gets turned on if running under Win32.

Some users report that Sys::SigAction does not play nicely with FCGI, so you can set the use_native_signals to a true value to use the built-in %SIG handlers instead of Sys::SigAction.

AUTHOR

Peter Karman <pkarman@atomiclearning.com>.

CREDITS

Thanks to Bill Moseley and Yuval Kogman for feedback and API suggestions.

Thanks to Nilson Santos Figueiredo Junior for the Win32 suggestions.

COPYRIGHT

Copyright 2006 by Atomic Learning, Inc. All rights reserved.

This code is licensed under the same terms as Perl itself.

SEE ALSO

http://modperlbook.org/html/ch06_10.html, DBI, Sys::SigAction, Time::HiRes