puma 4.1.1 → 4.2.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.

@@ -74,7 +74,6 @@ module Puma
74
74
  @started_at = Time.now
75
75
  @last_checkin = Time.now
76
76
  @last_status = '{}'
77
- @dead = false
78
77
  @term = false
79
78
  end
80
79
 
@@ -89,14 +88,6 @@ module Puma
89
88
  @stage = :booted
90
89
  end
91
90
 
92
- def dead?
93
- @dead
94
- end
95
-
96
- def dead!
97
- @dead = true
98
- end
99
-
100
91
  def term?
101
92
  @term
102
93
  end
@@ -225,8 +216,10 @@ module Puma
225
216
  log "- Stopping #{w.pid} for phased upgrade..."
226
217
  end
227
218
 
228
- w.term
229
- log "- #{w.signal} sent to #{w.pid}..."
219
+ unless w.term?
220
+ w.term
221
+ log "- #{w.signal} sent to #{w.pid}..."
222
+ end
230
223
  end
231
224
  end
232
225
  end
@@ -253,6 +246,7 @@ module Puma
253
246
  @suicide_pipe.close
254
247
 
255
248
  Thread.new do
249
+ Puma.set_thread_name "worker check pipe"
256
250
  IO.select [@check_pipe]
257
251
  log "! Detected parent died, dying"
258
252
  exit! 1
@@ -275,6 +269,7 @@ module Puma
275
269
  server = start_server
276
270
 
277
271
  Signal.trap "SIGTERM" do
272
+ @worker_write << "e#{Process.pid}\n" rescue nil
278
273
  server.stop
279
274
  end
280
275
 
@@ -287,6 +282,7 @@ module Puma
287
282
  end
288
283
 
289
284
  Thread.new(@worker_write) do |io|
285
+ Puma.set_thread_name "stat payload"
290
286
  base_payload = "p#{Process.pid}"
291
287
 
292
288
  while true
@@ -352,6 +348,8 @@ module Puma
352
348
  Dir.chdir dir
353
349
  end
354
350
 
351
+ # Inside of a child process, this will return all zeroes, as @workers is only populated in
352
+ # the master process.
355
353
  def stats
356
354
  old_worker_count = @workers.count { |w| w.phase != @phase }
357
355
  booted_worker_count = @workers.count { |w| w.booted? }
@@ -506,8 +504,11 @@ module Puma
506
504
  w.boot!
507
505
  log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
508
506
  force_check = true
507
+ when "e"
508
+ # external term, see worker method, Signal.trap "SIGTERM"
509
+ w.instance_variable_set :@term, true
509
510
  when "t"
510
- w.dead!
511
+ w.term unless w.term?
511
512
  force_check = true
512
513
  when "p"
513
514
  w.ping!(result.sub(/^\d+/,'').chomp)
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "4.1.1".freeze
104
- CODE_NAME = "Fourth and One".freeze
103
+ PUMA_VERSION = VERSION = "4.2.1".freeze
104
+ CODE_NAME = "Distant Airhorns".freeze
105
105
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
106
 
107
107
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -122,27 +122,24 @@ module Puma
122
122
  REQUEST_URI= 'REQUEST_URI'.freeze
123
123
  REQUEST_PATH = 'REQUEST_PATH'.freeze
124
124
  QUERY_STRING = 'QUERY_STRING'.freeze
125
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
125
126
 
126
127
  PATH_INFO = 'PATH_INFO'.freeze
127
128
 
128
129
  PUMA_TMP_BASE = "puma".freeze
129
130
 
130
- # Indicate that we couldn't parse the request
131
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
132
-
133
- # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
134
- ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
135
-
136
- # The standard empty 408 response for requests that timed out.
137
- ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze
138
-
139
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
140
-
141
- # Indicate that there was an internal error, obviously.
142
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
143
-
144
- # A common header for indicating the server is too busy. Not used yet.
145
- ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
131
+ ERROR_RESPONSE = {
132
+ # Indicate that we couldn't parse the request
133
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
134
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
135
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
136
+ # The standard empty 408 response for requests that timed out.
137
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
138
+ # Indicate that there was an internal error, obviously.
139
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
140
+ # A common header for indicating the server is too busy. Not used yet.
141
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
142
+ }
146
143
 
