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.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +489 -70
  3. data/README.md +143 -174
  4. data/docs/architecture.md +36 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +1 -1
  6. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  7. data/docs/images/puma-connection-flow.png +0 -0
  8. data/docs/images/puma-general-arch.png +0 -0
  9. data/docs/nginx.md +2 -2
  10. data/docs/plugins.md +28 -0
  11. data/docs/restart.md +39 -0
  12. data/docs/signals.md +56 -3
  13. data/docs/systemd.md +272 -0
  14. data/ext/puma_http11/extconf.rb +2 -0
  15. data/ext/puma_http11/http11_parser.c +291 -447
  16. data/ext/puma_http11/http11_parser.h +1 -0
  17. data/ext/puma_http11/http11_parser.java.rl +5 -5
  18. data/ext/puma_http11/http11_parser.rl +10 -9
  19. data/ext/puma_http11/http11_parser_common.rl +1 -1
  20. data/ext/puma_http11/io_buffer.c +8 -8
  21. data/ext/puma_http11/mini_ssl.c +64 -6
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +113 -131
  23. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +9 -2
  24. data/ext/puma_http11/puma_http11.c +1 -0
  25. data/lib/puma/app/status.rb +9 -1
  26. data/lib/puma/binder.rb +90 -38
  27. data/lib/puma/cli.rb +134 -491
  28. data/lib/puma/client.rb +142 -4
  29. data/lib/puma/cluster.rb +132 -76
  30. data/lib/puma/commonlogger.rb +19 -20
  31. data/lib/puma/compat.rb +3 -7
  32. data/lib/puma/configuration.rb +206 -67
  33. data/lib/puma/const.rb +21 -31
  34. data/lib/puma/control_cli.rb +92 -103
  35. data/lib/puma/convenient.rb +23 -0
  36. data/lib/puma/daemon_ext.rb +6 -0
  37. data/lib/puma/detect.rb +10 -1
  38. data/lib/puma/dsl.rb +203 -45
  39. data/lib/puma/events.rb +22 -13
  40. data/lib/puma/io_buffer.rb +1 -1
  41. data/lib/puma/jruby_restart.rb +1 -2
  42. data/lib/puma/launcher.rb +431 -0
  43. data/lib/puma/minissl.rb +83 -4
  44. data/lib/puma/null_io.rb +19 -11
  45. data/lib/puma/plugin/tmp_restart.rb +34 -0
  46. data/lib/puma/plugin.rb +115 -0
  47. data/lib/puma/rack/backports/uri/common_193.rb +17 -13
  48. data/lib/puma/rack/builder.rb +3 -0
  49. data/lib/puma/rack/urlmap.rb +9 -8
  50. data/lib/puma/reactor.rb +18 -0
  51. data/lib/puma/runner.rb +43 -15
  52. data/lib/puma/server.rb +141 -35
  53. data/lib/puma/single.rb +16 -6
  54. data/lib/puma/state_file.rb +29 -0
  55. data/lib/puma/tcp_logger.rb +8 -1
  56. data/lib/puma/thread_pool.rb +60 -10
  57. data/lib/puma/util.rb +1 -5
  58. data/lib/puma.rb +13 -4
  59. data/lib/rack/handler/puma.rb +76 -29
  60. data/tools/jungle/README.md +12 -2
  61. data/tools/jungle/init.d/README.md +9 -2
  62. data/tools/jungle/init.d/puma +86 -59
  63. data/tools/jungle/init.d/run-puma +16 -1
  64. data/tools/jungle/rc.d/README.md +74 -0
  65. data/tools/jungle/rc.d/puma +61 -0
  66. data/tools/jungle/rc.d/puma.conf +10 -0
  67. data/tools/jungle/upstart/puma.conf +1 -1
  68. data/tools/trickletest.rb +1 -1
  69. metadata +28 -95
  70. data/COPYING +0 -55
  71. data/Gemfile +0 -13
  72. data/Manifest.txt +0 -74
  73. data/Rakefile +0 -158
  74. data/docs/config.md +0 -0
  75. data/lib/puma/capistrano.rb +0 -94
  76. data/lib/puma/rack/backports/uri/common_18.rb +0 -56
  77. data/lib/puma/rack/backports/uri/common_192.rb +0 -52
  78. 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
- @in_data_phase = true
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
- def initialize(cli)
6
- super cli
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.waitall
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
- if dir = @options[:worker_directory]
34
- log "+ Changing to #{dir}"
35
- Dir.chdir dir
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.new - @first_term_sent) > @options[:worker_shutdown_timeout]
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.new
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
- @options[:before_worker_fork].each { |h| h.call(idx) }
124
+ @launcher.config.run_hooks :before_worker_fork, idx
116
125
 
117
126
  pid = fork { worker(idx, master) }
118
- @cli.debug "Spawned worker: #{pid}"
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
- @options[:after_worker_boot].each { |h| h.call }
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 + 5
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 = "puma: cluster worker #{index}: #{master}"
200
- title << " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
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'] and File.exist?("Gemfile")
218
- log "+ Gemfile in context: #{File.expand_path("Gemfile")}"
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
- hooks = @options[:before_worker_boot]
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
- payload = "p#{Process.pid}\n"
277
+ base_payload = "p#{Process.pid}"
241
278
 
242
279
  while true
243
- sleep 5
244
- io << payload
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
- hooks = @options[:before_worker_shutdown]
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
- if dir = @options[:worker_directory]
292
- log "+ Changing to #{dir}"
293
- Dir.chdir dir
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!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count} }!
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 @cli.config.app_configured?
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
- @cli.binder.parse @options[:binds], self
420
+ @launcher.binder.parse @options[:binds], self
342
421
  end
343
422
 
344
423
  read, @wakeup = Puma::Util.pipe
345
424
 
346
- Signal.trap "SIGCHLD" do
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
- start_control
443
+ Plugins.fire_background
392
444
 
393
- @cli.write_state
445
+ @launcher.write_state
446
+
447
+ start_control
394
448
 
395
449
  @master_read, @worker_write = read, @wakeup
396
450
 
397
- hooks = @options[:before_fork]
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
- @cli.events.fire_on_booted!
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
- res = IO.select([read], nil, nil, 5)
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
- pid = read.gets.to_i
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
@@ -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
- logger = @logger || env['rack.errors']
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["REQUEST_METHOD"],
58
- env["PATH_INFO"],
59
- env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
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
- PATH_INFO = 'PATH_INFO'.freeze
65
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
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? ? "" : "?"+env[QUERY_STRING],
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
- 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
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