sidekiq-alive-next 3.1.1 → 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.
@@ -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
@@ -1,46 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rack"
4
- require "rackup"
5
-
6
3
  module SidekiqAlive
7
- class Server
4
+ module Server
8
5
  class << self
9
6
  def run!
10
- handler = Rackup::Handler.get(server)
11
-
12
- Signal.trap("TERM") { handler.shutdown }
13
-
14
- handler.run(self, Port: port, Host: host, AccessLog: [], Logger: SidekiqAlive.logger)
7
+ server.run!
15
8
  end
16
9
 
17
- def host
18
- SidekiqAlive.config.host
19
- end
10
+ private
20
11
 
21
- def port
22
- SidekiqAlive.config.port
12
+ def server
13
+ use_rack? ? Rack : Default
23
14
  end
24
15
 
25
- def path
26
- SidekiqAlive.config.path
27
- end
16
+ def use_rack?
17
+ return false unless SidekiqAlive.config.server
28
18
 
29
- def server
30
- SidekiqAlive.config.server
19
+ Helpers.use_rackup? || Helpers.use_rack?
31
20
  end
32
21
 
33
- def call(env)
34
- if Rack::Request.new(env).path != path
35
- [404, {}, ["Not found"]]
36
- elsif SidekiqAlive.alive?
37
- [200, {}, ["Alive!"]]
38
- else
39
- response = "Can't find the alive key"
40
- SidekiqAlive.logger.error(response)
41
- [404, {}, [response]]
42
- end
22
+ def logger
23
+ SidekiqAlive.logger
43
24
  end
44
25
  end
45
26
  end
46
27
  end
28
+
29
+ require_relative "server/default"
30
+ require_relative "server/rack"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SidekiqAlive
4
- VERSION = "3.1.1"
4
+ VERSION = "3.2.0"
5
5
  end
@@ -5,20 +5,16 @@ module SidekiqAlive
5
5
  include Sidekiq::Worker
6
6
  sidekiq_options retry: false
7
7
 
8
+ # Passing the hostname argument it's only for debugging enqueued jobs
8
9
  def perform(_hostname = SidekiqAlive.hostname)
9
10
  # Checks if custom liveness probe passes should fail or return false
10
11
  return unless config.custom_liveness_probe.call
11
12
 
12
13
  # Writes the liveness in Redis
13
14
  write_living_probe
15
+ remove_orphaned_queues
14
16
  # schedules next living probe
15
- self.class.perform_in(config.time_to_live / 2, current_hostname)
16
- end
17
-
18
- def hostname_registered?(hostname)
19
- SidekiqAlive.registered_instances.any? do |ri|
20
- /#{hostname}/ =~ ri
21
- end
17
+ self.class.perform_in(config.worker_interval, current_hostname)
22
18
  end
23
19
 
24
20
  def write_living_probe
@@ -34,6 +30,17 @@ module SidekiqAlive
34
30
  end
35
31
  end
36
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
+
37
44
  def current_hostname
38
45
  SidekiqAlive.hostname
39
46
  end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "sidekiq"