147
144
  # The basic max request size we'll try to read.
148
145
  CHUNK_SIZE = 16 * 1024
@@ -22,6 +22,7 @@ module Puma
22
22
  @control_auth_token = nil
23
23
  @config_file = nil
24
24
  @command = nil
25
+ @environment = ENV['RACK_ENV'] || "development"
25
26
 
26
27
  @argv = argv.dup
27
28
  @stdout = stdout
@@ -59,6 +60,11 @@ module Puma
59
60
  @config_file = arg
60
61
  end
61
62
 
63
+ o.on "-e", "--environment ENVIRONMENT",
64
+ "The environment to run the Rack app on (default development)" do |arg|
65
+ @environment = arg
66
+ end
67
+
62
68
  o.on_tail("-H", "--help", "Show this message") do
63
69
  @stdout.puts o
64
70
  exit
@@ -76,8 +82,10 @@ module Puma
76
82
  @command = argv.shift
77
83
 
78
84
  unless @config_file == '-'
79
- if @config_file.nil? and File.exist?('config/puma.rb')
80
- @config_file = 'config/puma.rb'
85
+ if @config_file.nil?
86
+ @config_file = %W(config/puma/#{@environment}.rb config/puma.rb).find do |f|
87
+ File.exist?(f)
88
+ end
81
89
  end
82
90
 
83
91
  if @config_file
@@ -258,6 +266,7 @@ module Puma
258
266
  run_args += ["--control-url", @control_url] if @control_url
259
267
  run_args += ["--control-token", @control_auth_token] if @control_auth_token
260
268
  run_args += ["-C", @config_file] if @config_file
269
+ run_args += ["-e", @environment] if @environment
261
270
 
262
271
  events = Puma::Events.new @stdout, @stderr
263
272
 
@@ -396,7 +396,7 @@ module Puma
396
396
  # keystore_pass: password
397
397
  # }
398
398
  def ssl_bind(host, port, opts)
399
- verify = opts.fetch(:verify_mode, 'none')
399
+ verify = opts.fetch(:verify_mode, 'none').to_s
400
400
  no_tlsv1 = opts.fetch(:no_tlsv1, 'false')
401
401
  no_tlsv1_1 = opts.fetch(:no_tlsv1_1, 'false')
402
402
  ca_additions = "&ca=#{opts[:ca]}" if ['peer', 'force_peer'].include?(verify)
@@ -584,6 +584,7 @@ module Puma
584
584
  # dictates.
585
585
  #
586
586
  # @note This is incompatible with +preload_app!+.
587
+ # @note This is only supported for RubyGems 2.2+
587
588
  def prune_bundler(answer=true)
588
589
  @options[:prune_bundler] = answer
589
590
  end
@@ -601,6 +602,21 @@ module Puma
601
602
  @options[:raise_exception_on_sigterm] = answer
602
603
  end
603
604
 
605
+ # When using prune_bundler, if extra runtime dependencies need to be loaded to
606
+ # initialize your app, then this setting can be used.
607
+ #
608
+ # Before bundler is pruned, the gem names supplied will be looked up in the bundler
609
+ # context and then loaded again after bundler is pruned.
610
+ # Only applies if prune_bundler is used.
611
+ #
612
+ # @example
613
+ # extra_runtime_dependencies ['gem_name_1', 'gem_name_2']
614
+ # @example
615
+ # extra_runtime_dependencies ['puma_worker_killer']
616
+ def extra_runtime_dependencies(answer = [])
617
+ @options[:extra_runtime_dependencies] = Array(answer)
618
+ end
619
+
604
620
  # Additional text to display in process listing.
605
621
  #
606
622
  # If you do not specify a tag, Puma will infer it. If you do not want Puma
@@ -2,12 +2,9 @@
2
2
 
3
3
  require 'puma/events'
4
4
  require 'puma/detect'
5
-
6
5
  require 'puma/cluster'
7
6
  require 'puma/single'
8
-
9
7
  require 'puma/const'
10
-
11
8
  require 'puma/binder'
12
9
 
13
10
  module Puma
@@ -126,19 +123,6 @@ module Puma
126
123
  File.unlink(path) if path && File.exist?(path)
127
124
  end
128
125
 
129
- # If configured, write the pid of the current process out
130
- # to a file.
131
- def write_pid
132
- path = @options[:pidfile]
133
- return unless path
134
-
135
- File.open(path, 'w') { |f| f.puts Process.pid }
136
- cur = Process.pid
137
- at_exit do
138
- delete_pidfile if cur == Process.pid
139
- end
140
- end
141
-
142
126
  # Begin async shutdown of the server
143
127
  def halt
144
128
  @status = :halt
@@ -200,6 +184,7 @@ module Puma
200
184
  when :exit
201
185
  # nothing
202
186
  end
187
+ @binder.close_unix_paths
203
188
  end
204
189
 
205
190
  # Return which tcp port the launcher is using, if it's using TCP
@@ -217,16 +202,24 @@ module Puma
217
202
  end
218
203
 
219
204
  def close_binder_listeners
220
- @binder.listeners.each do |l, io|
221
- io.close
222
- uri = URI.parse(l)
223
- next unless uri.scheme == 'unix'
224
- File.unlink("#{uri.host}#{uri.path}")
225
- end
205
+ @binder.close_listeners
226
206
  end
227
207
 
228
208
  private
229
209
 
210
+ # If configured, write the pid of the current process out
211
+ # to a file.
212
+ def write_pid
213
+ path = @options[:pidfile]
214
+ return unless path
215
+
216
+ File.open(path, 'w') { |f| f.puts Process.pid }
217
+ cur = Process.pid
218
+ at_exit do
219
+ delete_pidfile if cur == Process.pid
220
+ end
221
+ end
222
+
230
223
  def reload_worker_directory
231
224
  @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
232
225
  end
@@ -246,48 +239,71 @@ module Puma
246
239
  Dir.chdir(@restart_dir)
247
240
  Kernel.exec(*argv)
248
241
  else
249
- redirects = {:close_others => true}
250
- @binder.listeners.each_with_index do |(l, io), i|
251
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
252
- redirects[io.to_i] = io.to_i
253
- end
254
-
255
242
  argv = restart_args
256
243
  Dir.chdir(@restart_dir)
257
- argv += [redirects]
244
+ argv += [@binder.redirects_for_restart]
258
245
  Kernel.exec(*argv)
259
246
  end
260
247
  end
261
248
 
262
- def prune_bundler
263
- return unless defined?(Bundler)
264
- puma = Bundler.rubygems.loaded_specs("puma")
265
- dirs = puma.require_paths.map { |x| File.join(puma.full_gem_path, x) }
249
+ def dependencies_and_files_to_require_after_prune
250
+ puma = spec_for_gem("puma")
251
+
252
+ deps = puma.runtime_dependencies.map do |d|
253
+ "#{d.name}:#{spec_for_gem(d.name).version}"
254
+ end
255
+
256
+ [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
257
+ end
258
+
259
+ def extra_runtime_deps_directories
260
+ Array(@options[:extra_runtime_dependencies]).map do |d_name|
261
+ if (spec = spec_for_gem(d_name))
262
+ require_paths_for_gem(spec)
263
+ else
264
+ log "* Could not load extra dependency: #{d_name}"
265
+ nil
266
+ end
267
+ end.flatten.compact
268
+ end
269
+
270
+ def puma_wild_location
271
+ puma = spec_for_gem("puma")
272
+ dirs = require_paths_for_gem(puma)
266
273
  puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
274
+ File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
275
+ end
267
276
 
268
- unless puma_lib_dir
277
+ def prune_bundler
278
+ return unless defined?(Bundler)
279
+ require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
280
+ unless puma_wild_location
269
281
  log "! Unable to prune Bundler environment, continuing"
270
282
  return
271
283
  end
272
284
 
273
- deps = puma.runtime_dependencies.map do |d|
274
- spec = Bundler.rubygems.loaded_specs(d.name)
275
- "#{d.name}:#{spec.version.to_s}"
276
- end
285
+ deps, dirs = dependencies_and_files_to_require_after_prune
277
286
 
278
287
  log '* Pruning Bundler environment'
279
288
  home = ENV['GEM_HOME']
280
289
  Bundler.with_clean_env do
281
290
  ENV['GEM_HOME'] = home
282
291
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
283
- wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
284
- args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
292
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
285
293
  # Ruby 2.0+ defaults to true which breaks socket activation
286
294
  args += [{:close_others => false}]
287
295
  Kernel.exec(*args)
288
296
  end
289
297
  end
290
298
 
299
+ def spec_for_gem(gem_name)
300
+ Bundler.rubygems.loaded_specs(gem_name)
301
+ end
302
+
303
+ def require_paths_for_gem(gem_spec)
304
+ gem_spec.full_require_paths
305
+ end
306
+
291
307
  def log(str)
292
308
  @events.log str
293
309
  end
@@ -307,6 +323,21 @@ module Puma
307
323
  log "- Goodbye!"
308
324
  end
309
325
 
326
+ def log_thread_status
327
+ Thread.list.each do |thread|
328
+ log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
329
+ logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
330
+ logstr += " #{thread.name}" if thread.respond_to?(:name)
331
+ log logstr
332
+
333
+ if thread.backtrace
334
+ log thread.backtrace.join("\n")
335
+ else
336
+ log "<no backtrace available>"
337
+ end
338
+ end
339
+ end
340
+
310
341
  def set_process_title
311
342
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
312
343
  end
@@ -406,12 +437,6 @@ module Puma
406
437
 
407
438
  begin
408
439
  Signal.trap "SIGINT" do
409
- if Puma.jruby?
410
- @status = :exit
411
- graceful_stop
412
- exit
413
- end
414
-
415
440
  stop
416
441
  end
417
442
  rescue Exception
@@ -429,6 +454,22 @@ module Puma
429
454
  rescue Exception
430
455
  log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
431
456
  end
457
+
458
+ begin
459
+ Signal.trap "SIGINFO" do
460
+ log_thread_status
461
+ end
462
+ rescue Exception
463
+ # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
464
+ # to see this constantly on Linux.
465
+ end
466
+ end
467
+
468
+ def require_rubygems_min_version!(min_version, feature)
469
+ return if min_version <= Gem::Version.new(Gem::VERSION)
470
+
471
+ raise "#{feature} is not supported on your version of RubyGems. " \
472
+ "You must have RubyGems #{min_version}+ to use this feature."
432
473
  end
433
474
  end
434
475
  end
@@ -62,8 +62,11 @@ module Puma
62
62
  end
63
63
 
64
64
  def fire_background
65
- @background.each do |b|
66
- Thread.new(&b)
65
+ @background.each_with_index do |b, i|
66
+ Thread.new do
67
+ Puma.set_thread_name "plugin background #{i}"
68
+ b.call
69
+ end
67
70
  end
68
71
  end
69
72
  end
@@ -225,7 +225,7 @@ module Puma
225
225
  # will be flooding them with errors when persistent connections
226
226
  # are closed.
227
227
  rescue ConnectionError
228
- c.write_500
228
+ c.write_error(500)
229
229
  c.close
230
230
 
231
231
  clear_monitor mon
@@ -252,7 +252,7 @@ module Puma
252
252
  rescue HttpParserError => e
253
253
  @server.lowlevel_error(e, c.env)
254
254
 
255
- c.write_400
255
+ c.write_error(400)
256
256
  c.close
257
257
 
258
258
  clear_monitor mon
@@ -261,7 +261,7 @@ module Puma
261
261
  rescue StandardError => e
262
262
  @server.lowlevel_error(e, c.env)
263
263
 
264
- c.write_500
264
+ c.write_error(500)
265
265
  c.close
266
266
 
267
267
  clear_monitor mon
@@ -277,7 +277,7 @@ module Puma
277
277
  while @timeouts.first.value.timeout_at < now
278
278
  mon = @timeouts.shift
279
279
  c = mon.value
280
- c.write_408 if c.in_data_phase
280
+ c.write_error(408) if c.in_data_phase
281
281
  c.close
282
282
 
283
283
  clear_monitor mon
@@ -307,6 +307,7 @@ module Puma
307
307
 
308
308
  def run_in_thread
309
309
  @thread = Thread.new do
310
+ Puma.set_thread_name "reactor"
310
311
  begin
311
312
  run_internal
312
313
  rescue StandardError => e