File Coverage

blib/lib/Feersum/Runner.pm
Criterion Covered Total %
statement 121 131 92.3
branch 26 50 52.0
condition 11 18 61.1
subroutine 24 25 96.0
pod 4 4 100.0
total 186 228 81.5


line stmt bran cond sub pod time code
1             package Feersum::Runner;
2 10     10   3030 use warnings;
  10         42  
  10         509  
3 10     10   102 use strict;
  10         63  
  10         388  
4              
5 10     10   5802 use EV;
  10         22799  
  10         404  
6 10     10   4342 use Feersum;
  10         29  
  10         534  
7 10     10   73 use Socket qw/SOMAXCONN/;
  10         18  
  10         981  
8 10     10   3188 use POSIX ();
  10         36889  
  10         346  
9 10     10   80 use Scalar::Util qw/weaken/;
  10         30  
  10         828  
10 10     10   136 use Carp qw/carp croak/;
  10         32  
  10         573  
11              
12 10     10   64 use constant DEATH_TIMER => 5.0; # seconds
  10         27  
  10         1223  
13 10     10   61 use constant DEATH_TIMER_INCR => 2.0; # seconds
  10         20  
  10         513  
14 10     10   56 use constant DEFAULT_HOST => 'localhost';
  10         20  
  10         520  
15 10     10   70 use constant DEFAULT_PORT => 5000;
  10         29  
  10         13170  
