home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.f-secure.com
/
2014.06.ftp.f-secure.com.tar
/
ftp.f-secure.com
/
support
/
hotfix
/
fsis
/
IS-SpamControl.fsfix
/
iufssc
/
lib
/
Mail
/
SpamAssassin
/
Timeout.pm
< prev
next >
Wrap
Text File
|
2006-11-29
|
6KB
|
225 lines
# <@LICENSE>
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to you under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# </@LICENSE>
=head1 NAME
Mail::SpamAssassin::Timeout - safe, reliable timeouts in perl
=head1 SYNOPSIS
# non-timeout code...
my $t = Mail::SpamAssassin::Timeout->new({ secs => 5 });
$t->run(sub {
# code to run with a 5-second timeout...
});
if ($t->timed_out()) {
# do something...
}
# more non-timeout code...
=head1 DESCRIPTION
This module provides a safe, reliable and clean API to provide
C<alarm(2)>-based timeouts for perl code.
Note that C<$SIG{ALRM}> is used to provide the timeout, so this will not
interrupt out-of-control regular expression matches.
Nested timeouts are supported.
=head1 PUBLIC METHODS
=over 4
=cut
package Mail::SpamAssassin::Timeout;
use strict;
use warnings;
use bytes;
use vars qw{
@ISA
};
@ISA = qw();
###########################################################################
=item my $t = Mail::SpamAssassin::Timeout->new({ ... options ... });
Constructor. Options include:
=over 4
=item secs => $seconds
timeout, in seconds. Optional; if not specified, no timeouts will be applied.
=back
=cut
sub new {
my ($class, $opts) = @_;
$class = ref($class) || $class;
my %selfval = $opts ? %{$opts} : ();
my $self = \%selfval;
bless ($self, $class);
$self;
}
###########################################################################
=item $t->run($coderef)
Run a code reference within the currently-defined timeout.
The timeout is as defined by the B<secs> parameter to the constructor.
Returns whatever the subroutine returns, or C<undef> on timeout.
If the timer times out, C<$t-<gt>timed_out()> will return C<1>.
Time elapsed is not cumulative; multiple runs of C<run> will restart the
timeout from scratch.
=item $t->run_and_catch($coderef)
Run a code reference, as per C<$t-<gt>run()>, but also catching any
C<die()> calls within the code reference.
Returns C<undef> if no C<die()> call was executed and C<$@> was unset, or the
value of C<$@> if it was set. (The timeout event doesn't count as a C<die()>.)
=cut
sub run { $_[0]->_run($_[1], 0); }
sub run_and_catch { $_[0]->_run($_[1], 1); }
sub _run { # private
my ($self, $sub, $and_catch) = @_;
delete $self->{timed_out};
if (!$self->{secs}) { # no timeout! just call the sub and return.
return &$sub;
}
# assertion
if ($self->{secs} < 0) {
die "Mail::SpamAssassin::Timeout: oops? neg value for 'secs': $self->{secs}";
}
my $oldalarm = 0;
my $ret;
# bug 4699: under heavy load, an alarm may fire while $@ will contain "",
# which isn't very useful. this counter works around it safely, since
# it will not require malloc() be called if it fires
my $timedout = 0;
eval {
# note use of local to ensure closed scope here
local $SIG{ALRM} = sub { $timedout++; die "__alarm__ignore__\n" };
local $SIG{__DIE__}; # bug 4631
$oldalarm = alarm($self->{secs});
$ret = &$sub;
# Unset the alarm() before we leave eval{ } scope, as that stack-pop
# operation can take a second or two under load. Note: previous versions
# restored $oldalarm here; however, that is NOT what we want to do, since
# it creates a new race condition, namely that an old alarm could then fire
# while the stack-pop was underway, thereby appearing to be *this* timeout
# timing out. In terms of how we might possibly have nested timeouts in
# SpamAssassin, this is an academic issue with little impact, but it's
# still worth avoiding anyway.
alarm 0;
};
my $err = $@;
if (defined $oldalarm) {
# now, we could have died from a SIGALRM == timed out. if so,
# restore the previously-active one, or zero all timeouts if none
# were previously active.
alarm $oldalarm;
}
if ($err) {
if ($err =~ /__alarm__ignore__/) {
$self->{timed_out} = 1;
} else {
if ($and_catch) {
return $@;
} else {
die $@; # propagate any "real" errors
}
}
} elsif ($timedout) {
warn "timeout with empty \$@"; # this is worth complaining about
$self->{timed_out} = 1;
}
if ($and_catch) {
return; # undef
} else {
return $ret;
}
}
###########################################################################
=item $t->timed_out()
Returns C<1> if the most recent code executed in C<run()> timed out, or
C<undef> if it did not.
=cut
sub timed_out {
my ($self) = @_;
return $self->{timed_out};
}
###########################################################################
=item $t->reset()
If called within a C<run()> code reference, causes the current alarm timer to
be reset to its starting value.
=cut
sub reset {
my ($self) = @_;
alarm($self->{secs});
}
###########################################################################
1;