File Coverage

blib/lib/Mojolicious/Plugin/ForkAndGo.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::ForkAndGo;
2 1     1   499 use Mojo::Base 'Mojolicious::Plugin';
  1         1  
  1         7  
3              
4 1     1   179 use Mojo::IOLoop;
  1         1  
  1         8  
5 1     1   461 use IO::Pipely 'pipely';
  1         1584  
  1         53  
6 1     1   5 use POSIX qw(setsid);
  1         1  
  1         6  
7 1     1   53 use Scalar::Util qw(openhandle looks_like_number weaken);
  1         1  
  1         51  
8 1     1   255 use Devel::Refcount qw(refcount);
  0            
  0            
9             use File::Spec::Functions qw(catfile tmpdir);
10             use IO::Handle;
11             use Fcntl;
12             use Mojo::Util qw(slurp spurt steady_time);
13            
14             our $VERSION = '0.01';
15             our $app;
16             our $state = {};
17             our $pkg = __PACKAGE__;
18              
19             use constant DEBUG => $ENV{MOJOLICIOUS_PLUGIN_FORKANDGO_DEBUG} || 0;
20              
21             sub register {
22             my ($self, $app, $ops) = @_;
23              
24             $Mojolicious::Plugin::ForkAndGo::app = $app;
25              
26             my $forked = catfile(tmpdir, 'forkngo.state');
27              
28             if ($ENV{HYPNOTOAD_EXE} && ($ENV{HYPNOTOAD_REV} && 2 == $ENV{HYPNOTOAD_REV})) {
29             unlink($forked);
30             }
31             elsif ($ARGV[0] && $ARGV[0] =~ m/^(daemon|prefork)$/) {
32             unlink($forked);
33             }
34              
35             $app->helper(forked => sub {
36             my $code = pop;
37              
38             Mojo::IOLoop->next_tick(sub {
39             # TODO: Somehow clean leak this up
40             my $code_key = steady_time;
41              
42             # Create forks on same worker
43             eval {
44             sysopen(my $fh, $forked, O_RDWR|O_CREAT|O_EXCL) or die;
45             spurt($$, $forked);
46             };
47             if ($@) {
48             my $do_over = slurp($forked);
49             return unless $do_over == $$;
50              
51             $app->log->info("$$: created[1] next_tick: $forked") if DEBUG;
52             }
53             else {
54             $app->log->info("$$: created[0] next_tick: $forked") if DEBUG;
55             }
56              
57             my ($r, $w) = pipely;
58            
59             $state->{code}{$code_key}{r} = $r;
60             $state->{code}{$code_key}{w} = $w;
61              
62             $state->{callback}{$code_key} = $code;
63              
64             $pkg->fork($code_key);
65             });
66             });
67              
68             if ($ops->{process}) {
69             $self->$_ for @{ $ops->{process} };
70             }
71             }
72              
73             sub minion {
74             my $self = shift;
75              
76             $app->plugin(qw(Mojolicious::Plugin::ForkCall))
77             unless $app->can("fork_call");
78              
79             $app->forked(sub {
80             $app->fork_call(
81             sub {
82             # I dunno why I have (or if I have) to do this for hypnotoad
83             delete($ENV{HYPNOTOAD_APP});
84             delete($ENV{HYPNOTOAD_EXE});
85             delete($ENV{HYPNOTOAD_FOREGROUND});
86             delete($ENV{HYPNOTOAD_REV});
87             delete($ENV{HYPNOTOAD_STOP});
88             delete($ENV{HYPNOTOAD_TEST});
89             delete($ENV{MOJO_APP_LOADER});
90            
91             my @cmd = (
92             $^X,
93             $0,
94             "minion",
95             "worker"
96             );
97             $0 = join(" ", @cmd);
98              
99             $app->log->debug("$$: ForkAndGo minion worker") if DEBUG;
100             system(@cmd) == 0
101             or die("0: $?");
102              
103             return 1;
104             },
105             sub {
106             exit;
107             }
108             );
109              
110             Mojo::IOLoop->start;
111             });
112             }
113              
114             sub fork {
115             my $code_key = pop;
116              
117             my $r = $state->{code}{$code_key}{r};
118             my $w = $state->{code}{$code_key}{w};
119              
120             die "Can't fork: $!" unless defined(my $pid = fork);
121             if ($pid) { # Parent
122             close($r);
123              
124             return $pid;
125             }
126             close($w);
127             POSIX::setsid or die "Can't start a new session: $!";
128              
129             $app->log->info("$$: Child running: $$: " . getppid);
130              
131             # Child
132             Mojo::IOLoop->reset;
133              
134             my $stream = Mojo::IOLoop::Stream->new($r)->timeout(0);
135             Mojo::IOLoop->stream($stream);
136              
137             $stream->on(error => sub {
138             $app->log->info("$$: Child exiting: error: $$: $_[1]");
139              
140             $pkg->_cleanup;
141              
142             exit;
143             });
144             $stream->on(close => sub {
145             $app->log->info("$$: Child exiting: close: $$");
146              
147             $pkg->_cleanup;
148              
149             exit;
150             });
151              
152             Mojo::IOLoop->recurring(1 => sub {
153             my $loop = shift;
154              
155             my $str = sprintf("%s: %s: %s: $r", getppid, refcount($r), openhandle($r) // "CLOSED");
156             $app->log->info("$$: Child recurring: $str");
157             }) if DEBUG;
158              
159             my $code = $state->{callback}{$code_key};
160             $code->($app);
161              
162             return $pid;
163             }
164              
165             sub _cleanup {
166             $app->log->info("$$: Child -KILL: $$") if DEBUG;
167              
168             kill('-KILL', $$);
169             }
170              
171             1;
172             __END__