puma 4.3.12 → 5.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1526 -524
  3. data/LICENSE +23 -20
  4. data/README.md +120 -36
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +33 -0
  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 +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +85 -128
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +44 -10
  28. data/ext/puma_http11/http11_parser.c +45 -47
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +1 -1
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +0 -0
  33. data/ext/puma_http11/mini_ssl.c +225 -89
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +50 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +146 -84
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +212 -220
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +58 -49
  48. data/lib/puma/const.rb +22 -7
  49. data/lib/puma/control_cli.rb +99 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +368 -96
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -34
  54. data/lib/puma/io_buffer.rb +9 -2
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +128 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +137 -50
  60. data/lib/puma/null_io.rb +18 -1
  61. data/lib/puma/plugin/tmp_restart.rb +0 -0
  62. data/lib/puma/plugin.rb +3 -12
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +1 -5
  65. data/lib/puma/rack/urlmap.rb +0 -0
  66. data/lib/puma/rack_default.rb +0 -0
  67. data/lib/puma/reactor.rb +85 -369
  68. data/lib/puma/request.rb +489 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +292 -763
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +48 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +32 -4
  76. data/lib/puma.rb +48 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/lib/rack/version_restriction.rb +15 -0
  79. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  80. data/tools/trickletest.rb +0 -0
  81. metadata +29 -24
  82. data/docs/tcp_mode.md +0 -96
  83. data/ext/puma_http11/io_buffer.c +0 -155
  84. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  85. data/lib/puma/accept_nonblock.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -41
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. data/tools/jungle/upstart/puma.conf +0 -69
@@ -11,6 +11,7 @@ module Puma
11
11
 
12
12
  DefaultTCPHost = "0.0.0.0"
13
13
  DefaultTCPPort = 9292
14
+ DefaultWorkerCheckInterval = 5
14
15
  DefaultWorkerTimeout = 60
15
16
  DefaultWorkerShutdownTimeout = 30
16
17
  end
@@ -54,9 +55,7 @@ module Puma
54
55
  attr_reader :user_options, :file_options, :default_options
55
56
 
56
57
  def [](key)
57
- return user_options[key] if user_options.key?(key)
58
- return file_options[key] if file_options.key?(key)
59
- return default_options[key] if default_options.key?(key)
58
+ fetch(key)
60
59
  end
61
60
 
62
61
  def []=(key, value)
@@ -64,7 +63,11 @@ module Puma
64
63
  end
65
64
 
66
65
  def fetch(key, default_value = nil)
67
- self[key] || default_value
66
+ return user_options[key] if user_options.key?(key)
67
+ return file_options[key] if file_options.key?(key)
68
+ return default_options[key] if default_options.key?(key)
69
+
70
+ default_value
68
71
  end
69
72
 
70
73
  def all_of(key)
@@ -90,6 +93,12 @@ module Puma
90
93
  end
91
94
  end
92
95
  end
96
+
97
+ def final_options
98
+ default_options
99
+ .merge(file_options)
100
+ .merge(user_options)
101
+ end
93
102
  end
94
103
 
95
104
  # The main configuration class of Puma.
@@ -106,16 +115,17 @@ module Puma
106
115
  #
107
116
  # It also handles loading plugins.
108
117
  #
109
- # > Note: `:port` and `:host` are not valid keys. By they time they make it to the
118
+ # [Note:]
119
+ # `:port` and `:host` are not valid keys. By the time they make it to the
110
120
  # configuration options they are expected to be incorporated into a `:binds` key.
111
121
  # Under the hood the DSL maps `port` and `host` calls to `:binds`
112
122
  #
113
- # config = Configuration.new({}) do |user_config, file_config, default_config|
114
- # user_config.port 3003
115
- # end
116
- # config.load
117
- # puts config.options[:port]
118
- # # => 3003
123
+ # config = Configuration.new({}) do |user_config, file_config, default_config|
124
+ # user_config.port 3003
125
+ # end
126
+ # config.load
127
+ # puts config.options[:port]
128
+ # # => 3003
119
129
  #
120
130
  # It is expected that `load` is called on the configuration instance after setting
121
131
  # config. This method expands any values in `config_file` and puts them into the
