puma 5.5.2 → 6.3.0

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +336 -3
  3. data/README.md +61 -16
  4. data/bin/puma-wild +1 -1
  5. data/docs/architecture.md +4 -4
  6. data/docs/compile_options.md +34 -0
  7. data/docs/fork_worker.md +1 -3
  8. data/docs/nginx.md +1 -1
  9. data/docs/signals.md +1 -0
  10. data/docs/systemd.md +1 -2
  11. data/docs/testing_benchmarks_local_files.md +150 -0
  12. data/docs/testing_test_rackup_ci_files.md +36 -0
  13. data/ext/puma_http11/extconf.rb +28 -14
  14. data/ext/puma_http11/http11_parser.c +1 -1
  15. data/ext/puma_http11/http11_parser.h +1 -1
  16. data/ext/puma_http11/http11_parser.java.rl +2 -2
  17. data/ext/puma_http11/http11_parser.rl +2 -2
  18. data/ext/puma_http11/http11_parser_common.rl +2 -2
  19. data/ext/puma_http11/mini_ssl.c +135 -23
  20. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  21. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  22. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
  23. data/ext/puma_http11/puma_http11.c +18 -10
  24. data/lib/puma/app/status.rb +7 -4
  25. data/lib/puma/binder.rb +62 -51
  26. data/lib/puma/cli.rb +19 -20
  27. data/lib/puma/client.rb +108 -26
  28. data/lib/puma/cluster/worker.rb +23 -16
  29. data/lib/puma/cluster/worker_handle.rb +8 -1
  30. data/lib/puma/cluster.rb +62 -41
  31. data/lib/puma/commonlogger.rb +21 -14
  32. data/lib/puma/configuration.rb +76 -55
  33. data/lib/puma/const.rb +133 -97
  34. data/lib/puma/control_cli.rb +21 -18
  35. data/lib/puma/detect.rb +12 -2
  36. data/lib/puma/dsl.rb +270 -55
  37. data/lib/puma/error_logger.rb +18 -9
  38. data/lib/puma/events.rb +6 -126
  39. data/lib/puma/io_buffer.rb +39 -4
  40. data/lib/puma/jruby_restart.rb +2 -1
  41. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  42. data/lib/puma/launcher.rb +114 -175
  43. data/lib/puma/log_writer.rb +147 -0
  44. data/lib/puma/minissl/context_builder.rb +30 -16
  45. data/lib/puma/minissl.rb +126 -17
  46. data/lib/puma/null_io.rb +5 -0
  47. data/lib/puma/plugin/systemd.rb +90 -0
  48. data/lib/puma/plugin/tmp_restart.rb +1 -1
  49. data/lib/puma/plugin.rb +1 -1
  50. data/lib/puma/rack/builder.rb +6 -6
  51. data/lib/puma/rack_default.rb +19 -4
  52. data/lib/puma/reactor.rb +19 -10
  53. data/lib/puma/request.rb +365 -161
  54. data/lib/puma/runner.rb +55 -22
  55. data/lib/puma/sd_notify.rb +149 -0
  56. data/lib/puma/server.rb +91 -94
  57. data/lib/puma/single.rb +13 -11
  58. data/lib/puma/state_file.rb +39 -7
  59. data/lib/puma/thread_pool.rb +25 -21
  60. data/lib/puma/util.rb +12 -14
  61. data/lib/puma.rb +12 -11
  62. data/lib/rack/handler/puma.rb +113 -86
  63. data/tools/Dockerfile +1 -1
  64. metadata +11 -6
  65. data/lib/puma/queue_close.rb +0 -26
  66. data/lib/puma/systemd.rb +0 -46
data/lib/puma/const.rb CHANGED
@@ -5,7 +5,6 @@ module Puma
5
5
  class UnsupportedOption < RuntimeError
6
6
  end
7
7
 
8
-
9
8
  # Every standard HTTP code mapped to the appropriate message. These are
10
9
  # used so frequently that they are placed directly in Puma for easy
11
10
  # access rather than Puma::Const itself.
