File Coverage

blib/lib/Plack/Middleware/Timeout.pm
Criterion Covered Total %
statement 63 63 100.0
branch 14 16 87.5
condition 1 3 33.3
subroutine 15 15 100.0
pod 2 2 100.0
total 95 99 95.9


line stmt bran cond sub pod time code
1             package Plack::Middleware::Timeout;
2              
3 1     1   438 use strict;
  1         2  
  1         23  
4 1     1   5 use warnings;
  1         2  
  1         22  
5 1     1   390 use parent 'Plack::Middleware';
  1         290  
  1         5  
6 1     1   13601 use Plack::Util::Accessor qw(timeout response soft_timeout on_soft_timeout);
  1         2  
  1         4  
7 1     1   466 use Plack::Request;
  1         68227  
  1         33  
8 1     1   542 use Plack::Response;
  1         1023  
  1         27  
9 1     1   387 use Scope::Guard ();
  1         410  
  1         25  
10 1     1   465 use Time::HiRes qw(alarm time);
  1         1242  
  1         4  
11 1     1   171 use Carp qw(croak);
  1         2  
  1         60  
12 1     1   497 use HTTP::Status qw(HTTP_GATEWAY_TIMEOUT);
  1         4058  
  1         505  
13              
14             our $VERSION = '0.11';
15              
16             sub prepare_app {
17 5     5 1 7231 my $self = shift;
18              
19 5 50       25 $self->timeout(120) unless $self->timeout;
20              
21 5         73 for my $param (qw(response on_soft_timeout)) {
22 10 100       55 next unless defined $self->$param;
23 1 50       7 croak "parameter $param isn't a CODE reference!"
24             unless ref( $self->$param ) eq 'CODE';
25             }
26             }
27              
28             my $default_on_soft_timeout = sub {
29             warn sprintf "Soft timeout reached for uri '%s' (soft timeout: %ds) request took %ds", @_;
30             };
31              
32             sub call {
33 5     5 1 21092 my ( $self, $env ) = @_;
34              
35 5         11 my $alarm_msg = 'Plack::Middleware::Timeout';
36 5     2   202 local $SIG{ALRM} = sub { die $alarm_msg };
  2         8000394  
37              
38 5         21 my $time_started = 0;
39 5         9 local $@;
40             eval {
41              
42 5         20 $time_started = time();
43 5         29 alarm($self->timeout);
44              
45             my $guard = Scope::Guard->new(sub {
46 5     5   328 alarm 0;
47 5         111 });
48              
49 5         79 my $soft_timeout_guard;
50              
51 5 100       15 if ( my $soft_timeout = $self->soft_timeout ) {
52             $soft_timeout_guard = Scope::Guard->new(
53             sub {
54 3 100   3   4000546 if ( time() - $time_started > $soft_timeout ) {
55 1   33     9 my $on_soft_timeout =
56             $self->on_soft_timeout || $default_on_soft_timeout;
57              
58 1         43 my $request = Plack::Request->new($env);
59              
60 1         19 $on_soft_timeout->(
61             $request->uri,
62             $soft_timeout,
63             );
64             }
65             }
66 3         34 );
67             }
68              
69 5         66 return $self->app->($env);
70              
71 5 100       11 } or do {
72 3         10 my $error = $@;
73 3 100       25 if ( $error =~ /Plack::Middleware::Timeout/ ) {
74              
75 2         31 my $request = Plack::Request->new($env);
76 2         47 my $response = Plack::Response->new(HTTP_GATEWAY_TIMEOUT);
77 2 100       66 if ( my $build_response_coderef = $self->response ) {
78 1         11 $build_response_coderef->($response);
79             }
80             else {
81             # warn by default, so there's a trace of the timeout left somewhere
82 1         11 warn sprintf
83             "Terminated request for uri '%s' due to timeout (%ds)",
84             $request->uri,
85             $self->timeout;
86             }
87              
88 2         364 return $response->finalize;
89             } else {
90             # something else blew up, so rethrow it
91 1         16 die $@;
92             }
93             };
94             }
95              
96             1;
97              
98             __END__