puma 4.1.1 → 4.3.6

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.3.6".freeze
104
+ CODE_NAME = "Mysterious Traveller".freeze
105
105
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
106
 
107
107
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -118,31 +118,35 @@ module Puma
118
118
  # sending data back
119
119
  WRITE_TIMEOUT = 10
120
120
 
121
+ # How many requests to attempt inline before sending a client back to
122
+ # the reactor to be subject to normal ordering. The idea here is that
123
+ # we amortize the cost of going back to the reactor for a well behaved
124
+ # but very "greedy" client across 10 requests. This prevents a not
125
+ # well behaved client from monopolizing the thread forever.
126
+ MAX_FAST_INLINE = 10
127
+
121
128
  # The original URI requested by the client.
122
129
  REQUEST_URI= 'REQUEST_URI'.freeze
123
130
  REQUEST_PATH = 'REQUEST_PATH'.freeze
124
131
  QUERY_STRING = 'QUERY_STRING'.freeze
132
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
125
133
 
126
134
  PATH_INFO = 'PATH_INFO'.freeze
127
135
 
128
136
  PUMA_TMP_BASE = "puma".freeze
129
137
 
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
138
+ ERROR_RESPONSE = {
139
+ # Indicate that we couldn't parse the request
140
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
141
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
142
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
143
+ # The standard empty 408 response for requests that timed out.
144
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
145
+ # Indicate that there was an internal error, obviously.
146
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
147
+ # A common header for indicating the server is too busy. Not used yet.
148
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
149
+ }
146
150
 
147
151
  # The basic max request size we'll try to read.
148
152
  CHUNK_SIZE = 16 * 1024
@@ -224,6 +228,7 @@ module Puma
224
228
  COLON = ": ".freeze
225
229
 
226
230
  NEWLINE = "\n".freeze
231
+ HTTP_INJECTION_REGEX = /[\r\n]/.freeze
227
232
 
228
233
  HIJACK_P = "rack.hijack?".freeze
229
234
  HIJACK = "rack.hijack".freeze
@@ -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']
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,12 @@ 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
+ environment = @environment || 'development'
86
+
87
+ if @config_file.nil?
88
+ @config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
89
+ File.exist?(f)
90
+ end
81
91
  end
82
92
 
83
93
  if @config_file
@@ -122,7 +132,7 @@ module Puma
122
132
  @pid = sf.pid
123
133
  elsif @pidfile
124
134
  # get pid from pid_file
125
- @pid = File.open(@pidfile).gets.to_i
135
+ File.open(@pidfile) { |f| @pid = f.read.to_i }
126
136
  end
127
137
  end
128
138
 
@@ -131,6 +141,12 @@ module Puma
131
141
 
132
142
  # create server object by scheme
133
143
  server = case uri.scheme
144
+ when "ssl"
145
+ require 'openssl'
146
+ OpenSSL::SSL::SSLSocket.new(
147
+ TCPSocket.new(uri.host, uri.port),
148
+ OpenSSL::SSL::SSLContext.new
149
+ ).tap(&:connect)
134
150
  when "tcp"
135
151
  TCPSocket.new uri.host, uri.port
136
152
  when "unix"
@@ -258,6 +274,7 @@ module Puma
258
274
  run_args += ["--control-url", @control_url] if @control_url
259
275
  run_args += ["--control-token", @control_auth_token] if @control_auth_token
260
276
  run_args += ["-C", @config_file] if @config_file
277
+ run_args += ["-e", @environment] if @environment
261
278
 
262
279
  events = Puma::Events.new @stdout, @stderr
263
280
 
@@ -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)
@@ -583,7 +583,10 @@ module Puma
583
583
  # new Bundler context and thus can float around as the release
584
584
  # dictates.
585
585
  #
586
+ # See also: extra_runtime_dependencies
587
+ #
586
588
  # @note This is incompatible with +preload_app!+.
589
+ # @note This is only supported for RubyGems 2.2+
587
590
  def prune_bundler(answer=true)
588
591
  @options[:prune_bundler] = answer
589
592
  end
@@ -601,6 +604,21 @@ module Puma
601
604
  @options[:raise_exception_on_sigterm] = answer
602
605
  end
603
606
 
607
+ # When using prune_bundler, if extra runtime dependencies need to be loaded to
608
+ # initialize your app, then this setting can be used. This includes any Puma plugins.
609
+ #
610
+ # Before bundler is pruned, the gem names supplied will be looked up in the bundler
611
+ # context and then loaded again after bundler is pruned.
612
+ # Only applies if prune_bundler is used.
613
+ #
614
+ # @example
615
+ # extra_runtime_dependencies ['gem_name_1', 'gem_name_2']
616
+ # @example
617
+ # extra_runtime_dependencies ['puma_worker_killer', 'puma-heroku']
618
+ def extra_runtime_dependencies(answer = [])
619
+ @options[:extra_runtime_dependencies] = Array(answer)
620
+ end
621
+
604
622
  # Additional text to display in process listing.
605
623
  #
606
624
  # 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