puma 5.3.2 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +284 -11
  3. data/LICENSE +0 -0
  4. data/README.md +61 -16
  5. data/bin/puma-wild +1 -1
  6. data/docs/architecture.md +49 -16
  7. data/docs/compile_options.md +38 -2
  8. data/docs/deployment.md +53 -67
  9. data/docs/fork_worker.md +1 -3
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +0 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +15 -15
  19. data/docs/rails_dev_mode.md +2 -3
  20. data/docs/restart.md +6 -6
  21. data/docs/signals.md +11 -10
  22. data/docs/stats.md +8 -8
  23. data/docs/systemd.md +64 -67
  24. data/docs/testing_benchmarks_local_files.md +150 -0
  25. data/docs/testing_test_rackup_ci_files.md +36 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +44 -13
  29. data/ext/puma_http11/http11_parser.c +24 -11
  30. data/ext/puma_http11/http11_parser.h +1 -1
  31. data/ext/puma_http11/http11_parser.java.rl +2 -2
  32. data/ext/puma_http11/http11_parser.rl +2 -2
  33. data/ext/puma_http11/http11_parser_common.rl +3 -3
  34. data/ext/puma_http11/mini_ssl.c +122 -23
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +50 -48
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
  39. data/ext/puma_http11/puma_http11.c +18 -10
  40. data/lib/puma/app/status.rb +9 -6
  41. data/lib/puma/binder.rb +81 -42
  42. data/lib/puma/cli.rb +23 -19
  43. data/lib/puma/client.rb +124 -30
  44. data/lib/puma/cluster/worker.rb +21 -29
  45. data/lib/puma/cluster/worker_handle.rb +8 -1
  46. data/lib/puma/cluster.rb +57 -48
  47. data/lib/puma/commonlogger.rb +0 -0
  48. data/lib/puma/configuration.rb +74 -55
  49. data/lib/puma/const.rb +21 -24
  50. data/lib/puma/control_cli.rb +22 -19
  51. data/lib/puma/detect.rb +10 -2
  52. data/lib/puma/dsl.rb +196 -57
  53. data/lib/puma/error_logger.rb +17 -9
  54. data/lib/puma/events.rb +6 -126
  55. data/lib/puma/io_buffer.rb +29 -4
  56. data/lib/puma/jruby_restart.rb +2 -1
  57. data/lib/puma/{json.rb → json_serialization.rb} +1 -1
  58. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  59. data/lib/puma/launcher.rb +108 -154
  60. data/lib/puma/log_writer.rb +137 -0
  61. data/lib/puma/minissl/context_builder.rb +29 -16
  62. data/lib/puma/minissl.rb +115 -38
  63. data/lib/puma/null_io.rb +5 -0
  64. data/lib/puma/plugin/tmp_restart.rb +1 -1
  65. data/lib/puma/plugin.rb +2 -2
  66. data/lib/puma/rack/builder.rb +5 -5
  67. data/lib/puma/rack/urlmap.rb +0 -0
  68. data/lib/puma/rack_default.rb +1 -1
  69. data/lib/puma/reactor.rb +3 -3
  70. data/lib/puma/request.rb +293 -153
  71. data/lib/puma/runner.rb +63 -28
  72. data/lib/puma/server.rb +83 -88
  73. data/lib/puma/single.rb +10 -10
  74. data/lib/puma/state_file.rb +39 -7
  75. data/lib/puma/systemd.rb +3 -2
  76. data/lib/puma/thread_pool.rb +22 -17
  77. data/lib/puma/util.rb +20 -15
  78. data/lib/puma.rb +12 -9
  79. data/lib/rack/handler/puma.rb +9 -9
  80. data/tools/Dockerfile +1 -1
  81. data/tools/trickletest.rb +0 -0
  82. metadata +13 -9
  83. data/lib/puma/queue_close.rb +0 -26
data/lib/puma/cluster.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/runner'
4
- require 'puma/util'
5
- require 'puma/plugin'
6
- require 'puma/cluster/worker_handle'
7
- require 'puma/cluster/worker'
3
+ require_relative 'runner'
4
+ require_relative 'util'
5
+ require_relative 'plugin'
6
+ require_relative 'cluster/worker_handle'
7
+ require_relative 'cluster/worker'
8
8
 