5
+ require "sidekiq/api"
6
+ require "singleton"
7
+ require "sidekiq_alive/version"
8
+ require "sidekiq_alive/config"
9
+ require "sidekiq_alive/helpers"
10
+ require "sidekiq_alive/redis"
11
+
12
+ module SidekiqAlive
13
+ HOSTNAME_REGISTRY = "sidekiq-alive-hostnames"
14
+ CAPSULE_NAME = "sidekiq-alive"
15
+
16
+ class << self
17
+ def start
18
+ Sidekiq.configure_server do |sq_config|
19
+ sq_config.on(:startup) do
20
+ SidekiqAlive::Worker.sidekiq_options(queue: current_queue)
21
+
22
+ if Helpers.sidekiq_7
23
+ sq_config.capsule(CAPSULE_NAME) do |cap|
24
+ cap.concurrency = config.concurrency
25
+ cap.queues = [current_queue]
26
+ end
27
+ else
28
+ (sq_config.respond_to?(:[]) ? sq_config[:queues] : sq_config.options[:queues]).unshift(current_queue)
29
+ end
30
+
31
+ logger.info("[SidekiqAlive] #{startup_info}")
32
+ register_current_instance
33
+ store_alive_key
34
+ # Passing the hostname argument it's only for debugging enqueued jobs
35
+ SidekiqAlive::Worker.perform_async(hostname)
36
+ @server = SidekiqAlive::Server.run!
37
+
38
+ logger.info("[SidekiqAlive] #{successful_startup_text}")
39
+ end
40
+
41
+ sq_config.on(:quiet) do
42
+ logger.info("[SidekiqAlive] #{shutdown_info}")
43
+ purge_pending_jobs
44
+ # set web server to quiet mode
45
+ @server&.quiet!
46
+ end
47
+
48
+ sq_config.on(:shutdown) do
49
+ remove_queue
50
+ # make sure correct redis connection pool is used
51
+ # sidekiq will terminate non internal capsules
52
+ Redis.adapter("internal").zrem(HOSTNAME_REGISTRY, current_instance_register_key)
53
+ config.shutdown_callback.call
54
+ end
55
+ end
56
+ end
57
+
58
+ def current_queue
59
+ "#{config.queue_prefix}-#{hostname}"
60
+ end
61
+
62
+ def register_current_instance
63
+ register_instance(current_instance_register_key)
64
+ end
65
+
66
+ def registered_instances
67
+ # before we return we make sure we expire old keys
68
+ expire_old_keys
69
+ redis.zrange(HOSTNAME_REGISTRY, 0, -1)
70
+ end
71
+
72
+ def purge_pending_jobs
73
+ schedule_set = Sidekiq::ScheduledSet.new
74
+ jobs = if Helpers.sidekiq_5
75
+ schedule_set.select { |job| job.klass == "SidekiqAlive::Worker" && job.queue == current_queue }
76
+ else
77
+ schedule_set.scan('"class":"SidekiqAlive::Worker"').select { |job| job.queue == current_queue }
78
+ end
79
+
80
+ unless jobs.empty?
81
+ logger.info("[SidekiqAlive] Purging #{jobs.count} pending jobs for #{hostname}")
82
+ jobs.each(&:delete)
83
+ end
84
+ end
85
+
86
+ def remove_queue
87
+ logger.info("[SidekiqAlive] Removing queue #{current_queue}")
88
+ Sidekiq::Queue.new(current_queue).clear
89
+ end
90
+
91
+ def current_instance_register_key
92
+ "#{config.registered_instance_key}::#{hostname}"
93
+ end
94
+
95
+ def current_instance_registered?
96
+ redis.get(current_instance_register_key)
97
+ end
98
+
99
+ def store_alive_key
100
+ redis.set(current_lifeness_key, time: Time.now.to_i, ex: config.time_to_live.to_i)
101
+ end
102
+
103
+ def redis
104
+ @redis ||= Redis.adapter
105
+ end
106
+
107
+ def alive?
108
+ redis.ttl(current_lifeness_key) != -2
109
+ end
110
+
111
+ # CONFIG ---------------------------------------
112
+
113
+ def setup
114
+ yield(config)
115
+ end
116
+
117
+ def logger
118
+ config.logger || Sidekiq.logger
119
+ end
120
+
121
+ def config
122
+ @config ||= SidekiqAlive::Config.instance
123
+ end
124
+
125
+ def current_lifeness_key
126
+ "#{config.liveness_key}::#{hostname}"
127
+ end
128
+
129
+ def hostname
130
+ ENV["HOSTNAME"] || "HOSTNAME_NOT_SET"
131
+ end
132
+
133
+ def shutdown_info
134
+ "Shutting down sidekiq-alive!"
135
+ end
136
+
137
+ def startup_info
138
+ info = {
139
+ hostname: hostname,
140
+ port: config.port,
141
+ ttl: config.time_to_live,
142
+ queue: current_queue,
143
+ register_set: HOSTNAME_REGISTRY,
144
+ liveness_key: current_lifeness_key,
145
+ register_key: current_instance_register_key,
146
+ }
147
+
148
+ "Starting sidekiq-alive: #{info}"
149
+ end
150
+
151
+ def successful_startup_text
152
+ "Successfully started sidekiq-alive, registered with key: " \
153
+ "#{current_instance_register_key} on set #{HOSTNAME_REGISTRY}"
154
+ end
155
+
156
+ def expire_old_keys
157
+ # we get every key that should be expired by now
158
+ keys_to_expire = redis.zrangebyscore(HOSTNAME_REGISTRY, 0, Time.now.to_i)
159
+ # then we remove it
160
+ keys_to_expire.each { |key| redis.zrem(HOSTNAME_REGISTRY, key) }
161
+ end
162
+
163
+ def register_instance(instance_name)
164
+ expiration = Time.now.to_i + config.registration_ttl.to_i
165
+ redis.zadd(HOSTNAME_REGISTRY, expiration, instance_name)
166
+ expire_old_keys
167
+ end
168
+ end
169
+ end
170
+
171
+ require "sidekiq_alive/worker"
172
+ require "sidekiq_alive/server"
173
+
174
+ SidekiqAlive.start unless ENV.fetch("DISABLE_SIDEKIQ_ALIVE", "").casecmp("true").zero?
metadata CHANGED
@@ -1,15 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-alive-next
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrejs Cunskis
8
- - Artur Pañach
9
- autorequire:
10
8
  bindir: bin
