puma 4.3.6 → 5.6.4

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1486 -518
  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 +46 -9
  28. data/ext/puma_http11/http11_parser.c +68 -57
  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 +1 -1
  33. data/ext/puma_http11/mini_ssl.c +275 -122
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +51 -51
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +105 -61
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +47 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +174 -91
  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 +18 -9
  49. data/lib/puma/control_cli.rb +93 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +364 -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 +117 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +128 -46
  60. data/lib/puma/null_io.rb +13 -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 +472 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +287 -743
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +47 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +20 -1
  76. data/lib/puma.rb +46 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  79. data/tools/trickletest.rb +0 -0
  80. metadata +28 -24
  81. data/docs/tcp_mode.md +0 -96
  82. data/ext/puma_http11/io_buffer.c +0 -155
  83. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  84. data/lib/puma/accept_nonblock.rb +0 -29
  85. data/lib/puma/tcp_logger.rb +0 -41
  86. data/tools/jungle/README.md +0 -19
  87. data/tools/jungle/init.d/README.md +0 -61
  88. data/tools/jungle/init.d/puma +0 -421
  89. data/tools/jungle/init.d/run-puma +0 -18
  90. data/tools/jungle/upstart/README.md +0 -61
  91. data/tools/jungle/upstart/puma-manager.conf +0 -31
  92. 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
@@ -76,7 +76,7 @@ module Puma
76
76
  508 => 'Loop Detected',
77
77
  510 => 'Not Extended',
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,8 +100,9 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "4.3.6".freeze
104
- CODE_NAME = "Mysterious Traveller".freeze
103
+ PUMA_VERSION = VERSION = "5.6.4".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
@@ -144,9 +145,11 @@ module Puma
144
145
  408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
145
146
  # Indicate that there was an internal error, obviously.
146
147
  500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
148
+ # Incorrect or invalid header value
149
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
147
150
  # A common header for indicating the server is too busy. Not used yet.
148
151
  503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
149
- }
152
+ }.freeze
150
153
 
151
154
  # The basic max request size we'll try to read.
152
155
  CHUNK_SIZE = 16 * 1024
@@ -175,7 +178,6 @@ module Puma
175
178
  PORT_443 = "443".freeze
176
179
  LOCALHOST = "localhost".freeze
177
180
  LOCALHOST_IP = "127.0.0.1".freeze
178
- LOCALHOST_ADDR = "127.0.0.1:0".freeze
179
181
 
180
182
  SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
181
183
  HTTP_11 = "HTTP/1.1".freeze
@@ -228,7 +230,6 @@ module Puma
228
230
  COLON = ": ".freeze
229
231
 
230
232
  NEWLINE = "\n".freeze
231
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
232
233
 
233
234
  HIJACK_P = "rack.hijack?".freeze
234
235
  HIJACK = "rack.hijack".freeze
@@ -236,8 +237,16 @@ module Puma
236
237
 
237
238
  EARLY_HINTS = "rack.early_hints".freeze
238
239
 
239
- # Mininum interval to checks worker health
240
- WORKER_CHECK_INTERVAL = 5
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
+ # Banned keys of response header
248
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
241
249
 
250
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
242
251
  end
243
252
  end
@@ -11,7 +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}
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
37
+
38
+ # @version 5.0.0
39
+ PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
15
40
 
16
41
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
17
42
  @state = nil
@@ -22,7 +47,7 @@ module Puma
22
47
  @control_auth_token = nil
23
48
  @config_file = nil
24
49
  @command = nil
25
- @environment = ENV['RACK_ENV']
50
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
26
51
 
27
52
  @argv = argv.dup
28
53
  @stdout = stdout
@@ -30,7 +55,7 @@ module Puma
30
55
  @cli_options = {}
31
56
 
32
57
  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("|")})"
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("|")})"
34
59
 
35
60
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
36
61
  @state = arg
@@ -71,7 +96,7 @@ module Puma
71
96
  end
72
97
 
73
98
  o.on_tail("-V", "--version", "Show version") do
74
- puts Const::PUMA_VERSION
99
+ @stdout.puts Const::PUMA_VERSION
75
100
  exit
76
101
  end
77
102
  end
@@ -81,6 +106,15 @@ module Puma
81
106
 
82
107
  @command = argv.shift
83
108
 
109
+ # check presence of command
110
+ unless @command
111
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
112
+ end
113
+
114
+ unless CMD_PATH_SIG_MAP.key? @command
115
+ raise "Invalid command: #{@command}"
116
+ end
117
+
84
118
  unless @config_file == '-'