9
9
  require 'time'
10
10
 
@@ -17,8 +17,8 @@ module Puma
17
17
  # via the `spawn_workers` method call. Each worker will have it's own
18
18
  # instance of a `Puma::Server`.
19
19
  class Cluster < Runner
20
- def initialize(cli, events)
21
- super cli, events
20
+ def initialize(launcher)
21
+ super(launcher)
22
22
 
23
23
  @phase = 0
24
24
  @workers = []
@@ -27,6 +27,10 @@ module Puma
27
27
  @phased_restart = false
28
28
  end
29
29
 
30
+ # Returns the list of cluster worker handles.
31
+ # @return [Array<Puma::Cluster::WorkerHandle>]
32
+ attr_reader :workers
33
+
30
34
  def stop_workers
31
35
  log "- Gracefully shutting down workers..."
32
36
  @workers.each { |x| x.term }
@@ -92,7 +96,7 @@ module Puma
92
96
 
93
97
  # @version 5.0.0
94
98
  def spawn_worker(idx, master)
95
- @launcher.config.run_hooks :before_worker_fork, idx, @launcher.events
99
+ @config.run_hooks(:before_worker_fork, idx, @log_writer)
96
100
 
97
101
  pid = fork { worker(idx, master) }
98
102
  if !pid
@@ -101,31 +105,49 @@ module Puma
101
105
  exit! 1
102
106
  end
103
107
 
104
- @launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
108
+ @config.run_hooks(:after_worker_fork, idx, @log_writer)
105
109
  pid
106
110
  end
107
111
 
108
112
  def cull_workers
109
113
  diff = @workers.size - @options[:workers]
110
114
  return if diff < 1
115
+ debug "Culling #{diff} workers"
111
116
 
112
- debug "Culling #{diff.inspect} workers"
113
-
114
- workers_to_cull = @workers[-diff,diff]
115
- debug "Workers to cull: #{workers_to_cull.inspect}"
117
+ workers = workers_to_cull(diff)
118
+ debug "Workers to cull: #{workers.inspect}"
116
119
 
117
- workers_to_cull.each do |worker|
120
+ workers.each do |worker|
118
121
  log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
119
122
  worker.term
120
123
  end
121
124
  end
122
125
 
126
+ def workers_to_cull(diff)
127
+ workers = @workers.sort_by(&:started_at)
128
+
129
+ # In fork_worker mode, worker 0 acts as our master process.
130
+ # We should avoid culling it to preserve copy-on-write memory gains.
131
+ workers.reject! { |w| w.index == 0 } if @options[:fork_worker]
132
+
133
+ workers[cull_start_index(diff), diff]
134
+ end
135
+
136
+ def cull_start_index(diff)
137
+ case @options[:worker_culling_strategy]
138
+ when :oldest
139
+ 0
140
+ else # :youngest
141
+ -diff
142
+ end
143
+ end
144
+
123
145
  # @!attribute [r] next_worker_index
124
146
  def next_worker_index
125
- all_positions = 0...@options[:workers]
126
- occupied_positions = @workers.map { |w| w.index }
127
- available_positions = all_positions.to_a - occupied_positions
128
- available_positions.first
147
+ occupied_positions = @workers.map(&:index)
148
+ idx = 0
149
+ idx += 1 until !occupied_positions.include?(idx)
150
+ idx
129
151
  end
130
152
 
131
153
  def all_workers_booted?
@@ -135,7 +157,7 @@ module Puma
135
157
  def check_workers
136
158
  return if @next_check >= Time.now
137
159
 
138
- @next_check = Time.now + Const::WORKER_CHECK_INTERVAL
160
+ @next_check = Time.now + @options[:worker_check_interval]
139
161
 
140
162
  timeout_workers
141
163
  wait_workers
@@ -158,20 +180,10 @@ module Puma
158
180
  end
159
181
  end
160
182
 
161
- @next_check = [
162
- @workers.reject(&:term?).map(&:ping_timeout).min,
163
- @next_check
164
- ].compact.min
165
- end
166
-
167
- def wakeup!
168
- return unless @wakeup
183
+ t = @workers.reject(&:term?)
184
+ t.map!(&:ping_timeout)
169
185
 
