puma 6.0.2 → 6.4.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +213 -7
- data/LICENSE +0 -0
- data/README.md +59 -13
- data/bin/puma-wild +0 -0
- data/docs/architecture.md +0 -0
- data/docs/compile_options.md +0 -0
- data/docs/deployment.md +0 -0
- data/docs/fork_worker.md +0 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +0 -0
- data/docs/plugins.md +0 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +1 -0
- data/docs/signals.md +0 -0
- data/docs/stats.md +0 -0
- data/docs/systemd.md +3 -6
- data/docs/testing_benchmarks_local_files.md +0 -0
- data/docs/testing_test_rackup_ci_files.md +0 -0
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +5 -1
- data/ext/puma_http11/http11_parser.c +0 -0
- data/ext/puma_http11/http11_parser.h +0 -0
- data/ext/puma_http11/http11_parser.java.rl +0 -0
- data/ext/puma_http11/http11_parser.rl +0 -0
- data/ext/puma_http11/http11_parser_common.rl +0 -0
- data/ext/puma_http11/mini_ssl.c +96 -9
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
- data/ext/puma_http11/puma_http11.c +0 -0
- data/lib/puma/app/status.rb +1 -1
- data/lib/puma/binder.rb +14 -11
- data/lib/puma/cli.rb +5 -1
- data/lib/puma/client.rb +77 -16
- data/lib/puma/cluster/worker.rb +5 -0
- data/lib/puma/cluster/worker_handle.rb +0 -0
- data/lib/puma/cluster.rb +71 -10
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +6 -4
- data/lib/puma/const.rb +58 -9
- data/lib/puma/control_cli.rb +12 -5
- data/lib/puma/detect.rb +5 -4
- data/lib/puma/dsl.rb +157 -7
- data/lib/puma/error_logger.rb +2 -1
- data/lib/puma/events.rb +0 -0
- data/lib/puma/io_buffer.rb +0 -0
- data/lib/puma/jruby_restart.rb +0 -0
- data/lib/puma/json_serialization.rb +0 -0
- data/lib/puma/launcher/bundle_pruner.rb +0 -0
- data/lib/puma/launcher.rb +9 -22
- data/lib/puma/log_writer.rb +14 -4
- data/lib/puma/minissl/context_builder.rb +3 -0
- data/lib/puma/minissl.rb +22 -0
- data/lib/puma/null_io.rb +16 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +0 -0
- data/lib/puma/rack/builder.rb +2 -2
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +18 -3
- data/lib/puma/reactor.rb +16 -7
- data/lib/puma/request.rb +91 -64
- data/lib/puma/runner.rb +13 -2
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +91 -27
- data/lib/puma/single.rb +2 -0
- data/lib/puma/state_file.rb +2 -2
- data/lib/puma/thread_pool.rb +41 -3
- data/lib/puma/util.rb +0 -0
- data/lib/puma.rb +0 -0
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +2 -2
- data/tools/trickletest.rb +0 -0
- metadata +5 -4
- data/lib/puma/systemd.rb +0 -47
data/lib/puma/reactor.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'queue_close' unless ::Queue.instance_methods.include? :close
|
4
|
-
|
5
3
|
module Puma
|
6
4
|
class UnsupportedBackend < StandardError; end
|
7
5
|
|
@@ -22,10 +20,12 @@ module Puma
|
|
22
20
|
# its timeout elapses, or when the Reactor shuts down.
|
23
21
|
def initialize(backend, &block)
|
24
22
|
require 'nio'
|
25
|
-
|
26
|
-
|
23
|
+
valid_backends = [:auto, *::NIO::Selector.backends]
|
24
|
+
unless valid_backends.include?(backend)
|
25
|
+
raise ArgumentError.new("unsupported IO selector backend: #{backend} (available backends: #{valid_backends.join(', ')})")
|
27
26
|
end
|
28
|
-
|
27
|
+
|
28
|
+
@selector = ::NIO::Selector.new(NIO::Selector.backends.delete(backend))
|
29
29
|
@input = Queue.new
|
30
30
|
@timeouts = []
|
31
31
|
@block = block
|
@@ -67,6 +67,7 @@ module Puma
|
|
67
67
|
private
|
68
68
|
|
69
69
|
def select_loop
|
70
|
+
close_selector = true
|
70
71
|
begin
|
71
72
|
until @input.closed? && @input.empty?
|
72
73
|
# Wakeup any registered object that receives incoming data.
|
@@ -89,11 +90,19 @@ module Puma
|
|
89
90
|
rescue StandardError => e
|
90
91
|
STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
|
91
92
|
STDERR.puts e.backtrace
|
92
|
-
|
93
|
+
|
94
|
+
# NoMethodError may be rarely raised when calling @selector.select, which
|
95
|
+
# is odd. Regardless, it may continue for thousands of calls if retried.
|
96
|
+
# Also, when it raises, @selector.close also raises an error.
|
97
|
+
if NoMethodError === e
|
98
|
+
close_selector = false
|
99
|
+
else
|
100
|
+
retry
|
101
|
+
end
|
93
102
|
end
|
94
103
|
# Wakeup all remaining objects on shutdown.
|
95
104
|
@timeouts.each(&@block)
|
96
|
-
@selector.close
|
105
|
+
@selector.close if close_selector
|
97
106
|
end
|
98
107
|
|
99
108
|
# Start monitoring the object.
|
data/lib/puma/request.rb
CHANGED
@@ -53,8 +53,13 @@ module Puma
|
|
53
53
|
socket = client.io # io may be a MiniSSL::Socket
|
54
54
|
app_body = nil
|
55
55
|
|
56
|
+
|
56
57
|
return false if closed_socket?(socket)
|
57
58
|
|
59
|
+
if client.http_content_length_limit_exceeded
|
60
|
+
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
61
|
+
end
|
62
|
+
|
58
63
|
normalize_env env, client
|
59
64
|
|
60
65
|
env[PUMA_SOCKET] = socket
|
@@ -72,7 +77,9 @@ module Puma
|
|
72
77
|
if @early_hints
|
73
78
|
env[EARLY_HINTS] = lambda { |headers|
|
74
79
|
begin
|
75
|
-
|
80
|
+
unless (str = str_early_hints headers).empty?
|
81
|
+
fast_write_str socket, "HTTP/1.1 103 Early Hints\r\n#{str}\r\n"
|
82
|
+
end
|
76
83
|
rescue ConnectionError => e
|
77
84
|
@log_writer.debug_error e
|
78
85
|
# noop, if we lost the socket we just won't send the early hints
|
@@ -88,7 +95,7 @@ module Puma
|
|
88
95
|
env[RACK_AFTER_REPLY] ||= []
|
89
96
|
|
90
97
|
begin
|
91
|
-
if
|
98
|
+
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
|
92
99
|
status, headers, app_body = @thread_pool.with_force_shutdown do
|
93
100
|
@app.call(env)
|
94
101
|
end
|
@@ -101,6 +108,7 @@ module Puma
|
|
101
108
|
# is called
|
102
109
|
res_body = app_body
|
103
110
|
|
111
|
+
# full hijack, app called env['rack.hijack']
|
104
112
|
return :async if client.hijacked
|
105
113
|
|
106
114
|
status = status.to_i
|
@@ -164,78 +172,87 @@ module Puma
|
|
164
172
|
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
165
173
|
|
166
174
|
close_body = false
|
175
|
+
response_hijack = nil
|
176
|
+
content_length = resp_info[:content_length]
|
177
|
+
keep_alive = resp_info[:keep_alive]
|
167
178
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
if
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
179
|
+
if res_body.respond_to?(:each) && !resp_info[:response_hijack]
|
180
|
+
# below converts app_body into body, dependent on app_body's characteristics, and
|
181
|
+
# content_length will be set if it can be determined
|
182
|
+
if !content_length && !resp_info[:transfer_encoding] && status != 204
|
183
|
+
if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
|
184
|
+
array_body.is_a?(Array)
|
185
|
+
body = array_body.compact
|
186
|
+
content_length = body.sum(&:bytesize)
|
187
|
+
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
188
|
+
body = res_body
|
189
|
+
content_length = body.size
|
190
|
+
elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
|
191
|
+
body = File.open fn, 'rb'
|
192
|
+
content_length = body.size
|
193
|
+
close_body = true
|
194
|
+
else
|
195
|
+
body = res_body
|
196
|
+
end
|
197
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
|
178
198
|
File.readable?(fn = res_body.to_path)
|
179
199
|
body = File.open fn, 'rb'
|
180
|
-
|
200
|
+
content_length = body.size
|
181
201
|
close_body = true
|
202
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
|
203
|
+
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
204
|
+
# Sprockets::Asset
|
205
|
+
content_length = res_body.bytesize unless content_length
|
206
|
+
if (body_str = res_body.to_hash[:source])
|
207
|
+
body = [body_str]
|
208
|
+
else # avoid each and use a File object
|
209
|
+
body = File.open fn, 'rb'
|
210
|
+
close_body = true
|
211
|
+
end
|
182
212
|
else
|
183
213
|
body = res_body
|
184
214
|
end
|
185
|
-
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
|
186
|
-
File.readable?(fn = res_body.to_path)
|
187
|
-
body = File.open fn, 'rb'
|
188
|
-
resp_info[:content_length] = body.size
|
189
|
-
close_body = true
|
190
|
-
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) && res_body.respond_to?(:each) &&
|
191
|
-
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
192
|
-
# Sprockets::Asset
|
193
|
-
resp_info[:content_length] = res_body.bytesize unless resp_info[:content_length]
|
194
|
-
if res_body.to_hash[:source] # use each to return @source
|
195
|
-
body = res_body
|
196
|
-
else # avoid each and use a File object
|
197
|
-
body = File.open fn, 'rb'
|
198
|
-
close_body = true
|
199
|
-
end
|
200
215
|
else
|
201
|
-
|
216
|
+
# partial hijack, from Rack spec:
|
217
|
+
# Servers must ignore the body part of the response tuple when the
|
218
|
+
# rack.hijack response header is present.
|
219
|
+
response_hijack = resp_info[:response_hijack] || res_body
|
202
220
|
end
|
203
221
|
|
204
222
|
line_ending = LINE_END
|
205
223
|
|
206
|
-
content_length = resp_info[:content_length]
|
207
|
-
keep_alive = resp_info[:keep_alive]
|
208
|
-
|
209
|
-
if res_body && !res_body.respond_to?(:each)
|
210
|
-
response_hijack = res_body
|
211
|
-
else
|
212
|
-
response_hijack = resp_info[:response_hijack]
|
213
|
-
end
|
214
|
-
|
215
224
|
cork_socket socket
|
216
225
|
|
217
226
|
if resp_info[:no_body]
|
218
|
-
|
227
|
+
# 101 (Switching Protocols) doesn't return here or have content_length,
|
228
|
+
# it should be using `response_hijack`
|
229
|
+
unless status == 101
|
230
|
+
if content_length && status != 204
|
231
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
232
|
+
end
|
233
|
+
|
234
|
+
io_buffer << LINE_END
|
235
|
+
fast_write_str socket, io_buffer.read_and_reset
|
236
|
+
socket.flush
|
237
|
+
return keep_alive
|
238
|
+
end
|
239
|
+
else
|
240
|
+
if content_length
|
219
241
|
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
242
|
+
chunked = false
|
243
|
+
elsif !response_hijack && resp_info[:allow_chunked]
|
244
|
+
io_buffer << TRANSFER_ENCODING_CHUNKED
|
245
|
+
chunked = true
|
220
246
|
end
|
221
|
-
|
222
|
-
io_buffer << LINE_END
|
223
|
-
fast_write_str socket, io_buffer.read_and_reset
|
224
|
-
socket.flush
|
225
|
-
return keep_alive
|
226
|
-
end
|
227
|
-
if content_length
|
228
|
-
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
229
|
-
chunked = false
|
230
|
-
elsif !response_hijack and resp_info[:allow_chunked]
|
231
|
-
io_buffer << TRANSFER_ENCODING_CHUNKED
|
232
|
-
chunked = true
|
233
247
|
end
|
234
248
|
|
235
249
|
io_buffer << line_ending
|
236
250
|
|
251
|
+
# partial hijack, we write headers, then hand the socket to the app via
|
252
|
+
# response_hijack.call
|
237
253
|
if response_hijack
|
238
254
|
fast_write_str socket, io_buffer.read_and_reset
|
255
|
+
uncork_socket socket
|
239
256
|
response_hijack.call socket
|
240
257
|
return :async
|
241
258
|
end
|
@@ -295,8 +312,8 @@ module Puma
|
|
295
312
|
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
296
313
|
if body.is_a?(::File) && body.respond_to?(:read)
|
297
314
|
if chunked # would this ever happen?
|
298
|
-
while
|
299
|
-
io_buffer.append
|
315
|
+
while chunk = body.read(BODY_LEN_MAX)
|
316
|
+
io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
|
300
317
|
end
|
301
318
|
fast_write_str socket, CLOSE_CHUNKED
|
302
319
|
else
|
@@ -347,16 +364,22 @@ module Puma
|
|
347
364
|
fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
|
348
365
|
else
|
349
366
|
# for enum bodies
|
350
|
-
fast_write_str socket, io_buffer.read_and_reset
|
351
367
|
if chunked
|
368
|
+
empty_body = true
|
352
369
|
body.each do |part|
|
353
|
-
next if (byte_size = part.bytesize).zero?
|
354
|
-
|
355
|
-
|
356
|
-
|
370
|
+
next if part.nil? || (byte_size = part.bytesize).zero?
|
371
|
+
empty_body = false
|
372
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
373
|
+
fast_write_str socket, io_buffer.read_and_reset
|
374
|
+
end
|
375
|
+
if empty_body
|
376
|
+
io_buffer << CLOSE_CHUNKED
|
377
|
+
fast_write_str socket, io_buffer.read_and_reset
|
378
|
+
else
|
379
|
+
fast_write_str socket, CLOSE_CHUNKED
|
357
380
|
end
|
358
|
-
fast_write_str socket, CLOSE_CHUNKED
|
359
381
|
else
|
382
|
+
fast_write_str socket, io_buffer.read_and_reset
|
360
383
|
body.each do |part|
|
361
384
|
next if part.bytesize.zero?
|
362
385
|
fast_write_str socket, part
|
@@ -397,7 +420,11 @@ module Puma
|
|
397
420
|
|
398
421
|
unless env[REQUEST_PATH]
|
399
422
|
# it might be a dumbass full host request header
|
400
|
-
uri =
|
423
|
+
uri = begin
|
424
|
+
URI.parse(env[REQUEST_URI])
|
425
|
+
rescue URI::InvalidURIError
|
426
|
+
raise Puma::HttpParserError
|
427
|
+
end
|
401
428
|
env[REQUEST_PATH] = uri.path
|
402
429
|
|
403
430
|
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
@@ -476,7 +503,7 @@ module Puma
|
|
476
503
|
to_add = nil
|
477
504
|
|
478
505
|
env.each do |k,v|
|
479
|
-
if k.start_with?("HTTP_")
|
506
|
+
if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
|
480
507
|
if to_delete
|
481
508
|
to_delete << k
|
482
509
|
else
|
@@ -504,7 +531,7 @@ module Puma
|
|
504
531
|
# @version 5.0.3
|
505
532
|
#
|
506
533
|
def str_early_hints(headers)
|
507
|
-
eh_str = +"
|
534
|
+
eh_str = +""
|
508
535
|
headers.each_pair do |k, vs|
|
509
536
|
next if illegal_header_key?(k)
|
510
537
|
|
@@ -513,11 +540,11 @@ module Puma
|
|
513
540
|
next if illegal_header_value?(v)
|
514
541
|
eh_str << "#{k}: #{v}\r\n"
|
515
542
|
end
|
516
|
-
|
543
|
+
elsif !(vs.to_s.empty? || !illegal_header_value?(vs))
|
517
544
|
eh_str << "#{k}: #{vs}\r\n"
|
518
545
|
end
|
519
546
|
end
|
520
|
-
|
547
|
+
eh_str.freeze
|
521
548
|
end
|
522
549
|
private :str_early_hints
|
523
550
|
|
data/lib/puma/runner.rb
CHANGED
@@ -70,12 +70,16 @@ module Puma
|
|
70
70
|
|
71
71
|
app = Puma::App::Status.new @launcher, token
|
72
72
|
|
73
|
-
# A Reactor is not created
|
73
|
+
# A Reactor is not created and nio4r is not loaded when 'queue_requests: false'
|
74
74
|
# Use `nil` for events, no hooks in control server
|
75
75
|
control = Puma::Server.new app, nil,
|
76
76
|
{ min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
|
77
77
|
|
78
|
-
|
78
|
+
begin
|
79
|
+
control.binder.parse [str], nil, 'Starting control server'
|
80
|
+
rescue Errno::EADDRINUSE, Errno::EACCES => e
|
81
|
+
raise e, "Error: Control server address '#{str}' is already in use. Original error: #{e.message}"
|
82
|
+
end
|
79
83
|
|
80
84
|
control.run thread_name: 'ctl'
|
81
85
|
@control = control
|
@@ -198,5 +202,12 @@ module Puma
|
|
198
202
|
}
|
199
203
|
}
|
200
204
|
end
|
205
|
+
|
206
|
+
# this method call should always be guarded by `@log_writer.debug?`
|
207
|
+
def debug_loaded_extensions(str)
|
208
|
+
@log_writer.debug "────────────────────────────────── #{str}"
|
209
|
+
re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
|
210
|
+
$LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
|
211
|
+
end
|
201
212
|
end
|
202
213
|
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
|