rsense-server 0.5.0
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/.gitignore +23 -0
- data/Gemfile +14 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +1 -0
- data/README.md +51 -0
- data/Rakefile +9 -0
- data/bin/_rsense.rb +115 -0
- data/config/puma.rb +2 -0
- data/lib/rsense/server/code.rb +38 -0
- data/lib/rsense/server/command/completion_result.rb +11 -0
- data/lib/rsense/server/command/special_meth.rb +18 -0
- data/lib/rsense/server/command/type_inference_method.rb +24 -0
- data/lib/rsense/server/command.rb +239 -0
- data/lib/rsense/server/config.rb +70 -0
- data/lib/rsense/server/gem_path.rb +18 -0
- data/lib/rsense/server/listeners/find_definition_event_listener.rb +91 -0
- data/lib/rsense/server/listeners/where_event_listener.rb +39 -0
- data/lib/rsense/server/load_path.rb +62 -0
- data/lib/rsense/server/options.rb +85 -0
- data/lib/rsense/server/parser.rb +17 -0
- data/lib/rsense/server/path_info.rb +17 -0
- data/lib/rsense/server/project.rb +24 -0
- data/lib/rsense/server/version.rb +5 -0
- data/lib/rsense/server.rb +18 -0
- data/rsense-server.gemspec +35 -0
- data/spec/fixtures/config_fixture/.rsense +4 -0
- data/spec/fixtures/deeply/nested/thing.rb +0 -0
- data/spec/fixtures/find_def_sample.json +10 -0
- data/spec/fixtures/sample.json +10 -0
- data/spec/fixtures/test_gem/.gitignore +22 -0
- data/spec/fixtures/test_gem/Gemfile +4 -0
- data/spec/fixtures/test_gem/LICENSE.txt +22 -0
- data/spec/fixtures/test_gem/README.md +29 -0
- data/spec/fixtures/test_gem/Rakefile +2 -0
- data/spec/fixtures/test_gem/lib/sample/version.rb +3 -0
- data/spec/fixtures/test_gem/lib/sample.rb +16 -0
- data/spec/fixtures/test_gem/sample.gemspec +23 -0
- data/spec/fixtures/test_gem/test.json +10 -0
- data/spec/rsense/server/code_spec.rb +44 -0
- data/spec/rsense/server/command/special_meth_spec.rb +23 -0
- data/spec/rsense/server/command_spec.rb +108 -0
- data/spec/rsense/server/config_spec.rb +27 -0
- data/spec/rsense/server/gem_path_spec.rb +16 -0
- data/spec/rsense/server/load_path_spec.rb +63 -0
- data/spec/rsense/server/options_spec.rb +33 -0
- data/spec/rsense/server/path_info_spec.rb +11 -0
- data/spec/rsense/server/project_spec.rb +18 -0
- data/spec/rsense/server_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/vendor/gems/puma-2.8.2-java/COPYING +55 -0
- data/vendor/gems/puma-2.8.2-java/DEPLOYMENT.md +92 -0
- data/vendor/gems/puma-2.8.2-java/Gemfile +17 -0
- data/vendor/gems/puma-2.8.2-java/History.txt +532 -0
- data/vendor/gems/puma-2.8.2-java/LICENSE +26 -0
- data/vendor/gems/puma-2.8.2-java/Manifest.txt +68 -0
- data/vendor/gems/puma-2.8.2-java/README.md +251 -0
- data/vendor/gems/puma-2.8.2-java/Rakefile +158 -0
- data/vendor/gems/puma-2.8.2-java/bin/puma +10 -0
- data/vendor/gems/puma-2.8.2-java/bin/puma-wild +17 -0
- data/vendor/gems/puma-2.8.2-java/bin/pumactl +12 -0
- data/vendor/gems/puma-2.8.2-java/docs/config.md +0 -0
- data/vendor/gems/puma-2.8.2-java/docs/nginx.md +80 -0
- data/vendor/gems/puma-2.8.2-java/docs/signals.md +42 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/ext_help.h +15 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/extconf.rb +8 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser.c +1225 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser.h +64 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser.java.rl +161 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser.rl +146 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/http11_parser_common.rl +54 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/io_buffer.c +155 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/mini_ssl.c +195 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/org/jruby/puma/Http11.java +225 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/org/jruby/puma/Http11Parser.java +488 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/org/jruby/puma/MiniSSL.java +289 -0
- data/vendor/gems/puma-2.8.2-java/ext/puma_http11/puma_http11.c +491 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/accept_nonblock.rb +23 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/app/status.rb +59 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/binder.rb +298 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/capistrano.rb +86 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/cli.rb +587 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/client.rb +289 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/cluster.rb +389 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/compat.rb +18 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/configuration.rb +377 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/const.rb +165 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/control_cli.rb +251 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/daemon_ext.rb +25 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/delegation.rb +11 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/detect.rb +4 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/events.rb +130 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/io_buffer.rb +7 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/java_io_buffer.rb +45 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/jruby_restart.rb +83 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/minissl.rb +148 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/null_io.rb +34 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/puma_http11.jar +0 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/rack_default.rb +7 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/rack_patch.rb +45 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/reactor.rb +183 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/runner.rb +146 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/server.rb +801 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/single.rb +102 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/tcp_logger.rb +32 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/thread_pool.rb +185 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma/util.rb +9 -0
- data/vendor/gems/puma-2.8.2-java/lib/puma.rb +14 -0
- data/vendor/gems/puma-2.8.2-java/lib/rack/handler/puma.rb +66 -0
- data/vendor/gems/puma-2.8.2-java/puma.gemspec +55 -0
- data/vendor/gems/puma-2.8.2-java/test/test_app_status.rb +92 -0
- data/vendor/gems/puma-2.8.2-java/test/test_cli.rb +173 -0
- data/vendor/gems/puma-2.8.2-java/test/test_config.rb +26 -0
- data/vendor/gems/puma-2.8.2-java/test/test_http10.rb +27 -0
- data/vendor/gems/puma-2.8.2-java/test/test_http11.rb +144 -0
- data/vendor/gems/puma-2.8.2-java/test/test_integration.rb +165 -0
- data/vendor/gems/puma-2.8.2-java/test/test_iobuffer.rb +38 -0
- data/vendor/gems/puma-2.8.2-java/test/test_minissl.rb +25 -0
- data/vendor/gems/puma-2.8.2-java/test/test_null_io.rb +31 -0
- data/vendor/gems/puma-2.8.2-java/test/test_persistent.rb +238 -0
- data/vendor/gems/puma-2.8.2-java/test/test_puma_server.rb +323 -0
- data/vendor/gems/puma-2.8.2-java/test/test_rack_handler.rb +10 -0
- data/vendor/gems/puma-2.8.2-java/test/test_rack_server.rb +141 -0
- data/vendor/gems/puma-2.8.2-java/test/test_tcp_rack.rb +42 -0
- data/vendor/gems/puma-2.8.2-java/test/test_thread_pool.rb +156 -0
- data/vendor/gems/puma-2.8.2-java/test/test_unix_socket.rb +39 -0
- data/vendor/gems/puma-2.8.2-java/test/test_ws.rb +89 -0
- data/vendor/gems/puma-2.8.2-java/tools/jungle/README.md +9 -0
- data/vendor/gems/puma-2.8.2-java/tools/jungle/init.d/README.md +54 -0
- data/vendor/gems/puma-2.8.2-java/tools/jungle/init.d/puma +332 -0
- data/vendor/gems/puma-2.8.2-java/tools/jungle/init.d/run-puma +3 -0
- data/vendor/gems/puma-2.8.2-java/tools/jungle/upstart/README.md +61 -0
- data/vendor/gems/puma-2.8.2-java/tools/jungle/upstart/puma-manager.conf +31 -0
- data/vendor/gems/puma-2.8.2-java/tools/jungle/upstart/puma.conf +63 -0
- data/vendor/gems/puma-2.8.2-java/tools/trickletest.rb +45 -0
- metadata +389 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
class IO
|
|
2
|
+
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
|
3
|
+
# So this either creates the constant (on 1.8), or harmlessly
|
|
4
|
+
# reopens it (on 1.9).
|
|
5
|
+
module WaitReadable
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require 'puma/detect'
|
|
10
|
+
|
|
11
|
+
if Puma::IS_JRUBY
|
|
12
|
+
# We have to work around some OpenSSL buffer/io-readiness bugs
|
|
13
|
+
# so we pull it in regardless of if the user is binding
|
|
14
|
+
# to an SSL socket
|
|
15
|
+
require 'openssl'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module Puma
|
|
19
|
+
|
|
20
|
+
class ConnectionError < RuntimeError; end
|
|
21
|
+
|
|
22
|
+
class Client
|
|
23
|
+
include Puma::Const
|
|
24
|
+
|
|
25
|
+
def initialize(io, env=nil)
|
|
26
|
+
@io = io
|
|
27
|
+
@to_io = io.to_io
|
|
28
|
+
@proto_env = env
|
|
29
|
+
if !env
|
|
30
|
+
@env = nil
|
|
31
|
+
else
|
|
32
|
+
@env = env.dup
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@parser = HttpParser.new
|
|
36
|
+
@parsed_bytes = 0
|
|
37
|
+
@read_header = true
|
|
38
|
+
@ready = false
|
|
39
|
+
|
|
40
|
+
@body = nil
|
|
41
|
+
@buffer = nil
|
|
42
|
+
|
|
43
|
+
@timeout_at = nil
|
|
44
|
+
|
|
45
|
+
@requests_served = 0
|
|
46
|
+
@hijacked = false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked
|
|
50
|
+
|
|
51
|
+
def inspect
|
|
52
|
+
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# For the hijack protocol (allows us to just put the Client object
|
|
56
|
+
# into the env)
|
|
57
|
+
def call
|
|
58
|
+
@hijacked = true
|
|
59
|
+
env[HIJACK_IO] ||= @io
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def in_data_phase
|
|
63
|
+
!@read_header
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def set_timeout(val)
|
|
67
|
+
@timeout_at = Time.now + val
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def reset(fast_check=true)
|
|
71
|
+
@parser.reset
|
|
72
|
+
@read_header = true
|
|
73
|
+
@env = @proto_env.dup
|
|
74
|
+
@body = nil
|
|
75
|
+
@parsed_bytes = 0
|
|
76
|
+
@ready = false
|
|
77
|
+
|
|
78
|
+
if @buffer
|
|
79
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
|
80
|
+
|
|
81
|
+
if @parser.finished?
|
|
82
|
+
return setup_body
|
|
83
|
+
elsif @parsed_bytes >= MAX_HEADER
|
|
84
|
+
raise HttpParserError,
|
|
85
|
+
"HEADER is longer than allowed, aborting client early."
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
return false
|
|
89
|
+
elsif fast_check &&
|
|
90
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
|
91
|
+
return try_to_finish
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def close
|
|
96
|
+
begin
|
|
97
|
+
@io.close
|
|
98
|
+
rescue IOError
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The object used for a request with no body. All requests with
|
|
103
|
+
# no body share this one object since it has no state.
|
|
104
|
+
EmptyBody = NullIO.new
|
|
105
|
+
|
|
106
|
+
def setup_body
|
|
107
|
+
@in_data_phase = true
|
|
108
|
+
body = @parser.body
|
|
109
|
+
cl = @env[CONTENT_LENGTH]
|
|
110
|
+
|
|
111
|
+
unless cl
|
|
112
|
+
@buffer = body.empty? ? nil : body
|
|
113
|
+
@body = EmptyBody
|
|
114
|
+
@requests_served += 1
|
|
115
|
+
@ready = true
|
|
116
|
+
return true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
remain = cl.to_i - body.bytesize
|
|
120
|
+
|
|
121
|
+
if remain <= 0
|
|
122
|
+
@body = StringIO.new(body)
|
|
123
|
+
@buffer = nil
|
|
124
|
+
@requests_served += 1
|
|
125
|
+
@ready = true
|
|
126
|
+
return true
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
if remain > MAX_BODY
|
|
130
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
|
131
|
+
@body.binmode
|
|
132
|
+
else
|
|
133
|
+
# The body[0,0] trick is to get an empty string in the same
|
|
134
|
+
# encoding as body.
|
|
135
|
+
@body = StringIO.new body[0,0]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
@body.write body
|
|
139
|
+
|
|
140
|
+
@body_remain = remain
|
|
141
|
+
|
|
142
|
+
@read_header = false
|
|
143
|
+
|
|
144
|
+
return false
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def try_to_finish
|
|
148
|
+
return read_body unless @read_header
|
|
149
|
+
|
|
150
|
+
begin
|
|
151
|
+
data = @io.read_nonblock(CHUNK_SIZE)
|
|
152
|
+
rescue Errno::EAGAIN
|
|
153
|
+
return false
|
|
154
|
+
rescue SystemCallError, IOError
|
|
155
|
+
raise ConnectionError, "Connection error detected during read"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if @buffer
|
|
159
|
+
@buffer << data
|
|
160
|
+
else
|
|
161
|
+
@buffer = data
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
|
165
|
+
|
|
166
|
+
if @parser.finished?
|
|
167
|
+
return setup_body
|
|
168
|
+
elsif @parsed_bytes >= MAX_HEADER
|
|
169
|
+
raise HttpParserError,
|
|
170
|
+
"HEADER is longer than allowed, aborting client early."
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
false
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if IS_JRUBY
|
|
177
|
+
def jruby_start_try_to_finish
|
|
178
|
+
return read_body unless @read_header
|
|
179
|
+
|
|
180
|
+
begin
|
|
181
|
+
data = @io.sysread_nonblock(CHUNK_SIZE)
|
|
182
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
183
|
+
return false if e.kind_of? IO::WaitReadable
|
|
184
|
+
raise e
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if @buffer
|
|
188
|
+
@buffer << data
|
|
189
|
+
else
|
|
190
|
+
@buffer = data
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
|
194
|
+
|
|
195
|
+
if @parser.finished?
|
|
196
|
+
return setup_body
|
|
197
|
+
elsif @parsed_bytes >= MAX_HEADER
|
|
198
|
+
raise HttpParserError,
|
|
199
|
+
"HEADER is longer than allowed, aborting client early."
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
false
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def eagerly_finish
|
|
206
|
+
return true if @ready
|
|
207
|
+
|
|
208
|
+
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
|
209
|
+
return true if jruby_start_try_to_finish
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
|
213
|
+
try_to_finish
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
else
|
|
217
|
+
|
|
218
|
+
def eagerly_finish
|
|
219
|
+
return true if @ready
|
|
220
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
|
221
|
+
try_to_finish
|
|
222
|
+
end
|
|
223
|
+
end # IS_JRUBY
|
|
224
|
+
|
|
225
|
+
def read_body
|
|
226
|
+
# Read an odd sized chunk so we can read even sized ones
|
|
227
|
+
# after this
|
|
228
|
+
remain = @body_remain
|
|
229
|
+
|
|
230
|
+
if remain > CHUNK_SIZE
|
|
231
|
+
want = CHUNK_SIZE
|
|
232
|
+
else
|
|
233
|
+
want = remain
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
begin
|
|
237
|
+
chunk = @io.read_nonblock(want)
|
|
238
|
+
rescue Errno::EAGAIN
|
|
239
|
+
return false
|
|
240
|
+
rescue SystemCallError, IOError
|
|
241
|
+
raise ConnectionError, "Connection error detected during read"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# No chunk means a closed socket
|
|
245
|
+
unless chunk
|
|
246
|
+
@body.close
|
|
247
|
+
@buffer = nil
|
|
248
|
+
@requests_served += 1
|
|
249
|
+
@ready = true
|
|
250
|
+
raise EOFError
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
remain -= @body.write(chunk)
|
|
254
|
+
|
|
255
|
+
if remain <= 0
|
|
256
|
+
@body.rewind
|
|
257
|
+
@buffer = nil
|
|
258
|
+
@requests_served += 1
|
|
259
|
+
@ready = true
|
|
260
|
+
return true
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
@body_remain = remain
|
|
264
|
+
|
|
265
|
+
false
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def write_400
|
|
269
|
+
begin
|
|
270
|
+
@io << ERROR_400_RESPONSE
|
|
271
|
+
rescue StandardError
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def write_408
|
|
276
|
+
begin
|
|
277
|
+
@io << ERROR_408_RESPONSE
|
|
278
|
+
rescue StandardError
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def write_500
|
|
283
|
+
begin
|
|
284
|
+
@io << ERROR_500_RESPONSE
|
|
285
|
+
rescue StandardError
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
require 'puma/runner'
|
|
2
|
+
|
|
3
|
+
module Puma
|
|
4
|
+
class Cluster < Runner
|
|
5
|
+
def initialize(cli)
|
|
6
|
+
super cli
|
|
7
|
+
|
|
8
|
+
@phase = 0
|
|
9
|
+
@workers = []
|
|
10
|
+
@next_check = nil
|
|
11
|
+
|
|
12
|
+
@phased_state = :idle
|
|
13
|
+
@phased_restart = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def stop_workers
|
|
17
|
+
log "- Gracefully shutting down workers..."
|
|
18
|
+
@workers.each { |x| x.term }
|
|
19
|
+
|
|
20
|
+
begin
|
|
21
|
+
Process.waitall
|
|
22
|
+
rescue Interrupt
|
|
23
|
+
log "! Cancelled waiting for workers"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start_phased_restart
|
|
28
|
+
@phase += 1
|
|
29
|
+
log "- Starting phased worker restart, phase: #{@phase}"
|
|
30
|
+
|
|
31
|
+
# Be sure to change the directory again before loading
|
|
32
|
+
# the app. This way we can pick up new code.
|
|
33
|
+
if dir = @options[:worker_directory]
|
|
34
|
+
log "+ Changing to #{dir}"
|
|
35
|
+
Dir.chdir dir
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Worker
|
|
40
|
+
def initialize(idx, pid, phase)
|
|
41
|
+
@index = idx
|
|
42
|
+
@pid = pid
|
|
43
|
+
@phase = phase
|
|
44
|
+
@stage = :started
|
|
45
|
+
@signal = "TERM"
|
|
46
|
+
@last_checkin = Time.now
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin
|
|
50
|
+
|
|
51
|
+
def booted?
|
|
52
|
+
@stage == :booted
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def boot!
|
|
56
|
+
@last_checkin = Time.now
|
|
57
|
+
@stage = :booted
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ping!
|
|
61
|
+
@last_checkin = Time.now
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ping_timeout?(which)
|
|
65
|
+
Time.now - @last_checkin > which
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def term
|
|
69
|
+
begin
|
|
70
|
+
if @first_term_sent && (Time.new - @first_term_sent) > 30
|
|
71
|
+
@signal = "KILL"
|
|
72
|
+
else
|
|
73
|
+
@first_term_sent ||= Time.new
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
Process.kill @signal, @pid
|
|
77
|
+
rescue Errno::ESRCH
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def kill
|
|
82
|
+
Process.kill "KILL", @pid
|
|
83
|
+
rescue Errno::ESRCH
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def spawn_workers
|
|
88
|
+
diff = @options[:workers] - @workers.size
|
|
89
|
+
|
|
90
|
+
master = Process.pid
|
|
91
|
+
|
|
92
|
+
diff.times do
|
|
93
|
+
idx = next_worker_index
|
|
94
|
+
|
|
95
|
+
pid = fork { worker(idx, master) }
|
|
96
|
+
@cli.debug "Spawned worker: #{pid}"
|
|
97
|
+
@workers << Worker.new(idx, pid, @phase)
|
|
98
|
+
@options[:after_worker_boot].each { |h| h.call }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if diff > 0
|
|
102
|
+
@phased_state = :idle
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def next_worker_index
|
|
107
|
+
all_positions = 0...@options[:workers]
|
|
108
|
+
occupied_positions = @workers.map { |w| w.index }
|
|
109
|
+
available_positions = all_positions.to_a - occupied_positions
|
|
110
|
+
available_positions.first
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def all_workers_booted?
|
|
114
|
+
@workers.count { |w| !w.booted? } == 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def check_workers
|
|
118
|
+
return if @next_check && @next_check >= Time.now
|
|
119
|
+
|
|
120
|
+
@next_check = Time.now + 5
|
|
121
|
+
|
|
122
|
+
any = false
|
|
123
|
+
|
|
124
|
+
@workers.each do |w|
|
|
125
|
+
if w.ping_timeout?(@options[:worker_timeout])
|
|
126
|
+
log "! Terminating timed out worker: #{w.pid}"
|
|
127
|
+
w.kill
|
|
128
|
+
any = true
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# If we killed any timed out workers, try to catch them
|
|
133
|
+
# during this loop by giving the kernel time to kill them.
|
|
134
|
+
sleep 1 if any
|
|
135
|
+
|
|
136
|
+
while @workers.any?
|
|
137
|
+
pid = Process.waitpid(-1, Process::WNOHANG)
|
|
138
|
+
break unless pid
|
|
139
|
+
|
|
140
|
+
@workers.delete_if { |w| w.pid == pid }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
spawn_workers
|
|
144
|
+
|
|
145
|
+
if all_workers_booted?
|
|
146
|
+
# If we're running at proper capacity, check to see if
|
|
147
|
+
# we need to phase any workers out (which will restart
|
|
148
|
+
# in the right phase).
|
|
149
|
+
#
|
|
150
|
+
w = @workers.find { |x| x.phase != @phase }
|
|
151
|
+
|
|
152
|
+
if w
|
|
153
|
+
if @phased_state == :idle
|
|
154
|
+
@phased_state = :waiting
|
|
155
|
+
log "- Stopping #{w.pid} for phased upgrade..."
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
w.term
|
|
159
|
+
log "- #{w.signal} sent to #{w.pid}..."
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def wakeup!
|
|
165
|
+
begin
|
|
166
|
+
@wakeup.write "!" unless @wakeup.closed?
|
|
167
|
+
rescue SystemCallError, IOError
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def worker(index, master)
|
|
172
|
+
$0 = "puma: cluster worker #{index}: #{master}"
|
|
173
|
+
Signal.trap "SIGINT", "IGNORE"
|
|
174
|
+
|
|
175
|
+
@master_read.close
|
|
176
|
+
@suicide_pipe.close
|
|
177
|
+
|
|
178
|
+
Thread.new do
|
|
179
|
+
IO.select [@check_pipe]
|
|
180
|
+
log "! Detected parent died, dying"
|
|
181
|
+
exit! 1
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# If we're not running under a Bundler context, then
|
|
185
|
+
# report the info about the context we will be using
|
|
186
|
+
if !ENV['BUNDLER_GEMFILE'] and File.exist?("Gemfile")
|
|
187
|
+
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Invoke any worker boot hooks so they can get
|
|
191
|
+
# things in shape before booting the app.
|
|
192
|
+
hooks = @options[:before_worker_boot]
|
|
193
|
+
hooks.each { |h| h.call(index) }
|
|
194
|
+
|
|
195
|
+
server = start_server
|
|
196
|
+
|
|
197
|
+
Signal.trap "SIGTERM" do
|
|
198
|
+
server.stop
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
begin
|
|
202
|
+
@worker_write << "b#{Process.pid}\n"
|
|
203
|
+
rescue SystemCallError, IOError
|
|
204
|
+
STDERR.puts "Master seems to have exitted, exitting."
|
|
205
|
+
return
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
Thread.new(@worker_write) do |io|
|
|
209
|
+
payload = "p#{Process.pid}\n"
|
|
210
|
+
|
|
211
|
+
while true
|
|
212
|
+
sleep 5
|
|
213
|
+
io << payload
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
server.run.join
|
|
218
|
+
|
|
219
|
+
ensure
|
|
220
|
+
@worker_write.close
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def restart
|
|
224
|
+
@restart = true
|
|
225
|
+
stop
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def phased_restart
|
|
229
|
+
return false if @options[:preload_app]
|
|
230
|
+
|
|
231
|
+
@phased_restart = true
|
|
232
|
+
wakeup!
|
|
233
|
+
|
|
234
|
+
true
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def stop
|
|
238
|
+
@status = :stop
|
|
239
|
+
wakeup!
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def stop_blocked
|
|
243
|
+
@status = :stop if @status == :run
|
|
244
|
+
wakeup!
|
|
245
|
+
@control.stop(true) if @control
|
|
246
|
+
Process.waitall
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def halt
|
|
250
|
+
@status = :halt
|
|
251
|
+
wakeup!
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def stats
|
|
255
|
+
%Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{@workers.count{|w| w.booted?}} }!
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def preload?
|
|
259
|
+
@options[:preload_app]
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def run
|
|
263
|
+
@status = :run
|
|
264
|
+
|
|
265
|
+
output_header "cluster"
|
|
266
|
+
|
|
267
|
+
log "* Process workers: #{@options[:workers]}"
|
|
268
|
+
|
|
269
|
+
if preload?
|
|
270
|
+
log "* Preloading application"
|
|
271
|
+
load_and_bind
|
|
272
|
+
else
|
|
273
|
+
log "* Phased restart available"
|
|
274
|
+
|
|
275
|
+
unless @cli.config.app_configured?
|
|
276
|
+
error "No application configured, nothing to run"
|
|
277
|
+
exit 1
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
@cli.binder.parse @options[:binds], self
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
read, @wakeup = Puma::Util.pipe
|
|
284
|
+
|
|
285
|
+
Signal.trap "SIGCHLD" do
|
|
286
|
+
wakeup!
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
Signal.trap "TTIN" do
|
|
290
|
+
@options[:workers] += 1
|
|
291
|
+
wakeup!
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
Signal.trap "TTOU" do
|
|
295
|
+
@options[:workers] -= 1 if @options[:workers] >= 2
|
|
296
|
+
@workers.last.term
|
|
297
|
+
wakeup!
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
master_pid = Process.pid
|
|
301
|
+
|
|
302
|
+
Signal.trap "SIGTERM" do
|
|
303
|
+
# The worker installs their own SIGTERM when booted.
|
|
304
|
+
# Until then, this is run by the worker and the worker
|
|
305
|
+
# should just exit if they get it.
|
|
306
|
+
if Process.pid != master_pid
|
|
307
|
+
log "Early termination of worker"
|
|
308
|
+
exit! 0
|
|
309
|
+
else
|
|
310
|
+
stop
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Used by the workers to detect if the master process dies.
|
|
315
|
+
# If select says that @check_pipe is ready, it's because the
|
|
316
|
+
# master has exited and @suicide_pipe has been automatically
|
|
317
|
+
# closed.
|
|
318
|
+
#
|
|
319
|
+
@check_pipe, @suicide_pipe = Puma::Util.pipe
|
|
320
|
+
|
|
321
|
+
if daemon?
|
|
322
|
+
log "* Daemonizing..."
|
|
323
|
+
Process.daemon(true)
|
|
324
|
+
else
|
|
325
|
+
log "Use Ctrl-C to stop"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
redirect_io
|
|
329
|
+
|
|
330
|
+
start_control
|
|
331
|
+
|
|
332
|
+
@cli.write_state
|
|
333
|
+
|
|
334
|
+
@master_read, @worker_write = read, @wakeup
|
|
335
|
+
spawn_workers
|
|
336
|
+
|
|
337
|
+
Signal.trap "SIGINT" do
|
|
338
|
+
stop
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
@cli.events.fire_on_booted!
|
|
342
|
+
|
|
343
|
+
begin
|
|
344
|
+
while @status == :run
|
|
345
|
+
begin
|
|
346
|
+
res = IO.select([read], nil, nil, 5)
|
|
347
|
+
|
|
348
|
+
if res
|
|
349
|
+
req = read.read_nonblock(1)
|
|
350
|
+
|
|
351
|
+
next if !req || req == "!"
|
|
352
|
+
|
|
353
|
+
pid = read.gets.to_i
|
|
354
|
+
|
|
355
|
+
if w = @workers.find { |x| x.pid == pid }
|
|
356
|
+
case req
|
|
357
|
+
when "b"
|
|
358
|
+
w.boot!
|
|
359
|
+
log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
|
|
360
|
+
when "p"
|
|
361
|
+
w.ping!
|
|
362
|
+
end
|
|
363
|
+
else
|
|
364
|
+
log "! Out-of-sync worker list, no #{pid} worker"
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
if @phased_restart
|
|
369
|
+
start_phased_restart
|
|
370
|
+
@phased_restart = false
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
check_workers
|
|
374
|
+
|
|
375
|
+
rescue Interrupt
|
|
376
|
+
@status = :stop
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
stop_workers unless @status == :halt
|
|
381
|
+
ensure
|
|
382
|
+
@check_pipe.close
|
|
383
|
+
@suicide_pipe.close
|
|
384
|
+
read.close
|
|
385
|
+
@wakeup.close
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Provides code to work properly on 1.8 and 1.9
|
|
2
|
+
|
|
3
|
+
class String
|
|
4
|
+
unless method_defined? :bytesize
|
|
5
|
+
alias_method :bytesize, :size
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
unless method_defined? :byteslice
|
|
9
|
+
if RUBY_VERSION < '1.9'
|
|
10
|
+
alias_method :byteslice, :[]
|
|
11
|
+
else
|
|
12
|
+
def byteslice(*arg)
|
|
13
|
+
enc = self.encoding
|
|
14
|
+
self.dup.force_encoding(Encoding::ASCII_8BIT).slice(*arg).force_encoding(enc)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|