PageRenderTime 35ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/code/sloop_server.pm

https://github.com/billstclair/Loom
Perl | 261 lines | 150 code | 58 blank | 53 comment | 23 complexity | 6b7ac2c07bd3c9ac770c5809ac7f4f53 MD5 | raw file
  1. use strict;
  2. use file;
  3. use signal;
  4. use sloop_client;
  5. use sloop_config;
  6. use sloop_io;
  7. use sloop_status;
  8. use sloop_top;
  9. use POSIX "sys_wait_h"; # for the WNOHANG constant
  10. use Socket;
  11. # LATER 0331 do we get a catchable signal when shutting down this machine?
  12. # I noticed that the pid file wasn't getting cleared.
  13. =pod
  14. =head1 NAME
  15. sloop_listen - Listen for inbound connections and fork handler process
  16. The main server loop. Listen for incoming connections and handle them.
  17. Continue doing this until the process sees a TERM or INT interrupt signal.
  18. =head1 REFERENCES
  19. =item http://www.unix.org.ua/orelly/perl/prog/ch06_02.htm#PERL2-CH-6-SECT-2.4.1
  20. =item http://www.unix.com.ua/orelly/perl/cookbook/ch16_20.htm
  21. =item http://www.unix.org.ua/orelly/perl/cookbook/ch17_03.htm
  22. =cut
  23. sub sloop_listen
  24. {
  25. my $host_port = sloop_config("host_port");
  26. my $host_ip = sloop_config("host_ip");
  27. my $max_children = sloop_config("max_children");
  28. # Create the server listener socket.
  29. my $server_socket;
  30. my $proto = getprotobyname('tcp');
  31. socket($server_socket, PF_INET, SOCK_STREAM, $proto)
  32. or die "Could not create the listener socket: $!";
  33. # Set flags so we can restart our server quickly (reuse the bind addr).
  34. setsockopt($server_socket, SOL_SOCKET, SO_REUSEADDR, 1);
  35. # Build socket address and bind server to it.
  36. my $host_addr = $host_ip eq "*" ? INADDR_ANY : inet_aton($host_ip);
  37. my $server_addr = sockaddr_in($host_port,$host_addr);
  38. bind($server_socket, $server_addr)
  39. or die "Could not bind to port $host_port : $!\n";
  40. # Establish a queue for incoming connections.
  41. listen($server_socket, SOMAXCONN)
  42. or die "Could not listen on port $host_port : $!\n";
  43. # Accept and process connections.
  44. while (1)
  45. {
  46. if (signal_get_child())
  47. {
  48. # CHLD signal: At least one child process exited. Wait for all
  49. # children to exit to avoid zombies.
  50. while (1)
  51. {
  52. my $child = waitpid(-1, &WNOHANG);
  53. last if $child <= 0;
  54. if (WIFEXITED($?))
  55. {
  56. # The $child process actually exited.
  57. # my $exit_code = ($? >> 8); # if you're interested
  58. sloop_child_exits($child);
  59. }
  60. else
  61. {
  62. # The $child process temporarily stopped due to some other
  63. # condition (suspension, SIGSTOP). I've tested SIGSTOP
  64. # and SIGCONT and haven't made this condition happen, but
  65. # the Perl Cookbook says we should check WIFEXITED so
  66. # that's what I did.
  67. }
  68. }
  69. signal_put_child(0);
  70. }
  71. my $client_socket;
  72. my $remote_addr = accept($client_socket, $server_socket);
  73. last if signal_get_interrupt();
  74. # INT or TERM signal: Exit immediately.
  75. if (!$remote_addr)
  76. {
  77. print STDERR "error in accept : $!\n" if !signal_get_child();
  78. next;
  79. }
  80. # Received new inbound connection. Fork a handler process if we
  81. # have the capacity.
  82. if (sloop_num_children() >= $max_children)
  83. {
  84. close($client_socket);
  85. next;
  86. }
  87. my $child = fork;
  88. if (!defined $child)
  89. {
  90. # Too many processes.
  91. print STDERR "fork error: $!\n";
  92. close($client_socket);
  93. }
  94. elsif ($child != 0)
  95. {
  96. # This is the parent process.
  97. sloop_child_enters($child);
  98. close($client_socket);
  99. }
  100. else
  101. {
  102. # This is the child process.
  103. close($server_socket);
  104. sloop_io_init($client_socket);
  105. sloop_client_respond();
  106. close($client_socket);
  107. exit;
  108. }
  109. }
  110. close($server_socket);
  111. # Now send a TERM signal to all the children to terminate them.
  112. kill 'TERM', sloop_children();
  113. return;
  114. }
  115. # Stop the currently running server process, making sure it is actually gone
  116. # from the "ps" listing before returning. That's the only way to guarantee
  117. # that the listening socket is fully released, allowing us to restart the
  118. # server immediately without getting a "bind: Address already in use" error.
  119. # Return false if the process fails to exit after waiting 2 minutes.
  120. sub sloop_stop
  121. {
  122. my ($curr_pid) = sloop_info();
  123. return 1 if $curr_pid eq "";
  124. system("kill $curr_pid");
  125. my $beg_time = time;
  126. my $warn_time = $beg_time;
  127. while (1)
  128. {
  129. my $found_process = 0;
  130. my $fh;
  131. open($fh, "ps -p $curr_pid|") or die "error: $!";
  132. while (<$fh>)
  133. {
  134. chomp;
  135. my $line = $_;
  136. $line =~ s/^\s+//;
  137. $line =~ s/\s+$//;
  138. my ($pid,$dev,$time,$name) = split(/\s+/, $line);
  139. next if $pid ne $curr_pid;
  140. $found_process = 1;
  141. last;
  142. }
  143. close($fh);
  144. last if !$found_process; # process is gone
  145. my $time = time;
  146. my $elapse = $time - $beg_time;
  147. if ($elapse >= 120)
  148. {
  149. print STDERR "Gave up after $elapse seconds, sorry.\n";
  150. return 0;
  151. }
  152. if ($time - $warn_time >= 10)
  153. {
  154. $warn_time = $time;
  155. print STDERR "Still waiting for old process $curr_pid "
  156. ."to exit after $elapse seconds ...\n";
  157. }
  158. sleep(1);
  159. }
  160. return 1;
  161. }
  162. # Start the server running in the background and loop until terminated.
  163. sub sloop_start
  164. {
  165. my $child = fork;
  166. if (!defined $child)
  167. {
  168. # The fork failed, probably because too many processes are running.
  169. print STDERR "The server failed to start because of this error: $!\n";
  170. exit(1);
  171. }
  172. elsif ($child != 0)
  173. {
  174. # The parent process does nothing.
  175. exit(0);
  176. }
  177. # This is the child process, which runs in the background and implements
  178. # the server.
  179. sloop_status_start();
  180. sloop_listen();
  181. sloop_status_stop();
  182. return;
  183. }
  184. # Run the listener loop. The listener accepts inbound connections and forks a
  185. # child process to handle them.
  186. sub sloop_respond
  187. {
  188. my $do_start = shift;
  189. signal_init();
  190. sloop_status_init();
  191. return if !sloop_stop(); # Stop the server if already running.
  192. sloop_start() if $do_start;
  193. return;
  194. }
  195. return 1;