Class | PhusionPassenger::AbstractServer |
In: |
lib/phusion_passenger/abstract_server.rb
|
Parent: | Object |
An abstract base class for a server, with the following properties:
A message is just an ordered list of strings. The first element in the message is the _message name_.
The server will also reset all signal handlers (in the child process). That is, it will respond to all signals in the default manner. The only exception is SIGHUP, which is ignored. One may define additional signal handlers using define_signal_handler().
Before an AbstractServer can be used, it must first be started by calling start(). When it is no longer needed, stop() should be called.
Here‘s an example on using AbstractServer:
class MyServer < PhusionPassenger::AbstractServer def initialize super() define_message_handler(:hello, :handle_hello) end def hello(first_name, last_name) send_to_server('hello', first_name, last_name) reply, pointless_number = recv_from_server puts "The server said: #{reply}" puts "In addition, it sent this pointless number: #{pointless_number}" end private def handle_hello(first_name, last_name) send_to_client("Hello #{first_name} #{last_name}, how are you?", 1234) end end server = MyServer.new server.start server.hello("Joe", "Dalton") server.stop
SERVER_TERMINATION_SIGNAL | = | "SIGTERM" |
last_activity_time | [RW] | The last time when this AbstractServer had processed a message. |
max_idle_time | [RW] | The maximum time that this AbstractServer may be idle. Used by AbstractServerCollection to determine when this object should be cleaned up. nil or 0 indicate that this object should never be idle cleaned. |
next_cleaning_time | [RW] | Used by AbstractServerCollection to remember when this AbstractServer should be idle cleaned. |
# File lib/phusion_passenger/abstract_server.rb, line 103 103: def initialize 104: @done = false 105: @message_handlers = {} 106: @signal_handlers = {} 107: @orig_signal_handlers = {} 108: @last_activity_time = Time.now 109: end
Start the server. This method does not block since the server runs asynchronously from the current process.
You may only call this method if the server is not already started. Otherwise, a ServerAlreadyStarted will be raised.
Derived classes may raise additional exceptions.
# File lib/phusion_passenger/abstract_server.rb, line 118 118: def start 119: if started? 120: raise ServerAlreadyStarted, "Server is already started" 121: end 122: 123: @parent_socket, @child_socket = UNIXSocket.pair 124: before_fork 125: @pid = fork 126: if @pid.nil? 127: begin 128: STDOUT.sync = true 129: STDERR.sync = true 130: @parent_socket.close 131: 132: # During Passenger's early days, we used to close file descriptors based 133: # on a white list of file descriptors. That proved to be way too fragile: 134: # too many file descriptors are being left open even though they shouldn't 135: # be. So now we close file descriptors based on a black list. 136: file_descriptors_to_leave_open = [0, 1, 2, @child_socket.fileno] 137: NativeSupport.close_all_file_descriptors(file_descriptors_to_leave_open) 138: # In addition to closing the file descriptors, one must also close 139: # the associated IO objects. This is to prevent IO.close from 140: # double-closing already closed file descriptors. 141: close_all_io_objects_for_fds(file_descriptors_to_leave_open) 142: 143: # At this point, RubyGems might have open file handles for which 144: # the associated file descriptors have just been closed. This can 145: # result in mysterious 'EBADFD' errors. So we force RubyGems to 146: # clear all open file handles. 147: Gem.clear_paths 148: 149: start_synchronously(@child_socket) 150: rescue Interrupt 151: # Do nothing. 152: rescue SignalException => signal 153: if signal.message == SERVER_TERMINATION_SIGNAL 154: # Do nothing. 155: else 156: print_exception(self.class.to_s, signal) 157: end 158: rescue Exception => e 159: print_exception(self.class.to_s, e) 160: ensure 161: exit! 162: end 163: end 164: @child_socket.close 165: @parent_channel = MessageChannel.new(@parent_socket) 166: end
Start the server, but in the current process instead of in a child process. This method blocks until the server‘s main loop has ended.
socket is the socket that the server should listen on. The server main loop will end if the socket has been closed.
All hooks will be called, except before_fork().
# File lib/phusion_passenger/abstract_server.rb, line 175 175: def start_synchronously(socket) 176: @child_socket = socket 177: @child_channel = MessageChannel.new(socket) 178: begin 179: reset_signal_handlers 180: initialize_server 181: begin 182: main_loop 183: ensure 184: finalize_server 185: end 186: ensure 187: revert_signal_handlers 188: end 189: end
Stop the server. The server will quit as soon as possible. This method waits until the server has been stopped.
When calling this method, the server must already be started. If not, a ServerNotStarted will be raised.
# File lib/phusion_passenger/abstract_server.rb, line 196 196: def stop 197: if !started? 198: raise ServerNotStarted, "Server is not started" 199: end 200: 201: @parent_socket.close 202: @parent_channel = nil 203: 204: # Wait at most 3 seconds for server to exit. If it doesn't do that, 205: # we kill it. If that doesn't work either, we kill it forcefully with 206: # SIGKILL. 207: begin 208: Timeout::timeout(3) do 209: Process.waitpid(@pid) rescue nil 210: end 211: rescue Timeout::Error 212: Process.kill(SERVER_TERMINATION_SIGNAL, @pid) rescue nil 213: begin 214: Timeout::timeout(3) do 215: Process.waitpid(@pid) rescue nil 216: end 217: rescue Timeout::Error 218: Process.kill('SIGKILL', @pid) rescue nil 219: Process.waitpid(@pid, Process::WNOHANG) rescue nil 220: end 221: end 222: end
Return the communication channel with the client (i.e. the parent process that started the server). This is a MessageChannel object.
# File lib/phusion_passenger/abstract_server.rb, line 282 282: def client 283: return @child_channel 284: end
Define a handler for a message. message_name is the name of the message to handle, and handler is the name of a method to be called (this may either be a String or a Symbol).
A message is just a list of strings, and so handler will be called with the message as its arguments, excluding the first element. See also the example in the class description.
# File lib/phusion_passenger/abstract_server.rb, line 257 257: def define_message_handler(message_name, handler) 258: @message_handlers[message_name.to_s] = handler 259: end
Define a handler for a signal.
# File lib/phusion_passenger/abstract_server.rb, line 262 262: def define_signal_handler(signal, handler) 263: @signal_handlers[signal.to_s] = handler 264: end
A hook which is called when the server is being stopped. This is called in the child process, after the main loop has been left. The default implementation does nothing, this method is supposed to be overrided by child classes.
# File lib/phusion_passenger/abstract_server.rb, line 249 249: def finalize_server 250: end
A hook which is called when the server is being started. This is called in the child process, before the main loop is entered. The default implementation does nothing, this method is supposed to be overrided by child classes.
# File lib/phusion_passenger/abstract_server.rb, line 243 243: def initialize_server 244: end
Return the communication channel with the server. This is a MessageChannel object.
Raises ServerNotStarted if the server hasn‘t been started yet.
This method may only be called in the parent process, and not in the started server process.
# File lib/phusion_passenger/abstract_server.rb, line 273 273: def server 274: if !started? 275: raise ServerNotStarted, "Server hasn't been started yet. Please call start() first." 276: end 277: return @parent_channel 278: end