@@ -19,6 +18,7 @@ module Puma
19
18
  100 => 'Continue',
20
19
  101 => 'Switching Protocols',
21
20
  102 => 'Processing',
21
+ 103 => 'Early Hints',
22
22
  200 => 'OK',
23
23
  201 => 'Created',
24
24
  202 => 'Accepted',
@@ -50,16 +50,16 @@ module Puma
50
50
  410 => 'Gone',
51
51
  411 => 'Length Required',
52
52
  412 => 'Precondition Failed',
53
- 413 => 'Payload Too Large',
53
+ 413 => 'Content Too Large',
54
54
  414 => 'URI Too Long',
55
55
  415 => 'Unsupported Media Type',
56
56
  416 => 'Range Not Satisfiable',
57
57
  417 => 'Expectation Failed',
58
- 418 => 'I\'m A Teapot',
59
58
  421 => 'Misdirected Request',
60
- 422 => 'Unprocessable Entity',
59
+ 422 => 'Unprocessable Content',
61
60
  423 => 'Locked',
62
61
  424 => 'Failed Dependency',
62
+ 425 => 'Too Early',
63
63
  426 => 'Upgrade Required',
64
64
  428 => 'Precondition Required',
65
65
  429 => 'Too Many Requests',
@@ -74,9 +74,9 @@ module Puma
74
74
  506 => 'Variant Also Negotiates',
75
75
  507 => 'Insufficient Storage',
76
76
  508 => 'Loop Detected',
77
- 510 => 'Not Extended',
77
+ 510 => 'Not Extended (OBSOLETED)',
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,54 +100,41 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.5.2".freeze
104
- CODE_NAME = "Zawgyi".freeze
103
+ PUMA_VERSION = VERSION = "6.3.0"
104
+ CODE_NAME = "Mugi No Toki Itaru"
105
105
 
106
- PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
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
- REQUEST_URI= 'REQUEST_URI'.freeze
131
- REQUEST_PATH = 'REQUEST_PATH'.freeze
132
- QUERY_STRING = 'QUERY_STRING'.freeze
133
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
115
+ REQUEST_URI= "REQUEST_URI"
116
+ REQUEST_PATH = "REQUEST_PATH"
117
+ QUERY_STRING = "QUERY_STRING"
118
+ CONTENT_LENGTH = "CONTENT_LENGTH"
134
119
 
135
- PATH_INFO = 'PATH_INFO'.freeze
120
+ PATH_INFO = "PATH_INFO"
136
121
 
137
- PUMA_TMP_BASE = "puma".freeze
122
+ PUMA_TMP_BASE = "puma"
138
123
 
139
124
  ERROR_RESPONSE = {
140
125
  # Indicate that we couldn't parse the request
141
- 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
126
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
142
127
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
143
- 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
128
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n",
144
129
  # The standard empty 408 response for requests that timed out.
145
- 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
130
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n",
146
131
  # Indicate that there was an internal error, obviously.
147
- 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
132
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
133
+ # Incorrect or invalid header value
134
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n",
148
135
  # A common header for indicating the server is too busy. Not used yet.
149
- 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
150
- }
136
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\n"
137
+ }.freeze
151
138
 
152
139
  # The basic max request size we'll try to read.
153
140
  CHUNK_SIZE = 16 * 1024
@@ -159,87 +146,136 @@ module Puma
159
146
  # Maximum request body size before it is moved out of memory and into a tempfile for reading.
160
147
  MAX_BODY = MAX_HEADER
161
148
 
