steamcannon-thin 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +288 -0
  2. data/COPYING +18 -0
  3. data/README +69 -0
  4. data/Rakefile +44 -0
  5. data/benchmark/abc +51 -0
  6. data/benchmark/benchmarker.rb +80 -0
  7. data/benchmark/runner +82 -0
  8. data/bin/thin +6 -0
  9. data/example/adapter.rb +32 -0
  10. data/example/async_app.ru +126 -0
  11. data/example/async_chat.ru +247 -0
  12. data/example/async_tailer.ru +100 -0
  13. data/example/config.ru +22 -0
  14. data/example/monit_sockets +20 -0
  15. data/example/monit_unixsock +20 -0
  16. data/example/myapp.rb +1 -0
  17. data/example/ramaze.ru +12 -0
  18. data/example/thin.god +80 -0
  19. data/example/thin_solaris_smf.erb +36 -0
  20. data/example/thin_solaris_smf.readme.txt +150 -0
  21. data/example/vlad.rake +64 -0
  22. data/ext/thin_parser/common.rl +55 -0
  23. data/ext/thin_parser/ext_help.h +14 -0
  24. data/ext/thin_parser/extconf.rb +6 -0
  25. data/ext/thin_parser/parser.c +1249 -0
  26. data/ext/thin_parser/parser.h +49 -0
  27. data/ext/thin_parser/parser.rl +157 -0
  28. data/ext/thin_parser/thin.c +436 -0
  29. data/lib/rack/adapter/loader.rb +91 -0
  30. data/lib/rack/adapter/rails.rb +183 -0
  31. data/lib/thin.rb +56 -0
  32. data/lib/thin/backends/base.rb +149 -0
  33. data/lib/thin/backends/swiftiply_client.rb +56 -0
  34. data/lib/thin/backends/tcp_server.rb +29 -0
  35. data/lib/thin/backends/unix_server.rb +51 -0
  36. data/lib/thin/command.rb +53 -0
  37. data/lib/thin/connection.rb +224 -0
  38. data/lib/thin/controllers/cluster.rb +178 -0
  39. data/lib/thin/controllers/controller.rb +188 -0
  40. data/lib/thin/controllers/service.rb +75 -0
  41. data/lib/thin/controllers/service.sh.erb +39 -0
  42. data/lib/thin/daemonizing.rb +180 -0
  43. data/lib/thin/headers.rb +39 -0
  44. data/lib/thin/logging.rb +54 -0
  45. data/lib/thin/request.rb +156 -0
  46. data/lib/thin/response.rb +101 -0
  47. data/lib/thin/runner.rb +220 -0
  48. data/lib/thin/server.rb +253 -0
  49. data/lib/thin/stats.html.erb +216 -0
  50. data/lib/thin/stats.rb +52 -0
  51. data/lib/thin/statuses.rb +43 -0
  52. data/lib/thin/version.rb +32 -0
  53. data/lib/thin_parser.so +0 -0
  54. data/spec/backends/swiftiply_client_spec.rb +66 -0
  55. data/spec/backends/tcp_server_spec.rb +33 -0
  56. data/spec/backends/unix_server_spec.rb +37 -0
  57. data/spec/command_spec.rb +25 -0
  58. data/spec/configs/cluster.yml +9 -0
  59. data/spec/configs/single.yml +9 -0
  60. data/spec/connection_spec.rb +106 -0
  61. data/spec/controllers/cluster_spec.rb +267 -0
  62. data/spec/controllers/controller_spec.rb +129 -0
  63. data/spec/controllers/service_spec.rb +50 -0
  64. data/spec/daemonizing_spec.rb +196 -0
  65. data/spec/headers_spec.rb +40 -0
  66. data/spec/logging_spec.rb +46 -0
  67. data/spec/perf/request_perf_spec.rb +50 -0
  68. data/spec/perf/response_perf_spec.rb +19 -0
  69. data/spec/perf/server_perf_spec.rb +39 -0
  70. data/spec/rack/loader_spec.rb +42 -0
  71. data/spec/rack/rails_adapter_spec.rb +173 -0
  72. data/spec/rails_app/app/controllers/application.rb +10 -0
  73. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  74. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  75. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  76. data/spec/rails_app/config/boot.rb +109 -0
  77. data/spec/rails_app/config/environment.rb +64 -0
  78. data/spec/rails_app/config/environments/development.rb +18 -0
  79. data/spec/rails_app/config/environments/production.rb +19 -0
  80. data/spec/rails_app/config/environments/test.rb +22 -0
  81. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  82. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  83. data/spec/rails_app/config/routes.rb +35 -0
  84. data/spec/rails_app/public/404.html +30 -0
  85. data/spec/rails_app/public/422.html +30 -0
  86. data/spec/rails_app/public/500.html +30 -0
  87. data/spec/rails_app/public/dispatch.cgi +10 -0
  88. data/spec/rails_app/public/dispatch.fcgi +24 -0
  89. data/spec/rails_app/public/dispatch.rb +10 -0
  90. data/spec/rails_app/public/favicon.ico +0 -0
  91. data/spec/rails_app/public/images/rails.png +0 -0
  92. data/spec/rails_app/public/index.html +277 -0
  93. data/spec/rails_app/public/javascripts/application.js +2 -0
  94. data/spec/rails_app/public/javascripts/controls.js +963 -0
  95. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  96. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  97. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  98. data/spec/rails_app/public/robots.txt +5 -0
  99. data/spec/rails_app/script/about +3 -0
  100. data/spec/rails_app/script/console +3 -0
  101. data/spec/rails_app/script/destroy +3 -0
  102. data/spec/rails_app/script/generate +3 -0
  103. data/spec/rails_app/script/performance/benchmarker +3 -0
  104. data/spec/rails_app/script/performance/profiler +3 -0
  105. data/spec/rails_app/script/performance/request +3 -0
  106. data/spec/rails_app/script/plugin +3 -0
  107. data/spec/rails_app/script/process/inspector +3 -0
  108. data/spec/rails_app/script/process/reaper +3 -0
  109. data/spec/rails_app/script/process/spawner +3 -0
  110. data/spec/rails_app/script/runner +3 -0
  111. data/spec/rails_app/script/server +3 -0
  112. data/spec/request/mongrel_spec.rb +39 -0
  113. data/spec/request/parser_spec.rb +254 -0
  114. data/spec/request/persistent_spec.rb +35 -0
  115. data/spec/request/processing_spec.rb +50 -0
  116. data/spec/response_spec.rb +91 -0
  117. data/spec/runner_spec.rb +168 -0
  118. data/spec/server/builder_spec.rb +44 -0
  119. data/spec/server/pipelining_spec.rb +110 -0
  120. data/spec/server/robustness_spec.rb +34 -0
  121. data/spec/server/stopping_spec.rb +55 -0
  122. data/spec/server/swiftiply.yml +6 -0
  123. data/spec/server/swiftiply_spec.rb +32 -0
  124. data/spec/server/tcp_spec.rb +57 -0
  125. data/spec/server/threaded_spec.rb +27 -0
  126. data/spec/server/unix_socket_spec.rb +26 -0
  127. data/spec/server_spec.rb +100 -0
  128. data/spec/spec_helper.rb +220 -0
  129. data/tasks/announce.rake +22 -0
  130. data/tasks/deploy.rake +13 -0
  131. data/tasks/email.erb +30 -0
  132. data/tasks/gem.rake +66 -0
  133. data/tasks/rdoc.rake +25 -0
  134. data/tasks/site.rake +15 -0
  135. data/tasks/spec.rake +43 -0
  136. data/tasks/stats.rake +28 -0
  137. metadata +251 -0