@@ -137,6 +147,10 @@ module Puma
137
147
  @file_dsl = DSL.new(@options.file_options, self)
138
148
  @default_dsl = DSL.new(@options.default_options, self)
139
149
 
150
+ if !@options[:prune_bundler]
151
+ default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
152
+ end
153
+
140
154
  if block
141
155
  configure(&block)
142
156
  end
@@ -167,27 +181,37 @@ module Puma
167
181
  self
168
182
  end
169
183
 
184
+ # @version 5.0.0
185
+ def default_max_threads
186
+ Puma.mri? ? 5 : 16
187
+ end
188
+
170
189
  def puma_default_options
171
190
  {
172
- :min_threads => 0,
173
- :max_threads => 16,
191
+ :min_threads => Integer(ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS'] || 0),
192
+ :max_threads => Integer(ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS'] || default_max_threads),
174
193
  :log_requests => false,
175
194
  :debug => false,
176
195
  :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
177
- :workers => 0,
178
- :daemon => false,
196
+ :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
197
+ :silence_single_worker_warning => false,
179
198
  :mode => :http,
199
+ :worker_check_interval => DefaultWorkerCheckInterval,
180
200
  :worker_timeout => DefaultWorkerTimeout,
181
201
  :worker_boot_timeout => DefaultWorkerTimeout,
182
202
  :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
203
+ :worker_culling_strategy => :youngest,
183
204
  :remote_address => :socket,
184
205
  :tag => method(:infer_tag),
185
- :environment => -> { ENV['RACK_ENV'] || "development" },
206
+ :environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
186
207
  :rackup => DefaultRackup,
187
208
  :logger => STDOUT,
188
209
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
189
210
  :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
190
- :raise_exception_on_sigterm => true
211
+ :raise_exception_on_sigterm => true,
212
+ :max_fast_inline => Const::MAX_FAST_INLINE,
213
+ :io_selector_backend => :auto,
214
+ :mutate_stdout_and_stderr_to_sync_on_write => true,
191
215
  }
192
216
  end
193
217
 
@@ -245,14 +269,6 @@ module Puma
245
269
  def app
246
270
  found = options[:app] || load_rackup
247
271
 
248
- if @options[:mode] == :tcp
249
- require 'puma/tcp_logger'
250
-
251
- logger = @options[:logger]
252
- quiet = !@options[:log_requests]
253
- return TCPLogger.new(logger, found, quiet)
254
- end
255
-
256
272
  if @options[:log_requests]
257
273
  require 'puma/commonlogger'
258
274
  logger = @options[:logger]
@@ -275,8 +291,19 @@ module Puma
275
291
  @plugins.create name
276
292
  end
277
293
 
278
- def run_hooks(key, arg)
279
- @options.all_of(key).each { |b| b.call arg }
294
+ def run_hooks(key, arg, events)
295
+ @options.all_of(key).each do |b|
296
+ begin
297
+ b.call arg
298
+ rescue => e
299
+ events.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
300
+ events.debug e.backtrace.join("\n")
301
+ end
302
+ end
303
+ end
304
+
305
+ def final_options
306
+ @options.final_options
280
307
  end
281
308
 
282
309
  def self.temp_path
@@ -319,6 +346,8 @@ module Puma
319
346
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
320
347
 
321
348
  rack_app, rack_options = rack_builder.parse_file(rackup)
349
+ rack_options = rack_options || {}
350
+
322
351
  @options.file_options.merge!(rack_options)
323
352
 
324
353
  config_ru_binds = []
@@ -332,29 +361,9 @@ module Puma
332
361
  end
333
362
 
334
363
  def self.random_token
335
- begin
336
- require 'openssl'
337
- rescue LoadError
338
- end
339
-
340
- count = 16
341
-
342
- bytes = nil
343
-
344
- if defined? OpenSSL::Random
345
- bytes = OpenSSL::Random.random_bytes(count)
346
- elsif File.exist?("/dev/urandom")
347
- File.open('/dev/urandom') { |f| bytes = f.read(count) }
348
- end
349
-
350
- if bytes
351
- token = "".dup
352
- bytes.each_byte { |b| token << b.to_s(16) }
353
- else
354
- token = (0..count).to_a.map { rand(255).to_s(16) }.join
355
- end
364
+ require 'securerandom' unless defined?(SecureRandom)
356
365
 
357
- return token
366
+ SecureRandom.hex(16)
358
367
  end
359
368
  end
360
369
  end
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,9 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "4.3.12".freeze
104
- CODE_NAME = "Mysterious Traveller".freeze
103
+ PUMA_VERSION = VERSION = "5.6.9".freeze
104
+ CODE_NAME = "Birdie's Version".freeze
105
+
105
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
107
 
107
108
  FAST_TRACK_KA_TIMEOUT = 0.2
@@ -177,7 +178,6 @@ module Puma
177
178
  PORT_443 = "443".freeze
178
179
  LOCALHOST = "localhost".freeze
179
180
  LOCALHOST_IP = "127.0.0.1".freeze
180
- LOCALHOST_ADDR = "127.0.0.1:0".freeze
181
181
 
182
182
  SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
183
183
  HTTP_11 = "HTTP/1.1".freeze
@@ -230,7 +230,6 @@ module Puma
230
230
  COLON = ": ".freeze
231
231
 
232
232
  NEWLINE = "\n".freeze
233
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
234
233
 
235
234
  HIJACK_P = "rack.hijack?".freeze
236
235
  HIJACK = "rack.hijack".freeze
@@ -238,8 +237,24 @@ module Puma
238
237
 
239
238
  EARLY_HINTS = "rack.early_hints".freeze
240
239
 
241
- # Mininum interval to checks worker health
242
- WORKER_CHECK_INTERVAL = 5
243
-
240
+ # Illegal character in the key or value of response header
241
+ DQUOTE = "\"".freeze
242
+ HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
243
+ ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
244
+ # header values can contain HTAB?
245
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
246
+
247
+ # The keys of headers that should not be convert to underscore
248
+ # normalized versions. These headers are ignored at the request reading layer,
249
+ # but if we normalize them after reading, it's just confusing for the application.
250
+ UNMASKABLE_HEADERS = {
251
+ "HTTP_TRANSFER,ENCODING" => true,
252
+ "HTTP_CONTENT,LENGTH" => true,
253
+ }
254
+
255
+ # Banned keys of response header
256
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
257
+
258
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
244
259
  end
245
260
  end
@@ -11,7 +11,36 @@ 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}
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
+ 'info' => 'SIGINFO',
22
+ 'phased-restart' => 'SIGUSR1',
23
+ 'refork' => 'SIGURG',
24
+ 'reload-worker-directory' => nil,
25
+ 'reopen-log' => 'SIGHUP',
26
+ 'restart' => 'SIGUSR2',
27
+ 'start' => nil,
28
+ 'stats' => nil,
29
+ 'status' => '',
30
+ 'stop' => 'SIGTERM',
31
+ 'thread-backtraces' => nil,
32
+ 'worker-count-down' => 'SIGTTOU',
33
+ 'worker-count-up' => 'SIGTTIN'
34
+ }.freeze
35
+
36
+ # @deprecated 6.0.0
37
+ COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
38
+
39
+ # commands that cannot be used in a request
40
+ NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
41
+
42
+ # @version 5.0.0
43
+ PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
15
44
 