170
- begin
171
- @wakeup.write "!" unless @wakeup.closed?
172
- rescue SystemCallError, IOError
173
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
174
- end
186
+ @next_check = [t.min, @next_check].compact.min
175
187
  end
176
188
 
177
189
  def worker(index, master)
@@ -201,8 +213,8 @@ module Puma
201
213
  stop
202
214
  end
203
215
 
204
- def phased_restart
205
- return false if @options[:preload_app]
216
+ def phased_restart(refork = false)
217
+ return false if @options[:preload_app] && !refork
206
218
 
207
219
  @phased_restart = true
208
220
  wakeup!
@@ -218,7 +230,7 @@ module Puma
218
230
  def stop_blocked
219
231
  @status = :stop if @status == :run
220
232
  wakeup!
221
- @control.stop(true) if @control
233
+ @control&.stop true
222
234
  Process.waitall
223
235
  end
224
236
 
@@ -257,7 +269,7 @@ module Puma
257
269
  booted_workers: worker_status.count { |w| w[:booted] },
258
270
  old_workers: old_worker_count,
259
271
  worker_status: worker_status,
260
- }
272
+ }.merge(super)
261
273
  end
262
274
 
263
275
  def preload?
@@ -269,7 +281,7 @@ module Puma
269
281
  if (worker = @workers.find { |w| w.index == 0 })
270
282
  worker.phase += 1
271
283
  end
272
- phased_restart
284
+ phased_restart(true)
273
285
  end
274
286
 
275
287
  # We do this in a separate method to keep the lambda scope
@@ -282,7 +294,7 @@ module Puma
282
294
 
283
295
  # Auto-fork after the specified number of requests.
284
296
  if (fork_requests = @options[:fork_worker].to_i) > 0
285
- @launcher.events.register(:ping!) do |w|
297
+ @events.register(:ping!) do |w|
286
298
  fork_worker! if w.index == 0 &&
287
299
  w.phase == 0 &&
288
300
  w.last_status[:requests_count] >= fork_requests
@@ -364,12 +376,12 @@ module Puma
364
376
  else
365
377
  log "* Restarts: (\u2714) hot (\u2714) phased"
366
378
 
367
- unless @launcher.config.app_configured?
379
+ unless @config.app_configured?
368
380
  error "No application configured, nothing to run"
369
381
  exit 1
370
382
  end
371
383
 
372
- @launcher.binder.parse @options[:binds], self
384
+ @launcher.binder.parse @options[:binds]
373
385
  end
374
386
 
375
387
  read, @wakeup = Puma::Util.pipe
@@ -401,8 +413,7 @@ module Puma
401
413
 
402
414
  @master_read, @worker_write = read, @wakeup
403
415
 
404
- @launcher.config.run_hooks :before_fork, nil, @launcher.events
405
- Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
416
+ @config.run_hooks(:before_fork, nil, @log_writer)
406
417
 
407
418
  spawn_workers
408
419
 
@@ -426,9 +437,7 @@ module Puma
426
437
 
427
438
  check_workers
428
439
 
429
- res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
430
-
431
- if res
440
+ if read.wait_readable([0, @next_check - Time.now].max)
432
441
  req = read.read_nonblock(1)
433
442
 
434
443
  @next_check = Time.now if req == "!"
@@ -452,14 +461,14 @@ module Puma
452
461
  workers_not_booted -= 1
453
462
  when "e"
454
463
  # external term, see worker method, Signal.trap "SIGTERM"
455
- w.instance_variable_set :@term, true
464
+ w.term!
456
465
  when "t"
457
466
  w.term unless w.term?
458
467
  when "p"
459
468
  w.ping!(result.sub(/^\d+/,'').chomp)
460
- @launcher.events.fire(:ping!, w)
469
+ @events.fire(:ping!, w)
461
470
  if !booted && @workers.none? {|worker| worker.last_status.empty?}
462
- @launcher.events.fire_on_booted!
471
+ @events.fire_on_booted!
463
472
  booted = true
464
473
  end