@@ -0,0 +1,56 @@
1
+ module Thin
2
+ module Backends
3
+ # Backend to act as a Swiftiply client (http://swiftiply.swiftcore.org).
4
+ class SwiftiplyClient < Base
5
+ attr_accessor :key
6
+
7
+ attr_accessor :host, :port
8
+
9
+ def initialize(host, port, options={})
10
+ @host = host
11
+ @port = port.to_i
12
+ @key = options[:swiftiply].to_s
13
+ super()
14
+ end
15
+
16
+ # Connect the server
17
+ def connect
18
+ EventMachine.connect(@host, @port, SwiftiplyConnection, &method(:initialize_connection))
19
+ end
20
+
21
+ # Stops the server
22
+ def disconnect
23
+ EventMachine.stop
24
+ end
25
+
26
+ def to_s
27
+ "#{@host}:#{@port} swiftiply"
28
+ end
29
+ end
30
+ end
31
+
32
+ class SwiftiplyConnection < Connection
33
+ def connection_completed
34
+ send_data swiftiply_handshake(@backend.key)
35
+ end
36
+
37
+ def persistent?
38
+ true
39
+ end
40
+
41
+ def unbind
42
+ super
43
+ EventMachine.add_timer(rand(2)) { reconnect(@backend.host, @backend.port) } if @backend.running?
44
+ end
45
+
46
+ protected
47
+ def swiftiply_handshake(key)
48
+ 'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i)}.join << sprintf('%04x', @backend.port) << sprintf('%02x', key.length) << key
49
+ end
50
+
51
+ # For some reason Swiftiply request the current host
52
+ def host_ip
53
+ Socket.gethostbyname(@backend.host)[3].unpack('CCCC') rescue [0,0,0,0]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,29 @@
1
+ module Thin
2
+ module Backends
3
+ # Backend to act as a TCP socket server.
4
+ class TcpServer < Base
5
+ # Address and port on which the server is listening for connections.
6
+ attr_accessor :host, :port
7
+
8
+ def initialize(host, port)
9
+ @host = host
10
+ @port = port
11
+ super()
12
+ end
13
+
14
+ # Connect the server
15
+ def connect
16
+ @signature = EventMachine.start_server(@host, @port, Connection, &method(:initialize_connection))
17
+ end
18
+
19
+ # Stops the server
20
+ def disconnect
21
+ EventMachine.stop_server(@signature)
22
+ end
23
+
24
+ def to_s
25
+ "#{@host}:#{@port}"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,51 @@
1
+ module Thin
2
+ module Backends
3
+ # Backend to act as a UNIX domain socket server.
4
+ class UnixServer < Base
5
+ # UNIX domain socket on which the server is listening for connections.
6
+ attr_accessor :socket
7
+
8
+ def initialize(socket)
9
+ raise PlatformNotSupported, 'UNIX domain sockets not available on Windows' if Thin.win?
10
+ @socket = socket
11
+ super()
12
+ end
13
+
14
+ # Connect the server
15
+ def connect
16
+ at_exit { remove_socket_file } # In case it crashes
17
+ EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
18
+ # HACK EventMachine.start_unix_domain_server doesn't return the connection signature
19
+ # so we have to go in the internal stuff to find it.
20
+ @signature = EventMachine.instance_eval{@acceptors.keys.first}
21
+ end
22
+
23
+ # Stops the server
24
+ def disconnect
25
+ EventMachine.stop_server(@signature)
26
+ end
27
+
28
+ # Free up resources used by the backend.
29
+ def close
30
+ remove_socket_file
31
+ end
32
+
33
+ def to_s
34
+ @socket
35
+ end
36
+
37
+ protected
38
+ def remove_socket_file
39
+ File.delete(@socket) if @socket && File.exist?(@socket)
40
+ end
41
+ end
42
+ end
43
+
44
+ # Connection through a UNIX domain socket.
45
+ class UnixConnection < Connection
46
+ protected
47
+ def socket_address
48
+ '127.0.0.1' # Unix domain sockets can only be local
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
1
+ require 'open3'
2
+
3
+ module Thin
4
+ # Run a command through the +thin+ command-line script.
5
+ class Command
6
+ include Logging
7
+
8
+ class << self
9
+ # Path to the +thin+ script used to control the servers.
10
+ # Leave this to default to use the one in the path.
11
+ attr_accessor :script
12
+ end
13
+
14
+ def initialize(name, options={})
15
+ @name = name
16
+ @options = options
17
+ end
18
+
19
+ def self.run(*args)
20
+ new(*args).run
21
+ end
22
+
23
+ # Send the command to the +thin+ script
24
+ def run
25
+ shell_cmd = shellify
26
+ trace shell_cmd
27
+ trap('INT') {} # Ignore INT signal to pass CTRL+C to subprocess
28
+ Open3.popen3(shell_cmd) do |stdin, stdout, stderr|
29
+ log stdout.gets until stdout.eof?
30
+ log stderr.gets until stderr.eof?
31
+ end
32
+ end
33
+
34
+ # Turn into a runnable shell command
35
+ def shellify
36
+ shellified_options = @options.inject([]) do |args, (name, value)|
37
+ option_name = name.to_s.tr("_", "-")
38
+ case value
39
+ when NilClass,
40
+ TrueClass then args << "--#{option_name}"
41
+ when FalseClass
42
+ when Array then value.each { |v| args << "--#{option_name}=#{v.inspect}" }
43
+ else args << "--#{option_name}=#{value.inspect}"
44
+ end
45
+ args
46
+ end
47
+
48
+ raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
49
+
50
+ "#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,224 @@
1
+ require 'socket'
2
+
3
+ module Thin
4
+ # Connection between the server and client.
5
+ # This class is instanciated by EventMachine on each new connection
6
+ # that is opened.
7
+ class Connection < EventMachine::Connection
8
+ CONTENT_LENGTH = 'Content-Length'.freeze
9
+ TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
10
+ CHUNKED_REGEXP = /\bchunked\b/i.freeze
11
+
12
+ include Logging
13
+
14
+ # This is a template async response. N.B. Can't use string for body on 1.9
15
+ AsyncResponse = [-1, {}, []].freeze
16
+
17
+ # Rack application (adapter) served by this connection.
18
+ attr_accessor :app
19
+
20
+ # Backend to the server
21
+ attr_accessor :backend
22
+
23
+ # Current request served by the connection
24
+ attr_accessor :request
25
+
26
+ # Next response sent through the connection
27
+ attr_accessor :response
28
+
29
+ # Calling the application in a threaded allowing
30
+ # concurrent processing of requests.
31
+ attr_writer :threaded
32
+
33
+ # Get the connection ready to process a request.
34
+ def post_init
35
+ @request = Request.new
36
+ @response = Response.new
37
+ end
38
+
39
+ # Called when data is received from the client.
40
+ def receive_data(data)
41
+ trace { data }
42
+ process if @request.parse(data)
43
+ rescue InvalidRequest => e
44
+ log "!! Invalid request"
45
+ log_error e
46
+ close_connection
47
+ end
48
+
49
+ # Called when all data was received and the request
50
+ # is ready to be processed.
51
+ def process
52
+ if threaded?
53
+ @request.threaded = true
54
+ EventMachine.defer(method(:pre_process), method(:post_process))
55
+ else
56
+ @request.threaded = false
57
+ post_process(pre_process)
58
+ end
59
+ end
60
+
61
+ def pre_process
62
+ # Add client info to the request env
63
+ @request.remote_address = remote_address
64
+
65
+ # Connection may be closed unless the App#call response was a [-1, ...]
66
+ # It should be noted that connection objects will linger until this
67
+ # callback is no longer referenced, so be tidy!
68
+ @request.async_callback = method(:post_process)
69
+
70
+ @request.env["rack.url_scheme"] = "https" if backend.ssl?
71
+
72
+ # When we're under a non-async framework like rails, we can still spawn
73
+ # off async responses using the callback info, so there's little point
74
+ # in removing this.
75
+ response = AsyncResponse
76
+ catch(:async) do
77
+ # Process the request calling the Rack adapter
78
+ response = @app.call(@request.env)
79
+ end
80
+ response
81
+ rescue Exception
82
+ handle_error
83
+ terminate_request
84
+ nil # Signal to post_process that the request could not be processed
85
+ end
86
+
87
+ def post_process(result)
88
+ return unless result
89
+ result = result.to_a
90
+
91
+ # Status code -1 indicates that we're going to respond later (async).
92
+ return if result.first == AsyncResponse.first
93
+
94
+ # Set the Content-Length header if possible
95
+ set_content_length(result) if need_content_length?(result)
96
+
97
+ @response.status, @response.headers, @response.body = *result
98
+
99
+ log "!! Rack application returned nil body. Probably you wanted it to be an empty string?" if @response.body.nil?
100
+
101
+ # Make the response persistent if requested by the client
102
+ @response.persistent! if @request.persistent?
103
+
104
+ # Send the response
105
+ @response.each do |chunk|
106
+ trace { chunk }
107
+ send_data chunk
108
+ end
109
+
110
+ rescue Exception
111
+ handle_error
112
+ ensure
113
+ # If the body is being deferred, then terminate afterward.
114
+ if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
115
+ @response.body.callback { terminate_request }
116
+ @response.body.errback { terminate_request }
117
+ else
118
+ # Don't terminate the response if we're going async.
119
+ terminate_request unless result && result.first == AsyncResponse.first
120
+ end
121
+ end
122
+
123
+ # Logs catched exception and closes the connection.
124
+ def handle_error
125
+ log "!! Unexpected error while processing request: #{$!.message}"
126
+ log_error
127
+ close_connection rescue nil
128
+ end
129
+
130
+ def close_request_response
131
+ @request.async_close.succeed if @request.async_close
132
+ @request.close rescue nil
133
+ @response.close rescue nil
134
+ end
135
+
136
+ # Does request and response cleanup (closes open IO streams and
137
+ # deletes created temporary files).
138
+ # Re-initializes response and request if client supports persistent
139
+ # connection.
140
+ def terminate_request
141
+ unless persistent?
142
+ close_connection_after_writing rescue nil
143
+ close_request_response
144
+ else
145
+ close_request_response
146
+ # Prepare the connection for another request if the client
147
+ # supports HTTP pipelining (persistent connection).
148
+ post_init
149
+ end
150
+ end
151
+
152
+ # Called when the connection is unbinded from the socket
153
+ # and can no longer be used to process requests.
154
+ def unbind
155
+ @request.async_close.succeed if @request.async_close
156
+ @response.body.fail if @response.body.respond_to?(:fail)
157
+ @backend.connection_finished(self)
158
+ end
159
+
160
+ # Allows this connection to be persistent.
161
+ def can_persist!
162
+ @can_persist = true
163
+ end
164
+
165
+ # Return +true+ if this connection is allowed to stay open and be persistent.
166
+ def can_persist?
167
+ @can_persist
168
+ end
169
+
170
+ # Return +true+ if the connection must be left open
171
+ # and ready to be reused for another request.
172
+ def persistent?
173
+ @can_persist && @response.persistent?
174
+ end
175
+
176
+ # +true+ if <tt>app.call</tt> will be called inside a thread.
177
+ # You can set all requests as threaded setting <tt>Connection#threaded=true</tt>
178
+ # or on a per-request case returning +true+ in <tt>app.deferred?</tt>.
179
+ def threaded?
180
+ @threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
181
+ end
182
+
183
+ # IP Address of the remote client.
184
+ def remote_address
185
+ socket_address
186
+ rescue Exception
187
+ log_error
188
+ nil
189
+ end
190
+
191
+ protected
192
+
193
+ # Returns IP address of peer as a string.
194
+ def socket_address
195
+ Socket.unpack_sockaddr_in(get_peername)[1]
196
+ end
197
+
198
+ private
199
+ def need_content_length?(result)
200
+ status, headers, body = result
201
+ return false if status == -1
202
+ return false if headers.has_key?(CONTENT_LENGTH)
203
+ return false if (100..199).include?(status) || status == 204 || status == 304
204
+ return false if headers.has_key?(TRANSFER_ENCODING) && headers[TRANSFER_ENCODING] =~ CHUNKED_REGEXP
205
+ return false unless body.kind_of?(String) || body.kind_of?(Array)
206
+ true
207
+ end
208
+
209
+ def set_content_length(result)
210
+ headers, body = result[1..2]
211
+ case body
212
+ when String
213
+ # See http://redmine.ruby-lang.org/issues/show/203
214
+ headers[CONTENT_LENGTH] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
215
+ when Array
216
+ bytes = 0
217
+ body.each do |p|
218
+ bytes += p.respond_to?(:bytesize) ? p.bytesize : p.size
219
+ end
220
+ headers[CONTENT_LENGTH] = bytes.to_s
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,178 @@
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