puma 5.2.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.
- checksums.yaml +4 -4
- data/History.md +483 -4
- data/README.md +101 -20
- data/bin/puma-wild +1 -1
- data/docs/architecture.md +50 -16
- data/docs/compile_options.md +38 -2
- data/docs/deployment.md +53 -67
- data/docs/fork_worker.md +1 -3
- data/docs/jungle/rc.d/README.md +1 -1
- data/docs/kubernetes.md +1 -1
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +2 -3
- data/docs/restart.md +7 -7
- data/docs/signals.md +11 -10
- data/docs/stats.md +8 -8
- data/docs/systemd.md +65 -69
- 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 +44 -13
- data/ext/puma_http11/http11_parser.c +24 -11
- data/ext/puma_http11/http11_parser.h +2 -2
- 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 +3 -3
- data/ext/puma_http11/mini_ssl.c +150 -23
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +50 -48
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +188 -102
- data/ext/puma_http11/puma_http11.c +18 -10
- data/lib/puma/app/status.rb +10 -7
- data/lib/puma/binder.rb +112 -62
- data/lib/puma/cli.rb +24 -20
- data/lib/puma/client.rb +162 -36
- data/lib/puma/cluster/worker.rb +31 -27
- data/lib/puma/cluster/worker_handle.rb +12 -1
- data/lib/puma/cluster.rb +102 -61
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +78 -54
- data/lib/puma/const.rb +135 -97
- data/lib/puma/control_cli.rb +25 -20
- data/lib/puma/detect.rb +12 -2
- data/lib/puma/dsl.rb +308 -58
- data/lib/puma/error_logger.rb +20 -11
- 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/{json.rb → json_serialization.rb} +1 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +114 -173
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +30 -16
- data/lib/puma/minissl.rb +132 -38
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +2 -2
- data/lib/puma/rack/builder.rb +7 -7
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +373 -153
- data/lib/puma/runner.rb +74 -28
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +127 -136
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +39 -7
- data/lib/puma/thread_pool.rb +33 -26
- data/lib/puma/util.rb +20 -15
- data/lib/puma.rb +28 -11
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +1 -1
- metadata +15 -10
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
data/lib/puma/server.rb
CHANGED
@@ -2,18 +2,19 @@
|
|
2
2
|
|
3
3
|
require 'stringio'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
require_relative 'thread_pool'
|
6
|
+
require_relative 'const'
|
7
|
+
require_relative 'log_writer'
|
8
|
+
require_relative 'events'
|
9
|
+
require_relative 'null_io'
|
10
|
+
require_relative 'reactor'
|
11
|
+
require_relative 'client'
|
12
|
+
require_relative 'binder'
|
13
|
+
require_relative 'util'
|
14
|
+
require_relative 'request'
|
15
15
|
|
16
16
|
require 'socket'
|
17
|
+
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
17
18
|
require 'forwardable'
|
18
19
|
|
19
20
|
module Puma
|
@@ -29,12 +30,12 @@ module Puma
|
|
29
30
|
#
|
30
31
|
# Each `Puma::Server` will have one reactor and one thread pool.
|
31
32
|
class Server
|
32
|
-
|
33
33
|
include Puma::Const
|
34
34
|
include Request
|
35
35
|
extend Forwardable
|
36
36
|
|
37
37
|
attr_reader :thread
|
38
|
+
attr_reader :log_writer
|
38
39
|
attr_reader :events
|
39
40
|
attr_reader :min_threads, :max_threads # for #stats
|
40
41
|
attr_reader :requests_count # @version 5.0.0
|
@@ -44,23 +45,19 @@ module Puma
|
|
44
45
|
:leak_stack_on_error,
|
45
46
|
:persistent_timeout, :reaping_time
|
46
47
|
|
47
|
-
# @deprecated v6.0.0
|
48
|
-
attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
|
49
|
-
:leak_stack_on_error, :min_threads, :max_threads,
|
50
|
-
:persistent_timeout, :reaping_time
|
51
|
-
|
52
48
|
attr_accessor :app
|
53
49
|
attr_accessor :binder
|
54
50
|
|
55
51
|
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
|
56
52
|
:add_unix_listener, :connected_ports
|
57
53
|
|
58
|
-
|
54
|
+
THREAD_LOCAL_KEY = :puma_server
|
59
55
|
|
60
56
|
# Create a server for the rack app +app+.
|
61
57
|
#
|
62
|
-
# +
|
63
|
-
#
|
58
|
+
# +log_writer+ is a Puma::LogWriter object used to log info and error messages.
|
59
|
+
#
|
60
|
+
# +events+ is a Puma::Events object used to notify application status events.
|
64
61
|
#
|
65
62
|
# Server#run returns a thread that you can join on to wait for the server
|
66
63
|
# to do its work.
|
@@ -69,34 +66,53 @@ module Puma
|
|
69
66
|
# and have default values set via +fetch+. Normally the values are set via
|
70
67
|
# `::Puma::Configuration.puma_default_options`.
|
71
68
|
#
|
72
|
-
|
69
|
+
# @note The `events` parameter is set to nil, and set to `Events.new` in code.
|
70
|
+
# Often `options` needs to be passed, but `events` does not. Using nil allows
|
71
|
+
# calling code to not require events.rb.
|
72
|
+
#
|
73
|
+
def initialize(app, events = nil, options = {})
|
73
74
|
@app = app
|
74
|
-
@events = events
|
75
|
+
@events = events || Events.new
|
75
76
|
|
76
77
|
@check, @notify = nil
|
77
78
|
@status = :stop
|
78
79
|
|
79
|
-
@auto_trim_time = 30
|
80
|
-
@reaping_time = 1
|
81
|
-
|
82
80
|
@thread = nil
|
83
81
|
@thread_pool = nil
|
84
82
|
|
85
|
-
@options = options
|
83
|
+
@options = if options.is_a?(UserFileDefaultOptions)
|
84
|
+
options
|
85
|
+
else
|
86
|
+
UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
|
87
|
+
end
|
86
88
|
|
87
|
-
@
|
88
|
-
@
|
89
|
-
@
|
90
|
-
@
|
91
|
-
@
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@
|
89
|
+
@log_writer = @options.fetch :log_writer, LogWriter.stdio
|
90
|
+
@early_hints = @options[:early_hints]
|
91
|
+
@first_data_timeout = @options[:first_data_timeout]
|
92
|
+
@min_threads = @options[:min_threads]
|
93
|
+
@max_threads = @options[:max_threads]
|
94
|
+
@persistent_timeout = @options[:persistent_timeout]
|
95
|
+
@queue_requests = @options[:queue_requests]
|
96
|
+
@max_fast_inline = @options[:max_fast_inline]
|
97
|
+
@io_selector_backend = @options[:io_selector_backend]
|
98
|
+
@http_content_length_limit = @options[:http_content_length_limit]
|
99
|
+
|
100
|
+
# make this a hash, since we prefer `key?` over `include?`
|
101
|
+
@supported_http_methods =
|
102
|
+
if @options[:supported_http_methods] == :any
|
103
|
+
:any
|
104
|
+
else
|
105
|
+
if (ary = @options[:supported_http_methods])
|
106
|
+
ary
|
107
|
+
else
|
108
|
+
SUPPORTED_HTTP_METHODS
|
109
|
+
end.sort.product([nil]).to_h.freeze
|
110
|
+
end
|
95
111
|
|
96
112
|
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
97
113
|
@leak_stack_on_error = @options[:environment] ? temp : true
|
98
114
|
|
99
|
-
@binder = Binder.new(
|
115
|
+
@binder = Binder.new(log_writer)
|
100
116
|
|
101
117
|
ENV['RACK_ENV'] ||= "development"
|
102
118
|
|
@@ -114,7 +130,7 @@ module Puma
|
|
114
130
|
class << self
|
115
131
|
# @!attribute [r] current
|
116
132
|
def current
|
117
|
-
Thread.current[
|
133
|
+
Thread.current[THREAD_LOCAL_KEY]
|
118
134
|
end
|
119
135
|
|
120
136
|
# :nodoc:
|
@@ -137,8 +153,6 @@ module Puma
|
|
137
153
|
# socket parameter may be an MiniSSL::Socket, so use to_io
|
138
154
|
#
|
139
155
|
if tcp_cork_supported?
|
140
|
-
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
141
|
-
|
142
156
|
# 6 == Socket::IPPROTO_TCP
|
143
157
|
# 3 == TCP_CORK
|
144
158
|
# 1/0 == turn on/off
|
@@ -147,7 +161,7 @@ module Puma
|
|
147
161
|
begin
|
148
162
|
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
|
149
163
|
rescue IOError, SystemCallError
|
150
|
-
|
164
|
+
Puma::Util.purge_interrupt_queue
|
151
165
|
end
|
152
166
|
end
|
153
167
|
|
@@ -156,7 +170,7 @@ module Puma
|
|
156
170
|
begin
|
157
171
|
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
|
158
172
|
rescue IOError, SystemCallError
|
159
|
-
|
173
|
+
Puma::Util.purge_interrupt_queue
|
160
174
|
end
|
161
175
|
end
|
162
176
|
else
|
@@ -168,14 +182,16 @@ module Puma
|
|
168
182
|
end
|
169
183
|
|
170
184
|
if closed_socket_supported?
|
185
|
+
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
186
|
+
|
171
187
|
def closed_socket?(socket)
|
172
|
-
|
173
|
-
return false unless @precheck_closing
|
188
|
+
skt = socket.to_io
|
189
|
+
return false unless skt.kind_of?(TCPSocket) && @precheck_closing
|
174
190
|
|
175
191
|
begin
|
176
|
-
tcp_info =
|
192
|
+
tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
177
193
|
rescue IOError, SystemCallError
|
178
|
-
|
194
|
+
Puma::Util.purge_interrupt_queue
|
179
195
|
@precheck_closing = false
|
180
196
|
false
|
181
197
|
else
|
@@ -192,12 +208,12 @@ module Puma
|
|
192
208
|
|
193
209
|
# @!attribute [r] backlog
|
194
210
|
def backlog
|
195
|
-
@thread_pool
|
211
|
+
@thread_pool&.backlog
|
196
212
|
end
|
197
213
|
|
198
214
|
# @!attribute [r] running
|
199
215
|
def running
|
200
|
-
@thread_pool
|
216
|
+
@thread_pool&.spawned
|
201
217
|
end
|
202
218
|
|
203
219
|
|
@@ -210,7 +226,7 @@ module Puma
|
|
210
226
|
# value would be 4 until it finishes processing.
|
211
227
|
# @!attribute [r] pool_capacity
|
212
228
|
def pool_capacity
|
213
|
-
@thread_pool
|
229
|
+
@thread_pool&.pool_capacity
|
214
230
|
end
|
215
231
|
|
216
232
|
# Runs the server.
|
@@ -219,35 +235,23 @@ module Puma
|
|
219
235
|
# up in the background to handle requests. Otherwise requests
|
220
236
|
# are handled synchronously.
|
221
237
|
#
|
222
|
-
def run(background=true, thread_name: '
|
238
|
+
def run(background=true, thread_name: 'srv')
|
223
239
|
BasicSocket.do_not_reverse_lookup = true
|
224
240
|
|
225
241
|
@events.fire :state, :booting
|
226
242
|
|
227
243
|
@status = :run
|
228
244
|
|
229
|
-
@thread_pool = ThreadPool.new(
|
230
|
-
@min_threads,
|
231
|
-
@max_threads,
|
232
|
-
::Puma::IOBuffer,
|
233
|
-
&method(:process_client)
|
234
|
-
)
|
235
|
-
|
236
|
-
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
237
|
-
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
245
|
+
@thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
|
238
246
|
|
239
247
|
if @queue_requests
|
240
|
-
@reactor = Reactor.new(@io_selector_backend
|
248
|
+
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
|
241
249
|
@reactor.run
|
242
250
|
end
|
243
251
|
|
244
|
-
if @reaping_time
|
245
|
-
@thread_pool.auto_reap!(@reaping_time)
|
246
|
-
end
|
247
252
|
|
248
|
-
if @
|
249
|
-
|
250
|
-
end
|
253
|
+
@thread_pool.auto_reap! if @options[:reaping_time]
|
254
|
+
@thread_pool.auto_trim! if @options[:auto_trim_time]
|
251
255
|
|
252
256
|
@check, @notify = Puma::Util.pipe unless @notify
|
253
257
|
|
@@ -295,6 +299,9 @@ module Puma
|
|
295
299
|
@thread_pool << client
|
296
300
|
elsif shutdown || client.timeout == 0
|
297
301
|
client.timeout!
|
302
|
+
else
|
303
|
+
client.set_timeout(@first_data_timeout)
|
304
|
+
false
|
298
305
|
end
|
299
306
|
rescue StandardError => e
|
300
307
|
client_error(e, client)
|
@@ -308,47 +315,52 @@ module Puma
|
|
308
315
|
sockets = [check] + @binder.ios
|
309
316
|
pool = @thread_pool
|
310
317
|
queue_requests = @queue_requests
|
318
|
+
drain = @options[:drain_on_shutdown] ? 0 : nil
|
311
319
|
|
312
|
-
|
313
|
-
remote_addr_header = nil
|
314
|
-
|
315
|
-
case @options[:remote_address]
|
320
|
+
addr_send_name, addr_value = case @options[:remote_address]
|
316
321
|
when :value
|
317
|
-
|
322
|
+
[:peerip=, @options[:remote_address_value]]
|
318
323
|
when :header
|
319
|
-
remote_addr_header
|
324
|
+
[:remote_addr_header=, @options[:remote_address_header]]
|
325
|
+
when :proxy_protocol
|
326
|
+
[:expect_proxy_proto=, @options[:remote_address_proxy_protocol]]
|
327
|
+
else
|
328
|
+
[nil, nil]
|
320
329
|
end
|
321
330
|
|
322
|
-
while @status == :run
|
331
|
+
while @status == :run || (drain && shutting_down?)
|
323
332
|
begin
|
324
|
-
ios = IO.select sockets
|
333
|
+
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
|
334
|
+
break unless ios
|
325
335
|
ios.first.each do |sock|
|
326
336
|
if sock == check
|
327
337
|
break if handle_check
|
328
338
|
else
|
329
339
|
pool.wait_until_not_full
|
330
|
-
pool.wait_for_less_busy_worker(
|
331
|
-
@options[:wait_for_less_busy_worker].to_f)
|
340
|
+
pool.wait_for_less_busy_worker(@options[:wait_for_less_busy_worker])
|
332
341
|
|
333
342
|
io = begin
|
334
343
|
sock.accept_nonblock
|
335
344
|
rescue IO::WaitReadable
|
336
345
|
next
|
337
346
|
end
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
pool << client
|
347
|
+
drain += 1 if shutting_down?
|
348
|
+
pool << Client.new(io, @binder.env(sock)).tap { |c|
|
349
|
+
c.listener = sock
|
350
|
+
c.http_content_length_limit = @http_content_length_limit
|
351
|
+
c.send(addr_send_name, addr_value) if addr_value
|
352
|
+
}
|
345
353
|
end
|
346
354
|
end
|
347
|
-
rescue
|
348
|
-
|
355
|
+
rescue IOError, Errno::EBADF
|
356
|
+
# In the case that any of the sockets are unexpectedly close.
|
357
|
+
raise
|
358
|
+
rescue StandardError => e
|
359
|
+
@log_writer.unknown_error e, nil, "Listen loop"
|
349
360
|
end
|
350
361
|
end
|
351
362
|
|
363
|
+
@log_writer.debug "Drained #{drain} additional connections." if drain
|
352
364
|
@events.fire :state, @status
|
353
365
|
|
354
366
|
if queue_requests
|
@@ -357,15 +369,15 @@ module Puma
|
|
357
369
|
end
|
358
370
|
graceful_shutdown if @status == :stop || @status == :restart
|
359
371
|
rescue Exception => e
|
360
|
-
@
|
372
|
+
@log_writer.unknown_error e, nil, "Exception handling servers"
|
361
373
|
ensure
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
374
|
+
# Errno::EBADF is infrequently raised
|
375
|
+
[@check, @notify].each do |io|
|
376
|
+
begin
|
377
|
+
io.close unless io.closed?
|
378
|
+
rescue Errno::EBADF
|
379
|
+
end
|
367
380
|
end
|
368
|
-
@notify.close
|
369
381
|
@notify = nil
|
370
382
|
@check = nil
|
371
383
|
end
|
@@ -389,7 +401,7 @@ module Puma
|
|
389
401
|
return true
|
390
402
|
end
|
391
403
|
|
392
|
-
|
404
|
+
false
|
393
405
|
end
|
394
406
|
|
395
407
|
# Given a connection on +client+, handle the incoming requests,
|
@@ -402,9 +414,9 @@ module Puma
|
|
402
414
|
# returning.
|
403
415
|
#
|
404
416
|
# Return true if one or more requests were processed.
|
405
|
-
def process_client(client
|
417
|
+
def process_client(client)
|
406
418
|
# Advertise this server into the thread
|
407
|
-
Thread.current[
|
419
|
+
Thread.current[THREAD_LOCAL_KEY] = self
|
408
420
|
|
409
421
|
clean_thread_locals = @options[:clean_thread_locals]
|
410
422
|
close_socket = true
|
@@ -428,31 +440,28 @@ module Puma
|
|
428
440
|
|
429
441
|
while true
|
430
442
|
@requests_count += 1
|
431
|
-
case handle_request(client,
|
443
|
+
case handle_request(client, requests + 1)
|
432
444
|
when false
|
433
445
|
break
|
434
446
|
when :async
|
435
447
|
close_socket = false
|
436
448
|
break
|
437
449
|
when true
|
438
|
-
buffer.reset
|
439
|
-
|
440
450
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
441
451
|
|
442
452
|
requests += 1
|
443
453
|
|
444
|
-
|
454
|
+
# As an optimization, try to read the next request from the
|
455
|
+
# socket for a short time before returning to the reactor.
|
456
|
+
fast_check = @status == :run
|
445
457
|
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
# one once every max_fast_inline requests.
|
451
|
-
check_for_more_data = false
|
452
|
-
end
|
458
|
+
# Always pass the client back to the reactor after a reasonable
|
459
|
+
# number of inline requests if there are other requests pending.
|
460
|
+
fast_check = false if requests >= @max_fast_inline &&
|
461
|
+
@thread_pool.backlog > 0
|
453
462
|
|
454
463
|
next_request_ready = with_force_shutdown(client) do
|
455
|
-
client.reset(
|
464
|
+
client.reset(fast_check)
|
456
465
|
end
|
457
466
|
|
458
467
|
unless next_request_ready
|
@@ -471,15 +480,15 @@ module Puma
|
|
471
480
|
# The ensure tries to close +client+ down
|
472
481
|
requests > 0
|
473
482
|
ensure
|
474
|
-
|
483
|
+
client.io_buffer.reset
|
475
484
|
|
476
485
|
begin
|
477
486
|
client.close if close_socket
|
478
487
|
rescue IOError, SystemCallError
|
479
|
-
|
488
|
+
Puma::Util.purge_interrupt_queue
|
480
489
|
# Already closed
|
481
490
|
rescue StandardError => e
|
482
|
-
@
|
491
|
+
@log_writer.unknown_error e, nil, "Client"
|
483
492
|
end
|
484
493
|
end
|
485
494
|
end
|
@@ -502,13 +511,16 @@ module Puma
|
|
502
511
|
lowlevel_error(e, client.env)
|
503
512
|
case e
|
504
513
|
when MiniSSL::SSLError
|
505
|
-
@
|
514
|
+
@log_writer.ssl_error e, client.io
|
506
515
|
when HttpParserError
|
507
516
|
client.write_error(400)
|
508
|
-
@
|
517
|
+
@log_writer.parse_error e, client
|
518
|
+
when HttpParserError501
|
519
|
+
client.write_error(501)
|
520
|
+
@log_writer.parse_error e, client
|
509
521
|
else
|
510
522
|
client.write_error(500)
|
511
|
-
@
|
523
|
+
@log_writer.unknown_error e, nil, "Read"
|
512
524
|
end
|
513
525
|
end
|
514
526
|
|
@@ -526,7 +538,8 @@ module Puma
|
|
526
538
|
end
|
527
539
|
|
528
540
|
if @leak_stack_on_error
|
529
|
-
|
541
|
+
backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
|
542
|
+
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
|
530
543
|
else
|
531
544
|
[status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
532
545
|
end
|
@@ -550,28 +563,6 @@ module Puma
|
|
550
563
|
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
551
564
|
end
|
552
565
|
|
553
|
-
if @options[:drain_on_shutdown]
|
554
|
-
count = 0
|
555
|
-
|
556
|
-
while true
|
557
|
-
ios = IO.select @binder.ios, nil, nil, 0
|
558
|
-
break unless ios
|
559
|
-
|
560
|
-
ios.first.each do |sock|
|
561
|
-
begin
|
562
|
-
if io = sock.accept_nonblock
|
563
|
-
count += 1
|
564
|
-
client = Client.new io, @binder.env(sock)
|
565
|
-
@thread_pool << client
|
566
|
-
end
|
567
|
-
rescue SystemCallError
|
568
|
-
end
|
569
|
-
end
|
570
|
-
end
|
571
|
-
|
572
|
-
@events.debug "Drained #{count} additional connections."
|
573
|
-
end
|
574
|
-
|
575
566
|
if @status != :restart
|
576
567
|
@binder.close
|
577
568
|
end
|
@@ -587,13 +578,13 @@ module Puma
|
|
587
578
|
|
588
579
|
def notify_safely(message)
|
589
580
|
@notify << message
|
590
|
-
rescue IOError, NoMethodError, Errno::EPIPE
|
581
|
+
rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
|
591
582
|
# The server, in another thread, is shutting down
|
592
|
-
|
583
|
+
Puma::Util.purge_interrupt_queue
|
593
584
|
rescue RuntimeError => e
|
594
585
|
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
595
586
|
if e.message.include?('IOError')
|
596
|
-
|
587
|
+
Puma::Util.purge_interrupt_queue
|
597
588
|
else
|
598
589
|
raise e
|
599
590
|
end
|
data/lib/puma/single.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require_relative 'runner'
|
4
|
+
require_relative 'detect'
|
5
|
+
require_relative 'plugin'
|
6
6
|
|
7
7
|
module Puma
|
8
8
|
# This class is instantiated by the `Puma::Launcher` and used
|
@@ -16,26 +16,26 @@ module Puma
|
|
16
16
|
# @!attribute [r] stats
|
17
17
|
def stats
|
18
18
|
{
|
19
|
-
started_at: @started_at
|
20
|
-
}.merge(@server.stats)
|
19
|
+
started_at: utc_iso8601(@started_at)
|
20
|
+
}.merge(@server.stats).merge(super)
|
21
21
|
end
|
22
22
|
|
23
23
|
def restart
|
24
|
-
@server
|
24
|
+
@server&.begin_restart
|
25
25
|
end
|
26
26
|
|
27
27
|
def stop
|
28
|
-
@server
|
28
|
+
@server&.stop false
|
29
29
|
end
|
30
30
|
|
31
31
|
def halt
|
32
|
-
@server
|
32
|
+
@server&.halt
|
33
33
|
end
|
34
34
|
|
35
35
|
def stop_blocked
|
36
36
|
log "- Gracefully stopping, waiting for requests to finish"
|
37
|
-
@control
|
38
|
-
@server
|
37
|
+
@control&.stop true
|
38
|
+
@server&.stop true
|
39
39
|
end
|
40
40
|
|
41
41
|
def run
|
@@ -55,7 +55,9 @@ module Puma
|
|
55
55
|
log "Use Ctrl-C to stop"
|
56
56
|
redirect_io
|
57
57
|
|
58
|
-
@
|
58
|
+
@events.fire_on_booted!
|
59
|
+
|
60
|
+
debug_loaded_extensions("Loaded Extensions:") if @log_writer.debug?
|
59
61
|
|
60
62
|
begin
|
61
63
|
server_thread.join
|
data/lib/puma/state_file.rb
CHANGED
@@ -1,15 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'yaml'
|
4
|
-
|
5
3
|
module Puma
|
4
|
+
|
5
|
+
# Puma::Launcher uses StateFile to write a yaml file for use with Puma::ControlCLI.
|
6
|
+
#
|
7
|
+
# In previous versions of Puma, YAML was used to read/write the state file.
|
8
|
+
# Since Puma is similar to Bundler/RubyGems in that it may load before one's app
|
9
|
+
# does, minimizing the dependencies that may be shared with the app is desired.
|
10
|
+
#
|
11
|
+
# At present, it only works with numeric and string values. It is still a valid
|
12
|
+
# yaml file, and the CI tests parse it with Psych.
|
13
|
+
#
|
6
14
|
class StateFile
|
15
|
+
|
16
|
+
ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
|
17
|
+
|
7
18
|
def initialize
|
8
19
|
@options = {}
|
9
20
|
end
|
10
21
|
|
11
22
|
def save(path, permission = nil)
|
12
|
-
contents =
|
23
|
+
contents = +"---\n"
|
24
|
+
@options.each do |k,v|
|
25
|
+
next unless ALLOWED_FIELDS.include? k
|
26
|
+
case v
|
27
|
+
when Numeric
|
28
|
+
contents << "#{k}: #{v}\n"
|
29
|
+
when String
|
30
|
+
next if v.strip.empty?
|
31
|
+
contents << (k == 'running_from' || v.to_s.include?(' ') ?
|
32
|
+
"#{k}: \"#{v}\"\n" : "#{k}: #{v}\n")
|
33
|
+
end
|
34
|
+
end
|
13
35
|
if permission
|
14
36
|
File.write path, contents, mode: 'wb:UTF-8'
|
15
37
|
else
|
@@ -18,12 +40,22 @@ module Puma
|
|
18
40
|
end
|
19
41
|
|
20
42
|
def load(path)
|
21
|
-
|
43
|
+
File.read(path).lines.each do |line|
|
44
|
+
next if line.start_with? '#'
|
45
|
+
k,v = line.split ':', 2
|
46
|
+
next unless v && ALLOWED_FIELDS.include?(k)
|
47
|
+
v = v.strip
|
48
|
+
@options[k] =
|
49
|
+
case v
|
50
|
+
when '' then nil
|
51
|
+
when /\A\d+\z/ then v.to_i
|
52
|
+
when /\A\d+\.\d+\z/ then v.to_f
|
53
|
+
else v.gsub(/\A"|"\z/, '')
|
54
|
+
end
|
55
|
+
end
|
22
56
|
end
|
23
57
|
|
24
|
-
|
25
|
-
|
26
|
-
FIELDS.each do |f|
|
58
|
+
ALLOWED_FIELDS.each do |f|
|
27
59
|
define_method f do
|
28
60
|
@options[f]
|
29
61
|
end
|