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   1855369 use warnings;
  9         38  
  9         464  
3 9     9   32 use strict;
  9         10  
  9         248  
4              
5 9     9   3386 use EV;
  9         15229  
  9         276  
6 9     9   3620 use Feersum;
  9         22  
  9         431  
7 9         1103 use Socket qw/SOMAXCONN SOL_SOCKET SO_REUSEADDR AF_INET SOCK_STREAM
8 9     9   61 inet_aton pack_sockaddr_in/;
  9         12  
9             BEGIN {
10             # IPv6 support (Socket 1.95+, Perl 5.14+)
11 9         214 eval { Socket->import(qw/AF_INET6 inet_pton pack_sockaddr_in6/); 1 }
  9         541  
12 9 50   9   45 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         130 eval { Socket->import('SO_REUSEPORT'); 1 }
  9         146  
21 9 50   9   20 or *SO_REUSEPORT = sub () { undef };
22             }
23 9     9   4104 use POSIX ();
  9         50072  
  9         308  
24 9     9   47 use Scalar::Util qw/weaken/;
  9         9  
  9         401  
25 9     9   36 use Carp qw/carp croak/;
  9         14  
  9         312  
26 9     9   1131 use File::Spec::Functions 'rel2abs';
  9         1742  
  9         382  
27              
28 9     9   33 use constant DEATH_TIMER => 5.0; # seconds
  9         14  
  9         556  
29 9     9   37 use constant DEATH_TIMER_INCR => 2.0; # seconds
  9         9  
  9         288  
30 9     9   29 use constant DEFAULT_HOST => 'localhost';
  9         10  
  9         243  
31 9     9   29 use constant DEFAULT_PORT => 5000;
  9         9  
  9         321  
32 9   50 9   27 use constant MAX_PRE_FORK => $ENV{FEERSUM_MAX_PRE_FORK} || 1000;
  9         10  
  9         32213  
