puma 5.0.4 → 5.5.1

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +250 -48
  3. data/README.md +90 -24
  4. data/docs/architecture.md +57 -20
  5. data/docs/compile_options.md +21 -0
  6. data/docs/deployment.md +53 -67
  7. data/docs/fork_worker.md +2 -0
  8. data/docs/jungle/rc.d/README.md +1 -1
  9. data/docs/kubernetes.md +66 -0
  10. data/docs/plugins.md +15 -15
  11. data/docs/rails_dev_mode.md +28 -0
  12. data/docs/restart.md +7 -7
  13. data/docs/signals.md +10 -10
  14. data/docs/stats.md +142 -0
  15. data/docs/systemd.md +85 -66
  16. data/ext/puma_http11/extconf.rb +36 -6
  17. data/ext/puma_http11/http11_parser.c +64 -59
  18. data/ext/puma_http11/http11_parser.h +1 -1
  19. data/ext/puma_http11/http11_parser.java.rl +1 -1
  20. data/ext/puma_http11/http11_parser.rl +1 -1
  21. data/ext/puma_http11/http11_parser_common.rl +1 -1
  22. data/ext/puma_http11/mini_ssl.c +177 -84
  23. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +39 -41
  24. data/ext/puma_http11/puma_http11.c +8 -2
  25. data/lib/puma/app/status.rb +4 -7
  26. data/lib/puma/binder.rb +121 -46
  27. data/lib/puma/cli.rb +9 -0
  28. data/lib/puma/client.rb +58 -19
  29. data/lib/puma/cluster/worker.rb +19 -16
  30. data/lib/puma/cluster/worker_handle.rb +9 -2
  31. data/lib/puma/cluster.rb +46 -22
  32. data/lib/puma/configuration.rb +18 -2
  33. data/lib/puma/const.rb +14 -4
  34. data/lib/puma/control_cli.rb +76 -71
  35. data/lib/puma/detect.rb +14 -10
  36. data/lib/puma/dsl.rb +143 -26
  37. data/lib/puma/error_logger.rb +12 -5
  38. data/lib/puma/events.rb +18 -3
  39. data/lib/puma/json_serialization.rb +96 -0
  40. data/lib/puma/launcher.rb +54 -6
  41. data/lib/puma/minissl/context_builder.rb +6 -0
  42. data/lib/puma/minissl.rb +54 -38
  43. data/lib/puma/null_io.rb +12 -0
  44. data/lib/puma/plugin.rb +1 -1
  45. data/lib/puma/queue_close.rb +7 -7
  46. data/lib/puma/rack/builder.rb +1 -1
  47. data/lib/puma/reactor.rb +19 -12
  48. data/lib/puma/request.rb +45 -16
  49. data/lib/puma/runner.rb +38 -13
  50. data/lib/puma/server.rb +62 -123
  51. data/lib/puma/state_file.rb +5 -3
  52. data/lib/puma/systemd.rb +46 -0
  53. data/lib/puma/thread_pool.rb +10 -7
  54. data/lib/puma/util.rb +8 -1
  55. data/lib/puma.rb +36 -10
  56. data/lib/rack/handler/puma.rb +1 -0
  57. metadata +15 -9
data/lib/puma/cluster.rb CHANGED
@@ -43,6 +43,7 @@ module Puma
43
43
  end
44
44
 
45
45
  def start_phased_restart
46
+ @events.fire_on_restart!
46
47
  @phase += 1
47
48
  log "- Starting phased worker restart, phase: #{@phase}"
48
49
 
@@ -114,7 +115,7 @@ module Puma
114
115
  debug "Workers to cull: #{workers_to_cull.inspect}"
115
116
 
116
117
  workers_to_cull.each do |worker|
117
- log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
118
+ log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
118
119
  worker.term
119
120
  end
120
121
  end
@@ -163,16 +164,6 @@ module Puma
163
164
  ].compact.min
164
165
  end
165
166
 
166
- def wakeup!
167
- return unless @wakeup
168
-
169
- begin
170
- @wakeup.write "!" unless @wakeup.closed?
171
- rescue SystemCallError, IOError
172
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
173
- end
174
- end
175
-
176
167
  def worker(index, master)
177
168
  @workers = []
178
169
 
@@ -317,7 +308,7 @@ module Puma
317
308
 
318
309
  stop_workers
319
310
  stop
320
-
311
+ @events.fire_on_stopped!
321
312
  raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
