puma 4.1.1 → 4.2.0

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)
@@ -528,6 +529,7 @@ module Puma
528
529
  @suicide_pipe.close
529
530
  read.close
530
531
  @wakeup.close
532
+ @launcher.close_binder_unix_paths
531
533
  end
532
534
  end
533
535
 
@@ -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.0".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
 
@@ -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
@@ -217,16 +201,28 @@ module Puma
217
201
  end
218
202
 
219
203
  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
204
+ @binder.close_listeners
205
+ end
206
+
207
+ def close_binder_unix_paths
208
+ @binder.close_unix_paths
226
209
  end
227
210
 
228
211
  private
229
212
 
213
+ # If configured, write the pid of the current process out
214
+ # to a file.
215
+ def write_pid
216
+ path = @options[:pidfile]
217
+ return unless path
218
+
219
+ File.open(path, 'w') { |f| f.puts Process.pid }
220
+ cur = Process.pid
221
+ at_exit do
222
+ delete_pidfile if cur == Process.pid
223
+ end
224
+ end
225
+
230
226
  def reload_worker_directory
231
227
  @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
232
228
  end
@@ -246,48 +242,71 @@ module Puma
246
242
  Dir.chdir(@restart_dir)
247
243
  Kernel.exec(*argv)
248
244
  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
245
  argv = restart_args
256
246
  Dir.chdir(@restart_dir)
257
- argv += [redirects]
247
+ argv += [@binder.redirects_for_restart]
258
248
  Kernel.exec(*argv)
259
249
  end
260
250
  end
261
251
 
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) }
252
+ def dependencies_and_files_to_require_after_prune
253
+ puma = spec_for_gem("puma")
254
+
255
+ deps = puma.runtime_dependencies.map do |d|
256
+ "#{d.name}:#{spec_for_gem(d.name).version}"
257
+ end
258
+
259
+ [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
260
+ end
261
+
262
+ def extra_runtime_deps_directories
263
+ Array(@options[:extra_runtime_dependencies]).map do |d_name|
264
+ if (spec = spec_for_gem(d_name))
265
+ require_paths_for_gem(spec)
266
+ else
267
+ log "* Could not load extra dependency: #{d_name}"
268
+ nil
269
+ end
270
+ end.flatten.compact
271
+ end
272
+
273
+ def puma_wild_location
274
+ puma = spec_for_gem("puma")
275
+ dirs = require_paths_for_gem(puma)
266
276
  puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
277
+ File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
278
+ end
267
279
 
268
- unless puma_lib_dir
280
+ def prune_bundler
281
+ return unless defined?(Bundler)
282
+ require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
283
+ unless puma_wild_location
269
284
  log "! Unable to prune Bundler environment, continuing"
270
285
  return
271
286
  end
272
287
 
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
288
+ deps, dirs = dependencies_and_files_to_require_after_prune
277
289
 
278
290
  log '* Pruning Bundler environment'
279
291
  home = ENV['GEM_HOME']
280
292
  Bundler.with_clean_env do
281
293
  ENV['GEM_HOME'] = home
282
294
  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
295
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
285
296
  # Ruby 2.0+ defaults to true which breaks socket activation
286
297
  args += [{:close_others => false}]
287
298
  Kernel.exec(*args)
288
299
  end
289
300
  end
290
301
 
302
+ def spec_for_gem(gem_name)
303
+ Bundler.rubygems.loaded_specs(gem_name)
304
+ end
305
+
306
+ def require_paths_for_gem(gem_spec)
307
+ gem_spec.full_require_paths
308
+ end
309
+
291
310
  def log(str)
292
311
  @events.log str
293
312
  end
@@ -307,6 +326,21 @@ module Puma
307
326
  log "- Goodbye!"
308
327
  end
309
328
 
329
+ def log_thread_status
330
+ Thread.list.each do |thread|
331
+ log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
332
+ logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
333
+ logstr += " #{thread.name}" if thread.respond_to?(:name)
334
+ log logstr
335
+
336
+ if thread.backtrace
337
+ log thread.backtrace.join("\n")
338
+ else
339
+ log "<no backtrace available>"
340
+ end
341
+ end
342
+ end
343
+
310
344
  def set_process_title
311
345
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
312
346
  end
@@ -406,12 +440,6 @@ module Puma
406
440
 
407
441
  begin
408
442
  Signal.trap "SIGINT" do
409
- if Puma.jruby?
410
- @status = :exit
411
- graceful_stop
412
- exit
413
- end
414
-
415
443
  stop
416
444
  end
417
445
  rescue Exception
@@ -429,6 +457,22 @@ module Puma
429
457
  rescue Exception
430
458
  log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
431
459
  end
460
+
461
+ begin
462
+ Signal.trap "SIGINFO" do
463
+ log_thread_status
464
+ end
465
+ rescue Exception
466
+ # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
467
+ # to see this constantly on Linux.
468
+ end
469
+ end
470
+
471
+ def require_rubygems_min_version!(min_version, feature)
472
+ return if min_version <= Gem::Version.new(Gem::VERSION)
473
+
474
+ raise "#{feature} is not supported on your version of RubyGems. " \
475
+ "You must have RubyGems #{min_version}+ to use this feature."
432
476
  end
433
477
  end
434
478
  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