16
45
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
17
46
  @state = nil
@@ -22,7 +51,7 @@ module Puma
22
51
  @control_auth_token = nil
23
52
  @config_file = nil
24
53
  @command = nil
25
- @environment = ENV['RACK_ENV']
54
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
26
55
 
27
56
  @argv = argv.dup
28
57
  @stdout = stdout
@@ -30,7 +59,7 @@ module Puma
30
59
  @cli_options = {}
31
60
 
32
61
  opts = OptionParser.new do |o|
33
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
62
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
34
63
 
35
64
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
36
65
  @state = arg
@@ -71,7 +100,7 @@ module Puma
71
100
  end
72
101
 
73
102
  o.on_tail("-V", "--version", "Show version") do
74
- puts Const::PUMA_VERSION
103
+ @stdout.puts Const::PUMA_VERSION
75
104
  exit
76
105
  end
77
106
  end
@@ -81,6 +110,15 @@ module Puma
81
110
 
82
111
  @command = argv.shift
83
112
 
113
+ # check presence of command
114
+ unless @command
115
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
116
+ end
117
+
118
+ unless CMD_PATH_SIG_MAP.key? @command
119
+ raise "Invalid command: #{@command}"
120
+ end
121
+
84
122
  unless @config_file == '-'
85
123
  environment = @environment || 'development'
