4 # An exception class to handle the event that server didn't start on time
5 class RestartTimeout < RuntimeError; end
8 # Control a set of servers.
9 # * Generate start and stop commands and run them.
10 # * Inject the port or socket number in the pid and log filenames.
11 # Servers are started throught the +thin+ command-line script.
12 class Cluster < Controller
13 # Cluster only options that should not be passed in the command sent
14 # to the indiviual servers.
15 CLUSTER_OPTIONS = [:servers, :only, :onebyone, :wait]
17 # Maximum wait time for the server to be restarted
18 DEFAULT_WAIT_TIME = 30 # seconds
20 # Create a new cluster of servers launched using +options+.
21 def initialize(options)
23 # Cluster can only contain daemonized servers
24 @options.merge!(:daemonize => true)
27 def first_port; @options[:port] end
28 def address; @options[:address] end
29 def socket; @options[:socket] end
30 def pid_file; @options[:pid] end
31 def log_file; @options[:log] end
32 def size; @options[:servers] end
33 def only; @options[:only] end
34 def onebyone; @options[:onebyone] end
35 def wait; @options[:wait] end
38 @options.has_key?(:swiftiply)
43 with_each_server { |n| start_server n }
46 # Start a single server
47 def start_server(number)
48 log "Starting server on #{server_id(number)} ... "
55 with_each_server { |n| stop_server n }
58 # Stop a single server
59 def stop_server(number)
60 log "Stopping server on #{server_id(number)} ... "
65 # Stop and start the servers.
68 # Let's do a normal restart by defaults
70 sleep 0.1 # Let's breath a bit shall we ?
73 with_each_server do |n|
75 sleep 0.1 # Let's breath a bit shall we ?
77 wait_until_server_started(n)
82 def test_socket(number)
84 UNIXSocket.new(socket_for(number))
86 TCPSocket.new(address, number)
92 # Make sure the server is running before moving on to the next one.
93 def wait_until_server_started(number)
94 log "Waiting for server to start ..."
95 STDOUT.flush # Need this to make sure user got the message
99 if test_socket = test_socket(number)
106 raise RestartTimeout, "The server didn't start in time. Please look at server's log file " +
107 "for more information, or set the value of 'wait' in your config " +
108 "file to be higher (defaults: 30)."
113 def server_id(number)
117 [address, first_port, number].join(':')
119 [address, number].join(':')
123 def log_file_for(number)
124 include_server_number log_file, number
127 def pid_file_for(number)
128 include_server_number pid_file, number
131 def socket_for(number)
132 include_server_number socket, number
136 File.read(pid_file_for(number)).chomp.to_i
140 # Send the command to the +thin+ script
142 cmd_options = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
143 cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
145 cmd_options.merge!(:socket => socket_for(number))
147 cmd_options.merge!(:port => first_port)
149 cmd_options.merge!(:port => number)
151 Command.run(cmd, cmd_options)
156 if first_port && only < 80
157 # interpret +only+ as a sequence number
158 yield first_port + only
160 # interpret +only+ as an absolute port number
163 elsif socket || swiftiply?
164 size.times { |n| yield n }
166 size.times { |n| yield first_port + n }
170 # Add the server port or number in the filename
171 # so each instance get its own file
172 def include_server_number(path, number)
173 ext = File.extname(path)
174 path.gsub(/#{ext}$/, ".#{number}#{ext}")