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 >
Text File  |  2006-11-29  |  6KB  |  225 lines

  1. # <@LICENSE>
  2. # Licensed to the Apache Software Foundation (ASF) under one or more
  3. # contributor license agreements.  See the NOTICE file distributed with
  4. # this work for additional information regarding copyright ownership.
  5. # The ASF licenses this file to you under the Apache License, Version 2.0
  6. # (the "License"); you may not use this file except in compliance with
  7. # the License.  You may obtain a copy of the License at:
  8. #     http://www.apache.org/licenses/LICENSE-2.0
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. # </@LICENSE>
  15.  
  16. =head1 NAME
  17.  
  18. Mail::SpamAssassin::Timeout - safe, reliable timeouts in perl
  19.  
  20. =head1 SYNOPSIS
  21.  
  22.     # non-timeout code...
  23.  
  24.     my $t = Mail::SpamAssassin::Timeout->new({ secs => 5 });
  25.     
  26.     $t->run(sub {
  27.         # code to run with a 5-second timeout...
  28.     });
  29.  
  30.     if ($t->timed_out()) {
  31.         # do something...
  32.     }
  33.  
  34.     # more non-timeout code...
  35.  
  36. =head1 DESCRIPTION
  37.  
  38. This module provides a safe, reliable and clean API to provide
  39. C<alarm(2)>-based timeouts for perl code.
  40.  
  41. Note that C<$SIG{ALRM}> is used to provide the timeout, so this will not
  42. interrupt out-of-control regular expression matches.
  43.  
  44. Nested timeouts are supported.
  45.  
  46. =head1 PUBLIC METHODS
  47.  
  48. =over 4
  49.  
  50. =cut
  51.  
  52. package Mail::SpamAssassin::Timeout;
  53.  
  54. use strict;
  55. use warnings;
  56. use bytes;
  57.  
  58. use vars qw{
  59.   @ISA
  60. };
  61.  
  62. @ISA = qw();
  63.  
  64. ###########################################################################
  65.  
  66. =item my $t = Mail::SpamAssassin::Timeout->new({ ... options ... });
  67.  
  68. Constructor.  Options include:
  69.  
  70. =over 4
  71.  
  72. =item secs => $seconds
  73.  
  74. timeout, in seconds.  Optional; if not specified, no timeouts will be applied.
  75.  
  76. =back
  77.  
  78. =cut
  79.  
  80. sub new {
  81.   my ($class, $opts) = @_;
  82.   $class = ref($class) || $class;
  83.   my %selfval = $opts ? %{$opts} : ();
  84.   my $self = \%selfval;
  85.  
  86.   bless ($self, $class);
  87.   $self;
  88. }
  89.  
  90. ###########################################################################
  91.  
  92. =item $t->run($coderef)
  93.  
  94. Run a code reference within the currently-defined timeout.
  95.  
  96. The timeout is as defined by the B<secs> parameter to the constructor.
  97.  
  98. Returns whatever the subroutine returns, or C<undef> on timeout.
  99. If the timer times out, C<$t-<gt>timed_out()> will return C<1>.
  100.  
  101. Time elapsed is not cumulative; multiple runs of C<run> will restart the
  102. timeout from scratch.
  103.  
  104. =item $t->run_and_catch($coderef)
  105.  
  106. Run a code reference, as per C<$t-<gt>run()>, but also catching any
  107. C<die()> calls within the code reference.
  108.  
  109. Returns C<undef> if no C<die()> call was executed and C<$@> was unset, or the
  110. value of C<$@> if it was set.  (The timeout event doesn't count as a C<die()>.)
  111.  
  112. =cut
  113.  
  114. sub run { $_[0]->_run($_[1], 0); }
  115.  
  116. sub run_and_catch { $_[0]->_run($_[1], 1); }
  117.  
  118. sub _run {      # private
  119.   my ($self, $sub, $and_catch) = @_;
  120.  
  121.   delete $self->{timed_out};
  122.  
  123.   if (!$self->{secs}) { # no timeout!  just call the sub and return.
  124.     return &$sub;
  125.   }
  126.  
  127.   # assertion
  128.   if ($self->{secs} < 0) {
  129.     die "Mail::SpamAssassin::Timeout: oops? neg value for 'secs': $self->{secs}";
  130.   }
  131.  
  132.   my $oldalarm = 0;
  133.   my $ret;
  134.  
  135.   # bug 4699: under heavy load, an alarm may fire while $@ will contain "",
  136.   # which isn't very useful.  this counter works around it safely, since
  137.   # it will not require malloc() be called if it fires
  138.   my $timedout = 0;
  139.  
  140.   eval {
  141.     # note use of local to ensure closed scope here
  142.     local $SIG{ALRM} = sub { $timedout++; die "__alarm__ignore__\n" };
  143.     local $SIG{__DIE__};   # bug 4631
  144.  
  145.     $oldalarm = alarm($self->{secs});
  146.  
  147.     $ret = &$sub;
  148.  
  149.     # Unset the alarm() before we leave eval{ } scope, as that stack-pop
  150.     # operation can take a second or two under load. Note: previous versions
  151.     # restored $oldalarm here; however, that is NOT what we want to do, since
  152.     # it creates a new race condition, namely that an old alarm could then fire
  153.     # while the stack-pop was underway, thereby appearing to be *this* timeout
  154.     # timing out. In terms of how we might possibly have nested timeouts in
  155.     # SpamAssassin, this is an academic issue with little impact, but it's
  156.     # still worth avoiding anyway.
  157.  
  158.     alarm 0;
  159.   };
  160.  
  161.   my $err = $@;
  162.  
  163.   if (defined $oldalarm) {
  164.     # now, we could have died from a SIGALRM == timed out.  if so,
  165.     # restore the previously-active one, or zero all timeouts if none
  166.     # were previously active.
  167.     alarm $oldalarm;
  168.   }
  169.  
  170.   if ($err) {
  171.     if ($err =~ /__alarm__ignore__/) {
  172.       $self->{timed_out} = 1;
  173.     } else {
  174.       if ($and_catch) {
  175.         return $@;
  176.       } else {
  177.         die $@;             # propagate any "real" errors
  178.       }
  179.     }
  180.   } elsif ($timedout) {
  181.     warn "timeout with empty \$@";  # this is worth complaining about
  182.     $self->{timed_out} = 1;
  183.   }
  184.  
  185.   if ($and_catch) {
  186.     return;                 # undef
  187.   } else {
  188.     return $ret;
  189.   }
  190. }
  191.  
  192. ###########################################################################
  193.  
  194. =item $t->timed_out()
  195.  
  196. Returns C<1> if the most recent code executed in C<run()> timed out, or
  197. C<undef> if it did not.
  198.  
  199. =cut
  200.  
  201. sub timed_out {
  202.   my ($self) = @_;
  203.   return $self->{timed_out};
  204. }
  205.  
  206. ###########################################################################
  207.  
  208. =item $t->reset()
  209.  
  210. If called within a C<run()> code reference, causes the current alarm timer to
  211. be reset to its starting value.
  212.  
  213. =cut
  214.  
  215. sub reset {
  216.   my ($self) = @_;
  217.   alarm($self->{secs});
  218. }
  219.  
  220. ###########################################################################
  221.  
  222. 1;
  223.