source: main/trunk/greenstone2/perllib/cpan/Mojo/Server/Prefork.pm@ 32205

Last change on this file since 32205 was 32205, checked in by ak19, 6 years ago

First set of commits to do with implementing the new 'paged_html' output option of PDFPlugin that uses using xpdftools' new pdftohtml. So far tested only on Linux (64 bit), but things work there so I'm optimistically committing the changes since they work. 2. Committing the pre-built Linux binaries of XPDFtools for both 32 and 64 bit built by the XPDF group. 2. To use the correct bitness variant of xpdftools, setup.bash now exports the BITNESS env var, consulted by gsConvert.pl. 3. All the perl code changes to do with using xpdf tools' pdftohtml to generate paged_html and feed it in the desired form into GS(3): gsConvert.pl, PDFPlugin.pm and its parent ConvertBinaryPFile.pm have been modified to make it all work. xpdftools' pdftohtml generates a folder containing an html file and a screenshot for each page in a PDF (as well as an index.html linking to each page's html). However, we want a single html file that contains each individual 'page' html's content in a div, and need to do some further HTML style, attribute and structure modifications to massage the xpdftool output to what we want for GS. In order to parse and manipulate the HTML 'DOM' to do this, we're using the Mojo::DOM package that Dr Bainbridge found and which he's compiled up. Mojo::DOM is therefore also committed in this revision. Some further changes and some display fixes are required, but need to check with the others about that.

File size: 12.2 KB
Line 
1package Mojo::Server::Prefork;
2use Mojo::Base 'Mojo::Server::Daemon';
3
4use Config;
5use File::Spec::Functions 'tmpdir';
6use Mojo::File 'path';
7use Mojo::Util 'steady_time';
8use POSIX 'WNOHANG';
9use Scalar::Util 'weaken';
10
11has accepts => 10000;
12has cleanup => 1;
13has graceful_timeout => 120;
14has heartbeat_timeout => 30;
15has heartbeat_interval => 5;
16has pid_file => sub { path(tmpdir, 'prefork.pid')->to_string };
17has spare => 2;
18has workers => 4;
19
20sub DESTROY { unlink $_[0]->pid_file if $_[0]->cleanup }
21
22sub check_pid {
23 my $file = shift->pid_file;
24 return undef unless open my $handle, '<', $file;
25 my $pid = <$handle>;
26 chomp $pid;
27
28 # Running
29 return $pid if $pid && kill 0, $pid;
30
31 # Not running
32 unlink $file;
33 return undef;
34}
35
36sub ensure_pid_file {
37 my ($self, $pid) = @_;
38
39 # Check if PID file already exists
40 return if -e (my $file = $self->pid_file);
41
42 # Create PID file
43 $self->app->log->error(qq{Can't create process id file "$file": $!})
44 and die qq{Can't create process id file "$file": $!}
45 unless open my $handle, '>', $file;
46 $self->app->log->info(qq{Creating process id file "$file"});
47 chmod 0644, $handle;
48 print $handle "$pid\n";
49}
50
51sub healthy {
52 scalar grep { $_->{healthy} } values %{shift->{pool}};
53}
54
55sub run {
56 my $self = shift;
57
58 # No fork emulation support
59 say 'Pre-forking does not support fork emulation.' and exit 0
60 if $Config{d_pseudofork};
61
62 # Pipe for worker communication
63 pipe($self->{reader}, $self->{writer}) or die "Can't create pipe: $!";
64
65 # Clean manager environment
66 local $SIG{CHLD} = sub {
67 while ((my $pid = waitpid -1, WNOHANG) > 0) {
68 $self->emit(reap => $pid)->_stopped($pid);
69 }
70 };
71 local $SIG{INT} = local $SIG{TERM} = sub { $self->_term };
72 local $SIG{QUIT} = sub { $self->_term(1) };
73 local $SIG{TTIN} = sub { $self->workers($self->workers + 1) };
74 local $SIG{TTOU} = sub {
75 $self->workers > 0 ? $self->workers($self->workers - 1) : return;
76 for my $w (values %{$self->{pool}}) {
77 ($w->{graceful} = steady_time) and last unless $w->{graceful};
78 }
79 };
80
81 # Preload application before starting workers
82 $self->start->app->log->info("Manager $$ started");
83 $self->ioloop->max_accepts($self->accepts);
84 $self->{running} = 1;
85 $self->_manage while $self->{running};
86 $self->app->log->info("Manager $$ stopped");
87}
88
89sub _heartbeat { shift->{writer}->syswrite("$$:$_[0]\n") or exit 0 }
90
91sub _manage {
92 my $self = shift;
93
94 # Spawn more workers if necessary and check PID file
95 if (!$self->{finished}) {
96 my $graceful = grep { $_->{graceful} } values %{$self->{pool}};
97 my $spare = $self->spare;
98 $spare = $graceful ? $graceful > $spare ? $spare : $graceful : 0;
99 my $need = ($self->workers - keys %{$self->{pool}}) + $spare;
100 $self->_spawn while $need-- > 0;
101 $self->ensure_pid_file($$);
102 }
103
104 # Shutdown
105 elsif (!keys %{$self->{pool}}) { return delete $self->{running} }
106
107 # Wait for heartbeats
108 $self->_wait;
109
110 my $interval = $self->heartbeat_interval;
111 my $ht = $self->heartbeat_timeout;
112 my $gt = $self->graceful_timeout;
113 my $log = $self->app->log;
114 my $time = steady_time;
115
116 for my $pid (keys %{$self->{pool}}) {
117 next unless my $w = $self->{pool}{$pid};
118
119 # No heartbeat (graceful stop)
120 $log->error("Worker $pid has no heartbeat ($ht seconds), restarting")
121 and $w->{graceful} = $time
122 if !$w->{graceful} && ($w->{time} + $interval + $ht <= $time);
123
124 # Graceful stop with timeout
125 my $graceful = $w->{graceful} ||= $self->{graceful} ? $time : undef;
126 $log->info("Stopping worker $pid gracefully ($gt seconds)")
127 and (kill 'QUIT', $pid or $self->_stopped($pid))
128 if $graceful && !$w->{quit}++;
129 $w->{force} = 1 if $graceful && $graceful + $gt <= $time;
130
131 # Normal stop
132 $log->warn("Stopping worker $pid immediately")
133 and (kill 'KILL', $pid or $self->_stopped($pid))
134 if $w->{force} || ($self->{finished} && !$graceful);
135 }
136}
137
138sub _spawn {
139 my $self = shift;
140
141 # Manager
142 die "Can't fork: $!" unless defined(my $pid = fork);
143 return $self->emit(spawn => $pid)->{pool}{$pid} = {time => steady_time}
144 if $pid;
145
146 # Heartbeat messages
147 my $loop = $self->cleanup(0)->ioloop;
148 my $finished = 0;
149 $loop->on(finish => sub { $finished = 1 });
150 weaken $self;
151 my $cb = sub { $self->_heartbeat($finished) };
152 $loop->next_tick($cb);
153 $loop->recurring($self->heartbeat_interval => $cb);
154
155 # Clean worker environment
156 $SIG{$_} = 'DEFAULT' for qw(CHLD INT TERM TTIN TTOU);
157 $SIG{QUIT} = sub { $loop->stop_gracefully };
158 $loop->on(finish => sub { $self->max_requests(1)->close_connections });
159 delete $self->{reader};
160 srand;
161
162 $self->app->log->info("Worker $$ started");
163 $loop->start;
164 exit 0;
165}
166
167sub _stopped {
168 my ($self, $pid) = @_;
169
170 return unless my $w = delete $self->{pool}{$pid};
171
172 my $log = $self->app->log;
173 $log->info("Worker $pid stopped");
174 $log->error("Worker $pid stopped too early, shutting down") and $self->_term
175 unless $w->{healthy};
176}
177
178sub _term {
179 my ($self, $graceful) = @_;
180 @{$self->emit(finish => $graceful)}{qw(finished graceful)} = (1, $graceful);
181}
182
183sub _wait {
184 my $self = shift;
185
186 # Poll for heartbeats
187 my $reader = $self->emit('wait')->{reader};
188 return unless Mojo::Util::_readable(1000, fileno($reader));
189 return unless $reader->sysread(my $chunk, 4194304);
190
191 # Update heartbeats (and stop gracefully if necessary)
192 my $time = steady_time;
193 while ($chunk =~ /(\d+):(\d)\n/g) {
194 next unless my $w = $self->{pool}{$1};
195 @$w{qw(healthy time)} = (1, $time) and $self->emit(heartbeat => $1);
196 $w->{graceful} ||= $time if $2;
197 }
198}
199
2001;
201
202=encoding utf8
203
204=head1 NAME
205
206Mojo::Server::Prefork - Pre-forking non-blocking I/O HTTP and WebSocket server
207
208=head1 SYNOPSIS
209
210 use Mojo::Server::Prefork;
211
212 my $prefork = Mojo::Server::Prefork->new(listen => ['http://*:8080']);
213 $prefork->unsubscribe('request')->on(request => sub {
214 my ($prefork, $tx) = @_;
215
216 # Request
217 my $method = $tx->req->method;
218 my $path = $tx->req->url->path;
219
220 # Response
221 $tx->res->code(200);
222 $tx->res->headers->content_type('text/plain');
223 $tx->res->body("$method request for $path!");
224
225 # Resume transaction
226 $tx->resume;
227 });
228 $prefork->run;
229
230=head1 DESCRIPTION
231
232L<Mojo::Server::Prefork> is a full featured, UNIX optimized, pre-forking
233non-blocking I/O HTTP and WebSocket server, built around the very well tested
234and reliable L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, UNIX domain socket,
235Comet (long polling), keep-alive and multiple event loop support. Note that the
236server uses signals for process management, so you should avoid modifying signal
237handlers in your applications.
238
239For better scalability (epoll, kqueue) and to provide non-blocking name
240resolution, SOCKS5 as well as TLS support, the optional modules L<EV> (4.0+),
241L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and
242L<IO::Socket::SSL> (1.84+) will be used automatically if possible. Individual
243features can also be disabled with the C<MOJO_NO_NNR>, C<MOJO_NO_SOCKS> and
244C<MOJO_NO_TLS> environment variables.
245
246See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
247
248=head1 MANAGER SIGNALS
249
250The L<Mojo::Server::Prefork> manager process can be controlled at runtime with
251the following signals.
252
253=head2 INT, TERM
254
255Shut down server immediately.
256
257=head2 QUIT
258
259Shut down server gracefully.
260
261=head2 TTIN
262
263Increase worker pool by one.
264
265=head2 TTOU
266
267Decrease worker pool by one.
268
269=head1 WORKER SIGNALS
270
271L<Mojo::Server::Prefork> worker processes can be controlled at runtime with the
272following signals.
273
274=head2 QUIT
275
276Stop worker gracefully.
277
278=head1 EVENTS
279
280L<Mojo::Server::Prefork> inherits all events from L<Mojo::Server::Daemon> and
281can emit the following new ones.
282
283=head2 finish
284
285 $prefork->on(finish => sub {
286 my ($prefork, $graceful) = @_;
287 ...
288 });
289
290Emitted when the server shuts down.
291
292 $prefork->on(finish => sub {
293 my ($prefork, $graceful) = @_;
294 say $graceful ? 'Graceful server shutdown' : 'Server shutdown';
295 });
296
297=head2 heartbeat
298
299 $prefork->on(heartbeat => sub {
300 my ($prefork, $pid) = @_;
301 ...
302 });
303
304Emitted when a heartbeat message has been received from a worker.
305
306 $prefork->on(heartbeat => sub {
307 my ($prefork, $pid) = @_;
308 say "Worker $pid has a heartbeat";
309 });
310
311=head2 reap
312
313 $prefork->on(reap => sub {
314 my ($prefork, $pid) = @_;
315 ...
316 });
317
318Emitted when a child process exited.
319
320 $prefork->on(reap => sub {
321 my ($prefork, $pid) = @_;
322 say "Worker $pid stopped";
323 });
324
325=head2 spawn
326
327 $prefork->on(spawn => sub {
328 my ($prefork, $pid) = @_;
329 ...
330 });
331
332Emitted when a worker process is spawned.
333
334 $prefork->on(spawn => sub {
335 my ($prefork, $pid) = @_;
336 say "Worker $pid started";
337 });
338
339=head2 wait
340
341 $prefork->on(wait => sub {
342 my $prefork = shift;
343 ...
344 });
345
346Emitted when the manager starts waiting for new heartbeat messages.
347
348 $prefork->on(wait => sub {
349 my $prefork = shift;
350 my $workers = $prefork->workers;
351 say "Waiting for heartbeat messages from $workers workers";
352 });
353
354=head1 ATTRIBUTES
355
356L<Mojo::Server::Prefork> inherits all attributes from L<Mojo::Server::Daemon>
357and implements the following new ones.
358
359=head2 accepts
360
361 my $accepts = $prefork->accepts;
362 $prefork = $prefork->accepts(100);
363
364Maximum number of connections a worker is allowed to accept, before stopping
365gracefully and then getting replaced with a newly started worker, passed along
366to L<Mojo::IOLoop/"max_accepts">, defaults to C<10000>. Setting the value to
367C<0> will allow workers to accept new connections indefinitely. Note that up to
368half of this value can be subtracted randomly to improve load balancing, and to
369make sure that not all workers restart at the same time.
370
371=head2 cleanup
372
373 my $bool = $prefork->cleanup;
374 $prefork = $prefork->cleanup($bool);
375
376Delete L</"pid_file"> automatically once it is not needed anymore, defaults to
377a true value.
378
379=head2 graceful_timeout
380
381 my $timeout = $prefork->graceful_timeout;
382 $prefork = $prefork->graceful_timeout(15);
383
384Maximum amount of time in seconds stopping a worker gracefully may take before
385being forced, defaults to C<120>. Note that this value should usually be a
386little larger than the maximum amount of time you expect any one request to
387take.
388
389=head2 heartbeat_interval
390
391 my $interval = $prefork->heartbeat_interval;
392 $prefork = $prefork->heartbeat_interval(3);
393
394Heartbeat interval in seconds, defaults to C<5>.
395
396=head2 heartbeat_timeout
397
398 my $timeout = $prefork->heartbeat_timeout;
399 $prefork = $prefork->heartbeat_timeout(2);
400
401Maximum amount of time in seconds before a worker without a heartbeat will be
402stopped gracefully, defaults to C<30>. Note that this value should usually be a
403little larger than the maximum amount of time you expect any one operation to
404block the event loop.
405
406=head2 pid_file
407
408 my $file = $prefork->pid_file;
409 $prefork = $prefork->pid_file('/tmp/prefork.pid');
410
411Full path of process id file, defaults to C<prefork.pid> in a temporary
412directory.
413
414=head2 spare
415
416 my $spare = $prefork->spare;
417 $prefork = $prefork->spare(4);
418
419Temporarily spawn up to this number of additional workers if there is a need,
420defaults to C<2>. This allows for new workers to be started while old ones are
421still shutting down gracefully, drastically reducing the performance cost of
422worker restarts.
423
424=head2 workers
425
426 my $workers = $prefork->workers;
427 $prefork = $prefork->workers(10);
428
429Number of worker processes, defaults to C<4>. A good rule of thumb is two
430worker processes per CPU core for applications that perform mostly non-blocking
431operations, blocking operations often require more and benefit from decreasing
432concurrency with L<Mojo::Server::Daemon/"clients"> (often as low as C<1>).
433
434=head1 METHODS
435
436L<Mojo::Server::Prefork> inherits all methods from L<Mojo::Server::Daemon> and
437implements the following new ones.
438
439=head2 check_pid
440
441 my $pid = $prefork->check_pid;
442
443Get process id for running server from L</"pid_file"> or delete it if server is
444not running.
445
446 say 'Server is not running' unless $prefork->check_pid;
447
448=head2 ensure_pid_file
449
450 $prefork->ensure_pid_file($pid);
451
452Ensure L</"pid_file"> exists.
453
454=head2 healthy
455
456 my $healthy = $prefork->healthy;
457
458Number of currently active worker processes with a heartbeat.
459
460=head2 run
461
462 $prefork->run;
463
464Run server and wait for L</"MANAGER SIGNALS">.
465
466=head1 SEE ALSO
467
468L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
469
470=cut
Note: See TracBrowser for help on using the repository browser.