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.

Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +213 -7
  3. data/LICENSE +0 -0
  4. data/README.md +59 -13
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +0 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +0 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +12 -0
  17. data/docs/nginx.md +0 -0
  18. data/docs/plugins.md +0 -0
  19. data/docs/rails_dev_mode.md +0 -0
  20. data/docs/restart.md +1 -0
  21. data/docs/signals.md +0 -0
  22. data/docs/stats.md +0 -0
  23. data/docs/systemd.md +3 -6
  24. data/docs/testing_benchmarks_local_files.md +0 -0
  25. data/docs/testing_test_rackup_ci_files.md +0 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +5 -1
  29. data/ext/puma_http11/http11_parser.c +0 -0
  30. data/ext/puma_http11/http11_parser.h +0 -0
  31. data/ext/puma_http11/http11_parser.java.rl +0 -0
  32. data/ext/puma_http11/http11_parser.rl +0 -0
  33. data/ext/puma_http11/http11_parser_common.rl +0 -0
  34. data/ext/puma_http11/mini_ssl.c +96 -9
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
  39. data/ext/puma_http11/puma_http11.c +0 -0
  40. data/lib/puma/app/status.rb +1 -1
  41. data/lib/puma/binder.rb +14 -11
  42. data/lib/puma/cli.rb +5 -1
  43. data/lib/puma/client.rb +77 -16
  44. data/lib/puma/cluster/worker.rb +5 -0
  45. data/lib/puma/cluster/worker_handle.rb +0 -0
  46. data/lib/puma/cluster.rb +71 -10
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +6 -4
  49. data/lib/puma/const.rb +58 -9
  50. data/lib/puma/control_cli.rb +12 -5
  51. data/lib/puma/detect.rb +5 -4
  52. data/lib/puma/dsl.rb +157 -7
  53. data/lib/puma/error_logger.rb +2 -1
  54. data/lib/puma/events.rb +0 -0
  55. data/lib/puma/io_buffer.rb +0 -0
  56. data/lib/puma/jruby_restart.rb +0 -0
  57. data/lib/puma/json_serialization.rb +0 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +0 -0
  59. data/lib/puma/launcher.rb +9 -22
  60. data/lib/puma/log_writer.rb +14 -4
  61. data/lib/puma/minissl/context_builder.rb +3 -0
  62. data/lib/puma/minissl.rb +22 -0
  63. data/lib/puma/null_io.rb +16 -2
  64. data/lib/puma/plugin/systemd.rb +90 -0
  65. data/lib/puma/plugin/tmp_restart.rb +0 -0
  66. data/lib/puma/plugin.rb +0 -0
  67. data/lib/puma/rack/builder.rb +2 -2
  68. data/lib/puma/rack/urlmap.rb +1 -1
  69. data/lib/puma/rack_default.rb +18 -3
  70. data/lib/puma/reactor.rb +16 -7
  71. data/lib/puma/request.rb +91 -64
  72. data/lib/puma/runner.rb +13 -2
  73. data/lib/puma/sd_notify.rb +149 -0
  74. data/lib/puma/server.rb +91 -27
  75. data/lib/puma/single.rb +2 -0
  76. data/lib/puma/state_file.rb +2 -2
  77. data/lib/puma/thread_pool.rb +41 -3
  78. data/lib/puma/util.rb +0 -0
  79. data/lib/puma.rb +0 -0
  80. data/lib/rack/handler/puma.rb +113 -86
  81. data/tools/Dockerfile +2 -2
  82. data/tools/trickletest.rb +0 -0
  83. metadata +5 -4
  84. 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
- unless backend == :auto || NIO::Selector.backends.include?(backend)
26
- raise "unsupported IO selector backend: #{backend} (available backends: #{NIO::Selector.backends.join(', ')})"
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
- @selector = backend == :auto ? NIO::Selector.new : NIO::Selector.new(backend)
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
- retry
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
- fast_write_str socket, str_early_hints(headers)
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 SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
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
- # below converts app_body into body, dependent on app_body's characteristics, and
169
- # resp_info[:content_length] will be set if it can be determined
170
- if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
171
- if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary)
172
- body = array_body
173
- resp_info[:content_length] = body.sum(&:bytesize)
174
- elsif res_body.is_a?(File) && res_body.respond_to?(:size)
175
- body = res_body
176
- resp_info[:content_length] = body.size
177
- elsif res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
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
- resp_info[:content_length] = body.size
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
- body = res_body
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
- if content_length and status != 204
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 part = body.read(BODY_LEN_MAX)
299
- io_buffer.append part.bytesize.to_s(16), LINE_END, part, LINE_END
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
- fast_write_str socket, (byte_size.to_s(16) << LINE_END)
355
- fast_write_str socket, part
356
- fast_write_str socket, LINE_END
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 = URI.parse(env[REQUEST_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_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
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 = +"HTTP/1.1 103 Early Hints\r\n"
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
- else
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
- "#{eh_str}\r\n".freeze
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 aand nio4r is not loaded when 'queue_requests: false'
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
- control.binder.parse [str], nil, 'Starting control server'
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