16              
17             our $INSTANCE;
18             sub new { ## no critic (RequireArgUnpacking)
19 12     12 1 106341 my $c = shift;
20             croak "Only one Feersum::Runner instance can be active at a time"
21 12 50 66     279 if $INSTANCE && $INSTANCE->{running};
22 12         331 $INSTANCE = bless {quiet=>1, @_, running=>0}, $c;
23 12         102 return $INSTANCE;
24             }
25              
26             sub DESTROY {
27 3     3   15 local $@;
28 3         7 my $self = shift;
29 3 50       12 if (my $f = $self->{endjinn}) {
30 0     0   0 $f->request_handler(sub{});
31 0         0 $f->unlisten();
32             }
33 3         13 $self->{_quit} = undef;
34 3         20 return;
35             }
36              
37             sub _prepare {
38 8     8   71 my $self = shift;
39              
40             $self->{listen} ||=
41 8   50     191 [ ($self->{host}||DEFAULT_HOST).':'.($self->{port}||DEFAULT_PORT) ];
      50        
      100        
42             croak "Feersum doesn't support multiple 'listen' directives yet"
43 8 50       39 if @{$self->{listen}} > 1;
  8         63  
44 8         26 my $listen = shift @{$self->{listen}};
  8         67  
45              
46 8         43 my $sock;
47 8 50       132 if ($listen =~ m#^unix/#) {
48 0         0 croak "listening on a unix socket isn't supported yet";
49             }
50             else {
51 8         210 require IO::Socket::INET;
52 8         419 $sock = IO::Socket::INET->new(
53             LocalAddr => $listen,
54             ReuseAddr => 1,
55             Proto => 'tcp',
56             Listen => SOMAXCONN,
57             Blocking => 0,
58             );
59 8 50       8567 croak "couldn't bind to socket: $!" unless $sock;
60             }
61 8         52 $self->{sock} = $sock;
62 8         193 my $f = Feersum->endjinn;
63 8         146 $f->use_socket($sock);
64              
65 8 50       122 if ($self->{options}) {
66             # Plack::Runner puts these here
67 0         0 $self->{pre_fork} = delete $self->{options}{pre_fork};
68             }
69              
70 8         101 $self->{endjinn} = $f;
71 8         32 return;
72             }
73              
74             # for overriding:
75             sub assign_request_handler { ## no critic (RequireArgUnpacking)
76 3     3 1 78 return $_[0]->{endjinn}->request_handler($_[1]);
77             }
78              
79             sub run {
80 8     8 1 594 my $self = shift;
81 8         217 weaken $self;
82              
83 8 50       204 $self->{quiet} or warn "Feersum [$$]: starting...\n";
84 8         177 $self->_prepare();
85              
86 8   66     90 my $app = shift || delete $self->{app};
87              
88 8 50 66     83 if (!$app && $self->{app_file}) {
89 3         99 local ($@, $!);
90 3         3615 $app = do $self->{app_file};
91 3 50       15 warn "couldn't parse $self->{app_file}: $@" if $@;
92 3 50 33     45 warn "couldn't do $self->{app_file}: $!" if ($! && !defined $app);
93 3 50       21 warn "couldn't run $self->{app_file}: didn't return anything"
94             unless $app;
95             }
96 8 50       44 die "app not defined or failed to compile" unless $app;
97              
98 8         37 $self->assign_request_handler($app);
99 8         21 undef $app;
100              
101 8     4   215 $self->{_quit} = EV::signal 'QUIT', sub { $self->quit };
  4         148  
102              
103 8 100       52 $self->_start_pre_fork if $self->{pre_fork};
104 6         94202 EV::run;
105 0 0       0 $self->{quiet} or warn "Feersum [$$]: done\n";
106 0         0 $self->DESTROY();
107 0         0 return;
108             }
109              
110             sub _fork_another {
111 5     5   31 my ($self, $slot) = @_;
112 5         20 weaken $self;
113              
114 5         4574 my $pid = fork;
115 5 50       248 croak "failed to fork: $!" unless defined $pid;
116 5 100       97 unless ($pid) {
117 2         122 EV::default_loop()->loop_fork;
118 2 50       40 $self->{quiet} or warn "Feersum [$$]: starting\n";
119 2         141 delete $self->{_kids};
120 2         65 delete $self->{pre_fork};
121 2         29 eval { EV::run; }; ## no critic (RequireCheckingReturnValueOfEval)
  2         48082  
122 0 0       0 carp $@ if $@;
123 0 0       0 POSIX::exit($@ ? -1 : 0); ## no critic (ProhibitMagicNumbers)
124             }
125              
126 3         8 $self->{_n_kids}++;
127             $self->{_kids}[$slot] = EV::child $pid, 0, sub {
128 1     1   13 my $w = shift;
129 1 50       65 $self->{quiet} or warn "Feersum [$$]: child $pid exited ".
130             "with rstatus ".$w->rstatus."\n";
131 1         6 $self->{_n_kids}--;
132 1 50       16 if ($self->{_shutdown}) {
133 1 50       11 EV::break(EV::BREAK_ALL()) unless $self->{_n_kids};
134 1         6688829 return;
135             }
136 0         0 $self->_fork_another();
137 3         321 };
138 3         132 return;
139             }
140              
141             sub _start_pre_fork {
142 3     3   168 my $self = shift;
143              
144 3         165 POSIX::setsid();
145              
146 3         15 $self->{_kids} = [];
147 3         12 $self->{_n_kids} = 0;
148 3         21 $self->_fork_another($_) for (1 .. $self->{pre_fork});
149              
150 1         59 $self->{endjinn}->unlisten();
151 1         16 return;
152             }
153              
154             sub quit {
155 9     9 1 57 my $self = shift;
156 9 100       317418 return if $self->{_shutdown};
157              
158 8         53 $self->{_shutdown} = 1;
159 8 50       149 $self->{quiet} or warn "Feersum [$$]: shutting down...\n";
160 8         59 my $death = DEATH_TIMER;
161              
162 8 100       102 if ($self->{_n_kids}) {
163             # in parent, broadcast SIGQUIT to the group (not self)
164 2         158 kill 3, -$$; ## no critic (ProhibitMagicNumbers)
165 2         43 $death += DEATH_TIMER_INCR;
166             }
167             else {
168             # in child or solo process
169 6     6   244 $self->{endjinn}->graceful_shutdown(sub { POSIX::exit(0) });
  6         393  
170             }
171              
172 2     2   118 $self->{_death} = EV::timer $death, 0, sub { POSIX::exit(1) };
  2         197  
173 2         7006206 return;
174             }
175              
176             1;
177             __END__