PageRenderTime 65ms CodeModel.GetById 37ms RepoModel.GetById 0ms app.codeStats 0ms

/code/sloop_server.pm

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