puma 2.16.0 → 3.11.4
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 +5 -5
- data/{History.txt → History.md} +489 -70
- data/README.md +143 -174
- data/docs/architecture.md +36 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +1 -1
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +28 -0
- data/docs/restart.md +39 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +272 -0
- data/ext/puma_http11/extconf.rb +2 -0
- data/ext/puma_http11/http11_parser.c +291 -447
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.java.rl +5 -5
- data/ext/puma_http11/http11_parser.rl +10 -9
- data/ext/puma_http11/http11_parser_common.rl +1 -1
- data/ext/puma_http11/io_buffer.c +8 -8
- data/ext/puma_http11/mini_ssl.c +64 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +113 -131
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +9 -2
- data/ext/puma_http11/puma_http11.c +1 -0
- data/lib/puma/app/status.rb +9 -1
- data/lib/puma/binder.rb +90 -38
- data/lib/puma/cli.rb +134 -491
- data/lib/puma/client.rb +142 -4
- data/lib/puma/cluster.rb +132 -76
- data/lib/puma/commonlogger.rb +19 -20
- data/lib/puma/compat.rb +3 -7
- data/lib/puma/configuration.rb +206 -67
- data/lib/puma/const.rb +21 -31
- data/lib/puma/control_cli.rb +92 -103
- data/lib/puma/convenient.rb +23 -0
- data/lib/puma/daemon_ext.rb +6 -0
- data/lib/puma/detect.rb +10 -1
- data/lib/puma/dsl.rb +203 -45
- data/lib/puma/events.rb +22 -13
- data/lib/puma/io_buffer.rb +1 -1
- data/lib/puma/jruby_restart.rb +1 -2
- data/lib/puma/launcher.rb +431 -0
- data/lib/puma/minissl.rb +83 -4
- data/lib/puma/null_io.rb +19 -11
- data/lib/puma/plugin/tmp_restart.rb +34 -0
- data/lib/puma/plugin.rb +115 -0
- data/lib/puma/rack/backports/uri/common_193.rb +17 -13
- data/lib/puma/rack/builder.rb +3 -0
- data/lib/puma/rack/urlmap.rb +9 -8
- data/lib/puma/reactor.rb +18 -0
- data/lib/puma/runner.rb +43 -15
- data/lib/puma/server.rb +141 -35
- data/lib/puma/single.rb +16 -6
- data/lib/puma/state_file.rb +29 -0
- data/lib/puma/tcp_logger.rb +8 -1
- data/lib/puma/thread_pool.rb +60 -10
- data/lib/puma/util.rb +1 -5
- data/lib/puma.rb +13 -4
- data/lib/rack/handler/puma.rb +76 -29
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +9 -2
- data/tools/jungle/init.d/puma +86 -59
- data/tools/jungle/init.d/run-puma +16 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/jungle/upstart/puma.conf +1 -1
- data/tools/trickletest.rb +1 -1
- metadata +28 -95
- data/COPYING +0 -55
- data/Gemfile +0 -13
- data/Manifest.txt +0 -74
- data/Rakefile +0 -158
- data/docs/config.md +0 -0
- data/lib/puma/capistrano.rb +0 -94
- data/lib/puma/rack/backports/uri/common_18.rb +0 -56
- data/lib/puma/rack/backports/uri/common_192.rb +0 -52
- data/puma.gemspec +0 -52
data/lib/puma/client.rb
CHANGED
|
@@ -7,6 +7,8 @@ class IO
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
require 'puma/detect'
|
|
10
|
+
require 'puma/delegation'
|
|
11
|
+
require 'tempfile'
|
|
10
12
|
|
|
11
13
|
if Puma::IS_JRUBY
|
|
12
14
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
|
@@ -21,6 +23,7 @@ module Puma
|
|
|
21
23
|
|
|
22
24
|
class Client
|
|
23
25
|
include Puma::Const
|
|
26
|
+
extend Puma::Delegation
|
|
24
27
|
|
|
25
28
|
def initialize(io, env=nil)
|
|
26
29
|
@io = io
|
|
@@ -57,6 +60,8 @@ module Puma
|
|
|
57
60
|
|
|
58
61
|
attr_accessor :remote_addr_header
|
|
59
62
|
|
|
63
|
+
forward :closed?, :@io
|
|
64
|
+
|
|
60
65
|
def inspect
|
|
61
66
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
|
62
67
|
end
|
|
@@ -106,6 +111,7 @@ module Puma
|
|
|
106
111
|
begin
|
|
107
112
|
@io.close
|
|
108
113
|
rescue IOError
|
|
114
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
109
115
|
end
|
|
110
116
|
end
|
|
111
117
|
|
|
@@ -113,9 +119,123 @@ module Puma
|
|
|
113
119
|
# no body share this one object since it has no state.
|
|
114
120
|
EmptyBody = NullIO.new
|
|
115
121
|
|
|
122
|
+
def setup_chunked_body(body)
|
|
123
|
+
@chunked_body = true
|
|
124
|
+
@partial_part_left = 0
|
|
125
|
+
@prev_chunk = ""
|
|
126
|
+
|
|
127
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
|
128
|
+
@body.binmode
|
|
129
|
+
@tempfile = @body
|
|
130
|
+
|
|
131
|
+
return decode_chunk(body)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def decode_chunk(chunk)
|
|
135
|
+
if @partial_part_left > 0
|
|
136
|
+
if @partial_part_left <= chunk.size
|
|
137
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
|
138
|
+
chunk = chunk[@partial_part_left..-1]
|
|
139
|
+
else
|
|
140
|
+
@body << chunk
|
|
141
|
+
@partial_part_left -= chunk.size
|
|
142
|
+
return false
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if @prev_chunk.empty?
|
|
147
|
+
io = StringIO.new(chunk)
|
|
148
|
+
else
|
|
149
|
+
io = StringIO.new(@prev_chunk+chunk)
|
|
150
|
+
@prev_chunk = ""
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
while !io.eof?
|
|
154
|
+
line = io.gets
|
|
155
|
+
if line.end_with?("\r\n")
|
|
156
|
+
len = line.strip.to_i(16)
|
|
157
|
+
if len == 0
|
|
158
|
+
@body.rewind
|
|
159
|
+
rest = io.read
|
|
160
|
+
@buffer = rest.empty? ? nil : rest
|
|
161
|
+
@requests_served += 1
|
|
162
|
+
@ready = true
|
|
163
|
+
return true
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
len += 2
|
|
167
|
+
|
|
168
|
+
part = io.read(len)
|
|
169
|
+
|
|
170
|
+
unless part
|
|
171
|
+
@partial_part_left = len
|
|
172
|
+
next
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
got = part.size
|
|
176
|
+
|
|
177
|
+
case
|
|
178
|
+
when got == len
|
|
179
|
+
@body << part[0..-3] # to skip the ending \r\n
|
|
180
|
+
when got <= len - 2
|
|
181
|
+
@body << part
|
|
182
|
+
@partial_part_left = len - part.size
|
|
183
|
+
when got == len - 1 # edge where we get just \r but not \n
|
|
184
|
+
@body << part[0..-2]
|
|
185
|
+
@partial_part_left = len - part.size
|
|
186
|
+
end
|
|
187
|
+
else
|
|
188
|
+
@prev_chunk = line
|
|
189
|
+
return false
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
return false
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def read_chunked_body
|
|
197
|
+
while true
|
|
198
|
+
begin
|
|
199
|
+
chunk = @io.read_nonblock(4096)
|
|
200
|
+
rescue Errno::EAGAIN
|
|
201
|
+
return false
|
|
202
|
+
rescue SystemCallError, IOError
|
|
203
|
+
raise ConnectionError, "Connection error detected during read"
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# No chunk means a closed socket
|
|
207
|
+
unless chunk
|
|
208
|
+
@body.close
|
|
209
|
+
@buffer = nil
|
|
210
|
+
@requests_served += 1
|
|
211
|
+
@ready = true
|
|
212
|
+
raise EOFError
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
return true if decode_chunk(chunk)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
116
219
|
def setup_body
|
|
117
|
-
@
|
|
220
|
+
if @env[HTTP_EXPECT] == CONTINUE
|
|
221
|
+
# TODO allow a hook here to check the headers before
|
|
222
|
+
# going forward
|
|
223
|
+
@io << HTTP_11_100
|
|
224
|
+
@io.flush
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
@read_header = false
|
|
228
|
+
|
|
118
229
|
body = @parser.body
|
|
230
|
+
|
|
231
|
+
te = @env[TRANSFER_ENCODING2]
|
|
232
|
+
|
|
233
|
+
if te && CHUNKED.casecmp(te) == 0
|
|
234
|
+
return setup_chunked_body(body)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
@chunked_body = false
|
|
238
|
+
|
|
119
239
|
cl = @env[CONTENT_LENGTH]
|
|
120
240
|
|
|
121
241
|
unless cl
|
|
@@ -150,8 +270,6 @@ module Puma
|
|
|
150
270
|
|
|
151
271
|
@body_remain = remain
|
|
152
272
|
|
|
153
|
-
@read_header = false
|
|
154
|
-
|
|
155
273
|
return false
|
|
156
274
|
end
|
|
157
275
|
|
|
@@ -166,6 +284,14 @@ module Puma
|
|
|
166
284
|
raise ConnectionError, "Connection error detected during read"
|
|
167
285
|
end
|
|
168
286
|
|
|
287
|
+
# No data means a closed socket
|
|
288
|
+
unless data
|
|
289
|
+
@buffer = nil
|
|
290
|
+
@requests_served += 1
|
|
291
|
+
@ready = true
|
|
292
|
+
raise EOFError
|
|
293
|
+
end
|
|
294
|
+
|
|
169
295
|
if @buffer
|
|
170
296
|
@buffer << data
|
|
171
297
|
else
|
|
@@ -180,7 +306,7 @@ module Puma
|
|
|
180
306
|
raise HttpParserError,
|
|
181
307
|
"HEADER is longer than allowed, aborting client early."
|
|
182
308
|
end
|
|
183
|
-
|
|
309
|
+
|
|
184
310
|
false
|
|
185
311
|
end
|
|
186
312
|
|
|
@@ -195,6 +321,14 @@ module Puma
|
|
|
195
321
|
raise e
|
|
196
322
|
end
|
|
197
323
|
|
|
324
|
+
# No data means a closed socket
|
|
325
|
+
unless data
|
|
326
|
+
@buffer = nil
|
|
327
|
+
@requests_served += 1
|
|
328
|
+
@ready = true
|
|
329
|
+
raise EOFError
|
|
330
|
+
end
|
|
331
|
+
|
|
198
332
|
if @buffer
|
|
199
333
|
@buffer << data
|
|
200
334
|
else
|
|
@@ -242,6 +376,10 @@ module Puma
|
|
|
242
376
|
end
|
|
243
377
|
|
|
244
378
|
def read_body
|
|
379
|
+
if @chunked_body
|
|
380
|
+
return read_chunked_body
|
|
381
|
+
end
|
|
382
|
+
|
|
245
383
|
# Read an odd sized chunk so we can read even sized ones
|
|
246
384
|
# after this
|
|
247
385
|
remain = @body_remain
|
data/lib/puma/cluster.rb
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
require 'puma/runner'
|
|
2
|
+
require 'puma/util'
|
|
3
|
+
require 'puma/plugin'
|
|
4
|
+
|
|
5
|
+
require 'time'
|
|
2
6
|
|
|
3
7
|
module Puma
|
|
4
8
|
class Cluster < Runner
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
WORKER_CHECK_INTERVAL = 5
|
|
10
|
+
|
|
11
|
+
def initialize(cli, events)
|
|
12
|
+
super cli, events
|
|
7
13
|
|
|
8
14
|
@phase = 0
|
|
9
15
|
@workers = []
|
|
@@ -18,7 +24,7 @@ module Puma
|
|
|
18
24
|
@workers.each { |x| x.term }
|
|
19
25
|
|
|
20
26
|
begin
|
|
21
|
-
Process.
|
|
27
|
+
@workers.each { |w| Process.waitpid(w.pid) }
|
|
22
28
|
rescue Interrupt
|
|
23
29
|
log "! Cancelled waiting for workers"
|
|
24
30
|
end
|
|
@@ -30,10 +36,9 @@ module Puma
|
|
|
30
36
|
|
|
31
37
|
# Be sure to change the directory again before loading
|
|
32
38
|
# the app. This way we can pick up new code.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
end
|
|
39
|
+
dir = @launcher.restart_dir
|
|
40
|
+
log "+ Changing to #{dir}"
|
|
41
|
+
Dir.chdir dir
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
def redirect_io
|
|
@@ -52,9 +57,11 @@ module Puma
|
|
|
52
57
|
@options = options
|
|
53
58
|
@first_term_sent = nil
|
|
54
59
|
@last_checkin = Time.now
|
|
60
|
+
@last_status = '{}'
|
|
61
|
+
@dead = false
|
|
55
62
|
end
|
|
56
63
|
|
|
57
|
-
attr_reader :index, :pid, :phase, :signal, :last_checkin
|
|
64
|
+
attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
|
|
58
65
|
|
|
59
66
|
def booted?
|
|
60
67
|
@stage == :booted
|
|
@@ -73,8 +80,9 @@ module Puma
|
|
|
73
80
|
@dead = true
|
|
74
81
|
end
|
|
75
82
|
|
|
76
|
-
def ping!
|
|
83
|
+
def ping!(status)
|
|
77
84
|
@last_checkin = Time.now
|
|
85
|
+
@last_status = status
|
|
78
86
|
end
|
|
79
87
|
|
|
80
88
|
def ping_timeout?(which)
|
|
@@ -83,10 +91,10 @@ module Puma
|
|
|
83
91
|
|
|
84
92
|
def term
|
|
85
93
|
begin
|
|
86
|
-
if @first_term_sent && (Time.
|
|
94
|
+
if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
|
|
87
95
|
@signal = "KILL"
|
|
88
96
|
else
|
|
89
|
-
@first_term_sent ||= Time.
|
|
97
|
+
@first_term_sent ||= Time.now
|
|
90
98
|
end
|
|
91
99
|
|
|
92
100
|
Process.kill @signal, @pid
|
|
@@ -107,17 +115,25 @@ module Puma
|
|
|
107
115
|
|
|
108
116
|
def spawn_workers
|
|
109
117
|
diff = @options[:workers] - @workers.size
|
|
118
|
+
return if diff < 1
|
|
110
119
|
|
|
111
120
|
master = Process.pid
|
|
112
121
|
|
|
113
122
|
diff.times do
|
|
114
123
|
idx = next_worker_index
|
|
115
|
-
@
|
|
124
|
+
@launcher.config.run_hooks :before_worker_fork, idx
|
|
116
125
|
|
|
117
126
|
pid = fork { worker(idx, master) }
|
|
118
|
-
|
|
127
|
+
if !pid
|
|
128
|
+
log "! Complete inability to spawn new workers detected"
|
|
129
|
+
log "! Seppuku is the only choice."
|
|
130
|
+
exit! 1
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
debug "Spawned worker: #{pid}"
|
|
119
134
|
@workers << Worker.new(idx, pid, @phase, @options)
|
|
120
|
-
|
|
135
|
+
|
|
136
|
+
@launcher.config.run_hooks :after_worker_fork, idx
|
|
121
137
|
end
|
|
122
138
|
|
|
123
139
|
if diff > 0
|
|
@@ -125,6 +141,21 @@ module Puma
|
|
|
125
141
|
end
|
|
126
142
|
end
|
|
127
143
|
|
|
144
|
+
def cull_workers
|
|
145
|
+
diff = @workers.size - @options[:workers]
|
|
146
|
+
return if diff < 1
|
|
147
|
+
|
|
148
|
+
debug "Culling #{diff.inspect} workers"
|
|
149
|
+
|
|
150
|
+
workers_to_cull = @workers[-diff,diff]
|
|
151
|
+
debug "Workers to cull: #{workers_to_cull.inspect}"
|
|
152
|
+
|
|
153
|
+
workers_to_cull.each do |worker|
|
|
154
|
+
log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
|
|
155
|
+
worker.term
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
128
159
|
def next_worker_index
|
|
129
160
|
all_positions = 0...@options[:workers]
|
|
130
161
|
occupied_positions = @workers.map { |w| w.index }
|
|
@@ -139,7 +170,7 @@ module Puma
|
|
|
139
170
|
def check_workers(force=false)
|
|
140
171
|
return if !force && @next_check && @next_check >= Time.now
|
|
141
172
|
|
|
142
|
-
@next_check = Time.now +
|
|
173
|
+
@next_check = Time.now + WORKER_CHECK_INTERVAL
|
|
143
174
|
|
|
144
175
|
any = false
|
|
145
176
|
|
|
@@ -165,6 +196,7 @@ module Puma
|
|
|
165
196
|
|
|
166
197
|
@workers.delete_if(&:dead?)
|
|
167
198
|
|
|
199
|
+
cull_workers
|
|
168
200
|
spawn_workers
|
|
169
201
|
|
|
170
202
|
if all_workers_booted?
|
|
@@ -192,12 +224,13 @@ module Puma
|
|
|
192
224
|
begin
|
|
193
225
|
@wakeup.write "!" unless @wakeup.closed?
|
|
194
226
|
rescue SystemCallError, IOError
|
|
227
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
195
228
|
end
|
|
196
229
|
end
|
|
197
230
|
|
|
198
231
|
def worker(index, master)
|
|
199
|
-
title
|
|
200
|
-
title
|
|
232
|
+
title = "puma: cluster worker #{index}: #{master}"
|
|
233
|
+
title += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
|
|
201
234
|
$0 = title
|
|
202
235
|
|
|
203
236
|
Signal.trap "SIGINT", "IGNORE"
|
|
@@ -214,14 +247,17 @@ module Puma
|
|
|
214
247
|
|
|
215
248
|
# If we're not running under a Bundler context, then
|
|
216
249
|
# report the info about the context we will be using
|
|
217
|
-
if !ENV['BUNDLE_GEMFILE']
|
|
218
|
-
|
|
250
|
+
if !ENV['BUNDLE_GEMFILE']
|
|
251
|
+
if File.exist?("Gemfile")
|
|
252
|
+
log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
|
|
253
|
+
elsif File.exist?("gems.rb")
|
|
254
|
+
log "+ Gemfile in context: #{File.expand_path("gems.rb")}"
|
|
255
|
+
end
|
|
219
256
|
end
|
|
220
257
|
|
|
221
258
|
# Invoke any worker boot hooks so they can get
|
|
222
259
|
# things in shape before booting the app.
|
|
223
|
-
|
|
224
|
-
hooks.each { |h| h.call(index) }
|
|
260
|
+
@launcher.config.run_hooks :before_worker_boot, index
|
|
225
261
|
|
|
226
262
|
server = start_server
|
|
227
263
|
|
|
@@ -232,16 +268,25 @@ module Puma
|
|
|
232
268
|
begin
|
|
233
269
|
@worker_write << "b#{Process.pid}\n"
|
|
234
270
|
rescue SystemCallError, IOError
|
|
271
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
235
272
|
STDERR.puts "Master seems to have exited, exiting."
|
|
236
273
|
return
|
|
237
274
|
end
|
|
238
275
|
|
|
239
276
|
Thread.new(@worker_write) do |io|
|
|
240
|
-
|
|
277
|
+
base_payload = "p#{Process.pid}"
|
|
241
278
|
|
|
242
279
|
while true
|
|
243
|
-
sleep
|
|
244
|
-
|
|
280
|
+
sleep WORKER_CHECK_INTERVAL
|
|
281
|
+
begin
|
|
282
|
+
b = server.backlog || 0
|
|
283
|
+
r = server.running || 0
|
|
284
|
+
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r} }\n!
|
|
285
|
+
io << payload
|
|
286
|
+
rescue IOError
|
|
287
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
|
288
|
+
break
|
|
289
|
+
end
|
|
245
290
|
end
|
|
246
291
|
end
|
|
247
292
|
|
|
@@ -249,8 +294,7 @@ module Puma
|
|
|
249
294
|
|
|
250
295
|
# Invoke any worker shutdown hooks so they can prevent the worker
|
|
251
296
|
# exiting until any background operations are completed
|
|
252
|
-
|
|
253
|
-
hooks.each { |h| h.call(index) }
|
|
297
|
+
@launcher.config.run_hooks :before_worker_shutdown, index
|
|
254
298
|
ensure
|
|
255
299
|
@worker_write << "t#{Process.pid}\n" rescue nil
|
|
256
300
|
@worker_write.close
|
|
@@ -288,22 +332,57 @@ module Puma
|
|
|
288
332
|
end
|
|
289
333
|
|
|
290
334
|
def reload_worker_directory
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
end
|
|
335
|
+
dir = @launcher.restart_dir
|
|
336
|
+
log "+ Changing to #{dir}"
|
|
337
|
+
Dir.chdir dir
|
|
295
338
|
end
|
|
296
339
|
|
|
297
340
|
def stats
|
|
298
341
|
old_worker_count = @workers.count { |w| w.phase != @phase }
|
|
299
342
|
booted_worker_count = @workers.count { |w| w.booted? }
|
|
300
|
-
%Q!{ "
|
|
343
|
+
worker_status = '[' + @workers.map { |w| %Q!{ "pid": #{w.pid}, "index": #{w.index}, "phase": #{w.phase}, "booted": #{w.booted?}, "last_checkin": "#{w.last_checkin.utc.iso8601}", "last_status": #{w.last_status} }!}.join(",") + ']'
|
|
344
|
+
%Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
|
|
301
345
|
end
|
|
302
346
|
|
|
303
347
|
def preload?
|
|
304
348
|
@options[:preload_app]
|
|
305
349
|
end
|
|
306
350
|
|
|
351
|
+
# We do this in a separate method to keep the lambda scope
|
|
352
|
+
# of the signals handlers as small as possible.
|
|
353
|
+
def setup_signals
|
|
354
|
+
Signal.trap "SIGCHLD" do
|
|
355
|
+
wakeup!
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
Signal.trap "TTIN" do
|
|
359
|
+
@options[:workers] += 1
|
|
360
|
+
wakeup!
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
Signal.trap "TTOU" do
|
|
364
|
+
@options[:workers] -= 1 if @options[:workers] >= 2
|
|
365
|
+
wakeup!
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
master_pid = Process.pid
|
|
369
|
+
|
|
370
|
+
Signal.trap "SIGTERM" do
|
|
371
|
+
# The worker installs their own SIGTERM when booted.
|
|
372
|
+
# Until then, this is run by the worker and the worker
|
|
373
|
+
# should just exit if they get it.
|
|
374
|
+
if Process.pid != master_pid
|
|
375
|
+
log "Early termination of worker"
|
|
376
|
+
exit! 0
|
|
377
|
+
else
|
|
378
|
+
stop_workers
|
|
379
|
+
stop
|
|
380
|
+
|
|
381
|
+
raise SignalException, "SIGTERM"
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
307
386
|
def run
|
|
308
387
|
@status = :run
|
|
309
388
|
|
|
@@ -333,44 +412,17 @@ module Puma
|
|
|
333
412
|
else
|
|
334
413
|
log "* Phased restart available"
|
|
335
414
|
|
|
336
|
-
unless @
|
|
415
|
+
unless @launcher.config.app_configured?
|
|
337
416
|
error "No application configured, nothing to run"
|
|
338
417
|
exit 1
|
|
339
418
|
end
|
|
340
419
|
|
|
341
|
-
@
|
|
420
|
+
@launcher.binder.parse @options[:binds], self
|
|
342
421
|
end
|
|
343
422
|
|
|
344
423
|
read, @wakeup = Puma::Util.pipe
|
|
345
424
|
|
|
346
|
-
|
|
347
|
-
wakeup!
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
Signal.trap "TTIN" do
|
|
351
|
-
@options[:workers] += 1
|
|
352
|
-
wakeup!
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
Signal.trap "TTOU" do
|
|
356
|
-
@options[:workers] -= 1 if @options[:workers] >= 2
|
|
357
|
-
@workers.last.term
|
|
358
|
-
wakeup!
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
master_pid = Process.pid
|
|
362
|
-
|
|
363
|
-
Signal.trap "SIGTERM" do
|
|
364
|
-
# The worker installs their own SIGTERM when booted.
|
|
365
|
-
# Until then, this is run by the worker and the worker
|
|
366
|
-
# should just exit if they get it.
|
|
367
|
-
if Process.pid != master_pid
|
|
368
|
-
log "Early termination of worker"
|
|
369
|
-
exit! 0
|
|
370
|
-
else
|
|
371
|
-
stop
|
|
372
|
-
end
|
|
373
|
-
end
|
|
425
|
+
setup_signals
|
|
374
426
|
|
|
375
427
|
# Used by the workers to detect if the master process dies.
|
|
376
428
|
# If select says that @check_pipe is ready, it's because the
|
|
@@ -388,14 +440,15 @@ module Puma
|
|
|
388
440
|
|
|
389
441
|
redirect_io
|
|
390
442
|
|
|
391
|
-
|
|
443
|
+
Plugins.fire_background
|
|
392
444
|
|
|
393
|
-
@
|
|
445
|
+
@launcher.write_state
|
|
446
|
+
|
|
447
|
+
start_control
|
|
394
448
|
|
|
395
449
|
@master_read, @worker_write = read, @wakeup
|
|
396
450
|
|
|
397
|
-
|
|
398
|
-
hooks.each { |h| h.call }
|
|
451
|
+
@launcher.config.run_hooks :before_fork, nil
|
|
399
452
|
|
|
400
453
|
spawn_workers
|
|
401
454
|
|
|
@@ -403,21 +456,31 @@ module Puma
|
|
|
403
456
|
stop
|
|
404
457
|
end
|
|
405
458
|
|
|
406
|
-
@
|
|
459
|
+
@launcher.events.fire_on_booted!
|
|
407
460
|
|
|
408
461
|
begin
|
|
462
|
+
force_check = false
|
|
463
|
+
|
|
409
464
|
while @status == :run
|
|
410
465
|
begin
|
|
411
|
-
|
|
466
|
+
if @phased_restart
|
|
467
|
+
start_phased_restart
|
|
468
|
+
@phased_restart = false
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
check_workers force_check
|
|
412
472
|
|
|
413
473
|
force_check = false
|
|
414
474
|
|
|
475
|
+
res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
|
|
476
|
+
|
|
415
477
|
if res
|
|
416
478
|
req = read.read_nonblock(1)
|
|
417
479
|
|
|
418
480
|
next if !req || req == "!"
|
|
419
481
|
|
|
420
|
-
|
|
482
|
+
result = read.gets
|
|
483
|
+
pid = result.to_i
|
|
421
484
|
|
|
422
485
|
if w = @workers.find { |x| x.pid == pid }
|
|
423
486
|
case req
|
|
@@ -429,20 +492,13 @@ module Puma
|
|
|
429
492
|
w.dead!
|
|
430
493
|
force_check = true
|
|
431
494
|
when "p"
|
|
432
|
-
w.ping!
|
|
495
|
+
w.ping!(result.sub(/^\d+/,'').chomp)
|
|
433
496
|
end
|
|
434
497
|
else
|
|
435
498
|
log "! Out-of-sync worker list, no #{pid} worker"
|
|
436
499
|
end
|
|
437
500
|
end
|
|
438
501
|
|
|
439
|
-
if @phased_restart
|
|
440
|
-
start_phased_restart
|
|
441
|
-
@phased_restart = false
|
|
442
|
-
end
|
|
443
|
-
|
|
444
|
-
check_workers force_check
|
|
445
|
-
|
|
446
502
|
rescue Interrupt
|
|
447
503
|
@status = :stop
|
|
448
504
|
end
|
data/lib/puma/commonlogger.rb
CHANGED
|
@@ -21,6 +21,13 @@ module Puma
|
|
|
21
21
|
# %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
|
|
22
22
|
FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
|
|
23
23
|
|
|
24
|
+
HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
|
|
25
|
+
|
|
26
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
|
27
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
|
28
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
|
29
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
|
30
|
+
|
|
24
31
|
def initialize(app, logger=nil)
|
|
25
32
|
@app = app
|
|
26
33
|
@logger = logger
|
|
@@ -42,36 +49,23 @@ module Puma
|
|
|
42
49
|
[status, header, body]
|
|
43
50
|
end
|
|
44
51
|
|
|
45
|
-
HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
|
|
46
|
-
|
|
47
52
|
private
|
|
48
53
|
|
|
49
54
|
def log_hijacking(env, status, header, began_at)
|
|
50
55
|
now = Time.now
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
logger.write HIJACK_FORMAT % [
|
|
57
|
+
msg = HIJACK_FORMAT % [
|
|
54
58
|
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
|
|
55
59
|
env["REMOTE_USER"] || "-",
|
|
56
60
|
now.strftime("%d/%b/%Y %H:%M:%S"),
|
|
57
|
-
env[
|
|
58
|
-
env[
|
|
59
|
-
env[
|
|
61
|
+
env[REQUEST_METHOD],
|
|
62
|
+
env[PATH_INFO],
|
|
63
|
+
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
|
60
64
|
env["HTTP_VERSION"],
|
|
61
65
|
now - began_at ]
|
|
62
|
-
end
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
|
67
|
-
QUERY_STRING = 'QUERY_STRING'.freeze
|
|
68
|
-
CACHE_CONTROL = 'Cache-Control'.freeze
|
|
69
|
-
CONTENT_LENGTH = 'Content-Length'.freeze
|
|
70
|
-
CONTENT_TYPE = 'Content-Type'.freeze
|
|
71
|
-
|
|
72
|
-
GET = 'GET'.freeze
|
|
73
|
-
HEAD = 'HEAD'.freeze
|
|
74
|
-
|
|
67
|
+
write(msg)
|
|
68
|
+
end
|
|
75
69
|
|
|
76
70
|
def log(env, status, header, began_at)
|
|
77
71
|
now = Time.now
|
|
@@ -83,13 +77,18 @@ module Puma
|
|
|
83
77
|
now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
|
84
78
|
env[REQUEST_METHOD],
|
|
85
79
|
env[PATH_INFO],
|
|
86
|
-
env[QUERY_STRING].empty? ? "" : "
|
|
80
|
+
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
|
87
81
|
env["HTTP_VERSION"],
|
|
88
82
|
status.to_s[0..3],
|
|
89
83
|
length,
|
|
90
84
|
now - began_at ]
|
|
91
85
|
|
|
86
|
+
write(msg)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def write(msg)
|
|
92
90
|
logger = @logger || env['rack.errors']
|
|
91
|
+
|
|
93
92
|
# Standard library logger doesn't support write but it supports << which actually
|
|
94
93
|
# calls to write on the log device without formatting
|
|
95
94
|
if logger.respond_to?(:write)
|
data/lib/puma/compat.rb
CHANGED
|
@@ -6,13 +6,9 @@ class String
|
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
unless method_defined? :byteslice
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def byteslice(*arg)
|
|
13
|
-
enc = self.encoding
|
|
14
|
-
self.dup.force_encoding(Encoding::ASCII_8BIT).slice(*arg).force_encoding(enc)
|
|
15
|
-
end
|
|
9
|
+
def byteslice(*arg)
|
|
10
|
+
enc = self.encoding
|
|
11
|
+
self.dup.force_encoding(Encoding::ASCII_8BIT).slice(*arg).force_encoding(enc)
|
|
16
12
|
end
|
|
17
13
|
end
|
|
18
14
|
end
|