puma 2.2.0 → 8.0.2

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.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +3334 -0
  3. data/LICENSE +23 -20
  4. data/README.md +387 -100
  5. data/bin/puma-wild +25 -0
  6. data/bin/pumactl +1 -1
  7. data/docs/5.0-Upgrade.md +98 -0
  8. data/docs/6.0-Upgrade.md +56 -0
  9. data/docs/7.0-Upgrade.md +52 -0
  10. data/docs/8.0-Upgrade.md +100 -0
  11. data/docs/architecture.md +74 -0
  12. data/docs/compile_options.md +55 -0
  13. data/docs/deployment.md +137 -0
  14. data/docs/fork_worker.md +41 -0
  15. data/docs/grpc.md +62 -0
  16. data/docs/images/favicon.svg +1 -0
  17. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  18. data/docs/images/puma-connection-flow.png +0 -0
  19. data/docs/images/puma-general-arch.png +0 -0
  20. data/docs/images/running-puma.svg +1 -0
  21. data/docs/images/standard-logo.svg +1 -0
  22. data/docs/java_options.md +54 -0
  23. data/docs/jungle/README.md +9 -0
  24. data/docs/jungle/rc.d/README.md +74 -0
  25. data/docs/jungle/rc.d/puma +61 -0
  26. data/docs/jungle/rc.d/puma.conf +10 -0
  27. data/docs/kubernetes.md +73 -0
  28. data/docs/nginx.md +5 -5
  29. data/docs/plugins.md +42 -0
  30. data/docs/rails_dev_mode.md +28 -0
  31. data/docs/restart.md +65 -0
  32. data/docs/signals.md +98 -0
  33. data/docs/stats.md +148 -0
  34. data/docs/systemd.md +253 -0
  35. data/docs/testing_benchmarks_local_files.md +150 -0
  36. data/docs/testing_test_rackup_ci_files.md +36 -0
  37. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  38. data/ext/puma_http11/extconf.rb +59 -2
  39. data/ext/puma_http11/http11_parser.c +319 -487
  40. data/ext/puma_http11/http11_parser.h +15 -13
  41. data/ext/puma_http11/http11_parser.java.rl +64 -94
  42. data/ext/puma_http11/http11_parser.rl +27 -24
  43. data/ext/puma_http11/http11_parser_common.rl +8 -8
  44. data/ext/puma_http11/mini_ssl.c +696 -38
  45. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  46. data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  47. data/ext/puma_http11/org/jruby/puma/Http11.java +241 -145
  48. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +174 -221
  49. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
  50. data/ext/puma_http11/puma_http11.c +183 -175
  51. data/lib/puma/app/status.rb +77 -29
  52. data/lib/puma/binder.rb +349 -119
  53. data/lib/puma/cli.rb +163 -801
  54. data/lib/puma/client.rb +627 -144
  55. data/lib/puma/client_env.rb +171 -0
  56. data/lib/puma/cluster/worker.rb +183 -0
  57. data/lib/puma/cluster/worker_handle.rb +127 -0
  58. data/lib/puma/cluster.rb +634 -0
  59. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  60. data/lib/puma/commonlogger.rb +115 -0
  61. data/lib/puma/configuration.rb +420 -198
  62. data/lib/puma/const.rb +266 -115
  63. data/lib/puma/control_cli.rb +219 -120
  64. data/lib/puma/detect.rb +56 -2
  65. data/lib/puma/dsl.rb +1562 -0
  66. data/lib/puma/error_logger.rb +115 -0
  67. data/lib/puma/events.rb +44 -60
  68. data/lib/puma/io_buffer.rb +48 -5
  69. data/lib/puma/jruby_restart.rb +2 -51
  70. data/lib/puma/json_serialization.rb +96 -0
  71. data/lib/puma/launcher/bundle_pruner.rb +102 -0
  72. data/lib/puma/launcher.rb +501 -0
  73. data/lib/puma/log_writer.rb +153 -0
  74. data/lib/puma/minissl/context_builder.rb +96 -0
  75. data/lib/puma/minissl.rb +355 -37
  76. data/lib/puma/null_io.rb +79 -12
  77. data/lib/puma/plugin/systemd.rb +90 -0
  78. data/lib/puma/plugin/tmp_restart.rb +36 -0
  79. data/lib/puma/plugin.rb +111 -0
  80. data/lib/puma/rack/builder.rb +297 -0
  81. data/lib/puma/rack/urlmap.rb +93 -0
  82. data/lib/puma/rack_default.rb +21 -4
  83. data/lib/puma/reactor.rb +102 -133
  84. data/lib/puma/response.rb +532 -0
  85. data/lib/puma/runner.rb +211 -0
  86. data/lib/puma/sd_notify.rb +146 -0
  87. data/lib/puma/server.rb +582 -445
  88. data/lib/puma/server_plugin_control.rb +32 -0
  89. data/lib/puma/single.rb +72 -0
  90. data/lib/puma/state_file.rb +69 -0
  91. data/lib/puma/thread_pool.rb +389 -57
  92. data/lib/puma/util.rb +125 -0
  93. data/lib/puma.rb +80 -6
  94. data/lib/rack/handler/puma.rb +125 -47
  95. data/tools/Dockerfile +26 -0
  96. data/tools/trickletest.rb +44 -0
  97. metadata +88 -148
  98. data/COPYING +0 -55
  99. data/Gemfile +0 -10
  100. data/History.txt +0 -302
  101. data/Manifest.txt +0 -60
  102. data/Rakefile +0 -165
  103. data/TODO +0 -5
  104. data/docs/config.md +0 -0
  105. data/ext/puma_http11/ext_help.h +0 -15
  106. data/ext/puma_http11/io_buffer.c +0 -154
  107. data/lib/puma/accept_nonblock.rb +0 -23
  108. data/lib/puma/capistrano.rb +0 -34
  109. data/lib/puma/compat.rb +0 -11
  110. data/lib/puma/daemon_ext.rb +0 -20
  111. data/lib/puma/delegation.rb +0 -11
  112. data/lib/puma/java_io_buffer.rb +0 -45
  113. data/lib/puma/rack_patch.rb +0 -25
  114. data/puma.gemspec +0 -45
  115. data/test/test_app_status.rb +0 -88
  116. data/test/test_cli.rb +0 -171
  117. data/test/test_config.rb +0 -16
  118. data/test/test_http10.rb +0 -27
  119. data/test/test_http11.rb +0 -126
  120. data/test/test_integration.rb +0 -154
  121. data/test/test_iobuffer.rb +0 -38
  122. data/test/test_minissl.rb +0 -25
  123. data/test/test_null_io.rb +0 -31
  124. data/test/test_persistent.rb +0 -238
  125. data/test/test_puma_server.rb +0 -224
  126. data/test/test_rack_handler.rb +0 -10
  127. data/test/test_rack_server.rb +0 -141
  128. data/test/test_thread_pool.rb +0 -146
  129. data/test/test_unix_socket.rb +0 -39
  130. data/test/test_ws.rb +0 -89
  131. data/tools/jungle/init.d/README.md +0 -54
  132. data/tools/jungle/init.d/puma +0 -332
  133. data/tools/jungle/init.d/run-puma +0 -3
  134. data/tools/jungle/upstart/README.md +0 -61
  135. data/tools/jungle/upstart/puma-manager.conf +0 -31
  136. data/tools/jungle/upstart/puma.conf +0 -52
