File Coverage

blib/lib/Feersum/Runner.pm
Criterion Covered Total %
statement 198 334 59.2
branch 66 194 34.0
condition 15 62 24.1
subroutine 28 31 90.3
pod 4 4 100.0
total 311 625 49.7


line stmt bran cond sub pod time code
1             package Feersum::Runner;
2 9     9   2351619 use warnings;
  9         31  
  9         907  
3 9     9   62 use strict;
  9         18  
  9         355  
4              
5 9     9   5229 use EV;
  9         21924  
  9         328  
6 9     9   5113 use Feersum;
  9         37  
  9         629  
7 9         1581 use Socket qw/SOMAXCONN SOL_SOCKET SO_REUSEADDR AF_INET SOCK_STREAM
8 9     9   84 inet_aton pack_sockaddr_in/;
  9         23  
9             BEGIN {
10             # IPv6 support (Socket 1.95+, Perl 5.14+)
11 9         334 eval { Socket->import(qw/AF_INET6 inet_pton pack_sockaddr_in6/); 1 }
  9         658  
12 9 50   9   30 or do {
13 0         0 *AF_INET6 = sub () { undef };
14 0         0 *inet_pton = sub { undef };
  0         0  
15 0         0 *pack_sockaddr_in6 = sub { undef };
  0         0  
16             };
17             }
18             BEGIN {
19             # SO_REUSEPORT may not be available on all systems
20 9         281 eval { Socket->import('SO_REUSEPORT'); 1 }
  9         299  
21 9 50   9   30 or *SO_REUSEPORT = sub () { undef };
22             }
23 9     9   6153 use POSIX ();
  9         74091  
  9         438  
24 9     9   67 use Scalar::Util qw/weaken/;
  9         21  
  9         564  
25 9     9   52 use Carp qw/carp croak/;
  9         16  
  9         470  
26 9     9   1869 use File::Spec::Functions 'rel2abs';
  9         2924  
  9         652  
27              
28 9     9   79 use constant DEATH_TIMER => 5.0; # seconds
  9         23  
  9         839  
29 9     9   55 use constant DEATH_TIMER_INCR => 2.0; # seconds
  9         19  
  9         498  
30 9     9   55 use constant DEFAULT_HOST => 'localhost';
  9         11  
  9         465  
31 9     9   73 use constant DEFAULT_PORT => 5000;
  9         16  
  9         589  
32 9   50 9   52 use constant MAX_PRE_FORK => $ENV{FEERSUM_MAX_PRE_FORK} || 1000;
  9         21  
  9         44871  
