puma 6.0.0 → 6.2.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +100 -5
- data/README.md +15 -3
- data/docs/nginx.md +1 -1
- data/docs/systemd.md +1 -2
- data/lib/puma/binder.rb +4 -3
- data/lib/puma/cli.rb +1 -1
- data/lib/puma/client.rb +32 -4
- data/lib/puma/cluster/worker.rb +5 -0
- data/lib/puma/cluster.rb +5 -5
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +2 -0
- data/lib/puma/const.rb +76 -84
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +62 -2
- data/lib/puma/error_logger.rb +2 -1
- data/lib/puma/io_buffer.rb +10 -0
- data/lib/puma/launcher.rb +9 -22
- data/lib/puma/log_writer.rb +13 -3
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/rack_default.rb +18 -3
- data/lib/puma/reactor.rb +1 -1
- data/lib/puma/request.rb +177 -119
- data/lib/puma/runner.rb +11 -0
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +6 -7
- data/lib/puma/single.rb +3 -1
- data/lib/puma/thread_pool.rb +1 -4
- data/lib/puma.rb +1 -3
- data/lib/rack/handler/puma.rb +117 -94
- metadata +4 -3
- data/lib/puma/systemd.rb +0 -47
data/lib/puma/const.rb
CHANGED
@@ -5,7 +5,6 @@ module Puma
|
|
5
5
|
class UnsupportedOption < RuntimeError
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
8
|
# Every standard HTTP code mapped to the appropriate message. These are
|
10
9
|
# used so frequently that they are placed directly in Puma for easy
|
11
10
|
# access rather than Puma::Const itself.
|
@@ -100,10 +99,10 @@ module Puma
|
|
100
99
|
# too taxing on performance.
|
101
100
|
module Const
|
102
101
|
|
103
|
-
PUMA_VERSION = VERSION = "6.
|
104
|
-
CODE_NAME = "
|
102
|
+
PUMA_VERSION = VERSION = "6.2.1"
|
103
|
+
CODE_NAME = "Speaking of Now"
|
105
104
|
|
106
|
-
PUMA_SERVER_STRING = [
|
105
|
+
PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
|
107
106
|
|
108
107
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
109
108
|
|
@@ -112,28 +111,28 @@ module Puma
|
|
112
111
|
WRITE_TIMEOUT = 10
|
113
112
|
|
114
113
|
# The original URI requested by the client.
|
115
|
-
REQUEST_URI=
|
116
|
-
REQUEST_PATH =
|
117
|
-
QUERY_STRING =
|
118
|
-
CONTENT_LENGTH = "CONTENT_LENGTH"
|
114
|
+
REQUEST_URI= "REQUEST_URI"
|
115
|
+
REQUEST_PATH = "REQUEST_PATH"
|
116
|
+
QUERY_STRING = "QUERY_STRING"
|
117
|
+
CONTENT_LENGTH = "CONTENT_LENGTH"
|
119
118
|
|
120
|
-
PATH_INFO =
|
119
|
+
PATH_INFO = "PATH_INFO"
|
121
120
|
|
122
|
-
PUMA_TMP_BASE = "puma"
|
121
|
+
PUMA_TMP_BASE = "puma"
|
123
122
|
|
124
123
|
ERROR_RESPONSE = {
|
125
124
|
# Indicate that we couldn't parse the request
|
126
|
-
400 => "HTTP/1.1 400 Bad Request\r\n\r\n"
|
125
|
+
400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
|
127
126
|
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
128
|
-
404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND"
|
127
|
+
404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND",
|
129
128
|
# The standard empty 408 response for requests that timed out.
|
130
|
-
408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n"
|
129
|
+
408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n",
|
131
130
|
# Indicate that there was an internal error, obviously.
|
132
|
-
500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n"
|
131
|
+
500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
|
133
132
|
# Incorrect or invalid header value
|
134
|
-
501 => "HTTP/1.1 501 Not Implemented\r\n\r\n"
|
133
|
+
501 => "HTTP/1.1 501 Not Implemented\r\n\r\n",
|
135
134
|
# A common header for indicating the server is too busy. Not used yet.
|
136
|
-
503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY"
|
135
|
+
503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY"
|
137
136
|
}.freeze
|
138
137
|
|
139
138
|
# The basic max request size we'll try to read.
|
@@ -146,95 +145,88 @@ module Puma
|
|
146
145
|
# Maximum request body size before it is moved out of memory and into a tempfile for reading.
|
147
146
|
MAX_BODY = MAX_HEADER
|
148
147
|
|
149
|
-
REQUEST_METHOD = "REQUEST_METHOD"
|
150
|
-
HEAD = "HEAD"
|
151
|
-
|
152
|
-
POST = "POST".freeze
|
153
|
-
PUT = "PUT".freeze
|
154
|
-
DELETE = "DELETE".freeze
|
155
|
-
OPTIONS = "OPTIONS".freeze
|
156
|
-
TRACE = "TRACE".freeze
|
157
|
-
PATCH = "PATCH".freeze
|
158
|
-
SUPPORTED_HTTP_METHODS = [HEAD, GET, POST, PUT, DELETE, OPTIONS, TRACE, PATCH].freeze
|
148
|
+
REQUEST_METHOD = "REQUEST_METHOD"
|
149
|
+
HEAD = "HEAD"
|
150
|
+
SUPPORTED_HTTP_METHODS = %w[HEAD GET POST PUT DELETE OPTIONS TRACE PATCH].freeze
|
159
151
|
# ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
|
160
|
-
LINE_END = "\r\n"
|
161
|
-
REMOTE_ADDR = "REMOTE_ADDR"
|
162
|
-
HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
|
163
|
-
HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL"
|
164
|
-
HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME"
|
165
|
-
HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO"
|
152
|
+
LINE_END = "\r\n"
|
153
|
+
REMOTE_ADDR = "REMOTE_ADDR"
|
154
|
+
HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
|
155
|
+
HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL"
|
156
|
+
HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME"
|
157
|
+
HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO"
|
166
158
|
|
167
|
-
SERVER_NAME = "SERVER_NAME"
|
168
|
-
SERVER_PORT = "SERVER_PORT"
|
169
|
-
HTTP_HOST = "HTTP_HOST"
|
170
|
-
PORT_80 = "80"
|
171
|
-
PORT_443 = "443"
|
172
|
-
LOCALHOST = "localhost"
|
173
|
-
LOCALHOST_IPV4 = "127.0.0.1"
|
174
|
-
LOCALHOST_IPV6 = "::1"
|
175
|
-
UNSPECIFIED_IPV4 = "0.0.0.0"
|
176
|
-
UNSPECIFIED_IPV6 = "::"
|
159
|
+
SERVER_NAME = "SERVER_NAME"
|
160
|
+
SERVER_PORT = "SERVER_PORT"
|
161
|
+
HTTP_HOST = "HTTP_HOST"
|
162
|
+
PORT_80 = "80"
|
163
|
+
PORT_443 = "443"
|
164
|
+
LOCALHOST = "localhost"
|
165
|
+
LOCALHOST_IPV4 = "127.0.0.1"
|
166
|
+
LOCALHOST_IPV6 = "::1"
|
167
|
+
UNSPECIFIED_IPV4 = "0.0.0.0"
|
168
|
+
UNSPECIFIED_IPV6 = "::"
|
177
169
|
|
178
|
-
SERVER_PROTOCOL = "SERVER_PROTOCOL"
|
179
|
-
HTTP_11 = "HTTP/1.1"
|
170
|
+
SERVER_PROTOCOL = "SERVER_PROTOCOL"
|
171
|
+
HTTP_11 = "HTTP/1.1"
|
180
172
|
|
181
|
-
SERVER_SOFTWARE = "SERVER_SOFTWARE"
|
182
|
-
GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
|
183
|
-
CGI_VER = "CGI/1.2"
|
173
|
+
SERVER_SOFTWARE = "SERVER_SOFTWARE"
|
174
|
+
GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
|
175
|
+
CGI_VER = "CGI/1.2"
|
184
176
|
|
185
|
-
STOP_COMMAND = "?"
|
186
|
-
HALT_COMMAND = "!"
|
187
|
-
RESTART_COMMAND = "R"
|
177
|
+
STOP_COMMAND = "?"
|
178
|
+
HALT_COMMAND = "!"
|
179
|
+
RESTART_COMMAND = "R"
|
188
180
|
|
189
|
-
RACK_INPUT = "rack.input"
|
190
|
-
RACK_URL_SCHEME = "rack.url_scheme"
|
191
|
-
RACK_AFTER_REPLY = "rack.after_reply"
|
192
|
-
PUMA_SOCKET = "puma.socket"
|
193
|
-
PUMA_CONFIG = "puma.config"
|
194
|
-
PUMA_PEERCERT = "puma.peercert"
|
181
|
+
RACK_INPUT = "rack.input"
|
182
|
+
RACK_URL_SCHEME = "rack.url_scheme"
|
183
|
+
RACK_AFTER_REPLY = "rack.after_reply"
|
184
|
+
PUMA_SOCKET = "puma.socket"
|
185
|
+
PUMA_CONFIG = "puma.config"
|
186
|
+
PUMA_PEERCERT = "puma.peercert"
|
195
187
|
|
196
|
-
HTTP = "http"
|
197
|
-
HTTPS = "https"
|
188
|
+
HTTP = "http"
|
189
|
+
HTTPS = "https"
|
198
190
|
|
199
|
-
HTTPS_KEY = "HTTPS"
|
191
|
+
HTTPS_KEY = "HTTPS"
|
200
192
|
|
201
|
-
HTTP_VERSION = "HTTP_VERSION"
|
202
|
-
HTTP_CONNECTION = "HTTP_CONNECTION"
|
203
|
-
HTTP_EXPECT = "HTTP_EXPECT"
|
204
|
-
CONTINUE = "100-continue"
|
193
|
+
HTTP_VERSION = "HTTP_VERSION"
|
194
|
+
HTTP_CONNECTION = "HTTP_CONNECTION"
|
195
|
+
HTTP_EXPECT = "HTTP_EXPECT"
|
196
|
+
CONTINUE = "100-continue"
|
205
197
|
|
206
|
-
HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n"
|
207
|
-
HTTP_11_200 = "HTTP/1.1 200 OK\r\n"
|
208
|
-
HTTP_10_200 = "HTTP/1.0 200 OK\r\n"
|
198
|
+
HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n"
|
199
|
+
HTTP_11_200 = "HTTP/1.1 200 OK\r\n"
|
200
|
+
HTTP_10_200 = "HTTP/1.0 200 OK\r\n"
|
209
201
|
|
210
|
-
CLOSE = "close"
|
211
|
-
KEEP_ALIVE = "keep-alive"
|
202
|
+
CLOSE = "close"
|
203
|
+
KEEP_ALIVE = "keep-alive"
|
212
204
|
|
213
|
-
CONTENT_LENGTH2 = "content-length"
|
214
|
-
CONTENT_LENGTH_S = "Content-Length: "
|
215
|
-
TRANSFER_ENCODING = "transfer-encoding"
|
216
|
-
TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
|
205
|
+
CONTENT_LENGTH2 = "content-length"
|
206
|
+
CONTENT_LENGTH_S = "Content-Length: "
|
207
|
+
TRANSFER_ENCODING = "transfer-encoding"
|
208
|
+
TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
|
217
209
|
|
218
|
-
CONNECTION_CLOSE = "Connection: close\r\n"
|
219
|
-
CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
|
210
|
+
CONNECTION_CLOSE = "Connection: close\r\n"
|
211
|
+
CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
|
220
212
|
|
221
|
-
TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
|
222
|
-
CLOSE_CHUNKED = "0\r\n\r\n"
|
213
|
+
TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
|
214
|
+
CLOSE_CHUNKED = "0\r\n\r\n"
|
223
215
|
|
224
|
-
CHUNKED = "chunked"
|
216
|
+
CHUNKED = "chunked"
|
225
217
|
|
226
|
-
COLON = ": "
|
218
|
+
COLON = ": "
|
227
219
|
|
228
|
-
NEWLINE = "\n"
|
220
|
+
NEWLINE = "\n"
|
229
221
|
|
230
|
-
HIJACK_P = "rack.hijack?"
|
231
|
-
HIJACK = "rack.hijack"
|
232
|
-
HIJACK_IO = "rack.hijack_io"
|
222
|
+
HIJACK_P = "rack.hijack?"
|
223
|
+
HIJACK = "rack.hijack"
|
224
|
+
HIJACK_IO = "rack.hijack_io"
|
233
225
|
|
234
|
-
EARLY_HINTS = "rack.early_hints"
|
226
|
+
EARLY_HINTS = "rack.early_hints"
|
235
227
|
|
236
228
|
# Illegal character in the key or value of response header
|
237
|
-
DQUOTE = "\""
|
229
|
+
DQUOTE = "\""
|
238
230
|
HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
|
239
231
|
ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
|
240
232
|
# header values can contain HTAB?
|
data/lib/puma/detect.rb
CHANGED
data/lib/puma/dsl.rb
CHANGED
@@ -65,6 +65,7 @@ module Puma
|
|
65
65
|
|
66
66
|
ca_additions = "&ca=#{Puma::Util.escape(opts[:ca])}" if ['peer', 'force_peer'].include?(verify)
|
67
67
|
|
68
|
+
low_latency_str = opts.key?(:low_latency) ? "&low_latency=#{opts[:low_latency]}" : ''
|
68
69
|
backlog_str = opts[:backlog] ? "&backlog=#{Integer(opts[:backlog])}" : ''
|
69
70
|
|
70
71
|
if defined?(JRUBY_VERSION)
|
@@ -114,7 +115,7 @@ module Puma
|
|
114
115
|
end
|
115
116
|
|
116
117
|
"ssl://#{host}:#{port}?#{cert_flags}#{key_flags}#{ssl_cipher_filter}" \
|
117
|
-
"#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}"
|
118
|
+
"#{reuse_flag}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}#{backlog_str}#{low_latency_str}"
|
118
119
|
end
|
119
120
|
end
|
120
121
|
|
@@ -250,6 +251,7 @@ module Puma
|
|
250
251
|
#
|
251
252
|
# * Set the socket backlog depth with +backlog+, default is 1024.
|
252
253
|
# * Set up an SSL certificate with +key+ & +cert+.
|
254
|
+
# * Set up an SSL certificate for mTLS with +key+, +cert+, +ca+ and +verify_mode+.
|
253
255
|
# * Set whether to optimize for low latency instead of throughput with
|
254
256
|
# +low_latency+, default is to not optimize for low latency. This is done
|
255
257
|
# via +Socket::TCP_NODELAY+.
|
@@ -259,6 +261,8 @@ module Puma
|
|
259
261
|
# bind 'unix:///var/run/puma.sock?backlog=512'
|
260
262
|
# @example SSL cert
|
261
263
|
# bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem'
|
264
|
+
# @example SSL cert for mutual TLS (mTLS)
|
265
|
+
# bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
|
262
266
|
# @example Disable optimization for low latency
|
263
267
|
# bind 'tcp://0.0.0.0:9292?low_latency=false'
|
264
268
|
# @example Socket permissions
|
@@ -415,6 +419,11 @@ module Puma
|
|
415
419
|
@options[:log_requests] = which
|
416
420
|
end
|
417
421
|
|
422
|
+
# Pass in a custom logging class instance
|
423
|
+
def custom_logger(custom_logger)
|
424
|
+
@options[:custom_logger] = custom_logger
|
425
|
+
end
|
426
|
+
|
418
427
|
# Show debugging info
|
419
428
|
#
|
420
429
|
def debug
|
@@ -581,6 +590,11 @@ module Puma
|
|
581
590
|
@options[:silence_single_worker_warning] = true
|
582
591
|
end
|
583
592
|
|
593
|
+
# Disable warning message when running single mode with callback hook defined.
|
594
|
+
def silence_fork_callback_warning
|
595
|
+
@options[:silence_fork_callback_warning] = true
|
596
|
+
end
|
597
|
+
|
584
598
|
# Code to run immediately before master process
|
585
599
|
# forks workers (once on boot). These hooks can block if necessary
|
586
600
|
# to wait for background operations unknown to Puma to finish before
|
@@ -596,6 +610,8 @@ module Puma
|
|
596
610
|
# puts "Starting workers..."
|
597
611
|
# end
|
598
612
|
def before_fork(&block)
|
613
|
+
warn_if_in_single_mode('before_fork')
|
614
|
+
|
599
615
|
@options[:before_fork] ||= []
|
600
616
|
@options[:before_fork] << block
|
601
617
|
end
|
@@ -611,6 +627,8 @@ module Puma
|
|
611
627
|
# puts 'Before worker boot...'
|
612
628
|
# end
|
613
629
|
def on_worker_boot(key = nil, &block)
|
630
|
+
warn_if_in_single_mode('on_worker_boot')
|
631
|
+
|
614
632
|
process_hook :before_worker_boot, key, block, 'on_worker_boot'
|
615
633
|
end
|
616
634
|
|
@@ -627,6 +645,8 @@ module Puma
|
|
627
645
|
# puts 'On worker shutdown...'
|
628
646
|
# end
|
629
647
|
def on_worker_shutdown(key = nil, &block)
|
648
|
+
warn_if_in_single_mode('on_worker_shutdown')
|
649
|
+
|
630
650
|
process_hook :before_worker_shutdown, key, block, 'on_worker_shutdown'
|
631
651
|
end
|
632
652
|
|
@@ -641,6 +661,8 @@ module Puma
|
|
641
661
|
# puts 'Before worker fork...'
|
642
662
|
# end
|
643
663
|
def on_worker_fork(&block)
|
664
|
+
warn_if_in_single_mode('on_worker_fork')
|
665
|
+
|
644
666
|
process_hook :before_worker_fork, nil, block, 'on_worker_fork'
|
645
667
|
end
|
646
668
|
|
@@ -655,11 +677,23 @@ module Puma
|
|
655
677
|
# puts 'After worker fork...'
|
656
678
|
# end
|
657
679
|
def after_worker_fork(&block)
|
680
|
+
warn_if_in_single_mode('after_worker_fork')
|
681
|
+
|
658
682
|
process_hook :after_worker_fork, nil, block, 'after_worker_fork'
|
659
683
|
end
|
660
684
|
|
661
685
|
alias_method :after_worker_boot, :after_worker_fork
|
662
686
|
|
687
|
+
# Code to run after puma is booted (works for both: single and clustered)
|
688
|
+
#
|
689
|
+
# @example
|
690
|
+
# on_booted do
|
691
|
+
# puts 'After booting...'
|
692
|
+
# end
|
693
|
+
def on_booted(&block)
|
694
|
+
@config.options[:events].on_booted(&block)
|
695
|
+
end
|
696
|
+
|
663
697
|
# When `fork_worker` is enabled, code to run in Worker 0
|
664
698
|
# before all other workers are re-forked from this process,
|
665
699
|
# after the server has temporarily stopped serving requests
|
@@ -1019,6 +1053,19 @@ module Puma
|
|
1019
1053
|
@options[:mutate_stdout_and_stderr_to_sync_on_write] = enabled
|
1020
1054
|
end
|
1021
1055
|
|
1056
|
+
# Specify how big the request payload should be, in bytes.
|
1057
|
+
# This limit is compared against Content-Length HTTP header.
|
1058
|
+
# If the payload size (CONTENT_LENGTH) is larger than http_content_length_limit,
|
1059
|
+
# HTTP 413 status code is returned.
|
1060
|
+
#
|
1061
|
+
# When no Content-Length http header is present, it is compared against the
|
1062
|
+
# size of the body of the request.
|
1063
|
+
#
|
1064
|
+
# The default value for http_content_length_limit is nil.
|
1065
|
+
def http_content_length_limit(limit)
|
1066
|
+
@options[:http_content_length_limit] = limit
|
1067
|
+
end
|
1068
|
+
|
1022
1069
|
private
|
1023
1070
|
|
1024
1071
|
# To avoid adding cert_pem and key_pem as URI params, we store them on the
|
@@ -1046,7 +1093,20 @@ module Puma
|
|
1046
1093
|
elsif key.nil?
|
1047
1094
|
@options[options_key] << block
|
1048
1095
|
else
|
1049
|
-
raise "'#{
|
1096
|
+
raise "'#{meth}' key must be String or Symbol"
|
1097
|
+
end
|
1098
|
+
end
|
1099
|
+
|
1100
|
+
def warn_if_in_single_mode(hook_name)
|
1101
|
+
return if @options[:silence_fork_callback_warning]
|
1102
|
+
|
1103
|
+
if (@options[:workers] || 0) == 0
|
1104
|
+
log_string =
|
1105
|
+
"Warning: You specified code to run in a `#{hook_name}` block, " \
|
1106
|
+
"but Puma is not configured to run in cluster mode (worker count > 0 ), " \
|
1107
|
+
"so your `#{hook_name}` block did not run"
|
1108
|
+
|
1109
|
+
LogWriter.stdio.log(log_string)
|
1050
1110
|
end
|
1051
1111
|
end
|
1052
1112
|
end
|
data/lib/puma/error_logger.rb
CHANGED
@@ -102,7 +102,8 @@ module Puma
|
|
102
102
|
@ioerr.is_a?(IO) and @ioerr.wait_writable(1)
|
103
103
|
@ioerr.write "#{w_str}\n"
|
104
104
|
@ioerr.flush unless @ioerr.sync
|
105
|
-
rescue Errno::EPIPE, Errno::EBADF, IOError
|
105
|
+
rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
|
106
|
+
# 'Invalid argument' (Errno::EINVAL) may be raised by flush
|
106
107
|
end
|
107
108
|
end
|
108
109
|
rescue ThreadError
|
data/lib/puma/io_buffer.rb
CHANGED
@@ -22,6 +22,16 @@ module Puma
|
|
22
22
|
read
|
23
23
|
end
|
24
24
|
|
25
|
+
# Read & Reset - returns contents and resets
|
26
|
+
# @return [String] StringIO contents
|
27
|
+
def read_and_reset
|
28
|
+
rewind
|
29
|
+
str = read
|
30
|
+
truncate 0
|
31
|
+
rewind
|
32
|
+
str
|
33
|
+
end
|
34
|
+
|
25
35
|
alias_method :clear, :reset
|
26
36
|
|
27
37
|
# before Ruby 2.5, `write` would only take one argument
|
data/lib/puma/launcher.rb
CHANGED
@@ -59,6 +59,13 @@ module Puma
|
|
59
59
|
|
60
60
|
@environment = conf.environment
|
61
61
|
|
62
|
+
# Load the systemd integration if we detect systemd's NOTIFY_SOCKET.
|
63
|
+
# Skip this on JRuby though, because it is incompatible with the systemd
|
64
|
+
# integration due to https://github.com/jruby/jruby/issues/6504
|
65
|
+
if ENV["NOTIFY_SOCKET"] && !Puma.jruby?
|
66
|
+
@config.plugins.create('systemd')
|
67
|
+
end
|
68
|
+
|
62
69
|
if @config.options[:bind_to_activated_sockets]
|
63
70
|
@config.options[:binds] = @binder.synthesize_binds_from_activated_fs(
|
64
71
|
@config.options[:binds],
|
@@ -72,6 +79,8 @@ module Puma
|
|
72
79
|
@log_writer.formatter = LogWriter::PidFormatter.new if clustered?
|
73
80
|
@log_writer.formatter = options[:log_formatter] if @options[:log_formatter]
|
74
81
|
|
82
|
+
@log_writer.custom_logger = options[:custom_logger] if @options[:custom_logger]
|
83
|
+
|
75
84
|
generate_restart_data
|
76
85
|
|
77
86
|
if clustered? && !Puma.forkable?
|
@@ -180,7 +189,6 @@ module Puma
|
|
180
189
|
|
181
190
|
setup_signals
|
182
191
|
set_process_title
|
183
|
-
integrate_with_systemd
|
184
192
|
|
185
193
|
# This blocks until the server is stopped
|
186
194
|
@runner.run
|
@@ -311,27 +319,6 @@ module Puma
|
|
311
319
|
@runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
|
312
320
|
end
|
313
321
|
|
314
|
-
# Puma's systemd integration allows Puma to inform systemd:
|
315
|
-
# 1. when it has successfully started
|
316
|
-
# 2. when it is starting shutdown
|
317
|
-
# 3. periodically for a liveness check with a watchdog thread
|
318
|
-
def integrate_with_systemd
|
319
|
-
return unless ENV["NOTIFY_SOCKET"]
|
320
|
-
|
321
|
-
begin
|
322
|
-
require_relative 'systemd'
|
323
|
-
rescue LoadError
|
324
|
-
log "Systemd integration failed. It looks like you're trying to use systemd notify but don't have sd_notify gem installed"
|
325
|
-
return
|
326
|
-
end
|
327
|
-
|
328
|
-
log "* Enabling systemd notification integration"
|
329
|
-
|
330
|
-
systemd = Systemd.new(@log_writer, @events)
|
331
|
-
systemd.hook_events
|
332
|
-
systemd.start_watchdog
|
333
|
-
end
|
334
|
-
|
335
322
|
def log(str)
|
336
323
|
@log_writer.log(str)
|
337
324
|
end
|
data/lib/puma/log_writer.rb
CHANGED
@@ -28,11 +28,12 @@ module Puma
|
|
28
28
|
attr_reader :stdout,
|
29
29
|
:stderr
|
30
30
|
|
31
|
-
attr_accessor :formatter
|
31
|
+
attr_accessor :formatter, :custom_logger
|
32
32
|
|
33
33
|
# Create a LogWriter that prints to +stdout+ and +stderr+.
|
34
34
|
def initialize(stdout, stderr)
|
35
35
|
@formatter = DefaultFormatter.new
|
36
|
+
@custom_logger = nil
|
36
37
|
@stdout = stdout
|
37
38
|
@stderr = stderr
|
38
39
|
|
@@ -59,7 +60,11 @@ module Puma
|
|
59
60
|
|
60
61
|
# Write +str+ to +@stdout+
|
61
62
|
def log(str)
|
62
|
-
|
63
|
+
if @custom_logger&.respond_to?(:write)
|
64
|
+
@custom_logger.write(format(str))
|
65
|
+
else
|
66
|
+
internal_write "#{@formatter.call str}\n"
|
67
|
+
end
|
63
68
|
end
|
64
69
|
|
65
70
|
def write(str)
|
@@ -73,13 +78,18 @@ module Puma
|
|
73
78
|
@stdout.is_a?(IO) and @stdout.wait_writable(1)
|
74
79
|
@stdout.write w_str
|
75
80
|
@stdout.flush unless @stdout.sync
|
76
|
-
rescue Errno::EPIPE, Errno::EBADF, IOError
|
81
|
+
rescue Errno::EPIPE, Errno::EBADF, IOError, Errno::EINVAL
|
82
|
+
# 'Invalid argument' (Errno::EINVAL) may be raised by flush
|
77
83
|
end
|
78
84
|
end
|
79
85
|
rescue ThreadError
|
80
86
|
end
|
81
87
|
private :internal_write
|
82
88
|
|
89
|
+
def debug?
|
90
|
+
@debug
|
91
|
+
end
|
92
|
+
|
83
93
|
def debug(str)
|
84
94
|
log("% #{str}") if @debug
|
85
95
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../plugin'
|
4
|
+
|
5
|
+
# Puma's systemd integration allows Puma to inform systemd:
|
6
|
+
# 1. when it has successfully started
|
7
|
+
# 2. when it is starting shutdown
|
8
|
+
# 3. periodically for a liveness check with a watchdog thread
|
9
|
+
# 4. periodically set the status
|
10
|
+
Puma::Plugin.create do
|
11
|
+
def start(launcher)
|
12
|
+
require_relative '../sd_notify'
|
13
|
+
|
14
|
+
launcher.log_writer.log "* Enabling systemd notification integration"
|
15
|
+
|
16
|
+
# hook_events
|
17
|
+
launcher.events.on_booted { Puma::SdNotify.ready }
|
18
|
+
launcher.events.on_stopped { Puma::SdNotify.stopping }
|
19
|
+
launcher.events.on_restart { Puma::SdNotify.reloading }
|
20
|
+
|
21
|
+
# start watchdog
|
22
|
+
if Puma::SdNotify.watchdog?
|
23
|
+
ping_f = watchdog_sleep_time
|
24
|
+
|
25
|
+
in_background do
|
26
|
+
launcher.log_writer.log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
|
27
|
+
loop do
|
28
|
+
sleep ping_f
|
29
|
+
Puma::SdNotify.watchdog
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# start status loop
|
35
|
+
instance = self
|
36
|
+
sleep_time = 1.0
|
37
|
+
in_background do
|
38
|
+
launcher.log_writer.log "Sending status to systemd every #{sleep_time.round(1)} sec"
|
39
|
+
|
40
|
+
loop do
|
41
|
+
sleep sleep_time
|
42
|
+
# TODO: error handling?
|
43
|
+
Puma::SdNotify.status(instance.status)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def status
|
49
|
+
if clustered?
|
50
|
+
messages = stats[:worker_status].map do |worker|
|
51
|
+
common_message(worker[:last_status])
|
52
|
+
end.join(',')
|
53
|
+
|
54
|
+
"Puma #{Puma::Const::VERSION}: cluster: #{booted_workers}/#{workers}, worker_status: [#{messages}]"
|
55
|
+
else
|
56
|
+
"Puma #{Puma::Const::VERSION}: worker: #{common_message(stats)}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def watchdog_sleep_time
|
63
|
+
usec = Integer(ENV["WATCHDOG_USEC"])
|
64
|
+
|
65
|
+
sec_f = usec / 1_000_000.0
|
66
|
+
# "It is recommended that a daemon sends a keep-alive notification message
|
67
|
+
# to the service manager every half of the time returned here."
|
68
|
+
sec_f / 2
|
69
|
+
end
|
70
|
+
|
71
|
+
def stats
|
72
|
+
Puma.stats_hash
|
73
|
+
end
|
74
|
+
|
75
|
+
def clustered?
|
76
|
+
stats.has_key?(:workers)
|
77
|
+
end
|
78
|
+
|
79
|
+
def workers
|
80
|
+
stats.fetch(:workers, 1)
|
81
|
+
end
|
82
|
+
|
83
|
+
def booted_workers
|
84
|
+
stats.fetch(:booted_workers, 1)
|
85
|
+
end
|
86
|
+
|
87
|
+
def common_message(stats)
|
88
|
+
"{ #{stats[:running]}/#{stats[:max_threads]} threads, #{stats[:pool_capacity]} available, #{stats[:backlog]} backlog }"
|
89
|
+
end
|
90
|
+
end
|
data/lib/puma/rack_default.rb
CHANGED
@@ -2,8 +2,23 @@
|
|
2
2
|
|
3
3
|
require_relative '../rack/handler/puma'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
# rackup was removed in Rack 3, it is now a separate gem
|
6
|
+
if Object.const_defined? :Rackup
|
7
|
+
module Rackup
|
8
|
+
module Handler
|
9
|
+
def self.default(options = {})
|
10
|
+
::Rackup::Handler::Puma
|
11
|
+
end
|
12
|
+
end
|
8
13
|
end
|
14
|
+
elsif Object.const_defined?(:Rack) && Rack::RELEASE < '3'
|
15
|
+
module Rack
|
16
|
+
module Handler
|
17
|
+
def self.default(options = {})
|
18
|
+
::Rack::Handler::Puma
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise "Rack 3 must be used with the Rackup gem"
|
9
24
|
end
|