11
9
  cert_chain: []
12
- date: 2022-12-02 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
13
11
  dependencies:
14
12
  - !ruby/object:Gem::Dependency
15
13
  name: bundler
@@ -45,14 +43,14 @@ dependencies:
45
43
  requirements:
46
44
  - - "~>"
47
45
  - !ruby/object:Gem::Version
48
- version: 2.0.2
46
+ version: 2.2.0
49
47
  type: :development
50
48
  prerelease: false
51
49
  version_requirements: !ruby/object:Gem::Requirement
52
50
  requirements:
53
51
  - - "~>"
54
52
  - !ruby/object:Gem::Version
55
- version: 2.0.2
53
+ version: 2.2.0
56
54
  - !ruby/object:Gem::Dependency
57
55
  name: rake
58
56
  requirement: !ruby/object:Gem::Requirement
@@ -87,14 +85,14 @@ dependencies:
87
85
  requirements:
88
86
  - - "~>"
89
87
  - !ruby/object:Gem::Version
90
- version: '3.0'
88
+ version: '5.0'
91
89
  type: :development
92
90
  prerelease: false
93
91
  version_requirements: !ruby/object:Gem::Requirement
94
92
  requirements:
95
93
  - - "~>"
96
94
  - !ruby/object:Gem::Version
97
- version: '3.0'
95
+ version: '5.0'
98
96
  - !ruby/object:Gem::Dependency
99
97
  name: rubocop-shopify
100
98
  requirement: !ruby/object:Gem::Requirement
@@ -110,93 +108,109 @@ dependencies:
110
108
  - !ruby/object:Gem::Version
111
109
  version: '2.10'
112
110
  - !ruby/object:Gem::Dependency
113
- name: solargraph
111
+ name: semver2
114
112
  requirement: !ruby/object:Gem::Requirement
115
113
  requirements:
116
114
  - - "~>"
117
115
  - !ruby/object:Gem::Version
118
- version: 0.47.2
116
+ version: '3.4'
119
117
  type: :development
120
118
  prerelease: false
121
119
  version_requirements: !ruby/object:Gem::Requirement
122
120
  requirements:
123
121
  - - "~>"
124
122
  - !ruby/object:Gem::Version
125
- version: 0.47.2
123
+ version: '3.4'
126
124
  - !ruby/object:Gem::Dependency
127
- name: rack
125
+ name: simplecov
128
126
  requirement: !ruby/object:Gem::Requirement
129
127
  requirements:
130
- - - ">="
131
- - !ruby/object:Gem::Version
132
- version: '3'
133
- - - "<"
128
+ - - "~>"
134
129
  - !ruby/object:Gem::Version
135
- version: '4'
136
- type: :runtime
130
+ version: 0.22.0
131
+ type: :development
137
132
  prerelease: false
138
133
  version_requirements: !ruby/object:Gem::Requirement
139
134
  requirements:
140
- - - ">="
141
- - !ruby/object:Gem::Version
142
- version: '3'
143
- - - "<"
135
+ - - "~>"
144
136
  - !ruby/object:Gem::Version
145
- version: '4'
137
+ version: 0.22.0
146
138
  - !ruby/object:Gem::Dependency
147
- name: rackup
139
+ name: simplecov-cobertura
148
140
  requirement: !ruby/object:Gem::Requirement
149
141
  requirements:
150
- - - ">="
142
+ - - "~>"
151
143
  - !ruby/object:Gem::Version
152
- version: '0.2'
153
- type: :runtime
144
+ version: 2.1.0
145
+ type: :development
154
146
  prerelease: false
155
147
  version_requirements: !ruby/object:Gem::Requirement
156
148
  requirements:
157
- - - ">="
149
+ - - "~>"
158
150
  - !ruby/object:Gem::Version
159
- version: '0.2'
151
+ version: 2.1.0
160
152
  - !ruby/object:Gem::Dependency
161
- name: sidekiq
153
+ name: solargraph
162
154
  requirement: !ruby/object:Gem::Requirement
