Skip to content

Commit

Permalink
Finish the spawn manager.
Browse files Browse the repository at this point in the history
  • Loading branch information
Hongli Lai committed Jan 25, 2008
1 parent 6c1ee04 commit 4814279
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 26 deletions.
26 changes: 22 additions & 4 deletions lib/mod_rails/framework_spawner.rb
Expand Up @@ -15,6 +15,9 @@ class FrameworkSpawner < AbstractServer
APP_SPAWNER_MAX_IDLE_TIME = 120

include Utils

# An attribute, used internally. This should not be used outside mod_rails.
attr_accessor :time

# Creates a new instance of FrameworkSpawner.
#
Expand Down Expand Up @@ -79,8 +82,12 @@ def spawn_application(app_root, username = nil)
end
end

def reload(app_root)
send_to_server("reload", normalize_path(app_root))
def reload(app_root = nil)
if app_root.nil?
send_to_server("reload")
else
send_to_server("reload", normalize_path(app_root))
end
rescue Errno::EPIPE, Errno::EBADF, IOError, SocketError
raise IOError, "Cannot send reload command to the framework spawner server."
end
Expand Down Expand Up @@ -163,9 +170,20 @@ def handle_spawn_application(app_root, username)
end
end

def handle_reload(app_root)
def handle_reload(app_root = nil)
@spawners_lock.synchronize do
@spawners.delete(app_root)
if app_root.nil?
@spawners.each_value do |spawner|
spawner.stop
end
@spawners.clear
else
spawner = @spawners[app_root]
if spawner
spawner.stop
end
@spawners.delete(app_root)
end
end
end

Expand Down
10 changes: 9 additions & 1 deletion lib/mod_rails/message_channel.rb
@@ -1,3 +1,4 @@
require 'mod_rails/native_support'
module ModRails # :nodoc:

# This class can be wrapped around an IO object to provide the ability
Expand All @@ -20,6 +21,9 @@ class MessageChannel
DELIMITER = "\0"
DELIMITER_NAME = "null byte"

include NativeSupport
private :send_fd

# The wrapped IO object.
attr_reader :io

Expand Down Expand Up @@ -88,7 +92,11 @@ def write(name, *args)
# connection.
# Raises IOError if the IO stream is already closed on this side.
def send_io(io)
@io.send_io(io)
if io.respond_to?(:send_io)
@io.send_io(io)
else
send_fd(@io.fileno, io.fileno)
end
end

# Receive an IO object (a file descriptor) from the channel. The other
Expand Down
5 changes: 3 additions & 2 deletions lib/mod_rails/request_handler.rb
@@ -1,7 +1,7 @@
# NOTE: we make use of pipes instead of Unix sockets, because
# experimentation has shown that pipes are slightly faster.

module ModRails # :nodoc:

class RequestHandler
def initialize(reader_pipe, writer_pipe)
@reader = reader_pipe
Expand Down Expand Up @@ -48,5 +48,6 @@ def revert_signal_handlers
trap(signal, handler)
end
end
end # class SCGIHandler
end

end # module ModRails
146 changes: 127 additions & 19 deletions lib/mod_rails/spawn_manager.rb
@@ -1,61 +1,169 @@
if __FILE__ == $0
$LOAD_PATH << "#{File.dirname(__FILE__)}/.."
end
require 'mod_rails/framework_spawner'
require 'mod_rails/application'
require 'mod_rails/message_channel'
require 'mod_rails/core_extensions'
module ModRails # :nodoc:

class SpawnManager
SPAWNER_CLEAN_INTERVAL = 125
SPAWNER_MAX_IDLE_TIME = 120

def initialize
@previous_signal_handlers = {}
@spawners = {}
@lock = Mutex.new
@cond = ConditionVariable.new
@cleaner_thread = Thread.new do
cleaner_thread_main
end
end

def cleanup
@lock.synchronize do
@cond.signal
end
@cleaner_thread.join
@lock.synchronize do
@spawners.each_value do |spawner|
spawner.stop
end
@spawners.clear
end
end

def spawn_application(app_root, username = nil)
framework_version = Application.get_framework_version(app_root)
spawner = @spawners[framework_version]
if !spawner
spawner = FrameworkSpawner.new(framework_version)
@spawners[framework_version] = spawner
spawner.start
@lock.synchronize do
spawner = @spawners[framework_version]
if !spawner
spawner = FrameworkSpawner.new(framework_version)
@spawners[framework_version] = spawner
spawner.start
end
end
spawner.time = Time.now
return spawner.spawn_application(app_root, username)
end

def server_main(unix_socket)
@done = false
@channel = MessageChannel.new(unix_socket)
install_signal_handlers
@done = false
while !@done
begin
name, *args = channel.read
if name.nil?
begin
while !@done
begin
name, *args = @channel.read
if name.nil?
@done = true
elsif !MESSAGE_HANDLERS.has_key?(name)
raise StandardError, "Unknown message '#{name}' received."
else
name, *args = message
__send__(MESSAGE_HANDLERS[name], *args)
end
rescue ExitNow
@done = true
elsif name.???
else
name, *args = message
__send__(MESSAGE_HANDLERS[name], *args)
end
end
ensure
revert_signal_handlers
end
@spawners.each_value do |spawner|
spawner.stop
end
@spawners.clear
end

private
class ExitNow < RuntimeError
end

SIGNAL_HANDLERS = {
'TERM' => :exit_now!,
'INT' => :exit_now!,
'USR1' => :exit_asap,
'HUP' => :reload
}
MESSAGE_HANDLERS = {
'spawn_application' => :handle_spawn_application
}

def install_signal_handlers
Signal.list.each_key do |signal|
begin
prev_handler = trap(signal, 'DEFAULT')
if prev_handler != 'DEFAULT'
@previous_signal_handlers[signal] = prev_handler
end
rescue ArgumentError
# Signal cannot be trapped; ignore it.
end
end
SIGNAL_HANDLERS.each_pair do |signal, handler|
if handler == :ignore
trap(signal, 'IGNORE')
else
trap(signal) do
__send__(handler)
end
end
end
end

def revert_signal_handlers
@previous_signal_handlers.each_pair do |signal, handler|
trap(signal, handler)
end
@previous_signal_handlers = {}
end

def handle_spawn_application(app_root, username)
app = spawn_application(app_root, username)
@channel.write(app.pid)
@channel.send_io(app.socket)
app.socket.close
end

def cleaner_thread_main
@lock.synchronize do
while true
if @cond.timed_wait(@lock, SPAWNER_CLEAN_INTERVAL)
break
else
current_time = Time.now
@spawners.keys.each do |key|
spawner = @spawners[key]
if current_time - spawner.time > SPAWNER_MAX_IDLE_TIME
spawner.stop
@spawners.delete(key)
end
end
end
end
end
end

def exit_now!
raise ExitNow, "Exit requested"
end

def exit_asap
@done = true
end

def reload
@lock.synchronize do
@spawners.each_each do |spawner|
spawner.reload
end
end
end
end

if __FILE__ == $0
exit(SpawnManager.new.server_main)
unix_socket = IO.new(ARGV[0].to_i, "a+")
spawn_manager = SpawnManager.new
spawn_manager.server_main(unix_socket)
spawn_manager.cleanup
end

end # module ModRails
1 change: 1 addition & 0 deletions test/message_channel_test.rb
@@ -1,3 +1,4 @@
$LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"
require 'test/unit'
require 'socket'
require 'mod_rails/message_channel'
Expand Down

0 comments on commit 4814279

Please sign in to comment.