File Coverage

blib/lib/Doit/Fork.pm
Criterion Covered Total %
statement 85 96 88.5
branch 16 24 66.6
condition n/a
subroutine 14 16 87.5
pod 0 3 0.0
total 115 139 82.7


line stmt bran cond sub pod time code
1             # -*- perl -*-
2              
3             #
4             # Author: Slaven Rezic
5             #
6             # Copyright (C) 2017,2023,2024 Slaven Rezic. All rights reserved.
7             # This package is free software; you can redistribute it and/or
8             # modify it under the same terms as Perl itself.
9             #
10             # Mail: slaven@rezic.de
11             # WWW: http://www.rezic.de/eserte/
12             #
13              
14             package Doit::Fork;
15              
16 2     2   18 use Doit;
  2         4  
  2         20  
17              
18 2     2   14 use strict;
  2         2  
  2         46  
19 2     2   14 use warnings;
  2         4  
  2         124  
20             our $VERSION = '0.03';
21              
22 2     2   10 use vars '@ISA'; @ISA = ('Doit::_AnyRPCImpl');
  2         4  
  2         154  
23              
24 2     2   14 use Doit::Log;
  2         4  
  2         1768  
25              
26             our @last_exits;
27             our $keep_last_exits; $keep_last_exits = 10 if !defined $keep_last_exits;
28              
29 2     2 0 24 sub new { bless {}, shift }
30 2     2 0 8 sub functions { qw() }
31              
32             sub do_connect {
33 2     2 0 8 my($class, %opts) = @_;
34              
35 2         6 my $dry_run = delete $opts{dry_run};
36 2         4 my $debug = delete $opts{debug};
37 2 50       18 die "Unhandled options: " . join(" ", %opts) if %opts;
38              
39 2         6 my $self = bless { }, $class;
40              
41 2         4 my $d;
42 2 50       10 if ($debug) {
43             $d = sub ($) {
44 0     0   0 Doit::Log::info("PARENT: $_[0]");
45 0         0 };
46             } else {
47 2     3   12 $d = sub ($) { };
48             }
49 2         8 $self->{d} = $d;
50              
51 2         1556 require IO::Pipe;
52 2         3518 my $pipe_to_fork = IO::Pipe->new;
53 2         324 my $pipe_from_fork = IO::Pipe->new;
54 2         5774 my $worker_pid = fork;
55 2 50       367 if (!defined $worker_pid) {
    100          
56 0         0 error "fork failed: $!";
57             } elsif ($worker_pid == 0) {
58 1         18 my $d = do {
59 1 50       58 local @ARGV = $dry_run ? '--dry-run' : ();
60 1         140 Doit->init;
61             };
62 1         40 $pipe_to_fork->reader;
63 1         170 $pipe_from_fork->writer;
64 1         105 $pipe_from_fork->autoflush(1);
65 1         123 Doit::RPC::PipeServer->new($d, $pipe_to_fork, $pipe_from_fork, debug => $debug)->run;
66 1         48 CORE::exit(0);
67             }
68              
69 1         156 $d->("Forked worker $worker_pid...");
70              
71 1         68 $pipe_to_fork->writer;
72 1         537 $pipe_from_fork->reader;
73 1         173 $self->{rpc} = Doit::RPC::Client->new($pipe_from_fork, $pipe_to_fork, label => "fork:", debug => $debug);
74 1         20 $self->{pid} = $worker_pid;
75              
76 1         45 $self;
77             }
78              
79             sub DESTROY {
80 4     4   12 my $self = shift;
81             # Note: if new() is called without followed by do_connect(), then no {pid} is set
82 4 100       37 if (defined $self->{pid}) {
83 1         10 $self->{d}->("About to destroy fork with pid $self->{pid}...");
84             }
85 4         61 delete $self->{rpc};
86 4 100       360 if (defined $self->{pid}) {
87 1         26 $self->{d}->(" reap child process");
88 1         1801914 waitpid $self->{pid}, 0;
89 1         33 my %exit_res = Doit::_analyze_dollar_questionmark();
90 1         8 $exit_res{pid} = $self->{pid};
91 1         5 push @last_exits, \%exit_res;
92 1 50       4 if (defined $keep_last_exits) {
93 1         34 while (@last_exits > $keep_last_exits) {
94 0         0 shift @last_exits;
95             }
96             }
97             }
98             }
99              
100             {
101             package Doit::RPC::PipeServer;
102 2     2   20 use vars '@ISA'; @ISA = ('Doit::RPC');
  2         6  
  2         1408  
103              
104             sub new {
105 1     1   25 my($class, $runner, $pipe_to_server, $pipe_from_server, %options) = @_;
106              
107 1         8 my $debug = delete $options{debug};
108 1 50       15 die "Unhandled options: " . join(" ", %options) if %options;
109              
110 1         39 bless {
111             runner => $runner,
112             pipe_to_server => $pipe_to_server,
113             pipe_from_server => $pipe_from_server,
114             debug => $debug,
115             }, $class;
116             }
117              
118             sub run {
119 1     1   3 my($self) = @_;
120              
121 1         2 my $d;
122 1 50       49 if ($self->{debug}) {
123             $d = sub ($) {
124 0     0   0 Doit::Log::info("WORKER: $_[0]");
125 0         0 };
126             } else {
127 1     15   34 $d = sub ($) { };
128             }
129              
130 1         45 $d->("Start worker ($$)...");
131 1         13 my $pipe_to_server = $self->{pipe_to_server};
132 1         3 my $pipe_from_server = $self->{pipe_from_server};
133              
134 1         7 $self->{infh} = $pipe_to_server;
135 1         9 $self->{outfh} = $pipe_from_server;
136 1         13 while () {
137 5         44 $d->(" waiting for line from comm");
138 5         68 my($context, @data) = $self->receive_data;
139 5 100       226 if (!defined $context) {
    50          
140 1         9 $d->(" got eof");
141 1         7 $pipe_to_server->close;
142 1         96 $pipe_from_server->close;
143 1         36 return;
144             } elsif ($data[0] =~ m{^exit$}) {
145 0         0 $d->(" got exit command");
146 0         0 $self->send_data('r', 'bye-bye');
147 0         0 $pipe_to_server->close;
148 0         0 $pipe_from_server->close;
149 0         0 return;
150             }
151 4         17 $d->(" calling method $data[0]");
152 4         42 my($rettype, @ret) = $self->{runner}->call_wrapped_method($context, @data);
153 4         15 $d->(" sending result back");
154 4         54 $self->send_data($rettype, @ret);
155             }
156             }
157             }
158              
159             1;
160              
161             __END__