puma 3.12.6 → 4.3.10

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +145 -3
  3. data/README.md +76 -48
  4. data/docs/architecture.md +1 -0
  5. data/docs/deployment.md +24 -4
  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/plugins.md +20 -10
  10. data/docs/restart.md +4 -2
  11. data/docs/systemd.md +27 -9
  12. data/docs/tcp_mode.md +96 -0
  13. data/ext/puma_http11/PumaHttp11Service.java +2 -0
  14. data/ext/puma_http11/extconf.rb +13 -0
  15. data/ext/puma_http11/http11_parser.c +58 -70
  16. data/ext/puma_http11/http11_parser.java.rl +21 -37
  17. data/ext/puma_http11/http11_parser_common.rl +4 -4
  18. data/ext/puma_http11/mini_ssl.c +78 -8
  19. data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
  20. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +86 -99
  21. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  22. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -4
  23. data/ext/puma_http11/puma_http11.c +3 -0
  24. data/lib/puma/accept_nonblock.rb +7 -1
  25. data/lib/puma/app/status.rb +37 -29
  26. data/lib/puma/binder.rb +38 -60
  27. data/lib/puma/cli.rb +4 -0
  28. data/lib/puma/client.rb +242 -208
  29. data/lib/puma/cluster.rb +53 -30
  30. data/lib/puma/configuration.rb +4 -3
  31. data/lib/puma/const.rb +22 -18
  32. data/lib/puma/control_cli.rb +30 -5
  33. data/lib/puma/dsl.rb +299 -75
  34. data/lib/puma/events.rb +4 -1
  35. data/lib/puma/io_buffer.rb +1 -6
  36. data/lib/puma/launcher.rb +95 -53
  37. data/lib/puma/minissl/context_builder.rb +76 -0
  38. data/lib/puma/minissl.rb +35 -17
  39. data/lib/puma/plugin/tmp_restart.rb +2 -0
  40. data/lib/puma/plugin.rb +5 -2
  41. data/lib/puma/rack/builder.rb +2 -0
  42. data/lib/puma/rack/urlmap.rb +2 -0
  43. data/lib/puma/rack_default.rb +2 -0
  44. data/lib/puma/reactor.rb +110 -57
  45. data/lib/puma/runner.rb +11 -3
  46. data/lib/puma/server.rb +73 -57
  47. data/lib/puma/single.rb +3 -3
  48. data/lib/puma/thread_pool.rb +15 -33
  49. data/lib/puma/util.rb +1 -6
  50. data/lib/puma.rb +8 -0
  51. data/lib/rack/handler/puma.rb +3 -3
  52. data/tools/docker/Dockerfile +16 -0
  53. data/tools/jungle/init.d/puma +6 -6
  54. data/tools/trickletest.rb +0 -1
  55. metadata +26 -13
  56. data/lib/puma/compat.rb +0 -14
  57. data/lib/puma/convenient.rb +0 -25
  58. data/lib/puma/daemon_ext.rb +0 -33
  59. data/lib/puma/delegation.rb +0 -13
  60. data/lib/puma/java_io_buffer.rb +0 -47
  61. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/cluster.rb CHANGED
@@ -19,8 +19,6 @@ module Puma
19
19
  # via the `spawn_workers` method call. Each worker will have it's own
20
20
  # instance of a `Puma::Server`.
21
21
  class Cluster < Runner
22
- WORKER_CHECK_INTERVAL = 5
23
-
24
22
  def initialize(cli, events)
25
23
  super cli, events
26
24
 
@@ -37,7 +35,11 @@ module Puma
37
35
  @workers.each { |x| x.term }
38
36
 
39
37
  begin
40
- @workers.each { |w| Process.waitpid(w.pid) }
38
+ loop do
39
+ wait_workers
40
+ break if @workers.empty?
41
+ sleep 0.2
42
+ end
41
43
  rescue Interrupt
42
44
  log "! Cancelled waiting for workers"
43
45
  end
@@ -69,12 +71,13 @@ module Puma
69
71
  @signal = "TERM"
70
72
  @options = options
71
73
  @first_term_sent = nil
74
+ @started_at = Time.now
72
75
  @last_checkin = Time.now
73
76
  @last_status = '{}'
74
- @dead = false
77
+ @term = false
75
78
  end
76
79
 
77
- attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status
80
+ attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
78
81
 
79
82
  def booted?
80
83
  @stage == :booted
@@ -85,12 +88,8 @@ module Puma
85
88
  @stage = :booted
86
89
  end
87
90
 
88
- def dead?
89
- @dead
90
- end
91
-
92
- def dead!
93
- @dead = true
91
+ def term?
92
+ @term
94
93
  end