85
119
  environment = @environment || 'development'
86
120
 
@@ -99,16 +133,6 @@ module Puma
99
133
  @pidfile ||= config.options[:pidfile]
100
134
  end
101
135
  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
136
  rescue => e
113
137
  @stdout.puts e.message
114
138
  exit 1
@@ -132,7 +156,7 @@ module Puma
132
156
  @pid = sf.pid
133
157
  elsif @pidfile
134
158
  # get pid from pid_file
135
- File.open(@pidfile) { |f| @pid = f.read.to_i }
159
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
136
160
  end
137
161
  end
138
162
 
@@ -140,23 +164,29 @@ module Puma
140
164
  uri = URI.parse @control_url
141
165
 
142
166
  # 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"
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}"
160
190
  else
161
191
  url = "/#{@command}"
162
192
 
@@ -164,10 +194,10 @@ module Puma
164
194
  url = url + "?token=#{@control_auth_token}"
165
195
  end
166
196
 
167
- server << "GET #{url} HTTP/1.0\r\n\r\n"
197
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
168
198
 
169
199
  unless data = server.read
170
- raise "Server closed connection before responding"
200
+ raise 'Server closed connection before responding'
171
201
  end
172
202
 
173
203
  response = data.split("\r\n")
@@ -176,67 +206,55 @@ module Puma
176
206
  raise "Server sent empty response"
177
207
  end
178
208
 
179
- (@http,@code,@message) = response.first.split(" ",3)
209
+ @http, @code, @message = response.first.split(' ',3)
180
210
 
181
- if @code == "403"
182
- raise "Unauthorized access to server (wrong auth token)"
183
- elsif @code == "404"
211
+ if @code == '403'
212
+ raise 'Unauthorized access to server (wrong auth token)'
213
+ elsif @code == '404'
184
214
  raise "Command error: #{response.last}"
185
- elsif @code != "200"
215
+ elsif @code != '200'
186
216
  raise "Bad response from server: #{@code}"
187
217
  end
188
218
 
189
219
  message "Command #{@command} sent success"
190
- message response.last if @command == "stats" || @command == "gc-stats"
220
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
191
221
  end
192
222
  ensure
193
- server.close if server && !server.closed?
223
+ if server
224
+ if uri.scheme == 'ssl'
225
+ server.sysclose
226
+ else
227
+ server.close unless server.closed?
228
+ end
229
+ end
194
230
  end
195
231
 
196
232
  def send_signal
197
233
  unless @pid
198
- raise "Neither pid nor control url available"
234
+ raise 'Neither pid nor control url available'
199
235
  end
200
236
 
201
237
  begin
238
+ sig = CMD_PATH_SIG_MAP[@command]
202
239
 
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"
240
+ if sig.nil?
241
+ @stdout.puts "'#{@command}' not available via pid only"
242
+ @stdout.flush unless @stdout.sync
219
243
  return
220
-
221
- when "phased-restart"
222
- Process.kill "SIGUSR1", @pid
223
-
224
- when "status"
244
+ elsif sig.start_with? 'SIG'
245
+ Process.kill sig, @pid
246
+ elsif @command == 'status'
225
247
  begin
226
248
  Process.kill 0, @pid
227
- puts "Puma is started"
249
+ @stdout.puts 'Puma is started'
250
+ @stdout.flush unless @stdout.sync
228
251
  rescue Errno::ESRCH
229
- raise "Puma is not running"
252
+ raise 'Puma is not running'
230
253
  end
231
-
232
- return
233
-
234
- else
235
254
  return
236
255
  end
237
-
238
256
  rescue SystemCallError
239
- if @command == "restart"
257
+ if @command == 'restart'
240
258
  start
241
259
  else
242
260
  raise "No pid '#{@pid}' found"
@@ -247,14 +265,13 @@ module Puma
247
265
  end
248
266
 
249
267
  def run
250
- return start if @command == "start"
251
-
268
+ return start if @command == 'start'
252
269
  prepare_configuration
253
270
 
254
- if Puma.windows?
271
+ if Puma.windows? || @control_url
255
272
  send_request
256
273
  else
257
- @control_url ? send_request : send_signal
274
+ send_signal
258
275
  end
259
276
 
260
277
  rescue => e
@@ -262,7 +279,7 @@ module Puma
262
279
  exit 1
263
280
  end
264
281
 
265
- private
282
+ private
266
283
  def start
267
284
  require 'puma/cli'
268
285
 
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