initial import of FCGI-ProcManager 0.10 from CPAN
[catagits/FCGI-ProcManager.git] / ProcManager.pm
1 package FCGI::ProcManager;
2
3 # Copyright (c) 2000, FundsXpress Financial Network, Inc.
4 # This library is free software released under the GNU Lesser General
5 # Public License, Version 2.1.  Please read the important licensing and
6 # disclaimer information included below.
7
8 # $Id: ProcManager.pm,v 1.5 2000/11/18 07:06:09 muaddib Exp $
9
10 use strict;
11 use vars qw(@valid_states);
12 BEGIN {
13   $FCGI::ProcManager::VERSION = '0.10';
14   @valid_states = qw(managing handling idle);
15 }
16
17 =head1 NAME
18
19  FCGI::ProcManager - functions for managing FastCGI applications.
20
21 =head1 SYNOPSIS
22
23  # In its simplest form.
24  use CGI::Fast;
25  use FCGI::ProcManager;
26  my $proc_manager = FCGI::ProcManager->new({n_processes=>10});
27  $proc_manager->manage();
28  while (my $cgi = CGI::Fast->new()) {
29    #...
30
31 =head1 DESCRIPTION
32
33 FCGI::ProcManager is used to serve as a FastCGI process manager.
34 The parent uses fork(2) and wait(2) to manage a set of FastCGI application
35 servers.  more later.
36
37 =head1 METHODS
38
39 =head2 new
40
41 =cut
42
43 sub new {
44   my ($proto,$init) = @_;
45
46   my $this = {};
47   bless $this, ref($proto)||$proto;
48
49   $init and %$this = %$init;
50   defined $this->n_processes() or
51         $this->n_processes($ENV{PROCMANAGER_PROCESSES});
52
53   $this->{PIDS} = {};
54
55   return $this;
56 }
57
58 =head2 manage
59
60  global
61  () manage(int processes_to_spawn)
62
63 DESCRIPTION:
64
65 When this is called by a FastCGI script to manage application servers.
66
67 =cut
68
69 sub manage {
70   my ($this) = @_;
71
72   # initialize state and begin to handle signals.
73   $this->register_sig_handler();
74
75   # return right away if we are not managing any processes.
76   $this->n_processes() or return 1;
77
78   # call the (possibly overloaded) pre-manage initialization.
79   $this->state("managing");
80   $this->pre_manage_init();
81
82   # write out the pid file.
83   $this->write_pid_file();
84
85   my ($pid);
86   MANAGE: while (1) {
87
88     # do things that we only do when we're not already managing processes..
89     if (! %{$this->{PIDS}}) {
90       if (!$this->n_processes()) {
91         $this->remove_pid_file();
92         last;
93       } elsif ($this->want_to_die()) {
94         $this->remove_pid_file();
95         $this->exit("Manager $$ dying from death request.\n");
96       } elsif ($this->n_processes() < 0) {
97         $this->remove_pid_file();
98         $this->abort("Manager $$ dying from number of processes exception: ".
99                      $this->n_processes(), -( 1 + $this->n_processes()));
100       }
101     }
102
103     # if we have fewer children than we want..
104     PIDS: while (keys(%{$this->{PIDS}}) < $this->n_processes()) {
105
106       # fork.
107       if ($pid = fork()) {
108         # the parent notes the child.
109         $this->warn("started process $pid\n");
110         $this->{PIDS}->{$pid} = { pid=>$pid };
111
112       } elsif (! defined $pid) {
113         # handle errors um  gracefully.
114         $this->abort("fork: $!\n");
115
116       } else {
117         # the child returns to the calling application.
118         print "$$ lasting..\n";
119         last MANAGE;
120       }
121     }
122
123     # wait on the next child to die.
124     $this->abort("wait: $!\n") if ($pid = wait()) < 0;
125     $this->warn("Child process $pid died with exit status $?\n");
126     delete $this->{PIDS}->{$pid}
127         or $this->abort("Internal error: ".
128                         "wait() returned non-existent pid=$pid??\n");
129
130   }# while 1
131
132   # call the (possibly overloaded) post-manage initialization.
133   $this->post_manage_init();
134
135   $this->state("idle");
136
137   print "$$ returning..\n";
138   # children and parent with n_processes == 0 return to calling app.
139   return 1;
140 }
141
142 =head2 pre_manage_init
143
144 =cut
145
146 sub pre_manage_init {
147   my ($this) = @_;
148 }
149
150 =head2 post_manage_init
151
152 =cut
153
154 sub post_manage_init {
155   my ($this) = @_;
156 }
157
158 =head2 pre_dispatch
159
160 =cut
161
162 sub pre_dispatch {
163   my ($this) = @_;
164   $this->state("handling");
165 }
166
167 =head2 post_dispatch
168
169 =cut
170
171 sub post_dispatch {
172   my ($this) = @_;
173   $this->want_to_die() and 
174     $this->exit("Process $$ responding to death request.");
175   $this->state("idle");
176 }
177
178 =head2 write_pid_file
179
180 =cut
181
182 sub write_pid_file {
183   my ($this,$fname) = @_;
184   $fname ||= $this->pid_fname() or return;
185   if (!open PIDFILE, ">$fname") {
186     $this->warn("open: $fname: $!\n");
187     return;
188   }
189   print PIDFILE "$$\n";
190   close PIDFILE;
191 }
192
193 =head2 remove_pid_file
194
195 =cut
196
197 sub remove_pid_file {
198   my ($this,$fname) = @_;
199   $fname ||= $this->pid_fname() or return;
200   my $ret = unlink($fname) or $this->warn("unlink: $fname: $!\n");
201   return $ret;
202 }
203
204 =head2 gen_mutator
205
206 =cut
207
208 sub gen_mutator {
209   my ($this,$key,$value) = @_;
210   defined $value and $this->{$key} = $value;
211   return $this->{$key};
212 }
213
214 =head2 n_processes
215
216 =head2 want_to_die
217
218 =head2 no_signals
219
220 =head2 pid_fname
221
222 =cut
223
224 sub n_processes { shift->gen_mutator("n_processes",@_); }
225 sub want_to_die { shift->gen_mutator("want_to_die",@_); }
226 sub no_signals  { shift->gen_mutator("no_signals",@_);  }
227 sub pid_fname   { shift->gen_mutator("pid_fname",@_);   }
228
229 =head2 state
230
231 =cut
232
233 sub state {
234   my ($this,$new_state) = @_;
235   if (defined $new_state) {
236     if (!grep {$new_state eq $_} @valid_states) {
237       $this->abort("Invalid state: $new_state\n");
238     }
239     $this->{state} = $new_state;
240   }
241   defined $this->{state} or $this->{state} = "idle";
242   return $this->{state};
243 }
244
245 =head2 register_sig_handler
246
247 =cut
248
249 sub register_sig_handler {
250   my ($this) = @_;
251   return if $this->no_signals();
252   $SIG{TERM} = sub { $this->sig_method(@_) };
253   $SIG{HUP} = sub { $this->sig_method(@_) };
254 }
255
256 =head2 unregister_sig_handler
257
258 =cut
259
260 sub unregister_sig_handler {
261   my ($this) = @_;
262   return if $this->no_signals();
263   undef $SIG{TERM};
264   undef $SIG{HUP};
265 }
266
267 =head2 sig_method
268
269 =cut
270
271 sub sig_method {
272   my ($this,$name) = @_;
273   if ($name eq "TERM") {
274     if ($this->state() eq "idle") {
275       $this->exit("Process $$ dying after receiving SIG$name.\n");
276     } else {
277       $this->warn("Process $$ received SIG$name.  Cleaning up.\n");
278       $this->want_to_die(1);
279       $this->n_processes(-1);
280       # is the following necessary?
281       kill $name, keys %{$this->{PIDS}};
282     }
283   } else {
284     $this->warn("I don't know what to do with $name yet.. ignoring?\n");
285   }
286 }
287
288 =head2 warn
289
290 =cut
291
292 sub warn {
293   my ($this,$msg) = @_;
294   print STDERR $msg;
295 }
296
297 =head2 exit
298
299 =cut
300
301 sub exit {
302   my ($this,$msg,$n) = @_;
303   $n ||= 0;
304   $this->warn($msg);
305   $@ = $msg;
306   exit $n;
307 }
308
309 =head2 abort
310
311 =cut
312
313 sub abort {
314   my ($this,$msg,$n) = @_;
315   $n ||= 1;
316   $this->exit($msg,1);
317 }
318
319 1;
320 __END__
321
322 =head1 BUGS
323
324 No known bugs, but this does not mean no bugs exist.
325
326 =head1 SEE ALSO
327
328 L<FCGI>.
329
330 =head1 COPYRIGHT
331
332  FCGI-ProcManager - A Perl FCGI Process Manager
333  Copyright (c) 2000, FundsXpress Financial Network, Inc.
334
335  This library is free software; you can redistribute it and/or
336  modify it under the terms of the GNU Lesser General Public
337  License as published by the Free Software Foundation; either
338  version 2 of the License, or (at your option) any later version.
339
340  BECAUSE THIS LIBRARY IS LICENSED FREE OF CHARGE, THIS LIBRARY IS
341  BEING PROVIDED "AS IS WITH ALL FAULTS," WITHOUT ANY WARRANTIES
342  OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT
343  LIMITATION, ANY IMPLIED WARRANTIES OF TITLE, NONINFRINGEMENT,
344  MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, AND THE
345  ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY,
346  AND EFFORT IS WITH THE YOU.  See the GNU Lesser General Public
347  License for more details.
348
349  You should have received a copy of the GNU Lesser General Public
350  License along with this library; if not, write to the Free Software
351  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
352
353 =cut