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/request.rb
CHANGED
@@ -14,15 +14,18 @@ module Puma
|
|
14
14
|
#
|
15
15
|
module Request # :nodoc:
|
16
16
|
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
17
|
+
# Single element array body: smaller bodies are written to io_buffer first,
|
18
|
+
# then a single write from io_buffer. Larger sizes are written separately.
|
19
|
+
# Also fixes max size of chunked file body read.
|
20
|
+
BODY_LEN_MAX = 1_024 * 256
|
20
21
|
|
21
|
-
#
|
22
|
+
# File body: smaller bodies are combined with io_buffer, then written to
|
23
|
+
# socket. Larger bodies are written separately using `copy_stream`
|
22
24
|
IO_BODY_MAX = 1_024 * 64
|
23
25
|
|
24
|
-
#
|
25
|
-
|
26
|
+
# Array body: elements are collected in io_buffer. When io_buffer's size
|
27
|
+
# exceeds value, they are written to the socket.
|
28
|
+
IO_BUFFER_LEN_MAX = 1_024 * 512
|
26
29
|
|
27
30
|
SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
|
28
31
|
|
@@ -41,16 +44,22 @@ module Puma
|
|
41
44
|
#
|
42
45
|
# Finally, it'll return +true+ on keep-alive connections.
|
43
46
|
# @param client [Puma::Client]
|
44
|
-
# @param io_buffer [Puma::IOBuffer]
|
45
47
|
# @param requests [Integer]
|
46
48
|
# @return [Boolean,:async]
|
47
49
|
#
|
48
|
-
def handle_request(client,
|
50
|
+
def handle_request(client, requests)
|
49
51
|
env = client.env
|
52
|
+
io_buffer = client.io_buffer
|
50
53
|
socket = client.io # io may be a MiniSSL::Socket
|
54
|
+
app_body = nil
|
55
|
+
|
51
56
|
|
52
57
|
return false if closed_socket?(socket)
|
53
58
|
|
59
|
+
if client.http_content_length_limit_exceeded
|
60
|
+
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
61
|
+
end
|
62
|
+
|
54
63
|
normalize_env env, client
|
55
64
|
|
56
65
|
env[PUMA_SOCKET] = socket
|
@@ -85,14 +94,19 @@ module Puma
|
|
85
94
|
|
86
95
|
begin
|
87
96
|
if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
|
88
|
-
status, headers,
|
97
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
89
98
|
@app.call(env)
|
90
99
|
end
|
91
100
|
else
|
92
101
|
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
93
|
-
status, headers,
|
102
|
+
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
94
103
|
end
|
95
104
|
|
105
|
+
# app_body needs to always be closed, hold value in case lowlevel_error
|
106
|
+
# is called
|
107
|
+
res_body = app_body
|
108
|
+
|
109
|
+
# full hijack, app called env['rack.hijack']
|
96
110
|
return :async if client.hijacked
|
97
111
|
|
98
112
|
status = status.to_i
|
@@ -114,114 +128,136 @@ module Puma
|
|
114
128
|
|
115
129
|
status, headers, res_body = lowlevel_error(e, env, 500)
|
116
130
|
end
|
117
|
-
prepare_response(status, headers, res_body,
|
131
|
+
prepare_response(status, headers, res_body, requests, client)
|
132
|
+
ensure
|
133
|
+
io_buffer.reset
|
134
|
+
uncork_socket client.io
|
135
|
+
app_body.close if app_body.respond_to? :close
|
136
|
+
client.tempfile&.unlink
|
137
|
+
after_reply = env[RACK_AFTER_REPLY] || []
|
138
|
+
begin
|
139
|
+
after_reply.each { |o| o.call }
|
140
|
+
rescue StandardError => e
|
141
|
+
@log_writer.debug_error e
|
142
|
+
end unless after_reply.empty?
|
118
143
|
end
|
119
144
|
|
120
145
|
# Assembles the headers and prepares the body for actually sending the
|
121
|
-
# response via
|
146
|
+
# response via `#fast_write_response`.
|
122
147
|
#
|
123
148
|
# @param status [Integer] the status returned by the Rack application
|
124
149
|
# @param headers [Hash] the headers returned by the Rack application
|
125
|
-
# @param
|
126
|
-
# a call to `lowlevel_error`
|
127
|
-
# @param io_buffer [Puma::IOBuffer] modified in place
|
150
|
+
# @param res_body [Array] the body returned by the Rack application or
|
151
|
+
# a call to `Server#lowlevel_error`
|
128
152
|
# @param requests [Integer] number of inline requests handled
|
129
153
|
# @param client [Puma::Client]
|
130
|
-
# @return [Boolean,:async]
|
131
|
-
def prepare_response(status, headers,
|
154
|
+
# @return [Boolean,:async] keep-alive status or `:async`
|
155
|
+
def prepare_response(status, headers, res_body, requests, client)
|
132
156
|
env = client.env
|
133
|
-
socket
|
134
|
-
|
135
|
-
after_reply = env[RACK_AFTER_REPLY] || []
|
157
|
+
socket = client.io
|
158
|
+
io_buffer = client.io_buffer
|
136
159
|
|
137
160
|
return false if closed_socket?(socket)
|
138
161
|
|
139
|
-
|
140
|
-
|
141
|
-
#
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
162
|
+
# Close the connection after a reasonable number of inline requests
|
163
|
+
# if the server is at capacity and the listener has a new connection ready.
|
164
|
+
# This allows Puma to service connections fairly when the number
|
165
|
+
# of concurrent connections exceeds the size of the threadpool.
|
166
|
+
force_keep_alive = requests < @max_fast_inline ||
|
167
|
+
@thread_pool.busy_threads < @max_threads ||
|
168
|
+
!client.listener.to_io.wait_readable(0)
|
169
|
+
|
170
|
+
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
171
|
+
|
172
|
+
close_body = false
|
173
|
+
response_hijack = nil
|
174
|
+
content_length = resp_info[:content_length]
|
175
|
+
keep_alive = resp_info[:keep_alive]
|
176
|
+
|
177
|
+
if res_body.respond_to?(:each) && !resp_info[:response_hijack]
|
178
|
+
# below converts app_body into body, dependent on app_body's characteristics, and
|
179
|
+
# content_length will be set if it can be determined
|
180
|
+
if !content_length && !resp_info[:transfer_encoding] && status != 204
|
181
|
+
if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
|
182
|
+
array_body.is_a?(Array)
|
183
|
+
body = array_body.compact
|
184
|
+
content_length = body.sum(&:bytesize)
|
185
|
+
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
186
|
+
body = res_body
|
187
|
+
content_length = body.size
|
188
|
+
elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
|
189
|
+
body = File.open fn, 'rb'
|
190
|
+
content_length = body.size
|
191
|
+
close_body = true
|
192
|
+
else
|
193
|
+
body = res_body
|
153
194
|
end
|
154
|
-
|
155
|
-
|
156
|
-
resp_info[:content_length] = app_body.size
|
157
|
-
body = app_body
|
158
|
-
elsif app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
|
159
|
-
File.readable?(fn = app_body.to_path)
|
195
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
|
196
|
+
File.readable?(fn = res_body.to_path)
|
160
197
|
body = File.open fn, 'rb'
|
161
|
-
|
198
|
+
content_length = body.size
|
199
|
+
close_body = true
|
200
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
|
201
|
+
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
202
|
+
# Sprockets::Asset
|
203
|
+
content_length = res_body.bytesize unless content_length
|
204
|
+
if (body_str = res_body.to_hash[:source])
|
205
|
+
body = [body_str]
|
206
|
+
else # avoid each and use a File object
|
207
|
+
body = File.open fn, 'rb'
|
208
|
+
close_body = true
|
209
|
+
end
|
162
210
|
else
|
163
|
-
body =
|
211
|
+
body = res_body
|
164
212
|
end
|
165
|
-
elsif !app_body.is_a?(::File) && app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
|
166
|
-
File.readable?(fn = app_body.to_path)
|
167
|
-
body = File.open fn, 'rb'
|
168
|
-
resp_info[:content_length] = body.size
|
169
213
|
else
|
170
|
-
|
214
|
+
# partial hijack, from Rack spec:
|
215
|
+
# Servers must ignore the body part of the response tuple when the
|
216
|
+
# rack.hijack response header is present.
|
217
|
+
response_hijack = resp_info[:response_hijack] || res_body
|
171
218
|
end
|
172
219
|
|
173
220
|
line_ending = LINE_END
|
174
221
|
|
175
|
-
content_length = resp_info[:content_length]
|
176
|
-
keep_alive = resp_info[:keep_alive]
|
177
|
-
|
178
|
-
if app_body && !app_body.respond_to?(:each)
|
179
|
-
response_hijack = app_body
|
180
|
-
else
|
181
|
-
response_hijack = resp_info[:response_hijack]
|
182
|
-
end
|
183
|
-
|
184
222
|
cork_socket socket
|
185
223
|
|
186
224
|
if resp_info[:no_body]
|
187
|
-
|
225
|
+
# 101 (Switching Protocols) doesn't return here or have content_length,
|
226
|
+
# it should be using `response_hijack`
|
227
|
+
unless status == 101
|
228
|
+
if content_length && status != 204
|
229
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
230
|
+
end
|
231
|
+
|
232
|
+
io_buffer << LINE_END
|
233
|
+
fast_write_str socket, io_buffer.read_and_reset
|
234
|
+
socket.flush
|
235
|
+
return keep_alive
|
236
|
+
end
|
237
|
+
else
|
238
|
+
if content_length
|
188
239
|
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
240
|
+
chunked = false
|
241
|
+
elsif !response_hijack && resp_info[:allow_chunked]
|
242
|
+
io_buffer << TRANSFER_ENCODING_CHUNKED
|
243
|
+
chunked = true
|
189
244
|
end
|
190
|
-
|
191
|
-
io_buffer << LINE_END
|
192
|
-
fast_write_str socket, io_buffer.to_s
|
193
|
-
return keep_alive
|
194
|
-
end
|
195
|
-
if content_length
|
196
|
-
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
197
|
-
chunked = false
|
198
|
-
elsif !response_hijack and resp_info[:allow_chunked]
|
199
|
-
io_buffer << TRANSFER_ENCODING_CHUNKED
|
200
|
-
chunked = true
|
201
245
|
end
|
202
246
|
|
203
247
|
io_buffer << line_ending
|
204
248
|
|
249
|
+
# partial hijack, we write headers, then hand the socket to the app via
|
250
|
+
# response_hijack.call
|
205
251
|
if response_hijack
|
206
|
-
fast_write_str socket, io_buffer.
|
252
|
+
fast_write_str socket, io_buffer.read_and_reset
|
253
|
+
uncork_socket socket
|
207
254
|
response_hijack.call socket
|
208
255
|
return :async
|
209
256
|
end
|
210
257
|
|
211
258
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
259
|
+
body.close if close_body
|
212
260
|
keep_alive
|
213
|
-
ensure
|
214
|
-
io_buffer.reset
|
215
|
-
resp_info = nil
|
216
|
-
uncork_socket socket
|
217
|
-
app_body.close if app_body.respond_to? :close
|
218
|
-
client.tempfile&.unlink
|
219
|
-
|
220
|
-
begin
|
221
|
-
after_reply.each { |o| o.call }
|
222
|
-
rescue StandardError => e
|
223
|
-
@log_writer.debug_error e
|
224
|
-
end unless after_reply.empty?
|
225
261
|
end
|
226
262
|
|
227
263
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -237,10 +273,10 @@ module Puma
|
|
237
273
|
|
238
274
|
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
239
275
|
# and body segments (called by `fast_write_response`).
|
240
|
-
# Writes a string to
|
276
|
+
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
241
277
|
# Large strings may not be written in one pass, especially if `io` is a
|
242
278
|
# `MiniSSL::Socket`.
|
243
|
-
# @param
|
279
|
+
# @param socket [#write_nonblock] the request/response socket
|
244
280
|
# @param str [String] the string written to the io
|
245
281
|
# @raise [ConnectionError]
|
246
282
|
#
|
@@ -249,7 +285,7 @@ module Puma
|
|
249
285
|
byte_size = str.bytesize
|
250
286
|
while n < byte_size
|
251
287
|
begin
|
252
|
-
n += socket.
|
288
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
253
289
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
254
290
|
unless socket.wait_writable WRITE_TIMEOUT
|
255
291
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
@@ -267,39 +303,41 @@ module Puma
|
|
267
303
|
# @param socket [#write] the response socket
|
268
304
|
# @param body [Enumerable, File] the body object
|
269
305
|
# @param io_buffer [Puma::IOBuffer] contains headers
|
270
|
-
# @param
|
306
|
+
# @param chunked [Boolean]
|
307
|
+
# @paramn content_length [Integer
|
271
308
|
# @raise [ConnectionError]
|
272
309
|
#
|
273
310
|
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
274
|
-
if body.is_a?(::File)
|
311
|
+
if body.is_a?(::File) && body.respond_to?(:read)
|
275
312
|
if chunked # would this ever happen?
|
276
|
-
while
|
277
|
-
io_buffer.append
|
313
|
+
while chunk = body.read(BODY_LEN_MAX)
|
314
|
+
io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
|
278
315
|
end
|
279
|
-
|
280
|
-
fast_write_str socket, io_buffer.to_s
|
316
|
+
fast_write_str socket, CLOSE_CHUNKED
|
281
317
|
else
|
282
318
|
if content_length <= IO_BODY_MAX
|
283
|
-
io_buffer.write body.
|
284
|
-
fast_write_str socket, io_buffer.
|
319
|
+
io_buffer.write body.read(content_length)
|
320
|
+
fast_write_str socket, io_buffer.read_and_reset
|
285
321
|
else
|
286
|
-
fast_write_str socket, io_buffer.
|
322
|
+
fast_write_str socket, io_buffer.read_and_reset
|
287
323
|
IO.copy_stream body, socket
|
288
324
|
end
|
289
325
|
end
|
290
|
-
body.close
|
291
326
|
elsif body.is_a?(::Array) && body.length == 1
|
292
|
-
body_first =
|
293
|
-
|
327
|
+
body_first = nil
|
328
|
+
# using body_first = body.first causes issues?
|
329
|
+
body.each { |str| body_first ||= str }
|
330
|
+
|
331
|
+
if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
|
332
|
+
# smaller body, write to io_buffer first
|
333
|
+
io_buffer.write body_first
|
334
|
+
fast_write_str socket, io_buffer.read_and_reset
|
335
|
+
else
|
294
336
|
# large body, write both header & body to socket
|
295
|
-
fast_write_str socket, io_buffer.
|
337
|
+
fast_write_str socket, io_buffer.read_and_reset
|
296
338
|
fast_write_str socket, body_first
|
297
|
-
else
|
298
|
-
# smaller body, write to stream first
|
299
|
-
io_buffer.write body_first
|
300
|
-
fast_write_str socket, io_buffer.to_s
|
301
339
|
end
|
302
|
-
|
340
|
+
elsif body.is_a?(::Array)
|
303
341
|
# for array bodies, flush io_buffer to socket when size is greater than
|
304
342
|
# IO_BUFFER_LEN_MAX
|
305
343
|
if chunked
|
@@ -307,8 +345,7 @@ module Puma
|
|
307
345
|
next if (byte_size = part.bytesize).zero?
|
308
346
|
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
309
347
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
310
|
-
fast_write_str socket, io_buffer.
|
311
|
-
io_buffer.reset
|
348
|
+
fast_write_str socket, io_buffer.read_and_reset
|
312
349
|
end
|
313
350
|
end
|
314
351
|
io_buffer.write CLOSE_CHUNKED
|
@@ -317,13 +354,37 @@ module Puma
|
|
317
354
|
next if part.bytesize.zero?
|
318
355
|
io_buffer.write part
|
319
356
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
320
|
-
fast_write_str socket, io_buffer.
|
321
|
-
io_buffer.reset
|
357
|
+
fast_write_str socket, io_buffer.read_and_reset
|
322
358
|
end
|
323
359
|
end
|
324
360
|
end
|
325
|
-
|
361
|
+
# may write last body part for non-chunked, also headers if array is empty
|
362
|
+
fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
|
363
|
+
else
|
364
|
+
# for enum bodies
|
365
|
+
if chunked
|
366
|
+
empty_body = true
|
367
|
+
body.each do |part|
|
368
|
+
next if part.nil? || (byte_size = part.bytesize).zero?
|
369
|
+
empty_body = false
|
370
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
371
|
+
fast_write_str socket, io_buffer.read_and_reset
|
372
|
+
end
|
373
|
+
if empty_body
|
374
|
+
io_buffer << CLOSE_CHUNKED
|
375
|
+
fast_write_str socket, io_buffer.read_and_reset
|
376
|
+
else
|
377
|
+
fast_write_str socket, CLOSE_CHUNKED
|
378
|
+
end
|
379
|
+
else
|
380
|
+
fast_write_str socket, io_buffer.read_and_reset
|
381
|
+
body.each do |part|
|
382
|
+
next if part.bytesize.zero?
|
383
|
+
fast_write_str socket, part
|
384
|
+
end
|
385
|
+
end
|
326
386
|
end
|
387
|
+
socket.flush
|
327
388
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
328
389
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
329
390
|
rescue Errno::EPIPE, SystemCallError, IOError
|
@@ -436,7 +497,7 @@ module Puma
|
|
436
497
|
to_add = nil
|
437
498
|
|
438
499
|
env.each do |k,v|
|
439
|
-
if k.start_with?("HTTP_")
|
500
|
+
if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
|
440
501
|
if to_delete
|
441
502
|
to_delete << k
|
442
503
|
else
|
@@ -496,12 +557,13 @@ module Puma
|
|
496
557
|
# @param content_length [Integer,nil] content length if it can be determined from the
|
497
558
|
# response body
|
498
559
|
# @param io_buffer [Puma::IOBuffer] modified inn place
|
499
|
-
# @param
|
500
|
-
#
|
560
|
+
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
561
|
+
# status and `@max_fast_inline`
|
501
562
|
# @return [Hash] resp_info
|
502
563
|
# @version 5.0.3
|
503
564
|
#
|
504
|
-
def str_headers(env, status, headers, res_body, io_buffer,
|
565
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
566
|
+
|
505
567
|
line_ending = LINE_END
|
506
568
|
colon = COLON
|
507
569
|
|
@@ -544,13 +606,8 @@ module Puma
|
|
544
606
|
# if running without request queueing
|
545
607
|
resp_info[:keep_alive] &&= @queue_requests
|
546
608
|
|
547
|
-
#
|
548
|
-
|
549
|
-
# This allows Puma to service connections fairly when the number
|
550
|
-
# of concurrent connections exceeds the size of the threadpool.
|
551
|
-
resp_info[:keep_alive] &&= requests < @max_fast_inline ||
|
552
|
-
@thread_pool.busy_threads < @max_threads ||
|
553
|
-
!client.listener.to_io.wait_readable(0)
|
609
|
+
# see prepare_response
|
610
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
554
611
|
|
555
612
|
resp_info[:response_hijack] = nil
|
556
613
|
|
@@ -560,7 +617,8 @@ module Puma
|
|
560
617
|
case k.downcase
|
561
618
|
when CONTENT_LENGTH2
|
562
619
|
next if illegal_header_value?(vs)
|
563
|
-
|
620
|
+
# nil.to_i is 0, nil&.to_i is nil
|
621
|
+
resp_info[:content_length] = vs&.to_i
|
564
622
|
next
|
565
623
|
when TRANSFER_ENCODING
|
566
624
|
resp_info[:allow_chunked] = false
|
data/lib/puma/runner.rb
CHANGED
@@ -182,6 +182,10 @@ module Puma
|
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
185
|
+
def utc_iso8601(val)
|
186
|
+
"#{val.utc.strftime '%FT%T'}Z"
|
187
|
+
end
|
188
|
+
|
185
189
|
def stats
|
186
190
|
{
|
187
191
|
versions: {
|
@@ -194,5 +198,12 @@ module Puma
|
|
194
198
|
}
|
195
199
|
}
|
196
200
|
end
|
201
|
+
|
202
|
+
# this method call should always be guarded by `@log_writer.debug?`
|
203
|
+
def debug_loaded_extensions(str)
|
204
|
+
@log_writer.debug "────────────────────────────────── #{str}"
|
205
|
+
re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
|
206
|
+
$LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
|
207
|
+
end
|
197
208
|
end
|
198
209
|
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
|
5
|
+
module Puma
|
6
|
+
# The MIT License
|
7
|
+
#
|
8
|
+
# Copyright (c) 2017-2022 Agis Anastasopoulos
|
9
|
+
#
|
10
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
11
|
+
# this software and associated documentation files (the "Software"), to deal in
|
12
|
+
# the Software without restriction, including without limitation the rights to
|
13
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
14
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
15
|
+
# subject to the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be included in all
|
18
|
+
# copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
22
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
23
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
24
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
25
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26
|
+
#
|
27
|
+
# This is a copy of https://github.com/agis/ruby-sdnotify as of commit cca575c
|
28
|
+
# The only changes made was "rehoming" it within the Puma module to avoid
|
29
|
+
# namespace collisions and applying standard's code formatting style.
|
30
|
+
#
|
31
|
+
# SdNotify is a pure-Ruby implementation of sd_notify(3). It can be used to
|
32
|
+
# notify systemd about state changes. Methods of this package are no-op on
|
33
|
+
# non-systemd systems (eg. Darwin).
|
34
|
+
#
|
35
|
+
# The API maps closely to the original implementation of sd_notify(3),
|
36
|
+
# therefore be sure to check the official man pages prior to using SdNotify.
|
37
|
+
#
|
38
|
+
# @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
39
|
+
module SdNotify
|
40
|
+
# Exception raised when there's an error writing to the notification socket
|
41
|
+
class NotifyError < RuntimeError; end
|
42
|
+
|
43
|
+
READY = "READY=1"
|
44
|
+
RELOADING = "RELOADING=1"
|
45
|
+
STOPPING = "STOPPING=1"
|
46
|
+
STATUS = "STATUS="
|
47
|
+
ERRNO = "ERRNO="
|
48
|
+
MAINPID = "MAINPID="
|
49
|
+
WATCHDOG = "WATCHDOG=1"
|
50
|
+
FDSTORE = "FDSTORE=1"
|
51
|
+
|
52
|
+
def self.ready(unset_env=false)
|
53
|
+
notify(READY, unset_env)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.reloading(unset_env=false)
|
57
|
+
notify(RELOADING, unset_env)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.stopping(unset_env=false)
|
61
|
+
notify(STOPPING, unset_env)
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param status [String] a custom status string that describes the current
|
65
|
+
# state of the service
|
66
|
+
def self.status(status, unset_env=false)
|
67
|
+
notify("#{STATUS}#{status}", unset_env)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param errno [Integer]
|
71
|
+
def self.errno(errno, unset_env=false)
|
72
|
+
notify("#{ERRNO}#{errno}", unset_env)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param pid [Integer]
|
76
|
+
def self.mainpid(pid, unset_env=false)
|
77
|
+
notify("#{MAINPID}#{pid}", unset_env)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.watchdog(unset_env=false)
|
81
|
+
notify(WATCHDOG, unset_env)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.fdstore(unset_env=false)
|
85
|
+
notify(FDSTORE, unset_env)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param [Boolean] true if the service manager expects watchdog keep-alive
|
89
|
+
# notification messages to be sent from this process.
|
90
|
+
#
|
91
|
+
# If the $WATCHDOG_USEC environment variable is set,
|
92
|
+
# and the $WATCHDOG_PID variable is unset or set to the PID of the current
|
93
|
+
# process
|
94
|
+
#
|
95
|
+
# @note Unlike sd_watchdog_enabled(3), this method does not mutate the
|
96
|
+
# environment.
|
97
|
+
def self.watchdog?
|
98
|
+
wd_usec = ENV["WATCHDOG_USEC"]
|
99
|
+
wd_pid = ENV["WATCHDOG_PID"]
|
100
|
+
|
101
|
+
return false if !wd_usec
|
102
|
+
|
103
|
+
begin
|
104
|
+
wd_usec = Integer(wd_usec)
|
105
|
+
rescue
|
106
|
+
return false
|
107
|
+
end
|
108
|
+
|
109
|
+
return false if wd_usec <= 0
|
110
|
+
return true if !wd_pid || wd_pid == $$.to_s
|
111
|
+
|
112
|
+
false
|
113
|
+
end
|
114
|
+
|
115
|
+
# Notify systemd with the provided state, via the notification socket, if
|
116
|
+
# any.
|
117
|
+
#
|
118
|
+
# Generally this method will be used indirectly through the other methods
|
119
|
+
# of the library.
|
120
|
+
#
|
121
|
+
# @param state [String]
|
122
|
+
# @param unset_env [Boolean]
|
123
|
+
#
|
124
|
+
# @return [Fixnum, nil] the number of bytes written to the notification
|
125
|
+
# socket or nil if there was no socket to report to (eg. the program wasn't
|
126
|
+
# started by systemd)
|
127
|
+
#
|
128
|
+
# @raise [NotifyError] if there was an error communicating with the systemd
|
129
|
+
# socket
|
130
|
+
#
|
131
|
+
# @see https://www.freedesktop.org/software/systemd/man/sd_notify.html
|
132
|
+
def self.notify(state, unset_env=false)
|
133
|
+
sock = ENV["NOTIFY_SOCKET"]
|
134
|
+
|
135
|
+
return nil if !sock
|
136
|
+
|
137
|
+
ENV.delete("NOTIFY_SOCKET") if unset_env
|
138
|
+
|
139
|
+
begin
|
140
|
+
Addrinfo.unix(sock, :DGRAM).connect do |s|
|
141
|
+
s.close_on_exec = true
|
142
|
+
s.write(state)
|
143
|
+
end
|
144
|
+
rescue StandardError => e
|
145
|
+
raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|