puma 5.0.4 → 5.5.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +250 -48
  3. data/README.md +90 -24
  4. data/docs/architecture.md +57 -20
  5. data/docs/compile_options.md +21 -0
  6. data/docs/deployment.md +53 -67
  7. data/docs/fork_worker.md +2 -0
  8. data/docs/jungle/rc.d/README.md +1 -1
  9. data/docs/kubernetes.md +66 -0
  10. data/docs/plugins.md +15 -15
  11. data/docs/rails_dev_mode.md +28 -0
  12. data/docs/restart.md +7 -7
  13. data/docs/signals.md +10 -10
  14. data/docs/stats.md +142 -0
  15. data/docs/systemd.md +85 -66
  16. data/ext/puma_http11/extconf.rb +36 -6
  17. data/ext/puma_http11/http11_parser.c +64 -59
  18. data/ext/puma_http11/http11_parser.h +1 -1
  19. data/ext/puma_http11/http11_parser.java.rl +1 -1
  20. data/ext/puma_http11/http11_parser.rl +1 -1
  21. data/ext/puma_http11/http11_parser_common.rl +1 -1
  22. data/ext/puma_http11/mini_ssl.c +177 -84
  23. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +39 -41
  24. data/ext/puma_http11/puma_http11.c +8 -2
  25. data/lib/puma/app/status.rb +4 -7
  26. data/lib/puma/binder.rb +121 -46
  27. data/lib/puma/cli.rb +9 -0
  28. data/lib/puma/client.rb +58 -19
  29. data/lib/puma/cluster/worker.rb +19 -16
  30. data/lib/puma/cluster/worker_handle.rb +9 -2
  31. data/lib/puma/cluster.rb +46 -22
  32. data/lib/puma/configuration.rb +18 -2
  33. data/lib/puma/const.rb +14 -4
  34. data/lib/puma/control_cli.rb +76 -71
  35. data/lib/puma/detect.rb +14 -10
  36. data/lib/puma/dsl.rb +143 -26
  37. data/lib/puma/error_logger.rb +12 -5
  38. data/lib/puma/events.rb +18 -3
  39. data/lib/puma/json_serialization.rb +96 -0
  40. data/lib/puma/launcher.rb +54 -6
  41. data/lib/puma/minissl/context_builder.rb +6 -0
  42. data/lib/puma/minissl.rb +54 -38
  43. data/lib/puma/null_io.rb +12 -0
  44. data/lib/puma/plugin.rb +1 -1
  45. data/lib/puma/queue_close.rb +7 -7
  46. data/lib/puma/rack/builder.rb +1 -1
  47. data/lib/puma/reactor.rb +19 -12
  48. data/lib/puma/request.rb +45 -16
  49. data/lib/puma/runner.rb +38 -13
  50. data/lib/puma/server.rb +62 -123
  51. data/lib/puma/state_file.rb +5 -3
  52. data/lib/puma/systemd.rb +46 -0
  53. data/lib/puma/thread_pool.rb +10 -7
  54. data/lib/puma/util.rb +8 -1
  55. data/lib/puma.rb +36 -10
  56. data/lib/rack/handler/puma.rb +1 -0
  57. metadata +15 -9
data/lib/puma/binder.rb CHANGED
@@ -13,7 +13,7 @@ module Puma
13
13
  require 'puma/minissl'
14
14
  require 'puma/minissl/context_builder'
15
15
 
16
- # Odd bug in 'pure Ruby' nio4r verion 2.5.2, which installs with Ruby 2.3.
16
+ # Odd bug in 'pure Ruby' nio4r version 2.5.2, which installs with Ruby 2.3.
17
17
  # NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
18
18
  # The bug was that it did not require openssl.
19
19
  # @todo remove when Ruby 2.3 support is dropped
@@ -41,6 +41,7 @@ module Puma
41
41
  "rack.multithread".freeze => conf.options[:max_threads] > 1,
42
42
  "rack.multiprocess".freeze => conf.options[:workers] >= 1,
43
43
  "rack.run_once".freeze => false,
44
+ RACK_URL_SCHEME => conf.options[:rack_url_scheme],
44
45
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
45
46
 
46
47
  # I'd like to set a default CONTENT_TYPE here but some things
@@ -56,6 +57,7 @@ module Puma
56
57
 
57
58
  @envs = {}
58
59
  @ios = []
60
+ localhost_authority
59
61
  end
