A menudo es necesario reconfigurar un servicio que está en ejecución y es conveniente hacerlo sin detenerlo. En UNIX existe un convenio que es utilizar la señal HUP para reiniciar un servidor. Esto es, según este convenio un servidor que recibe la señal de HUP vuelve a cargar y analizar sus ficheros de configuración y se reconfigura de acuerdo a los cambios.
Se elegió la señal de HUP
dado que esta no es normalmente recibida
por un daemon ya que este se ha disociado de la terminal de control.
Ahora instalamos manejadores para las señales de TERM , HUP e INT :
14 # signal handler for child die events 15 $SIG{TERM} = $SIG{INT} = \&do_term; 16 $SIG{HUP} = \&do_hup;
El puerto se lee desde línea de comandos. No se retira (no se usa shift
)
pues la subrutina do_relaunch
puede que necesite volver a accederla mas tarde.
18 my $port = $ARGV[0] || PORT;
La inicialización del socket y el bucle de espera y manejo de las conexiones
registra un cambio: en vez de llamar directamente a fork
se usa la función
launch_child
, definida en el módulo Daemon .
19 my $listen_socket = IO::Socket::INET->new(LocalPort => $port, 20 Listen => 20, 21 Proto => 'tcp', 22 Reuse => 1); 23 die "Can't create a listening socket: $@" unless $listen_socket; 24 my $pid = init_server(PIDFILE,USER,GROUP,$port); 25 26 log_notice "Server accepting connections on port $port\n"; 27 28 while (my $connection = $listen_socket->accept) { 29 my $host = $connection->peerhost; 30 my $child = launch_child(undef,ELIZA_HOME); 31 if ($child == 0) { 32 $listen_socket->close; 33 log_notice("Accepting a connection from $host\n"); 34 interact($connection); 35 log_notice("Connection from $host finished\n"); 36 exit 0; 37 } 38 $connection->close; 39 }
La subrutina launch_child
hace el fork, llama a chroot
y renuncia a los
privilegios de root (mediante la llamada a prepare_child
).
64 sub launch_child { 65 my $callback = shift; 66 my $home = shift; 67 my $signals = POSIX::SigSet->new(SIGINT,SIGCHLD,SIGTERM,SIGHUP); 68 sigprocmask(SIG_BLOCK,$signals); # block inconvenient signals 69 log_die("Can't fork: $!") unless defined (my $child = fork()); 70 if ($child) { 71 $CHILDREN{$child} = $callback || 1; 72 } else { 73 $SIG{HUP} = $SIG{INT} = $SIG{CHLD} = $SIG{TERM} = 'DEFAULT'; 74 prepare_child($home); 75 } 76 sigprocmask(SIG_UNBLOCK,$signals); # unblock signals 77 return $child; 78 }Además
launch_child
almacena en el hash %CHILDREN
un callback que será llamado cuando el hijo termine y que puede ser
definido por el programador.
Las claves de este hash serán utilizadas para eliminar a los hijos
cuando se reciba una señal de HUP
o de terminación.
97 sub kill_children { 98 kill TERM => keys %CHILDREN; 99 # wait until all the children die 100 sleep while %CHILDREN; 101 }
El manejador do:term
se encarga de al señal TERM
. Registra
un mensaje al sistema de log, llama a la subrutina kill_children
y finaliza el programa.
51 sub do_term { 52 log_notice("TERM signal received, terminating children...\n"); 53 kill_children(); 54 exit 0; 55 }
El manejador do_hup
se encarga de la señal de HUP
.
57 sub do_hup { 58 log_notice("HUP signal received, reinitializing...\n"); 59 log_notice("Closing listen socket...\n"); 60 close $listen_socket; 61 log_notice("Terminating children...\n"); 62 kill_children; 63 log_notice("Trying to relaunch...\n"); 64 do_relaunch(); 65 log_die("Relaunch failed. Died"); 66 }La subrutina
do_relaunch
intentará relanzar el programa
y si tiene éxito no retorna.
El programa principal contiene un bloque END
que emite un mensaje de log.
82 END { 83 log_notice("Server exiting normally\n") if (defined($pid) && $$ == $pid); 84 }
casiano@beowulf:~/src/perl/serverwithhup$ cat -n eliza_hup.pl 1 #!/usr/bin/perl -w -T 2 use strict; 3 use lib '.'; 4 use Chatbot::Eliza; 5 use IO::Socket; 6 use Daemon; 7 8 use constant PORT => 1002; 9 use constant PIDFILE => '/var/run/eliza_hup.pid'; 10 use constant USER => 'nobody'; 11 use constant GROUP => 'nogroup'; 12 use constant ELIZA_HOME => '/var/www/'; 13 14 # signal handler for child die events 15 $SIG{TERM} = $SIG{INT} = \&do_term; 16 $SIG{HUP} = \&do_hup; 17 18 my $port = $ARGV[0] || PORT; 19 my $listen_socket = IO::Socket::INET->new(LocalPort => $port, 20 Listen => 20, 21 Proto => 'tcp', 22 Reuse => 1); 23 die "Can't create a listening socket: $@" unless $listen_socket; 24 my $pid = init_server(PIDFILE,USER,GROUP,$port); 25 26 log_notice "Server accepting connections on port $port\n"; 27 28 while (my $connection = $listen_socket->accept) { 29 my $host = $connection->peerhost; 30 my $child = launch_child(undef,ELIZA_HOME); 31 if ($child == 0) { 32 $listen_socket->close; 33 log_notice("Accepting a connection from $host\n"); 34 interact($connection); 35 log_notice("Connection from $host finished\n"); 36 exit 0; 37 } 38 $connection->close; 39 } 40 41 sub interact { 42 my $sock = shift; 43 STDIN->fdopen($sock,"r") or die "Can't reopen STDIN: $!"; 44 STDOUT->fdopen($sock,"w") or die "Can't reopen STDOUT: $!"; 45 STDERR->fdopen($sock,"w") or die "Can't reopen STDERR: $!"; 46 $| = 1; 47 my $bot = Chatbot::Eliza->new; 48 $bot->command_interface; 49 } 50 51 sub do_term { 52 log_notice("TERM signal received, terminating children...\n"); 53 kill_children(); 54 exit 0; 55 } 56 57 sub do_hup { 58 log_notice("HUP signal received, reinitializing...\n"); 59 log_notice("Closing listen socket...\n"); 60 close $listen_socket; 61 log_notice("Terminating children...\n"); 62 kill_children; 63 log_notice("Trying to relaunch...\n"); 64 do_relaunch(); 65 log_die("Relaunch failed. Died"); 66 } 67 68 { 69 no warnings 'redefine'; 70 71 sub Chatbot::Eliza::_testquit { 72 my ($self,$string) = @_; 73 return 1 unless defined $string; # test for EOF 74 foreach (@{$self->{quit}}) { return 1 if $string =~ /\b$_\b/i }; 75 } 76 77 # prevents an annoying warning from Chatbot::Eliza module 78 sub Chatbot::Eliza::DESTROY { } 79 } 80 81 82 END { 83 log_notice("Server exiting normally\n") if (defined($pid) && $$ == $pid); 84 }