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;
|