puma 5.6.5 → 6.4.2
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 +338 -14
- data/LICENSE +0 -0
- data/README.md +79 -29
- data/bin/puma-wild +1 -1
- data/docs/architecture.md +0 -0
- data/docs/compile_options.md +34 -0
- data/docs/deployment.md +0 -0
- data/docs/fork_worker.md +1 -3
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +0 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +1 -0
- data/docs/signals.md +0 -0
- data/docs/stats.md +0 -0
- data/docs/systemd.md +3 -6
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +16 -9
- 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 +127 -19
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- 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 +157 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +4 -4
- data/lib/puma/binder.rb +50 -53
- data/lib/puma/cli.rb +16 -18
- data/lib/puma/client.rb +100 -26
- data/lib/puma/cluster/worker.rb +18 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +102 -40
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +77 -59
- data/lib/puma/const.rb +129 -92
- data/lib/puma/control_cli.rb +15 -11
- data/lib/puma/detect.rb +7 -4
- data/lib/puma/dsl.rb +250 -56
- data/lib/puma/error_logger.rb +18 -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/json_serialization.rb +0 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +102 -175
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +26 -12
- data/lib/puma/minissl.rb +104 -11
- data/lib/puma/null_io.rb +16 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +0 -0
- data/lib/puma/rack/builder.rb +6 -6
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +19 -10
- data/lib/puma/request.rb +365 -170
- data/lib/puma/runner.rb +56 -20
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +137 -89
- data/lib/puma/single.rb +13 -11
- data/lib/puma/state_file.rb +3 -6
- data/lib/puma/thread_pool.rb +57 -19
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +12 -11
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +2 -2
- data/tools/trickletest.rb +0 -0
- metadata +11 -6
- data/lib/puma/queue_close.rb +0 -26
- data/lib/puma/systemd.rb +0 -46
data/lib/puma/server.rb
CHANGED
@@ -2,20 +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'
|
18
|
-
require 'forwardable'
|
17
|
+
require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
|
19
18
|
|
20
19
|
module Puma
|
21
20
|
|
@@ -30,39 +29,30 @@ module Puma
|
|
30
29
|
#
|
31
30
|
# Each `Puma::Server` will have one reactor and one thread pool.
|
32
31
|
class Server
|
33
|
-
|
34
32
|
include Puma::Const
|
35
33
|
include Request
|
36
|
-
extend Forwardable
|
37
34
|
|
38
35
|
attr_reader :thread
|
36
|
+
attr_reader :log_writer
|
39
37
|
attr_reader :events
|
40
38
|
attr_reader :min_threads, :max_threads # for #stats
|
41
39
|
attr_reader :requests_count # @version 5.0.0
|
42
|
-
attr_reader :log_writer # to help with backports
|
43
40
|
|
44
41
|
# @todo the following may be deprecated in the future
|
45
42
|
attr_reader :auto_trim_time, :early_hints, :first_data_timeout,
|
46
43
|
:leak_stack_on_error,
|
47
44
|
:persistent_timeout, :reaping_time
|
48
45
|
|
49
|
-
# @deprecated v6.0.0
|
50
|
-
attr_writer :auto_trim_time, :early_hints, :first_data_timeout,
|
51
|
-
:leak_stack_on_error, :min_threads, :max_threads,
|
52
|
-
:persistent_timeout, :reaping_time
|
53
|
-
|
54
46
|
attr_accessor :app
|
55
47
|
attr_accessor :binder
|
56
48
|
|
57
|
-
|
58
|
-
:add_unix_listener, :connected_ports
|
59
|
-
|
60
|
-
ThreadLocalKey = :puma_server
|
49
|
+
THREAD_LOCAL_KEY = :puma_server
|
61
50
|
|
62
51
|
# Create a server for the rack app +app+.
|
63
52
|
#
|
64
|
-
# +
|
65
|
-
#
|
53
|
+
# +log_writer+ is a Puma::LogWriter object used to log info and error messages.
|
54
|
+
#
|
55
|
+
# +events+ is a Puma::Events object used to notify application status events.
|
66
56
|
#
|
67
57
|
# Server#run returns a thread that you can join on to wait for the server
|
68
58
|
# to do its work.
|
@@ -71,35 +61,56 @@ module Puma
|
|
71
61
|
# and have default values set via +fetch+. Normally the values are set via
|
72
62
|
# `::Puma::Configuration.puma_default_options`.
|
73
63
|
#
|
74
|
-
|
64
|
+
# @note The `events` parameter is set to nil, and set to `Events.new` in code.
|
65
|
+
# Often `options` needs to be passed, but `events` does not. Using nil allows
|
66
|
+
# calling code to not require events.rb.
|
67
|
+
#
|
68
|
+
def initialize(app, events = nil, options = {})
|
75
69
|
@app = app
|
76
|
-
@events = events
|
77
|
-
@log_writer = events
|
70
|
+
@events = events || Events.new
|
78
71
|
|
79
72
|
@check, @notify = nil
|
80
73
|
@status = :stop
|
81
74
|
|
82
|
-
@auto_trim_time = 30
|
83
|
-
@reaping_time = 1
|
84
|
-
|
85
75
|
@thread = nil
|
86
76
|
@thread_pool = nil
|
87
77
|
|
88
|
-
@options = options
|
78
|
+
@options = if options.is_a?(UserFileDefaultOptions)
|
79
|
+
options
|
80
|
+
else
|
81
|
+
UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
|
82
|
+
end
|
89
83
|
|
90
|
-
@
|
91
|
-
@
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@
|
95
|
-
@
|
96
|
-
@
|
97
|
-
@
|
84
|
+
@clustered = (@options.fetch :workers, 0) > 0
|
85
|
+
@worker_write = @options[:worker_write]
|
86
|
+
@log_writer = @options.fetch :log_writer, LogWriter.stdio
|
87
|
+
@early_hints = @options[:early_hints]
|
88
|
+
@first_data_timeout = @options[:first_data_timeout]
|
89
|
+
@persistent_timeout = @options[:persistent_timeout]
|
90
|
+
@idle_timeout = @options[:idle_timeout]
|
91
|
+
@min_threads = @options[:min_threads]
|
92
|
+
@max_threads = @options[:max_threads]
|
93
|
+
@queue_requests = @options[:queue_requests]
|
94
|
+
@max_fast_inline = @options[:max_fast_inline]
|
95
|
+
@io_selector_backend = @options[:io_selector_backend]
|
96
|
+
@http_content_length_limit = @options[:http_content_length_limit]
|
97
|
+
|
98
|
+
# make this a hash, since we prefer `key?` over `include?`
|
99
|
+
@supported_http_methods =
|
100
|
+
if @options[:supported_http_methods] == :any
|
101
|
+
:any
|
102
|
+
else
|
103
|
+
if (ary = @options[:supported_http_methods])
|
104
|
+
ary
|
105
|
+
else
|
106
|
+
SUPPORTED_HTTP_METHODS
|
107
|
+
end.sort.product([nil]).to_h.freeze
|
108
|
+
end
|
98
109
|
|
99
110
|
temp = !!(@options[:environment] =~ /\A(development|test)\z/)
|
100
111
|
@leak_stack_on_error = @options[:environment] ? temp : true
|
101
112
|
|
102
|
-
@binder = Binder.new(
|
113
|
+
@binder = Binder.new(log_writer)
|
103
114
|
|
104
115
|
ENV['RACK_ENV'] ||= "development"
|
105
116
|
|
@@ -108,6 +119,8 @@ module Puma
|
|
108
119
|
@precheck_closing = true
|
109
120
|
|
110
121
|
@requests_count = 0
|
122
|
+
|
123
|
+
@idle_timeout_reached = false
|
111
124
|
end
|
112
125
|
|
113
126
|
def inherit_binder(bind)
|
@@ -117,7 +130,7 @@ module Puma
|
|
117
130
|
class << self
|
118
131
|
# @!attribute [r] current
|
119
132
|
def current
|
120
|
-
Thread.current[
|
133
|
+
Thread.current[THREAD_LOCAL_KEY]
|
121
134
|
end
|
122
135
|
|
123
136
|
# :nodoc:
|
@@ -195,12 +208,12 @@ module Puma
|
|
195
208
|
|
196
209
|
# @!attribute [r] backlog
|
197
210
|
def backlog
|
198
|
-
@thread_pool
|
211
|
+
@thread_pool&.backlog
|
199
212
|
end
|
200
213
|
|
201
214
|
# @!attribute [r] running
|
202
215
|
def running
|
203
|
-
@thread_pool
|
216
|
+
@thread_pool&.spawned
|
204
217
|
end
|
205
218
|
|
206
219
|
|
@@ -213,7 +226,7 @@ module Puma
|
|
213
226
|
# value would be 4 until it finishes processing.
|
214
227
|
# @!attribute [r] pool_capacity
|
215
228
|
def pool_capacity
|
216
|
-
@thread_pool
|
229
|
+
@thread_pool&.pool_capacity
|
217
230
|
end
|
218
231
|
|
219
232
|
# Runs the server.
|
@@ -229,29 +242,16 @@ module Puma
|
|
229
242
|
|
230
243
|
@status = :run
|
231
244
|
|
232
|
-
@thread_pool = ThreadPool.new(
|
233
|
-
thread_name,
|
234
|
-
@min_threads,
|
235
|
-
@max_threads,
|
236
|
-
::Puma::IOBuffer,
|
237
|
-
&method(:process_client)
|
238
|
-
)
|
239
|
-
|
240
|
-
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
241
|
-
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
245
|
+
@thread_pool = ThreadPool.new(thread_name, @options) { |client| process_client client }
|
242
246
|
|
243
247
|
if @queue_requests
|
244
|
-
@reactor = Reactor.new(@io_selector_backend
|
248
|
+
@reactor = Reactor.new(@io_selector_backend) { |c| reactor_wakeup c }
|
245
249
|
@reactor.run
|
246
250
|
end
|
247
251
|
|
248
|
-
if @reaping_time
|
249
|
-
@thread_pool.auto_reap!(@reaping_time)
|
250
|
-
end
|
251
252
|
|
252
|
-
if @
|
253
|
-
|
254
|
-
end
|
253
|
+
@thread_pool.auto_reap! if @options[:reaping_time]
|
254
|
+
@thread_pool.auto_trim! if @options[:auto_trim_time]
|
255
255
|
|
256
256
|
@check, @notify = Puma::Util.pipe unless @notify
|
257
257
|
|
@@ -330,8 +330,28 @@ module Puma
|
|
330
330
|
|
331
331
|
while @status == :run || (drain && shutting_down?)
|
332
332
|
begin
|
333
|
-
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 :
|
334
|
-
|
333
|
+
ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
|
334
|
+
unless ios
|
335
|
+
unless shutting_down?
|
336
|
+
@idle_timeout_reached = true
|
337
|
+
|
338
|
+
if @clustered
|
339
|
+
@worker_write << "i#{Process.pid}\n" rescue nil
|
340
|
+
next
|
341
|
+
else
|
342
|
+
@log_writer.log "- Idle timeout reached"
|
343
|
+
@status = :stop
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
break
|
348
|
+
end
|
349
|
+
|
350
|
+
if @idle_timeout_reached && @clustered
|
351
|
+
@idle_timeout_reached = false
|
352
|
+
@worker_write << "i#{Process.pid}\n" rescue nil
|
353
|
+
end
|
354
|
+
|
335
355
|
ios.first.each do |sock|
|
336
356
|
if sock == check
|
337
357
|
break if handle_check
|
@@ -347,6 +367,7 @@ module Puma
|
|
347
367
|
drain += 1 if shutting_down?
|
348
368
|
pool << Client.new(io, @binder.env(sock)).tap { |c|
|
349
369
|
c.listener = sock
|
370
|
+
c.http_content_length_limit = @http_content_length_limit
|
350
371
|
c.send(addr_send_name, addr_value) if addr_value
|
351
372
|
}
|
352
373
|
end
|
@@ -355,27 +376,27 @@ module Puma
|
|
355
376
|
# In the case that any of the sockets are unexpectedly close.
|
356
377
|
raise
|
357
378
|
rescue StandardError => e
|
358
|
-
@
|
379
|
+
@log_writer.unknown_error e, nil, "Listen loop"
|
359
380
|
end
|
360
381
|
end
|
361
382
|
|
362
|
-
@
|
383
|
+
@log_writer.debug "Drained #{drain} additional connections." if drain
|
363
384
|
@events.fire :state, @status
|
364
385
|
|
365
386
|
if queue_requests
|
366
387
|
@queue_requests = false
|
367
388
|
@reactor.shutdown
|
368
389
|
end
|
390
|
+
|
369
391
|
graceful_shutdown if @status == :stop || @status == :restart
|
370
392
|
rescue Exception => e
|
371
|
-
@
|
393
|
+
@log_writer.unknown_error e, nil, "Exception handling servers"
|
372
394
|
ensure
|
373
|
-
# RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
|
374
395
|
# Errno::EBADF is infrequently raised
|
375
396
|
[@check, @notify].each do |io|
|
376
397
|
begin
|
377
398
|
io.close unless io.closed?
|
378
|
-
rescue Errno::EBADF
|
399
|
+
rescue Errno::EBADF
|
379
400
|
end
|
380
401
|
end
|
381
402
|
@notify = nil
|
@@ -414,9 +435,9 @@ module Puma
|
|
414
435
|
# returning.
|
415
436
|
#
|
416
437
|
# Return true if one or more requests were processed.
|
417
|
-
def process_client(client
|
438
|
+
def process_client(client)
|
418
439
|
# Advertise this server into the thread
|
419
|
-
Thread.current[
|
440
|
+
Thread.current[THREAD_LOCAL_KEY] = self
|
420
441
|
|
421
442
|
clean_thread_locals = @options[:clean_thread_locals]
|
422
443
|
close_socket = true
|
@@ -440,15 +461,13 @@ module Puma
|
|
440
461
|
|
441
462
|
while true
|
442
463
|
@requests_count += 1
|
443
|
-
case handle_request(client,
|
464
|
+
case handle_request(client, requests + 1)
|
444
465
|
when false
|
445
466
|
break
|
446
467
|
when :async
|
447
468
|
close_socket = false
|
448
469
|
break
|
449
470
|
when true
|
450
|
-
buffer.reset
|
451
|
-
|
452
471
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
453
472
|
|
454
473
|
requests += 1
|
@@ -478,11 +497,11 @@ module Puma
|
|
478
497
|
end
|
479
498
|
true
|
480
499
|
rescue StandardError => e
|
481
|
-
client_error(e, client)
|
500
|
+
client_error(e, client, requests)
|
482
501
|
# The ensure tries to close +client+ down
|
483
502
|
requests > 0
|
484
503
|
ensure
|
485
|
-
|
504
|
+
client.io_buffer.reset
|
486
505
|
|
487
506
|
begin
|
488
507
|
client.close if close_socket
|
@@ -490,7 +509,7 @@ module Puma
|
|
490
509
|
Puma::Util.purge_interrupt_queue
|
491
510
|
# Already closed
|
492
511
|
rescue StandardError => e
|
493
|
-
@
|
512
|
+
@log_writer.unknown_error e, nil, "Client"
|
494
513
|
end
|
495
514
|
end
|
496
515
|
end
|
@@ -506,23 +525,23 @@ module Puma
|
|
506
525
|
# :nocov:
|
507
526
|
|
508
527
|
# Handle various error types thrown by Client I/O operations.
|
509
|
-
def client_error(e, client)
|
528
|
+
def client_error(e, client, requests = 1)
|
510
529
|
# Swallow, do not log
|
511
530
|
return if [ConnectionError, EOFError].include?(e.class)
|
512
531
|
|
513
|
-
lowlevel_error(e, client.env)
|
514
532
|
case e
|
515
533
|
when MiniSSL::SSLError
|
516
|
-
|
534
|
+
lowlevel_error(e, client.env)
|
535
|
+
@log_writer.ssl_error e, client.io
|
517
536
|
when HttpParserError
|
518
|
-
client
|
519
|
-
@
|
537
|
+
response_to_error(client, requests, e, 400)
|
538
|
+
@log_writer.parse_error e, client
|
520
539
|
when HttpParserError501
|
521
|
-
client
|
522
|
-
@
|
540
|
+
response_to_error(client, requests, e, 501)
|
541
|
+
@log_writer.parse_error e, client
|
523
542
|
else
|
524
|
-
client
|
525
|
-
@
|
543
|
+
response_to_error(client, requests, e, 500)
|
544
|
+
@log_writer.unknown_error e, nil, "Read"
|
526
545
|
end
|
527
546
|
end
|
528
547
|
|
@@ -543,10 +562,17 @@ module Puma
|
|
543
562
|
backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
|
544
563
|
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
|
545
564
|
else
|
546
|
-
[status, {}, ["
|
565
|
+
[status, {}, [""]]
|
547
566
|
end
|
548
567
|
end
|
549
568
|
|
569
|
+
def response_to_error(client, requests, err, status_code)
|
570
|
+
status, headers, res_body = lowlevel_error(err, client.env, status_code)
|
571
|
+
prepare_response(status, headers, res_body, requests, client)
|
572
|
+
client.write_error(status_code)
|
573
|
+
end
|
574
|
+
private :response_to_error
|
575
|
+
|
550
576
|
# Wait for all outstanding requests to finish.
|
551
577
|
#
|
552
578
|
def graceful_shutdown
|
@@ -580,7 +606,7 @@ module Puma
|
|
580
606
|
|
581
607
|
def notify_safely(message)
|
582
608
|
@notify << message
|
583
|
-
rescue IOError, NoMethodError, Errno::EPIPE
|
609
|
+
rescue IOError, NoMethodError, Errno::EPIPE, Errno::EBADF
|
584
610
|
# The server, in another thread, is shutting down
|
585
611
|
Puma::Util.purge_interrupt_queue
|
586
612
|
rescue RuntimeError => e
|
@@ -625,5 +651,27 @@ module Puma
|
|
625
651
|
def stats
|
626
652
|
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
627
653
|
end
|
654
|
+
|
655
|
+
# below are 'delegations' to binder
|
656
|
+
# remove in Puma 7?
|
657
|
+
|
658
|
+
|
659
|
+
def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
|
660
|
+
@binder.add_tcp_listener host, port, optimize_for_latency, backlog
|
661
|
+
end
|
662
|
+
|
663
|
+
def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
|
664
|
+
backlog = 1024)
|
665
|
+
@binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
|
666
|
+
end
|
667
|
+
|
668
|
+
def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
|
669
|
+
@binder.add_unix_listener path, umask, mode, backlog
|
670
|
+
end
|
671
|
+
|
672
|
+
# @!attribute [r] connected_ports
|
673
|
+
def connected_ports
|
674
|
+
@binder.connected_ports
|
675
|
+
end
|
628
676
|
end
|
629
677
|
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
@@ -15,15 +15,12 @@ module Puma
|
|
15
15
|
|
16
16
|
ALLOWED_FIELDS = %w!control_url control_auth_token pid running_from!
|
17
17
|
|
18
|
-
# @deprecated 6.0.0
|
19
|
-
FIELDS = ALLOWED_FIELDS
|
20
|
-
|
21
18
|
def initialize
|
22
19
|
@options = {}
|
23
20
|
end
|
24
21
|
|
25
22
|
def save(path, permission = nil)
|
26
|
-
contents = "---\n"
|
23
|
+
contents = +"---\n"
|
27
24
|
@options.each do |k,v|
|
28
25
|
next unless ALLOWED_FIELDS.include? k
|
29
26
|
case v
|
@@ -59,11 +56,11 @@ module Puma
|
|
59
56
|
end
|
60
57
|
|
61
58
|
ALLOWED_FIELDS.each do |f|
|
62
|
-
define_method f do
|
59
|
+
define_method f.to_sym do
|
63
60
|
@options[f]
|
64
61
|
end
|
65
62
|
|
66
|
-
define_method "#{f}=" do |v|
|
63
|
+
define_method :"#{f}=" do |v|
|
67
64
|
@options[f] = v
|
68
65
|
end
|
69
66
|
end
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'thread'
|
4
4
|
|
5
|
+
require_relative 'io_buffer'
|
6
|
+
|
5
7
|
module Puma
|
6
8
|
# Internal Docs for A simple thread pool management object.
|
7
9
|
#
|
@@ -29,7 +31,7 @@ module Puma
|
|
29
31
|
# The block passed is the work that will be performed in each
|
30
32
|
# thread.
|
31
33
|
#
|
32
|
-
def initialize(name,
|
34
|
+
def initialize(name, options = {}, &block)
|
33
35
|
@not_empty = ConditionVariable.new
|
34
36
|
@not_full = ConditionVariable.new
|
35
37
|
@mutex = Mutex.new
|
@@ -40,10 +42,19 @@ module Puma
|
|
40
42
|
@waiting = 0
|
41
43
|
|
42
44
|
@name = name
|
43
|
-
@min = Integer(
|
44
|
-
@max = Integer(
|
45
|
+
@min = Integer(options[:min_threads])
|
46
|
+
@max = Integer(options[:max_threads])
|
47
|
+
# Not an 'exposed' option, options[:pool_shutdown_grace_time] is used in CI
|
48
|
+
# to shorten @shutdown_grace_time from SHUTDOWN_GRACE_TIME. Parallel CI
|
49
|
+
# makes stubbing constants difficult.
|
50
|
+
@shutdown_grace_time = Float(options[:pool_shutdown_grace_time] || SHUTDOWN_GRACE_TIME)
|
45
51
|
@block = block
|
46
|
-
@
|
52
|
+
@out_of_band = options[:out_of_band]
|
53
|
+
@clean_thread_locals = options[:clean_thread_locals]
|
54
|
+
@before_thread_start = options[:before_thread_start]
|
55
|
+
@before_thread_exit = options[:before_thread_exit]
|
56
|
+
@reaping_time = options[:reaping_time]
|
57
|
+
@auto_trim_time = options[:auto_trim_time]
|
47
58
|
|
48
59
|
@shutdown = false
|
49
60
|
|
@@ -62,14 +73,11 @@ module Puma
|
|
62
73
|
end
|
63
74
|
end
|
64
75
|
|
65
|
-
@clean_thread_locals = false
|
66
76
|
@force_shutdown = false
|
67
77
|
@shutdown_mutex = Mutex.new
|
68
78
|
end
|
69
79
|
|
70
80
|
attr_reader :spawned, :trim_requested, :waiting
|
71
|
-
attr_accessor :clean_thread_locals
|
72
|
-
attr_accessor :out_of_band_hook # @version 5.0.0
|
73
81
|
|
74
82
|
def self.clean_thread_locals
|
75
83
|
Thread.current.keys.each do |key| # rubocop: disable Style/HashEachMethods
|
@@ -101,6 +109,7 @@ module Puma
|
|
101
109
|
def spawn_thread
|
102
110
|
@spawned += 1
|
103
111
|
|
112
|
+
trigger_before_thread_start_hooks
|
104
113
|
th = Thread.new(@spawned) do |spawned|
|
105
114
|
Puma.set_thread_name '%s tp %03i' % [@name, spawned]
|
106
115
|
todo = @todo
|
@@ -109,8 +118,6 @@ module Puma
|
|
109
118
|
not_empty = @not_empty
|
110
119
|
not_full = @not_full
|
111
120
|
|
112
|
-
extra = @extra.map { |i| i.new }
|
113
|
-
|
114
121
|
while true
|
115
122
|
work = nil
|
116
123
|
|
@@ -121,6 +128,7 @@ module Puma
|
|
121
128
|
@spawned -= 1
|
122
129
|
@workers.delete th
|
123
130
|
not_full.signal
|
131
|
+
trigger_before_thread_exit_hooks
|
124
132
|
Thread.exit
|
125
133
|
end
|
126
134
|
|
@@ -144,7 +152,7 @@ module Puma
|
|
144
152
|
end
|
145
153
|
|
146
154
|
begin
|
147
|
-
@out_of_band_pending = true if block.call(work
|
155
|
+
@out_of_band_pending = true if block.call(work)
|
148
156
|
rescue Exception => e
|
149
157
|
STDERR.puts "Error reached top of thread-pool: #{e.message} (#{e.class})"
|
150
158
|
end
|
@@ -158,14 +166,44 @@ module Puma
|
|
158
166
|
|
159
167
|
private :spawn_thread
|
160
168
|
|
169
|
+
def trigger_before_thread_start_hooks
|
170
|
+
return unless @before_thread_start&.any?
|
171
|
+
|
172
|
+
@before_thread_start.each do |b|
|
173
|
+
begin
|
174
|
+
b.call
|
175
|
+
rescue Exception => e
|
176
|
+
STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
nil
|
180
|
+
end
|
181
|
+
|
182
|
+
private :trigger_before_thread_start_hooks
|
183
|
+
|
184
|
+
def trigger_before_thread_exit_hooks
|
185
|
+
return unless @before_thread_exit&.any?
|
186
|
+
|
187
|
+
@before_thread_exit.each do |b|
|
188
|
+
begin
|
189
|
+
b.call
|
190
|
+
rescue Exception => e
|
191
|
+
STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
nil
|
195
|
+
end
|
196
|
+
|
197
|
+
private :trigger_before_thread_exit_hooks
|
198
|
+
|
161
199
|
# @version 5.0.0
|
162
200
|
def trigger_out_of_band_hook
|
163
|
-
return false unless
|
201
|
+
return false unless @out_of_band&.any?
|
164
202
|
|
165
203
|
# we execute on idle hook when all threads are free
|
166
204
|
return false unless @spawned == @waiting
|
167
205
|
|
168
|
-
|
206
|
+
@out_of_band.each(&:call)
|
169
207
|
true
|
170
208
|
rescue Exception => e
|
171
209
|
STDERR.puts "Exception calling out_of_band_hook: #{e.message} (#{e.class})"
|
@@ -319,12 +357,12 @@ module Puma
|
|
319
357
|
end
|
320
358
|
end
|
321
359
|
|
322
|
-
def auto_trim!(timeout
|
360
|
+
def auto_trim!(timeout=@auto_trim_time)
|
323
361
|
@auto_trim = Automaton.new(self, timeout, "#{@name} threadpool trimmer", :trim)
|
324
362
|
@auto_trim.start!
|
325
363
|
end
|
326
364
|
|
327
|
-
def auto_reap!(timeout
|
365
|
+
def auto_reap!(timeout=@reaping_time)
|
328
366
|
@reaper = Automaton.new(self, timeout, "#{@name} threadpool reaper", :reap)
|
329
367
|
@reaper.start!
|
330
368
|
end
|
@@ -344,8 +382,8 @@ module Puma
|
|
344
382
|
|
345
383
|
# Tell all threads in the pool to exit and wait for them to finish.
|
346
384
|
# Wait +timeout+ seconds then raise +ForceShutdown+ in remaining threads.
|
347
|
-
# Next, wait an extra +
|
348
|
-
# Finally, wait
|
385
|
+
# Next, wait an extra +@shutdown_grace_time+ seconds then force-kill remaining
|
386
|
+
# threads. Finally, wait 1 second for remaining threads to exit.
|
349
387
|
#
|
350
388
|
def shutdown(timeout=-1)
|
351
389
|
threads = with_mutex do
|
@@ -354,8 +392,8 @@ module Puma
|
|
354
392
|
@not_empty.broadcast
|
355
393
|
@not_full.broadcast
|
356
394
|
|
357
|
-
@auto_trim
|
358
|
-
@reaper
|
395
|
+
@auto_trim&.stop
|
396
|
+
@reaper&.stop
|
359
397
|
# dup workers so that we join them all safely
|
360
398
|
@workers.dup
|
361
399
|
end
|
@@ -382,7 +420,7 @@ module Puma
|
|
382
420
|
t.raise ForceShutdown if t[:with_force_shutdown]
|
383
421
|
end
|
384
422
|
end
|
385
|
-
join.call(
|
423
|
+
join.call(@shutdown_grace_time)
|
386
424
|
|
387
425
|
# If threads are _still_ running, forcefully kill them and wait to finish.
|
388
426
|
threads.each(&:kill)
|