@@ -1,49 +1,92 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
- require 'puma/const'
3
- require 'puma/configuration'
4
- require 'yaml'
4
+ require_relative 'const'
5
+ require_relative 'detect'
5
6
  require 'uri'
6
7
  require 'socket'
8
+
7
9
  module Puma
8
10
  class ControlCLI
9
11
 
10
- COMMANDS = %w{halt restart start stats status stop}
11
-
12
- def is_windows?
13
- RUBY_PLATFORM =~ /(win|w)32$/ ? true : false
14
- end
15
-
16
- def initialize(argv, stdout=STDOUT, stderr=STDERR)
17
- @argv = argv
12
+ # values must be string or nil
13
+ # value of `nil` means command cannot be processed via signal
14
+ # @version 5.0.3
15
+ CMD_PATH_SIG_MAP = {
16
+ 'gc' => nil,
17
+ 'gc-stats' => nil,
18
+ 'halt' => 'SIGQUIT',
19
+ 'info' => Puma.backtrace_signal,
20
+ 'phased-restart' => 'SIGUSR1',
21
+ 'refork' => 'SIGURG',
22
+ 'reload-worker-directory' => nil,
23
+ 'reopen-log' => 'SIGHUP',
24
+ 'restart' => 'SIGUSR2',
25
+ 'start' => nil,
26
+ 'stats' => nil,
27
+ 'status' => '',
28
+ 'stop' => 'SIGTERM',
29
+ 'thread-backtraces' => nil,
30
+ 'worker-count-down' => 'SIGTTOU',
31
+ 'worker-count-up' => 'SIGTTIN'
32
+ }.freeze
33
+
34
+ # commands that cannot be used in a request
35
+ NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
36
+
37
+ # @version 5.0.0
38
+ PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
39
+
40
+ def initialize(argv, stdout=STDOUT, stderr=STDERR, env: ENV)
41
+ @state = nil
42
+ @quiet = false
43
+ @pidfile = nil
44
+ @pid = nil
45
+ @control_url = nil
46
+ @control_auth_token = nil
47
+ @config_file = nil
48
+ @command = nil
49
+ @environment = env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV']
50
+
51
+ @argv = argv.dup
18
52
  @stdout = stdout
