File Coverage

blib/lib/Win32/ProcFarm/Parent.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             #############################################################################
2             #
3             # Win32::ProcFarm::Parent - stand-in for child process in ProcFarm RPC system
4             #
5             # Author: Toby Everett
6             # Revision: 2.15
7             # Last Change: Added support for exe-based child process
8             #############################################################################
9             # Copyright 1999, 2000, 2001 Toby Everett. All rights reserved.
10             #
11             # This file is distributed under the Artistic License. See
12             # http://www.ActiveState.com/corporate/artistic_license.htm or
13             # the license that comes with your perl distribution.
14             #
15             # For comments, questions, bugs or general interest, feel free to
16             # contact Toby Everett at teverett@alascom.att.com
17             #############################################################################
18            
19             =head1 NAME
20            
21             Win32::ProcFarm::Parent - stand-in for child process in ProcFarm RPC system
22            
23             =head1 SYNOPSIS
24            
25             use Win32::ProcFarm::Parent;
26             use Win32::ProcFarm::Port;
27            
28             $port_obj = Win32::ProcFarm::Port->new(9000, 1);
29            
30             $iface = Win32::ProcFarm::Parent->new_async($port_obj, 'Child.pl', Win32::GetCwd);
31            
32             $iface->connect;
33            
34             $iface->execute('child_sub', @params);
35            
36             until($iface->get_state eq 'fin') {
37             print "Waiting for ReturnValue.\n";
38             sleep(1);
39             }
40             print "GotReturnValue.\n";
41             print $iface->get_retval;
42            
43             =head1 DESCRIPTION
44            
45             =head2 Installation instructions
46            
47             This installs with MakeMaker as part of Win32::ProcFarm.
48            
49             To install via MakeMaker, it's the usual procedure - download from CPAN,
50             extract, type "perl Makefile.PL", "nmake" then "nmake install". Don't
51             do an "nmake test" because the I haven't written a test suite yet.
52            
53             =head2 State Diagram
54            
55             C is designed to provide support for asynchronous subroutine calls
56             against the child process. To support this, the C object can be in one
57             of four states.
58            
59             =over 4
60            
61             = item C
62            
63             In the C state, the C object has been asynchronously spun off, but
64             has yet to establish a communications channel via the C object. A call to
65             the C method will rectify this situation and move the object into the C state.
66            
67             =item C
68            
69             In the C state, the child process has yet to be assigned a task and is waiting for one to be
70             assigned. A call to the C method will assign the child process a task and move the
71             C object into the C state.
72            
73             =item C
74            
75             In the C state, the child process has been assigned a task and is busy executing it. Calls
76             to the C method will check to see if the task has finished executing. If it has, the
77             C object will retrieve the return values, store them internally, and move
78             the object into the C state.
79            
80             =item C
81            
82             In the C state, the C object is waiting for the return values to be
83             retrieved by the C method. A call to that method will return the values and move the
84             object back into the C state.
85            
86             =back
87            
88             =head1 METHODS
89            
90             =cut
91            
92 1     1   1137 use Data::Dumper;
  1         11515  
  1         96  
93 1     1   2251 use Win32::Process;
  0            
  0            