322
313
  exit 0 # Clean exit, workers were stopped
323
314
  end
@@ -329,15 +320,25 @@ module Puma
329
320
 
330
321
  output_header "cluster"
331
322
 
332
- log "* Process workers: #{@options[:workers]}"
333
-
334
- before = Thread.list
323
+ # This is aligned with the output from Runner, see Runner#output_header
324
+ log "* Workers: #{@options[:workers]}"
335
325
 
336
326
  if preload?
327
+ # Threads explicitly marked as fork safe will be ignored. Used in Rails,
328
+ # but may be used by anyone. Note that we need to explicit
329
+ # Process::Waiter check here because there's a bug in Ruby 2.6 and below
330
+ # where calling thread_variable_get on a Process::Waiter will segfault.
331
+ # We can drop that clause once those versions of Ruby are no longer
332
+ # supported.
333
+ fork_safe = ->(t) { !t.is_a?(Process::Waiter) && t.thread_variable_get(:fork_safe) }
334
+
335
+ before = Thread.list.reject(&fork_safe)
336
+
337
+ log "* Restarts: (\u2714) hot (\u2716) phased"
337
338
  log "* Preloading application"
338
339
  load_and_bind
339
340
 
340
- after = Thread.list
341
+ after = Thread.list.reject(&fork_safe)
341
342
 
342
343
  if after.size > before.size
343
344
  threads = (after - before)
@@ -351,7 +352,7 @@ module Puma
351
352
  end
352
353
  end
353
354
  else
354
- log "* Phased restart available"
355
+ log "* Restarts: (\u2714) hot (\u2714) phased"
355
356
 
356
357
  unless @launcher.config.app_configured?
357
358
  error "No application configured, nothing to run"
@@ -378,6 +379,8 @@ module Puma
378
379
 
379
380
  log "Use Ctrl-C to stop"
380
381
 
382
+ single_worker_warning
383
+
381
384
  redirect_io
382
385
 
383
386
  Plugins.fire_background
@@ -399,19 +402,21 @@ module Puma
399
402
 
400
403
  begin
401
404
  booted = false
405
+ in_phased_restart = false
406
+ workers_not_booted = @options[:workers]
402
407
 
403
408
  while @status == :run
404
409
  begin
405
410
  if @phased_restart
406
411
  start_phased_restart
407
412
  @phased_restart = false
413
+ in_phased_restart = true
414
+ workers_not_booted = @options[:workers]
408
415
  end
409
416
 
410
417
  check_workers
411
418
 
412
- res = IO.select([read], nil, nil, [0, @next_check - Time.now].max)
413
-
414
- if res
419
+ if read.wait_readable([0, @next_check - Time.now].max)
415
420
  req = read.read_nonblock(1)
416
421
 
417
422
  @next_check = Time.now if req == "!"
@@ -430,8 +435,9 @@ module Puma
430
435
  case req
431
436
  when "b"
432
437
  w.boot!
433
- log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
438
+ log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
434
439
  @next_check = Time.now
440
+ workers_not_booted -= 1
435
441
  when "e"
436
442
  # external term, see worker method, Signal.trap "SIGTERM"
437
443
  w.instance_variable_set :@term, true
@@ -449,6 +455,10 @@ module Puma
449
455
  log "! Out-of-sync worker list, no #{pid} worker"
450
456
  end
451
457
  end
458
+ if in_phased_restart && workers_not_booted.zero?
459
+ @events.fire_on_booted!
460
+ in_phased_restart = false
461
+ end
452
462
 
453
463
  rescue Interrupt
454
464
  @status = :stop
@@ -466,6 +476,15 @@ module Puma
466
476
 
467
477
  private
468
478
 
479
+ def single_worker_warning
480
+ return if @options[:workers] != 1 || @options[:silence_single_worker_warning]
481
+
482
+ log "! WARNING: Detected running cluster mode with 1 worker."
483
+ log "! Running Puma in cluster mode with a single worker is often a misconfiguration."
484
+ log "! Consider running Puma in single-mode (workers = 0) in order to reduce memory overhead."
485
+ log "! Set the `silence_single_worker_warning` option to silence this warning message."
486
+ end
487
+
469
488
  # loops thru @workers, removing workers that exited, and calling
470
489
  # `#term` if needed
471
490
  def wait_workers
@@ -495,7 +514,12 @@ module Puma
495
514
  def timeout_workers
496
515
  @workers.each do |w|
