sidekiq-throttled 0.17.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.adoc +314 -0
- data/lib/sidekiq/throttled/config.rb +44 -0
- data/lib/sidekiq/throttled/cooldown.rb +55 -0
- data/lib/sidekiq/throttled/expirable_set.rb +70 -0
- data/lib/sidekiq/throttled/job.rb +4 -4
- data/lib/sidekiq/throttled/middlewares/server.rb +28 -0
- data/lib/sidekiq/throttled/patches/basic_fetch.rb +53 -0
- data/lib/sidekiq/throttled/registry.rb +4 -7
- data/lib/sidekiq/throttled/strategy/concurrency.rb +4 -6
- data/lib/sidekiq/throttled/strategy/threshold.rb +4 -6
- data/lib/sidekiq/throttled/strategy.rb +10 -10
- data/lib/sidekiq/throttled/strategy_collection.rb +2 -3
- data/lib/sidekiq/throttled/version.rb +1 -1
- data/lib/sidekiq/throttled/web.rb +2 -45
- data/lib/sidekiq/throttled/worker.rb +1 -1
- data/lib/sidekiq/throttled.rb +45 -57
- metadata +25 -70
- data/.coveralls.yml +0 -1
- data/.github/dependabot.yml +0 -12
- data/.github/workflows/ci.yml +0 -52
- data/.gitignore +0 -12
- data/.rspec +0 -5
- data/.rubocop.yml +0 -20
- data/.rubocop_todo.yml +0 -68
- data/.travis.yml +0 -37
- data/.yardopts +0 -1
- data/Appraisals +0 -9
- data/CHANGES.md +0 -300
- data/Gemfile +0 -34
- data/Guardfile +0 -25
- data/README.md +0 -297
- data/Rakefile +0 -27
- data/gemfiles/sidekiq_6.4.gemfile +0 -33
- data/gemfiles/sidekiq_6.5.gemfile +0 -33
- data/lib/sidekiq/throttled/communicator/callbacks.rb +0 -72
- data/lib/sidekiq/throttled/communicator/exception_handler.rb +0 -25
- data/lib/sidekiq/throttled/communicator/listener.rb +0 -109
- data/lib/sidekiq/throttled/communicator.rb +0 -116
- data/lib/sidekiq/throttled/configuration.rb +0 -50
- data/lib/sidekiq/throttled/expirable_list.rb +0 -70
- data/lib/sidekiq/throttled/fetch/unit_of_work.rb +0 -83
- data/lib/sidekiq/throttled/fetch.rb +0 -94
- data/lib/sidekiq/throttled/middleware.rb +0 -22
- data/lib/sidekiq/throttled/patches/queue.rb +0 -18
- data/lib/sidekiq/throttled/queue_name.rb +0 -46
- data/lib/sidekiq/throttled/queues_pauser.rb +0 -152
- data/lib/sidekiq/throttled/testing.rb +0 -12
- data/lib/sidekiq/throttled/utils.rb +0 -19
- data/lib/sidekiq/throttled/web/queues.html.erb +0 -49
- data/lib/sidekiq/throttled/web/summary_fix.js +0 -10
- data/lib/sidekiq/throttled/web/summary_fix.rb +0 -35
- data/rubocop/layout.yml +0 -24
- data/rubocop/lint.yml +0 -41
- data/rubocop/metrics.yml +0 -4
- data/rubocop/performance.yml +0 -25
- data/rubocop/rspec.yml +0 -3
- data/rubocop/style.yml +0 -84
- data/sidekiq-throttled.gemspec +0 -36
- /data/{LICENSE.md → LICENSE.txt} +0 -0
data/README.md
DELETED
@@ -1,297 +0,0 @@
|
|
1
|
-
# Sidekiq::Throttled
|
2
|
-
|
3
|
-
[![CI Status](https://github.com/ixti/sidekiq-throttled/actions/workflows/ci.yml/badge.svg)](https://github.com/ixti/sidekiq-throttled/actions/workflows/ci.yml)
|
4
|
-
[![Latest Version](https://badge.fury.io/rb/sidekiq-throttled.svg)](http://rubygems.org/gems/sidekiq-throttled)
|
5
|
-
[![API Docs](https://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/sidekiq-throttled)
|
6
|
-
|
7
|
-
Concurrency and threshold throttling for [Sidekiq][sidekiq].
|
8
|
-
|
9
|
-
## Installation
|
10
|
-
|
11
|
-
Add this line to your application's Gemfile:
|
12
|
-
|
13
|
-
``` ruby
|
14
|
-
gem "sidekiq-throttled"
|
15
|
-
```
|
16
|
-
|
17
|
-
And then execute:
|
18
|
-
|
19
|
-
$ bundle
|
20
|
-
|
21
|
-
Or install it yourself as:
|
22
|
-
|
23
|
-
$ gem install sidekiq-throttled
|
24
|
-
|
25
|
-
|
26
|
-
## Usage
|
27
|
-
|
28
|
-
Add somewhere in your app's bootstrap (e.g. `config/initializers/sidekiq.rb` if
|
29
|
-
you are using Rails):
|
30
|
-
|
31
|
-
``` ruby
|
32
|
-
require "sidekiq/throttled"
|
33
|
-
Sidekiq::Throttled.setup!
|
34
|
-
```
|
35
|
-
|
36
|
-
Load order can be an issue if you are using other Sidekiq plugins and/or middleware.
|
37
|
-
To prevent any problems, add the `.setup!` call to the bottom of your init file.
|
38
|
-
|
39
|
-
Once you've done that you can include `Sidekiq::Throttled::Job` to your
|
40
|
-
job classes and configure throttling:
|
41
|
-
|
42
|
-
``` ruby
|
43
|
-
class MyJob
|
44
|
-
include Sidekiq::Job
|
45
|
-
include Sidekiq::Throttled::Job
|
46
|
-
|
47
|
-
sidekiq_options :queue => :my_queue
|
48
|
-
|
49
|
-
sidekiq_throttle(
|
50
|
-
# Allow maximum 10 concurrent jobs of this class at a time.
|
51
|
-
:concurrency => { :limit => 10 },
|
52
|
-
# Allow maximum 1K jobs being processed within one hour window.
|
53
|
-
:threshold => { :limit => 1_000, :period => 1.hour }
|
54
|
-
)
|
55
|
-
|
56
|
-
def perform
|
57
|
-
# ...
|
58
|
-
end
|
59
|
-
end
|
60
|
-
```
|
61
|
-
|
62
|
-
**NOTE:** `Sidekiq::Throttled::Job` is aliased as `Sidekiq::Throttled::Worker`,
|
63
|
-
thus if you're using Sidekiq prior 6.3.0 version, or you using `Sidekiq::Worker`
|
64
|
-
naming convention, you can use the alias for consistency:
|
65
|
-
|
66
|
-
``` ruby
|
67
|
-
class MyWorker
|
68
|
-
include Sidekiq::Worker
|
69
|
-
include Sidekiq::Throttled::Worker
|
70
|
-
|
71
|
-
# ...
|
72
|
-
end
|
73
|
-
```
|
74
|
-
|
75
|
-
|
76
|
-
### Observer
|
77
|
-
|
78
|
-
You can specify an observer that will be called on throttling. To do so pass an
|
79
|
-
`:observer` option with callable object:
|
80
|
-
|
81
|
-
``` ruby
|
82
|
-
class MyJob
|
83
|
-
include Sidekiq::Job
|
84
|
-
include Sidekiq::Throttled::Job
|
85
|
-
|
86
|
-
MY_OBSERVER = lambda do |strategy, *args|
|
87
|
-
# do something
|
88
|
-
end
|
89
|
-
|
90
|
-
sidekiq_options :queue => :my_queue
|
91
|
-
|
92
|
-
sidekiq_throttle(
|
93
|
-
:concurrency => { :limit => 10 },
|
94
|
-
:threshold => { :limit => 100, :period => 1.hour }
|
95
|
-
:observer => MY_OBSERVER
|
96
|
-
)
|
97
|
-
|
98
|
-
def perform(*args)
|
99
|
-
# ...
|
100
|
-
end
|
101
|
-
end
|
102
|
-
```
|
103
|
-
|
104
|
-
Observer will receive `strategy, *args` arguments, where `strategy` is a Symbol
|
105
|
-
`:concurrency` or `:threshold`, and `*args` are the arguments that were passed
|
106
|
-
to the job.
|
107
|
-
|
108
|
-
|
109
|
-
### Dynamic throttling
|
110
|
-
|
111
|
-
You can throttle jobs dynamically with `:key_suffix` option:
|
112
|
-
|
113
|
-
``` ruby
|
114
|
-
class MyJob
|
115
|
-
include Sidekiq::Job
|
116
|
-
include Sidekiq::Throttled::Job
|
117
|
-
|
118
|
-
sidekiq_options :queue => :my_queue
|
119
|
-
|
120
|
-
sidekiq_throttle(
|
121
|
-
# Allow maximum 10 concurrent jobs per user at a time.
|
122
|
-
:concurrency => { :limit => 10, :key_suffix => -> (user_id) { user_id } }
|
123
|
-
)
|
124
|
-
|
125
|
-
def perform(user_id)
|
126
|
-
# ...
|
127
|
-
end
|
128
|
-
end
|
129
|
-
```
|
130
|
-
|
131
|
-
You can also supply dynamic values for limits and periods by supplying a proc
|
132
|
-
for these values. The proc will be evaluated at the time the job is fetched
|
133
|
-
and will receive the same arguments that are passed to the job.
|
134
|
-
|
135
|
-
``` ruby
|
136
|
-
class MyJob
|
137
|
-
include Sidekiq::Job
|
138
|
-
include Sidekiq::Throttled::Job
|
139
|
-
|
140
|
-
sidekiq_options :queue => :my_queue
|
141
|
-
|
142
|
-
sidekiq_throttle(
|
143
|
-
# Allow maximum 1000 concurrent jobs of this class at a time for VIPs and 10 for all other users.
|
144
|
-
:concurrency => {
|
145
|
-
:limit => ->(user_id) { User.vip?(user_id) ? 1_000 : 10 },
|
146
|
-
:key_suffix => ->(user_id) { User.vip?(user_id) ? "vip" : "std" }
|
147
|
-
},
|
148
|
-
# Allow 1000 jobs/hour to be processed for VIPs and 10/day for all others
|
149
|
-
:threshold => {
|
150
|
-
:limit => ->(user_id) { User.vip?(user_id) ? 1_000 : 10 },
|
151
|
-
:period => ->(user_id) { User.vip?(user_id) ? 1.hour : 1.day },
|
152
|
-
:key_suffix => ->(user_id) { User.vip?(user_id) ? "vip" : "std" }
|
153
|
-
)
|
154
|
-
|
155
|
-
def perform(user_id)
|
156
|
-
# ...
|
157
|
-
end
|
158
|
-
end
|
159
|
-
```
|
160
|
-
|
161
|
-
You also can use several different keys to throttle one worker.
|
162
|
-
|
163
|
-
``` ruby
|
164
|
-
class MyJob
|
165
|
-
include Sidekiq::Job
|
166
|
-
include Sidekiq::Throttled::Job
|
167
|
-
|
168
|
-
sidekiq_options :queue => :my_queue
|
169
|
-
|
170
|
-
sidekiq_throttle(
|
171
|
-
# Allow maximum 10 concurrent jobs per project at a time and maximum 2 jobs per user
|
172
|
-
:concurrency => [
|
173
|
-
{ :limit => 10, :key_suffix => -> (project_id, user_id) { project_id } },
|
174
|
-
{ :limit => 2, :key_suffix => -> (project_id, user_id) { user_id } }
|
175
|
-
]
|
176
|
-
# For :threshold it works the same
|
177
|
-
)
|
178
|
-
|
179
|
-
def perform(project_id, user_id)
|
180
|
-
# ...
|
181
|
-
end
|
182
|
-
end
|
183
|
-
```
|
184
|
-
|
185
|
-
**NB** Don't forget to specify `:key_suffix` and make it return different values
|
186
|
-
if you are using dynamic limit/period options. Otherwise you risk getting into
|
187
|
-
some trouble.
|
188
|
-
|
189
|
-
|
190
|
-
### Concurrency throttling fine-tuning
|
191
|
-
|
192
|
-
Concurrency throttling is based on distributed locks. Those locks have default
|
193
|
-
time to live (TTL) set to 15 minutes. If your job takes more than 15 minutes
|
194
|
-
to finish, lock will be released and you might end up with more jobs running
|
195
|
-
concurrently than you expect.
|
196
|
-
|
197
|
-
This is done to avoid deadlocks - when by any reason (e.g. Sidekiq process was
|
198
|
-
OOM-killed) cleanup middleware wasn't executed and locks were not released.
|
199
|
-
|
200
|
-
If your job takes more than 15 minutes to complete, you can tune concurrency
|
201
|
-
lock TTL to fit your needs:
|
202
|
-
|
203
|
-
``` ruby
|
204
|
-
# Set concurrency strategy lock TTL to 1 hour.
|
205
|
-
sidekiq_throttle(:concurrency => { :limit => 20, :ttl => 1.hour.to_i })
|
206
|
-
```
|
207
|
-
|
208
|
-
|
209
|
-
## Enhanced Queues list
|
210
|
-
|
211
|
-
This gem provides ability to pause/resume queues from processing by workers.
|
212
|
-
So you may simply pause particular queue without need to stop and reconfigure
|
213
|
-
workers by simply pushing a button on sidekiq web UI.
|
214
|
-
|
215
|
-
By default we add *Enhanced Queues* tab with this functionality. But if you
|
216
|
-
want you can override default *Queues* tab completely (notice that page will
|
217
|
-
still be available using it's URL, but tab will be pointing enhanced version).
|
218
|
-
To do so, just call `Sidekiq::Throttled::Web.enhance_queues_tab!` somewhere
|
219
|
-
in your initializer/bootstrap code. If you are using rails, you might want to
|
220
|
-
add it right into your `config/routes.rb` file:
|
221
|
-
|
222
|
-
``` ruby
|
223
|
-
# file config/routes.rb
|
224
|
-
|
225
|
-
require "sidekiq/web"
|
226
|
-
require "sidekiq/throttled/web"
|
227
|
-
|
228
|
-
Rails.application.routes.draw do
|
229
|
-
# ...
|
230
|
-
|
231
|
-
# Replace Sidekiq Queues with enhanced version!
|
232
|
-
Sidekiq::Throttled::Web.enhance_queues_tab!
|
233
|
-
|
234
|
-
# Mount Sidekiq Web UI to `/sidekiq` endpoint
|
235
|
-
mount Sidekiq::Web => "/sidekiq"
|
236
|
-
|
237
|
-
# ...
|
238
|
-
end
|
239
|
-
```
|
240
|
-
|
241
|
-
|
242
|
-
## Supported Ruby Versions
|
243
|
-
|
244
|
-
This library aims to support and is [tested against][ci] the following Ruby
|
245
|
-
versions:
|
246
|
-
|
247
|
-
* Ruby 2.7.x
|
248
|
-
* Ruby 3.0.x
|
249
|
-
* Ruby 3.1.x
|
250
|
-
|
251
|
-
If something doesn't work on one of these versions, it's a bug.
|
252
|
-
|
253
|
-
This library may inadvertently work (or seem to work) on other Ruby versions,
|
254
|
-
however support will only be provided for the versions listed above.
|
255
|
-
|
256
|
-
If you would like this library to support another Ruby version or
|
257
|
-
implementation, you may volunteer to be a maintainer. Being a maintainer
|
258
|
-
entails making sure all tests run and pass on that implementation. When
|
259
|
-
something breaks on your implementation, you will be responsible for providing
|
260
|
-
patches in a timely fashion. If critical issues for a particular implementation
|
261
|
-
exist at the time of a major release, support for that Ruby version may be
|
262
|
-
dropped.
|
263
|
-
|
264
|
-
|
265
|
-
## Supported Sidekiq Versions
|
266
|
-
|
267
|
-
This library aims to support work with following [Sidekiq][sidekiq] versions:
|
268
|
-
|
269
|
-
* Sidekiq 6.4.x
|
270
|
-
* Sidekiq 6.5.x
|
271
|
-
|
272
|
-
|
273
|
-
## Contributing
|
274
|
-
|
275
|
-
* Fork sidekiq-throttled on GitHub
|
276
|
-
* Make your changes
|
277
|
-
* Ensure all tests pass (`bundle exec rake`)
|
278
|
-
* Send a pull request
|
279
|
-
* If we like them we'll merge them
|
280
|
-
* If we've accepted a patch, feel free to ask for commit access!
|
281
|
-
|
282
|
-
|
283
|
-
## Development
|
284
|
-
|
285
|
-
```
|
286
|
-
bundle update
|
287
|
-
bundle exec appraisal install # install dependencies for all gemfiles
|
288
|
-
bundle exec appraisal update # update dependencies for all gemfiles
|
289
|
-
bundle exec appraisal rspec # run rspec against each gemfile
|
290
|
-
bundle exec rubocop # run static code analysis
|
291
|
-
```
|
292
|
-
|
293
|
-
Don't forget to run `appraisal update` after any changes to `Gemfile`.
|
294
|
-
|
295
|
-
|
296
|
-
[ci]: https://github.com/ixti/sidekiq-throttled/actions/workflows/ci.yml
|
297
|
-
[sidekiq]: https://github.com/mperham/sidekiq
|
data/Rakefile
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bundler/gem_tasks"
|
4
|
-
|
5
|
-
require "rspec/core/rake_task"
|
6
|
-
RSpec::Core::RakeTask.new(:spec)
|
7
|
-
|
8
|
-
desc "Run RuboCop"
|
9
|
-
task :rubocop do
|
10
|
-
require "rubocop"
|
11
|
-
result = RuboCop::CLI.new.run([])
|
12
|
-
abort("RuboCop failed!") if result.nonzero?
|
13
|
-
end
|
14
|
-
|
15
|
-
namespace :rubocop do
|
16
|
-
desc "Auto-correct RuboCop offenses"
|
17
|
-
task :autocorrect do
|
18
|
-
require "rubocop"
|
19
|
-
result = RuboCop::CLI.new.run(["--auto-correct"])
|
20
|
-
abort("RuboCop failed!") if result.nonzero?
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
default_suite = ENV["CI"] ? :spec : %i[spec rubocop]
|
25
|
-
named_suites = { "rubocop" => :rubocop, "rspec" => :spec }
|
26
|
-
|
27
|
-
task :default => named_suites.fetch(ENV.fetch("SUITE", nil), default_suite)
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
gem "appraisal"
|
6
|
-
gem "rake"
|
7
|
-
gem "rspec"
|
8
|
-
gem "sidekiq", "~> 6.4.0"
|
9
|
-
|
10
|
-
group :development do
|
11
|
-
gem "byebug"
|
12
|
-
gem "guard", require: false
|
13
|
-
gem "guard-rspec", require: false
|
14
|
-
gem "guard-rubocop", require: false
|
15
|
-
end
|
16
|
-
|
17
|
-
group :test do
|
18
|
-
gem "apparition"
|
19
|
-
gem "capybara"
|
20
|
-
gem "puma"
|
21
|
-
gem "rack-test"
|
22
|
-
gem "sinatra"
|
23
|
-
gem "timecop"
|
24
|
-
end
|
25
|
-
|
26
|
-
group :lint do
|
27
|
-
gem "rubocop", require: false
|
28
|
-
gem "rubocop-performance", require: false
|
29
|
-
gem "rubocop-rake", require: false
|
30
|
-
gem "rubocop-rspec", require: false
|
31
|
-
end
|
32
|
-
|
33
|
-
gemspec path: "../"
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
gem "appraisal"
|
6
|
-
gem "rake"
|
7
|
-
gem "rspec"
|
8
|
-
gem "sidekiq", "~> 6.5.0"
|
9
|
-
|
10
|
-
group :development do
|
11
|
-
gem "byebug"
|
12
|
-
gem "guard", require: false
|
13
|
-
gem "guard-rspec", require: false
|
14
|
-
gem "guard-rubocop", require: false
|
15
|
-
end
|
16
|
-
|
17
|
-
group :test do
|
18
|
-
gem "apparition"
|
19
|
-
gem "capybara"
|
20
|
-
gem "puma"
|
21
|
-
gem "rack-test"
|
22
|
-
gem "sinatra"
|
23
|
-
gem "timecop"
|
24
|
-
end
|
25
|
-
|
26
|
-
group :lint do
|
27
|
-
gem "rubocop", require: false
|
28
|
-
gem "rubocop-performance", require: false
|
29
|
-
gem "rubocop-rake", require: false
|
30
|
-
gem "rubocop-rspec", require: false
|
31
|
-
end
|
32
|
-
|
33
|
-
gemspec path: "../"
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "fiber"
|
4
|
-
|
5
|
-
require "sidekiq/throttled/communicator/exception_handler"
|
6
|
-
|
7
|
-
module Sidekiq
|
8
|
-
module Throttled
|
9
|
-
class Communicator
|
10
|
-
# Callbacks registry and runner. Runs registered callbacks in dedicated
|
11
|
-
# Fiber solving issue with ConnectionPool and Redis client in subscriber
|
12
|
-
# mode.
|
13
|
-
#
|
14
|
-
# Once Redis entered subscriber mode `#subscribe` method, it can't be used
|
15
|
-
# for any command but pub/sub or quit, making it impossible to use for
|
16
|
-
# anything else. ConnectionPool binds reserved client to Thread, thus
|
17
|
-
# nested `#with` calls inside same thread result into a same connection.
|
18
|
-
# That makes it impossible to issue any normal Redis commands from
|
19
|
-
# within listener Thread.
|
20
|
-
#
|
21
|
-
# @private
|
22
|
-
class Callbacks
|
23
|
-
include ExceptionHandler
|
24
|
-
|
25
|
-
# Initializes callbacks registry.
|
26
|
-
def initialize
|
27
|
-
@mutex = Mutex.new
|
28
|
-
@handlers = Hash.new { |h, k| h[k] = [] }
|
29
|
-
end
|
30
|
-
|
31
|
-
# Registers handler of given event.
|
32
|
-
#
|
33
|
-
# @example
|
34
|
-
#
|
35
|
-
# callbacks.on "and out comes wolves" do |who|
|
36
|
-
# puts "#{who} let the dogs out?!"
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# @param [#to_s] event
|
40
|
-
# @raise [ArgumentError] if no handler block given
|
41
|
-
# @yield [*args] Runs given block upon `event`
|
42
|
-
# @yieldreturn [void]
|
43
|
-
# @return [self]
|
44
|
-
def on(event, &handler)
|
45
|
-
raise ArgumentError, "No block given" unless handler
|
46
|
-
|
47
|
-
@mutex.synchronize { @handlers[event.to_s] << handler }
|
48
|
-
self
|
49
|
-
end
|
50
|
-
|
51
|
-
# Runs event handlers with given args.
|
52
|
-
#
|
53
|
-
# @param [#to_s] event
|
54
|
-
# @param [Object] payload
|
55
|
-
# @return [void]
|
56
|
-
def run(event, payload = nil)
|
57
|
-
@mutex.synchronize do
|
58
|
-
fiber = Fiber.new do
|
59
|
-
@handlers[event.to_s].each do |callback|
|
60
|
-
callback.call(payload)
|
61
|
-
rescue => e
|
62
|
-
handle_exception(e, :context => "sidekiq:throttled")
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
fiber.resume
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "sidekiq"
|
4
|
-
require "sidekiq/version"
|
5
|
-
|
6
|
-
module Sidekiq
|
7
|
-
module Throttled
|
8
|
-
class Communicator
|
9
|
-
if Sidekiq::VERSION >= "6.5.0"
|
10
|
-
module ExceptionHandler
|
11
|
-
def handle_exception(*args)
|
12
|
-
Sidekiq.handle_exception(*args)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
# NOTE: `Sidekiq.default_error_handler` is private API
|
17
|
-
Sidekiq.error_handlers << Sidekiq.method(:default_error_handler)
|
18
|
-
else
|
19
|
-
require "sidekiq/exception_handler"
|
20
|
-
|
21
|
-
ExceptionHandler = ::Sidekiq::ExceptionHandler
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,109 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "sidekiq/throttled/communicator/exception_handler"
|
4
|
-
|
5
|
-
module Sidekiq
|
6
|
-
module Throttled
|
7
|
-
class Communicator
|
8
|
-
# Redis subscription listener thread.
|
9
|
-
#
|
10
|
-
# @private
|
11
|
-
class Listener < Thread
|
12
|
-
include ExceptionHandler
|
13
|
-
|
14
|
-
# Starts listener thread.
|
15
|
-
#
|
16
|
-
# @param [String] channel Redis pub/sub channel to listen
|
17
|
-
# @param [Callbacks] callbacks Message callbacks registry
|
18
|
-
def initialize(channel, callbacks)
|
19
|
-
@channel = channel
|
20
|
-
@callbacks = callbacks
|
21
|
-
@terminated = false
|
22
|
-
@subscribed = false
|
23
|
-
|
24
|
-
super { listen until @terminated }
|
25
|
-
end
|
26
|
-
|
27
|
-
# Whenever underlying redis client subscribed to pub/sup channel.
|
28
|
-
#
|
29
|
-
# @return [Boolean]
|
30
|
-
def ready?
|
31
|
-
@subscribed
|
32
|
-
end
|
33
|
-
|
34
|
-
# Whenever main loop is still running.
|
35
|
-
#
|
36
|
-
# @return [Boolean]
|
37
|
-
def listening?
|
38
|
-
!@terminated
|
39
|
-
end
|
40
|
-
|
41
|
-
# Stops listener.
|
42
|
-
#
|
43
|
-
# @return [void]
|
44
|
-
def stop
|
45
|
-
# Raising exception while client is in subscription mode makes
|
46
|
-
# redis close connection and thus causing ConnectionPool reopen
|
47
|
-
# it (normal mode). Otherwise subscription mode client will be
|
48
|
-
# pushed back to ConnectionPool causing problems.
|
49
|
-
raise Sidekiq::Shutdown
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
# Wraps {#subscribe} with exception handlers:
|
55
|
-
#
|
56
|
-
# - `Sidekiq::Shutdown` exception marks listener as stopped and returns
|
57
|
-
# making `while` loop of listener thread terminate.
|
58
|
-
#
|
59
|
-
# - `StandardError` got recorded to the log and swallowed,
|
60
|
-
# making `while` loop of the listener thread restart.
|
61
|
-
#
|
62
|
-
# - `Exception` is recorded to the log and re-raised.
|
63
|
-
#
|
64
|
-
# @return [void]
|
65
|
-
def listen # rubocop:disable Metrics/MethodLength
|
66
|
-
subscribe
|
67
|
-
rescue Sidekiq::Shutdown
|
68
|
-
@terminated = true
|
69
|
-
@subscribed = false
|
70
|
-
rescue StandardError => e # rubocop:disable Style/RescueStandardError
|
71
|
-
@subscribed = false
|
72
|
-
handle_exception(e, { :context => "sidekiq:throttled" })
|
73
|
-
sleep 1
|
74
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
75
|
-
@terminated = true
|
76
|
-
@subscribed = false
|
77
|
-
handle_exception(e, { :context => "sidekiq:throttled" })
|
78
|
-
raise
|
79
|
-
end
|
80
|
-
|
81
|
-
# Subscribes to channel and triggers all registerd handlers for
|
82
|
-
# received messages.
|
83
|
-
#
|
84
|
-
# @note Puts thread's Redis connection to subscription mode and
|
85
|
-
# locks thread.
|
86
|
-
#
|
87
|
-
# @see http://redis.io/topics/pubsub
|
88
|
-
# @see http://redis.io/commands/subscribe
|
89
|
-
# @see Callbacks#run
|
90
|
-
# @return [void]
|
91
|
-
def subscribe # rubocop:disable Metrics/MethodLength
|
92
|
-
Sidekiq.redis do |conn|
|
93
|
-
conn.subscribe @channel do |on|
|
94
|
-
on.subscribe do
|
95
|
-
@subscribed = true
|
96
|
-
@callbacks.run("ready")
|
97
|
-
end
|
98
|
-
|
99
|
-
on.message do |_channel, data|
|
100
|
-
message, payload = Marshal.load(data) # rubocop:disable Security/MarshalLoad:
|
101
|
-
@callbacks.run("message:#{message}", payload)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|