puma 6.4.3-java → 6.6.0-java
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +139 -3
- data/README.md +81 -21
- data/docs/fork_worker.md +11 -1
- data/docs/java_options.md +54 -0
- data/docs/plugins.md +4 -0
- data/docs/signals.md +2 -2
- data/docs/stats.md +8 -3
- data/docs/systemd.md +10 -1
- data/ext/puma_http11/extconf.rb +19 -16
- data/ext/puma_http11/mini_ssl.c +11 -1
- data/ext/puma_http11/org/jruby/puma/Http11.java +28 -7
- data/ext/puma_http11/puma_http11.c +4 -1
- data/lib/puma/app/status.rb +1 -1
- data/lib/puma/binder.rb +12 -4
- data/lib/puma/cli.rb +9 -5
- data/lib/puma/client.rb +35 -12
- data/lib/puma/cluster/worker.rb +9 -6
- data/lib/puma/cluster/worker_handle.rb +4 -5
- data/lib/puma/cluster.rb +31 -19
- data/lib/puma/configuration.rb +36 -18
- data/lib/puma/const.rb +14 -3
- data/lib/puma/control_cli.rb +4 -4
- data/lib/puma/dsl.rb +282 -43
- data/lib/puma/error_logger.rb +4 -4
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/launcher.rb +21 -8
- data/lib/puma/log_writer.rb +9 -9
- data/lib/puma/minissl/context_builder.rb +1 -0
- data/lib/puma/minissl.rb +1 -0
- data/lib/puma/null_io.rb +26 -0
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/request.rb +7 -3
- data/lib/puma/runner.rb +9 -2
- data/lib/puma/sd_notify.rb +1 -4
- data/lib/puma/server.rb +33 -22
- data/lib/puma/thread_pool.rb +14 -2
- data/lib/puma/util.rb +1 -1
- data/lib/rack/handler/puma.rb +8 -5
- metadata +9 -8
@@ -26,14 +26,14 @@ public class Http11 extends RubyObject {
|
|
26
26
|
public final static String MAX_FIELD_NAME_LENGTH_ERR = "HTTP element FIELD_NAME is longer than the 256 allowed length.";
|
27
27
|
public final static int MAX_FIELD_VALUE_LENGTH = 80 * 1024;
|
28
28
|
public final static String MAX_FIELD_VALUE_LENGTH_ERR = "HTTP element FIELD_VALUE is longer than the 81920 allowed length.";
|
29
|
-
public final static int MAX_REQUEST_URI_LENGTH = 1024 * 12;
|
30
|
-
public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the
|
29
|
+
public final static int MAX_REQUEST_URI_LENGTH = getConstLength("PUMA_REQUEST_URI_MAX_LENGTH", 1024 * 12);
|
30
|
+
public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the " + MAX_REQUEST_URI_LENGTH + " allowed length.";
|
31
31
|
public final static int MAX_FRAGMENT_LENGTH = 1024;
|
32
32
|
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 1024 allowed length.";
|
33
|
-
public final static int MAX_REQUEST_PATH_LENGTH = 8192;
|
34
|
-
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the
|
35
|
-
public final static int MAX_QUERY_STRING_LENGTH =
|
36
|
-
public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the
|
33
|
+
public final static int MAX_REQUEST_PATH_LENGTH = getConstLength("PUMA_REQUEST_PATH_MAX_LENGTH", 8192);
|
34
|
+
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the " + MAX_REQUEST_PATH_LENGTH + " allowed length.";
|
35
|
+
public final static int MAX_QUERY_STRING_LENGTH = getConstLength("PUMA_QUERY_STRING_MAX_LENGTH", 10 * 1024);
|
36
|
+
public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the " + MAX_QUERY_STRING_LENGTH +" allowed length.";
|
37
37
|
public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
|
38
38
|
public final static String MAX_HEADER_LENGTH_ERR = "HTTP element HEADER is longer than the 114688 allowed length.";
|
39
39
|
|
@@ -48,6 +48,27 @@ public class Http11 extends RubyObject {
|
|
48
48
|
public static final ByteList QUERY_STRING_BYTELIST = new ByteList(ByteList.plain("QUERY_STRING"));
|
49
49
|
public static final ByteList SERVER_PROTOCOL_BYTELIST = new ByteList(ByteList.plain("SERVER_PROTOCOL"));
|
50
50
|
|
51
|
+
public static String getEnvOrProperty(String name) {
|
52
|
+
String envValue = System.getenv(name);
|
53
|
+
return (envValue != null) ? envValue : System.getProperty(name);
|
54
|
+
}
|
55
|
+
|
56
|
+
public static int getConstLength(String name, Integer defaultValue) {
|
57
|
+
String stringValue = getEnvOrProperty(name);
|
58
|
+
if (stringValue == null || stringValue.isEmpty()) return defaultValue;
|
59
|
+
|
60
|
+
try {
|
61
|
+
int value = Integer.parseUnsignedInt(stringValue);
|
62
|
+
if (value <= 0) {
|
63
|
+
throw new NumberFormatException("The number is not positive.");
|
64
|
+
}
|
65
|
+
return value;
|
66
|
+
} catch (NumberFormatException e) {
|
67
|
+
System.err.println(String.format("The value %s for %s is invalid. Using default value %d instead.", stringValue, name, defaultValue));
|
68
|
+
return defaultValue;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
51
72
|
private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
52
73
|
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
53
74
|
return new Http11(runtime, klass);
|
@@ -56,7 +77,7 @@ public class Http11 extends RubyObject {
|
|
56
77
|
|
57
78
|
public static void createHttp11(Ruby runtime) {
|
58
79
|
RubyModule mPuma = runtime.defineModule("Puma");
|
59
|
-
mPuma.defineClassUnder("HttpParserError",runtime.getClass("
|
80
|
+
mPuma.defineClassUnder("HttpParserError",runtime.getClass("StandardError"),runtime.getClass("StandardError").getAllocator());
|
60
81
|
|
61
82
|
RubyClass cHttpParser = mPuma.defineClassUnder("HttpParser",runtime.getObject(),ALLOCATOR);
|
62
83
|
cHttpParser.defineAnnotatedMethods(Http11.class);
|
@@ -461,6 +461,9 @@ void Init_mini_ssl(VALUE mod);
|
|
461
461
|
|
462
462
|
void Init_puma_http11(void)
|
463
463
|
{
|
464
|
+
#ifdef HAVE_RB_EXT_RACTOR_SAFE
|
465
|
+
rb_ext_ractor_safe(true);
|
466
|
+
#endif
|
464
467
|
|
465
468
|
VALUE mPuma = rb_define_module("Puma");
|
466
469
|
VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
|
@@ -472,7 +475,7 @@ void Init_puma_http11(void)
|
|
472
475
|
DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
|
473
476
|
DEF_GLOBAL(request_path, "REQUEST_PATH");
|
474
477
|
|
475
|
-
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError",
|
478
|
+
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
|
476
479
|
rb_global_variable(&eHttpParserError);
|
477
480
|
|
478
481
|
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
data/lib/puma/app/status.rb
CHANGED
@@ -80,7 +80,7 @@ module Puma
|
|
80
80
|
|
81
81
|
def authenticate(env)
|
82
82
|
return true unless @auth_token
|
83
|
-
env['QUERY_STRING'].to_s.split(
|
83
|
+
env['QUERY_STRING'].to_s.split(/[&;]/).include? "token=#{@auth_token}"
|
84
84
|
end
|
85
85
|
|
86
86
|
def rack_response(status, body, content_type='application/json')
|
data/lib/puma/binder.rb
CHANGED
@@ -19,13 +19,14 @@ module Puma
|
|
19
19
|
|
20
20
|
RACK_VERSION = [1,6].freeze
|
21
21
|
|
22
|
-
def initialize(log_writer, conf = Configuration.new)
|
22
|
+
def initialize(log_writer, conf = Configuration.new, env: ENV)
|
23
23
|
@log_writer = log_writer
|
24
24
|
@conf = conf
|
25
25
|
@listeners = []
|
26
26
|
@inherited_fds = {}
|
27
27
|
@activated_sockets = {}
|
28
28
|
@unix_paths = []
|
29
|
+
@env = env
|
29
30
|
|
30
31
|
@proto_env = {
|
31
32
|
"rack.version".freeze => RACK_VERSION,
|
@@ -34,7 +35,7 @@ module Puma
|
|
34
35
|
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
35
36
|
"rack.run_once".freeze => false,
|
36
37
|
RACK_URL_SCHEME => conf.options[:rack_url_scheme],
|
37
|
-
"SCRIPT_NAME".freeze =>
|
38
|
+
"SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "",
|
38
39
|
|
39
40
|
# I'd like to set a default CONTENT_TYPE here but some things
|
40
41
|
# depend on their not being a default set and inferring
|
@@ -87,7 +88,7 @@ module Puma
|
|
87
88
|
# @version 5.0.0
|
88
89
|
#
|
89
90
|
def create_activated_fds(env_hash)
|
90
|
-
@log_writer.debug "ENV['LISTEN_FDS'] #{
|
91
|
+
@log_writer.debug "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
|
91
92
|
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
92
93
|
env_hash['LISTEN_FDS'].to_i.times do |index|
|
93
94
|
sock = TCPServer.for_fd(socket_activation_fd(index))
|
@@ -141,7 +142,14 @@ module Puma
|
|
141
142
|
end
|
142
143
|
end
|
143
144
|
|
145
|
+
def before_parse(&block)
|
146
|
+
@before_parse ||= []
|
147
|
+
@before_parse << block if block
|
148
|
+
@before_parse
|
149
|
+
end
|
150
|
+
|
144
151
|
def parse(binds, log_writer = nil, log_msg = 'Listening')
|
152
|
+
before_parse.each(&:call)
|
145
153
|
log_writer ||= @log_writer
|
146
154
|
binds.each do |str|
|
147
155
|
uri = URI.parse str
|
@@ -183,7 +191,7 @@ module Puma
|
|
183
191
|
io = inherit_unix_listener path, fd
|
184
192
|
log_writer.log "* Inherited #{str}"
|
185
193
|
elsif sock = @activated_sockets.delete([ :unix, path ]) ||
|
186
|
-
@activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
194
|
+
!abstract && @activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
187
195
|
@unix_paths << path unless abstract || File.exist?(path)
|
188
196
|
io = inherit_unix_listener path, sock
|
189
197
|
log_writer.log "* Activated #{str}"
|
data/lib/puma/cli.rb
CHANGED
@@ -24,7 +24,7 @@ module Puma
|
|
24
24
|
# Create a new CLI object using +argv+ as the command line
|
25
25
|
# arguments.
|
26
26
|
#
|
27
|
-
def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
|
27
|
+
def initialize(argv, log_writer = LogWriter.stdio, events = Events.new, env: ENV)
|
28
28
|
@debug = false
|
29
29
|
@argv = argv.dup
|
30
30
|
@log_writer = log_writer
|
@@ -39,7 +39,7 @@ module Puma
|
|
39
39
|
@control_url = nil
|
40
40
|
@control_options = {}
|
41
41
|
|
42
|
-
setup_options
|
42
|
+
setup_options env
|
43
43
|
|
44
44
|
begin
|
45
45
|
@parser.parse! @argv
|
@@ -63,7 +63,7 @@ module Puma
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
@launcher = Puma::Launcher.new(@conf, :log_writer
|
66
|
+
@launcher = Puma::Launcher.new(@conf, env: ENV, log_writer: @log_writer, events: @events, argv: argv)
|
67
67
|
end
|
68
68
|
|
69
69
|
attr_reader :launcher
|
@@ -92,8 +92,8 @@ module Puma
|
|
92
92
|
# Build the OptionParser object to handle the available options.
|
93
93
|
#
|
94
94
|
|
95
|
-
def setup_options
|
96
|
-
@conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
|
95
|
+
def setup_options(env = ENV)
|
96
|
+
@conf = Configuration.new({}, {events: @events}, env) do |user_config, file_config|
|
97
97
|
@parser = OptionParser.new do |o|
|
98
98
|
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
99
99
|
user_config.bind arg
|
@@ -157,6 +157,10 @@ module Puma
|
|
157
157
|
user_config.pidfile arg
|
158
158
|
end
|
159
159
|
|
160
|
+
o.on "--plugin PLUGIN", "Load the given PLUGIN. Can be used multiple times to load multiple plugins." do |arg|
|
161
|
+
user_config.plugin arg
|
162
|
+
end
|
163
|
+
|
160
164
|
o.on "--preload", "Preload the app. Cluster mode only" do
|
161
165
|
user_config.preload_app!
|
162
166
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -160,8 +160,6 @@ module Puma
|
|
160
160
|
@read_header = true
|
161
161
|
@read_proxy = !!@expect_proxy_proto
|
162
162
|
@env = @proto_env.dup
|
163
|
-
@body = nil
|
164
|
-
@tempfile = nil
|
165
163
|
@parsed_bytes = 0
|
166
164
|
@ready = false
|
167
165
|
@body_remain = 0
|
@@ -190,11 +188,11 @@ module Puma
|
|
190
188
|
rescue IOError
|
191
189
|
# swallow it
|
192
190
|
end
|
193
|
-
|
194
191
|
end
|
195
192
|
end
|
196
193
|
|
197
194
|
def close
|
195
|
+
tempfile_close
|
198
196
|
begin
|
199
197
|
@io.close
|
200
198
|
rescue IOError, Errno::EBADF
|
@@ -202,6 +200,15 @@ module Puma
|
|
202
200
|
end
|
203
201
|
end
|
204
202
|
|
203
|
+
def tempfile_close
|
204
|
+
tf_path = @tempfile&.path
|
205
|
+
@tempfile&.close
|
206
|
+
File.unlink(tf_path) if tf_path
|
207
|
+
@tempfile = nil
|
208
|
+
@body = nil
|
209
|
+
rescue Errno::ENOENT, IOError
|
210
|
+
end
|
211
|
+
|
205
212
|
# If necessary, read the PROXY protocol from the buffer. Returns
|
206
213
|
# false if more data is needed.
|
207
214
|
def try_to_parse_proxy_protocol
|
@@ -240,6 +247,7 @@ module Puma
|
|
240
247
|
|
241
248
|
return read_body if in_data_phase
|
242
249
|
|
250
|
+
data = nil
|
243
251
|
begin
|
244
252
|
data = @io.read_nonblock(CHUNK_SIZE)
|
245
253
|
rescue IO::WaitReadable
|
@@ -403,18 +411,33 @@ module Puma
|
|
403
411
|
return true
|
404
412
|
end
|
405
413
|
|
406
|
-
|
414
|
+
content_length = cl.to_i
|
415
|
+
|
416
|
+
remain = content_length - body.bytesize
|
407
417
|
|
408
418
|
if remain <= 0
|
409
|
-
|
410
|
-
|
419
|
+
# Part of the body is a pipelined request OR garbage. We'll deal with that later.
|
420
|
+
if content_length == 0
|
421
|
+
@body = EmptyBody
|
422
|
+
if body.empty?
|
423
|
+
@buffer = nil
|
424
|
+
else
|
425
|
+
@buffer = body
|
426
|
+
end
|
427
|
+
elsif remain == 0
|
428
|
+
@body = StringIO.new body
|
429
|
+
@buffer = nil
|
430
|
+
else
|
431
|
+
@body = StringIO.new(body[0,content_length])
|
432
|
+
@buffer = body[content_length..-1]
|
433
|
+
end
|
411
434
|
set_ready
|
412
435
|
return true
|
413
436
|
end
|
414
437
|
|
415
438
|
if remain > MAX_BODY
|
416
|
-
@body = Tempfile.
|
417
|
-
@body.
|
439
|
+
@body = Tempfile.create(Const::PUMA_TMP_BASE)
|
440
|
+
File.unlink @body.path unless IS_WINDOWS
|
418
441
|
@body.binmode
|
419
442
|
@tempfile = @body
|
420
443
|
else
|
@@ -478,7 +501,7 @@ module Puma
|
|
478
501
|
def read_chunked_body
|
479
502
|
while true
|
480
503
|
begin
|
481
|
-
chunk = @io.read_nonblock(
|
504
|
+
chunk = @io.read_nonblock(CHUNK_SIZE, @read_buffer)
|
482
505
|
rescue IO::WaitReadable
|
483
506
|
return false
|
484
507
|
rescue SystemCallError, IOError
|
@@ -506,8 +529,8 @@ module Puma
|
|
506
529
|
@prev_chunk = ""
|
507
530
|
@excess_cr = 0
|
508
531
|
|
509
|
-
@body = Tempfile.
|
510
|
-
@body.
|
532
|
+
@body = Tempfile.create(Const::PUMA_TMP_BASE)
|
533
|
+
File.unlink @body.path unless IS_WINDOWS
|
511
534
|
@body.binmode
|
512
535
|
@tempfile = @body
|
513
536
|
@chunked_content_length = 0
|
@@ -627,7 +650,7 @@ module Puma
|
|
627
650
|
@partial_part_left = len - part.size
|
628
651
|
end
|
629
652
|
else
|
630
|
-
if @prev_chunk.size +
|
653
|
+
if @prev_chunk.size + line.size >= MAX_CHUNK_HEADER_SIZE
|
631
654
|
raise HttpParserError, "maximum size of chunk header exceeded"
|
632
655
|
end
|
633
656
|
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -88,25 +88,27 @@ module Puma
|
|
88
88
|
server.begin_restart(true)
|
89
89
|
@config.run_hooks(:before_refork, nil, @log_writer, @hook_data)
|
90
90
|
end
|
91
|
+
elsif idx == -2 # refork cycle is done
|
92
|
+
@config.run_hooks(:after_refork, nil, @log_writer, @hook_data)
|
91
93
|
elsif idx == 0 # restart server
|
92
94
|
restart_server << true << false
|
93
95
|
else # fork worker
|
94
96
|
worker_pids << pid = spawn_worker(idx)
|
95
|
-
@worker_write << "
|
97
|
+
@worker_write << "#{PIPE_FORK}#{pid}:#{idx}\n" rescue nil
|
96
98
|
end
|
97
99
|
end
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
101
103
|
Signal.trap "SIGTERM" do
|
102
|
-
@worker_write << "
|
104
|
+
@worker_write << "#{PIPE_EXTERNAL_TERM}#{Process.pid}\n" rescue nil
|
103
105
|
restart_server.clear
|
104
106
|
server.stop
|
105
107
|
restart_server << false
|
106
108
|
end
|
107
109
|
|
108
110
|
begin
|
109
|
-
@worker_write << "
|
111
|
+
@worker_write << "#{PIPE_BOOT}#{Process.pid}:#{index}\n"
|
110
112
|
rescue SystemCallError, IOError
|
111
113
|
Puma::Util.purge_interrupt_queue
|
112
114
|
STDERR.puts "Master seems to have exited, exiting."
|
@@ -122,7 +124,7 @@ module Puma
|
|
122
124
|
|
123
125
|
stat_thread ||= Thread.new(@worker_write) do |io|
|
124
126
|
Puma.set_thread_name "stat pld"
|
125
|
-
base_payload = "
|
127
|
+
base_payload = "#{PIPE_PING}#{Process.pid}"
|
126
128
|
|
127
129
|
while true
|
128
130
|
begin
|
@@ -131,7 +133,8 @@ module Puma
|
|
131
133
|
t = server.pool_capacity || 0
|
132
134
|
m = server.max_threads || 0
|
133
135
|
rc = server.requests_count || 0
|
134
|
-
|
136
|
+
bt = server.busy_threads || 0
|
137
|
+
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads":#{m}, "requests_count":#{rc}, "busy_threads":#{bt} }\n!
|
135
138
|
io << payload
|
136
139
|
rescue IOError
|
137
140
|
Puma::Util.purge_interrupt_queue
|
@@ -147,7 +150,7 @@ module Puma
|
|
147
150
|
# exiting until any background operations are completed
|
148
151
|
@config.run_hooks(:before_worker_shutdown, index, @log_writer, @hook_data)
|
149
152
|
ensure
|
150
|
-
@worker_write << "
|
153
|
+
@worker_write << "#{PIPE_TERM}#{Process.pid}\n" rescue nil
|
151
154
|
@worker_write.close
|
152
155
|
end
|
153
156
|
|
@@ -51,13 +51,12 @@ module Puma
|
|
51
51
|
@term
|
52
52
|
end
|
53
53
|
|
54
|
+
STATUS_PATTERN = /{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads":(?<max_threads>\d*), "requests_count":(?<requests_count>\d*), "busy_threads":(?<busy_threads>\d*) }/
|
55
|
+
private_constant :STATUS_PATTERN
|
56
|
+
|
54
57
|
def ping!(status)
|
55
58
|
@last_checkin = Time.now
|
56
|
-
|
57
|
-
@last_status = captures.names.inject({}) do |hash, key|
|
58
|
-
hash[key.to_sym] = captures[key].to_i
|
59
|
-
hash
|
60
|
-
end
|
59
|
+
@last_status = status.match(STATUS_PATTERN).named_captures.map { |c_name, c| [c_name.to_sym, c.to_i] }.to_h
|
61
60
|
end
|
62
61
|
|
63
62
|
# @see Puma::Cluster#check_workers
|
data/lib/puma/cluster.rb
CHANGED
@@ -87,6 +87,10 @@ module Puma
|
|
87
87
|
|
88
88
|
if @options[:fork_worker] && all_workers_in_phase?
|
89
89
|
@fork_writer << "0\n"
|
90
|
+
|
91
|
+
if worker_at(0).phase > 0
|
92
|
+
@fork_writer << "-2\n"
|
93
|
+
end
|
90
94
|
end
|
91
95
|
end
|
92
96
|
|
@@ -348,8 +352,6 @@ module Puma
|
|
348
352
|
def run
|
349
353
|
@status = :run
|
350
354
|
|
351
|
-
@idle_workers = {}
|
352
|
-
|
353
355
|
output_header "cluster"
|
354
356
|
|
355
357
|
# This is aligned with the output from Runner, see Runner#output_header
|
@@ -440,7 +442,7 @@ module Puma
|
|
440
442
|
|
441
443
|
while @status == :run
|
442
444
|
begin
|
443
|
-
if all_workers_idle_timed_out?
|
445
|
+
if @options[:idle_timeout] && all_workers_idle_timed_out?
|
444
446
|
log "- All workers reached idle timeout"
|
445
447
|
break
|
446
448
|
end
|
@@ -456,14 +458,17 @@ module Puma
|
|
456
458
|
|
457
459
|
if read.wait_readable([0, @next_check - Time.now].max)
|
458
460
|
req = read.read_nonblock(1)
|
461
|
+
next unless req
|
459
462
|
|
460
|
-
|
461
|
-
|
463
|
+
if req == PIPE_WAKEUP
|
464
|
+
@next_check = Time.now
|
465
|
+
next
|
466
|
+
end
|
462
467
|
|
463
468
|
result = read.gets
|
464
469
|
pid = result.to_i
|
465
470
|
|
466
|
-
if req ==
|
471
|
+
if req == PIPE_BOOT || req == PIPE_FORK
|
467
472
|
pid, idx = result.split(':').map(&:to_i)
|
468
473
|
w = worker_at idx
|
469
474
|
w.pid = pid if w.pid.nil?
|
@@ -471,22 +476,22 @@ module Puma
|
|
471
476
|
|
472
477
|
if w = @workers.find { |x| x.pid == pid }
|
473
478
|
case req
|
474
|
-
when
|
479
|
+
when PIPE_BOOT
|
475
480
|
w.boot!
|
476
481
|
log "- Worker #{w.index} (PID: #{pid}) booted in #{w.uptime.round(2)}s, phase: #{w.phase}"
|
477
482
|
@next_check = Time.now
|
478
483
|
workers_not_booted -= 1
|
479
|
-
when
|
484
|
+
when PIPE_EXTERNAL_TERM
|
480
485
|
# external term, see worker method, Signal.trap "SIGTERM"
|
481
486
|
w.term!
|
482
|
-
when
|
487
|
+
when PIPE_TERM
|
483
488
|
w.term unless w.term?
|
484
|
-
when
|
489
|
+
when PIPE_PING
|
485
490
|
status = result.sub(/^\d+/,'').chomp
|
486
491
|
w.ping!(status)
|
487
492
|
@events.fire(:ping!, w)
|
488
493
|
|
489
|
-
if in_phased_restart && workers_not_booted.positive? && w0 = worker_at(0)
|
494
|
+
if in_phased_restart && @options[:fork_worker] && workers_not_booted.positive? && w0 = worker_at(0)
|
490
495
|
w0.ping!(status)
|
491
496
|
@events.fire(:ping!, w0)
|
492
497
|
end
|
@@ -496,11 +501,11 @@ module Puma
|
|
496
501
|
debug_loaded_extensions("Loaded Extensions - master:") if @log_writer.debug?
|
497
502
|
booted = true
|
498
503
|
end
|
499
|
-
when
|
500
|
-
if
|
501
|
-
|
504
|
+
when PIPE_IDLE
|
505
|
+
if idle_workers[pid]
|
506
|
+
idle_workers.delete pid
|
502
507
|
else
|
503
|
-
|
508
|
+
idle_workers[pid] = true
|
504
509
|
end
|
505
510
|
end
|
506
511
|
else
|
@@ -560,9 +565,12 @@ module Puma
|
|
560
565
|
@workers.reject! do |w|
|
561
566
|
next false if w.pid.nil?
|
562
567
|
begin
|
563
|
-
#
|
564
|
-
#
|
565
|
-
|
568
|
+
# We may need to check the PID individually because:
|
569
|
+
# 1. From Ruby versions 2.6 to 3.2, `Process.detach` can prevent or delay
|
570
|
+
# `Process.wait2(-1)` from detecting a terminated process: https://bugs.ruby-lang.org/issues/19837.
|
571
|
+
# 2. When `fork_worker` is enabled, some worker may not be direct children,
|
572
|
+
# but grand children. Because of this they won't be reaped by `Process.wait2(-1)`.
|
573
|
+
if reaped_children.delete(w.pid) || Process.wait(w.pid, Process::WNOHANG)
|
566
574
|
true
|
567
575
|
else
|
568
576
|
w.term if w.term?
|
@@ -602,7 +610,11 @@ module Puma
|
|
602
610
|
end
|
603
611
|
|
604
612
|
def idle_timed_out_worker_pids
|
605
|
-
|
613
|
+
idle_workers.keys
|
614
|
+
end
|
615
|
+
|
616
|
+
def idle_workers
|
617
|
+
@idle_workers ||= {}
|
606
618
|
end
|
607
619
|
end
|
608
620
|
end
|
data/lib/puma/configuration.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'rack/builder'
|
4
3
|
require_relative 'plugin'
|
5
4
|
require_relative 'const'
|
6
5
|
require_relative 'dsl'
|
@@ -131,6 +130,7 @@ module Puma
|
|
131
130
|
binds: ['tcp://0.0.0.0:9292'.freeze],
|
132
131
|
clean_thread_locals: false,
|
133
132
|
debug: false,
|
133
|
+
enable_keep_alives: true,
|
134
134
|
early_hints: nil,
|
135
135
|
environment: 'development'.freeze,
|
136
136
|
# Number of seconds to wait until we get the first data for the request.
|
@@ -173,8 +173,8 @@ module Puma
|
|
173
173
|
http_content_length_limit: nil
|
174
174
|
}
|
175
175
|
|
176
|
-
def initialize(user_options={}, default_options = {}, &block)
|
177
|
-
default_options = self.puma_default_options.merge(default_options)
|
176
|
+
def initialize(user_options={}, default_options = {}, env = ENV, &block)
|
177
|
+
default_options = self.puma_default_options(env).merge(default_options)
|
178
178
|
|
179
179
|
@options = UserFileDefaultOptions.new(user_options, default_options)
|
180
180
|
@plugins = PluginLoader.new
|
@@ -186,6 +186,8 @@ module Puma
|
|
186
186
|
default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
|
187
187
|
end
|
188
188
|
|
189
|
+
@puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
|
190
|
+
|
189
191
|
if block
|
190
192
|
configure(&block)
|
191
193
|
end
|
@@ -216,22 +218,27 @@ module Puma
|
|
216
218
|
self
|
217
219
|
end
|
218
220
|
|
219
|
-
def puma_default_options
|
221
|
+
def puma_default_options(env = ENV)
|
220
222
|
defaults = DEFAULTS.dup
|
221
|
-
puma_options_from_env.each { |k,v| defaults[k] = v if v }
|
223
|
+
puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
|
222
224
|
defaults
|
223
225
|
end
|
224
226
|
|
225
|
-
def puma_options_from_env
|
226
|
-
min =
|
227
|
-
max =
|
228
|
-
workers =
|
227
|
+
def puma_options_from_env(env = ENV)
|
228
|
+
min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
|
229
|
+
max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
|
230
|
+
workers = if env['WEB_CONCURRENCY'] == 'auto'
|
231
|
+
require_processor_counter
|
232
|
+
::Concurrent.available_processor_count
|
233
|
+
else
|
234
|
+
env['WEB_CONCURRENCY']
|
235
|
+
end
|
229
236
|
|
230
237
|
{
|
231
|
-
min_threads: min && Integer(min),
|
232
|
-
max_threads: max && Integer(max),
|
233
|
-
workers: workers && Integer(workers),
|
234
|
-
environment:
|
238
|
+
min_threads: min && min != "" && Integer(min),
|
239
|
+
max_threads: max && max != "" && Integer(max),
|
240
|
+
workers: workers && workers != "" && Integer(workers),
|
241
|
+
environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
|
235
242
|
}
|
236
243
|
end
|
237
244
|
|
@@ -311,6 +318,8 @@ module Puma
|
|
311
318
|
# @param arg [Launcher, Int] `:on_restart` passes Launcher
|
312
319
|
#
|
313
320
|
def run_hooks(key, arg, log_writer, hook_data = nil)
|
321
|
+
log_writer.debug "Running #{key} hooks"
|
322
|
+
|
314
323
|
@options.all_of(key).each do |b|
|
315
324
|
begin
|
316
325
|
if Array === b
|
@@ -339,12 +348,22 @@ module Puma
|
|
339
348
|
|
340
349
|
private
|
341
350
|
|
351
|
+
def require_processor_counter
|
352
|
+
require 'concurrent/utility/processor_counter'
|
353
|
+
rescue LoadError
|
354
|
+
warn <<~MESSAGE
|
355
|
+
WEB_CONCURRENCY=auto requires the "concurrent-ruby" gem to be installed.
|
356
|
+
Please add "concurrent-ruby" to your Gemfile.
|
357
|
+
MESSAGE
|
358
|
+
raise
|
359
|
+
end
|
360
|
+
|
342
361
|
# Load and use the normal Rack builder if we can, otherwise
|
343
362
|
# fallback to our minimal version.
|
344
363
|
def rack_builder
|
345
364
|
# Load bundler now if we can so that we can pickup rack from
|
346
365
|
# a Gemfile
|
347
|
-
if
|
366
|
+
if @puma_bundler_pruned
|
348
367
|
begin
|
349
368
|
require 'bundler/setup'
|
350
369
|
rescue LoadError
|
@@ -354,11 +373,10 @@ module Puma
|
|
354
373
|
begin
|
355
374
|
require 'rack'
|
356
375
|
require 'rack/builder'
|
376
|
+
::Rack::Builder
|
357
377
|
rescue LoadError
|
358
|
-
|
359
|
-
|
360
|
-
else
|
361
|
-
return ::Rack::Builder
|
378
|
+
require_relative 'rack/builder'
|
379
|
+
Puma::Rack::Builder
|
362
380
|
end
|
363
381
|
end
|
364
382
|
|
data/lib/puma/const.rb
CHANGED
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "6.
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "6.6.0"
|
104
|
+
CODE_NAME = "Return to Forever"
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
107
107
|
|
@@ -137,7 +137,7 @@ module Puma
|
|
137
137
|
}.freeze
|
138
138
|
|
139
139
|
# The basic max request size we'll try to read.
|
140
|
-
CHUNK_SIZE =
|
140
|
+
CHUNK_SIZE = 64 * 1024
|
141
141
|
|
142
142
|
# This is the maximum header that is allowed before a client is booted. The parser detects
|
143
143
|
# this, but we'd also like to do this as well.
|
@@ -293,5 +293,16 @@ module Puma
|
|
293
293
|
BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
|
294
294
|
|
295
295
|
PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
|
296
|
+
|
297
|
+
# All constants are prefixed with `PIPE_` to avoid name collisions.
|
298
|
+
module PipeRequest
|
299
|
+
PIPE_WAKEUP = "!"
|
300
|
+
PIPE_BOOT = "b"
|
301
|
+
PIPE_FORK = "f"
|
302
|
+
PIPE_EXTERNAL_TERM = "e"
|
303
|
+
PIPE_TERM = "t"
|
304
|
+
PIPE_PING = "p"
|
305
|
+
PIPE_IDLE = "i"
|
306
|
+
end
|
296
307
|
end
|
297
308
|
end
|