puma 6.6.1-java → 7.0.0-java

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.
@@ -110,7 +110,6 @@ module Puma
110
110
  begin
111
111
  @worker_write << "#{PIPE_BOOT}#{Process.pid}:#{index}\n"
112
112
  rescue SystemCallError, IOError
113
- Puma::Util.purge_interrupt_queue
114
113
  STDERR.puts "Master seems to have exited, exiting."
115
114
  return
116
115
  end
@@ -128,16 +127,16 @@ module Puma
128
127
 
129
128
  while true
130
129
  begin
131
- b = server.backlog || 0
132
- r = server.running || 0
133
- t = server.pool_capacity || 0
134
- m = server.max_threads || 0
135
- rc = server.requests_count || 0
136
- bt = server.busy_threads || 0
137
- payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads":#{m}, "requests_count":#{rc}, "busy_threads":#{bt} }\n!
138
- io << payload
130
+ payload = base_payload.dup
131
+
132
+ hsh = server.stats
133
+ hsh.each do |k, v|
134
+ payload << %Q! "#{k}":#{v || 0},!
135
+ end
136
+ # sub call properly adds 'closing' string
137
+ io << payload.sub(/,\z/, " }\n")
138
+ server.reset_max
139
139
  rescue IOError
140
- Puma::Util.purge_interrupt_queue
141
140
  break
142
141
  end
143
142
  sleep @options[:worker_check_interval]
@@ -4,13 +4,15 @@ module Puma
4
4
  class Cluster < Runner
5
5
  #—————————————————————— DO NOT USE — this class is for internal use only ———
6
6
 
7
-
8
7
  # This class represents a worker process from the perspective of the puma
9
8
  # master process. It contains information about the process and its health
10
9
  # and it exposes methods to control the process via IPC. It does not
11
10
  # include the actual logic executed by the worker process itself. For that,
12
11
  # see Puma::Cluster::Worker.
13
12
  class WorkerHandle # :nodoc:
13
+ # array of stat 'max' keys
14
+ WORKER_MAX_KEYS = [:backlog_max, :reactor_max]
15
+
14
16
  def initialize(idx, pid, phase, options)
15
17
  @index = idx
16
18
  @pid = pid
@@ -23,6 +25,7 @@ module Puma
23
25
  @last_checkin = Time.now
24
26
  @last_status = {}
25
27
  @term = false
28
+ @worker_max = Array.new WORKER_MAX_KEYS.length, 0
26
29
  end
27
30
 
28
31
  attr_reader :index, :pid, :phase, :signal, :last_checkin, :last_status, :started_at
@@ -51,12 +54,38 @@ module Puma
51
54
  @term
52
55
  end
53
56
 