497
516
  if !w.term? && w.ping_timeout <= Time.now
498
- log "! Terminating timed out worker: #{w.pid}"
517
+ details = if w.booted?
518
+ "(worker failed to check in within #{@options[:worker_timeout]} seconds)"
519
+ else
520
+ "(worker failed to boot within #{@options[:worker_boot_timeout]} seconds)"
521
+ end
522
+ log "! Terminating timed out worker #{details}: #{w.pid}"
499
523
  w.kill
500
524
  end
501
525
  end
@@ -92,6 +92,12 @@ module Puma
92
92
  end
93
93
  end
94
94
  end
95
+
96
+ def final_options
97
+ default_options
98
+ .merge(file_options)
99
+ .merge(user_options)
100
+ end
95
101
  end
96
102
 
97
103
  # The main configuration class of Puma.
@@ -187,18 +193,22 @@ module Puma
187
193
  :debug => false,
188
194
  :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
189
195
  :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
196
+ :silence_single_worker_warning => false,
190
197
  :mode => :http,
191
198
  :worker_timeout => DefaultWorkerTimeout,
192
199
  :worker_boot_timeout => DefaultWorkerTimeout,
193
200
  :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
194
201
  :remote_address => :socket,
195
202
  :tag => method(:infer_tag),
196
- :environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] || "development" },
203
+ :environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
197
204
  :rackup => DefaultRackup,
198
205
  :logger => STDOUT,
199
206
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
200
207
  :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
201
- :raise_exception_on_sigterm => true
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,
202
212
  }
203
213
  end
204
214
 
@@ -289,6 +299,10 @@ module Puma
289
299
  end
290
300
  end
291
301
 
302
+ def final_options
303
+ @options.final_options
304
+ end
305
+
292
306
  def self.temp_path
293
307
  require 'tmpdir'
294
308
 
@@ -329,6 +343,8 @@ module Puma
329
343
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
330
344
 
331
345
  rack_app, rack_options = rack_builder.parse_file(rackup)
346
+ rack_options = rack_options || {}
347
+
332
348
  @options.file_options.merge!(rack_options)
333
349
 
334
350
  config_ru_binds = []
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 = "5.0.4".freeze
104
- CODE_NAME = "Spoony Bard".freeze
103
+ PUMA_VERSION = VERSION = "5.5.1".freeze
104
+ CODE_NAME = "Zawgyi".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
107
107
 
@@ -228,7 +228,6 @@ module Puma
228
228
  COLON = ": ".freeze
229
229
 
230
230
  NEWLINE = "\n".freeze
231
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
232
231
 
233
232
  HIJACK_P = "rack.hijack?".freeze
234
233
  HIJACK = "rack.hijack".freeze
@@ -236,8 +235,19 @@ module Puma
236
235
 
237
236
  EARLY_HINTS = "rack.early_hints".freeze
238
237
 
239
- # Mininum interval to checks worker health
238
+ # Minimum interval to checks worker health
240
239
  WORKER_CHECK_INTERVAL = 5
241
240
 
241
+ # Illegal character in the key or value of response header
242
+ DQUOTE = "\"".freeze
243
+ HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
244
+ ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
245
+ # header values can contain HTAB?
246
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
247
+
248
+ # Banned keys of response header
249
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
250
+
251
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
242
252
  end
243
253
  end
@@ -11,10 +11,32 @@ require 'socket'
11
11
  module Puma
12
12
  class ControlCLI
13
13
 
14
- COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats thread-backtraces refork}
14
+ # values must be string or nil
15
+ # value of `nil` means command cannot be processed via signal
16
+ # @version 5.0.3
17
+ CMD_PATH_SIG_MAP = {
18
+ 'gc' => nil,
19
+ 'gc-stats' => nil,
20
+ 'halt' => 'SIGQUIT',
21
+ 'phased-restart' => 'SIGUSR1',
22
+ 'refork' => 'SIGURG',
23
+ 'reload-worker-directory' => nil,
24
+ 'restart' => 'SIGUSR2',
25
+ 'start' => nil,
26
+ 'stats' => nil,
27
+ 'status' => '',
28
+ 'stop' => 'SIGTERM',
29
+ 'thread-backtraces' => nil
30
+ }.freeze
31
+
32
+ # @deprecated 6.0.0
33
+ COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
34
+
35
+ # commands that cannot be used in a request
36
+ NO_REQ_COMMANDS = %w{refork}.freeze
15
37
 