60
62
 
61
63
  attr_reader :ios
@@ -95,6 +97,7 @@ module Puma
95
97
  # @version 5.0.0
96
98
  #
97
99
  def create_activated_fds(env_hash)
100
+ @events.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
98
101
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
99
102
  env_hash['LISTEN_FDS'].to_i.times do |index|
100
103
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -111,6 +114,43 @@ module Puma
111
114
  ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
112
115
  end
113
116
 
117
+ # Synthesize binds from systemd socket activation
118
+ #
119
+ # When systemd socket activation is enabled, it can be tedious to keep the
120
+ # binds in sync. This method can synthesize any binds based on the received
121
+ # activated sockets. Any existing matching binds will be respected.
122
+ #
123
+ # When only_matching is true in, all binds that do not match an activated
124
+ # socket is removed in place.
125
+ #
126
+ # It's a noop if no activated sockets were received.
127
+ def synthesize_binds_from_activated_fs(binds, only_matching)
128
+ return binds unless activated_sockets.any?
129
+
130
+ activated_binds = []
131
+
132
+ activated_sockets.keys.each do |proto, addr, port|
133
+ if port
134
+ tcp_url = "#{proto}://#{addr}:#{port}"
135
+ ssl_url = "ssl://#{addr}:#{port}"
136
+ ssl_url_prefix = "#{ssl_url}?"
137
+
138
+ existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
139
+
140
+ activated_binds << (existing || tcp_url)
141
+ else
142
+ # TODO: can there be a SSL bind without a port?
143
+ activated_binds << "#{proto}://#{addr}"
144
+ end
145
+ end
146
+
147
+ if only_matching
148
+ activated_binds
149
+ else
150
+ binds | activated_binds
151
+ end
152
+ end
153
+
114
154
  def parse(binds, logger, log_msg = 'Listening')
115
155
  binds.each do |str|
116
156
  uri = URI.parse str
@@ -123,21 +163,16 @@ module Puma
123
163
  io = inherit_tcp_listener uri.host, uri.port, sock
124
164
  logger.log "* Activated #{str}"
125
165
  else
166
+ ios_len = @ios.length
126
167
  params = Util.parse_query uri.query
127
168
 
128
- opt = params.key?('low_latency')
169
+ opt = params.key?('low_latency') && params['low_latency'] != 'false'
129
170
  bak = params.fetch('backlog', 1024).to_i
130
171
 
131
172
  io = add_tcp_listener uri.host, uri.port, opt, bak
132
173
 
133
- @ios.each do |i|
134
- next unless TCPServer === i
135
- addr = if i.local_address.ipv6?
136
- "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
137
- else
138
- i.local_address.ip_unpack.join(':')
139
- end
140
-
174
+ @ios[ios_len..-1].each do |i|
175
+ addr = loc_addr_str i
141
176
  logger.log "* #{log_msg} on http://#{addr}"
142
177
  end
143
178
  end
@@ -145,11 +180,20 @@ module Puma
145
180
  @listeners << [str, io] if io
146
181
  when "unix"
147
182
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
183
+ abstract = false
184
+ if str.start_with? 'unix://@'
185
+ raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
186
+ abstract = true
187
+ path = "@#{path}"
188
+ end
148
189
 
149
190
  if fd = @inherited_fds.delete(str)
191
+ @unix_paths << path unless abstract
150
192
  io = inherit_unix_listener path, fd
151
193
  logger.log "* Inherited #{str}"
152
- elsif sock = @activated_sockets.delete([ :unix, path ])
194
+ elsif sock = @activated_sockets.delete([ :unix, path ]) ||
195
+ @activated_sockets.delete([ :unix, File.realdirpath(path) ])
196
+ @unix_paths << path unless abstract || File.exist?(path)
153
197
  io = inherit_unix_listener path, sock
154
198
  logger.log "* Activated #{str}"
155
199
  else
@@ -173,6 +217,7 @@ module Puma
173
217
  end
174
218
  end
175
219
 
220
+ @unix_paths << path unless abstract || File.exist?(path)
176
221
  io = add_unix_listener path, umask, mode, backlog
177
222
  logger.log "* #{log_msg} on #{str}"
178
223
  end
@@ -183,7 +228,13 @@ module Puma
183
228
  raise "Puma compiled without SSL support" unless HAS_SSL
184
229
 
185
230
  params = Util.parse_query uri.query