54
- STATUS_PATTERN = /{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads":(?<max_threads>\d*), "requests_count":(?<requests_count>\d*), "busy_threads":(?<busy_threads>\d*) }/
55
- private_constant :STATUS_PATTERN
56
-
57
57
  def ping!(status)
58
+ hsh = {}
59
+ k, v = nil, nil
60
+ status.tr('}{"', '').strip.split(", ") do |kv|
61
+ cntr = 0
62
+ kv.split(':') do |t|
63
+ if cntr == 0
64
+ k = t
65
+ cntr = 1
66
+ else
67
+ v = t
68
+ end
69
+ end
70
+ hsh[k.to_sym] = v.to_i
71
+ end
72
+
73
+ # check stat max values, we can't signal workers to reset the max values,
74
+ # so we do so here
75
+ WORKER_MAX_KEYS.each_with_index do |key, idx|
76
+ if hsh[key] < @worker_max[idx]
77
+ hsh[key] = @worker_max[idx]
78
+ else
79
+ @worker_max[idx] = hsh[key]
80
+ end
81
+ end
58
82
  @last_checkin = Time.now
59
- @last_status = status.match(STATUS_PATTERN).named_captures.map { |c_name, c| [c_name.to_sym, c.to_i] }.to_h
83
+ @last_status = hsh
84
+ end
85
+
86
+ # Resets max values to zero. Called whenever `Cluster#stats` is called
87
+ def reset_max
88
+ WORKER_MAX_KEYS.length.times { |idx| @worker_max[idx] = 0 }
60
89
  end
61
90
 
62
91
  # @see Puma::Cluster#check_workers
data/lib/puma/cluster.rb CHANGED
@@ -22,6 +22,7 @@ module Puma
22
22
  @workers = []
23
23
  @next_check = Time.now
24
24
 
25
+ @worker_max = [] # keeps track of 'max' stat values
25
26
  @phased_restart = false
26
27
  end
27
28
 
@@ -45,8 +46,7 @@ module Puma
45
46
  end
46
47
 
47
48
  def start_phased_restart(refork = false)
48
- @events.fire_on_restart!
49
-
49
+ @events.fire_before_restart!
50
50
  @phase += 1
51
51
  if refork
52
52
  log "- Starting worker refork, phase: #{@phase}"
@@ -268,11 +268,14 @@ module Puma
268
268
  end
269
269
 
270
270
  # Inside of a child process, this will return all zeroes, as @workers is only populated in
271
- # the master process.
271
+ # the master process. Calling this also resets stat 'max' values to zero.
272
272
  # @!attribute [r] stats
273
+ # @return [Hash]
274
+
273
275
  def stats
274
276
  old_worker_count = @workers.count { |w| w.phase != @phase }
275
277
  worker_status = @workers.map do |w|
278
+ w.reset_max
276
279
  {
277
280
  started_at: utc_iso8601(w.started_at),
278
281
  pid: w.pid,
@@ -283,7 +286,6 @@ module Puma
283
286
  last_status: w.last_status,
284
287
  }
285
288
  end
286
-
287
289
  {
288
290
  started_at: utc_iso8601(@started_at),
289
291
  workers: @workers.size,
@@ -352,7 +354,7 @@ module Puma
352
354
 
353
355
  stop_workers
354
356
  stop
355
- @events.fire_on_stopped!
357
+ @events.fire_after_stopped!
356
358
  raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
357
359
  exit 0 # Clean exit, workers were stopped
358
360
  end
@@ -369,12 +371,8 @@ module Puma
369
371
 
370
372
  if preload?
371
373
  # Threads explicitly marked as fork safe will be ignored. Used in Rails,
372
- # but may be used by anyone. Note that we need to explicit
373
- # Process::Waiter check here because there's a bug in Ruby 2.6 and below
374
- # where calling thread_variable_get on a Process::Waiter will segfault.
375
- # We can drop that clause once those versions of Ruby are no longer
376
- # supported.
377
- fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) }
374
+ # but may be used by anyone.
375
+ fork_safe = ->(t) { t.thread_variable_get(:fork_safe) }
378
376
 
379
377
  before = Thread.list.reject(&fork_safe)
380
378
 
@@ -423,6 +421,7 @@ module Puma
423
421
 
424
422
  log "Use Ctrl-C to stop"
425
423
 
424
+ warn_ruby_mn_threads
426
425
  single_worker_warning
427
426
 
428
427
  redirect_io
@@ -511,7 +510,7 @@ module Puma
511
510
  end
512
511
 
513
512
  if !booted && @workers.none? {|worker| worker.last_status.empty?}
514
- @events.fire_on_booted!
513
+ @events.fire_after_booted!
515
514
  debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
516
515
  booted = true
517
516
  end
@@ -528,7 +527,7 @@ module Puma
528
527
  end
529
528
 
530
529
  if in_phased_restart && workers_not_booted.zero?
531
- @events.fire_on_booted!
530
+ @events.fire_after_booted!
532
531
  debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
533
532
  in_phased_restart = false
534
533
  end
@@ -29,13 +29,13 @@ module Puma
29
29
 
30
30
  CONTENT_LENGTH = 'Content-Length' # should be lower case from app,
31
31
  # Util::HeaderHash allows mixed