465
474
  end
File without changes
@@ -1,20 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/rack/builder'
4
- require 'puma/plugin'
5
- require 'puma/const'
3
+ require_relative 'rack/builder'
4
+ require_relative 'plugin'
5
+ require_relative 'const'
6
+ # note that dsl is loaded at end of file, requires ConfigDefault constants
6
7
 
7
8
  module Puma
8
-
9
- module ConfigDefault
10
- DefaultRackup = "config.ru"
11
-
12
- DefaultTCPHost = "0.0.0.0"
13
- DefaultTCPPort = 9292
14
- DefaultWorkerTimeout = 60
15
- DefaultWorkerShutdownTimeout = 30
16
- end
17
-
18
9
  # A class used for storing "leveled" configuration options.
19
10
  #
20
11
  # In this class any "user" specified options take precedence over any
@@ -135,7 +126,48 @@ module Puma
135
126
  # is done because an environment variable may have been modified while loading
136
127
  # configuration files.
137
128
  class Configuration
138
- include ConfigDefault
129
+ DEFAULTS = {
130
+ auto_trim_time: 30,
131
+ binds: ['tcp://0.0.0.0:9292'.freeze],
132
+ clean_thread_locals: false,
133
+ debug: false,
134
+ early_hints: nil,
135
+ environment: 'development'.freeze,
136
+ # Number of seconds to wait until we get the first data for the request
137
+ first_data_timeout: 30,
138
+ io_selector_backend: :auto,
139
+ log_requests: false,
140
+ logger: STDOUT,
141
+ # How many requests to attempt inline before sending a client back to
142
+ # the reactor to be subject to normal ordering. The idea here is that
143
+ # we amortize the cost of going back to the reactor for a well behaved
144
+ # but very "greedy" client across 10 requests. This prevents a not
145
+ # well behaved client from monopolizing the thread forever.
146
+ max_fast_inline: 10,
147
+ max_threads: Puma.mri? ? 5 : 16,
148
+ min_threads: 0,
149
+ mode: :http,
150
+ mutate_stdout_and_stderr_to_sync_on_write: true,
151
+ out_of_band: [],
152
+ # Number of seconds for another request within a persistent session.
153
+ persistent_timeout: 20,
154
+ queue_requests: true,
155
+ rackup: 'config.ru'.freeze,
156
+ raise_exception_on_sigterm: true,
157
+ reaping_time: 1,
158
+ remote_address: :socket,
159
+ silence_single_worker_warning: false,
160
+ tag: File.basename(Dir.getwd),
161
+ tcp_host: '0.0.0.0'.freeze,
162
+ tcp_port: 9292,
163
+ wait_for_less_busy_worker: 0.005,
164
+ worker_boot_timeout: 60,
165
+ worker_check_interval: 5,
166
+ worker_culling_strategy: :youngest,
167
+ worker_shutdown_timeout: 30,
168
+ worker_timeout: 60,
169
+ workers: 0,
170
+ }
139
171
 
140
172
  def initialize(user_options={}, default_options = {}, &block)
141
173
  default_options = self.puma_default_options.merge(default_options)
@@ -180,35 +212,22 @@ module Puma
180
212
  self
181
213
  end
182
214
 
183
- # @version 5.0.0
184
- def default_max_threads
185
- Puma.mri? ? 5 : 16
215
+ def puma_default_options
216
+ defaults = DEFAULTS.dup
217
+ puma_options_from_env.each { |k,v| defaults[k] = v if v }
218
+ defaults
186
219
  end
187
220
 