16
38
  # @version 5.0.0
17
- PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}
39
+ PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
18
40
 
19
41
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
20
42
  @state = nil
@@ -25,7 +47,7 @@ module Puma
25
47
  @control_auth_token = nil
26
48
  @config_file = nil
27
49
  @command = nil
28
- @environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
50
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
29
51
 
30
52
  @argv = argv.dup
31
53
  @stdout = stdout
@@ -33,7 +55,7 @@ module Puma
33
55
  @cli_options = {}
34
56
 
35
57
  opts = OptionParser.new do |o|
36
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
58
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
37
59
 
38
60
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
39
61
  @state = arg
@@ -74,7 +96,7 @@ module Puma
74
96
  end
75
97
 
76
98
  o.on_tail("-V", "--version", "Show version") do
77
- puts Const::PUMA_VERSION
99
+ @stdout.puts Const::PUMA_VERSION
78
100
  exit
79
101
  end
80
102
  end
@@ -86,10 +108,10 @@ module Puma
86
108
 
87
109
  # check presence of command
88
110
  unless @command
89
- raise "Available commands: #{COMMANDS.join(", ")}"
111
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
90
112
  end
91
113
 
92
- unless COMMANDS.include? @command
114
+ unless CMD_PATH_SIG_MAP.key? @command
93
115
  raise "Invalid command: #{@command}"
94
116
  end
95
117
 
@@ -134,7 +156,7 @@ module Puma
134
156
  @pid = sf.pid
135
157
  elsif @pidfile
136
158
  # get pid from pid_file
137
- File.open(@pidfile) { |f| @pid = f.read.to_i }
159
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
138
160
  end
139
161
  end
140
162
 
@@ -142,24 +164,29 @@ module Puma
142
164
  uri = URI.parse @control_url
143
165
 
144
166
  # create server object by scheme
145
- server = case uri.scheme
146
- when "ssl"
147
- require 'openssl'
148
- OpenSSL::SSL::SSLSocket.new(
149
- TCPSocket.new(uri.host, uri.port),
150
- OpenSSL::SSL::SSLContext.new)
151
- .tap { |ssl| ssl.sync_close = true } # default is false
152
- .tap(&:connect)
153
- when "tcp"
154
- TCPSocket.new uri.host, uri.port
155
- when "unix"
156
- UNIXSocket.new "#{uri.host}#{uri.path}"
157
- else
158
- raise "Invalid scheme: #{uri.scheme}"
159
- end
160
-
161
- if @command == "status"
162
- message "Puma is started"
167
+ server =
168
+ case uri.scheme
169
+ when 'ssl'
170
+ require 'openssl'
171
+ OpenSSL::SSL::SSLSocket.new(
172
+ TCPSocket.new(uri.host, uri.port),
173
+ OpenSSL::SSL::SSLContext.new)
174
+ .tap { |ssl| ssl.sync_close = true } # default is false
175
+ .tap(&:connect)
176
+ when 'tcp'
177
+ TCPSocket.new uri.host, uri.port
178
+ when 'unix'
179
+ # check for abstract UNIXSocket
180
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
181
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
182
+ else
183
+ raise "Invalid scheme: #{uri.scheme}"
184
+ end
185
+
186
+ if @command == 'status'
187
+ message 'Puma is started'
188
+ elsif NO_REQ_COMMANDS.include? @command
189
+ raise "Invalid request command: #{@command}"
163
190
  else
164
191
  url = "/#{@command}"
165
192
 
@@ -167,10 +194,10 @@ module Puma
167
194
  url = url + "?token=#{@control_auth_token}"
168
195
  end
169
196
 
170
- server << "GET #{url} HTTP/1.0\r\n\r\n"
197
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
171
198
 
172
199
  unless data = server.read
173
- raise "Server closed connection before responding"
200
+ raise 'Server closed connection before responding'
174
201
  end
175
202
 
176
203
  response = data.split("\r\n")
@@ -179,13 +206,13 @@ module Puma
179
206
  raise "Server sent empty response"
180
207
  end
181
208
 
182
- (@http,@code,@message) = response.first.split(" ",3)
209
+ @http, @code, @message = response.first.split(' ',3)
183
210
 
184
- if @code == "403"
185
- raise "Unauthorized access to server (wrong auth token)"
186
- elsif @code == "404"
211
+ if @code == '403'
212
+ raise 'Unauthorized access to server (wrong auth token)'
213
+ elsif @code == '404'
187
214
  raise "Command error: #{response.last}"