186
- ctx = MiniSSL::ContextBuilder.new(params, @events).context
231
+
232
+ # If key and certs are not defined and localhost gem is required.
233
+ # localhost gem will be used for self signed
234
+ # Load localhost authority if not loaded.
235
+ ctx = localhost_authority && localhost_authority_context if params.empty?
236
+
237
+ ctx ||= MiniSSL::ContextBuilder.new(params, @events).context
187
238
 
188
239
  if fd = @inherited_fds.delete(str)
189
240
  logger.log "* Inherited #{str}"
@@ -192,8 +243,13 @@ module Puma
192
243
  io = inherit_ssl_listener sock, ctx
193
244
  logger.log "* Activated #{str}"
194
245
  else
246
+ ios_len = @ios.length
195
247
  io = add_ssl_listener uri.host, uri.port, ctx
196
- logger.log "* Listening on #{str}"
248
+
249
+ @ios[ios_len..-1].each do |i|
250
+ addr = loc_addr_str i
251
+ logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
252
+ end
197
253
  end
198
254
 
199
255
  @listeners << [str, io] if io
@@ -221,17 +277,37 @@ module Puma
221
277
  end
222
278
 
223
279
  # Also close any unused activated sockets
224
- @activated_sockets.each do |key, sock|
225
- logger.log "* Closing unused activated socket: #{key.join ':'}"
226
- begin
227
- sock.close
228
- rescue SystemCallError
280
+ unless @activated_sockets.empty?
281
+ fds = @ios.map(&:to_i)
282
+ @activated_sockets.each do |key, sock|
283
+ next if fds.include? sock.to_i
284
+ logger.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
285
+ begin
286
+ sock.close
287
+ rescue SystemCallError
288
+ end
289
+ # We have to unlink a unix socket path that's not being used
290
+ File.unlink key[1] if key.first == :unix
229
291
  end
230
- # We have to unlink a unix socket path that's not being used
231
- File.unlink key[1] if key[0] == :unix
232
292
  end
233
293
  end
234
294
 
295
+ def localhost_authority
296
+ @localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
297
+ end
298
+
299
+ def localhost_authority_context
300
+ return unless localhost_authority
301
+
302
+ key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
303
+ [localhost_authority.key_path, localhost_authority.certificate_path]
304
+ else
305
+ local_certificates_path = File.expand_path("~/.localhost")
306
+ [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
307
+ end
308
+ MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @events).context
309
+ end
310
+
235
311
  # Tell the server to listen on host +host+, port +port+.
236
312
  # If +optimize_for_latency+ is true (the default) then clients connecting
237
313
  # will be optimized for latency over throughput.
@@ -249,6 +325,7 @@ module Puma
249
325
 
250
326
  host = host[1..-2] if host and host[0..0] == '['
251
327
  tcp_server = TCPServer.new(host, port)
328
+
252
329
  if optimize_for_latency
253
330
  tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
254
331
  end
@@ -260,11 +337,7 @@ module Puma
260
337
  end
261
338
 
262
339
  def inherit_tcp_listener(host, port, fd)
263
- if fd.kind_of? TCPServer
264
- s = fd
265
- else
266
- s = TCPServer.for_fd(fd)
267
- end
340
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
268
341
 
269
342
  @ios << s
270
343
  s
@@ -274,6 +347,8 @@ module Puma
274
347
  optimize_for_latency=true, backlog=1024)
275
348
 
276
349
  raise "Puma compiled without SSL support" unless HAS_SSL
350
+ # Puma will try to use local authority context if context is supplied nil
351
+ ctx ||= localhost_authority_context
277
352
 
278
353
  if host == "localhost"
279
354
  loopback_addresses.each do |addr|
@@ -301,12 +376,11 @@ module Puma
301
376
 
302
377
  def inherit_ssl_listener(fd, ctx)
303
378
  raise "Puma compiled without SSL support" unless HAS_SSL
379
+ # Puma will try to use local authority context if context is supplied nil
380
+ ctx ||= localhost_authority_context
381
+
382
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
304
383
 
305
- if fd.kind_of? TCPServer
306
- s = fd
307
- else
308
- s = TCPServer.for_fd(fd)
309
- end
310
384
  ssl = MiniSSL::Server.new(s, ctx)
311
385
 
312
386
  env = @proto_env.dup
@@ -321,8 +395,6 @@ module Puma
321
395
  # Tell the server to listen on +path+ as a UNIX domain socket.