188
- def puma_default_options
221
+ def puma_options_from_env
222
+ min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
223
+ max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
224
+ workers = ENV['WEB_CONCURRENCY']
225
+
189
226
  {
190
- :min_threads => Integer(ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS'] || 0),
191
- :max_threads => Integer(ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS'] || default_max_threads),
192
- :log_requests => false,
193
- :debug => false,
194
- :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
195
- :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
196
- :silence_single_worker_warning => false,
197
- :mode => :http,
198
- :worker_timeout => DefaultWorkerTimeout,
199
- :worker_boot_timeout => DefaultWorkerTimeout,
200
- :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
201
- :remote_address => :socket,
202
- :tag => method(:infer_tag),
203
- :environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] || "development" },
204
- :rackup => DefaultRackup,
205
- :logger => STDOUT,
206
- :persistent_timeout => Const::PERSISTENT_TIMEOUT,
207
- :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
208
- :raise_exception_on_sigterm => true,
209
- :max_fast_inline => Const::MAX_FAST_INLINE,
210
- :io_selector_backend => :auto,
211
- :mutate_stdout_and_stderr_to_sync_on_write => true,
227
+ min_threads: min && Integer(min),
228
+ max_threads: max && Integer(max),
229
+ workers: workers && Integer(workers),
230
+ environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
212
231
  }
213
232
  end
214
233
 
@@ -224,7 +243,7 @@ module Puma
224
243
  return [] if files == ['-']
225
244
  return files if files.any?
226
245
 
227
- first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
246
+ first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
228
247
  File.exist?(f)
229
248
  end
230
249
 
@@ -267,7 +286,7 @@ module Puma
267
286
  found = options[:app] || load_rackup
268
287
 
269
288
  if @options[:log_requests]
270
- require 'puma/commonlogger'
289
+ require_relative 'commonlogger'
271
290
  logger = @options[:logger]
272
291
  found = CommonLogger.new(found, logger)
273
292
  end
@@ -280,21 +299,25 @@ module Puma
280
299
  @options[:environment]
281
300
  end
282
301
 
283
- def environment_str
284
- environment.respond_to?(:call) ? environment.call : environment
285
- end
286
-
287
302
  def load_plugin(name)
288
303
  @plugins.create name
289
304
  end
290
305
 
291
- def run_hooks(key, arg, events)
306
+ # @param key [:Symbol] hook to run
307
+ # @param arg [Launcher, Int] `:on_restart` passes Launcher
308
+ #
309
+ def run_hooks(key, arg, log_writer, hook_data = nil)
292
310
  @options.all_of(key).each do |b|
293
311
  begin
294
- b.call arg
312
+ if Array === b
313
+ hook_data[b[1]] ||= Hash.new
314
+ b[0].call arg, hook_data[b[1]]
315
+ else
316
+ b.call arg
317
+ end
295
318
  rescue => e
296
- events.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
297
- events.debug e.backtrace.join("\n")
319
+ log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
320
+ log_writer.debug e.backtrace.join("\n")
298
321
  end
299
322
  end
300
323
  end
@@ -312,10 +335,6 @@ module Puma
312
335
 
313
336
  private
314
337
 
315
- def infer_tag
316
- File.basename(Dir.getwd)
317
- end
318
-
319
338
  # Load and use the normal Rack builder if we can, otherwise
320
339
  # fallback to our minimal version.
321
340
  def rack_builder
@@ -365,4 +384,4 @@ module Puma
365
384
  end
366
385
  end
367
386
 
368
- require 'puma/dsl'
387
+ require_relative 'dsl'
data/lib/puma/const.rb CHANGED
@@ -76,7 +76,7 @@ module Puma
76
76
  508 => 'Loop Detected',
77
77
  510 => 'Not Extended',
78
78
  511 => 'Network Authentication Required'
79
- }
79
+ }.freeze
80
80
 
81
81
  # For some HTTP status codes the client only expects headers.
82
82
  #
@@ -85,7 +85,7 @@ module Puma
85
85
  204 => true,
86
86
  205 => true,
87
87
  304 => true
88
- }
88
+ }.freeze
89
89
 
90
90
  # Frequently used constants when constructing requests or responses. Many times
91
91
  # the constant just refers to a string with the same contents. Using these constants
@@ -100,32 +100,17 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.3.2".freeze
104
- CODE_NAME = "Sweetnighter".freeze
103
+ PUMA_VERSION = VERSION = "6.0.0".freeze
104
+ CODE_NAME = "Sunflower".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
107
107
 
108
108
  FAST_TRACK_KA_TIMEOUT = 0.2
109
109
 
110
- # The default number of seconds for another request within a persistent
111
- # session.
112
- PERSISTENT_TIMEOUT = 20
113
-
114
- # The default number of seconds to wait until we get the first data
115
- # for the request
116
- FIRST_DATA_TIMEOUT = 30
117
-
118
110
  # How long to wait when getting some write blocking on the socket when
