sidekiq-alive-next 3.1.0 → 3.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a18bb6defb7f2d0d3eb1e1b01ce12b683c5c634be2e43b74600b787cdf4ae0e9
4
- data.tar.gz: d0bb5d30eeee984dee67e75228d5d6ca3df91301eea805414dee6665dd058859
3
+ metadata.gz: 8cbf2184e5b001d067c1a79b65daf927182fd132ca2554177e31a7f070176e12
4
+ data.tar.gz: e911131239e815e146bc66b6844f092571d07108ff6ae27ed2d730d39a49928a
5
5
  SHA512:
6
- metadata.gz: 15cbeff2addefafddb0416fc194d5943d190bf03d1482b57a7b09771c72edaf015cee7ea55b23a055a775526b5a55b854c5251f27835839339e1d31a996f0523
7
- data.tar.gz: 2c6e9f7aae03381045cc0b641b8caaff01ac6d8b51932f902e1578a6923e2ec62c4b1c4c6e9d6c54183a64fd56e0d2f766d26c99fd832f8c60b3fb5394f6babb
6
+ metadata.gz: 1a0ddb8c2bdd70be93b63659711967f617e7b4b4f84bc695646baa331c7de188ed3993ea30530a54dfb12dfcfbd374dd3b8a0a54e350b038a6e09bb10e18cb47
7
+ data.tar.gz: 551e3c7c103eb1c3067e1ad8084db084872817f30b14d58e9658b348c220e064b20f5b4352f60507372ace146957765bc2f9c17da6360eb7f20c46aa9b23a507
data/README.md CHANGED
@@ -5,7 +5,9 @@
5
5
  ![Workflow status](https://github.com/allure-framework/allure-ruby/workflows/Test/badge.svg)
6
6
  [![Known Vulnerabilities](https://snyk.io/test/github/andrcuns/sidekiq-alive/badge.svg)](https://snyk.io/test/github/andrcuns/sidekiq-alive)
7
7
 
8
- **This is the fork of 'arturictus/sidekiq_alive' with a few minor fixes and tweaks, all credit goes to author of original repo**
8
+ **This is the fork of '[arturictus/sidekiq_alive](https://github.com/arturictus/sidekiq_alive)' with a few minor fixes and tweaks, original implementation credit goes to author of original repo**
9
+
10
+ ---
9
11
 
10
12
  SidekiqAlive offers a solution to add liveness probe for a Sidekiq instance deployed in Kubernetes.
11
13
  This library can be used to check sidekiq health outside kubernetes.
@@ -49,11 +51,15 @@ gem 'sidekiq-alive-next'
49
51
 
50
52
  And then execute:
51
53
 
52
- $ bundle
54
+ ```console
55
+ bundle
56
+ ```
53
57
 
54
58
  Or install it yourself as:
55
59
 
56
- $ gem install sidekiq-alive-next
60
+ ```console
61
+ gem install sidekiq-alive-next
62
+ ```
57
63
 
58
64
  ## Usage
59
65
 
@@ -61,11 +67,11 @@ SidekiqAlive will start when running `sidekiq` command.
61
67
 
62
68
  Run `Sidekiq`
63
69
 
64
- ```
70
+ ```console
65
71
  bundle exec sidekiq
66
72
  ```
67
73
 
68
- ```
74
+ ```console
69
75
  curl localhost:7433
70
76
  #=> Alive!
71
77
  ```
@@ -74,7 +80,7 @@ curl localhost:7433
74
80
  You can disabled by setting `ENV` variable `DISABLE_SIDEKIQ_ALIVE`
75
81
  example:
76
82
 
77
- ```
83
+ ```console
78
84
  DISABLE_SIDEKIQ_ALIVE=true bundle exec sidekiq
79
85
  ```
80
86
 
@@ -137,8 +143,8 @@ kill -SIGTSTP $SIDEKIQ_PID
137
143
 
138
144
  Make it executable:
139
145
 
140
- ```
141
- $ chmod +x kube/sidekiq_quiet
146
+ ```console
147
+ chmod +x kube/sidekiq_quiet
142
148
  ```
143
149
 
144
150
  Execute it in your deployment preStop:
@@ -183,12 +189,12 @@ It's just up to you how you want to use it.
183
189
 
184
190
  An example in local would be:
185
191
 
186
- ```
192
+ ```console
187
193
  bundle exec sidekiq
188
194
  # let it initialize ...
189
195
  ```
190
196
 
191
- ```
197
+ ```console
192
198
  curl localhost:7433
193
199
  #=> Alive!
194
200
  ```
@@ -248,12 +254,10 @@ SidekiqAlive.setup do |config|
248
254
  # config.callback = proc { Net::HTTP.get("https://status.com/ping") }
249
255
 
250
256
  # ==> Shutdown callback
251
- # When sidekiq process is shutting down, you can perform some action, like cleaning up created queue
257
+ # When sidekiq process is shutting down, you can perform some arbitrary action.
252
258
  # default: proc {}
253
259
  #
254
- # config.shutdown_callback = proc do
255
- # Sidekiq::Queue.all.find { |q| q.name == "#{queue_prefix}-#{SidekiqAlive.hostname}" }&.clear
256
- # end
260
+ # config.shutdown_callback = proc { puts "Sidekiq is shutting down" }
257
261
 
258
262
  # ==> Queue Prefix
259
263
  # SidekiqAlive will run in a independent queue for each instance/replica
@@ -263,12 +267,32 @@ SidekiqAlive.setup do |config|
263
267
  #
264
268
  # config.queue_prefix = :other
265
269
 
270
+ # ==> Concurrency
271
+ # The maximum number of Redis connections requested for the SidekiqAlive pool.
272
+ # Can also be set with the environment variable SIDEKIQ_ALIVE_CONCURRENCY.
273
+ # NOTE: only effects Sidekiq 7 or greater.
274
+ # default: 2
275
+ #
276
+ # config.concurrency = 3
277
+
266
278
  # ==> Rack server
267
- # Web server used to serve an HTTP response.
279
+ # Web server used to serve an HTTP response. By default simple GServer based http server is used.
280
+ # To use specific server, rack gem version > 2 is required. For rack version >= 3, rackup gem is required.
268
281
  # Can also be set with the environment variable SIDEKIQ_ALIVE_SERVER.
269
- # default: 'webrick'
282
+ # default: nil
270
283
  #
271
284
  # config.server = 'puma'
285
+
286
+ # ==> Quiet mode timeout in seconds
287
+ # When sidekiq is shutting down, the Sidekiq process stops pulling jobs from the queue. This includes alive key update job. In case of
288
+ # long running jobs, alive key can expire before the job is finished. To avoid this, web server is set in to quiet mode
289
+ # and is returning 200 OK for healthcheck requests. To avoid infinite quiet mode in case sidekiq process is stuck in shutdown,
290
+ # timeout can be set. After timeout is reached, web server resumes normal operations and will return unhealthy status in case
291
+ # alive key is expired or purged from redis.
292
+ # default: 180
293
+ #
294
+ # config.quiet_timeout = 300
295
+
272
296
  end
273
297
  ```
274
298
 
@@ -278,11 +302,9 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
278
302
 
279
303
  To install this gem onto your local machine, run `bundle exec rake install`.
280
304
 
281
- Here is an example [rails app](https://github.com/arturictus/sidekiq_alive_example)
282
-
283
305
  ## Contributing
284
306
 
285
- Bug reports and pull requests are welcome on GitHub at https://github.com/andrcuns/sidekiq-alive. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
307
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/andrcuns/sidekiq-alive>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
286
308
 
287
309
  ## License
288
310
 
@@ -12,10 +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
- :shutdown_callback
17
+ :shutdown_callback,
18
+ :concurrency,
19
+ :server,
20
+ :quiet_timeout
19
21
 
20
22
  def initialize
21
23
  set_defaults
@@ -30,13 +32,19 @@ module SidekiqAlive
30
32
  @callback = proc {}
31
33
  @registered_instance_key = "SIDEKIQ_REGISTERED_INSTANCE"
32
34
  @queue_prefix = :"sidekiq-alive"
33
- @server = ENV.fetch("SIDEKIQ_ALIVE_SERVER", "webrick")
34
35
  @custom_liveness_probe = proc { true }
35
36
  @shutdown_callback = proc {}
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
36
40
  end
37
41
 
38
42
  def registration_ttl
39
- @registration_ttl || time_to_live + 60
43
+ @registration_ttl || time_to_live * 3
44
+ end
45
+
46
+ def worker_interval
47
+ time_to_live / 2
40
48
  end
41
49
  end
42
50
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqAlive
4
+ module Helpers
5
+ class << self
6
+ def sidekiq_7
7
+ current_sidekiq_version >= Gem::Version.new("7")
8
+ end
9
+
10
+ def sidekiq_6
11
+ current_sidekiq_version >= Gem::Version.new("6") &&
12
+ current_sidekiq_version < Gem::Version.new("7")
13
+ end
14
+
15
+ def sidekiq_5
16
+ current_sidekiq_version >= Gem::Version.new("5") &&
17
+ current_sidekiq_version < Gem::Version.new("6")
18
+ end
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
+
42
+ private
43
+
44
+ def current_sidekiq_version
45
+ Gem.loaded_specs["sidekiq"].version
46
+ end
47
+
48
+ def current_rack_version
49
+ Gem.loaded_specs["rack"].version
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqAlive
4
+ module Redis
5
+ class Base
6
+ def set(...)
7
+ raise(NotImplementedError)
8
+ end
9
+
10
+ def zadd(set_key, ex, key)
11
+ raise(NotImplementedError)
12
+ end
13
+
14
+ def zrange(set_key, start, stop)
15
+ raise(NotImplementedError)
16
+ end
17
+
18
+ def zrangebyscore(set_key, min, max)
19
+ raise(NotImplementedError)
20
+ end
21
+
22
+ def zrem(set_key, key)
23
+ raise(NotImplementedError)
24
+ end
25
+
26
+ def delete(key)
27
+ raise(NotImplementedError)
28
+ end
29
+
30
+ def ttl(...)
31
+ redis { |r| r.ttl(...) }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module SidekiqAlive
6
+ module Redis
7
+ # Wrapper for `redis-client` gem used by `sidekiq` > 7
8
+ # https://github.com/redis-rb/redis-client
9
+ class RedisClientGem < Base
10
+ def initialize(capsule = nil)
11
+ super()
12
+
13
+ @capsule = Sidekiq.default_configuration.capsules[capsule || CAPSULE_NAME]
14
+ end
15
+
16
+ def set(key, time:, ex:)
17
+ redis { |r| r.call("SET", key, time, ex: ex) }
18
+ end
19
+
20
+ def get(key)
21
+ redis { |r| r.call("GET", key) }
22
+ end
23
+
24
+ def zadd(set_key, ex, key)
25
+ redis { |r| r.call("ZADD", set_key, ex, key) }
26
+ end
27
+
28
+ def zrange(set_key, start, stop)
29
+ redis { |r| r.call("ZRANGE", set_key, start, stop) }
30
+ end
31
+
32
+ def zrangebyscore(set_key, min, max)
33
+ redis { |r| r.call("ZRANGEBYSCORE", set_key, min, max) }
34
+ end
35
+
36
+ def zrem(set_key, key)
37
+ redis { |r| r.call("ZREM", set_key, key) }
38
+ end
39
+
40
+ def delete(key)
41
+ redis { |r| r.call("DEL", key) }
42
+ end
43
+
44
+ private
45
+
46
+ def redis(&block)
47
+ # Default to Sidekiq.redis if capsule is not configured yet but redis adapter is accessed
48
+ (@capsule || Sidekiq).redis(&block)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module SidekiqAlive
6
+ module Redis
7
+ # Wrapper for `redis` gem used by sidekiq < 7
8
+ # https://github.com/redis/redis-rb
9
+ class RedisGem < Base
10
+ def set(key, time:, ex:)
11
+ redis { |r| r.set(key, time, ex: ex) }
12
+ end
13
+
14
+ def get(key)
15
+ redis { |r| r.get(key) }
16
+ end
17
+
18
+ def zadd(set_key, ex, key)
19
+ redis { |r| r.zadd(set_key, ex, key) }
20
+ end
21
+
22
+ def zrange(set_key, start, stop)
23
+ redis { |r| r.zrange(set_key, start, stop) }
24
+ end
25
+
26
+ def zrangebyscore(set_key, min, max)
27
+ redis { |r| r.zrangebyscore(set_key, min, max) }
28
+ end
29
+
30
+ def zrem(set_key, key)
31
+ redis { |r| r.zrem(set_key, key) }
32
+ end
33
+
34
+ def delete(key)
35
+ redis { |r| r.del(key) }
36
+ end
37
+
38
+ private
39
+
40
+ def redis(&block)
41
+ Sidekiq.redis(&block)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SidekiqAlive
4
+ module Redis
5
+ class << self
6
+ def adapter(capsule = nil)
7
+ Helpers.sidekiq_7 ? Redis::RedisClientGem.new(capsule) : Redis::RedisGem.new
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ require_relative "redis/base"
14
+ require_relative "redis/redis_client_gem"
15
+ require_relative "redis/redis_gem"
@@ -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