32
- HTTP_VERSION = Const::HTTP_VERSION
33
32
  HTTP_X_FORWARDED_FOR = Const::HTTP_X_FORWARDED_FOR
34
33
  PATH_INFO = Const::PATH_INFO
35
34
  QUERY_STRING = Const::QUERY_STRING
36
35
  REMOTE_ADDR = Const::REMOTE_ADDR
37
36
  REMOTE_USER = 'REMOTE_USER'
38
37
  REQUEST_METHOD = Const::REQUEST_METHOD
38
+ SERVER_PROTOCOL = Const::SERVER_PROTOCOL
39
39
 
40
40
  def initialize(app, logger=nil)
41
41
  @app = app
@@ -70,7 +70,7 @@ module Puma
70
70
  env[REQUEST_METHOD],
71
71
  env[PATH_INFO],
72
72
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
73
- env[HTTP_VERSION],
73
+ env[SERVER_PROTOCOL],
74
74
  now - began_at ]
75
75
 
76
76
  write(msg)
@@ -87,7 +87,7 @@ module Puma
87
87
  env[REQUEST_METHOD],
88
88
  env[PATH_INFO],
89
89
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
90
- env[HTTP_VERSION],
90
+ env[SERVER_PROTOCOL],
91
91
  status.to_s[0..3],
92
92
  length,
93
93
  now - began_at ]
@@ -3,6 +3,7 @@
3
3
  require_relative 'plugin'
4
4
  require_relative 'const'
5
5
  require_relative 'dsl'
6
+ require_relative 'events'
6
7
 
7
8
  module Puma
8
9
  # A class used for storing "leveled" configuration options.
@@ -112,7 +113,7 @@ module Puma
112
113
  # config = Configuration.new({}) do |user_config, file_config, default_config|
113
114
  # user_config.port 3003
114
115
  # end
115
- # config.load
116
+ # config.clamp
116
117
  # puts config.options[:port]
117
118
  # # => 3003
118
119
  #
@@ -125,10 +126,13 @@ module Puma
125
126
  # is done because an environment variable may have been modified while loading
126
127
  # configuration files.
127
128
  class Configuration