163
155
  requirements:
164
- - - ">="
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: 0.54.0
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
165
164
  - !ruby/object:Gem::Version
166
- version: '7'
165
+ version: 0.54.0
166
+ - !ruby/object:Gem::Dependency
167
+ name: base64
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
167
170
  - - "<"
168
171
  - !ruby/object:Gem::Version
169
- version: '8'
172
+ version: '1'
170
173
  type: :runtime
171
174
  prerelease: false
172
175
  version_requirements: !ruby/object:Gem::Requirement
173
176
  requirements:
174
- - - ">="
175
- - !ruby/object:Gem::Version
176
- version: '7'
177
177
  - - "<"
178
178
  - !ruby/object:Gem::Version
179
- version: '8'
179
+ version: '1'
180
180
  - !ruby/object:Gem::Dependency
181
- name: webrick
181
+ name: gserver
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: 0.0.1
187
+ type: :runtime
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: 0.0.1
194
+ - !ruby/object:Gem::Dependency
195
+ name: sidekiq
182
196
  requirement: !ruby/object:Gem::Requirement
183
197
  requirements:
184
198
  - - ">="
185
199
  - !ruby/object:Gem::Version
186
- version: '1'
200
+ version: '5'
187
201
  - - "<"
188
202
  - !ruby/object:Gem::Version
189
- version: '2'
203
+ version: '9'
190
204
  type: :runtime
191
205
  prerelease: false
192
206
  version_requirements: !ruby/object:Gem::Requirement
193
207
  requirements:
194
208
  - - ">="
195
209
  - !ruby/object:Gem::Version
196
- version: '1'
210
+ version: '5'
197
211
  - - "<"
198
212
  - !ruby/object:Gem::Version
199
- version: '2'
213
+ version: '9'
200
214
  description: |
201
215
  SidekiqAlive offers a solution to add liveness probe of a Sidekiq instance.
202
216
 
@@ -215,9 +229,18 @@ extensions: []
215
229
  extra_rdoc_files: []
216
230
  files:
217
231
  - README.md
218
- - lib/sidekiq-alive-next.rb
232
+ - lib/sidekiq_alive.rb
219
233
  - lib/sidekiq_alive/config.rb
234
+ - lib/sidekiq_alive/helpers.rb
235
+ - lib/sidekiq_alive/redis.rb
236
+ - lib/sidekiq_alive/redis/base.rb
237
+ - lib/sidekiq_alive/redis/redis_client_gem.rb
238
+ - lib/sidekiq_alive/redis/redis_gem.rb
220
239
  - lib/sidekiq_alive/server.rb
240
+ - lib/sidekiq_alive/server/base.rb
241
+ - lib/sidekiq_alive/server/default.rb
242
+ - lib/sidekiq_alive/server/http_server.rb
243
+ - lib/sidekiq_alive/server/rack.rb
221
244
  - lib/sidekiq_alive/version.rb
222
245
  - lib/sidekiq_alive/worker.rb
223
246
  homepage: https://github.com/andrcuns/sidekiq-alive
@@ -227,9 +250,8 @@ metadata:
227
250
  homepage_uri: https://github.com/andrcuns/sidekiq-alive
228
251
  source_code_uri: https://github.com/andrcuns/sidekiq-alive
229
252
  changelog_uri: https://github.com/andrcuns/sidekiq-alive/releases
230
- documentation_uri: https://github.com/andrcuns/sidekiq-alive/blob/v3.1.1/README.md
253
+ documentation_uri: https://github.com/andrcuns/sidekiq-alive/blob/v3.2.0/README.md
231
254
  bug_tracker_uri: https://github.com/andrcuns/sidekiq-alive/issues
232
- post_install_message:
233
255
  rdoc_options: []
234
256
  require_paths:
235
257
  - lib
@@ -237,15 +259,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
237
259
  requirements:
238
260
  - - ">="
239
261
  - !ruby/object:Gem::Version
240
- version: 2.7.0
262
+ version: '3.1'
241
263
  required_rubygems_version: !ruby/object:Gem::Requirement
242
264
  requirements:
243
265
  - - ">="
244
266
  - !ruby/object:Gem::Version
245
267
  version: '0'
246
268
  requirements: []
247
- rubygems_version: 3.3.7
248
- signing_key:
269
+ rubygems_version: 3.6.7
249
270
  specification_version: 4
250
271
  summary: Liveness probe for sidekiq on Kubernetes deployments.
251
272
  test_files: []