322
396
  #
323
397
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
324
- @unix_paths << path unless File.exist? path
325
-
326
398
  # Let anyone connect by default
327
399
  umask ||= 0
328
400
 
@@ -339,8 +411,7 @@ module Puma
339
411
  raise "There is already a server bound to: #{path}"
340
412
  end
341
413
  end
342
-
343
- s = UNIXServer.new(path)
414
+ s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
344
415
  s.listen backlog
345
416
  @ios << s
346
417
  ensure
@@ -359,13 +430,8 @@ module Puma
359
430
  end
360
431
 
361
432
  def inherit_unix_listener(path, fd)
362
- @unix_paths << path unless File.exist? path
433
+ s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
363
434
 
364
- if fd.kind_of? TCPServer
365
- s = fd
366
- else
367
- s = UNIXServer.for_fd fd
368
- end
369
435
  @ios << s
370
436
 
371
437
  env = @proto_env.dup
@@ -376,24 +442,24 @@ module Puma
376
442
  end
377
443
 
378
444
  def close_listeners
379
- listeners.each do |l, io|
380
- io.close unless io.closed? # Ruby 2.2 issue
381
- uri = URI.parse(l)
445
+ @listeners.each do |l, io|
446
+ io.close unless io.closed?
447
+ uri = URI.parse l
382
448
  next unless uri.scheme == 'unix'
383
449
  unix_path = "#{uri.host}#{uri.path}"
384
- File.unlink unix_path if unix_paths.include? unix_path
450
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
385
451
  end
386
452
  end
387
453
 
388
454
  def redirects_for_restart
389
- redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
455
+ redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
390
456
  redirects[:close_others] = true
391
457
  redirects
392
458
  end
393
459
 
394
460
  # @version 5.0.0
395
461
  def redirects_for_restart_env
396
- listeners.each_with_object({}).with_index do |(listen, memo), i|
462
+ @listeners.each_with_object({}).with_index do |(listen, memo), i|
397
463
  memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
398
464
  end
399
465
  end
@@ -407,6 +473,15 @@ module Puma
407
473
  end.map { |addrinfo| addrinfo.ip_address }.uniq
408
474
  end
409
475
 
476
+ def loc_addr_str(io)
477
+ loc_addr = io.to_io.local_address
478
+ if loc_addr.ipv6?
479
+ "[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
480
+ else
481
+ loc_addr.ip_unpack.join(':')
482
+ end
483
+ end
484
+
410
485
  # @version 5.0.0
411
486
  def socket_activation_fd(int)
412
487
  int + 3 # 3 is the magic number you add to follow the SA protocol
data/lib/puma/cli.rb CHANGED
@@ -104,10 +104,19 @@ module Puma
104
104
  user_config.bind arg
105
105
  end
106
106
 
107
+ o.on "--bind-to-activated-sockets [only]", "Bind to all activated sockets" do |arg|
108
+ user_config.bind_to_activated_sockets(arg || true)
109
+ end
110
+
107
111
  o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
108
112
  file_config.load arg
109
113
  end
110
114
 
115
+ # Identical to supplying --config "-", but more semantic
116
+ o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
117
+ file_config.load "-"
118
+ end
119
+
111
120
  o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
112
121
  configure_control_url(arg)
113
122
  end
data/lib/puma/client.rb CHANGED
@@ -56,6 +56,7 @@ module Puma
56
56
  @parser = HttpParser.new
57
57
  @parsed_bytes = 0
58
58
  @read_header = true
59
+ @read_proxy = false
59
60
  @ready = false
60
61
 
61
62
  @body = nil
@@ -69,7 +70,9 @@ module Puma
69
70
  @hijacked = false
70
71
 
71
72
  @peerip = nil
73
+ @listener = nil
72
74
  @remote_addr_header = nil
75
+ @expect_proxy_proto = false
73
76
 
74
77
  @body_remain = 0
75
78
 
@@ -81,7 +84,7 @@ module Puma
81
84
 
82
85
  attr_writer :peerip
83
86
 
84
- attr_accessor :remote_addr_header
87
+ attr_accessor :remote_addr_header, :listener
85
88
 
86
89
  def_delegators :@io, :closed?
87
90
 
@@ -105,7 +108,7 @@ module Puma
105
108
 
106
109
  # @!attribute [r] in_data_phase
107
110
  def in_data_phase