95
94
 
96
95
  def ping!(status)
@@ -107,9 +106,9 @@ module Puma
107
106
  if @first_term_sent && (Time.now - @first_term_sent) > @options[:worker_shutdown_timeout]
108
107
  @signal = "KILL"
109
108
  else
109
+ @term ||= true
110
110
  @first_term_sent ||= Time.now
111
111
  end
112
-
113
112
  Process.kill @signal, @pid
114
113
  rescue Errno::ESRCH
115
114
  end
@@ -183,7 +182,7 @@ module Puma
183
182
  def check_workers(force=false)
184
183
  return if !force && @next_check && @next_check >= Time.now
185
184
 
186
- @next_check = Time.now + WORKER_CHECK_INTERVAL
185
+ @next_check = Time.now + Const::WORKER_CHECK_INTERVAL
187
186
 
188
187
  any = false
189
188
 
@@ -200,15 +199,7 @@ module Puma
200
199
  # during this loop by giving the kernel time to kill them.
201
200
  sleep 1 if any
202
201
 
203
- while @workers.any?
204
- pid = Process.waitpid(-1, Process::WNOHANG)
205
- break unless pid
206
-
207
- @workers.delete_if { |w| w.pid == pid }
208
- end
209
-
210
- @workers.delete_if(&:dead?)
211
-
202
+ wait_workers
212
203
  cull_workers
213
204
  spawn_workers
214
205
 
@@ -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,10 +282,11 @@ 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
293
- sleep WORKER_CHECK_INTERVAL
289
+ sleep Const::WORKER_CHECK_INTERVAL
294
290
  begin
295
291
  b = server.backlog || 0
296
292
  r = server.running || 0