19
53
  @stderr = stderr
20
- @options = {}
21
-
54
+ @cli_options = {}
55
+
22
56
  opts = OptionParser.new do |o|
23
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token) (#{COMMANDS.join("|")})"
57
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
24
58
 
25
59
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
26
- @options[:status_path] = arg
60
+ @state = arg
27
61
  end
28
62
 
29
- o.on "-Q", "--quiet", "Not display messages" do |arg|
30
- @options[:quiet_flag] = true
63
+ o.on "-Q", "--quiet", "Do not display messages" do |arg|
64
+ @quiet = true
31
65
  end
32
66
 
33
67
  o.on "-P", "--pidfile PATH", "Pid file" do |arg|
34
- @options[:pid_file] = arg
68
+ @pidfile = arg
35
69
  end
36
70
 
37
71
  o.on "-p", "--pid PID", "Pid" do |arg|
38
- @options[:pid] = arg.to_i
72
+ @pid = arg.to_i
39
73
  end
40
74
 
41
75
  o.on "-C", "--control-url URL", "The bind url to use for the control server" do |arg|
42
- @options[:control_url] = arg
76
+ @control_url = arg
43
77
  end
44
78
 
45
79
  o.on "-T", "--control-token TOKEN", "The token to use as authentication for the control server" do |arg|
46
- @options[:control_auth_token] = arg
80
+ @control_auth_token = arg
81
+ end
82
+
83
+ o.on "-F", "--config-file PATH", "Puma config script" do |arg|
84
+ @config_file = arg
85
+ end
86
+
87
+ o.on "-e", "--environment ENVIRONMENT",
88
+ "The environment to run the Rack app on (default development)" do |arg|
89
+ @environment = arg
47
90
  end
48
91
 
49
92
  o.on_tail("-H", "--help", "Show this message") do
@@ -52,94 +95,119 @@ module Puma
52
95
  end
53
96
 
54
97
  o.on_tail("-V", "--version", "Show version") do
55
- puts Const::PUMA_VERSION
98
+ @stdout.puts Const::PUMA_VERSION
56
99
  exit
57
100
  end
58
101
  end
59
102
 
60
103
  opts.order!(argv) { |a| opts.terminate a }
61
-
62
- command = argv.shift
63
- @options[:command] = command if command
104
+ opts.parse!
64
105
 
65
- # check present of command
66
- unless @options[:command]
67
- raise "Available commands: #{COMMANDS.join(", ")}"
106
+ @command = argv.shift
107
+
108
+ # check presence of command
109
+ unless @command
110
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
68
111
  end
69
112
 
70
- unless COMMANDS.include? @options[:command]
71
- raise "Invalid command: #{@options[:command]}"
113
+ unless CMD_PATH_SIG_MAP.key? @command
114
+ raise "Invalid command: #{@command}"
72
115
  end
73
116
 
117
+ unless @config_file == '-'
118
+ environment = @environment || 'development'
119
+
120
+ if @config_file.nil?
121
+ @config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
122
+ File.exist?(f)
123
+ end
124
+ end
125
+
126
+ if @config_file
127
+ # needed because neither `Puma::CLI` or `Puma::Server` are loaded
128
+ require_relative '../puma'
129
+
130
+ require_relative 'configuration'
131
+ require_relative 'log_writer'
132
+
133
+ config = Puma::Configuration.new({ config_files: [@config_file] }, {} , env)
134
+ config.clamp
135
+
136
+ @state ||= config.options[:state]
137
+ @control_url ||= config.options[:control_url]
138
+ @control_auth_token ||= config.options[:control_auth_token]
139
+ @pidfile ||= config.options[:pidfile]
140
+ end
141
+ end
74
142
  rescue => e
75
143
  @stdout.puts e.message
76
144
  exit 1
77
145
  end
78
-
146
+
79
147
  def message(msg)
80
- @stdout.puts msg unless @options[:quiet_flag]
148
+ @stdout.puts msg unless @quiet
81
149
  end
82
150
 
83
151
  def prepare_configuration
84
- if @options.has_key? :status_path
85
- unless File.exist? @options[:status_path]
86
- raise "Status file not found: #{@options[:status_path]}"
152
+ if @state
153
+ unless File.exist? @state
154
+ raise "State file not found: #{@state}"
87
155
  end
88
156
 
89
- status = YAML.load File.read(@options[:status_path])
157
+ require_relative 'state_file'
90
158
 
91
- if status.has_key? "config"
159
+ sf = Puma::StateFile.new
160
+ sf.load @state
92
161
 
93
- conf = status["config"]
94
-
95
- # get control_url
96
- if url = conf.options[:control_url]
97
- @options[:control_url] = url
98
- end
99
-
100
- # get control_auth_token
101
- if token = conf.options[:control_auth_token]
102
- @options[:control_auth_token] = token
103
- end
104
-
105
- # get pid
106
- @options[:pid] = status["pid"].to_i
107
- else
108
- raise "Invalid status file: #{@options[:status_path]}"
109
- end
110
-
111
- elsif @options.has_key? :pid_file
162
+ @control_url = sf.control_url
163
+ @control_auth_token = sf.control_auth_token
164
+ @pid = sf.pid
165
+ elsif @pidfile
112
166
  # get pid from pid_file
113
- @options[:pid] = File.open(@options[:pid_file]).gets.to_i
167
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
114
168
  end
115
169
  end
116
170
 
117
171
  def send_request
118
- uri = URI.parse @options[:control_url]
119
-
172
+ uri = URI.parse @control_url
173
+
174
+ host = uri.host
175
+
120
176
  # create server object by scheme
121
- @server = case uri.scheme
122
- when "tcp"
123
- TCPSocket.new uri.host, uri.port
124
- when "unix"
125
- UNIXSocket.new "#{uri.host}#{uri.path}"
126
- else
127
- raise "Invalid scheme: #{uri.scheme}"
128
- end
129
-
130
- if @options[:command] == "status"
131
- message "Puma is started"
177
+ server =
178
+ case uri.scheme
179
+ when 'ssl'
180
+ require 'openssl'
181
+ host = host[1..-2] if host&.start_with? '['
182
+ OpenSSL::SSL::SSLSocket.new(
183
+ TCPSocket.new(host, uri.port),
184
+ OpenSSL::SSL::SSLContext.new)
185
+ .tap { |ssl| ssl.sync_close = true } # default is false
186
+ .tap(&:connect)
187
+ when 'tcp'
188
+ host = host[1..-2] if host&.start_with? '['
189
+ TCPSocket.new host, uri.port
190
+ when 'unix'
191
+ # check for abstract UNIXSocket
192
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
193
+ "\0#{host}#{uri.path}" : "#{host}#{uri.path}")
194
+ else
195
+ raise "Invalid scheme: #{uri.scheme}"
196
+ end
197
+
198
+ if @command == 'status'
199
+ message 'Puma is started'
132
200
  else