162
- REQUEST_METHOD = "REQUEST_METHOD".freeze
163
- HEAD = "HEAD".freeze
164
- # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
165
- LINE_END = "\r\n".freeze
166
- REMOTE_ADDR = "REMOTE_ADDR".freeze
167
- HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
168
- HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
169
- HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
170
- HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
149
+ REQUEST_METHOD = "REQUEST_METHOD"
150
+ HEAD = "HEAD"
151
+
152
+ # based on https://www.rfc-editor.org/rfc/rfc9110.html#name-overview,
153
+ # with CONNECT removed, and PATCH added
154
+ SUPPORTED_HTTP_METHODS = %w[HEAD GET POST PUT DELETE OPTIONS TRACE PATCH].freeze
155
+
156
+ # list from https://www.iana.org/assignments/http-methods/http-methods.xhtml
157
+ # as of 04-May-23
158
+ IANA_HTTP_METHODS = %w[
159
+ ACL
160
+ BASELINE-CONTROL
161
+ BIND
162
+ CHECKIN
163
+ CHECKOUT
164
+ CONNECT
165
+ COPY
166
+ DELETE
167
+ GET
168
+ HEAD
169
+ LABEL
170
+ LINK
171
+ LOCK
172
+ MERGE
173
+ MKACTIVITY
174
+ MKCALENDAR
175
+ MKCOL
176
+ MKREDIRECTREF
177
+ MKWORKSPACE
178
+ MOVE
179
+ OPTIONS
180
+ ORDERPATCH
181
+ PATCH
182
+ POST
183
+ PRI
184
+ PROPFIND
185
+ PROPPATCH
186
+ PUT
187
+ REBIND
188
+ REPORT
189
+ SEARCH
190
+ TRACE
191
+ UNBIND
192
+ UNCHECKOUT
193
+ UNLINK
194
+ UNLOCK
195
+ UPDATE
196
+ UPDATEREDIRECTREF
197
+ VERSION-CONTROL
198
+ ].freeze
171
199
 
172
- SERVER_NAME = "SERVER_NAME".freeze
173
- SERVER_PORT = "SERVER_PORT".freeze
174
- HTTP_HOST = "HTTP_HOST".freeze
175
- PORT_80 = "80".freeze
176
- PORT_443 = "443".freeze
177
- LOCALHOST = "localhost".freeze
178
- LOCALHOST_IP = "127.0.0.1".freeze
200
+ # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
201
+ LINE_END = "\r\n"
202
+ REMOTE_ADDR = "REMOTE_ADDR"
203
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
204
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL"
205
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME"
206
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO"
179
207
 
180
- SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
181
- HTTP_11 = "HTTP/1.1".freeze
208
+ SERVER_NAME = "SERVER_NAME"
209
+ SERVER_PORT = "SERVER_PORT"
210
+ HTTP_HOST = "HTTP_HOST"
211
+ PORT_80 = "80"
212
+ PORT_443 = "443"
213
+ LOCALHOST = "localhost"
214
+ LOCALHOST_IPV4 = "127.0.0.1"
215
+ LOCALHOST_IPV6 = "::1"
216
+ UNSPECIFIED_IPV4 = "0.0.0.0"
217
+ UNSPECIFIED_IPV6 = "::"
182
218
 
183
- SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
184
- GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
185
- CGI_VER = "CGI/1.2".freeze
219
+ SERVER_PROTOCOL = "SERVER_PROTOCOL"
220
+ HTTP_11 = "HTTP/1.1"
186
221
 
187
- STOP_COMMAND = "?".freeze
188
- HALT_COMMAND = "!".freeze
189
- RESTART_COMMAND = "R".freeze
222
+ SERVER_SOFTWARE = "SERVER_SOFTWARE"
223
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
224
+ CGI_VER = "CGI/1.2"
190
225
 
191
- RACK_INPUT = "rack.input".freeze
192
- RACK_URL_SCHEME = "rack.url_scheme".freeze
193
- RACK_AFTER_REPLY = "rack.after_reply".freeze
194
- PUMA_SOCKET = "puma.socket".freeze
195
- PUMA_CONFIG = "puma.config".freeze
196
- PUMA_PEERCERT = "puma.peercert".freeze
226
+ STOP_COMMAND = "?"
227
+ HALT_COMMAND = "!"
228
+ RESTART_COMMAND = "R"
197
229
 
