Class: PhusionPassenger::AbstractServer
- Includes:
- Utils
- Defined in:
- lib/phusion_passenger/abstract_server.rb
Overview
An abstract base class for a server, with the following properties:
-
The server has exactly one client, and is connected to that client at all times. The server will quit when the connection closes.
-
The server’s main loop may be run in a child process (and so is asynchronous from the main process).
-
One can communicate with the server through discrete messages (as opposed to byte streams).
-
The server can pass file descriptors (IO objects) back to the client.
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()
(: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
Direct Known Subclasses
Railz::ApplicationSpawner, Railz::FrameworkSpawner, SpawnManager
Defined Under Namespace
Classes: ServerAlreadyStarted, ServerError, ServerNotStarted, UnknownMessage
Constant Summary collapse
- SERVER_TERMINATION_SIGNAL =
"SIGTERM"
Instance Attribute Summary collapse
-
#last_activity_time ⇒ Object
The last time when this AbstractServer had processed a message.
-
#max_idle_time ⇒ Object
The maximum time that this AbstractServer may be idle.
-
#next_cleaning_time ⇒ Object
Used by AbstractServerCollection to remember when this AbstractServer should be idle cleaned.
Instance Method Summary collapse
-
#initialize ⇒ AbstractServer
constructor
A new instance of AbstractServer.
-
#server_pid ⇒ Object
Return the PID of the started server.
-
#start ⇒ Object
Start the server.
-
#start_synchronously(socket) ⇒ Object
Start the server, but in the current process instead of in a child process.
-
#started? ⇒ Boolean
Return whether the server has been started.
-
#stop ⇒ Object
Stop the server.
Constructor Details
#initialize ⇒ AbstractServer
Returns a new instance of AbstractServer.
109 110 111 112 113 114 115 |
# File 'lib/phusion_passenger/abstract_server.rb', line 109 def initialize @done = false @message_handlers = {} @signal_handlers = {} @orig_signal_handlers = {} @last_activity_time = Time.now end |
Instance Attribute Details
#last_activity_time ⇒ Object
The last time when this AbstractServer had processed a message.
97 98 99 |
# File 'lib/phusion_passenger/abstract_server.rb', line 97 def last_activity_time @last_activity_time end |
#max_idle_time ⇒ Object
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.
103 104 105 |
# File 'lib/phusion_passenger/abstract_server.rb', line 103 def max_idle_time @max_idle_time end |
#next_cleaning_time ⇒ Object
Used by AbstractServerCollection to remember when this AbstractServer should be idle cleaned.
107 108 109 |
# File 'lib/phusion_passenger/abstract_server.rb', line 107 def next_cleaning_time @next_cleaning_time end |
Instance Method Details
#server_pid ⇒ Object
Return the PID of the started server. This is only valid if start() has been called.
244 245 246 |
# File 'lib/phusion_passenger/abstract_server.rb', line 244 def server_pid return @pid end |
#start ⇒ Object
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.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/phusion_passenger/abstract_server.rb', line 124 def start if started? raise ServerAlreadyStarted, "Server is already started" end @parent_socket, @child_socket = UNIXSocket.pair before_fork @pid = fork if @pid.nil? begin STDOUT.sync = true STDERR.sync = true @parent_socket.close # During Passenger's early days, we used to close file descriptors based # on a white list of file descriptors. That proved to be way too fragile: # too many file descriptors are being left open even though they shouldn't # be. So now we close file descriptors based on a black list. # # Note that STDIN, STDOUT and STDERR may be temporarily set to # different file descriptors than 0, 1 and 2, e.g. in unit tests. # We don't want to close these either. file_descriptors_to_leave_open = [0, 1, 2, @child_socket.fileno, fileno_of(STDIN), fileno_of(STDOUT), fileno_of(STDERR)].compact.uniq NativeSupport.close_all_file_descriptors(file_descriptors_to_leave_open) # In addition to closing the file descriptors, one must also close # the associated IO objects. This is to prevent IO.close from # double-closing already closed file descriptors. close_all_io_objects_for_fds(file_descriptors_to_leave_open) # At this point, RubyGems might have open file handles for which # the associated file descriptors have just been closed. This can # result in mysterious 'EBADFD' errors. So we force RubyGems to # clear all open file handles. Gem.clear_paths # Reseed pseudo-random number generator for security reasons. srand start_synchronously(@child_socket) rescue Interrupt # Do nothing. rescue SignalException => signal if signal. == SERVER_TERMINATION_SIGNAL # Do nothing. else print_exception(self.class.to_s, signal) end rescue Exception => e print_exception(self.class.to_s, e) ensure exit! end end @child_socket.close @parent_channel = MessageChannel.new(@parent_socket) end |
#start_synchronously(socket) ⇒ Object
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().
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/phusion_passenger/abstract_server.rb', line 189 def start_synchronously(socket) @child_socket = socket @child_channel = MessageChannel.new(socket) begin reset_signal_handlers initialize_server begin main_loop ensure finalize_server end ensure revert_signal_handlers end end |
#started? ⇒ Boolean
Return whether the server has been started.
239 240 241 |
# File 'lib/phusion_passenger/abstract_server.rb', line 239 def started? return !@parent_channel.nil? end |
#stop ⇒ Object
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.
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/phusion_passenger/abstract_server.rb', line 210 def stop if !started? raise ServerNotStarted, "Server is not started" end @parent_socket.close @parent_channel = nil # Wait at most 3 seconds for server to exit. If it doesn't do that, # we kill it. If that doesn't work either, we kill it forcefully with # SIGKILL. begin Timeout::timeout(3) do Process.waitpid(@pid) rescue nil end rescue Timeout::Error Process.kill(SERVER_TERMINATION_SIGNAL, @pid) rescue nil begin Timeout::timeout(3) do Process.waitpid(@pid) rescue nil end rescue Timeout::Error Process.kill('SIGKILL', @pid) rescue nil Process.waitpid(@pid, Process::WNOHANG) rescue nil end end end |