108
- !@read_header
111
+ !(@read_header || @read_proxy)
109
112
  end
110
113
 
111
114
  def set_timeout(val)
@@ -120,16 +123,19 @@ module Puma
120
123
  def reset(fast_check=true)
121
124
  @parser.reset
122
125
  @read_header = true
126
+ @read_proxy = !!@expect_proxy_proto
123
127
  @env = @proto_env.dup
124
128
  @body = nil
125
129
  @tempfile = nil
126
130
  @parsed_bytes = 0
127
131
  @ready = false
128
132
  @body_remain = 0
129
- @peerip = nil
133
+ @peerip = nil if @remote_addr_header
130
134
  @in_last_chunk = false
131
135
 
132
136
  if @buffer
137
+ return false unless try_to_parse_proxy_protocol
138
+
133
139
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
134
140
 
135
141
  if @parser.finished?
@@ -142,8 +148,7 @@ module Puma
142
148
  return false
143
149
  else
144
150
  begin
145
- if fast_check &&
146
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
151
+ if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
147
152
  return try_to_finish
148
153
  end
149
154
  rescue IOError
@@ -157,12 +162,36 @@ module Puma
157
162
  begin
158
163
  @io.close
159
164
  rescue IOError
160
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
165
+ Puma::Util.purge_interrupt_queue
166
+ end
167
+ end
168
+
169
+ # If necessary, read the PROXY protocol from the buffer. Returns
170
+ # false if more data is needed.
171
+ def try_to_parse_proxy_protocol
172
+ if @read_proxy
173
+ if @expect_proxy_proto == :v1
174
+ if @buffer.include? "\r\n"
175
+ if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
176
+ if md[1]
177
+ @peerip = md[1].split(" ")[0]
178
+ end
179
+ @buffer = md.post_match
180
+ end
181
+ # if the buffer has a \r\n but doesn't have a PROXY protocol
182
+ # request, this is just HTTP from a non-PROXY client; move on
183
+ @read_proxy = false
184
+ return @buffer.size > 0
185
+ else
186
+ return false
187
+ end
188
+ end
161
189
  end
190
+ true
162
191
  end
163
192
 
164
193
  def try_to_finish
165
- return read_body unless @read_header
194
+ return read_body if in_data_phase
166
195
 
167
196
  begin
168
197
  data = @io.read_nonblock(CHUNK_SIZE)
@@ -187,6 +216,8 @@ module Puma
187
216
  @buffer = data
188
217
  end
189
218
 
219
+ return false unless try_to_parse_proxy_protocol
220
+
190
221
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
191
222
 
192
223
  if @parser.finished?
@@ -201,13 +232,13 @@ module Puma
201
232
 
202
233
  def eagerly_finish
203
234
  return true if @ready
204
- return false unless IO.select([@to_io], nil, nil, 0)
235
+ return false unless @to_io.wait_readable(0)
205
236
  try_to_finish
206
237
  end
207
238
 
208
239
  def finish(timeout)
209
240
  return if @ready
210
- IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
241
+ @to_io.wait_readable(timeout) || timeout! until try_to_finish
211
242
  end
212
243
 
213
244
  def timeout!
@@ -239,13 +270,19 @@ module Puma
239
270
  # @version 5.0.0
240
271
  #
241
272
  def can_close?
242
- # Allow connection to close if it's received at least one full request
243
- # and hasn't received any data for a future request.
244
- #
245
- # From RFC 2616 section 8.1.4:
246
- # Servers SHOULD always respond to at least one request per connection,
247
- # if at all possible.
248
- @requests_served > 0 && @parsed_bytes == 0
273
+ # Allow connection to close if we're not in the middle of parsing a request.
274
+ @parsed_bytes == 0
275
+ end
276
+
277
+ def expect_proxy_proto=(val)
278
+ if val
279
+ if @read_header
280
+ @read_proxy = true
281
+ end
282
+ else
283
+ @read_proxy = false
284
+ end
285
+ @expect_proxy_proto = val
249
286
  end
250
287
 
251
288
  private
@@ -300,6 +337,7 @@ module Puma
300
337
 
301
338
  if remain > MAX_BODY
302
339
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
340
+ @body.unlink
303
341
  @body.binmode
304
342
  @tempfile = @body
305
343
  else
@@ -312,7 +350,7 @@ module Puma
312
350
 
313
351
  @body_remain = remain
314
352
 