133
- url = "/#{@options[:command]}"
201
+ url = "/#{@command}"
134
202
 
135
- if @options.has_key?(:control_auth_token)
136
- url = url + "?token=#{@options[:control_auth_token]}"
203
+ if @control_auth_token
204
+ url = url + "?token=#{@control_auth_token}"
137
205
  end
138
206
 
139
- @server << "GET #{url} HTTP/1.0\r\n\r\n"
207
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
140
208
 
141
- unless data = @server.read
142
- raise "Server closed connection before responding"
209
+ unless data = server.read
210
+ raise 'Server closed connection before responding'
143
211
  end
144
212
 
145
213
  response = data.split("\r\n")
@@ -148,74 +216,105 @@ module Puma
148
216
  raise "Server sent empty response"
149
217
  end
150
218
 
151
- (@http,@code,@message) = response.first.split(" ")
219
+ @http, @code, @message = response.first.split(' ',3)
152
220
 
153
- if @code == "403"
154
- raise "Unauthorized access to server (wrong auth token)"
155
- elsif @code != "200"
221
+ if @code == '403'
222
+ raise 'Unauthorized access to server (wrong auth token)'
223
+ elsif @code == '404'
224
+ raise "Command error: #{response.last}"
225
+ elsif @code != '200'
156
226
  raise "Bad response from server: #{@code}"