33              
34             our $INSTANCE;
35             sub new { ## no critic (RequireArgUnpacking)
36 17     17 1 26689 my $c = shift;
37 17 100       948 if ($INSTANCE) {
38             croak "Only one Feersum::Runner instance can be active at a time"
39 1 50       5 if $INSTANCE->{running};
40             # Clean up old instance state before creating new one
41 1         5 $INSTANCE->_cleanup();
42 1         5 undef $INSTANCE;
43             }
44 17         700 $INSTANCE = bless {quiet=>1, @_, running=>0}, $c;
45 17         174 return $INSTANCE;
46             }
47              
48             sub _cleanup {
49 18     18   47 my $self = shift;
50 18 100       90 return if $self->{_cleaned_up};
51 17         51 $self->{_cleaned_up} = 1;
52 17 100       123 if (my $f = $self->{endjinn}) {
53 6     0   127 $f->request_handler(sub{});
54 6         65 $f->unlisten();
55             }
56 17         114 $self->{_quit} = undef;
57 17         59 $self->{running} = 0;
58 17         238 return;
59             }
60              
61             sub DESTROY {
62 14     14   12678 local $@;
63 14         45 $_[0]->_cleanup();
64             }
65              
66             sub _create_socket {
67 14     14   38 my ($self, $listen, $use_reuseport) = @_;
68              
69 14         155 my $sock;
70 14 50       149 if ($listen =~ m#^[/\.]+\w#) {
71 0         0 require IO::Socket::UNIX;
72 0 0       0 if (-S $listen) {
73 0 0       0 unlink $listen or carp "unlink stale socket '$listen': $!";
74             }
75 0         0 my $saved = umask(0);
76 0         0 $sock = eval {
77 0         0 IO::Socket::UNIX->new(
78             Local => rel2abs($listen),
79             Listen => SOMAXCONN,
80             );
81             };
82 0         0 my $err = $@;
83 0         0 umask($saved); # Restore umask even if socket creation failed
84 0 0       0 die $err if $err;
85 0 0       0 croak "couldn't bind to socket" unless $sock;
86 0 0       0 $sock->blocking(0) || do { close($sock); croak "couldn't unblock socket: $!"; };
  0         0  
  0         0  
87             }
88             else {
89 14         288 require IO::Socket::INET;
90             # SO_REUSEPORT must be set BEFORE bind for multiple sockets per port
91 14 50 33     102 if ($use_reuseport && defined SO_REUSEPORT) {
92             # Parse listen address - handle IPv6 bracketed notation [host]:port
93 0         0 my ($host, $port, $is_ipv6);
94 0 0       0 if ($listen =~ /^\[([^\]]+)\]:(\d*)$/) {
    0          
    0          
95             # IPv6 with port: [::1]:8080
96 0   0     0 ($host, $port, $is_ipv6) = ($1, $2 || 0, 1);
97             } elsif ($listen =~ /^\[([^\]]+)\]$/) {
98             # IPv6 without port: [::1]
99 0         0 ($host, $port, $is_ipv6) = ($1, 0, 1);
100             } elsif ($listen =~ /:.*:/) {
101             # Bare IPv6 - reject ambiguous cases that look like host:port
102 0 0       0 if ($listen =~ /:(\d{1,5})$/) {
103 0         0 my $maybe_port = $1;
104             # 5 digits = definitely a port; >=1024 = likely a port
105 0 0 0     0 if ($maybe_port <= 65535 && (length($maybe_port) == 5 || $maybe_port >= 1024)) {
      0        
106 0         0 croak "ambiguous IPv6 address '$listen': use bracket notation [host]:port " .
107             "(e.g., [::1]:$maybe_port or [2001:db8::1]:$maybe_port)";
108             }
109             }
110 0         0 ($host, $port, $is_ipv6) = ($listen, 0, 1);
111             } else {
112             # IPv4: host:port
113 0         0 ($host, $port) = split /:/, $listen, 2;
114 0   0     0 $host ||= '0.0.0.0';
115 0   0     0 $port ||= 0;
116 0         0 $is_ipv6 = 0;
117             }
118              
119             # Validate port range (0-65535)
120 0 0 0     0 if ($port !~ /^\d+$/ || $port > 65535) {
121 0         0 croak "invalid port '$port': must be 0-65535";
122             }
123              
124 0         0 my ($domain, $sockaddr);
125 0 0       0 if ($is_ipv6) {
126 0 0       0 defined AF_INET6()
127             or croak "IPv6 not supported on this system";
128 0 0       0 my $addr = inet_pton(AF_INET6(), $host)
129             or croak "couldn't resolve IPv6 address '$host'";
130 0         0 $domain = AF_INET6();
131 0         0 $sockaddr = pack_sockaddr_in6($port, $addr);
132             } else {
133 0 0       0 my $addr = inet_aton($host)
134             or croak "couldn't resolve address '$host'";
135 0         0 $domain = AF_INET();
136 0         0 $sockaddr = pack_sockaddr_in($port, $addr);
137             }
138              
139             # Create socket with correct address family
140 0 0       0 socket($sock, $domain, SOCK_STREAM(), 0)
141             or croak "couldn't create socket: $!";
142             setsockopt($sock, SOL_SOCKET, SO_REUSEADDR, pack("i", 1))
143 0 0       0 or do { close($sock); croak "setsockopt SO_REUSEADDR failed: $!"; };
  0         0  
  0         0  
144             setsockopt($sock, SOL_SOCKET, SO_REUSEPORT, pack("i", 1))
145 0 0       0 or do { close($sock); croak "setsockopt SO_REUSEPORT failed: $!"; };
  0         0  
  0         0  
146             bind($sock, $sockaddr)
147 0 0       0 or do { close($sock); croak "couldn't bind to socket: $!"; };
  0         0  
  0         0  
148             listen($sock, SOMAXCONN)
149 0 0       0 or do { close($sock); croak "couldn't listen: $!"; };
  0         0  
  0         0  
150              
151             # Wrap in IO::Handle for ->blocking() method
152 0         0 require IO::Handle;
153 0         0 bless $sock, 'IO::Handle';
154             $sock->blocking(0)
155 0 0       0 || do { close($sock); croak "couldn't unblock socket: $!"; };
  0         0  
  0         0  
156             }
157             else {
158             # Validate port in listen address for better error messages
159 14 50       121 if ($listen =~ /:(\d+)$/) {
    0          
160 14         153 my $port = $1;
161 14 50       57 croak "invalid port '$port': must be 0-65535" if $port > 65535;
162             } elsif ($listen =~ /:(\S+)$/) {
163 0         0 my $port = $1;
164 0 0       0 croak "invalid port '$port': must be numeric" unless $port =~ /^\d+$/;
165             }
166 14         361 $sock = IO::Socket::INET->new(
167             LocalAddr => $listen,
168             ReuseAddr => 1,
169             Proto => 'tcp',
170             Listen => SOMAXCONN,
171             Blocking => 0,
172             );
173 14 50       13427 croak "couldn't bind to socket: $!" unless $sock;
174             }
175             }
176 14         43 return $sock;
177             }
178              
179             sub _prepare {
180 13     13   114 my $self = shift;
181              
182             # Normalize listen to arrayref (accept scalar for convenience)
183 13 50 33     284 if (defined $self->{listen} && !ref $self->{listen}) {
184 0         0 $self->{listen} = [ $self->{listen} ];
185             }
186             $self->{listen} ||=
187 13   0     143 [ ($self->{host}||DEFAULT_HOST).':'.($self->{port}||DEFAULT_PORT) ];
      0        
      50        
188             croak "listen must be an array reference"
189 13 50       196 if ref $self->{listen} ne 'ARRAY';
190             croak "listen array cannot be empty"
191 13 50       47 if @{$self->{listen}} == 0;
  13         66  
192 13         36 $self->{_listen_addrs} = [ @{$self->{listen}} ];
  13         69  
193              
194 13 50       107 if (my $opts = $self->{options}) {
195 0         0 $self->{$_} = delete $opts->{$_} for grep defined($opts->{$_}),
196             qw/pre_fork keepalive read_timeout header_timeout write_timeout max_connection_reqs reuseport epoll_exclusive
197             read_priority write_priority accept_priority max_accept_per_loop max_connections
198             max_read_buf max_body_len max_uri_len wbuf_low_water
199             reverse_proxy proxy_protocol psgix_io h2 tls tls_cert_file tls_key_file/;
200             # Warn about unknown options (likely typos)
201 0         0 for my $unknown (keys %$opts) {
202 0         0 carp "Unknown option '$unknown' ignored";
203             }
204             }
205              
206             # Validate pre_fork early (before socket creation) to fail fast
207 13 100       56 if ($self->{pre_fork}) {
208 3         26 my $n = $self->{pre_fork};
209 3 50 33     272 if ($n !~ /^\d+$/ || $n < 1) {
210 0         0 croak "pre_fork must be a positive integer";
211             }
212 3 50       46 if ($n > MAX_PRE_FORK) {
213 0         0 croak "pre_fork=$n exceeds maximum of " . MAX_PRE_FORK;
214             }
215             }
216              
217             # Enable reuseport automatically in prefork mode if SO_REUSEPORT available
218 13   0     215 my $use_reuseport = $self->{reuseport} && $self->{pre_fork} && defined SO_REUSEPORT;
219 13         50 $self->{_use_reuseport} = $use_reuseport;
220              
221 13         267 my $f = Feersum->endjinn;
222              
223             # EPOLLEXCLUSIVE must be set BEFORE use_socket() so the separate accept epoll
224             # is created with EPOLLEXCLUSIVE flag (Linux 4.5+)
225 13 50 66     172 if ($self->{epoll_exclusive} && $self->{pre_fork} && $^O eq 'linux') {
      33        
226 2         112 $f->set_epoll_exclusive(1);
227             }
228              
229             # Create sockets and attach to server for each listen address
230 13         27 my @socks;
231 13         34 for my $listen (@{$self->{_listen_addrs}}) {
  13         48  
232 14         94 my $sock = $self->_create_socket($listen, $use_reuseport);
233 14         78 push @socks, $sock;
234 14         94 $f->use_socket($sock);
235             }
236 13         88 $self->{sock} = $socks[0]; # backward compat: primary socket
237 13         38 $self->{_socks} = \@socks; # all sockets
238              
239 13         94 $f->set_keepalive($_) for grep defined, delete $self->{keepalive};
240 13         39 $f->set_reverse_proxy($_) for grep defined, delete $self->{reverse_proxy};
241 13         46 $f->set_proxy_protocol($_) for grep defined, delete $self->{proxy_protocol};
242 13         35 $f->set_psgix_io($_) for grep defined, delete $self->{psgix_io};
243 13         55 $f->read_timeout($_) for grep defined, delete $self->{read_timeout};
244 13         35 $f->header_timeout($_) for grep defined, delete $self->{header_timeout};
245 13         33 $f->write_timeout($_) for grep defined, delete $self->{write_timeout};
246 13         51 $f->max_connection_reqs($_) for grep defined, delete $self->{max_connection_reqs};
247             # Validate priority values (-2 to +2 per libev)
248 13         39 for my $prio_name (qw/read_priority write_priority accept_priority/) {
249 36         101 my $val = $self->{$prio_name};
250 36 100       189 if (defined $val) {
251             # Must be an integer (not float, not string)
252 5 50       35 croak "$prio_name must be an integer" unless $val =~ /^-?\d+$/;
253 5 100 100     522 croak "$prio_name must be between -2 and 2" if $val < -2 || $val > 2;
254             }
255             }
256 10         40 $f->read_priority($_) for grep defined, delete $self->{read_priority};
257 10         50 $f->write_priority($_) for grep defined, delete $self->{write_priority};
258 10         29 $f->accept_priority($_) for grep defined, delete $self->{accept_priority};
259             # Validate max_accept_per_loop (must be positive integer)
260 10 50       64 if (defined(my $val = $self->{max_accept_per_loop})) {
261 0 0 0     0 croak "max_accept_per_loop must be a positive integer"
262             unless $val =~ /^\d+$/ && $val > 0;
263             }
264 10         29 $f->max_accept_per_loop($_) for grep defined, delete $self->{max_accept_per_loop};
265             # Validate max_connections (must be non-negative integer, 0 = unlimited)
266 10 50       71 if (defined(my $val = $self->{max_connections})) {
267 0 0       0 croak "max_connections must be a non-negative integer"
268             unless $val =~ /^\d+$/;
269             }
270 10         34 $f->max_connections($_) for grep defined, delete $self->{max_connections};
271 10         28 $f->max_read_buf($_) for grep defined, delete $self->{max_read_buf};
272 10         25 $f->max_body_len($_) for grep defined, delete $self->{max_body_len};
273 10         34 $f->max_uri_len($_) for grep defined, delete $self->{max_uri_len};
274 10         30 $f->wbuf_low_water($_) for grep defined, delete $self->{wbuf_low_water};
275              
276             # Build tls hash from flat options (for Plack -o tls_cert_file=... -o tls_key_file=...)
277 10 100       38 if (!$self->{tls}) {
278 8 100       51 if (my $cert = delete $self->{tls_cert_file}) {
    100          
279             my $key = delete $self->{tls_key_file}
280 1 50       166 or croak "tls_cert_file requires tls_key_file";
281 0         0 $self->{tls} = { cert_file => $cert, key_file => $key };
282             } elsif (delete $self->{tls_key_file}) {
283 1         130 croak "tls_key_file requires tls_cert_file";
284             }
285             } else {
286             # tls hash takes precedence; discard flat options
287 2         5 delete $self->{tls_cert_file};
288 2         6 delete $self->{tls_key_file};
289             }
290              
291             # TLS configuration: apply to all listeners
292 8 100       50 if (my $tls = delete $self->{tls}) {
293 2 50       9 croak "tls must be a hash reference" unless ref $tls eq 'HASH';
294 2 50       5 croak "tls requires cert_file" unless $tls->{cert_file};
295 2 50       6 croak "tls requires key_file" unless $tls->{key_file};
296 2 50 33     326 -f $tls->{cert_file} && -r _
297             or croak "tls cert_file '$tls->{cert_file}': not found or not readable";
298 0 0 0     0 -f $tls->{key_file} && -r _
299             or croak "tls key_file '$tls->{key_file}': not found or not readable";
300              
301             # H2 is off by default; only enable if h2 => 1 was passed
302 0 0       0 if (delete $self->{h2}) {
303 0         0 $tls->{h2} = 1;
304             }
305              
306 0 0       0 if ($f->has_tls()) {
307 0         0 for my $i (0 .. $#socks) {
308 0         0 $f->set_tls(listener => $i, %$tls);
309             }
310             # Save TLS config for reuseport children to re-apply
311 0         0 $self->{_tls_config} = $tls;
312 0 0       0 $self->{quiet} or warn "Feersum [$$]: TLS enabled on "
313             . scalar(@socks) . " listener(s)\n";
314             } else {
315 0         0 croak "tls option requires Feersum compiled with TLS support (need picotls submodule + OpenSSL; see Alien::OpenSSL)";
316             }
317             } else {
318 6 50       20 if (delete $self->{h2}) {
319 0         0 croak "h2 requires TLS (provide tls_cert_file and tls_key_file, or a tls hash)";
320             }
321             }
322              
323 6         29 $self->{endjinn} = $f;
324 6         25 return;
325             }
326              
327             # for overriding:
328             sub assign_request_handler { ## no critic (RequireArgUnpacking)
329 3     3 1 23 return $_[0]->{endjinn}->request_handler($_[1]);
330             }
331              
332             sub run {
333 3     3 1 169 my $self = shift;
334 3         82 weaken $self;
335              
336 3         183 $self->{running} = 1;
337 3 50       155 $self->{quiet} or warn "Feersum [$$]: starting...\n";
338 3         128 $self->_prepare();
339              
340 3   33     27 my $app = shift || delete $self->{app};
341              
342 3 50 33     74 if (!$app && $self->{app_file}) {
343 3         160 local ($@, $!);
344 3         248 $app = do(rel2abs($self->{app_file}));
345 3 50       18 warn "couldn't parse $self->{app_file}: $@" if $@;
346 3 50 33     27 warn "couldn't do $self->{app_file}: $!" if ($! && !defined $app);
347 3 50       19 warn "couldn't run $self->{app_file}: didn't return anything"
348             unless $app;
349             }
350 3 50       9 croak "app not defined or failed to compile" unless $app;
351              
352 3         16 $self->assign_request_handler($app);
353              
354 3 50   6   320 $self->{_quit} = EV::signal 'QUIT', sub { $self && $self->quit };
  6         139  
355              
356 3 50       37 $self->_start_pre_fork if $self->{pre_fork};
357 3         28183952 EV::run;
358 3 50       37 $self->{quiet} or warn "Feersum [$$]: done\n";
359 3         58 $self->_cleanup();
360 3         299 return;
361             }
362              
363             sub _fork_another {
364 20     20   75 my ($self, $slot) = @_;
365              
366 20         43702 my $pid = fork;
367 20 50       865 croak "failed to fork: $!" unless defined $pid;
368 20 50       597 unless ($pid) {
369 0         0 EV::default_loop()->loop_fork;
370 0 0       0 $self->{quiet} or warn "Feersum [$$]: starting\n";
371 0         0 delete $self->{_kids};
372 0         0 delete $self->{pre_fork};
373 0         0 $self->{_n_kids} = 0;
374              
375             # With SO_REUSEPORT, each child creates its own sockets
376             # This eliminates accept() contention for better scaling
377 0 0       0 if ($self->{_use_reuseport}) {
378 0         0 $self->{endjinn}->unlisten();
379 0 0       0 for my $old_sock (@{$self->{_socks} || []}) {
  0         0  
380             close($old_sock)
381 0 0       0 or do { warn "close parent socket in child: $!"; POSIX::_exit(1); };
  0         0  
  0         0  
382             }
383 0         0 my @new_socks;
384             eval {
385 0         0 for my $listen (@{$self->{_listen_addrs}}) {
  0         0  
386 0         0 my $sock = $self->_create_socket($listen, 1);
387 0         0 push @new_socks, $sock;
388 0         0 $self->{endjinn}->use_socket($sock);
389             }
390 0         0 1;
391 0 0       0 } or do {
392 0         0 warn "Feersum [$$]: child socket creation failed: $@";
393 0         0 POSIX::_exit(1);
394             };
395 0         0 $self->{sock} = $new_socks[0];
396 0         0 $self->{_socks} = \@new_socks;
397              
398             # Re-apply TLS config on new listeners
399 0 0       0 if (my $tls = $self->{_tls_config}) {
400 0         0 for my $i (0 .. $#new_socks) {
401 0         0 $self->{endjinn}->set_tls(listener => $i, %$tls);
402             }
403             }
404             }
405              
406 0         0 eval { EV::run; }; ## no critic (RequireCheckingReturnValueOfEval)
  0         0  
407 0 0       0 carp $@ if $@;
408 0 0       0 POSIX::_exit($@ ? 1 : 0); # _exit avoids running parent's END blocks
409             }
410              
411 20         1030 weaken $self; # prevent circular ref with watcher callback
412 20         114 $self->{_n_kids}++;
413             $self->{_kids}[$slot] = EV::child $pid, 0, sub {
414 20     20   54 my $w = shift;
415 20 50       111 return unless $self; # guard against destruction during shutdown
416 20 50       78 $self->{quiet} or warn "Feersum [$$]: child $pid exited ".
417             "with rstatus ".$w->rstatus."\n";
418 20         54 $self->{_n_kids}--;
419 20 50       58 if ($self->{_shutdown}) {
420 20 100       49 unless ($self->{_n_kids}) {
421 3         153 $self->{_death} = undef;
422 3         25 EV::break(EV::BREAK_ALL());
423             }
424 20         16892 return;
425             }
426             # Without SO_REUSEPORT, parent needs to accept during respawn
427 0 0       0 unless ($self->{_use_reuseport}) {
428 0         0 my $feersum = $self->{endjinn};
429 0 0       0 my @socks = @{$self->{_socks} || [$self->{sock}]};
  0         0  
430 0         0 my $all_valid = 1;
431 0         0 for my $sock (@socks) {
432 0 0       0 unless (defined fileno $sock) {
433 0         0 $all_valid = 0;
434 0         0 last;
435             }
436             }
437 0 0       0 if ($all_valid) {
438 0         0 for my $sock (@socks) {
439 0         0 $feersum->accept_on_fd(fileno $sock);
440             }
441 0         0 $self->_fork_another($slot);
442 0         0 $feersum->unlisten;
443             } else {
444 0         0 carp "fileno returned undef during respawn, cannot respawn worker";
445             }
446             }
447             else {
448             # With SO_REUSEPORT, just spawn new child (it creates its own socket)
449 0         0 $self->_fork_another($slot);
450             }
451 20         4138 };
452 20         1599 return;
453             }
454              
455             sub _start_pre_fork {
456 3     3   7 my $self = shift;
457              
458             # pre_fork value already validated in _prepare()
459              
460 3 50       481 POSIX::setsid() or croak "setsid() failed: $!";
461              
462 3         36 $self->{_kids} = [];
463 3         18 $self->{_n_kids} = 0;
464 3         48 $self->_fork_another($_) for (1 .. $self->{pre_fork});
465              
466             # Parent stops accepting - children handle connections
467 3         501 $self->{endjinn}->unlisten();
468              
469             # With SO_REUSEPORT, parent can close its sockets entirely
470             # Children have their own sockets
471 3 50       104 if ($self->{_use_reuseport}) {
472 0 0       0 for my $sock (@{$self->{_socks} || []}) {
  0         0  
473 0 0       0 close($sock)
474             or warn "close parent socket after fork: $!";
475             }
476 0         0 $self->{sock} = undef;
477 0         0 $self->{_socks} = [];
478             }
479 3         78 return;
480             }
481              
482             sub quit {
483 6     6 1 27 my $self = shift;
484 6 100       12744 return if $self->{_shutdown};
485              
486 3         58 $self->{_shutdown} = 1;
487 3 50       118 $self->{quiet} or warn "Feersum [$$]: shutting down...\n";
488 3         36 my $death = DEATH_TIMER;
489              
490 3 50       83 if ($self->{_n_kids}) {
491             # in parent, broadcast SIGQUIT to the process group (including self,
492             # but protected by _shutdown flag above)
493 3         295 kill POSIX::SIGQUIT, -$$;
494 3         45 $death += DEATH_TIMER_INCR;
495             }
496             else {
497             # in child or solo process
498 0     0   0 $self->{endjinn}->graceful_shutdown(sub { POSIX::_exit(0) });
  0         0  
499             }
500              
501 3     0   219 $self->{_death} = EV::timer $death, 0, sub { POSIX::_exit(1) };
  0         0  
502 3         86 return;
503             }
504              
505             1;
506             __END__