1 | package Mojo::Server::Prefork;
|
---|
2 | use Mojo::Base 'Mojo::Server::Daemon';
|
---|
3 |
|
---|
4 | use Config;
|
---|
5 | use File::Spec::Functions 'tmpdir';
|
---|
6 | use Mojo::File 'path';
|
---|
7 | use Mojo::Util 'steady_time';
|
---|
8 | use POSIX 'WNOHANG';
|
---|
9 | use Scalar::Util 'weaken';
|
---|
10 |
|
---|
11 | has accepts => 10000;
|
---|
12 | has cleanup => 1;
|
---|
13 | has graceful_timeout => 120;
|
---|
14 | has heartbeat_timeout => 30;
|
---|
15 | has heartbeat_interval => 5;
|
---|
16 | has pid_file => sub { path(tmpdir, 'prefork.pid')->to_string };
|
---|
17 | has spare => 2;
|
---|
18 | has workers => 4;
|
---|
19 |
|
---|
20 | sub DESTROY { unlink $_[0]->pid_file if $_[0]->cleanup }
|
---|
21 |
|
---|
22 | sub 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 |
|
---|
36 | sub 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 |
|
---|
51 | sub healthy {
|
---|
52 | scalar grep { $_->{healthy} } values %{shift->{pool}};
|
---|
53 | }
|
---|
54 |
|
---|
55 | sub 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 |
|
---|
89 | sub _heartbeat { shift->{writer}->syswrite("$$:$_[0]\n") or exit 0 }
|
---|
90 |
|
---|
91 | sub _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 |
|
---|
138 | sub _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 |
|
---|
167 | sub _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 |
|
---|
178 | sub _term {
|
---|
179 | my ($self, $graceful) = @_;
|
---|
180 | @{$self->emit(finish => $graceful)}{qw(finished graceful)} = (1, $graceful);
|
---|
181 | }
|
---|
182 |
|
---|
183 | sub _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 |
|
---|
200 | 1;
|
---|
201 |
|
---|
202 | =encoding utf8
|
---|
203 |
|
---|
204 | =head1 NAME
|
---|
205 |
|
---|
206 | Mojo::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 |
|
---|
232 | L<Mojo::Server::Prefork> is a full featured, UNIX optimized, pre-forking
|
---|
233 | non-blocking I/O HTTP and WebSocket server, built around the very well tested
|
---|
234 | and reliable L<Mojo::Server::Daemon>, with IPv6, TLS, SNI, UNIX domain socket,
|
---|
235 | Comet (long polling), keep-alive and multiple event loop support. Note that the
|
---|
236 | server uses signals for process management, so you should avoid modifying signal
|
---|
237 | handlers in your applications.
|
---|
238 |
|
---|
239 | For better scalability (epoll, kqueue) and to provide non-blocking name
|
---|
240 | resolution, SOCKS5 as well as TLS support, the optional modules L<EV> (4.0+),
|
---|
241 | L<Net::DNS::Native> (0.15+), L<IO::Socket::Socks> (0.64+) and
|
---|
242 | L<IO::Socket::SSL> (1.84+) will be used automatically if possible. Individual
|
---|
243 | features can also be disabled with the C<MOJO_NO_NNR>, C<MOJO_NO_SOCKS> and
|
---|
244 | C<MOJO_NO_TLS> environment variables.
|
---|
245 |
|
---|
246 | See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
|
---|
247 |
|
---|
248 | =head1 MANAGER SIGNALS
|
---|
249 |
|
---|
250 | The L<Mojo::Server::Prefork> manager process can be controlled at runtime with
|
---|
251 | the following signals.
|
---|
252 |
|
---|
253 | =head2 INT, TERM
|
---|
254 |
|
---|
255 | Shut down server immediately.
|
---|
256 |
|
---|
257 | =head2 QUIT
|
---|
258 |
|
---|
259 | Shut down server gracefully.
|
---|
260 |
|
---|
261 | =head2 TTIN
|
---|
262 |
|
---|
263 | Increase worker pool by one.
|
---|
264 |
|
---|
265 | =head2 TTOU
|
---|
266 |
|
---|
267 | Decrease worker pool by one.
|
---|
268 |
|
---|
269 | =head1 WORKER SIGNALS
|
---|
270 |
|
---|
271 | L<Mojo::Server::Prefork> worker processes can be controlled at runtime with the
|
---|
272 | following signals.
|
---|
273 |
|
---|
274 | =head2 QUIT
|
---|
275 |
|
---|
276 | Stop worker gracefully.
|
---|
277 |
|
---|
278 | =head1 EVENTS
|
---|
279 |
|
---|
280 | L<Mojo::Server::Prefork> inherits all events from L<Mojo::Server::Daemon> and
|
---|
281 | can emit the following new ones.
|
---|
282 |
|
---|
283 | =head2 finish
|
---|
284 |
|
---|
285 | $prefork->on(finish => sub {
|
---|
286 | my ($prefork, $graceful) = @_;
|
---|
287 | ...
|
---|
288 | });
|
---|
289 |
|
---|
290 | Emitted 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 |
|
---|
304 | Emitted 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 |
|
---|
318 | Emitted 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 |
|
---|
332 | Emitted 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 |
|
---|
346 | Emitted 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 |
|
---|
356 | L<Mojo::Server::Prefork> inherits all attributes from L<Mojo::Server::Daemon>
|
---|
357 | and implements the following new ones.
|
---|
358 |
|
---|
359 | =head2 accepts
|
---|
360 |
|
---|
361 | my $accepts = $prefork->accepts;
|
---|
362 | $prefork = $prefork->accepts(100);
|
---|
363 |
|
---|
364 | Maximum number of connections a worker is allowed to accept, before stopping
|
---|
365 | gracefully and then getting replaced with a newly started worker, passed along
|
---|
366 | to L<Mojo::IOLoop/"max_accepts">, defaults to C<10000>. Setting the value to
|
---|
367 | C<0> will allow workers to accept new connections indefinitely. Note that up to
|
---|
368 | half of this value can be subtracted randomly to improve load balancing, and to
|
---|
369 | make 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 |
|
---|
376 | Delete L</"pid_file"> automatically once it is not needed anymore, defaults to
|
---|
377 | a true value.
|
---|
378 |
|
---|
379 | =head2 graceful_timeout
|
---|
380 |
|
---|
381 | my $timeout = $prefork->graceful_timeout;
|
---|
382 | $prefork = $prefork->graceful_timeout(15);
|
---|
383 |
|
---|
384 | Maximum amount of time in seconds stopping a worker gracefully may take before
|
---|
385 | being forced, defaults to C<120>. Note that this value should usually be a
|
---|
386 | little larger than the maximum amount of time you expect any one request to
|
---|
387 | take.
|
---|
388 |
|
---|
389 | =head2 heartbeat_interval
|
---|
390 |
|
---|
391 | my $interval = $prefork->heartbeat_interval;
|
---|
392 | $prefork = $prefork->heartbeat_interval(3);
|
---|
393 |
|
---|
394 | Heartbeat 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 |
|
---|
401 | Maximum amount of time in seconds before a worker without a heartbeat will be
|
---|
402 | stopped gracefully, defaults to C<30>. Note that this value should usually be a
|
---|
403 | little larger than the maximum amount of time you expect any one operation to
|
---|
404 | block the event loop.
|
---|
405 |
|
---|
406 | =head2 pid_file
|
---|
407 |
|
---|
408 | my $file = $prefork->pid_file;
|
---|
409 | $prefork = $prefork->pid_file('/tmp/prefork.pid');
|
---|
410 |
|
---|
411 | Full path of process id file, defaults to C<prefork.pid> in a temporary
|
---|
412 | directory.
|
---|
413 |
|
---|
414 | =head2 spare
|
---|
415 |
|
---|
416 | my $spare = $prefork->spare;
|
---|
417 | $prefork = $prefork->spare(4);
|
---|
418 |
|
---|
419 | Temporarily spawn up to this number of additional workers if there is a need,
|
---|
420 | defaults to C<2>. This allows for new workers to be started while old ones are
|
---|
421 | still shutting down gracefully, drastically reducing the performance cost of
|
---|
422 | worker restarts.
|
---|
423 |
|
---|
424 | =head2 workers
|
---|
425 |
|
---|
426 | my $workers = $prefork->workers;
|
---|
427 | $prefork = $prefork->workers(10);
|
---|
428 |
|
---|
429 | Number of worker processes, defaults to C<4>. A good rule of thumb is two
|
---|
430 | worker processes per CPU core for applications that perform mostly non-blocking
|
---|
431 | operations, blocking operations often require more and benefit from decreasing
|
---|
432 | concurrency with L<Mojo::Server::Daemon/"clients"> (often as low as C<1>).
|
---|
433 |
|
---|
434 | =head1 METHODS
|
---|
435 |
|
---|
436 | L<Mojo::Server::Prefork> inherits all methods from L<Mojo::Server::Daemon> and
|
---|
437 | implements the following new ones.
|
---|
438 |
|
---|
439 | =head2 check_pid
|
---|
440 |
|
---|
441 | my $pid = $prefork->check_pid;
|
---|
442 |
|
---|
443 | Get process id for running server from L</"pid_file"> or delete it if server is
|
---|
444 | not 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 |
|
---|
452 | Ensure L</"pid_file"> exists.
|
---|
453 |
|
---|
454 | =head2 healthy
|
---|
455 |
|
---|
456 | my $healthy = $prefork->healthy;
|
---|
457 |
|
---|
458 | Number of currently active worker processes with a heartbeat.
|
---|
459 |
|
---|
460 | =head2 run
|
---|
461 |
|
---|
462 | $prefork->run;
|
---|
463 |
|
---|
464 | Run server and wait for L</"MANAGER SIGNALS">.
|
---|
465 |
|
---|
466 | =head1 SEE ALSO
|
---|
467 |
|
---|
468 | L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
|
---|
469 |
|
---|
470 | =cut
|
---|