129
+ class NotLoadedError < StandardError; end
130
+ class NotClampedError < StandardError; end
131
+
128
132
  DEFAULTS = {
129
133
  auto_trim_time: 30,
130
134
  binds: ['tcp://0.0.0.0:9292'.freeze],
131
- clean_thread_locals: false,
135
+ fiber_per_request: !!ENV.fetch("PUMA_FIBER_PER_REQUEST", false),
132
136
  debug: false,
133
137
  enable_keep_alives: true,
134
138
  early_hints: nil,
@@ -140,19 +144,17 @@ module Puma
140
144
  io_selector_backend: :auto,
141
145
  log_requests: false,
142
146
  logger: STDOUT,
143
- # How many requests to attempt inline before sending a client back to
144
- # the reactor to be subject to normal ordering. The idea here is that
145
- # we amortize the cost of going back to the reactor for a well behaved
146
- # but very "greedy" client across 10 requests. This prevents a not
147
- # well behaved client from monopolizing the thread forever.
148
- max_fast_inline: 10,
147
+ # Limits how many requests a keep alive connection can make.
148
+ # The connection will be closed after it reaches `max_keep_alive`
149
+ # requests.
150
+ max_keep_alive: 999,
149
151
  max_threads: Puma.mri? ? 5 : 16,
150
152
  min_threads: 0,
151
153
  mode: :http,
152
154
  mutate_stdout_and_stderr_to_sync_on_write: true,
153
155
  out_of_band: [],
154
156
  # Number of seconds for another request within a persistent session.
155
- persistent_timeout: 20,
157
+ persistent_timeout: ENV.fetch('PUMA_PERSISTENT_TIMEOUT', 65),
156
158
  queue_requests: true,
157
159
  rackup: 'config.ru'.freeze,
158
160
  raise_exception_on_sigterm: true,
@@ -176,24 +178,31 @@ module Puma
176
178
  def initialize(user_options={}, default_options = {}, env = ENV, &block)
177
179
  default_options = self.puma_default_options(env).merge(default_options)
178
180
 
179
- @options = UserFileDefaultOptions.new(user_options, default_options)
181
+ @_options = UserFileDefaultOptions.new(user_options, default_options)
180
182
  @plugins = PluginLoader.new
181
- @user_dsl = DSL.new(@options.user_options, self)
182
- @file_dsl = DSL.new(@options.file_options, self)
183
- @default_dsl = DSL.new(@options.default_options, self)
184
-
185
- if !@options[:prune_bundler]
186
- default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
187
- end
183
+ @events = @_options[:events] || Events.new
184
+ @hooks = {}
185
+ @user_dsl = DSL.new(@_options.user_options, self)
186
+ @file_dsl = DSL.new(@_options.file_options, self)
187
+ @default_dsl = DSL.new(@_options.default_options, self)
188
188
 
189
189
  @puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
190
190
 
191
191
  if block
192
192
  configure(&block)
193
193
  end
194
+
195
+ @loaded = false
196
+ @clamped = false
194
197
  end
195
198
 
196
- attr_reader :options, :plugins
199
+ attr_reader :plugins, :events, :hooks
200
+
201
+ def options
202
+ raise NotClampedError, "ensure clamp is called before accessing options" unless @clamped
203
+
204
+ @_options
205
+ end
197
206
 
198
207
  def configure
199
208
  yield @user_dsl, @file_dsl, @default_dsl
@@ -206,7 +215,7 @@ module Puma
206
215
  def initialize_copy(other)
207
216
  @conf = nil
208
217
  @cli_options = nil
209
- @options = @options.dup
218
+ @_options = @_options.dup
210
219
  end
211
220
 
212
221
  def flatten
@@ -214,7 +223,7 @@ module Puma
214
223
  end
215
224
 
216
225
  def flatten!
217
- @options = @options.flatten
226
+ @_options = @_options.flatten
218
227
  self
219
228
  end
220
229
 
@@ -243,18 +252,20 @@ module Puma
243
252
  end
244
253
 
245
254
  def load
255
+ @loaded = true
246
256
  config_files.each { |config_file| @file_dsl._load_from(config_file) }
247
-
248
- @options
257
+ @_options
249
258
  end
250
259
 
251
260
  def config_files
252
- files = @options.all_of(:config_files)
261
+ raise NotLoadedError, "ensure load is called before accessing config_files" unless @loaded
262
+
263
+ files = @_options.all_of(:config_files)
253
264
 
254
265
  return [] if files == ['-']
255
266
  return files if files.any?
256
267
 
257
- first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
268
+ first_default_file = %W(config/puma/#{@_options[:environment]}.rb config/puma.rb).find do |f|
258
269
  File.exist?(f)
259
270
  end
260
271
 
@@ -262,9 +273,16 @@ module Puma
262
273
  end
263
274
 
264
275
  # Call once all configuration (included from rackup files)
265
- # is loaded to flesh out any defaults
276
+ # is loaded to finalize defaults and lock in the configuration.
277
+ #
278
+ # This also calls load if it hasn't been called yet.
266
279
  def clamp
267
- @options.finalize_values
280
+ load unless @loaded
281
+ set_conditional_default_options
282
+ @_options.finalize_values
283
+ @clamped = true
284
+ warn_hooks
285
+ options
268
286
  end
269
287
 
270
288
  # Injects the Configuration object into the env
@@ -283,11 +301,11 @@ module Puma
283
301
  # Indicate if there is a properly configured app
284
302
  #
285
303
  def app_configured?
286
- @options[:app] || File.exist?(rackup)
304
+ options[:app] || File.exist?(rackup)
287
305
  end
288
306
 
289
307
  def rackup
290
- @options[:rackup]
308
+ options[:rackup]
291
309
  end
292
310
 
293
311
  # Load the specified rackup file, pull options from
@@ -296,9 +314,9 @@ module Puma
296
314
  def app
297
315
  found = options[:app] || load_rackup
298
316
 
299
- if @options[:log_requests]
317
+ if options[:log_requests]
300
318
  require_relative 'commonlogger'
301
- logger = @options[:logger]
319
+ logger = options[:custom_logger] ? options[:custom_logger] : options[:logger]
302
320
  found = CommonLogger.new(found, logger)
303
321
  end
304
322
 
@@ -307,7 +325,7 @@ module Puma
307
325
 
308
326
  # Return which environment we're running in
309
327
  def environment
310
- @options[:environment]
328
+ options[:environment]
311
329
  end
312
330
 
313
331
  def load_plugin(name)
@@ -315,18 +333,19 @@ module Puma
315
333
  end
316
334
 
317
335
  # @param key [:Symbol] hook to run
318
- # @param arg [Launcher, Int] `:on_restart` passes Launcher
336
+ # @param arg [Launcher, Int] `:before_restart` passes Launcher
319
337
  #
320
338
  def run_hooks(key, arg, log_writer, hook_data = nil)
321
339
  log_writer.debug "Running #{key} hooks"
322
340
 
323
- @options.all_of(key).each do |b|
341
+ options.all_of(key).each do |hook_options|
324
342
  begin
325
- if Array === b
326
- hook_data[b[1]] ||= Hash.new
327
- b[0].call arg, hook_data[b[1]]
343
+ block = hook_options[:block]
344
+ if id = hook_options[:id]
345
+ hook_data[id] ||= Hash.new
346
+ block.call arg, hook_data[id]
328
347
  else
329
- b.call arg
348
+ block.call arg
330
349
  end
331
350
  rescue => e
332
351
  log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
@@ -336,7 +355,7 @@ module Puma
336
355
  end
337
356
 
338
357
  def final_options
339
- @options.final_options
358
+ options.final_options
340
359
  end
341
360
 
342
361
  def self.temp_path
@@ -346,6 +365,12 @@ module Puma
346
365
  "#{Dir.tmpdir}/puma-status-#{t}-#{$$}"
347
366
  end
348
367
 
368
+ def self.random_token
369
+ require 'securerandom' unless defined?(SecureRandom)
370
+
371
+ SecureRandom.hex(16)
372
+ end
373
+
349
374
  private
350
375
 
351
376
  def require_processor_counter
@@ -386,22 +411,40 @@ module Puma
386
411
  rack_app, rack_options = rack_builder.parse_file(rackup)
387
412
  rack_options = rack_options || {}
388
413
 
389
- @options.file_options.merge!(rack_options)
414
+ options.file_options.merge!(rack_options)
390
415
 
391
416
  config_ru_binds = []
392
417
  rack_options.each do |k, v|
393
418
  config_ru_binds << v if k.to_s.start_with?("bind")
394
419
  end
395
420
 
396
- @options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
421
+ options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
397
422
 
398
423
  rack_app
399
424
  end
400
425
 
401
- def self.random_token
402
- require 'securerandom' unless defined?(SecureRandom)
426
+ def set_conditional_default_options
427
+ @_options.default_options[:preload_app] = !@_options[:prune_bundler] &&
428
+ (@_options[:workers] > 1) && Puma.forkable?
429
+ end
403
430
 
404
- SecureRandom.hex(16)
431
+ def warn_hooks
432
+ return if options[:workers] > 0
433
+ return if options[:silence_fork_callback_warning]
434
+
435
+ log_writer = LogWriter.stdio
436
+ @hooks.each_key do |hook|
437
+ options.all_of(hook).each do |hook_options|
438
+ next unless hook_options[:cluster_only]
439
+
440
+ log_writer.log(<<~MSG.tr("\n", " "))
441
+ Warning: The code in the `#{hook}` block will not execute
442
+ in the current Puma configuration. The `#{hook}` block only
443
+ executes in Puma's cluster mode. To fix this, either remove the
444
+ `#{hook}` call or increase Puma's worker count above zero.
445
+ MSG
446
+ end
447
+ end
405
448
  end
406
449
  end
407
450
  end
data/lib/puma/const.rb CHANGED
@@ -100,13 +100,11 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "6.6.1"
104
- CODE_NAME = "Return to Forever"
103
+ PUMA_VERSION = VERSION = "7.0.0"
104
+ CODE_NAME = "Romantic Warrior"
105
105
 
106
106
  PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
107
107
 
108
- FAST_TRACK_KA_TIMEOUT = 0.2
109
-
110
108
  # How long to wait when getting some write blocking on the socket when
111
109
  # sending data back
112
110
  WRITE_TIMEOUT = 10
@@ -125,9 +123,9 @@ module Puma
125
123
  # Indicate that we couldn't parse the request
126
124
  400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
127
125
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
128
- 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n",
126
+ 404 => "HTTP/1.1 404 Not Found\r\nconnection: close\r\n\r\n",
129
127
  # The standard empty 408 response for requests that timed out.
130
- 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n",
128
+ 408 => "HTTP/1.1 408 Request Timeout\r\nconnection: close\r\n\r\n",
131
129
  # Indicate that there was an internal error, obviously.
132
130
  500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
133
131
  # Incorrect or invalid header value
@@ -230,6 +228,7 @@ module Puma
230
228
  RACK_INPUT = "rack.input"
231
229
  RACK_URL_SCHEME = "rack.url_scheme"
232
230
  RACK_AFTER_REPLY = "rack.after_reply"
231
+ RACK_RESPONSE_FINISHED = "rack.response_finished"
233
232
  PUMA_SOCKET = "puma.socket"
234
233
  PUMA_CONFIG = "puma.config"
235
234
  PUMA_PEERCERT = "puma.peercert"
@@ -252,14 +251,14 @@ module Puma
252
251
  KEEP_ALIVE = "keep-alive"
253
252
 
254
253
  CONTENT_LENGTH2 = "content-length"
255
- CONTENT_LENGTH_S = "Content-Length: "
254
+ CONTENT_LENGTH_S = "content-length: "
256
255
  TRANSFER_ENCODING = "transfer-encoding"
257
256
  TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
258
257
 
259
- CONNECTION_CLOSE = "Connection: close\r\n"
260
- CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
258
+ CONNECTION_CLOSE = "connection: close\r\n"
259
+ CONNECTION_KEEP_ALIVE = "connection: keep-alive\r\n"
261
260
 
262
- TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
261
+ TRANSFER_ENCODING_CHUNKED = "transfer-encoding: chunked\r\n"
263
262
  CLOSE_CHUNKED = "0\r\n\r\n"
264
263
 
265
264
  CHUNKED = "chunked"
@@ -128,7 +128,8 @@ module Puma
128
128
  require_relative 'log_writer'
129
129
 
130
130
  config = Puma::Configuration.new({ config_files: [@config_file] }, {} , env)
131
- config.load
131
+ config.clamp
132
+
132
133
  @state ||= config.options[:state]
133
134
  @control_url ||= config.options[:control_url]
134
135
  @control_auth_token ||= config.options[:control_auth_token]
@@ -248,7 +249,7 @@ module Puma
248
249
  @stdout.flush unless @stdout.sync
249
250
  return
250
251
  elsif sig.start_with? 'SIG'
251
- if Signal.list.key? sig.sub(/\ASIG/, '')
252
+ if Signal.list.key? sig.delete_prefix('SIG')
252
253
  Process.kill sig, @pid
253
254
  else
254
255
  raise "Signal '#{sig}' not available'"
data/lib/puma/detect.rb CHANGED
@@ -18,6 +18,8 @@ module Puma
18
18
 
19
19
  IS_LINUX = !(IS_OSX || IS_WINDOWS)
20
20
 
21
+ IS_ARM = RUBY_PLATFORM.include? 'aarch64'
22
+
21
23
  # @version 5.2.0
22
24
  IS_MRI = RUBY_ENGINE == 'ruby'
23
25