~ubuntu-branches/ubuntu/lucid/spamassassin/lucid-proposed

« back to all changes in this revision

Viewing changes to lib/Mail/SpamAssassin/Timeout.pm

  • Committer: Bazaar Package Importer
  • Author(s): Laurent Bigonville, Ubuntu Merge-o-Matic, Laurent Bigonville
  • Date: 2006-07-31 15:40:08 UTC
  • mfrom: (1.1.4 upstream)
  • Revision ID: james.westby@ubuntu.com-20060731154008-j37ulp5pgfkddegw
Tags: 3.1.3-1ubuntu1
[ Ubuntu Merge-o-Matic ]
* Merge from debian unstable.

[ Laurent Bigonville ]
* fix debian/control.
* drop debian/patches/40_fix_dns_issue.

Show diffs side-by-side

added added

removed removed

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