198
- HTTP = "http".freeze
199
- HTTPS = "https".freeze
230
+ RACK_INPUT = "rack.input"
231
+ RACK_URL_SCHEME = "rack.url_scheme"
232
+ RACK_AFTER_REPLY = "rack.after_reply"
233
+ PUMA_SOCKET = "puma.socket"
234
+ PUMA_CONFIG = "puma.config"
235
+ PUMA_PEERCERT = "puma.peercert"
200
236
 
201
- HTTPS_KEY = "HTTPS".freeze
237
+ HTTP = "http"
238
+ HTTPS = "https"
202
239
 
203
- HTTP_VERSION = "HTTP_VERSION".freeze
204
- HTTP_CONNECTION = "HTTP_CONNECTION".freeze
205
- HTTP_EXPECT = "HTTP_EXPECT".freeze
206
- CONTINUE = "100-continue".freeze
240
+ HTTPS_KEY = "HTTPS"
207
241
 
208
- HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
209
- HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
210
- HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
242
+ HTTP_VERSION = "HTTP_VERSION"
243
+ HTTP_CONNECTION = "HTTP_CONNECTION"
244
+ HTTP_EXPECT = "HTTP_EXPECT"
245
+ CONTINUE = "100-continue"
211
246
 
212
- CLOSE = "close".freeze
213
- KEEP_ALIVE = "keep-alive".freeze
247
+ HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n"
248
+ HTTP_11_200 = "HTTP/1.1 200 OK\r\n"
249
+ HTTP_10_200 = "HTTP/1.0 200 OK\r\n"
214
250
 
215
- CONTENT_LENGTH2 = "content-length".freeze
216
- CONTENT_LENGTH_S = "Content-Length: ".freeze
217
- TRANSFER_ENCODING = "transfer-encoding".freeze
218
- TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
251
+ CLOSE = "close"
252
+ KEEP_ALIVE = "keep-alive"
219
253
 
220
- CONNECTION_CLOSE = "Connection: close\r\n".freeze
221
- CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
254
+ CONTENT_LENGTH2 = "content-length"
255
+ CONTENT_LENGTH_S = "Content-Length: "
256
+ TRANSFER_ENCODING = "transfer-encoding"
257
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
222
258
 
223
- TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
224
- CLOSE_CHUNKED = "0\r\n\r\n".freeze
259
+ CONNECTION_CLOSE = "Connection: close\r\n"
260
+ CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
225
261
 
226
- CHUNKED = "chunked".freeze
262
+ TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
263
+ CLOSE_CHUNKED = "0\r\n\r\n"
227
264
 
228
- COLON = ": ".freeze
265
+ CHUNKED = "chunked"
229
266
 
230
- NEWLINE = "\n".freeze
267
+ COLON = ": "
231
268
 
232
- HIJACK_P = "rack.hijack?".freeze
233
- HIJACK = "rack.hijack".freeze
234
- HIJACK_IO = "rack.hijack_io".freeze
269
+ NEWLINE = "\n"
235
270
 
236
- EARLY_HINTS = "rack.early_hints".freeze
271
+ HIJACK_P = "rack.hijack?"
272
+ HIJACK = "rack.hijack"
273
+ HIJACK_IO = "rack.hijack_io"
237
274
 
238
- # Minimum interval to checks worker health
239
- WORKER_CHECK_INTERVAL = 5
275
+ EARLY_HINTS = "rack.early_hints"
240
276
 
241
277
  # Illegal character in the key or value of response header
242
- DQUOTE = "\"".freeze
278
+ DQUOTE = "\""
243
279
  HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
244
280
  ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
245
281
  # header values can contain HTAB?
@@ -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
@@ -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,16 @@ 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')
19
+
20
+ IS_LINUX = !(IS_OSX || IS_WINDOWS)
15
21
 
16
22
  # @version 5.2.0
17
23
  IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
@@ -20,6 +26,10 @@ module Puma
20
26
  IS_JRUBY
21
27
  end
22
28
 
29
+ def self.osx?
30
+ IS_OSX
31
+ end
32
+
23
33
  def self.windows?
24
34
  IS_WINDOWS
25
35
  end