class PhusionPassenger::AbstractServer

server.stop
server.hello(“Joe”, “Dalton”)
server.start
server = MyServer.new
end
end
channel.write(“Hello #{first_name} #{last_name}, how are you?”, 1234)
def handle_hello(channel, first_name, last_name)
private
end
end
puts “In addition, it sent this pointless number: #{pointless_number}”
puts “The server said: #{reply}”
reply, pointless_number = channel.read
channel.write(‘hello’, first_name, last_name)
connect do |channel|
def hello(first_name, last_name)
end
define_message_handler(:hello, :handle_hello)
super()
def initialize
class MyServer < PhusionPassenger::AbstractServer
Here’s an example on using AbstractServer:
When it is no longer needed, stop() should be called.
Before an AbstractServer can be used, it must first be started by calling start().
ignored. One may define additional signal handlers using define_signal_handler().
all signals in the default manner. The only exception is SIGHUP, which is
The server will also reset all signal handlers. That is, it will respond to
- The server can pass file descriptors (IO objects) back to the client.
as opposed to byte streams.
- One can communicate with the server through discrete MessageChannel messages,
from the parent process).
- The server’s main loop may be run in a child process (and so is asynchronous
reference to the server, then the server will quit.
- The server is owned by one or more processes. If all processes close their
- The server is multithreaded and handles one client per thread.
- The server listens on a password protected Unix socket.
An abstract base class for a server that has the following properties:

def before_fork

The default implementation does nothing, this method is supposed to be overrided by child classes.
A hook which is called when the server is being started, just before forking a new process.
def before_fork
end

def connect

Raises: SystemCallError, IOError, SocketError

end
...
channel.write("a message")
server.connect do |channel|

will be closed.
be used for a single handler cycle; after the handler is done, the connection
The first message's name must match a handler name. The connection can only
Connects to the server and yields a channel for communication.
def connect
	channel = MessageChannel.new(UNIXSocket.new(@socket_filename))
	begin
		channel.write_scalar(@password)
		yield channel
	ensure
		channel.close
	end
end

def define_message_handler(message_name, handler)

arguments, excluding the first element. See also the example in the class description.
A message is just a list of strings, and so _handler_ will be called with the message as its

and _handler_ is the name of a method to be called (this may either be a String or a Symbol).
Define a handler for a message. _message_name_ is the name of the message to handle,
def define_message_handler(message_name, handler)
	@message_handlers[message_name.to_s] = handler
end

def define_signal_handler(signal, handler)

Define a handler for a signal.
def define_signal_handler(signal, handler)
	@signal_handlers[signal.to_s] = handler
end

def fileno_of(io)

def fileno_of(io)
	return io.fileno
rescue
	return nil
end

def finalize_server

The default implementation does nothing, this method is supposed to be overrided by child classes.
after the main loop has been left.
A hook which is called when the server is being stopped. This is called in the child process,
def finalize_server
end

def initialize(socket_filename = nil, password = nil)

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

def initialize_server

The default implementation does nothing, this method is supposed to be overrided by child classes.
before the main loop is entered.
A hook which is called when the server is being started. This is called in the child process,
def initialize_server
end

def reset_signal_handlers

before entering the main loop.
Reset all signal handlers to default. This is called in the child process,
def reset_signal_handlers
	Signal.list_trappable.each_key do |signal|
		begin
			@orig_signal_handlers[signal] = trap(signal, 'DEFAULT')
		rescue ArgumentError
			# Signal cannot be trapped; ignore it.
		end
	end
	@signal_handlers.each_pair do |signal, handler|
		trap(signal) do
			__send__(handler)
		end
	end
	trap('HUP', 'IGNORE')
end

def revert_signal_handlers

def revert_signal_handlers
	@orig_signal_handlers.each_pair do |signal, handler|
		trap(signal, handler)
	end
	@orig_signal_handlers.clear
end

def server_main_loop(password, server_socket)

def server_main_loop(password, server_socket)
	while true
		ios = select([@owner_socket, server_socket]).first
		if ios.include?(server_socket)
			client_socket = server_socket.accept
			begin
				client = MessageChannel.new(client_socket)
				
				client_password = client.read_scalar
				if client_password != password
					next
				end
				
				name, *args = client.read
				if name
					if @message_handlers.has_key?(name)
						__send__(@message_handlers[name], client, *args)
					else
						raise UnknownMessage, "Unknown message '#{name}' received."
					end
				end
			ensure
				client_socket.close
			end
		else
			break
		end
	end
end

def server_pid

Return the PID of the started server. This is only valid if #start has been called.
def server_pid
	return @pid
end

def start

Derived classes may raise additional exceptions.

Otherwise, a ServerAlreadyStarted will be raised.
You may only call this method if the server is not already started.

asynchronously from the current process.
Start the server. This method does not block since the server runs
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

def start_synchronously(socket_filename, password, server_socket, owner_socket)

All hooks will be called, except before_fork().

This method blocks until the server's main loop has ended.
Start the server, but in the current process instead of in a child process.
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

def started?

Return whether the server has been started.
def started?
	return !!@owner_socket
end

def stop

ServerNotStarted will be raised.
When calling this method, the server must already be started. If not, a

until the server has been stopped.
Stop the server. The server will quit as soon as possible. This method waits
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