@@ -352,11 +348,13 @@ 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? }
358
- 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(",") + ']'
359
- %Q!{ "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
356
+ worker_status = '[' + @workers.map { |w| %Q!{ "started_at": "#{w.started_at.utc.iso8601}", "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(",") + ']'
357
+ %Q!{ "started_at": "#{@started_at.utc.iso8601}", "workers": #{@workers.size}, "phase": #{@phase}, "booted_workers": #{booted_worker_count}, "old_workers": #{old_worker_count}, "worker_status": #{worker_status} }!
360
358
  end
361
359
 
362
360
  def preload?
@@ -390,10 +388,13 @@ module Puma
390
388
  log "Early termination of worker"
391
389
  exit! 0
392
390
  else
391
+ @launcher.close_binder_listeners
392
+
393
393
  stop_workers
394
394
  stop
395
395
 
396
- raise SignalException, "SIGTERM"
396
+ raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
397
+ exit 0 # Clean exit, workers were stopped
397
398
  end
398
399
  end
399
400
  end
@@ -487,7 +488,7 @@ module Puma
487
488
 
488
489
  force_check = false
489
490
 
490
- res = IO.select([read], nil, nil, WORKER_CHECK_INTERVAL)
491
+ res = IO.select([read], nil, nil, Const::WORKER_CHECK_INTERVAL)
491
492
 
492
493
  if res
493
494
  req = read.read_nonblock(1)
@@ -503,8 +504,11 @@ module Puma
503
504
  w.boot!
504
505
  log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
505
506
  force_check = true
507
+ when "e"
508
+ # external term, see worker method, Signal.trap "SIGTERM"
509
+ w.instance_variable_set :@term, true
506
510
  when "t"
507
- w.dead!
511
+ w.term unless w.term?
508
512
  force_check = true
509
513
  when "p"
510
514
  w.ping!(result.sub(/^\d+/,'').chomp)
@@ -527,5 +531,24 @@ module Puma
527
531
  @wakeup.close
528
532
  end
529
533
  end
534
+
535
+ private
536
+
537
+ # loops thru @workers, removing workers that exited, and calling
538
+ # `#term` if needed
539
+ def wait_workers
540
+ @workers.reject! do |w|
541
+ begin
542
+ if Process.wait(w.pid, Process::WNOHANG)
543
+ true
544
+ else
545
+ w.term if w.term?
546
+ nil
547
+ end
548
+ rescue Errno::ECHILD
549
+ true # child is already terminated
550
+ end
551
+ end
552
+ end
530
553
  end
531
554
  end
@@ -20,7 +20,7 @@ module Puma
20
20
  # In this class any "user" specified options take precedence over any
21
21
  # "file" specified options, take precedence over any "default" options.
22
22
  #
23
- # User input is prefered over "defaults":
23
+ # User input is preferred over "defaults":
24
24
  # user_options = { foo: "bar" }
25
25
  # default_options = { foo: "zoo" }
26
26
  # options = UserFileDefaultOptions.new(user_options, default_options)
@@ -32,7 +32,7 @@ module Puma
32
32
  # puts options.all_of(:foo)
33
33
  # # => ["bar", "zoo"]
34
34
  #
35
- # A "file" option can be set. This config will be prefered over "default" options
35
+ # A "file" option can be set. This config will be preferred over "default" options
36
36
  # but will defer to any available "user" specified options.
37
37
  #
38
38
  # user_options = { foo: "bar" }
@@ -186,7 +186,8 @@ module Puma
186
186
  :rackup => DefaultRackup,
187
187
  :logger => STDOUT,
188
188
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
189
- :first_data_timeout => Const::FIRST_DATA_TIMEOUT
189
+ :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
190
+ :raise_exception_on_sigterm => true
190
191
  }
191
192
  end
192
193
 
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "3.12.6".freeze
104
- CODE_NAME = "Llamas in Pajamas".freeze
103
+ PUMA_VERSION = VERSION = "4.3.10".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
@@ -129,27 +129,24 @@ module Puma
129
129
  REQUEST_URI= 'REQUEST_URI'.freeze
130
130
  REQUEST_PATH = 'REQUEST_PATH'.freeze
131
131
  QUERY_STRING = 'QUERY_STRING'.freeze
132
+ CONTENT_LENGTH = "CONTENT_LENGTH".freeze
132
133
 
133
134
  PATH_INFO = 'PATH_INFO'.freeze
134
135
 
135
136
  PUMA_TMP_BASE = "puma".freeze
136
137
 
137
- # Indicate that we couldn't parse the request
138
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
139
-
140
- # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
141
- ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
142
-
143
- # The standard empty 408 response for requests that timed out.
144
- ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze
145
-
146
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
147
-
148
- # Indicate that there was an internal error, obviously.
149
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
150
-
151
- # A common header for indicating the server is too busy. Not used yet.
152
- 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
+ }
153
150
 
154
151
  # The basic max request size we'll try to read.
155
152
  CHUNK_SIZE = 16 * 1024
@@ -167,6 +164,9 @@ module Puma
167
164
  LINE_END = "\r\n".freeze
168
165
  REMOTE_ADDR = "REMOTE_ADDR".freeze
169
166
  HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
167
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
168
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
169
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
170
170
 
171
171
  SERVER_NAME = "SERVER_NAME".freeze
172
172
  SERVER_PORT = "SERVER_PORT".freeze
@@ -235,5 +235,9 @@ module Puma
235
235
  HIJACK_IO = "rack.hijack_io".freeze
236
236
 
237
237
  EARLY_HINTS = "rack.early_hints".freeze
238
+
239
+ # Mininum interval to checks worker health
240
+ WORKER_CHECK_INTERVAL = 5
241
+
238
242
  end
239
243
  end
@@ -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
@@ -101,7 +111,6 @@ module Puma
101
111
 
102
112
  rescue => e
103
113
  @stdout.puts e.message
104
- @stdout.puts e.backtrace
105
114
  exit 1
106
115
  end
107
116
 
@@ -123,7 +132,7 @@ module Puma
123
132
  @pid = sf.pid
124
133
  elsif @pidfile
125
134
  # get pid from pid_file
126
- @pid = File.open(@pidfile).gets.to_i
135
+ File.open(@pidfile) { |f| @pid = f.read.to_i }
127
136
  end
128
137
  end
129
138
 
@@ -132,6 +141,12 @@ module Puma
132
141
 
133
142
  # create server object by scheme
134
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)
135
150
  when "tcp"
136
151
  TCPSocket.new uri.host, uri.port
137
152
  when "unix"
@@ -206,6 +221,16 @@ module Puma
206
221
  when "phased-restart"
207
222
  Process.kill "SIGUSR1", @pid
208
223
 
224
+ when "status"
225
+ begin
226
+ Process.kill 0, @pid
227
+ puts "Puma is started"
228
+ rescue Errno::ESRCH
229
+ raise "Puma is not running"
230
+ end
231
+
232
+ return
233
+
209
234
  else
210
235
  return
211
236
  end
@@ -234,7 +259,6 @@ module Puma
234
259
 
235
260
  rescue => e
236
261
  message e.message
237
- message e.backtrace
238
262
  exit 1
239
263
  end
240
264
 
@@ -250,6 +274,7 @@ module Puma
250
274
  run_args += ["--control-url", @control_url] if @control_url
251
275
  run_args += ["--control-token", @control_auth_token] if @control_auth_token
252
276
  run_args += ["-C", @config_file] if @config_file
277
+ run_args += ["-e", @environment] if @environment
253
278
 
254
279
  events = Puma::Events.new @stdout, @stderr
255
280