summaryrefslogtreecommitdiffstats
path: root/web/server/h2o/libh2o/share/h2o/start_server
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xweb/server/h2o/libh2o/share/h2o/start_server230
1 files changed, 230 insertions, 0 deletions
diff --git a/web/server/h2o/libh2o/share/h2o/start_server b/web/server/h2o/libh2o/share/h2o/start_server
new file mode 100755
index 00000000..b1e38099
--- /dev/null
+++ b/web/server/h2o/libh2o/share/h2o/start_server
@@ -0,0 +1,230 @@
+#! /bin/sh
+exec ${H2O_PERL:-perl} -x $0 "$@"
+#! perl
+# This chunk of stuff was generated by App::FatPacker. To find the original
+# file's code, look for the end of this BEGIN block or the string 'FATPACK'
+BEGIN {
+my %fatpacked;
+
+$fatpacked{"Server/Starter.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'SERVER_STARTER';
+ package Server::Starter;use 5.008;use strict;use warnings;use Carp;use Fcntl;use IO::Handle;use IO::Socket::UNIX;use POSIX qw(:sys_wait_h);use Socket ();use Server::Starter::Guard;use Fcntl qw(:flock);use Exporter qw(import);our$VERSION='0.31';our@EXPORT_OK=qw(start_server restart_server stop_server server_ports);my@signals_received;sub start_server {my$opts={(@_==1 ? @$_[0]: @_),};$opts->{interval}=1 if not defined$opts->{interval};$opts->{signal_on_hup}||='TERM';$opts->{signal_on_term}||='TERM';$opts->{backlog}||=Socket::SOMAXCONN();for ($opts->{signal_on_hup},$opts->{signal_on_term}){tr/a-z/A-Z/;s/^SIG//i}my$ports=$opts->{port};my$paths=$opts->{path};croak "either of ``port'' or ``path'' option is mandatory\n" unless$ports || $paths;$ports=[$ports ]if!ref$ports && defined$ports;$paths=[$paths ]if!ref$paths && defined$paths;croak "mandatory option ``exec'' is missing or is not an arrayref\n" unless$opts->{exec}&& ref$opts->{exec}eq 'ARRAY';$ENV{ENVDIR}=$opts->{envdir}if defined$opts->{envdir};$ENV{ENABLE_AUTO_RESTART}=$opts->{enable_auto_restart}if defined$opts->{enable_auto_restart};$ENV{KILL_OLD_DELAY}=$opts->{kill_old_delay}if defined$opts->{kill_old_delay};$ENV{AUTO_RESTART_INTERVAL}=$opts->{auto_restart_interval}if defined$opts->{auto_restart_interval};my$logfh;if ($opts->{log_file}){if ($opts->{log_file}=~ /^\s*\|\s*/s){my$cmd=$';open$logfh,'|-',$cmd or die "failed to open pipe:$opts->{log_file}: $!"}else {open$logfh,'>>',$opts->{log_file}or die "failed to open log file:$opts->{log_file}: $!"}$logfh->autoflush(1)}my$status_file_created;my$status_file_guard=$opts->{status_file}&& Server::Starter::Guard->new(sub {if ($status_file_created){unlink$opts->{status_file}}},);print STDERR "start_server (pid:$$) starting now...\n";my@sock;my@sockenv;for my$hostport (@$ports){my ($domain,$sa);my$fd;my$sockopts=sub {};if ($hostport =~ /^\s*(\d+)(?:\s*=(\d+))?\s*$/){$hostport=$1;$fd=$2;$domain=Socket::PF_INET;$sa=pack_sockaddr_in $1,Socket::inet_aton("0.0.0.0")}elsif ($hostport =~ /^\s*(?:\[\s*|)([^\]]*)\s*(?:\]\s*|):\s*(\d+)(?:\s*=(\d+))?\s*$/){my ($host,$port)=($1,$2);$fd=$3;if ($host =~ /:/){local $@;eval {$hostport="[$host]:$port";my$addr=Socket::inet_pton(Socket::AF_INET6(),$host)or die "failed to resolve host:$host:$!";$sa=Socket::pack_sockaddr_in6($port,$addr);$domain=Socket::PF_INET6()};if ($@){die "No support for IPv6. Please update Perl (or Perl modules)"}$sockopts=sub {my$sock=shift;local $@;eval {setsockopt$sock,Socket::IPPROTO_IPV6(),Socket::IPV6_V6ONLY(),1}}}else {$domain=Socket::PF_INET;$hostport="$host:$port";my$addr=gethostbyname$host or die "failed to resolve host:$host:$!";$sa=Socket::pack_sockaddr_in($port,$addr)}}else {croak "invalid ``port'' value:$hostport\n"}socket my$sock,$domain,Socket::SOCK_STREAM(),0 or die "failed to create socket:$!";setsockopt$sock,Socket::SOL_SOCKET,Socket::SO_REUSEADDR(),pack("l",1);$sockopts->($sock);bind$sock,$sa or die "failed to bind to $hostport:$!";listen$sock,$opts->{backlog}or die "listen(2) failed:$!";fcntl($sock,F_SETFD,my$flags='')or die "fcntl(F_SETFD, 0) failed:$!";if (defined$fd){POSIX::dup2($sock->fileno,$fd)or die "dup2(2) failed(${fd}): $!";print STDERR "socket is duplicated to file descriptor ${fd}\n";close$sock;push@sockenv,"$hostport=$fd"}else {push@sockenv,"$hostport=" .$sock->fileno}push@sock,$sock}my$path_remove_guard=Server::Starter::Guard->new(sub {-S $_ and unlink $_ for @$paths},);for my$path (@$paths){if (-S $path){warn "removing existing socket file:$path";unlink$path or die "failed to remove existing socket file:$path:$!"}unlink$path;my$saved_umask=umask(0);my$sock=IO::Socket::UNIX->new(Listen=>$opts->{backlog},Local=>$path,)or die "failed to listen to file $path:$!";umask($saved_umask);fcntl($sock,F_SETFD,my$flags='')or die "fcntl(F_SETFD, 0) failed:$!";push@sockenv,"$path=" .$sock->fileno;push@sock,$sock}$ENV{SERVER_STARTER_PORT}=join ";",@sockenv;$ENV{SERVER_STARTER_GENERATION}=0;_set_sighandler($_,sub {push@signals_received,$_[0]})for (qw/INT TERM HUP ALRM/);$SIG{PIPE}='IGNORE';my ($current_worker,%old_workers,$last_restart_time);my$update_status=$opts->{status_file}? sub {my$tmpfn="$opts->{status_file}.$$";open my$tmpfh,'>',$tmpfn or die "failed to create temporary file:$tmpfn:$!";$status_file_created=1;my%gen_pid=(($current_worker ? ($ENV{SERVER_STARTER_GENERATION}=>$current_worker): ()),map {$old_workers{$_}=>$_}keys%old_workers,);print$tmpfh "$_:$gen_pid{$_}\n" for sort keys%gen_pid;close$tmpfh;rename$tmpfn,$opts->{status_file}or die "failed to rename $tmpfn to $opts->{status_file}:$!"}: sub {};if ($logfh){STDOUT->flush;STDERR->flush;open STDOUT,'>&=',$logfh or die "failed to dup STDOUT to file: $!";open STDERR,'>&=',$logfh or die "failed to dup STDERR to file: $!";close$logfh;undef$logfh}if ($opts->{daemonize}){my$pid=fork;die "fork failed:$!" unless defined$pid;if ($pid!=0){$path_remove_guard->dismiss;exit 0}POSIX::setsid();$pid=fork;die "fork failed:$!" unless defined$pid;if ($pid!=0){$path_remove_guard->dismiss;exit 0}unless (grep /=0$/,@sockenv){close STDIN;open STDIN,'<','/dev/null' or die "reopen failed: $!"}}my$pid_file_guard=sub {return unless$opts->{pid_file};open my$fh,'>',$opts->{pid_file}or die "failed to open file:$opts->{pid_file}: $!";flock($fh,LOCK_EX)or die "flock failed($opts->{pid_file}): $!";print$fh "$$\n";$fh->flush();return Server::Starter::Guard->new(sub {unlink$opts->{pid_file}or warn "failed to unlink file:$opts->{pid_file}:$!";close$fh},)}->();my$start_worker=sub {my$pid;while (1){$ENV{SERVER_STARTER_GENERATION}++;$pid=fork;die "fork(2) failed:$!" unless defined$pid;if ($pid==0){my@args=@{$opts->{exec}};if (defined$opts->{dir}){chdir$opts->{dir}or die "failed to chdir:$opts->{dir}:$!"}{exec {$args[0]}@args};print STDERR "failed to exec $args[0]$!";exit(255)}print STDERR "starting new worker $pid\n";sleep$opts->{interval};if ((grep {$_ ne 'HUP'}@signals_received)|| waitpid($pid,WNOHANG)<= 0){last}print STDERR "new worker $pid seems to have failed to start, exit status:$?\n"}$current_worker=$pid;$last_restart_time=time;$update_status->()};my$wait=sub {my$block=@signals_received==0;my@r;if ($block && $ENV{ENABLE_AUTO_RESTART}){alarm(1);@r=_wait3($block);alarm(0)}else {@r=_wait3($block)}return@r};my$cleanup=sub {my$sig=shift;my$term_signal=$sig eq 'TERM' ? $opts->{signal_on_term}: 'TERM';$old_workers{$current_worker}=$ENV{SERVER_STARTER_GENERATION};undef$current_worker;print STDERR "received $sig, sending $term_signal to all workers:",join(',',sort keys%old_workers),"\n";kill$term_signal,$_ for sort keys%old_workers;while (%old_workers){if (my@r=_wait3(1)){my ($died_worker,$status)=@r;print STDERR "worker $died_worker died, status:$status\n";delete$old_workers{$died_worker};$update_status->()}}print STDERR "exiting\n"};$start_worker->();while (1){my@r=$wait->();my%loaded_env=_reload_env();my@loaded_env_keys=keys%loaded_env;local@ENV{@loaded_env_keys}=map {$loaded_env{$_}}(@loaded_env_keys);$ENV{AUTO_RESTART_INTERVAL}||=360 if$ENV{ENABLE_AUTO_RESTART};if (@r){my ($died_worker,$status)=@r;if ($died_worker==$current_worker){print STDERR "worker $died_worker died unexpectedly with status:$status, restarting\n";$start_worker->()}else {print STDERR "old worker $died_worker died, status:$status\n";delete$old_workers{$died_worker};$update_status->()}}my$restart;while (@signals_received){my$sig=shift@signals_received;if ($sig eq 'HUP'){print STDERR "received HUP, spawning a new worker\n";$restart=1;last}elsif ($sig eq 'ALRM'){}else {return$cleanup->($sig)}}if (!$restart && $ENV{ENABLE_AUTO_RESTART}){my$auto_restart_interval=$ENV{AUTO_RESTART_INTERVAL};my$elapsed_since_restart=time - $last_restart_time;if ($elapsed_since_restart >= $auto_restart_interval &&!%old_workers){print STDERR "autorestart triggered (interval=$auto_restart_interval)\n";$restart=1}elsif ($elapsed_since_restart >= $auto_restart_interval * 2){print STDERR "autorestart triggered (forced, interval=$auto_restart_interval)\n";$restart=1}}if ($restart){$old_workers{$current_worker}=$ENV{SERVER_STARTER_GENERATION};$start_worker->();print STDERR "new worker is now running, sending $opts->{signal_on_hup} to old workers:";if (%old_workers){print STDERR join(',',sort keys%old_workers),"\n"}else {print STDERR "none\n"}my$kill_old_delay=defined$ENV{KILL_OLD_DELAY}? $ENV{KILL_OLD_DELAY}: $ENV{ENABLE_AUTO_RESTART}? 5 : 0;if ($kill_old_delay!=0){print STDERR "sleeping $kill_old_delay secs before killing old workers\n";sleep$kill_old_delay}print STDERR "killing old workers\n";kill$opts->{signal_on_hup},$_ for sort keys%old_workers}}die "unreachable"}sub restart_server {my$opts={(@_==1 ? @$_[0]: @_),};die "--restart option requires --pid-file and --status-file to be set as well\n" unless$opts->{pid_file}&& $opts->{status_file};my$pid=do {open my$fh,'<',$opts->{pid_file}or die "failed to open file:$opts->{pid_file}:$!";my$line=<$fh>;chomp$line;$line};my$get_generations=sub {open my$fh,'<',$opts->{status_file}or die "failed to open file:$opts->{status_file}:$!";my%gen;while (my$line=<$fh>){if ($line =~ /^(\d+):/){$gen{$1}=1}}sort {$a <=> $b}keys%gen};my$wait_for=do {my@gens=$get_generations->()or die "no active process found in the status file";pop(@gens)+ 1};kill 'HUP',$pid or die "failed to send SIGHUP to the server process:$!";while (1){my@gens=$get_generations->();last if scalar(@gens)==1 && $gens[0]==$wait_for;sleep 1}}sub stop_server {my$opts={(@_==1 ? @$_[0]: @_),};die "--stop option requires --pid-file to be set as well\n" unless$opts->{pid_file};open my$fh,'<',$opts->{pid_file}or die "failed to open file:$opts->{pid_file}:$!";my$pid=do {my$line=<$fh>;chomp$line;$line};print STDERR "stop_server (pid:$$) stopping now (pid:$pid)...\n";kill 'TERM',$pid or die "failed to send SIGTERM to the server process:$!";flock($fh,LOCK_EX)or die "flock failed($opts->{pid_file}): $!";close$fh}sub server_ports {die "no environment variable SERVER_STARTER_PORT. Did you start the process using server_starter?",unless$ENV{SERVER_STARTER_PORT};my%ports=map {+(split /=/,$_,2)}split /;/,$ENV{SERVER_STARTER_PORT};\%ports}sub _reload_env {my$dn=$ENV{ENVDIR};return if!defined$dn or!-d $dn;my$d;opendir($d,$dn)or return;my%env;while (my$n=readdir($d)){next if$n =~ /^\./;open my$fh,'<',"$dn/$n" or next;chomp(my$v=<$fh>);$env{$n}=$v if defined$v}return%env}our$sighandler_should_die;my$sighandler_got_sig;sub _set_sighandler {my ($sig,$proc)=@_;$SIG{$sig}=sub {$proc->(@_);$sighandler_got_sig=1;die "got signal" if$sighandler_should_die}}sub _wait3 {my$block=shift;my$pid=-1;if ($block){local $@;eval {$sighandler_got_sig=0;local$sighandler_should_die=1;die "exit from eval" if$sighandler_got_sig;$pid=wait()};if ($pid==-1 && $@){$!=Errno::EINTR}}else {$pid=waitpid(-1,WNOHANG)}return$pid > 0 ? ($pid,$?): ()}1;
+SERVER_STARTER
+
+$fatpacked{"Server/Starter/Guard.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'SERVER_STARTER_GUARD';
+ package Server::Starter::Guard;use strict;use warnings;sub new {my ($klass,$handler)=@_;return bless {handler=>$handler,active=>1,},$klass}sub dismiss {shift->{active}=0}sub DESTROY {my$self=shift;$self->{active}&& $self->{handler}->()}1;
+SERVER_STARTER_GUARD
+
+s/^ //mg for values %fatpacked;
+
+my $class = 'FatPacked::'.(0+\%fatpacked);
+no strict 'refs';
+*{"${class}::files"} = sub { keys %{$_[0]} };
+
+if ($] < 5.008) {
+ *{"${class}::INC"} = sub {
+ if (my $fat = $_[0]{$_[1]}) {
+ my $pos = 0;
+ my $last = length $fat;
+ return (sub {
+ return 0 if $pos == $last;
+ my $next = (1 + index $fat, "\n", $pos) || $last;
+ $_ .= substr $fat, $pos, $next - $pos;
+ $pos = $next;
+ return 1;
+ });
+ }
+ };
+}
+
+else {
+ *{"${class}::INC"} = sub {
+ if (my $fat = $_[0]{$_[1]}) {
+ open my $fh, '<', \$fat
+ or die "FatPacker error loading $_[1] (could be a perl installation issue?)";
+ return $fh;
+ }
+ return;
+ };
+}
+
+unshift @INC, bless \%fatpacked, $class;
+ } # END OF FATPACK CODE
+
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+use Pod::Usage;
+use Server::Starter qw(start_server restart_server stop_server);
+
+my %opts = (
+ port => [],
+ path => [],
+);
+
+GetOptions(
+ map {
+ $_ => do {
+ my $name = (split '=', $_, 2)[0];
+ $name =~ s/-/_/g;
+ $opts{$name} ||= undef;
+ ref($opts{$name}) ? $opts{$name} : \$opts{$name};
+ },
+ } qw(port=s path=s interval=i log-file=s pid-file=s dir=s signal-on-hup=s signal-on-term=s
+ backlog=i envdir=s enable-auto-restart=i daemonize auto-restart-interval=i kill-old-delay=i
+ status-file=s restart stop help version),
+) or exit 1;
+pod2usage(
+ -exitval => 0,
+ -verbose => 1,
+) if $opts{help};
+if ($opts{version}) {
+ print "$Server::Starter::VERSION\n";
+ exit 0;
+}
+
+if ($opts{restart}) {
+ restart_server(%opts);
+ exit 0;
+}
+
+if ($opts{stop}) {
+ stop_server(%opts);
+ exit 0;
+}
+
+if ($opts{daemonize}) {
+ die "Usage: --log-file option must be specified together with --deamonize\n"
+ unless defined $opts{log_file};
+}
+
+# validate options
+die "server program not specified\n"
+ unless @ARGV;
+
+start_server(
+ %opts,
+ exec => \@ARGV,
+);
+
+__END__
+
+=head1 NAME
+
+start_server - a superdaemon for hot-deploying server programs
+
+=head1 SYNOPSIS
+
+ start_server [options] -- server-prog server-arg1 server-arg2 ...
+
+ # start Plack using Starlet listening at TCP port 8000
+ start_server --port=8000 -- plackup -s Starlet --max-workers=100 index.psgi
+
+=head1 DESCRIPTION
+
+This script is a frontend of L<Server::Starter>. For more information please refer to the documentation of the module.
+
+=head1 OPTIONS
+
+=head2 --port=(port|host:port|port=fd|host:port=fd)
+
+TCP port to listen to (if omitted, will not bind to any ports)
+
+If host is not specified, then the program will bind to the default address of IPv4 ("0.0.0.0").
+Square brackets should be used to specify an IPv6 address (e.g. --port=[::1]:8080)
+
+If fd is specified, then start_server allocates the socket at the given number.
+
+=head2 --path=path
+
+path at where to listen using unix socket (optional)
+
+=head2 --dir=path
+
+working directory, start_server do chdir to before exec (optional)
+
+=head2 --interval=seconds
+
+minimum interval to respawn the server program (default: 1)
+
+=head2 --signal-on-hup=SIGNAL
+
+name of the signal to be sent to the server process when start_server receives a SIGHUP (default: SIGTERM). If you use this option, be sure to also use C<--signal-on-term> below.
+
+=head2 --signal-on-term=SIGNAL
+
+name of the signal to be sent to the server process when start_server receives a SIGTERM (default: SIGTERM)
+
+=head2 --pid-file=filename
+
+if set, writes the process id of the start_server process to the file
+
+=head2 --status-file=filename
+
+if set, writes the status of the server process(es) to the file
+
+=head2 --envdir=ENVDIR
+
+directory that contains environment variables to the server processes.
+It is intended for use with C<envdir> in C<daemontools>.
+This can be overwritten by environment variable C<ENVDIR>.
+
+=head2 --log-file=file
+
+=head2 --log-file="| cmd args..."
+
+if set, redirects STDOUT and STDERR to given file or command
+
+=head2 --daemonize
+
+deamonizes the server (by doing fork,setsid,fork). Must be used together with C<--log-file>.
+
+=head2 --enable-auto-restart
+
+enables automatic restart by time.
+This can be overwritten by environment variable C<ENABLE_AUTO_RESTART>.
+
+=head2 --auto-restart-interval=seconds
+
+automatic restart interval (default 360). It is used with C<--enable-auto-restart> option.
+This can be overwritten by environment variable C<AUTO_RESTART_INTERVAL>.
+
+=head2 --kill-old-delay=seconds
+
+time to suspend to send a signal to the old worker. The default value is 5 when C<--enable-auto-restart> is set, 0 otherwise.
+This can be overwritten by environment variable C<KILL_OLD_DELAY>.
+
+=head2 --backlog=size
+
+specifies a listen backlog parameter, whose default is SOMAXCONN (usually 128 on Linux). While SOMAXCONN is enough for most loads, large backlog is required for heavy loads.
+
+=head2 --restart
+
+this is a wrapper command that reads the pid of the start_server process from --pid-file, sends SIGHUP to the process and waits until the server(s) of the older generation(s) die by monitoring the contents of the --status-file
+
+=head2 --stop
+
+this is a wrapper command that reads the pid of the start_server process from --pid-file, sends SIGTERM to the process.
+
+=head2 --help
+
+prints this help
+
+=head2 --version
+
+prints the version number
+
+=head1 AUTHOR
+
+Kazuho Oku
+
+=head1 SEE ALSO
+
+L<Server::Starter>
+
+=head1 LICENSE
+
+This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
+
+=cut