gross 1.7.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +410 -0
- data/README.md +102 -0
- data/Rakefile +25 -0
- data/bin/thin +6 -0
- data/example/adapter.rb +32 -0
- data/example/async_app.ru +126 -0
- data/example/async_chat.ru +247 -0
- data/example/async_tailer.ru +100 -0
- data/example/config.ru +22 -0
- data/example/monit_sockets +20 -0
- data/example/monit_unixsock +20 -0
- data/example/myapp.rb +1 -0
- data/example/ramaze.ru +12 -0
- data/example/thin.god +80 -0
- data/example/thin_solaris_smf.erb +36 -0
- data/example/thin_solaris_smf.readme.txt +150 -0
- data/example/vlad.rake +72 -0
- data/ext/thin_parser/common.rl +59 -0
- data/ext/thin_parser/ext_help.h +14 -0
- data/ext/thin_parser/extconf.rb +6 -0
- data/ext/thin_parser/parser.c +1447 -0
- data/ext/thin_parser/parser.h +49 -0
- data/ext/thin_parser/parser.rl +152 -0
- data/ext/thin_parser/thin.c +435 -0
- data/lib/rack/adapter/loader.rb +75 -0
- data/lib/rack/adapter/rails.rb +178 -0
- data/lib/thin.rb +45 -0
- data/lib/thin/backends/base.rb +167 -0
- data/lib/thin/backends/swiftiply_client.rb +56 -0
- data/lib/thin/backends/tcp_server.rb +34 -0
- data/lib/thin/backends/unix_server.rb +56 -0
- data/lib/thin/command.rb +53 -0
- data/lib/thin/connection.rb +215 -0
- data/lib/thin/controllers/cluster.rb +178 -0
- data/lib/thin/controllers/controller.rb +189 -0
- data/lib/thin/controllers/service.rb +76 -0
- data/lib/thin/controllers/service.sh.erb +39 -0
- data/lib/thin/daemonizing.rb +180 -0
- data/lib/thin/headers.rb +40 -0
- data/lib/thin/logging.rb +174 -0
- data/lib/thin/request.rb +162 -0
- data/lib/thin/response.rb +117 -0
- data/lib/thin/runner.rb +238 -0
- data/lib/thin/server.rb +290 -0
- data/lib/thin/stats.html.erb +216 -0
- data/lib/thin/stats.rb +52 -0
- data/lib/thin/statuses.rb +44 -0
- data/lib/thin/version.rb +32 -0
- metadata +156 -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,34 @@
|
|
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
|
+
binary_name = EventMachine.get_sockname( @signature )
|
18
|
+
port_name = Socket.unpack_sockaddr_in( binary_name )
|
19
|
+
@port = port_name[0]
|
20
|
+
@host = port_name[1]
|
21
|
+
@signature
|
22
|
+
end
|
23
|
+
|
24
|
+
# Stops the server
|
25
|
+
def disconnect
|
26
|
+
EventMachine.stop_server(@signature)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"#{@host}:#{@port}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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
|
+
old_umask = File.umask(0)
|
18
|
+
begin
|
19
|
+
EventMachine.start_unix_domain_server(@socket, UnixConnection, &method(:initialize_connection))
|
20
|
+
# HACK EventMachine.start_unix_domain_server doesn't return the connection signature
|
21
|
+
# so we have to go in the internal stuff to find it.
|
22
|
+
@signature = EventMachine.instance_eval{@acceptors.keys.first}
|
23
|
+
ensure
|
24
|
+
File.umask(old_umask)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Stops the server
|
29
|
+
def disconnect
|
30
|
+
EventMachine.stop_server(@signature)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Free up resources used by the backend.
|
34
|
+
def close
|
35
|
+
remove_socket_file
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
@socket
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
def remove_socket_file
|
44
|
+
File.delete(@socket) if @socket && File.exist?(@socket)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Connection through a UNIX domain socket.
|
50
|
+
class UnixConnection < Connection
|
51
|
+
protected
|
52
|
+
def socket_address
|
53
|
+
'127.0.0.1' # Unix domain sockets can only be local
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/thin/command.rb
ADDED
@@ -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_info stdout.gets until stdout.eof?
|
30
|
+
log_info 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,215 @@
|
|
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
|
+
include Logging
|
9
|
+
|
10
|
+
# This is a template async response. N.B. Can't use string for body on 1.9
|
11
|
+
AsyncResponse = [-1, {}, []].freeze
|
12
|
+
|
13
|
+
# Rack application (adapter) served by this connection.
|
14
|
+
attr_accessor :app
|
15
|
+
|
16
|
+
# Backend to the server
|
17
|
+
attr_accessor :backend
|
18
|
+
|
19
|
+
# Current request served by the connection
|
20
|
+
attr_accessor :request
|
21
|
+
|
22
|
+
# Next response sent through the connection
|
23
|
+
attr_accessor :response
|
24
|
+
|
25
|
+
# Calling the application in a threaded allowing
|
26
|
+
# concurrent processing of requests.
|
27
|
+
attr_writer :threaded
|
28
|
+
|
29
|
+
# Get the connection ready to process a request.
|
30
|
+
def post_init
|
31
|
+
@request = Request.new
|
32
|
+
@response = Response.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Called when data is received from the client.
|
36
|
+
def receive_data(data)
|
37
|
+
@idle = false
|
38
|
+
trace data
|
39
|
+
process if @request.parse(data)
|
40
|
+
rescue InvalidRequest => e
|
41
|
+
log_error("Invalid request", e)
|
42
|
+
post_process Response::BAD_REQUEST
|
43
|
+
end
|
44
|
+
|
45
|
+
# Called when all data was received and the request
|
46
|
+
# is ready to be processed.
|
47
|
+
def process
|
48
|
+
if threaded?
|
49
|
+
@request.threaded = true
|
50
|
+
EventMachine.defer { post_process(pre_process) }
|
51
|
+
else
|
52
|
+
@request.threaded = false
|
53
|
+
post_process(pre_process)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def ssl_verify_peer(cert)
|
58
|
+
# In order to make the cert available later we have to have made at least
|
59
|
+
# a show of verifying it.
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def pre_process
|
64
|
+
# Add client info to the request env
|
65
|
+
@request.remote_address = remote_address
|
66
|
+
|
67
|
+
# Connection may be closed unless the App#call response was a [-1, ...]
|
68
|
+
# It should be noted that connection objects will linger until this
|
69
|
+
# callback is no longer referenced, so be tidy!
|
70
|
+
@request.async_callback = method(:post_process)
|
71
|
+
|
72
|
+
if @backend.ssl?
|
73
|
+
@request.env["rack.url_scheme"] = "https"
|
74
|
+
|
75
|
+
if cert = get_peer_cert
|
76
|
+
@request.env['rack.peer_cert'] = cert
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# When we're under a non-async framework like rails, we can still spawn
|
81
|
+
# off async responses using the callback info, so there's little point
|
82
|
+
# in removing this.
|
83
|
+
response = AsyncResponse
|
84
|
+
catch(:async) do
|
85
|
+
# Process the request calling the Rack adapter
|
86
|
+
response = @app.call(@request.env)
|
87
|
+
end
|
88
|
+
response
|
89
|
+
rescue Exception => e
|
90
|
+
unexpected_error(e)
|
91
|
+
# Pass through error response
|
92
|
+
can_persist? && @request.persistent? ? Response::PERSISTENT_ERROR : Response::ERROR
|
93
|
+
end
|
94
|
+
|
95
|
+
def post_process(result)
|
96
|
+
return unless result
|
97
|
+
result = result.to_a
|
98
|
+
|
99
|
+
# Status code -1 indicates that we're going to respond later (async).
|
100
|
+
return if result.first == AsyncResponse.first
|
101
|
+
|
102
|
+
@response.status, @response.headers, @response.body = *result
|
103
|
+
|
104
|
+
log_error("Rack application returned nil body. " \
|
105
|
+
"Probably you wanted it to be an empty string?") if @response.body.nil?
|
106
|
+
|
107
|
+
# HEAD requests should not return a body.
|
108
|
+
@response.skip_body! if @request.head?
|
109
|
+
|
110
|
+
# Make the response persistent if requested by the client
|
111
|
+
@response.persistent! if @request.persistent?
|
112
|
+
|
113
|
+
# Send the response
|
114
|
+
@response.each do |chunk|
|
115
|
+
trace chunk
|
116
|
+
send_data chunk
|
117
|
+
end
|
118
|
+
|
119
|
+
rescue Exception => e
|
120
|
+
unexpected_error(e)
|
121
|
+
# Close connection since we can't handle response gracefully
|
122
|
+
close_connection
|
123
|
+
ensure
|
124
|
+
# If the body is being deferred, then terminate afterward.
|
125
|
+
if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
|
126
|
+
@response.body.callback { terminate_request }
|
127
|
+
@response.body.errback { terminate_request }
|
128
|
+
else
|
129
|
+
# Don't terminate the response if we're going async.
|
130
|
+
terminate_request unless result && result.first == AsyncResponse.first
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Logs information about an unexpected exceptional condition
|
135
|
+
def unexpected_error(e)
|
136
|
+
log_error("Unexpected error while processing request", e)
|
137
|
+
end
|
138
|
+
|
139
|
+
def close_request_response
|
140
|
+
@request.async_close.succeed if @request.async_close
|
141
|
+
@request.close rescue nil
|
142
|
+
@response.close rescue nil
|
143
|
+
end
|
144
|
+
|
145
|
+
# Does request and response cleanup (closes open IO streams and
|
146
|
+
# deletes created temporary files).
|
147
|
+
# Re-initializes response and request if client supports persistent
|
148
|
+
# connection.
|
149
|
+
def terminate_request
|
150
|
+
unless persistent?
|
151
|
+
close_connection_after_writing rescue nil
|
152
|
+
close_request_response
|
153
|
+
else
|
154
|
+
close_request_response
|
155
|
+
# Connection become idle but it's still open
|
156
|
+
@idle = true
|
157
|
+
# Prepare the connection for another request if the client
|
158
|
+
# supports HTTP pipelining (persistent connection).
|
159
|
+
post_init
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Called when the connection is unbinded from the socket
|
164
|
+
# and can no longer be used to process requests.
|
165
|
+
def unbind
|
166
|
+
@request.async_close.succeed if @request.async_close
|
167
|
+
@response.body.fail if @response.body.respond_to?(:fail)
|
168
|
+
@backend.connection_finished(self)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Allows this connection to be persistent.
|
172
|
+
def can_persist!
|
173
|
+
@can_persist = true
|
174
|
+
end
|
175
|
+
|
176
|
+
# Return +true+ if this connection is allowed to stay open and be persistent.
|
177
|
+
def can_persist?
|
178
|
+
@can_persist
|
179
|
+
end
|
180
|
+
|
181
|
+
# Return +true+ if the connection must be left open
|
182
|
+
# and ready to be reused for another request.
|
183
|
+
def persistent?
|
184
|
+
@can_persist && @response.persistent?
|
185
|
+
end
|
186
|
+
|
187
|
+
# Return +true+ if the connection is open but is not
|
188
|
+
# processing any user requests
|
189
|
+
def idle?
|
190
|
+
@idle
|
191
|
+
end
|
192
|
+
|
193
|
+
# +true+ if <tt>app.call</tt> will be called inside a thread.
|
194
|
+
# You can set all requests as threaded setting <tt>Connection#threaded=true</tt>
|
195
|
+
# or on a per-request case returning +true+ in <tt>app.deferred?</tt>.
|
196
|
+
def threaded?
|
197
|
+
@threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
|
198
|
+
end
|
199
|
+
|
200
|
+
# IP Address of the remote client.
|
201
|
+
def remote_address
|
202
|
+
socket_address
|
203
|
+
rescue Exception => e
|
204
|
+
log_error('Could not infer remote address', e)
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
|
208
|
+
protected
|
209
|
+
# Returns IP address of peer as a string.
|
210
|
+
def socket_address
|
211
|
+
peer = get_peername
|
212
|
+
Socket.unpack_sockaddr_in(peer)[1] if peer
|
213
|
+
end
|
214
|
+
end
|
215
|
+
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_info "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_info "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_info "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
|