OSDN Git Service

Replaced mongrel with thin
[redminele/redminele.git] / ruby / lib / ruby / gems / 1.8 / gems / thin-1.2.11-x86-mswin32 / lib / thin / controllers / cluster.rb
1 require 'socket'
2
3 module Thin
4   # An exception class to handle the event that server didn't start on time
5   class RestartTimeout < RuntimeError; end
6   
7   module Controllers
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]
16       
17       # Maximum wait time for the server to be restarted
18       DEFAULT_WAIT_TIME = 30    # seconds
19       
20       # Create a new cluster of servers launched using +options+.
21       def initialize(options)
22         super
23         # Cluster can only contain daemonized servers
24         @options.merge!(:daemonize => true)
25       end
26       
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
36       
37       def swiftiply?
38         @options.has_key?(:swiftiply)
39       end
40     
41       # Start the servers
42       def start
43         with_each_server { |n| start_server n }
44       end
45     
46       # Start a single server
47       def start_server(number)
48         log "Starting server on #{server_id(number)} ... "
49       
50         run :start, number
51       end
52   
53       # Stop the servers
54       def stop
55         with_each_server { |n| stop_server n }
56       end
57     
58       # Stop a single server
59       def stop_server(number)
60         log "Stopping server on #{server_id(number)} ... "
61       
62         run :stop, number
63       end
64     
65       # Stop and start the servers.
66       def restart
67         unless onebyone
68           # Let's do a normal restart by defaults
69           stop
70           sleep 0.1 # Let's breath a bit shall we ?
71           start
72         else
73           with_each_server do |n| 
74             stop_server(n)
75             sleep 0.1 # Let's breath a bit shall we ?
76             start_server(n)
77             wait_until_server_started(n)
78           end
79         end
80       end
81       
82       def test_socket(number)
83         if socket
84           UNIXSocket.new(socket_for(number))
85         else
86           TCPSocket.new(address, number)
87         end
88       rescue
89         nil
90       end
91       
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
96         
97         tries = 0
98         loop do
99           if test_socket = test_socket(number)
100             test_socket.close
101             break
102           elsif tries < wait
103             sleep 1
104             tries += 1
105           else
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)."
109           end
110         end
111       end
112     
113       def server_id(number)
114         if socket
115           socket_for(number)
116         elsif swiftiply?
117           [address, first_port, number].join(':')
118         else
119           [address, number].join(':')
120         end
121       end
122     
123       def log_file_for(number)
124         include_server_number log_file, number
125       end
126     
127       def pid_file_for(number)
128         include_server_number pid_file, number
129       end
130     
131       def socket_for(number)
132         include_server_number socket, number
133       end
134     
135       def pid_for(number)
136         File.read(pid_file_for(number)).chomp.to_i
137       end
138       
139       private
140         # Send the command to the +thin+ script
141         def run(cmd, number)
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))
144           if socket
145             cmd_options.merge!(:socket => socket_for(number))
146           elsif swiftiply?
147             cmd_options.merge!(:port => first_port)
148           else
149             cmd_options.merge!(:port => number)
150           end
151           Command.run(cmd, cmd_options)
152         end
153       
154         def with_each_server
155           if only
156             if first_port && only < 80
157               # interpret +only+ as a sequence number
158               yield first_port + only
159             else
160               # interpret +only+ as an absolute port number
161               yield only
162             end
163           elsif socket || swiftiply?
164             size.times { |n| yield n }
165           else
166             size.times { |n| yield first_port + n }
167           end
168         end
169       
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}")
175         end
176     end
177   end
178 end