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 theforward
feature.
METHODS
alarm
Access the Catalyst::Alarm object.
NOTE: This object won't exist if you do not configure the alarm.
The Catalyst::Alarm object has one non-accessor method: off.
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
andstop
. - 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.
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'] ],
});
prepare
Overridden internally.
forward
Overridden internally.
finalize
Overridden internally.
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. That's not really the fault of this module, but listed here in case you try using this on a Win32 system and it balks. You might try setting the PERL_SIGNALS environment variable to unsafe
but that wouldn't be very safe, now would it?
AUTHOR
Peter Karman <pkarman@atomiclearning.com>.
CREDITS
Thanks to Bill Moseley <moseley@hank.org> and Yuval Kogman <nothingmuch@woobling.org> for feedback and API suggestions.
COPYRIGHT
Copyright 2006 by Atomic Learning, Inc. All rights reserved.
This code is licensed under the same terms as Perl itself.
SEE ALSO
mod_perl chapter on alarm(), DBI, Sys::SigAction, Time::HiRes