sidekiq_alive 2.3.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +21 -11
- data/lib/sidekiq_alive/config.rb +9 -3
- data/lib/sidekiq_alive/helpers.rb +26 -0
- data/lib/sidekiq_alive/redis/redis_client_gem.rb +3 -3
- data/lib/sidekiq_alive/redis.rb +2 -2
- data/lib/sidekiq_alive/server/base.rb +52 -0
- data/lib/sidekiq_alive/server/default.rb +79 -0
- data/lib/sidekiq_alive/server/http_server.rb +140 -0
- data/lib/sidekiq_alive/server/rack.rb +67 -0
- data/lib/sidekiq_alive/server.rb +13 -28
- data/lib/sidekiq_alive/version.rb +1 -1
- data/lib/sidekiq_alive/worker.rb +13 -1
- data/lib/sidekiq_alive.rb +19 -21
- metadata +33 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 785267eeba9c94f17b29d9df50a17aaa787bfa32b321dc3c097c5d375fc68ef7
|
4
|
+
data.tar.gz: ace6c8c8b8285b3a4963db034a7ab439594adb4123db4957829ca81b3677c07b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51c04d062e6e2bdbdb6bdbcd803024d642847c3a4fe46df673790cc76b1ce1916aae2f90069bc00fd727242b5956ae9646fb52fa227869a08065099a8a09cd95
|
7
|
+
data.tar.gz: 3afc2cd02f1b64e68e99e06a556c9c8aaafff5f717f9fd91370b70fb239834bfcf3626819e6e19a054e50ba29bf977503848e4a02aa306727c1b68b79c391815
|
data/README.md
CHANGED
@@ -247,12 +247,10 @@ SidekiqAlive.setup do |config|
|
|
247
247
|
# config.callback = proc { Net::HTTP.get("https://status.com/ping") }
|
248
248
|
|
249
249
|
# ==> Shutdown callback
|
250
|
-
# When sidekiq process is shutting down, you can perform some action
|
250
|
+
# When sidekiq process is shutting down, you can perform some arbitrary action.
|
251
251
|
# default: proc {}
|
252
252
|
#
|
253
|
-
# config.shutdown_callback = proc
|
254
|
-
# Sidekiq::Queue.all.find { |q| q.name == "#{config.queue_prefix}-#{SidekiqAlive.hostname}" }&.clear
|
255
|
-
# end
|
253
|
+
# config.shutdown_callback = proc { puts "Sidekiq is shutting down" }
|
256
254
|
|
257
255
|
# ==> Queue Prefix
|
258
256
|
# SidekiqAlive will run in a independent queue for each instance/replica
|
@@ -262,13 +260,6 @@ SidekiqAlive.setup do |config|
|
|
262
260
|
#
|
263
261
|
# config.queue_prefix = :other
|
264
262
|
|
265
|
-
# ==> Rack server
|
266
|
-
# Web server used to serve an HTTP response.
|
267
|
-
# Can also be set with the environment variable SIDEKIQ_ALIVE_SERVER.
|
268
|
-
# default: 'webrick'
|
269
|
-
#
|
270
|
-
# config.server = 'puma'
|
271
|
-
|
272
263
|
# ==> Concurrency
|
273
264
|
# The maximum number of Redis connections requested for the SidekiqAlive pool.
|
274
265
|
# Can also be set with the environment variable SIDEKIQ_ALIVE_CONCURRENCY.
|
@@ -276,6 +267,25 @@ SidekiqAlive.setup do |config|
|
|
276
267
|
# default: 2
|
277
268
|
#
|
278
269
|
# config.concurrency = 3
|
270
|
+
|
271
|
+
# ==> Rack server
|
272
|
+
# Web server used to serve an HTTP response. By default simple GServer based http server is used.
|
273
|
+
# To use specific server, rack gem version > 2 is required. For rack version >= 3, rackup gem is required.
|
274
|
+
# Can also be set with the environment variable SIDEKIQ_ALIVE_SERVER.
|
275
|
+
# default: nil
|
276
|
+
#
|
277
|
+
# config.server = 'puma'
|
278
|
+
|
279
|
+
# ==> Quiet mode timeout in seconds
|
280
|
+
# When sidekiq is shutting down, the Sidekiq process stops pulling jobs from the queue. This includes alive key update job. In case of
|
281
|
+
# long running jobs, alive key can expire before the job is finished. To avoid this, web server is set in to quiet mode
|
282
|
+
# and is returning 200 OK for healthcheck requests. To avoid infinite quiet mode in case sidekiq process is stuck in shutdown,
|
283
|
+
# timeout can be set. After timeout is reached, web server resumes normal operations and will return unhealthy status in case
|
284
|
+
# alive key is expired or purged from redis.
|
285
|
+
# default: 180
|
286
|
+
#
|
287
|
+
# config.quiet_timeout = 300
|
288
|
+
|
279
289
|
end
|
280
290
|
```
|
281
291
|
|
data/lib/sidekiq_alive/config.rb
CHANGED
@@ -12,11 +12,12 @@ module SidekiqAlive
|
|
12
12
|
:callback,
|
13
13
|
:registered_instance_key,
|
14
14
|
:queue_prefix,
|
15
|
-
:server,
|
16
15
|
:custom_liveness_probe,
|
17
16
|
:logger,
|
18
17
|
:shutdown_callback,
|
19
|
-
:concurrency
|
18
|
+
:concurrency,
|
19
|
+
:server,
|
20
|
+
:quiet_timeout
|
20
21
|
|
21
22
|
def initialize
|
22
23
|
set_defaults
|
@@ -31,14 +32,19 @@ module SidekiqAlive
|
|
31
32
|
@callback = proc {}
|
32
33
|
@registered_instance_key = "SIDEKIQ_REGISTERED_INSTANCE"
|
33
34
|
@queue_prefix = :"sidekiq-alive"
|
34
|
-
@server = ENV.fetch("SIDEKIQ_ALIVE_SERVER", "webrick")
|
35
35
|
@custom_liveness_probe = proc { true }
|
36
36
|
@shutdown_callback = proc {}
|
37
37
|
@concurrency = Integer(ENV.fetch("SIDEKIQ_ALIVE_CONCURRENCY", 2), exception: false) || 2
|
38
|
+
@server = ENV.fetch("SIDEKIQ_ALIVE_SERVER", nil)
|
39
|
+
@quiet_timeout = Integer(ENV.fetch("SIDEKIQ_ALIVE_QUIET_TIMEOUT", 180), exception: false) || 180
|
38
40
|
end
|
39
41
|
|
40
42
|
def registration_ttl
|
41
43
|
@registration_ttl || time_to_live * 3
|
42
44
|
end
|
45
|
+
|
46
|
+
def worker_interval
|
47
|
+
time_to_live / 2
|
48
|
+
end
|
43
49
|
end
|
44
50
|
end
|
@@ -17,11 +17,37 @@ module SidekiqAlive
|
|
17
17
|
current_sidekiq_version < Gem::Version.new("6")
|
18
18
|
end
|
19
19
|
|
20
|
+
def use_rack?
|
21
|
+
return @use_rack if defined?(@use_rack)
|
22
|
+
|
23
|
+
require "rack"
|
24
|
+
@use_rack = current_rack_version < Gem::Version.new("3")
|
25
|
+
rescue LoadError
|
26
|
+
# currently this won't happen because rack is a dependency of sidekiq
|
27
|
+
@use_rack = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def use_rackup?
|
31
|
+
return @use_rackup if defined?(@use_rackup)
|
32
|
+
|
33
|
+
require "rackup"
|
34
|
+
@use_rackup = current_rack_version >= Gem::Version.new("3")
|
35
|
+
rescue LoadError
|
36
|
+
if current_rack_version >= Gem::Version.new("3")
|
37
|
+
SidekiqAlive.logger.warn("rackup gem required with rack >= 3, defaulting to default server")
|
38
|
+
end
|
39
|
+
@use_rackup = false
|
40
|
+
end
|
41
|
+
|
20
42
|
private
|
21
43
|
|
22
44
|
def current_sidekiq_version
|
23
45
|
Gem.loaded_specs["sidekiq"].version
|
24
46
|
end
|
47
|
+
|
48
|
+
def current_rack_version
|
49
|
+
Gem.loaded_specs["rack"].version
|
50
|
+
end
|
25
51
|
end
|
26
52
|
end
|
27
53
|
end
|
@@ -7,10 +7,10 @@ module SidekiqAlive
|
|
7
7
|
# Wrapper for `redis-client` gem used by `sidekiq` > 7
|
8
8
|
# https://github.com/redis-rb/redis-client
|
9
9
|
class RedisClientGem < Base
|
10
|
-
def initialize
|
11
|
-
super
|
10
|
+
def initialize(capsule = nil)
|
11
|
+
super()
|
12
12
|
|
13
|
-
@capsule = Sidekiq.default_configuration.capsules[CAPSULE_NAME]
|
13
|
+
@capsule = Sidekiq.default_configuration.capsules[capsule || CAPSULE_NAME]
|
14
14
|
end
|
15
15
|
|
16
16
|
def set(key, time:, ex:)
|
data/lib/sidekiq_alive/redis.rb
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
module SidekiqAlive
|
4
4
|
module Redis
|
5
5
|
class << self
|
6
|
-
def adapter
|
7
|
-
Helpers.sidekiq_7 ? Redis::RedisClientGem.new : Redis::RedisGem.new
|
6
|
+
def adapter(capsule = nil)
|
7
|
+
Helpers.sidekiq_7 ? Redis::RedisClientGem.new(capsule) : Redis::RedisGem.new
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SidekiqAlive
|
4
|
+
module Server
|
5
|
+
module Base
|
6
|
+
SHUTDOWN_SIGNAL = "TERM"
|
7
|
+
QUIET_SIGNAL = "TSTP"
|
8
|
+
|
9
|
+
# set web server to quiet mode
|
10
|
+
def quiet!
|
11
|
+
logger.info("[SidekiqAlive] Setting web server to quiet mode")
|
12
|
+
Process.kill(QUIET_SIGNAL, @server_pid) unless @server_pid.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def configure_shutdown
|
18
|
+
Kernel.at_exit do
|
19
|
+
next if @server_pid.nil?
|
20
|
+
|
21
|
+
logger.info("Shutting down SidekiqAlive web server")
|
22
|
+
Process.kill(SHUTDOWN_SIGNAL, @server_pid)
|
23
|
+
Process.wait(@server_pid)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def configure_shutdown_signal(&block)
|
28
|
+
Signal.trap(SHUTDOWN_SIGNAL, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def configure_quiet_signal(&block)
|
32
|
+
Signal.trap(QUIET_SIGNAL, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def host
|
36
|
+
SidekiqAlive.config.host
|
37
|
+
end
|
38
|
+
|
39
|
+
def port
|
40
|
+
SidekiqAlive.config.port.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def path
|
44
|
+
SidekiqAlive.config.path
|
45
|
+
end
|
46
|
+
|
47
|
+
def logger
|
48
|
+
SidekiqAlive.logger
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "http_server"
|
4
|
+
require_relative "base"
|
5
|
+
|
6
|
+
module SidekiqAlive
|
7
|
+
module Server
|
8
|
+
class Default < HttpServer
|
9
|
+
extend Base
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def run!
|
13
|
+
logger.info("[SidekiqAlive] Starting default healthcheck server on #{host}:#{port}")
|
14
|
+
@server_pid = ::Process.fork do
|
15
|
+
@server = new(port, host, path)
|
16
|
+
# stop is wrapped in a thread because gserver calls synchrnonize which raises an error when in trap context
|
17
|
+
configure_shutdown_signal { Thread.new { @server.stop } }
|
18
|
+
configure_quiet_signal { @server.quiet! }
|
19
|
+
|
20
|
+
@server.start
|
21
|
+
@server.join
|
22
|
+
end
|
23
|
+
configure_shutdown
|
24
|
+
logger.info("[SidekiqAlive] Web server started in subprocess with pid #{@server_pid}")
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(port, host, path, logger = SidekiqAlive.logger)
|
31
|
+
super(self, port, host, logger)
|
32
|
+
|
33
|
+
@path = path
|
34
|
+
end
|
35
|
+
|
36
|
+
def request_handler(req, res)
|
37
|
+
if req.path != path
|
38
|
+
res.status = 404
|
39
|
+
res.body = "Not found"
|
40
|
+
return logger.warn("[SidekiqAlive] Path '#{req.path}' not found")
|
41
|
+
end
|
42
|
+
|
43
|
+
if quiet?
|
44
|
+
res.status = 200
|
45
|
+
res.body = "Server is shutting down"
|
46
|
+
return logger.debug("[SidekiqAlive] Server in quiet mode, skipping alive key lookup!")
|
47
|
+
end
|
48
|
+
|
49
|
+
if SidekiqAlive.alive?
|
50
|
+
res.status = 200
|
51
|
+
res.body = "Alive!"
|
52
|
+
return logger.debug("[SidekiqAlive] Found alive key!")
|
53
|
+
end
|
54
|
+
|
55
|
+
response = "Can't find the alive key"
|
56
|
+
res.status = 404
|
57
|
+
res.body = response
|
58
|
+
logger.error("[SidekiqAlive] #{response}")
|
59
|
+
rescue StandardError => e
|
60
|
+
response = "Internal Server Error"
|
61
|
+
res.status = 500
|
62
|
+
res.body = response
|
63
|
+
logger.error("[SidekiqAlive] #{response} looking for alive key. Error: #{e.message}")
|
64
|
+
end
|
65
|
+
|
66
|
+
def quiet!
|
67
|
+
@quiet = Time.now
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
attr_reader :path
|
73
|
+
|
74
|
+
def quiet?
|
75
|
+
@quiet && (Time.now - @quiet) < SidekiqAlive.config.quiet_timeout
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "gserver"
|
4
|
+
|
5
|
+
module SidekiqAlive
|
6
|
+
module Server
|
7
|
+
# Simple HTTP server implementation
|
8
|
+
#
|
9
|
+
class HttpServer < GServer
|
10
|
+
# Request class for HTTP server
|
11
|
+
#
|
12
|
+
class Request
|
13
|
+
attr_reader :data, :header, :method, :path, :proto
|
14
|
+
|
15
|
+
def initialize(data, method = nil, path = nil, proto = nil)
|
16
|
+
@header = {}
|
17
|
+
@data = data
|
18
|
+
@method = method
|
19
|
+
@path = path
|
20
|
+
@proto = proto
|
21
|
+
end
|
22
|
+
|
23
|
+
def content_length
|
24
|
+
len = @header["Content-Length"]
|
25
|
+
return if len.nil?
|
26
|
+
|
27
|
+
len.to_i
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Response class for HTTP server
|
32
|
+
#
|
33
|
+
class Response
|
34
|
+
attr_reader :header
|
35
|
+
attr_accessor :body, :status, :status_message
|
36
|
+
|
37
|
+
def initialize(status = 200)
|
38
|
+
@status = status
|
39
|
+
@status_message = nil
|
40
|
+
@header = {}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize(handle_obj, port, host, logger = Logger.new($stdout))
|
45
|
+
@handler = handle_obj
|
46
|
+
@logger = logger
|
47
|
+
|
48
|
+
super(port, host, 1, nil, logger.debug?, logger.debug?)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :handler, :logger
|
54
|
+
|
55
|
+
CRLF = "\r\n"
|
56
|
+
HTTP_PROTO = "HTTP/1.1"
|
57
|
+
SERVER_NAME = "SidekiqAlive/#{SidekiqAlive::VERSION} (Ruby/#{RUBY_VERSION})"
|
58
|
+
|
59
|
+
# Default header for the server name
|
60
|
+
DEFAULT_HEADER = {
|
61
|
+
"Server" => SERVER_NAME,
|
62
|
+
}
|
63
|
+
|
64
|
+
# Mapping of status codes and error messages
|
65
|
+
STATUS_CODE_MAPPING = {
|
66
|
+
200 => "OK",
|
67
|
+
400 => "Bad Request",
|
68
|
+
403 => "Forbidden",
|
69
|
+
404 => "Not Found",
|
70
|
+
405 => "Method Not Allowed",
|
71
|
+
411 => "Length Required",
|
72
|
+
500 => "Internal Server Error",
|
73
|
+
}
|
74
|
+
|
75
|
+
def serve(io)
|
76
|
+
# parse first line
|
77
|
+
if io.gets =~ /^(\S+)\s+(\S+)\s+(\S+)/
|
78
|
+
request = Request.new(io, ::Regexp.last_match(1), ::Regexp.last_match(2), ::Regexp.last_match(3))
|
79
|
+
else
|
80
|
+
io << http_resp(status_code: 400)
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
# parse HTTP headers
|
85
|
+
while (line = io.gets) !~ /^(\n|\r)/
|
86
|
+
if line =~ /^([\w-]+):\s*(.*)$/
|
87
|
+
request.header[::Regexp.last_match(1)] = ::Regexp.last_match(2).strip
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
io.binmode
|
92
|
+
response = Response.new
|
93
|
+
|
94
|
+
# execute request handler
|
95
|
+
handler.request_handler(request, response)
|
96
|
+
|
97
|
+
http_response = http_resp(
|
98
|
+
status_code: response.status,
|
99
|
+
status_message: response.status_message,
|
100
|
+
header: response.header,
|
101
|
+
body: response.body,
|
102
|
+
)
|
103
|
+
|
104
|
+
# write response back to the client
|
105
|
+
io << http_response
|
106
|
+
rescue StandardError
|
107
|
+
io << http_resp(status_code: 500)
|
108
|
+
end
|
109
|
+
|
110
|
+
def http_header(header = nil)
|
111
|
+
new_header = DEFAULT_HEADER.dup
|
112
|
+
new_header.merge(header) unless header.nil?
|
113
|
+
|
114
|
+
new_header["Connection"] = "Keep-Alive"
|
115
|
+
new_header["Date"] = http_date(Time.now)
|
116
|
+
|
117
|
+
new_header
|
118
|
+
end
|
119
|
+
|
120
|
+
def http_resp(status_code:, status_message: nil, header: nil, body: nil)
|
121
|
+
status_message ||= STATUS_CODE_MAPPING[status_code]
|
122
|
+
status_line = "#{HTTP_PROTO} #{status_code} #{status_message}".rstrip + CRLF
|
123
|
+
|
124
|
+
resp_header = http_header(header)
|
125
|
+
resp_header["Content-Length"] = body.bytesize.to_s unless body.nil?
|
126
|
+
header_lines = resp_header.map { |k, v| "#{k}: #{v}#{CRLF}" }.join
|
127
|
+
|
128
|
+
[status_line, header_lines, CRLF, body].compact.join
|
129
|
+
end
|
130
|
+
|
131
|
+
def http_date(a_time)
|
132
|
+
a_time.gmtime.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
133
|
+
end
|
134
|
+
|
135
|
+
def log(msg)
|
136
|
+
logger.debug(msg)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module SidekiqAlive
|
6
|
+
module Server
|
7
|
+
class Rack
|
8
|
+
extend Base
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def run!
|
12
|
+
logger.info("[SidekiqAlive] Starting healthcheck '#{server}' server")
|
13
|
+
@server_pid = ::Process.fork do
|
14
|
+
@handler = handler
|
15
|
+
configure_shutdown_signal { @handler.shutdown }
|
16
|
+
configure_quiet_signal { @quiet = Time.now }
|
17
|
+
|
18
|
+
@handler.run(self, Port: port, Host: host, AccessLog: [], Logger: logger)
|
19
|
+
end
|
20
|
+
configure_shutdown
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def call(env)
|
26
|
+
req = ::Rack::Request.new(env)
|
27
|
+
|
28
|
+
if req.path != path
|
29
|
+
logger.warn("[SidekiqAlive] Path '#{req.path}' not found")
|
30
|
+
return [404, {}, ["Not found"]]
|
31
|
+
end
|
32
|
+
|
33
|
+
if quiet?
|
34
|
+
logger.debug("[SidekiqAlive] [SidekiqAlive] Server in quiet mode, skipping alive key lookup!")
|
35
|
+
return [200, {}, ["Server is shutting down"]]
|
36
|
+
end
|
37
|
+
|
38
|
+
if SidekiqAlive.alive?
|
39
|
+
logger.debug("[SidekiqAlive] Found alive key!")
|
40
|
+
return [200, {}, ["Alive!"]]
|
41
|
+
end
|
42
|
+
|
43
|
+
response = "Can't find the alive key"
|
44
|
+
logger.error("[SidekiqAlive] #{response}")
|
45
|
+
[404, {}, [response]]
|
46
|
+
rescue StandardError => e
|
47
|
+
logger.error("[SidekiqAlive] #{response} looking for alive key. Error: #{e.message}")
|
48
|
+
[500, {}, ["Internal Server Error"]]
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def quiet?
|
54
|
+
@quiet && (Time.now - @quiet) < SidekiqAlive.config.quiet_timeout
|
55
|
+
end
|
56
|
+
|
57
|
+
def handler
|
58
|
+
Helpers.use_rackup? ? ::Rackup::Handler.get(server) : ::Rack::Handler.get(server)
|
59
|
+
end
|
60
|
+
|
61
|
+
def server
|
62
|
+
SidekiqAlive.config.server
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/sidekiq_alive/server.rb
CHANGED
@@ -1,45 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rack"
|
4
|
-
|
5
3
|
module SidekiqAlive
|
6
|
-
|
4
|
+
module Server
|
7
5
|
class << self
|
8
6
|
def run!
|
9
|
-
|
10
|
-
|
11
|
-
Signal.trap("TERM") { handler.shutdown }
|
12
|
-
|
13
|
-
handler.run(self, Port: port, Host: host, AccessLog: [], Logger: SidekiqAlive.logger)
|
7
|
+
server.run!
|
14
8
|
end
|
15
9
|
|
16
|
-
|
17
|
-
SidekiqAlive.config.host
|
18
|
-
end
|
10
|
+
private
|
19
11
|
|
20
|
-
def
|
21
|
-
|
12
|
+
def server
|
13
|
+
use_rack? ? Rack : Default
|
22
14
|
end
|
23
15
|
|
24
|
-
def
|
25
|
-
SidekiqAlive.config.
|
26
|
-
end
|
16
|
+
def use_rack?
|
17
|
+
return false unless SidekiqAlive.config.server
|
27
18
|
|
28
|
-
|
29
|
-
SidekiqAlive.config.server
|
19
|
+
Helpers.use_rackup? || Helpers.use_rack?
|
30
20
|
end
|
31
21
|
|
32
|
-
def
|
33
|
-
|
34
|
-
[404, {}, ["Not found"]]
|
35
|
-
elsif SidekiqAlive.alive?
|
36
|
-
[200, {}, ["Alive!"]]
|
37
|
-
else
|
38
|
-
response = "Can't find the alive key"
|
39
|
-
SidekiqAlive.logger.error(response)
|
40
|
-
[404, {}, [response]]
|
41
|
-
end
|
22
|
+
def logger
|
23
|
+
SidekiqAlive.logger
|
42
24
|
end
|
43
25
|
end
|
44
26
|
end
|
45
27
|
end
|
28
|
+
|
29
|
+
require_relative "server/default"
|
30
|
+
require_relative "server/rack"
|
data/lib/sidekiq_alive/worker.rb
CHANGED
@@ -12,8 +12,9 @@ module SidekiqAlive
|
|
12
12
|
|
13
13
|
# Writes the liveness in Redis
|
14
14
|
write_living_probe
|
15
|
+
remove_orphaned_queues
|
15
16
|
# schedules next living probe
|
16
|
-
self.class.perform_in(config.
|
17
|
+
self.class.perform_in(config.worker_interval, current_hostname)
|
17
18
|
end
|
18
19
|
|
19
20
|
def write_living_probe
|
@@ -29,6 +30,17 @@ module SidekiqAlive
|
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
33
|
+
# Removes orphaned Sidekiq queues left behind by unexpected instance shutdowns (e.g., due to OOM)
|
34
|
+
def remove_orphaned_queues
|
35
|
+
# If the worker isn't executed within this window, the lifeness key expires
|
36
|
+
latency_threshold = config.time_to_live - config.worker_interval
|
37
|
+
Sidekiq::Queue.all
|
38
|
+
.filter { |q| q.name.start_with?(config.queue_prefix.to_s) }
|
39
|
+
.filter { |q| q.latency > latency_threshold }
|
40
|
+
.filter { |q| q.size == 1 && q.all? { |job| job.klass == self.class.name } }
|
41
|
+
.each(&:clear)
|
42
|
+
end
|
43
|
+
|
32
44
|
def current_hostname
|
33
45
|
SidekiqAlive.hostname
|
34
46
|
end
|
data/lib/sidekiq_alive.rb
CHANGED
@@ -27,28 +27,28 @@ module SidekiqAlive
|
|
27
27
|
(sq_config.respond_to?(:[]) ? sq_config[:queues] : sq_config.options[:queues]).unshift(current_queue)
|
28
28
|
end
|
29
29
|
|
30
|
-
logger.info(startup_info)
|
31
|
-
|
30
|
+
logger.info("[SidekiqAlive] #{startup_info}")
|
32
31
|
register_current_instance
|
33
|
-
|
34
32
|
store_alive_key
|
35
33
|
# Passing the hostname argument it's only for debugging enqueued jobs
|
36
34
|
SidekiqAlive::Worker.perform_async(hostname)
|
37
|
-
@
|
35
|
+
@server = SidekiqAlive::Server.run!
|
38
36
|
|
39
|
-
logger.info(successful_startup_text)
|
37
|
+
logger.info("[SidekiqAlive] #{successful_startup_text}")
|
40
38
|
end
|
41
39
|
|
42
40
|
sq_config.on(:quiet) do
|
43
|
-
|
44
|
-
|
41
|
+
logger.info("[SidekiqAlive] #{shutdown_info}")
|
42
|
+
purge_pending_jobs
|
43
|
+
# set web server to quiet mode
|
44
|
+
@server&.quiet!
|
45
45
|
end
|
46
46
|
|
47
47
|
sq_config.on(:shutdown) do
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
remove_queue
|
49
|
+
# make sure correct redis connection pool is used
|
50
|
+
# sidekiq will terminate non internal capsules
|
51
|
+
Redis.adapter("internal").zrem(HOSTNAME_REGISTRY, current_instance_register_key)
|
52
52
|
config.shutdown_callback.call
|
53
53
|
end
|
54
54
|
end
|
@@ -62,13 +62,6 @@ module SidekiqAlive
|
|
62
62
|
register_instance(current_instance_register_key)
|
63
63
|
end
|
64
64
|
|
65
|
-
def unregister_current_instance
|
66
|
-
# Delete any pending jobs for this instance
|
67
|
-
logger.info(shutdown_info)
|
68
|
-
purge_pending_jobs
|
69
|
-
redis.zrem(HOSTNAME_REGISTRY, current_instance_register_key)
|
70
|
-
end
|
71
|
-
|
72
65
|
def registered_instances
|
73
66
|
# before we return we make sure we expire old keys
|
74
67
|
expire_old_keys
|
@@ -82,9 +75,14 @@ module SidekiqAlive
|
|
82
75
|
else
|
83
76
|
schedule_set.scan('"class":"SidekiqAlive::Worker"').select { |job| job.queue == current_queue }
|
84
77
|
end
|
85
|
-
logger.info("[SidekiqAlive] Purging #{jobs.count} pending for #{hostname}")
|
86
|
-
jobs.each(&:delete)
|
87
78
|
|
79
|
+
unless jobs.empty?
|
80
|
+
logger.info("[SidekiqAlive] Purging #{jobs.count} pending jobs for #{hostname}")
|
81
|
+
jobs.each(&:delete)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def remove_queue
|
88
86
|
logger.info("[SidekiqAlive] Removing queue #{current_queue}")
|
89
87
|
Sidekiq::Queue.new(current_queue).clear
|
90
88
|
end
|
@@ -150,7 +148,7 @@ module SidekiqAlive
|
|
150
148
|
end
|
151
149
|
|
152
150
|
def successful_startup_text
|
153
|
-
"Successfully started sidekiq-alive, registered with key: "\
|
151
|
+
"Successfully started sidekiq-alive, registered with key: " \
|
154
152
|
"#{current_instance_register_key} on set #{HOSTNAME_REGISTRY}"
|
155
153
|
end
|
156
154
|
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sidekiq_alive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrejs Cunskis
|
8
8
|
- Artur Pañach
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2025-05-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -39,20 +39,6 @@ dependencies:
|
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '1.6'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: rack-test
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - "~>"
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: 2.1.0
|
49
|
-
type: :development
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - "~>"
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: 2.1.0
|
56
42
|
- !ruby/object:Gem::Dependency
|
57
43
|
name: rake
|
58
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,14 +73,14 @@ dependencies:
|
|
87
73
|
requirements:
|
88
74
|
- - "~>"
|
89
75
|
- !ruby/object:Gem::Version
|
90
|
-
version: '
|
76
|
+
version: '5.0'
|
91
77
|
type: :development
|
92
78
|
prerelease: false
|
93
79
|
version_requirements: !ruby/object:Gem::Requirement
|
94
80
|
requirements:
|
95
81
|
- - "~>"
|
96
82
|
- !ruby/object:Gem::Version
|
97
|
-
version: '
|
83
|
+
version: '5.0'
|
98
84
|
- !ruby/object:Gem::Dependency
|
99
85
|
name: rubocop-shopify
|
100
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,73 +96,67 @@ dependencies:
|
|
110
96
|
- !ruby/object:Gem::Version
|
111
97
|
version: '2.10'
|
112
98
|
- !ruby/object:Gem::Dependency
|
113
|
-
name:
|
99
|
+
name: semver2
|
114
100
|
requirement: !ruby/object:Gem::Requirement
|
115
101
|
requirements:
|
116
102
|
- - "~>"
|
117
103
|
- !ruby/object:Gem::Version
|
118
|
-
version:
|
104
|
+
version: '3.4'
|
119
105
|
type: :development
|
120
106
|
prerelease: false
|
121
107
|
version_requirements: !ruby/object:Gem::Requirement
|
122
108
|
requirements:
|
123
109
|
- - "~>"
|
124
110
|
- !ruby/object:Gem::Version
|
125
|
-
version:
|
111
|
+
version: '3.4'
|
126
112
|
- !ruby/object:Gem::Dependency
|
127
|
-
name:
|
113
|
+
name: solargraph
|
128
114
|
requirement: !ruby/object:Gem::Requirement
|
129
115
|
requirements:
|
130
|
-
- - "
|
116
|
+
- - "~>"
|
131
117
|
- !ruby/object:Gem::Version
|
132
|
-
version:
|
133
|
-
type: :
|
118
|
+
version: 0.54.0
|
119
|
+
type: :development
|
134
120
|
prerelease: false
|
135
121
|
version_requirements: !ruby/object:Gem::Requirement
|
136
122
|
requirements:
|
137
|
-
- - "
|
123
|
+
- - "~>"
|
138
124
|
- !ruby/object:Gem::Version
|
139
|
-
version:
|
125
|
+
version: 0.54.0
|
140
126
|
- !ruby/object:Gem::Dependency
|
141
|
-
name:
|
127
|
+
name: gserver
|
142
128
|
requirement: !ruby/object:Gem::Requirement
|
143
129
|
requirements:
|
144
|
-
- - "
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
version: '5'
|
147
|
-
- - "<"
|
130
|
+
- - "~>"
|
148
131
|
- !ruby/object:Gem::Version
|
149
|
-
version:
|
132
|
+
version: 0.0.1
|
150
133
|
type: :runtime
|
151
134
|
prerelease: false
|
152
135
|
version_requirements: !ruby/object:Gem::Requirement
|
153
136
|
requirements:
|
154
|
-
- - "
|
155
|
-
- !ruby/object:Gem::Version
|
156
|
-
version: '5'
|
157
|
-
- - "<"
|
137
|
+
- - "~>"
|
158
138
|
- !ruby/object:Gem::Version
|
159
|
-
version:
|
139
|
+
version: 0.0.1
|
160
140
|
- !ruby/object:Gem::Dependency
|
161
|
-
name:
|
141
|
+
name: sidekiq
|
162
142
|
requirement: !ruby/object:Gem::Requirement
|
163
143
|
requirements:
|
164
144
|
- - ">="
|
165
145
|
- !ruby/object:Gem::Version
|
166
|
-
version: '
|
146
|
+
version: '5'
|
167
147
|
- - "<"
|
168
148
|
- !ruby/object:Gem::Version
|
169
|
-
version: '
|
149
|
+
version: '9'
|
170
150
|
type: :runtime
|
171
151
|
prerelease: false
|
172
152
|
version_requirements: !ruby/object:Gem::Requirement
|
173
153
|
requirements:
|
174
154
|
- - ">="
|
175
155
|
- !ruby/object:Gem::Version
|
176
|
-
version: '
|
156
|
+
version: '5'
|
177
157
|
- - "<"
|
178
158
|
- !ruby/object:Gem::Version
|
179
|
-
version: '
|
159
|
+
version: '9'
|
180
160
|
description: |
|
181
161
|
SidekiqAlive offers a solution to add liveness probe of a Sidekiq instance.
|
182
162
|
|
@@ -204,6 +184,10 @@ files:
|
|
204
184
|
- lib/sidekiq_alive/redis/redis_client_gem.rb
|
205
185
|
- lib/sidekiq_alive/redis/redis_gem.rb
|
206
186
|
- lib/sidekiq_alive/server.rb
|
187
|
+
- lib/sidekiq_alive/server/base.rb
|
188
|
+
- lib/sidekiq_alive/server/default.rb
|
189
|
+
- lib/sidekiq_alive/server/http_server.rb
|
190
|
+
- lib/sidekiq_alive/server/rack.rb
|
207
191
|
- lib/sidekiq_alive/version.rb
|
208
192
|
- lib/sidekiq_alive/worker.rb
|
209
193
|
homepage: https://github.com/arturictus/sidekiq_alive
|
@@ -213,9 +197,9 @@ metadata:
|
|
213
197
|
homepage_uri: https://github.com/arturictus/sidekiq_alive
|
214
198
|
source_code_uri: https://github.com/arturictus/sidekiq_alive
|
215
199
|
changelog_uri: https://github.com/arturictus/sidekiq_alive/releases
|
216
|
-
documentation_uri: https://github.com/arturictus/sidekiq_alive/blob/v2.
|
200
|
+
documentation_uri: https://github.com/arturictus/sidekiq_alive/blob/v2.5.0/README.md
|
217
201
|
bug_tracker_uri: https://github.com/arturictus/sidekiq_alive/issues
|
218
|
-
post_install_message:
|
202
|
+
post_install_message:
|
219
203
|
rdoc_options: []
|
220
204
|
require_paths:
|
221
205
|
- lib
|
@@ -223,15 +207,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
223
207
|
requirements:
|
224
208
|
- - ">="
|
225
209
|
- !ruby/object:Gem::Version
|
226
|
-
version:
|
210
|
+
version: '3.1'
|
227
211
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
228
212
|
requirements:
|
229
213
|
- - ">="
|
230
214
|
- !ruby/object:Gem::Version
|
231
215
|
version: '0'
|
232
216
|
requirements: []
|
233
|
-
rubygems_version: 3.
|
234
|
-
signing_key:
|
217
|
+
rubygems_version: 3.5.22
|
218
|
+
signing_key:
|
235
219
|
specification_version: 4
|
236
220
|
summary: Liveness probe for sidekiq on Kubernetes deployments.
|
237
221
|
test_files: []
|