puma 6.4.3-java → 6.6.0-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|