puma 5.6.5 → 6.0.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.
- checksums.yaml +4 -4
- data/History.md +120 -11
- data/README.md +21 -17
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/nginx.md +1 -1
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +11 -8
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +36 -15
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +156 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +3 -3
- data/lib/puma/binder.rb +36 -42
- data/lib/puma/cli.rb +11 -17
- data/lib/puma/client.rb +26 -13
- data/lib/puma/cluster/worker.rb +13 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +28 -25
- data/lib/puma/configuration.rb +74 -58
- data/lib/puma/const.rb +14 -18
- data/lib/puma/control_cli.rb +3 -6
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +96 -52
- data/lib/puma/error_logger.rb +17 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +96 -156
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +23 -12
- data/lib/puma/minissl.rb +82 -11
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/rack/builder.rb +4 -4
- data/lib/puma/rack_default.rb +1 -1
- data/lib/puma/reactor.rb +4 -4
- data/lib/puma/request.rb +334 -166
- data/lib/puma/runner.rb +41 -20
- data/lib/puma/server.rb +55 -71
- data/lib/puma/single.rb +10 -10
- data/lib/puma/state_file.rb +1 -4
- data/lib/puma/systemd.rb +3 -2
- data/lib/puma/thread_pool.rb +16 -16
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +12 -9
- data/lib/rack/handler/puma.rb +9 -9
- metadata +7 -3
- data/lib/puma/queue_close.rb +0 -26
data/lib/puma/cluster.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
require_relative 'runner'
|
4
|
+
require_relative 'util'
|
5
|
+
require_relative 'plugin'
|
6
|
+
require_relative 'cluster/worker_handle'
|
7
|
+
require_relative 'cluster/worker'
|
8
8
|
|
9
9
|
require 'time'
|
10
10
|
|
@@ -17,8 +17,8 @@ module Puma
|
|
17
17
|
# via the `spawn_workers` method call. Each worker will have it's own
|
18
18
|
# instance of a `Puma::Server`.
|
19
19
|
class Cluster < Runner
|
20
|
-
def initialize(
|
21
|
-
super
|
20
|
+
def initialize(launcher)
|
21
|
+
super(launcher)
|
22
22
|
|
23
23
|
@phase = 0
|
24
24
|
@workers = []
|
@@ -27,6 +27,10 @@ module Puma
|
|
27
27
|
@phased_restart = false
|
28
28
|
end
|
29
29
|
|
30
|
+
# Returns the list of cluster worker handles.
|
31
|
+
# @return [Array<Puma::Cluster::WorkerHandle>]
|
32
|
+
attr_reader :workers
|
33
|
+
|
30
34
|
def stop_workers
|
31
35
|
log "- Gracefully shutting down workers..."
|
32
36
|
@workers.each { |x| x.term }
|
@@ -92,7 +96,7 @@ module Puma
|
|
92
96
|
|
93
97
|
# @version 5.0.0
|
94
98
|
def spawn_worker(idx, master)
|
95
|
-
@
|
99
|
+
@config.run_hooks(:before_worker_fork, idx, @log_writer)
|
96
100
|
|
97
101
|
pid = fork { worker(idx, master) }
|
98
102
|
if !pid
|
@@ -101,7 +105,7 @@ module Puma
|
|
101
105
|
exit! 1
|
102
106
|
end
|
103
107
|
|
104
|
-
@
|
108
|
+
@config.run_hooks(:after_worker_fork, idx, @log_writer)
|
105
109
|
pid
|
106
110
|
end
|
107
111
|
|
@@ -176,10 +180,10 @@ module Puma
|
|
176
180
|
end
|
177
181
|
end
|
178
182
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
].compact.min
|
183
|
+
t = @workers.reject(&:term?)
|
184
|
+
t.map!(&:ping_timeout)
|
185
|
+
|
186
|
+
@next_check = [t.min, @next_check].compact.min
|
183
187
|
end
|
184
188
|
|
185
189
|
def worker(index, master)
|
@@ -209,8 +213,8 @@ module Puma
|
|
209
213
|
stop
|
210
214
|
end
|
211
215
|
|
212
|
-
def phased_restart
|
213
|
-
return false if @options[:preload_app]
|
216
|
+
def phased_restart(refork = false)
|
217
|
+
return false if @options[:preload_app] && !refork
|
214
218
|
|
215
219
|
@phased_restart = true
|
216
220
|
wakeup!
|
@@ -226,7 +230,7 @@ module Puma
|
|
226
230
|
def stop_blocked
|
227
231
|
@status = :stop if @status == :run
|
228
232
|
wakeup!
|
229
|
-
@control
|
233
|
+
@control&.stop true
|
230
234
|
Process.waitall
|
231
235
|
end
|
232
236
|
|
@@ -265,7 +269,7 @@ module Puma
|
|
265
269
|
booted_workers: worker_status.count { |w| w[:booted] },
|
266
270
|
old_workers: old_worker_count,
|
267
271
|
worker_status: worker_status,
|
268
|
-
}
|
272
|
+
}.merge(super)
|
269
273
|
end
|
270
274
|
|
271
275
|
def preload?
|
@@ -277,7 +281,7 @@ module Puma
|
|
277
281
|
if (worker = @workers.find { |w| w.index == 0 })
|
278
282
|
worker.phase += 1
|
279
283
|
end
|
280
|
-
phased_restart
|
284
|
+
phased_restart(true)
|
281
285
|
end
|
282
286
|
|
283
287
|
# We do this in a separate method to keep the lambda scope
|
@@ -290,7 +294,7 @@ module Puma
|
|
290
294
|
|
291
295
|
# Auto-fork after the specified number of requests.
|
292
296
|
if (fork_requests = @options[:fork_worker].to_i) > 0
|
293
|
-
@
|
297
|
+
@events.register(:ping!) do |w|
|
294
298
|
fork_worker! if w.index == 0 &&
|
295
299
|
w.phase == 0 &&
|
296
300
|
w.last_status[:requests_count] >= fork_requests
|
@@ -372,12 +376,12 @@ module Puma
|
|
372
376
|
else
|
373
377
|
log "* Restarts: (\u2714) hot (\u2714) phased"
|
374
378
|
|
375
|
-
unless @
|
379
|
+
unless @config.app_configured?
|
376
380
|
error "No application configured, nothing to run"
|
377
381
|
exit 1
|
378
382
|
end
|
379
383
|
|
380
|
-
@launcher.binder.parse @options[:binds]
|
384
|
+
@launcher.binder.parse @options[:binds]
|
381
385
|
end
|
382
386
|
|
383
387
|
read, @wakeup = Puma::Util.pipe
|
@@ -409,8 +413,7 @@ module Puma
|
|
409
413
|
|
410
414
|
@master_read, @worker_write = read, @wakeup
|
411
415
|
|
412
|
-
@
|
413
|
-
Puma::Util.nakayoshi_gc @events if @options[:nakayoshi_fork]
|
416
|
+
@config.run_hooks(:before_fork, nil, @log_writer)
|
414
417
|
|
415
418
|
spawn_workers
|
416
419
|
|
@@ -463,9 +466,9 @@ module Puma
|
|
463
466
|
w.term unless w.term?
|
464
467
|
when "p"
|
465
468
|
w.ping!(result.sub(/^\d+/,'').chomp)
|
466
|
-
@
|
469
|
+
@events.fire(:ping!, w)
|
467
470
|
if !booted && @workers.none? {|worker| worker.last_status.empty?}
|
468
|
-
@
|
471
|
+
@events.fire_on_booted!
|
469
472
|
booted = true
|
470
473
|
end
|
471
474
|
end
|
data/lib/puma/configuration.rb
CHANGED
@@ -1,21 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require_relative 'rack/builder'
|
4
|
+
require_relative 'plugin'
|
5
|
+
require_relative 'const'
|
6
|
+
# note that dsl is loaded at end of file, requires ConfigDefault constants
|
6
7
|
|
7
8
|
module Puma
|
8
|
-
|
9
|
-
module ConfigDefault
|
10
|
-
DefaultRackup = "config.ru"
|
11
|
-
|
12
|
-
DefaultTCPHost = "0.0.0.0"
|
13
|
-
DefaultTCPPort = 9292
|
14
|
-
DefaultWorkerCheckInterval = 5
|
15
|
-
DefaultWorkerTimeout = 60
|
16
|
-
DefaultWorkerShutdownTimeout = 30
|
17
|
-
end
|
18
|
-
|
19
9
|
# A class used for storing "leveled" configuration options.
|
20
10
|
#
|
21
11
|
# In this class any "user" specified options take precedence over any
|
@@ -136,7 +126,48 @@ module Puma
|
|
136
126
|
# is done because an environment variable may have been modified while loading
|
137
127
|
# configuration files.
|
138
128
|
class Configuration
|
139
|
-
|
129
|
+
DEFAULTS = {
|
130
|
+
auto_trim_time: 30,
|
131
|
+
binds: ['tcp://0.0.0.0:9292'.freeze],
|
132
|
+
clean_thread_locals: false,
|
133
|
+
debug: false,
|
134
|
+
early_hints: nil,
|
135
|
+
environment: 'development'.freeze,
|
136
|
+
# Number of seconds to wait until we get the first data for the request
|
137
|
+
first_data_timeout: 30,
|
138
|
+
io_selector_backend: :auto,
|
139
|
+
log_requests: false,
|
140
|
+
logger: STDOUT,
|
141
|
+
# How many requests to attempt inline before sending a client back to
|
142
|
+
# the reactor to be subject to normal ordering. The idea here is that
|
143
|
+
# we amortize the cost of going back to the reactor for a well behaved
|
144
|
+
# but very "greedy" client across 10 requests. This prevents a not
|
145
|
+
# well behaved client from monopolizing the thread forever.
|
146
|
+
max_fast_inline: 10,
|
147
|
+
max_threads: Puma.mri? ? 5 : 16,
|
148
|
+
min_threads: 0,
|
149
|
+
mode: :http,
|
150
|
+
mutate_stdout_and_stderr_to_sync_on_write: true,
|
151
|
+
out_of_band: [],
|
152
|
+
# Number of seconds for another request within a persistent session.
|
153
|
+
persistent_timeout: 20,
|
154
|
+
queue_requests: true,
|
155
|
+
rackup: 'config.ru'.freeze,
|
156
|
+
raise_exception_on_sigterm: true,
|
157
|
+
reaping_time: 1,
|
158
|
+
remote_address: :socket,
|
159
|
+
silence_single_worker_warning: false,
|
160
|
+
tag: File.basename(Dir.getwd),
|
161
|
+
tcp_host: '0.0.0.0'.freeze,
|
162
|
+
tcp_port: 9292,
|
163
|
+
wait_for_less_busy_worker: 0.005,
|
164
|
+
worker_boot_timeout: 60,
|
165
|
+
worker_check_interval: 5,
|
166
|
+
worker_culling_strategy: :youngest,
|
167
|
+
worker_shutdown_timeout: 30,
|
168
|
+
worker_timeout: 60,
|
169
|
+
workers: 0,
|
170
|
+
}
|
140
171
|
|
141
172
|
def initialize(user_options={}, default_options = {}, &block)
|
142
173
|
default_options = self.puma_default_options.merge(default_options)
|
@@ -181,37 +212,22 @@ module Puma
|
|
181
212
|
self
|
182
213
|
end
|
183
214
|
|
184
|
-
|
185
|
-
|
186
|
-
|
215
|
+
def puma_default_options
|
216
|
+
defaults = DEFAULTS.dup
|
217
|
+
puma_options_from_env.each { |k,v| defaults[k] = v if v }
|
218
|
+
defaults
|
187
219
|
end
|
188
220
|
|
189
|
-
def
|
221
|
+
def puma_options_from_env
|
222
|
+
min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
|
223
|
+
max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
|
224
|
+
workers = ENV['WEB_CONCURRENCY']
|
225
|
+
|
190
226
|
{
|
191
|
-
:
|
192
|
-
:
|
193
|
-
:
|
194
|
-
:
|
195
|
-
:binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
|
196
|
-
:workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
|
197
|
-
:silence_single_worker_warning => false,
|
198
|
-
:mode => :http,
|
199
|
-
:worker_check_interval => DefaultWorkerCheckInterval,
|
200
|
-
:worker_timeout => DefaultWorkerTimeout,
|
201
|
-
:worker_boot_timeout => DefaultWorkerTimeout,
|
202
|
-
:worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
|
203
|
-
:worker_culling_strategy => :youngest,
|
204
|
-
:remote_address => :socket,
|
205
|
-
:tag => method(:infer_tag),
|
206
|
-
:environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
|
207
|
-
:rackup => DefaultRackup,
|
208
|
-
:logger => STDOUT,
|
209
|
-
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
210
|
-
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
|
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,
|
227
|
+
min_threads: min && Integer(min),
|
228
|
+
max_threads: max && Integer(max),
|
229
|
+
workers: workers && Integer(workers),
|
230
|
+
environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
|
215
231
|
}
|
216
232
|
end
|
217
233
|
|
@@ -227,7 +243,7 @@ module Puma
|
|
227
243
|
return [] if files == ['-']
|
228
244
|
return files if files.any?
|
229
245
|
|
230
|
-
first_default_file = %W(config/puma/#{
|
246
|
+
first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
|
231
247
|
File.exist?(f)
|
232
248
|
end
|
233
249
|
|
@@ -270,7 +286,7 @@ module Puma
|
|
270
286
|
found = options[:app] || load_rackup
|
271
287
|
|
272
288
|
if @options[:log_requests]
|
273
|
-
|
289
|
+
require_relative 'commonlogger'
|
274
290
|
logger = @options[:logger]
|
275
291
|
found = CommonLogger.new(found, logger)
|
276
292
|
end
|
@@ -283,21 +299,25 @@ module Puma
|
|
283
299
|
@options[:environment]
|
284
300
|
end
|
285
301
|
|
286
|
-
def environment_str
|
287
|
-
environment.respond_to?(:call) ? environment.call : environment
|
288
|
-
end
|
289
|
-
|
290
302
|
def load_plugin(name)
|
291
303
|
@plugins.create name
|
292
304
|
end
|
293
305
|
|
294
|
-
|
306
|
+
# @param key [:Symbol] hook to run
|
307
|
+
# @param arg [Launcher, Int] `:on_restart` passes Launcher
|
308
|
+
#
|
309
|
+
def run_hooks(key, arg, log_writer, hook_data = nil)
|
295
310
|
@options.all_of(key).each do |b|
|
296
311
|
begin
|
297
|
-
b
|
312
|
+
if Array === b
|
313
|
+
hook_data[b[1]] ||= Hash.new
|
314
|
+
b[0].call arg, hook_data[b[1]]
|
315
|
+
else
|
316
|
+
b.call arg
|
317
|
+
end
|
298
318
|
rescue => e
|
299
|
-
|
300
|
-
|
319
|
+
log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
|
320
|
+
log_writer.debug e.backtrace.join("\n")
|
301
321
|
end
|
302
322
|
end
|
303
323
|
end
|
@@ -315,10 +335,6 @@ module Puma
|
|
315
335
|
|
316
336
|
private
|
317
337
|
|
318
|
-
def infer_tag
|
319
|
-
File.basename(Dir.getwd)
|
320
|
-
end
|
321
|
-
|
322
338
|
# Load and use the normal Rack builder if we can, otherwise
|
323
339
|
# fallback to our minimal version.
|
324
340
|
def rack_builder
|
@@ -368,4 +384,4 @@ module Puma
|
|
368
384
|
end
|
369
385
|
end
|
370
386
|
|
371
|
-
|
387
|
+
require_relative 'dsl'
|
data/lib/puma/const.rb
CHANGED
@@ -100,32 +100,17 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "6.0.1".freeze
|
104
|
+
CODE_NAME = "Sunflower".freeze
|
105
105
|
|
106
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
115
|
REQUEST_URI= 'REQUEST_URI'.freeze
|
131
116
|
REQUEST_PATH = 'REQUEST_PATH'.freeze
|
@@ -163,6 +148,14 @@ module Puma
|
|
163
148
|
|
164
149
|
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
165
150
|
HEAD = "HEAD".freeze
|
151
|
+
GET = "GET".freeze
|
152
|
+
POST = "POST".freeze
|
153
|
+
PUT = "PUT".freeze
|
154
|
+
DELETE = "DELETE".freeze
|
155
|
+
OPTIONS = "OPTIONS".freeze
|
156
|
+
TRACE = "TRACE".freeze
|
157
|
+
PATCH = "PATCH".freeze
|
158
|
+
SUPPORTED_HTTP_METHODS = [HEAD, GET, POST, PUT, DELETE, OPTIONS, TRACE, PATCH].freeze
|
166
159
|
# ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
|
167
160
|
LINE_END = "\r\n".freeze
|
168
161
|
REMOTE_ADDR = "REMOTE_ADDR".freeze
|
@@ -177,7 +170,10 @@ module Puma
|
|
177
170
|
PORT_80 = "80".freeze
|
178
171
|
PORT_443 = "443".freeze
|
179
172
|
LOCALHOST = "localhost".freeze
|
180
|
-
|
173
|
+
LOCALHOST_IPV4 = "127.0.0.1".freeze
|
174
|
+
LOCALHOST_IPV6 = "::1".freeze
|
175
|
+
UNSPECIFIED_IPV4 = "0.0.0.0".freeze
|
176
|
+
UNSPECIFIED_IPV6 = "::".freeze
|
181
177
|
|
182
178
|
SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
|
183
179
|
HTTP_11 = "HTTP/1.1".freeze
|
data/lib/puma/control_cli.rb
CHANGED
@@ -33,9 +33,6 @@ module Puma
|
|
33
33
|
'worker-count-up' => 'SIGTTIN'
|
34
34
|
}.freeze
|
35
35
|
|
36
|
-
# @deprecated 6.0.0
|
37
|
-
COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
|
38
|
-
|
39
36
|
# commands that cannot be used in a request
|
40
37
|
NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
|
41
38
|
|
@@ -287,7 +284,7 @@ module Puma
|
|
287
284
|
|
288
285
|
private
|
289
286
|
def start
|
290
|
-
|
287
|
+
require_relative 'cli'
|
291
288
|
|
292
289
|
run_args = []
|
293
290
|
|
@@ -299,13 +296,13 @@ module Puma
|
|
299
296
|
run_args += ["-C", @config_file] if @config_file
|
300
297
|
run_args += ["-e", @environment] if @environment
|
301
298
|
|
302
|
-
|
299
|
+
log_writer = Puma::LogWriter.new(@stdout, @stderr)
|
303
300
|
|
304
301
|
# replace $0 because puma use it to generate restart command
|
305
302
|
puma_cmd = $0.gsub(/pumactl$/, 'puma')
|
306
303
|
$0 = puma_cmd if File.exist?(puma_cmd)
|
307
304
|
|
308
|
-
cli = Puma::CLI.new run_args,
|
305
|
+
cli = Puma::CLI.new run_args, log_writer
|
309
306
|
cli.run
|
310
307
|
end
|
311
308
|
end
|
data/lib/puma/detect.rb
CHANGED