157
227
  end
158
228
 
159
- message "Command #{@options[:command]} sent success"
160
- message response.last if @options[:command] == "stats"
229
+ message "Command #{@command} sent success"
230
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
231
+ end
232
+ ensure
233
+ if server
234
+ if uri.scheme == 'ssl'
235
+ server.sysclose
236
+ else
237
+ server.close unless server.closed?
238
+ end
161
239
  end
162
-
163
- @server.close
164
240
  end
165
241
 
166
242
  def send_signal
167
- unless pid = @options[:pid]
168
- raise "Neither pid nor control url available"
243
+ unless @pid
244
+ raise 'Neither pid nor control url available'
169
245
  end
170
246
 
171
247
  begin
172
- Process.getpgid pid
248
+ sig = CMD_PATH_SIG_MAP[@command]
249
+
250
+ if sig.nil?
251
+ @stdout.puts "'#{@command}' not available via pid only"
252
+ @stdout.flush unless @stdout.sync
253
+ return
254
+ elsif sig.start_with? 'SIG'
255
+ if Signal.list.key? sig.delete_prefix('SIG')
256
+ Process.kill sig, @pid
257
+ else
258
+ raise "Signal '#{sig}' not available'"
259
+ end
260
+ elsif @command == 'status'
261
+ begin
262
+ Process.kill 0, @pid
263
+ @stdout.puts 'Puma is started'
264
+ @stdout.flush unless @stdout.sync
265
+ rescue Errno::ESRCH
266
+ raise 'Puma is not running'
267
+ end
268
+ return
269
+ end
173
270
  rescue SystemCallError
174
- raise "No pid '#{pid}' found"
175
- end
176
-
177
- case @options[:command]
178
- when "restart"
179
- Process.kill "SIGUSR2", pid
180
-
181
- when "halt"
182
- Process.kill "QUIT", pid
183
-
184
- when "stop"
185
- Process.kill "SIGTERM", pid
186
-
187
- when "stats"
188
- puts "Stats not available via pid only"
189
- return
190
-
191
- else
192
- message "Puma is started"
193
- return
271
+ if @command == 'restart'
272
+ start
273
+ else
274
+ raise "No pid '#{@pid}' found"
275
+ end
194
276
  end