86
124
 
@@ -99,16 +137,6 @@ module Puma
99
137
  @pidfile ||= config.options[:pidfile]
100
138
  end
101
139
  end
102
-
103
- # check present of command
104
- unless @command
105
- raise "Available commands: #{COMMANDS.join(", ")}"
106
- end
107
-
108
- unless COMMANDS.include? @command
109
- raise "Invalid command: #{@command}"
110
- end
111
-
112
140
  rescue => e
113
141
  @stdout.puts e.message
114
142
  exit 1
@@ -132,7 +160,7 @@ module Puma
132
160
  @pid = sf.pid
133
161
  elsif @pidfile
134
162
  # get pid from pid_file
135
- File.open(@pidfile) { |f| @pid = f.read.to_i }
163
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
136
164
  end
137
165
  end
138
166
 
@@ -140,23 +168,27 @@ module Puma
140
168
  uri = URI.parse @control_url
141
169
 
142
170
  # create server object by scheme
143
- server = case uri.scheme
144
- when "ssl"
145
- require 'openssl'
146
- OpenSSL::SSL::SSLSocket.new(
147
- TCPSocket.new(uri.host, uri.port),
148
- OpenSSL::SSL::SSLContext.new
149
- ).tap(&:connect)
150
- when "tcp"
151
- TCPSocket.new uri.host, uri.port
152
- when "unix"
153
- UNIXSocket.new "#{uri.host}#{uri.path}"
154
- else
155
- raise "Invalid scheme: #{uri.scheme}"
156
- end
157
-
158
- if @command == "status"
159
- message "Puma is started"
171
+ server =
172
+ case uri.scheme
173
+ when 'ssl'
174
+ require 'openssl'
175
+ OpenSSL::SSL::SSLSocket.new(
176
+ TCPSocket.new(uri.host, uri.port),
177
+ OpenSSL::SSL::SSLContext.new)
178
+ .tap { |ssl| ssl.sync_close = true } # default is false
179
+ .tap(&:connect)
180
+ when 'tcp'
181
+ TCPSocket.new uri.host, uri.port
182
+ when 'unix'
183
+ # check for abstract UNIXSocket
184
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
185
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
186
+ else
187
+ raise "Invalid scheme: #{uri.scheme}"
188
+ end
189
+
190
+ if @command == 'status'
191
+ message 'Puma is started'
160
192
  else
161
193
  url = "/#{@command}"
162
194
 
@@ -164,10 +196,10 @@ module Puma
164
196
  url = url + "?token=#{@control_auth_token}"
165
197
  end
166
198
 
167
- server << "GET #{url} HTTP/1.0\r\n\r\n"
199
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
168
200
 
169
201
  unless data = server.read
170
- raise "Server closed connection before responding"
202
+ raise 'Server closed connection before responding'
171
203
  end
172
204
 
173
205
  response = data.split("\r\n")
@@ -176,67 +208,59 @@ module Puma
176
208
  raise "Server sent empty response"
177
209
  end
178
210
 
179
- (@http,@code,@message) = response.first.split(" ",3)
211
+ @http, @code, @message = response.first.split(' ',3)
180
212
 
181
- if @code == "403"
182
- raise "Unauthorized access to server (wrong auth token)"
183
- elsif @code == "404"
213
+ if @code == '403'
214
+ raise 'Unauthorized access to server (wrong auth token)'
215
+ elsif @code == '404'
184
216
  raise "Command error: #{response.last}"
185
- elsif @code != "200"
217
+ elsif @code != '200'
186
218
  raise "Bad response from server: #{@code}"
187
219
  end
188
220
 
189
221
  message "Command #{@command} sent success"