119
111
  # sending data back
120
112
  WRITE_TIMEOUT = 10
121
113
 
122
- # How many requests to attempt inline before sending a client back to
123
- # the reactor to be subject to normal ordering. The idea here is that
124
- # we amortize the cost of going back to the reactor for a well behaved
125
- # but very "greedy" client across 10 requests. This prevents a not
126
- # well behaved client from monopolizing the thread forever.
127
- MAX_FAST_INLINE = 10
128
-
129
114
  # The original URI requested by the client.
130
115
  REQUEST_URI= 'REQUEST_URI'.freeze
131
116
  REQUEST_PATH = 'REQUEST_PATH'.freeze
@@ -145,9 +130,11 @@ module Puma
145
130
  408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
146
131
  # Indicate that there was an internal error, obviously.
147
132
  500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
133
+ # Incorrect or invalid header value
134
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
148
135
  # A common header for indicating the server is too busy. Not used yet.
149
136
  503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
150
- }
137
+ }.freeze
151
138
 
152
139
  # The basic max request size we'll try to read.
153
140
  CHUNK_SIZE = 16 * 1024
@@ -161,6 +148,14 @@ module Puma
161
148
 
162
149
  REQUEST_METHOD = "REQUEST_METHOD".freeze
163
150
  HEAD = "HEAD".freeze
151
+ GET = "GET".freeze
152
+ POST = "POST".freeze
153
+ PUT = "PUT".freeze
154
+ DELETE = "DELETE".freeze
155
+ OPTIONS = "OPTIONS".freeze
156
+ TRACE = "TRACE".freeze
157
+ PATCH = "PATCH".freeze
158
+ SUPPORTED_HTTP_METHODS = [HEAD, GET, POST, PUT, DELETE, OPTIONS, TRACE, PATCH].freeze
164
159
  # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
165
160
  LINE_END = "\r\n".freeze
166
161
  REMOTE_ADDR = "REMOTE_ADDR".freeze
@@ -175,7 +170,10 @@ module Puma
175
170
  PORT_80 = "80".freeze
176
171
  PORT_443 = "443".freeze
177
172
  LOCALHOST = "localhost".freeze
178
- LOCALHOST_IP = "127.0.0.1".freeze
173
+ LOCALHOST_IPV4 = "127.0.0.1".freeze
174
+ LOCALHOST_IPV6 = "::1".freeze
175
+ UNSPECIFIED_IPV4 = "0.0.0.0".freeze
176
+ UNSPECIFIED_IPV6 = "::".freeze
179
177
 
180
178
  SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
181
179
  HTTP_11 = "HTTP/1.1".freeze
@@ -235,9 +233,6 @@ module Puma
235
233
 
236
234
  EARLY_HINTS = "rack.early_hints".freeze
237
235
 
238
- # Minimum interval to checks worker health
239
- WORKER_CHECK_INTERVAL = 5
240
-
241
236
  # Illegal character in the key or value of response header
242
237
  DQUOTE = "\"".freeze
243
238
  HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
@@ -247,5 +242,7 @@ module Puma
247
242
 
248
243
  # Banned keys of response header
249
244
  BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
245
+
246
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
250
247
  end
251
248
  end
@@ -17,26 +17,27 @@ module Puma
17
17
  CMD_PATH_SIG_MAP = {
18
18
  'gc' => nil,
19
19
  'gc-stats' => nil,
20
- 'halt' => 'SIGQUIT',
21
- 'phased-restart' => 'SIGUSR1',
22
- 'refork' => 'SIGURG',
20
+ 'halt' => 'SIGQUIT',
21
+ 'info' => 'SIGINFO',
22
+ 'phased-restart' => 'SIGUSR1',
23
+ 'refork' => 'SIGURG',
23
24
  'reload-worker-directory' => nil,
24
- 'restart' => 'SIGUSR2',
25
+ 'reopen-log' => 'SIGHUP',
26
+ 'restart' => 'SIGUSR2',
25
27
  'start' => nil,
26
28
  'stats' => nil,
27
29
  'status' => '',
28
- 'stop' => 'SIGTERM',
29
- 'thread-backtraces' => nil
30
+ 'stop' => 'SIGTERM',
31
+ 'thread-backtraces' => nil,
32
+ 'worker-count-down' => 'SIGTTOU',
33
+ 'worker-count-up' => 'SIGTTIN'
30
34
  }.freeze
