puma 6.0.2 → 6.4.2

Sign up to get free protection for your applications and to get access to all the features.
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