190
- message response.last if @command == "stats" || @command == "gc-stats"
222
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
191
223
  end
192
224
  ensure
193
- server.close if server && !server.closed?
225
+ if server
226
+ if uri.scheme == 'ssl'
227
+ server.sysclose
228
+ else
229
+ server.close unless server.closed?
230
+ end
231
+ end
194
232
  end
195
233
 
196
234
  def send_signal
197
235
  unless @pid
198
- raise "Neither pid nor control url available"
236
+ raise 'Neither pid nor control url available'
199
237
  end
200
238
 
201
239
  begin
240
+ sig = CMD_PATH_SIG_MAP[@command]
202
241
 
203
- case @command
204
- when "restart"
205
- Process.kill "SIGUSR2", @pid
206
-
207
- when "halt"
208
- Process.kill "QUIT", @pid
209
-
210
- when "stop"
211
- Process.kill "SIGTERM", @pid
212
-
213
- when "stats"
214
- puts "Stats not available via pid only"
215
- return
216
-
217
- when "reload-worker-directory"
218
- puts "reload-worker-directory not available via pid only"
242
+ if sig.nil?
243
+ @stdout.puts "'#{@command}' not available via pid only"
244
+ @stdout.flush unless @stdout.sync
219
245
  return
220
-
221
- when "phased-restart"
222
- Process.kill "SIGUSR1", @pid
223
-
224
- when "status"
246
+ elsif sig.start_with? 'SIG'
247
+ if Signal.list.key? sig.sub(/\ASIG/, '')
248
+ Process.kill sig, @pid
249
+ else
250
+ raise "Signal '#{sig}' not available'"
251
+ end
252
+ elsif @command == 'status'
225
253
  begin
226
254
  Process.kill 0, @pid
227
- puts "Puma is started"
255
+ @stdout.puts 'Puma is started'
256
+ @stdout.flush unless @stdout.sync
228
257
  rescue Errno::ESRCH
229
- raise "Puma is not running"
258
+ raise 'Puma is not running'
230
259
  end
231
-
232
- return
233
-
234
- else
235
260
  return
236
261
  end
237
-
238
262
  rescue SystemCallError
239
- if @command == "restart"
263
+ if @command == 'restart'
240
264
  start
241
265
  else
242
266
  raise "No pid '#{@pid}' found"
@@ -247,14 +271,13 @@ module Puma
247
271
  end
248
272
 
249
273
  def run
250
- return start if @command == "start"
251
-
274
+ return start if @command == 'start'
252
275
  prepare_configuration
253
276
 
254
- if Puma.windows?
277
+ if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
255
278
  send_request
256
279
  else
257
- @control_url ? send_request : send_signal
280
+ send_signal
258
281
  end
259
282
 
260
283
  rescue => e
@@ -262,7 +285,7 @@ module Puma
262
285
  exit 1
263
286
  end
264
287
 
265
- private
288
+ private
266
289
  def start
267
290
  require 'puma/cli'
268
291
 
data/lib/puma/detect.rb CHANGED
@@ -1,15 +1,42 @@
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
- IS_JRUBY = defined?(JRUBY_VERSION)
8
+ # @version 5.2.1
9
+ HAS_FORK = ::Process.respond_to? :fork
10
+
11
+ IS_JRUBY = Object.const_defined? :JRUBY_VERSION
12
+
13
+ IS_OSX = RUBY_PLATFORM.include? 'darwin'
14
+
15
+ IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
16
+ IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
17
+
18
+ # @version 5.2.0
19
+ IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
5
20
 
6
21
  def self.jruby?
7
22
  IS_JRUBY
8
23
  end
9
24
 
10
- IS_WINDOWS = RUBY_PLATFORM =~ /mswin|ming|cygwin/
25
+ def self.osx?
26
+ IS_OSX
27
+ end
11
28
 
12
29
  def self.windows?
13
30
  IS_WINDOWS
14
31
  end
32
+
33
+ # @version 5.0.0
34
+ def self.mri?
35
+ IS_MRI
36
+ end
37
+
38
+ # @version 5.0.0
39
+ def self.forkable?
40
+ HAS_FORK
41
+ end
15
42
  end