giga-smart-mod 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/giga-smart-mod.gemspec +12 -0
- data/thin-2.0.1/CHANGELOG +422 -0
- data/thin-2.0.1/README.md +88 -0
- data/thin-2.0.1/Rakefile +22 -0
- data/thin-2.0.1/bin/thin +6 -0
- data/thin-2.0.1/example/adapter.rb +32 -0
- data/thin-2.0.1/example/async_app.ru +126 -0
- data/thin-2.0.1/example/async_chat.ru +247 -0
- data/thin-2.0.1/example/async_tailer.ru +100 -0
- data/thin-2.0.1/example/config.ru +22 -0
- data/thin-2.0.1/example/monit_sockets +20 -0
- data/thin-2.0.1/example/monit_unixsock +20 -0
- data/thin-2.0.1/example/myapp.rb +1 -0
- data/thin-2.0.1/example/ramaze.ru +12 -0
- data/thin-2.0.1/example/thin.god +80 -0
- data/thin-2.0.1/example/thin_solaris_smf.erb +36 -0
- data/thin-2.0.1/example/thin_solaris_smf.readme.txt +150 -0
- data/thin-2.0.1/example/vlad.rake +72 -0
- data/thin-2.0.1/ext/thin_parser/common.rl +59 -0
- data/thin-2.0.1/ext/thin_parser/ext_help.h +14 -0
- data/thin-2.0.1/ext/thin_parser/extconf.rb +6 -0
- data/thin-2.0.1/ext/thin_parser/parser.c +1447 -0
- data/thin-2.0.1/ext/thin_parser/parser.h +49 -0
- data/thin-2.0.1/ext/thin_parser/parser.rl +152 -0
- data/thin-2.0.1/ext/thin_parser/thin.c +435 -0
- data/thin-2.0.1/lib/rack/adapter/loader.rb +82 -0
- data/thin-2.0.1/lib/rack/adapter/rails.rb +172 -0
- data/thin-2.0.1/lib/rack/handler/thin.rb +13 -0
- data/thin-2.0.1/lib/rackup/handler/thin.rb +13 -0
- data/thin-2.0.1/lib/thin/backends/base.rb +169 -0
- data/thin-2.0.1/lib/thin/backends/swiftiply_client.rb +66 -0
- data/thin-2.0.1/lib/thin/backends/tcp_server.rb +34 -0
- data/thin-2.0.1/lib/thin/backends/unix_server.rb +56 -0
- data/thin-2.0.1/lib/thin/command.rb +53 -0
- data/thin-2.0.1/lib/thin/connection.rb +219 -0
- data/thin-2.0.1/lib/thin/controllers/cluster.rb +178 -0
- data/thin-2.0.1/lib/thin/controllers/controller.rb +189 -0
- data/thin-2.0.1/lib/thin/controllers/service.rb +76 -0
- data/thin-2.0.1/lib/thin/controllers/service.sh.erb +39 -0
- data/thin-2.0.1/lib/thin/daemonizing.rb +199 -0
- data/thin-2.0.1/lib/thin/env.rb +35 -0
- data/thin-2.0.1/lib/thin/headers.rb +47 -0
- data/thin-2.0.1/lib/thin/logging.rb +174 -0
- data/thin-2.0.1/lib/thin/rackup/handler.rb +33 -0
- data/thin-2.0.1/lib/thin/request.rb +159 -0
- data/thin-2.0.1/lib/thin/response.rb +144 -0
- data/thin-2.0.1/lib/thin/runner.rb +238 -0
- data/thin-2.0.1/lib/thin/server.rb +290 -0
- data/thin-2.0.1/lib/thin/stats.html.erb +216 -0
- data/thin-2.0.1/lib/thin/stats.rb +52 -0
- data/thin-2.0.1/lib/thin/statuses.rb +48 -0
- data/thin-2.0.1/lib/thin/version.rb +19 -0
- data/thin-2.0.1/lib/thin.rb +45 -0
- metadata +94 -0
|
@@ -0,0 +1,219 @@
|
|
|
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 @backend.respond_to?(:port)
|
|
76
|
+
@request.env['SERVER_PORT'] = @backend.port.to_s
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if cert = get_peer_cert
|
|
80
|
+
@request.env['rack.peer_cert'] = cert
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# When we're under a non-async framework like rails, we can still spawn
|
|
85
|
+
# off async responses using the callback info, so there's little point
|
|
86
|
+
# in removing this.
|
|
87
|
+
response = AsyncResponse
|
|
88
|
+
catch(:async) do
|
|
89
|
+
# Process the request calling the Rack adapter
|
|
90
|
+
response = @app.call(@request.env)
|
|
91
|
+
end
|
|
92
|
+
response
|
|
93
|
+
rescue Exception => e
|
|
94
|
+
unexpected_error(e)
|
|
95
|
+
# Pass through error response
|
|
96
|
+
can_persist? && @request.persistent? ? Response::PERSISTENT_ERROR : Response::ERROR
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def post_process(result)
|
|
100
|
+
return unless result
|
|
101
|
+
result = result.to_a
|
|
102
|
+
|
|
103
|
+
# Status code -1 indicates that we're going to respond later (async).
|
|
104
|
+
return if result.first == AsyncResponse.first
|
|
105
|
+
|
|
106
|
+
@response.status, @response.headers, @response.body = *result
|
|
107
|
+
|
|
108
|
+
log_error("Rack application returned nil body. " \
|
|
109
|
+
"Probably you wanted it to be an empty string?") if @response.body.nil?
|
|
110
|
+
|
|
111
|
+
# HEAD requests should not return a body.
|
|
112
|
+
@response.skip_body! if @request.head?
|
|
113
|
+
|
|
114
|
+
# Make the response persistent if requested by the client
|
|
115
|
+
@response.persistent! if @request.persistent?
|
|
116
|
+
|
|
117
|
+
# Send the response
|
|
118
|
+
@response.each do |chunk|
|
|
119
|
+
trace chunk
|
|
120
|
+
send_data chunk
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
rescue Exception => e
|
|
124
|
+
unexpected_error(e)
|
|
125
|
+
# Close connection since we can't handle response gracefully
|
|
126
|
+
close_connection
|
|
127
|
+
ensure
|
|
128
|
+
# If the body is being deferred, then terminate afterward.
|
|
129
|
+
if @response.body.respond_to?(:callback) && @response.body.respond_to?(:errback)
|
|
130
|
+
@response.body.callback { terminate_request }
|
|
131
|
+
@response.body.errback { terminate_request }
|
|
132
|
+
else
|
|
133
|
+
# Don't terminate the response if we're going async.
|
|
134
|
+
terminate_request unless result && result.first == AsyncResponse.first
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Logs information about an unexpected exceptional condition
|
|
139
|
+
def unexpected_error(e)
|
|
140
|
+
log_error("Unexpected error while processing request", e)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def close_request_response
|
|
144
|
+
@request.async_close.succeed if @request.async_close
|
|
145
|
+
@request.close rescue nil
|
|
146
|
+
@response.close rescue nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Does request and response cleanup (closes open IO streams and
|
|
150
|
+
# deletes created temporary files).
|
|
151
|
+
# Re-initializes response and request if client supports persistent
|
|
152
|
+
# connection.
|
|
153
|
+
def terminate_request
|
|
154
|
+
unless persistent?
|
|
155
|
+
close_connection_after_writing rescue nil
|
|
156
|
+
close_request_response
|
|
157
|
+
else
|
|
158
|
+
close_request_response
|
|
159
|
+
# Connection become idle but it's still open
|
|
160
|
+
@idle = true
|
|
161
|
+
# Prepare the connection for another request if the client
|
|
162
|
+
# supports HTTP pipelining (persistent connection).
|
|
163
|
+
post_init
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Called when the connection is unbinded from the socket
|
|
168
|
+
# and can no longer be used to process requests.
|
|
169
|
+
def unbind
|
|
170
|
+
@request.async_close.succeed if @request.async_close
|
|
171
|
+
@response.body.fail if @response.body.respond_to?(:fail)
|
|
172
|
+
@backend.connection_finished(self)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Allows this connection to be persistent.
|
|
176
|
+
def can_persist!
|
|
177
|
+
@can_persist = true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Return +true+ if this connection is allowed to stay open and be persistent.
|
|
181
|
+
def can_persist?
|
|
182
|
+
@can_persist
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Return +true+ if the connection must be left open
|
|
186
|
+
# and ready to be reused for another request.
|
|
187
|
+
def persistent?
|
|
188
|
+
@can_persist && @response.persistent?
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Return +true+ if the connection is open but is not
|
|
192
|
+
# processing any user requests
|
|
193
|
+
def idle?
|
|
194
|
+
@idle
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# +true+ if <tt>app.call</tt> will be called inside a thread.
|
|
198
|
+
# You can set all requests as threaded setting <tt>Connection#threaded=true</tt>
|
|
199
|
+
# or on a per-request case returning +true+ in <tt>app.deferred?</tt>.
|
|
200
|
+
def threaded?
|
|
201
|
+
@threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# IP Address of the remote client.
|
|
205
|
+
def remote_address
|
|
206
|
+
socket_address
|
|
207
|
+
rescue Exception => e
|
|
208
|
+
log_error('Could not infer remote address', e)
|
|
209
|
+
nil
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
protected
|
|
213
|
+
# Returns IP address of peer as a string.
|
|
214
|
+
def socket_address
|
|
215
|
+
peer = get_peername
|
|
216
|
+
Socket.unpack_sockaddr_in(peer)[1] if peer
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
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
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Thin
|
|
4
|
+
# Error raised that will abort the process and print not backtrace.
|
|
5
|
+
class RunnerError < RuntimeError; end
|
|
6
|
+
|
|
7
|
+
# Raised when a mandatory option is missing to run a command.
|
|
8
|
+
class OptionRequired < RunnerError
|
|
9
|
+
def initialize(option)
|
|
10
|
+
super("#{option} option required")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Raised when an option is not valid.
|
|
15
|
+
class InvalidOption < RunnerError; end
|
|
16
|
+
|
|
17
|
+
# Build and control Thin servers.
|
|
18
|
+
# Hey Controller pattern is not only for web apps yo!
|
|
19
|
+
module Controllers
|
|
20
|
+
# Controls one Thin server.
|
|
21
|
+
# Allow to start, stop, restart and configure a single thin server.
|
|
22
|
+
class Controller
|
|
23
|
+
include Logging
|
|
24
|
+
|
|
25
|
+
# Command line options passed to the thin script
|
|
26
|
+
attr_accessor :options
|
|
27
|
+
|
|
28
|
+
def initialize(options)
|
|
29
|
+
@options = options
|
|
30
|
+
|
|
31
|
+
if @options[:socket]
|
|
32
|
+
@options.delete(:address)
|
|
33
|
+
@options.delete(:port)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def start
|
|
38
|
+
# Constantize backend class
|
|
39
|
+
@options[:backend] = eval(@options[:backend], TOPLEVEL_BINDING) if @options[:backend]
|
|
40
|
+
|
|
41
|
+
server = Server.new(@options[:socket] || @options[:address], # Server detects kind of socket
|
|
42
|
+
@options[:port], # Port ignored on UNIX socket
|
|
43
|
+
@options)
|
|
44
|
+
|
|
45
|
+
# Set options
|
|
46
|
+
server.pid_file = @options[:pid]
|
|
47
|
+
server.log_file = @options[:log]
|
|
48
|
+
server.timeout = @options[:timeout]
|
|
49
|
+
server.maximum_connections = @options[:max_conns]
|
|
50
|
+
server.maximum_persistent_connections = @options[:max_persistent_conns]
|
|
51
|
+
server.threaded = @options[:threaded]
|
|
52
|
+
server.no_epoll = @options[:no_epoll] if server.backend.respond_to?(:no_epoll=)
|
|
53
|
+
server.threadpool_size = @options[:threadpool_size] if server.threaded?
|
|
54
|
+
|
|
55
|
+
# ssl support
|
|
56
|
+
if @options[:ssl]
|
|
57
|
+
server.ssl = true
|
|
58
|
+
server.ssl_options = { :private_key_file => @options[:ssl_key_file], :cert_chain_file => @options[:ssl_cert_file], :verify_peer => !@options[:ssl_disable_verify], :ssl_version => @options[:ssl_version], :cipher_list => @options[:ssl_cipher_list]}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Detach the process, after this line the current process returns
|
|
62
|
+
server.daemonize if @options[:daemonize]
|
|
63
|
+
|
|
64
|
+
# +config+ must be called before changing privileges since it might require superuser power.
|
|
65
|
+
server.config
|
|
66
|
+
|
|
67
|
+
server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
|
|
68
|
+
|
|
69
|
+
# If a Rack config file is specified we eval it inside a Rack::Builder block to create
|
|
70
|
+
# a Rack adapter from it. Or else we guess which adapter to use and load it.
|
|
71
|
+
if @options[:rackup]
|
|
72
|
+
server.app = load_rackup_config
|
|
73
|
+
else
|
|
74
|
+
server.app = load_adapter
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# If a prefix is required, wrap in Rack URL mapper
|
|
78
|
+
server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
|
|
79
|
+
|
|
80
|
+
# If a stats URL is specified, wrap in Stats adapter
|
|
81
|
+
server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
|
|
82
|
+
|
|
83
|
+
# Register restart procedure which just start another process with same options,
|
|
84
|
+
# so that's why this is done here.
|
|
85
|
+
server.on_restart { Command.run(:start, @options) }
|
|
86
|
+
|
|
87
|
+
server.start
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def stop
|
|
91
|
+
raise OptionRequired, :pid unless @options[:pid]
|
|
92
|
+
|
|
93
|
+
tail_log(@options[:log]) do
|
|
94
|
+
if Server.kill(@options[:pid], @options[:force] ? 0 : (@options[:timeout] || 60))
|
|
95
|
+
wait_for_file :deletion, @options[:pid]
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def restart
|
|
101
|
+
raise OptionRequired, :pid unless @options[:pid]
|
|
102
|
+
|
|
103
|
+
tail_log(@options[:log]) do
|
|
104
|
+
if Server.restart(@options[:pid])
|
|
105
|
+
wait_for_file :creation, @options[:pid]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def config
|
|
111
|
+
config_file = @options.delete(:config) || raise(OptionRequired, :config)
|
|
112
|
+
|
|
113
|
+
# Stringify keys
|
|
114
|
+
@options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
|
|
115
|
+
|
|
116
|
+
File.open(config_file, 'w') { |f| f << @options.to_yaml }
|
|
117
|
+
log_info "Wrote configuration to #{config_file}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
protected
|
|
121
|
+
# Wait for a pid file to either be created or deleted.
|
|
122
|
+
def wait_for_file(state, file)
|
|
123
|
+
Timeout.timeout(@options[:timeout] || 30) do
|
|
124
|
+
case state
|
|
125
|
+
when :creation then sleep 0.1 until File.exist?(file)
|
|
126
|
+
when :deletion then sleep 0.1 while File.exist?(file)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Tail the log file of server +number+ during the execution of the block.
|
|
132
|
+
def tail_log(log_file)
|
|
133
|
+
if log_file
|
|
134
|
+
tail_thread = tail(log_file)
|
|
135
|
+
yield
|
|
136
|
+
tail_thread.kill
|
|
137
|
+
else
|
|
138
|
+
yield
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Acts like GNU tail command. Taken from Rails.
|
|
143
|
+
def tail(file)
|
|
144
|
+
cursor = File.exist?(file) ? File.size(file) : 0
|
|
145
|
+
last_checked = Time.now
|
|
146
|
+
tail_thread = Thread.new do
|
|
147
|
+
Thread.pass until File.exist?(file)
|
|
148
|
+
File.open(file, 'r') do |f|
|
|
149
|
+
loop do
|
|
150
|
+
f.seek cursor
|
|
151
|
+
if f.mtime > last_checked
|
|
152
|
+
last_checked = f.mtime
|
|
153
|
+
contents = f.read
|
|
154
|
+
cursor += contents.length
|
|
155
|
+
print contents
|
|
156
|
+
STDOUT.flush
|
|
157
|
+
end
|
|
158
|
+
sleep 0.1
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
|
|
163
|
+
tail_thread
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
def load_adapter
|
|
168
|
+
adapter = @options[:adapter] || Rack::Adapter.guess(@options[:chdir])
|
|
169
|
+
log_info "Using #{adapter} adapter"
|
|
170
|
+
Rack::Adapter.for(adapter, @options)
|
|
171
|
+
rescue Rack::AdapterNotFound => e
|
|
172
|
+
raise InvalidOption, e.message
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def load_rackup_config
|
|
176
|
+
ENV['RACK_ENV'] = @options[:environment]
|
|
177
|
+
case @options[:rackup]
|
|
178
|
+
when /\.rb$/
|
|
179
|
+
Kernel.load(@options[:rackup])
|
|
180
|
+
Object.const_get(File.basename(@options[:rackup], '.rb').capitalize.to_sym)
|
|
181
|
+
when /\.ru$/
|
|
182
|
+
Rack::Adapter.load(@options[:rackup])
|
|
183
|
+
else
|
|
184
|
+
raise "Invalid rackup file. please specify either a .ru or .rb file"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
|
|
3
|
+
module Thin
|
|
4
|
+
module Controllers
|
|
5
|
+
# System service controller to launch all servers which
|
|
6
|
+
# config files are in a directory.
|
|
7
|
+
class Service < Controller
|
|
8
|
+
INITD_PATH = File.directory?('/etc/rc.d') ? '/etc/rc.d/thin' : '/etc/init.d/thin'
|
|
9
|
+
DEFAULT_CONFIG_PATH = '/etc/thin'
|
|
10
|
+
TEMPLATE = File.dirname(__FILE__) + '/service.sh.erb'
|
|
11
|
+
|
|
12
|
+
def initialize(options)
|
|
13
|
+
super
|
|
14
|
+
|
|
15
|
+
raise PlatformNotSupported, 'Running as a service only supported on Linux' unless Thin.linux?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def config_path
|
|
19
|
+
@options[:all] || DEFAULT_CONFIG_PATH
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def start
|
|
23
|
+
run :start
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def stop
|
|
27
|
+
run :stop
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def restart
|
|
31
|
+
run :restart
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def install(config_files_path=DEFAULT_CONFIG_PATH)
|
|
35
|
+
if File.exist?(INITD_PATH)
|
|
36
|
+
log_info "Thin service already installed at #{INITD_PATH}"
|
|
37
|
+
else
|
|
38
|
+
log_info "Installing thin service at #{INITD_PATH} ..."
|
|
39
|
+
sh "mkdir -p #{File.dirname(INITD_PATH)}"
|
|
40
|
+
log_info "writing #{INITD_PATH}"
|
|
41
|
+
File.open(INITD_PATH, 'w') do |f|
|
|
42
|
+
f << ERB.new(File.read(TEMPLATE)).result(binding)
|
|
43
|
+
end
|
|
44
|
+
sh "chmod +x #{INITD_PATH}" # Make executable
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sh "mkdir -p #{config_files_path}"
|
|
48
|
+
|
|
49
|
+
log_info ''
|
|
50
|
+
log_info "To configure thin to start at system boot:"
|
|
51
|
+
log_info "on RedHat like systems:"
|
|
52
|
+
log_info " sudo /sbin/chkconfig --level 345 #{NAME} on"
|
|
53
|
+
log_info "on Debian-like systems (Ubuntu):"
|
|
54
|
+
log_info " sudo /usr/sbin/update-rc.d -f #{NAME} defaults"
|
|
55
|
+
log_info "on Gentoo:"
|
|
56
|
+
log_info " sudo rc-update add #{NAME} default"
|
|
57
|
+
log_info ''
|
|
58
|
+
log_info "Then put your config files in #{config_files_path}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
def run(command)
|
|
63
|
+
Dir[config_path + '/*'].each do |config|
|
|
64
|
+
next if config.end_with?("~")
|
|
65
|
+
log_info "[#{command}] #{config} ..."
|
|
66
|
+
Command.run(command, :config => config, :daemonize => true)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def sh(cmd)
|
|
71
|
+
log_info cmd
|
|
72
|
+
system(cmd)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
### BEGIN INIT INFO
|
|
3
|
+
# Provides: thin
|
|
4
|
+
# Required-Start: $local_fs $remote_fs
|
|
5
|
+
# Required-Stop: $local_fs $remote_fs
|
|
6
|
+
# Default-Start: 2 3 4 5
|
|
7
|
+
# Default-Stop: S 0 1 6
|
|
8
|
+
# Short-Description: thin initscript
|
|
9
|
+
# Description: thin
|
|
10
|
+
### END INIT INFO
|
|
11
|
+
|
|
12
|
+
# Original author: Forrest Robertson
|
|
13
|
+
|
|
14
|
+
# Do NOT "set -e"
|
|
15
|
+
|
|
16
|
+
DAEMON=<%= Command.script %>
|
|
17
|
+
SCRIPT_NAME=<%= INITD_PATH %>
|
|
18
|
+
CONFIG_PATH=<%= config_files_path %>
|
|
19
|
+
|
|
20
|
+
# Exit if the package is not installed
|
|
21
|
+
[ -x "$DAEMON" ] || exit 0
|
|
22
|
+
|
|
23
|
+
case "$1" in
|
|
24
|
+
start)
|
|
25
|
+
$DAEMON start --all $CONFIG_PATH
|
|
26
|
+
;;
|
|
27
|
+
stop)
|
|
28
|
+
$DAEMON stop --all $CONFIG_PATH
|
|
29
|
+
;;
|
|
30
|
+
restart)
|
|
31
|
+
$DAEMON restart --all $CONFIG_PATH
|
|
32
|
+
;;
|
|
33
|
+
*)
|
|
34
|
+
echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
|
|
35
|
+
exit 3
|
|
36
|
+
;;
|
|
37
|
+
esac
|
|
38
|
+
|
|
39
|
+
:
|