195
277
 
196
- message "Command #{@options[:command]} sent success"
278
+ message "Command #{@command} sent success"
197
279
  end
198
280
 
199
281
  def run
200
- if @options[:command] == "start"
201
- require 'puma/cli'
202
-
203
- cli = Puma::CLI.new @argv, @stdout, @stderr
204
- cli.run
205
- return
206
- end
207
-
282
+ return start if @command == 'start'
208
283
  prepare_configuration
209
-
210
- if is_windows?
284
+
285
+ if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
211
286
  send_request
212
287
  else
213
- @options.has_key?(:control_url) ? send_request : send_signal
288
+ send_signal
214
289
  end
215
290
 
216
291
  rescue => e
217
292
  message e.message
218
293
  exit 1
219
294
  end
295
+
296
+ private
297
+ def start
298
+ require_relative 'cli'
299
+
300
+ run_args = []
301
+
302
+ run_args += ["-S", @state] if @state
303
+ run_args += ["-q"] if @quiet
304
+ run_args += ["--pidfile", @pidfile] if @pidfile
305
+ run_args += ["--control-url", @control_url] if @control_url
306
+ run_args += ["--control-token", @control_auth_token] if @control_auth_token
307
+ run_args += ["-C", @config_file] if @config_file
308
+ run_args += ["-e", @environment] if @environment
309
+
310
+ log_writer = Puma::LogWriter.new(@stdout, @stderr)
311
+
312
+ # replace $0 because puma use it to generate restart command
313
+ puma_cmd = $0.gsub(/pumactl$/, 'puma')
314
+ $0 = puma_cmd if File.exist?(puma_cmd)
315
+
316
+ cli = Puma::CLI.new run_args, log_writer
317
+ cli.run
318
+ end
220
319
  end
221
- end
320
+ end
data/lib/puma/detect.rb CHANGED
@@ -1,4 +1,58 @@
1
+ # frozen_string_literal: true
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
+
1
7
  module Puma
2
- IS_JRUBY = defined?(JRUBY_VERSION)
3
- end
8
+ # @version 5.2.1
9
+ HAS_FORK = ::Process.respond_to? :fork
10
+
11
+ HAS_NATIVE_IO_WAIT = ::IO.public_instance_methods(false).include? :wait_readable
12
+
13
+ IS_JRUBY = Object.const_defined? :JRUBY_VERSION
14
+
15
+ IS_OSX = RUBY_DESCRIPTION.include? 'darwin'
16
+
17
+ IS_WINDOWS = RUBY_DESCRIPTION.match?(/mswin|ming|cygwin/)
18
+
19
+ IS_LINUX = !(IS_OSX || IS_WINDOWS)
4
20
 
21
+ IS_ARM = RUBY_PLATFORM.include? 'aarch64'
22
+
23
+ # @version 5.2.0
24
+ IS_MRI = RUBY_ENGINE == 'ruby'
25
+
26
+ def self.jruby?
27
+ IS_JRUBY
28
+ end
29
+
30
+ def self.osx?
31
+ IS_OSX
32
+ end
33
+
34
+ def self.windows?
35
+ IS_WINDOWS
36
+ end
37
+
38
+ BACKTRACE_SIGNAL =
39
+ if Signal.list.key?("INFO")
40
+ "SIGINFO"
41
+ elsif Signal.list.key?("PWR")
42
+ "SIGPWR"
43
+ end
44
+
45
+ # @version 5.0.0
46
+ def self.mri?
47
+ IS_MRI
48
+ end
49
+
50
+ # @version 5.0.0
51
+ def self.forkable?
52
+ HAS_FORK
53
+ end
54
+
55
+ def self.backtrace_signal
56
+ BACKTRACE_SIGNAL
57
+ end
58
+ end