File Coverage

blib/lib/RPC/ExtDirect/Server/Util.pm
Criterion Covered Total %
statement 76 142 53.5
branch 14 62 22.5
condition 1 12 8.3
subroutine 21 29 72.4
pod 4 13 30.7
total 116 258 44.9


line stmt bran cond sub pod time code
1             package RPC::ExtDirect::Server::Util;
2              
3 5     5   216751 use strict;
  5         7  
  5         108  
4 5     5   16 use warnings;
  5         10  
  5         110  
5 5     5   692 no warnings 'uninitialized'; ## no critic
  5         8  
  5         164  
6              
7 5     5   15 use Carp;
  5         5  
  5         238  
8 5     5   2241 use Socket;
  5         12869  
  5         1797  
9 5     5   6738 use Getopt::Std;
  5         145  
  5         226  
10 5     5   21 use Exporter;
  5         5  
  5         121  
11              
12 5     5   1833 use RPC::ExtDirect::Server;
  5         10  
  5         148  
13              
14 5     5   19 use base 'Exporter';
  5         4  
  5         4554  
15              
16             our @EXPORT = qw/
17             maybe_start_server
18             start_server
19             stop_server
20             /;
21              
22             ### PRIVATE PACKAGE SUBROUTINES ###
23             #
24             # Internal use only.
25             #
26              
27             {
28             my ($server_pid, $server_host, $server_port, $dont_stop);
29            
30 5     5 0 9 sub get_server_pid { $server_pid };
31 8     8 0 49 sub set_server_pid { $server_pid = shift; };
32            
33 3     3 0 4 sub get_server_host { $server_host };
34 3     3 0 9 sub set_server_host { $server_host = shift };
35            
36 3     3 0 3 sub get_server_port { $server_port };
37 8     8 0 18 sub set_server_port { $server_port = shift; };
38            
39 5     5 0 30 sub get_no_shutdown { $dont_stop };
40 0     0 1 0 sub no_shutdown { $dont_stop = shift; };
41             }
42              
43             ### EXPORTED PUBLIC PACKAGE SUBROUTINE ###
44             #
45             # See if a host and port were given in the @ARGV, and start a new
46             # server instance if not.
47             #
48              
49             sub maybe_start_server {
50 3 50   3 1 272 if ( @ARGV ) {
51 0         0 my %opt;
52            
53 0         0 getopts('h:p:fes:t:l:', \%opt);
54            
55 0 0       0 if ( $opt{p} ) {
56            
57             # If a port is given but not the host name,
58             # we assume localhost
59 0   0     0 my $host = $opt{h} || '127.0.0.1';
60 0         0 my $port = $opt{p};
61            
62 0 0       0 return wantarray ? ($host, $port) : "$host:$port";
63             }
64            
65             # Not quoting $opt{s} makes my text editor lose its mind ;)
66 0 0       0 push @_, static_dir => $opt{'s'} if $opt{'s'};
67 0 0       0 push @_, foreground => 1 if $opt{f};
68 0 0       0 push @_, enbugger => 1 if $opt{e};
69 0 0       0 push @_, enbugger_timer => $opt{t} if defined $opt{t};
70 0 0       0 push @_, host => $opt{h} if defined $opt{h};
71 0 0       0 push @_, port => $opt{l} if defined $opt{l};
72             }
73            
74 3         9 return start_server( @_ );
75             }
76              
77             ### EXPORTED PUBLIC PACKAGE SUBROUTINE ###
78             #
79             # Start an RPC::ExtDirect::Server instance, wait for it to bind
80             # to a port and return the host and port number.
81             # If an instance has already been started, return its parameters
82             # instead of starting a new one.
83             #
84              
85             sub start_server {
86 3     3 1 8 my (%arg) = @_;
87            
88             {
89 3         3 my $host = get_server_host;
  3         7  
90 3         7 my $port = get_server_port;
91            
92 3 50       9 if ( $port ) {
93 0 0       0 return wantarray ? ($host, $port) : "$host:$port";
94             }
95             }
96            
97             # This parameter is used for internal testing
98 3         5 my $sleep = delete $arg{sleep};
99 3         6 my $foreground = delete $arg{foreground};
100 3         4 my $enbugger = delete $arg{enbugger};
101 3         5 my $enbugger_timer = delete $arg{set_timer};
102 3   50     15 my $timeout = delete $arg{timeout} || 30;
103            
104             # Debug flag is checked below to avoid printing the banner
105 3 50       8 my $server_debug = $arg{config} ? $arg{config}->debug : $arg{debug};
106            
107             # We default to verbose exceptions, which is against Ext.Direct spec
108             # but feels somewhat saner and is better for testing
109 3 50       9 $arg{verbose_exceptions} = 1 unless defined $arg{verbose_exceptions};
110            
111 3 50       8 if ( $enbugger ) {
112 0         0 local $@;
113 0         0 eval "require Enbugger";
114             }
115            
116             # Interactive start means we're not forking but running the server
117             # in the current process. Useful for Enbugging.
118 3 50       7 if ( $foreground ) {
119 0 0       0 if ( $enbugger_timer ) {
120 0         0 my $old_alarm = $SIG{ALRM};
121            
122             $SIG{ALRM} = sub {
123 0     0   0 alarm 0;
124 0         0 $SIG{ALRM} = $old_alarm;
125 0         0 Enbugger->stop;
126 0         0 };
127            
128 0         0 alarm $enbugger_timer;
129             }
130            
131             do_start_server(
132             %arg,
133            
134             after_listener => sub {
135 0     0   0 my ($self) = @_;
136            
137 0         0 my $host = $self->host;
138 0         0 my $port = $self->port;
139            
140 0 0       0 print ref($self)." is listening on $host:$port\n"
141             unless $server_debug;
142             }
143 0         0 );
144            
145             # This should be unreachable, but just in case
146 0         0 exit 0;
147             }
148              
149 3         4 my ($pid, $pipe_rd, $pipe_wr);
150 3 50       57 pipe($pipe_rd, $pipe_wr) or die "Can't open pipe: $!";
151              
152 3 50 0     2386 if ( $pid = fork ) {
    0          
153 3         85 close $pipe_wr;
154 3     0   274 local $SIG{CHLD} = sub { waitpid $pid, 0 };
  0         0  
155              
156             # Wait until the kid starts up, but don't block forever either
157 3         35 my ($host, $port) = eval {
158 3     0   76 local $SIG{ALRM} = sub { die "alarm\n" };
  0         0  
159 3         29 alarm $timeout;
160            
161 3         7689 my ($host, $port) = split /:/, <$pipe_rd>;
162 3         48 close $pipe_rd;
163            
164 3         22 alarm 0;
165            
166 3         46 ($host, $port + 0); # Easier than chomp
167             };
168            
169 3 50       20 if ( my $err = $@ ) {
170             # If timed out, try to clean up the kid anyway
171 0         0 eval { kill 2, $pid };
  0         0  
172            
173 0 0       0 croak $err eq "alarm\n" ? "Timed out waiting for " .
174             "the server instance to start " .
175             "after $timeout seconds"
176             : $err
177             ;
178             }
179            
180 3         37 set_server_pid($pid);
181 3         10 set_server_host($host);
182 3         7 set_server_port($port);
183              
184 3 50       85 return wantarray ? ($host, $port)
185             : "$host:$port"
186             ;
187             }
188             elsif ( defined $pid && $pid == 0 ) {
189 0         0 close $pipe_rd;
190              
191 0         0 srand;
192            
193 0 0       0 sleep $sleep if $sleep;
194            
195             do_start_server(
196             %arg,
197            
198             after_listener => sub {
199 0     0   0 my $self = shift;
200            
201 0         0 my $host = inet_ntoa inet_aton $self->host;
202 0         0 my $port = $self->port;
203              
204 0         0 print $pipe_wr "$host:$port\n";
205 0         0 close $pipe_wr;
206            
207             my $after_setup_listener
208 0         0 = $self->{_old_after_setup_listener};
209            
210 0 0       0 $after_setup_listener->($self, @_)
211             if $after_setup_listener;
212             }
213 0         0 );
214              
215             # Should be unreachable, just in case
216 0         0 exit 0;
217             }
218             else {
219 0         0 croak "Can't fork: $!";
220             };
221              
222 0         0 return;
223             }
224              
225             ### EXPORTED PUBLIC PACKAGE SUBROUTINE ###
226             #
227             # Stop previously started server instance
228             #
229              
230             sub stop_server {
231 5     5 1 9 my ($pid) = @_;
232              
233 5 50       24 $pid = get_server_pid unless defined $pid;
234              
235 5 100       83 kill 2, $pid if defined $pid;
236              
237 5         16 set_server_port(undef);
238 5         13 set_server_pid(undef);
239             }
240              
241             ############## PRIVATE METHODS BELOW ##############
242              
243             ### PRIVATE PACKAGE SUBROUTINE ###
244             #
245             # Try to start the server, re-rolling port randomizer
246             # if the old port is taken
247             #
248              
249             sub do_start_server {
250 0     0 0   my (%arg) = @_;
251            
252 0           my $forced_port = defined $arg{port};
253 0           my $after_listener = delete $arg{after_listener};
254             my $server_class = delete $arg{server_class} ||
255 0   0       'RPC::ExtDirect::Server';
256              
257 0 0         if ( !$forced_port ) {
258 0           $arg{port} = random_port();
259             }
260              
261 0           my $server = $server_class->new(%arg);
262            
263             # TODO This is a dirty hack - find a better way of
264             # injecting after_setup_listener. Maybe send a patch
265             # to HTTP::Server::Simple maintainer to make this easier?
266 0 0         if ( $after_listener ) {
267             $server->{_old_after_setup_listener}
268 0           = $server_class->can('after_setup_listener');
269              
270 5     5   24 no strict 'refs';
  5         4  
  5         668  
271 0           *{$server_class.'::after_setup_listener'} = $after_listener;
  0            
272             }
273              
274             # If the port is taken, reroll the random generator and try again
275 0           do {
276 0           eval { $server->run() };
  0            
277              
278             # If the port was forced by the caller, punt
279 0 0 0       croak "$@\n" if $forced_port && $@;
280              
281 0           $server->port( random_port() );
282             }
283             while ( $@ );
284            
285 0           return 1; # This should be unreachable
286             }
287              
288             ### PRIVATE PACKAGE SUBROUTINE ###
289             #
290             # Generate a random port for the server to listen on
291             #
292              
293 0     0 0   sub random_port { 30000 + int rand 10000 };
294              
295             # Ensure that the server is stopped cleanly at exit
296 5 50   5   169739 END { stop_server unless get_no_shutdown }
297              
298             1;