Class: PhusionPassenger::AbstractServer
- Includes:
- Utils
- Defined in:
- lib/phusion_passenger/abstract_server.rb
Overview
An abstract base class for a server that has the following properties:
-
The server listens on a password protected Unix socket.
-
The server is multithreaded and handles one client per thread.
-
The server is owned by one or more processes. If all processes close their reference to the server, then the server will quit.
-
The server’s main loop may be run in a child process (and so is asynchronous from the parent process).
-
One can communicate with the server through discrete MessageChannel messages, as opposed to byte streams.
-
The server can pass file descriptors (IO objects) back to the client.
The server will also reset all signal handlers. 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)
connect do |channel|
channel.write('hello', first_name, last_name)
reply, pointless_number = channel.read
puts "The server said: #{reply}"
puts "In addition, it sent this pointless number: #{pointless_number}"
end
end
private
def handle_hello(channel, first_name, last_name)
channel.write("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
ClassicRails::ApplicationSpawner, ClassicRails::FrameworkSpawner, Rack::ApplicationSpawner, SpawnManager
Defined Under Namespace
Classes: InvalidPassword, ServerAlreadyStarted, ServerError, ServerNotStarted, UnknownMessage
Instance Attribute Summary collapse
-
#ignore_password_errors ⇒ Object
Returns the value of attribute ignore_password_errors.
-
#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.
-
#password ⇒ Object
readonly
Returns the value of attribute password.
Instance Method Summary collapse
-
#connect ⇒ Object
Connects to the server and yields a channel for communication.
-
#initialize(socket_filename = nil, password = nil) ⇒ 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_filename, password, server_socket, owner_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.
Methods included from Utils
Constructor Details
#initialize(socket_filename = nil, password = nil) ⇒ AbstractServer
Returns a new instance of AbstractServer.
116 117 118 119 120 121 122 123 124 125 |
# File 'lib/phusion_passenger/abstract_server.rb', line 116 def initialize(socket_filename = nil, password = nil) @socket_filename = socket_filename @password = password @socket_filename ||= "#{passenger_tmpdir}/spawn-server/socket.#{Process.pid}.#{object_id}" @password ||= generate_random_id(:base64) @message_handlers = {} @signal_handlers = {} @orig_signal_handlers = {} end |
Instance Attribute Details
#ignore_password_errors ⇒ Object
Returns the value of attribute ignore_password_errors.
104 105 106 |
# File 'lib/phusion_passenger/abstract_server.rb', line 104 def ignore_password_errors @ignore_password_errors 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.
110 111 112 |
# File 'lib/phusion_passenger/abstract_server.rb', line 110 def max_idle_time @max_idle_time end |
#next_cleaning_time ⇒ Object
Used by AbstractServerCollection to remember when this AbstractServer should be idle cleaned.
114 115 116 |
# File 'lib/phusion_passenger/abstract_server.rb', line 114 def next_cleaning_time @next_cleaning_time end |
#password ⇒ Object (readonly)
Returns the value of attribute password.
103 104 105 |
# File 'lib/phusion_passenger/abstract_server.rb', line 103 def password @password end |
Instance Method Details
#connect ⇒ Object
Connects to the server and yields a channel for communication. The first message’s name must match a handler name. The connection can only be used for a single handler cycle; after the handler is done, the connection will be closed.
server.connect do |channel|
channel.write("a message")
...
end
Raises: SystemCallError, IOError, SocketError
267 268 269 270 271 272 273 274 275 |
# File 'lib/phusion_passenger/abstract_server.rb', line 267 def connect channel = MessageChannel.new(UNIXSocket.new(@socket_filename)) begin channel.write_scalar(@password) yield channel ensure channel.close end end |
#server_pid ⇒ Object
Return the PID of the started server. This is only valid if #start has been called.
252 253 254 |
# File 'lib/phusion_passenger/abstract_server.rb', line 252 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.
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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/phusion_passenger/abstract_server.rb', line 134 def start if started? raise ServerAlreadyStarted, "Server is already started" end a, b = UNIXSocket.pair File.unlink(@socket_filename) rescue nil server_socket = UNIXServer.new(@socket_filename) File.chmod(0700, @socket_filename) before_fork @pid = fork if @pid.nil? has_exception = false begin STDOUT.sync = true STDERR.sync = true a.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, b.fileno, server_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(@socket_filename, @password, server_socket, b) rescue Interrupt # Do nothing. has_exception = true rescue Exception => e has_exception = true print_exception(self.class.to_s, e) ensure exit!(has_exception ? 1 : 0) end end server_socket.close b.close @owner_socket = a end |
#start_synchronously(socket_filename, password, server_socket, owner_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.
All hooks will be called, except before_fork().
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/phusion_passenger/abstract_server.rb', line 200 def start_synchronously(socket_filename, password, server_socket, owner_socket) @owner_socket = owner_socket begin reset_signal_handlers initialize_server begin server_main_loop(password, server_socket) ensure finalize_server end rescue Interrupt # Do nothing ensure @owner_socket = nil revert_signal_handlers File.unlink(socket_filename) rescue nil server_socket.close end end |
#started? ⇒ Boolean
Return whether the server has been started.
247 248 249 |
# File 'lib/phusion_passenger/abstract_server.rb', line 247 def started? return !!@owner_socket 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.
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/phusion_passenger/abstract_server.rb', line 225 def stop if !started? raise ServerNotStarted, "Server is not started" end begin @owner_socket.write("x") rescue Errno::EPIPE end @owner_socket.close @owner_socket = nil File.unlink(@socket_filename) rescue nil # Wait at most 4 seconds for server to exit. If it doesn't do that, # we kill it forcefully with SIGKILL. if !Process.timed_waitpid(@pid, 4) Process.kill('SIGKILL', @pid) rescue nil Process.timed_waitpid(@pid, 1) end end |