33              
34             our $INSTANCE;
35             sub new { ## no critic (RequireArgUnpacking)
36 17     17 1 19379 my $c = shift;
37 17 100       294 if ($INSTANCE) {
38             croak "Only one Feersum::Runner instance can be active at a time"
39 1 50       2 if $INSTANCE->{running};
40             # Clean up old instance state before creating new one
41 1         4 $INSTANCE->_cleanup();
42 1         2 undef $INSTANCE;
43             }
44 17         598 $INSTANCE = bless {quiet=>1, @_, running=>0}, $c;
45 17         125 return $INSTANCE;
46             }
47              
48             sub _cleanup {
49 18     18   32 my $self = shift;
50 18 100       63 return if $self->{_cleaned_up};
51 17         55 $self->{_cleaned_up} = 1;
52 17 100       90 if (my $f = $self->{endjinn}) {
53 6     0   90 $f->request_handler(sub{});
54 6         58 $f->unlisten();
55             }
56 17         90 $self->{_quit} = undef;
57 17         33 $self->{running} = 0;
58 17         242 return;
59             }
60              
61             sub DESTROY {
62 14     14   8924 local $@;
63 14         35 $_[0]->_cleanup();
64             }
65              
66             sub _create_socket {
67 14     14   33 my ($self, $listen, $use_reuseport) = @_;
68              
69 14         194 my $sock;
70 14 50       128 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         207 require IO::Socket::INET;
90             # SO_REUSEPORT must be set BEFORE bind for multiple sockets per port
91 14 50 33     42 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       70 if ($listen =~ /:(\d+)$/) {
    0          
160 14         132 my $port = $1;
161 14 50       68 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         319 $sock = IO::Socket::INET->new(
167             LocalAddr => $listen,
168             ReuseAddr => 1,
169             Proto => 'tcp',
170             Listen => SOMAXCONN,
171             Blocking => 0,
172             );
173 14 50       9578 croak "couldn't bind to socket: $!" unless $sock;
174             }
175             }
176 14         24 return $sock;
177             }
178              
179             sub _prepare {
180 13     13   68 my $self = shift;
181              
182             # Normalize listen to arrayref (accept scalar for convenience)
183 13 50 33     341 if (defined $self->{listen} && !ref $self->{listen}) {
184 0         0 $self->{listen} = [ $self->{listen} ];
185             }
186             $self->{listen} ||=
187 13   0     102 [ ($self->{host}||DEFAULT_HOST).':'.($self->{port}||DEFAULT_PORT) ];
      0        
      50        
188             croak "listen must be an array reference"
189 13 50       179 if ref $self->{listen} ne 'ARRAY';
190             croak "listen array cannot be empty"
191 13 50       26 if @{$self->{listen}} == 0;
  13         104  
192 13         34 $self->{_listen_addrs} = [ @{$self->{listen}} ];
  13         132  
193              
194 13 50       57 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       48 if ($self->{pre_fork}) {
208 3         30 my $n = $self->{pre_fork};
209 3 50 33     244 if ($n !~ /^\d+$/ || $n < 1) {
210 0         0 croak "pre_fork must be a positive integer";
211             }
212 3 50       26 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     58 my $use_reuseport = $self->{reuseport} && $self->{pre_fork} && defined SO_REUSEPORT;
219 13         26 $self->{_use_reuseport} = $use_reuseport;
220              
221 13         221 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     117 if ($self->{epoll_exclusive} && $self->{pre_fork} && $^O eq 'linux') {
      33        
226 2         110 $f->set_epoll_exclusive(1);
227             }
228              
229             # Create sockets and attach to server for each listen address
230 13         36 my @socks;
231 13         24 for my $listen (@{$self->{_listen_addrs}}) {
  13         45  
232 14         43 my $sock = $self->_create_socket($listen, $use_reuseport);
233 14         28 push @socks, $sock;
234 14         62 $f->use_socket($sock);
235             }
236 13         28 $self->{sock} = $socks[0]; # backward compat: primary socket
237 13         21 $self->{_socks} = \@socks; # all sockets
238              
239 13         78 $f->set_keepalive($_) for grep defined, delete $self->{keepalive};
240 13         23 $f->set_reverse_proxy($_) for grep defined, delete $self->{reverse_proxy};
241 13         22 $f->set_proxy_protocol($_) for grep defined, delete $self->{proxy_protocol};
242 13         20 $f->set_psgix_io($_) for grep defined, delete $self->{psgix_io};
243 13         21 $f->read_timeout($_) for grep defined, delete $self->{read_timeout};
244 13         19 $f->header_timeout($_) for grep defined, delete $self->{header_timeout};
245 13         28 $f->write_timeout($_) for grep defined, delete $self->{write_timeout};
246 13         32 $f->max_connection_reqs($_) for grep defined, delete $self->{max_connection_reqs};
247             # Validate priority values (-2 to +2 per libev)
248 13         26 for my $prio_name (qw/read_priority write_priority accept_priority/) {
249 36         63 my $val = $self->{$prio_name};
250 36 100       59 if (defined $val) {
251             # Must be an integer (not float, not string)
252 5 50       26 croak "$prio_name must be an integer" unless $val =~ /^-?\d+$/;
253 5 100 100     358 croak "$prio_name must be between -2 and 2" if $val < -2 || $val > 2;
254             }
255             }
256 10         30 $f->read_priority($_) for grep defined, delete $self->{read_priority};
257 10         34 $f->write_priority($_) for grep defined, delete $self->{write_priority};
258 10         15 $f->accept_priority($_) for grep defined, delete $self->{accept_priority};
259             # Validate max_accept_per_loop (must be positive integer)
260 10 50       27 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         25 $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       31 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         16 $f->max_connections($_) for grep defined, delete $self->{max_connections};
271 10         17 $f->max_read_buf($_) for grep defined, delete $self->{max_read_buf};
272 10         14 $f->max_body_len($_) for grep defined, delete $self->{max_body_len};
273 10         17 $f->max_uri_len($_) for grep defined, delete $self->{max_uri_len};
274 10         17 $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       23 if (!$self->{tls}) {
278 8 100       40 if (my $cert = delete $self->{tls_cert_file}) {
    100          
279             my $key = delete $self->{tls_key_file}
280 1 50       92 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         91 croak "tls_key_file requires tls_cert_file";
284             }
285             } else {
286             # tls hash takes precedence; discard flat options
287 2         4 delete $self->{tls_cert_file};
288 2         4 delete $self->{tls_key_file};
289             }
290              
291             # TLS configuration: apply to all listeners
292 8 100       39 if (my $tls = delete $self->{tls}) {
293 2 50       7 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       4 croak "tls requires key_file" unless $tls->{key_file};
296 2 50 33     279 -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       12 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         24 $self->{endjinn} = $f;
324 6         14 return;
325             }
326              
327             # for overriding:
328             sub assign_request_handler { ## no critic (RequireArgUnpacking)
329 3     3 1 21 return $_[0]->{endjinn}->request_handler($_[1]);
330             }
331              
332             sub run {
333 3     3 1 76 my $self = shift;
334 3         86 weaken $self;
335              
336 3         159 $self->{running} = 1;
337 3 50       167 $self->{quiet} or warn "Feersum [$$]: starting...\n";
338 3         103 $self->_prepare();
339              
340 3   33     24 my $app = shift || delete $self->{app};
341              
342 3 50 33     74 if (!$app && $self->{app_file}) {
343 3         123 local ($@, $!);
344 3         90 $app = do(rel2abs($self->{app_file}));
345 3 50       13 warn "couldn't parse $self->{app_file}: $@" if $@;
346 3 50 33     41 warn "couldn't do $self->{app_file}: $!" if ($! && !defined $app);
347 3 50       16 warn "couldn't run $self->{app_file}: didn't return anything"
348             unless $app;
349             }
350 3 50       7 croak "app not defined or failed to compile" unless $app;
351              
352 3         12 $self->assign_request_handler($app);
353              
354 3 50   6   167 $self->{_quit} = EV::signal 'QUIT', sub { $self && $self->quit };
  6         167  
355              
356 3 50       36 $self->_start_pre_fork if $self->{pre_fork};
357 3         27852586 EV::run;
358 3 50       15 $self->{quiet} or warn "Feersum [$$]: done\n";
359 3         38 $self->_cleanup();
360 3         94 return;
361             }
362              
363             sub _fork_another {
364 20     20   47 my ($self, $slot) = @_;
365              
366 20         23999 my $pid = fork;
367 20 50       918 croak "failed to fork: $!" unless defined $pid;
368 20 50       119 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         661 weaken $self; # prevent circular ref with watcher callback
412 20         177 $self->{_n_kids}++;
413             $self->{_kids}[$slot] = EV::child $pid, 0, sub {
414 20     20   87 my $w = shift;
415 20 50       88 return unless $self; # guard against destruction during shutdown
416 20 50       51 $self->{quiet} or warn "Feersum [$$]: child $pid exited ".
417             "with rstatus ".$w->rstatus."\n";
418 20         26 $self->{_n_kids}--;
419 20 50       77 if ($self->{_shutdown}) {
420 20 100       113 unless ($self->{_n_kids}) {
421 3         72 $self->{_death} = undef;
422 3         35 EV::break(EV::BREAK_ALL());
423             }
424 20         1289 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         3226 };
452 20         1020 return;
453             }
454              
455             sub _start_pre_fork {
456 3     3   6 my $self = shift;
457              
458             # pre_fork value already validated in _prepare()
459              
460 3 50       319 POSIX::setsid() or croak "setsid() failed: $!";
461              
462 3         28 $self->{_kids} = [];
463 3         24 $self->{_n_kids} = 0;
464 3         27 $self->_fork_another($_) for (1 .. $self->{pre_fork});
465              
466             # Parent stops accepting - children handle connections
467 3         439 $self->{endjinn}->unlisten();
468              
469             # With SO_REUSEPORT, parent can close its sockets entirely
470             # Children have their own sockets
471 3 50       100 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         70 return;
480             }
481              
482             sub quit {
483 6     6 1 31 my $self = shift;
484 6 100       10578 return if $self->{_shutdown};
485              
486 3         74 $self->{_shutdown} = 1;
487 3 50       129 $self->{quiet} or warn "Feersum [$$]: shutting down...\n";
488 3         33 my $death = DEATH_TIMER;
489              
490 3 50       65 if ($self->{_n_kids}) {
491             # in parent, broadcast SIGQUIT to the process group (including self,
492             # but protected by _shutdown flag above)
493 3         421 kill POSIX::SIGQUIT, -$$;
494 3         62 $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   269 $self->{_death} = EV::timer $death, 0, sub { POSIX::_exit(1) };
  0         0  
502 3         107 return;
503             }
504              
505             1;
506             __END__