315
- return false
353
+ false
316
354
  end
317
355
 
318
356
  def read_body
@@ -379,7 +417,7 @@ module Puma
379
417
  end
380
418
 
381
419
  if decode_chunk(chunk)
382
- @env[CONTENT_LENGTH] = @chunked_content_length
420
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
383
421
  return true
384
422
  end
385
423
  end
@@ -391,12 +429,13 @@ module Puma
391
429
  @prev_chunk = ""
392
430
 
393
431
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
432
+ @body.unlink
394
433
  @body.binmode
395
434
  @tempfile = @body
396
435
  @chunked_content_length = 0
397
436
 
398
437
  if decode_chunk(body)
399
- @env[CONTENT_LENGTH] = @chunked_content_length
438
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
400
439
  return true
401
440
  end
402
441
  end
@@ -33,9 +33,9 @@ module Puma
33
33
  Signal.trap "SIGINT", "IGNORE"
34
34
  Signal.trap "SIGCHLD", "DEFAULT"
35
35
 
36
- Thread.new do
36
+ Thread.new do
37
37
  Puma.set_thread_name "worker check pipe"
38
- IO.select [@check_pipe]
38
+ @check_pipe.wait_readable
39
39
  log "! Detected parent died, dying"
40
40
  exit! 1
41
41
  end
@@ -54,7 +54,14 @@ module Puma
54
54
  # things in shape before booting the app.
55
55
  @launcher.config.run_hooks :before_worker_boot, index, @launcher.events
56
56
 
57
+ begin
57
58
  server = @server ||= start_server
59
+ rescue Exception => e
60
+ log "! Unable to start worker"
61
+ log e.backtrace[0]
62
+ exit 1
63
+ end
64
+
58
65
  restart_server = Queue.new << true << false
59
66
 
60
67
  fork_worker = @options[:fork_worker] && index == 0
@@ -99,7 +106,7 @@ module Puma
99
106
  begin
100
107
  @worker_write << "b#{Process.pid}:#{index}\n"
101
108
  rescue SystemCallError, IOError
102
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
109
+ Puma::Util.purge_interrupt_queue
103
110
  STDERR.puts "Master seems to have exited, exiting."
104
111
  return
105
112
  end
@@ -108,13 +115,19 @@ module Puma
108
115
  server_thread = server.run
109
116
  stat_thread ||= Thread.new(@worker_write) do |io|
110
117
  Puma.set_thread_name "stat payload"
118
+ base_payload = "p#{Process.pid}"
111
119
 
112
120
  while true
113
121
  begin
114
- require 'json'
115
- io << "p#{Process.pid}#{server.stats.to_json}\n"
122
+ b = server.backlog || 0
123
+ r = server.running || 0
124
+ t = server.pool_capacity || 0
125
+ m = server.max_threads || 0
126
+ rc = server.requests_count || 0
127
+ payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
128
+ io << payload
116
129
  rescue IOError
117
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
130
+ Puma::Util.purge_interrupt_queue
118
131
  break
119
132
  end
120
133
  sleep Const::WORKER_CHECK_INTERVAL
@@ -155,16 +168,6 @@ module Puma
155
168
  @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
156
169
  pid
157
170
  end
158
-
159
- def wakeup!
160
- return unless @wakeup
161
-
162
- begin
163
- @wakeup.write "!" unless @wakeup.closed?
164
- rescue SystemCallError, IOError
165
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
166
- end
167
- end
168
171
  end
169
172
  end
170
173
  end
@@ -31,6 +31,10 @@ module Puma
31
31
  @stage == :booted
32
32
  end
33
33
 
34
+ def uptime
35
+ Time.now - started_at
36
+ end
37
+
34
38
  def boot!
35
39
  @last_checkin = Time.now
36
40
  @stage = :booted
@@ -42,8 +46,11 @@ module Puma
42
46
 
43
47
  def ping!(status)
44
48
  @last_checkin = Time.now
45
- require 'json'
46
- @last_status = JSON.parse(status, symbolize_names: true)
49
+ captures = status.match(/{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads": (?<max_threads>\d*), "requests_count": (?<requests_count>\d*) }/)
50
+ @last_status = captures.names.inject({}) do |hash, key|
51
+ hash[key.to_sym] = captures[key].to_i
52
+ hash
53
+ end
47
54
  end
48
55
 
49
56
  # @see Puma::Cluster#check_workers