188
- elsif @code != "200"
215
+ elsif @code != '200'
189
216
  raise "Bad response from server: #{@code}"
190
217
  end
191
218
 
@@ -194,7 +221,7 @@ module Puma
194
221
  end
195
222
  ensure
196
223
  if server
197
- if uri.scheme == "ssl"
224
+ if uri.scheme == 'ssl'
198
225
  server.sysclose
199
226
  else
200
227
  server.close unless server.closed?
@@ -204,51 +231,30 @@ module Puma
204
231
 
205
232
  def send_signal
206
233
  unless @pid
207
- raise "Neither pid nor control url available"
234
+ raise 'Neither pid nor control url available'
208
235
  end
209
236
 
210
237
  begin
238
+ sig = CMD_PATH_SIG_MAP[@command]
211
239
 
212
- case @command
213
- when "restart"
214
- Process.kill "SIGUSR2", @pid
215
-
216
- when "halt"
217
- Process.kill "QUIT", @pid
218
-
219
- when "stop"
220
- Process.kill "SIGTERM", @pid
221
-
222
- when "stats"
223
- puts "Stats not available via pid only"
224
- return
225
-
226
- when "reload-worker-directory"
227
- puts "reload-worker-directory not available via pid only"
240
+ if sig.nil?
241
+ @stdout.puts "'#{@command}' not available via pid only"
242
+ @stdout.flush unless @stdout.sync
228
243
  return
229
-
230
- when "phased-restart"
231
- Process.kill "SIGUSR1", @pid
232
-
233
- when "status"
244
+ elsif sig.start_with? 'SIG'
245
+ Process.kill sig, @pid
246
+ elsif @command == 'status'
234
247
  begin
235
248
  Process.kill 0, @pid
236
- puts "Puma is started"
249
+ @stdout.puts 'Puma is started'
250
+ @stdout.flush unless @stdout.sync
237
251
  rescue Errno::ESRCH
238
- raise "Puma is not running"
252
+ raise 'Puma is not running'
239
253
  end
240
-
241
- return
242
-
243
- when "refork"
244
- Process.kill "SIGURG", @pid
245
-
246
- else
247
254
  return
248
255
  end
249
-
250
256
  rescue SystemCallError
251
- if @command == "restart"
257
+ if @command == 'restart'
252
258
  start
253
259
  else
254
260
  raise "No pid '#{@pid}' found"
@@ -259,14 +265,13 @@ module Puma
259
265
  end
260
266
 
261
267
  def run
262
- return start if @command == "start"
263
-
268
+ return start if @command == 'start'
264
269
  prepare_configuration
265
270
 
266
- if Puma.windows?
271
+ if Puma.windows? || @control_url
267
272
  send_request
268
273
  else
269
- @control_url ? send_request : send_signal
274
+ send_signal
270
275
  end
271
276
 
272
277
  rescue => e
data/lib/puma/detect.rb CHANGED
@@ -1,32 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # This file can be loaded independently of puma.rb, so it cannot have any code
4
+ # that assumes puma.rb is loaded.
5
+
6
+
3
7
  module Puma
4
- # at present, MiniSSL::Engine is only defined in extension code, not in minissl.rb
5
- HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
8
+ # @version 5.2.1
9
+ HAS_FORK = ::Process.respond_to? :fork
6
10
 
7
- def self.ssl?
8
- HAS_SSL
9
- end
11
+ IS_JRUBY = Object.const_defined? :JRUBY_VERSION
10
12
 
11
- IS_JRUBY = defined?(JRUBY_VERSION)
13
+ IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/ ||
14
+ IS_JRUBY && RUBY_DESCRIPTION =~ /mswin/)
15
+
16
+ # @version 5.2.0
17
+ IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
12
18
 
13
19
  def self.jruby?
14
20
  IS_JRUBY
15
21
  end
16
22
 
17
- IS_WINDOWS = RUBY_PLATFORM =~ /mswin|ming|cygwin/
18
-
19
23
  def self.windows?
20
24
  IS_WINDOWS
21
25
  end
22
26
 
23
27
  # @version 5.0.0
24
28
  def self.mri?
25
- RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?
29
+ IS_MRI
26
30
  end
27
31
 
28
32
  # @version 5.0.0
29
33
  def self.forkable?
30
- ::Process.respond_to?(:fork)
34
+ HAS_FORK
31
35
  end
32
36
  end