31
35
 
32
- # @deprecated 6.0.0
33
- COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
34
-
35
36
  # commands that cannot be used in a request
36
- NO_REQ_COMMANDS = %w{refork}.freeze
37
+ NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
37
38
 
38
39
  # @version 5.0.0
39
- PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
40
+ PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
40
41
 
41
42
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
42
43
  @state = nil
@@ -47,7 +48,7 @@ module Puma
47
48
  @control_auth_token = nil
48
49
  @config_file = nil
49
50
  @command = nil
50
- @environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
51
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
51
52
 
52
53
  @argv = argv.dup
53
54
  @stdout = stdout
@@ -185,8 +186,6 @@ module Puma
185
186
 
186
187
  if @command == 'status'
187
188
  message 'Puma is started'
188
- elsif NO_REQ_COMMANDS.include? @command
189
- raise "Invalid request command: #{@command}"
190
189
  else
191
190
  url = "/#{@command}"
192
191
 
@@ -242,7 +241,11 @@ module Puma
242
241
  @stdout.flush unless @stdout.sync
243
242
  return
244
243
  elsif sig.start_with? 'SIG'
245
- Process.kill sig, @pid
244
+ if Signal.list.key? sig.sub(/\ASIG/, '')
245
+ Process.kill sig, @pid
246
+ else
247
+ raise "Signal '#{sig}' not available'"
248
+ end
246
249
  elsif @command == 'status'
247
250
  begin
248
251
  Process.kill 0, @pid
@@ -268,7 +271,7 @@ module Puma
268
271
  return start if @command == 'start'
269
272
  prepare_configuration
270
273
 
271
- if Puma.windows? || @control_url
274
+ if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
272
275
  send_request
273
276
  else
274
277
  send_signal
@@ -281,7 +284,7 @@ module Puma
281
284
 
282
285
  private
283
286
  def start
284
- require 'puma/cli'
287
+ require_relative 'cli'
285
288
 
286
289
  run_args = []
287
290
 
@@ -293,13 +296,13 @@ module Puma
293
296
  run_args += ["-C", @config_file] if @config_file
294
297
  run_args += ["-e", @environment] if @environment
295
298
 
296
- events = Puma::Events.new @stdout, @stderr
299
+ log_writer = Puma::LogWriter.new(@stdout, @stderr)
297
300
 
298
301
  # replace $0 because puma use it to generate restart command
299
302
  puma_cmd = $0.gsub(/pumactl$/, 'puma')
300
303
  $0 = puma_cmd if File.exist?(puma_cmd)
301
304
 
302
- cli = Puma::CLI.new run_args, events
305
+ cli = Puma::CLI.new run_args, log_writer
303
306
  cli.run
304
307
  end
305
308
  end
data/lib/puma/detect.rb CHANGED
@@ -8,10 +8,14 @@ module Puma
8
8
  # @version 5.2.1
9
9
  HAS_FORK = ::Process.respond_to? :fork
10
10
 
11
+ HAS_NATIVE_IO_WAIT = ::IO.public_instance_methods(false).include? :wait_readable
12
+
11
13
  IS_JRUBY = Object.const_defined? :JRUBY_VERSION
12
14
 
13
- IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/ ||
14
- IS_JRUBY && RUBY_DESCRIPTION =~ /mswin/)
15
+ IS_OSX = RUBY_PLATFORM.include? 'darwin'
16
+
17
+ IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
18
+ IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
15
19
 
16
20
  # @version 5.2.0
17
21
  IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
@@ -20,6 +24,10 @@ module Puma
20
24
  IS_JRUBY
21
25
  end
22
26
 
27
+ def self.osx?
28
+ IS_OSX
29
+ end
30
+
23
31
  def self.windows?
24
32
  IS_WINDOWS
25
33
  end