94             use Win32::ProcFarm::Port;
95             use Win32::ProcFarm::TickCount;
96            
97             package Win32::ProcFarm::Parent;
98            
99             use strict;
100             use vars qw($VERSION @ISA);
101            
102             $VERSION = '2.15';
103            
104             $Win32::ProcFarm::Parent::unique = 0;
105             $Win32::ProcFarm::Parent::processes = {};
106            
107             =head2 new_async
108            
109             The C method creates a new C object and spins off the child
110             process, but does not initiate communication with it. The C object is
111             left in the C state.
112            
113             The parameters are:
114            
115             =over 4
116            
117             =item $port_obj
118            
119             A C object that will be connected to by the child processes.
120            
121             =item $script
122            
123             The script name to execute for the child processes.
124            
125             =item $curdir
126            
127             The working directory to use when running the script. If this is the same directory the script is
128             in, the script name can be specified without a path.
129            
130             =item $timeout
131            
132             An optional value indicating how long jobs should be allowed to execute before they are deemed to
133             have blocked. Blocked jobs will be terminated and a new process created to take their place.
134            
135             =back
136            
137             =cut
138            
139             sub new_async {
140             my $class = shift;
141             my($port_obj, $script, $curdir, $timeout) = @_;
142            
143             my $self = {
144             'port_obj' => $port_obj,
145             'rin' => undef,
146             'socket' => undef,
147             'state' => undef,
148             'timeout' => $timeout,
149             'start' => undef,
150             'retval' => undef,
151             'script' => $script,
152             'curdir' => $curdir,
153             };
154             bless $self, $class;
155            
156             $self->_new_async;
157            
158             return $self;
159             }
160            
161             sub _new_async {
162             my $self = shift;
163            
164             my $process;
165             my $unique = $Win32::ProcFarm::Parent::unique++;
166             my $port_num = $self->{port_obj}->get_port_num;
167             my $script = $self->{script};
168             if ($script =~ /\.exe$/i) {
169             Win32::Process::Create($process, $script, "$script $port_num $unique", 0, 0, $self->{curdir}) or
170             die "Unable to start child process using '$script'.\n";
171             } else {
172             (my $perl_exe = $^X) =~ s/\\[^\\]+$/\\Perl.exe/;
173             Win32::Process::Create($process, $perl_exe, "perl $script $port_num $unique", 0, 0, $self->{curdir}) or
174             die "Unable to start child process using '$perl_exe'.\n";
175             }
176             $Win32::ProcFarm::Parent::processes->{$unique} = $process;
177             $self->{state} = 'init';
178             return $self;
179             }
180            
181             =head2 connect
182            
183             The C method initiates communication with B child process. Note that we cannot
184             presume that the order in which the child processes connect to the TCP port is the same order in
185             which they were started. The first thing the child process does upon the TCP connection being
186             accepted is to send its unique identifier, which the C object uses to
187             retrieve the appropriate C from the class hash of those objects.
188            
189             The C call moves the C object into the C state.
190            
191             =cut
192            
193             sub connect {
194             my $self = shift;
195            
196             $self->{state} eq 'init' or die "Illegal call to connect on Win32::ProcFarm::Parent object in state $self->{state}.";
197             $self->{socket} = $self->{port_obj}->get_next_connection;
198            
199             my $unique;
200             read($self->{socket}, $unique, 4) == 4 or die "Unable to read unique identifier.\n";
201             $unique = unpack("V", $unique);
202             exists $Win32::ProcFarm::Parent::processes->{$unique} or die "Missing process object for $unique.";
203             $self->{process_obj} = $Win32::ProcFarm::Parent::processes->{$unique};
204             delete $Win32::ProcFarm::Parent::processes->{$unique};
205            
206             $self->{rin} = '';
207             vec($self->{rin}, fileno($self->{socket}), 1) = 1;
208             $self->{state} = 'idle';
209             }
210            
211             =head2 execute
212            
213             The C command instructs the child process to start executing a given subroutine with a
214             list of passed parameters. The data is send over the socket connection and the
215             C object moved into the C state.
216            
217             =cut
218            
219             sub execute {
220             my $self = shift;
221             my($command, @params) = @_;
222            
223             $self->{state} eq 'idle' or die "Illegal call to execute on Win32::ProcFarm::Parent object in state $self->{state}.";
224             my $cmdstr = Data::Dumper->Dump([$command, \@params], ["command", "ptr2params"]);
225             my $temp = $self->{socket};
226             print $temp (pack("V", length($cmdstr)).$cmdstr);
227             $self->{start} = Win32::GetTickCount();
228             $self->{state} = 'wait';
229             }
230            
231             =head2 get_state
232            
233             The C method returns the current state. If the current state is C, the method
234             first checks to see if the child process has finished executing the subrouting call. If it has,
235             the method retrieves the return data and moves the C object into the
236             C state.
237            
238             The C method is also responsible for dealing with timeout scenarios where the child
239             process has exceeded the time allowed to execute the subroutine. In that situation, the child
240             process is terminated and a new child process initiated, connected to, and the
241             C object placed in the C state.
242            
243             =cut
244            
245             sub get_state {
246             my $self = shift;
247            
248             if ($self->{state} eq 'wait') {
249             my $rout;
250             select($rout=$self->{rin}, undef, undef, 0);
251             if ($rout eq $self->{rin}) {
252             $self->{retval} = $self->_get_retval;
253             $self->{state} = 'fin';
254             } else {
255             if ($self->{timeout} and Win32::ProcFarm::TickCount::compare(1000*$self->{timeout}+$self->{start}, Win32::GetTickCount()) == -1) {
256             $self->_reset();
257             }
258             }
259             }
260             return $self->{state};
261             }
262            
263             =head2 get_retval
264            
265             The C method returns the list of return values returned by the child process and moves
266             the C object into the C state.
267            
268             =cut
269            
270             sub get_retval {
271             my $self = shift;
272            
273             $self->{state} eq 'fin' or die "Illegal call to get_retval on Win32::ProcFarm::Parent object in state $self->{state}.";
274             my $temp = $self->{retval};
275             $self->{retval} = undef;
276             $self->{state} = 'idle';
277             return(@{$temp});
278             }
279            
280             sub _get_retval {
281             my $self = shift;
282             my($len, $retstr);
283            
284             unless (read($self->{socket}, $len, 4) == 4) {
285             $self->_reset;
286             return [];
287             }
288             $len = unpack("V", $len);
289             unless (read($self->{socket}, $retstr, $len) == $len) {
290             $self->_reset;
291             return [];
292             }
293            
294             my $ptr2retval;
295             eval($retstr);
296             return $ptr2retval;
297             }
298            
299             sub _reset {
300             my $self = shift;
301            
302             close($self->{socket});
303             unless ($self->{process_obj}->Wait(1)) {
304             $self->{process_obj}->Kill(0);
305             }
306             $self->_new_async;
307             $self->connect;
308             $self->{retval} = [];
309             $self->{state} = 'fin';
310             }
311            
312             sub DESTROY {
313             my $self = shift;
314            
315             foreach my $i (values %{$Win32::ProcFarm::Parent::processes}) {
316             unless ($i->Wait(1)) {
317             $i->Kill(0);
318             }
319             }
320             $Win32::ProcFarm::Parent::processes = {};
321            
322             $self->{socket} and close($self->{socket});
323             if ($self->{process_obj}) {
324             unless ($self->{process_obj}->Wait(1)) {
325             $self->{process_obj}->Kill(0);
326             }
327             }
328             }
329            
330             1;