sidekiq-throttled 0.18.0 → 1.1.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.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 +2 -4
- data/lib/sidekiq/throttled/strategy/threshold.rb +2 -4
- 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 +22 -67
- 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 -318
- 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
@@ -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
|
@@ -1,116 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "singleton"
|
4
|
-
|
5
|
-
require "sidekiq/throttled/communicator/exception_handler"
|
6
|
-
require "sidekiq/throttled/communicator/listener"
|
7
|
-
require "sidekiq/throttled/communicator/callbacks"
|
8
|
-
|
9
|
-
module Sidekiq
|
10
|
-
module Throttled
|
11
|
-
# Inter-process communication for sidekiq. It starts listener thread on
|
12
|
-
# sidekiq server and listens for incoming messages.
|
13
|
-
#
|
14
|
-
# @example
|
15
|
-
#
|
16
|
-
# # Add incoming message handler for server
|
17
|
-
# Communicator.instance.receive "knock" do |who|
|
18
|
-
# puts "#{who}'s knocking on the door"
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# # Emit message from console
|
22
|
-
# Sidekiq.redis do |conn|
|
23
|
-
# Communicator.instance.transmit(conn, "knock", "ixti")
|
24
|
-
# end
|
25
|
-
class Communicator
|
26
|
-
include Singleton
|
27
|
-
include ExceptionHandler
|
28
|
-
|
29
|
-
# Redis PUB/SUB channel name
|
30
|
-
#
|
31
|
-
# @see http://redis.io/topics/pubsub
|
32
|
-
CHANNEL_NAME = "sidekiq:throttled"
|
33
|
-
private_constant :CHANNEL_NAME
|
34
|
-
|
35
|
-
# Initializes singleton instance.
|
36
|
-
def initialize
|
37
|
-
@callbacks = Callbacks.new
|
38
|
-
@listener = nil
|
39
|
-
@mutex = Mutex.new
|
40
|
-
end
|
41
|
-
|
42
|
-
# Starts listener thread.
|
43
|
-
#
|
44
|
-
# @return [void]
|
45
|
-
def start_listener
|
46
|
-
@mutex.synchronize do
|
47
|
-
@listener ||= Listener.new(CHANNEL_NAME, @callbacks)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Stops listener thread.
|
52
|
-
#
|
53
|
-
# @return [void]
|
54
|
-
def stop_listener
|
55
|
-
@mutex.synchronize do
|
56
|
-
@listener&.stop
|
57
|
-
@listener = nil
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
# Configures Sidekiq server to start/stop listener thread.
|
62
|
-
#
|
63
|
-
# @private
|
64
|
-
# @return [void]
|
65
|
-
def setup!
|
66
|
-
Sidekiq.configure_server do |config|
|
67
|
-
config.on(:startup) { start_listener }
|
68
|
-
config.on(:quiet) { stop_listener }
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# Transmit message to listeners.
|
73
|
-
#
|
74
|
-
# @example
|
75
|
-
#
|
76
|
-
# Sidekiq.redis do |conn|
|
77
|
-
# Communicator.instance.transmit(conn, "knock")
|
78
|
-
# end
|
79
|
-
#
|
80
|
-
# @param [Redis] redis Redis client
|
81
|
-
# @param [#to_s] message
|
82
|
-
# @param [Object] payload
|
83
|
-
# @return [void]
|
84
|
-
def transmit(redis, message, payload = nil)
|
85
|
-
redis.publish(CHANNEL_NAME, Marshal.dump([message.to_s, payload]))
|
86
|
-
end
|
87
|
-
|
88
|
-
# Add incoming message handler.
|
89
|
-
#
|
90
|
-
# @example
|
91
|
-
#
|
92
|
-
# Communicator.instance.receive "knock" do |payload|
|
93
|
-
# # do something upon `knock` message
|
94
|
-
# end
|
95
|
-
#
|
96
|
-
# @param [#to_s] message
|
97
|
-
# @yield [payload] Runs given block everytime `message` being received.
|
98
|
-
# @yieldparam [Object, nil] payload Payload that was transmitted
|
99
|
-
# @yieldreturn [void]
|
100
|
-
# @return [void]
|
101
|
-
def receive(message, &handler)
|
102
|
-
@callbacks.on("message:#{message}", &handler)
|
103
|
-
end
|
104
|
-
|
105
|
-
# Communicator readiness hook.
|
106
|
-
#
|
107
|
-
# @yield Runs given block every time listener thread subscribes
|
108
|
-
# to Redis pub/sub channel.
|
109
|
-
# @return [void]
|
110
|
-
def ready(&handler)
|
111
|
-
@callbacks.on("ready", &handler)
|
112
|
-
yield if @listener&.ready?
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sidekiq
|
4
|
-
module Throttled
|
5
|
-
# Configuration holder.
|
6
|
-
class Configuration
|
7
|
-
# Class constructor.
|
8
|
-
def initialize
|
9
|
-
reset!
|
10
|
-
end
|
11
|
-
|
12
|
-
# Reset configuration to defaults.
|
13
|
-
#
|
14
|
-
# @return [self]
|
15
|
-
def reset!
|
16
|
-
@inherit_strategies = false
|
17
|
-
|
18
|
-
self
|
19
|
-
end
|
20
|
-
|
21
|
-
# Instructs throttler to lookup strategies in parent classes, if there's
|
22
|
-
# no own strategy:
|
23
|
-
#
|
24
|
-
# class FooJob
|
25
|
-
# include Sidekiq::Job
|
26
|
-
# include Sidekiq::Throttled::Job
|
27
|
-
#
|
28
|
-
# sidekiq_throttle :concurrency => { :limit => 42 }
|
29
|
-
# end
|
30
|
-
#
|
31
|
-
# class BarJob < FooJob
|
32
|
-
# end
|
33
|
-
#
|
34
|
-
# By default in the example above, `Bar` won't have throttling options.
|
35
|
-
# Set this flag to `true` to enable this lookup in initializer, after
|
36
|
-
# that `Bar` will use `Foo` throttling bucket.
|
37
|
-
def inherit_strategies=(value)
|
38
|
-
@inherit_strategies = value ? true : false
|
39
|
-
end
|
40
|
-
|
41
|
-
# Whenever throttled workers should inherit parent's strategies or not.
|
42
|
-
# Default: `false`.
|
43
|
-
#
|
44
|
-
# @return [Boolean]
|
45
|
-
def inherit_strategies?
|
46
|
-
@inherit_strategies
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "monitor"
|
4
|
-
|
5
|
-
module Sidekiq
|
6
|
-
module Throttled
|
7
|
-
# List that tracks when elements were added and enumerates over those not
|
8
|
-
# older than `ttl` seconds ago.
|
9
|
-
#
|
10
|
-
# ## Implementation
|
11
|
-
#
|
12
|
-
# Internally list holds an array of arrays. Thus each element is a tuple of
|
13
|
-
# monotonic timestamp (when element was added) and element itself:
|
14
|
-
#
|
15
|
-
# [
|
16
|
-
# [ 123456.7890, "default" ],
|
17
|
-
# [ 123456.7891, "urgent" ],
|
18
|
-
# [ 123457.9621, "urgent" ],
|
19
|
-
# ...
|
20
|
-
# ]
|
21
|
-
#
|
22
|
-
# It does not deduplicates elements. Eviction happens only upon elements
|
23
|
-
# retrieval (see {#each}).
|
24
|
-
#
|
25
|
-
# @see https://ruby-doc.org/core/Process.html#method-c-clock_gettime
|
26
|
-
# @see https://linux.die.net/man/3/clock_gettime
|
27
|
-
#
|
28
|
-
# @private
|
29
|
-
class ExpirableList
|
30
|
-
include Enumerable
|
31
|
-
|
32
|
-
# @param ttl [Float] elements time-to-live in seconds
|
33
|
-
def initialize(ttl)
|
34
|
-
@ttl = ttl.to_f
|
35
|
-
@arr = []
|
36
|
-
@mon = Monitor.new
|
37
|
-
end
|
38
|
-
|
39
|
-
# Pushes given element into the list.
|
40
|
-
#
|
41
|
-
# @params element [Object]
|
42
|
-
# @return [ExpirableList] self
|
43
|
-
def <<(element)
|
44
|
-
@mon.synchronize { @arr << [::Process.clock_gettime(::Process::CLOCK_MONOTONIC), element] }
|
45
|
-
self
|
46
|
-
end
|
47
|
-
|
48
|
-
# Evicts expired elements and calls the given block once for each element
|
49
|
-
# left, passing that element as a parameter.
|
50
|
-
#
|
51
|
-
# @yield [element]
|
52
|
-
# @return [Enumerator] if no block given
|
53
|
-
# @return [ExpirableList] self if block given
|
54
|
-
def each
|
55
|
-
return to_enum __method__ unless block_given?
|
56
|
-
|
57
|
-
@mon.synchronize do
|
58
|
-
horizon = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @ttl
|
59
|
-
|
60
|
-
# drop all elements older than horizon
|
61
|
-
@arr.shift while @arr[0] && @arr[0][0] < horizon
|
62
|
-
|
63
|
-
@arr.each { |x| yield x[1] }
|
64
|
-
end
|
65
|
-
|
66
|
-
self
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,83 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "sidekiq"
|
4
|
-
|
5
|
-
require "sidekiq/throttled/queue_name"
|
6
|
-
|
7
|
-
module Sidekiq
|
8
|
-
module Throttled
|
9
|
-
class Fetch
|
10
|
-
# BRPOP response envelope.
|
11
|
-
#
|
12
|
-
# @see Throttled::Fetch
|
13
|
-
# @private
|
14
|
-
class UnitOfWork
|
15
|
-
# @return [String] Redis key where job was pulled from
|
16
|
-
attr_reader :queue
|
17
|
-
|
18
|
-
# @return [String] Job's JSON payload
|
19
|
-
attr_reader :job
|
20
|
-
|
21
|
-
# @param [String] queue Redis key where job was pulled from
|
22
|
-
# @param [String] job Job's JSON payload
|
23
|
-
def initialize(queue, job)
|
24
|
-
@queue = queue
|
25
|
-
@job = job
|
26
|
-
end
|
27
|
-
|
28
|
-
# Callback that is called by `Sidekiq::Processor` when job was
|
29
|
-
# succeccfully processed. Most likely this is used by `ReliableFetch`
|
30
|
-
# of Sidekiq Pro/Enterprise to remove job from running queue.
|
31
|
-
#
|
32
|
-
# @return [void]
|
33
|
-
def acknowledge
|
34
|
-
# do nothing
|
35
|
-
end
|
36
|
-
|
37
|
-
# Normalized `queue` name.
|
38
|
-
#
|
39
|
-
# @see QueueName.normalize
|
40
|
-
# @return [String]
|
41
|
-
def queue_name
|
42
|
-
@queue_name ||= QueueName.normalize queue
|
43
|
-
end
|
44
|
-
|
45
|
-
# Pushes job back to the tail of the queue, so that it will be popped
|
46
|
-
# first next time fetcher will pull job.
|
47
|
-
#
|
48
|
-
# @note This is triggered when job was not finished and Sidekiq server
|
49
|
-
# process was terminated. It is a reverse of whatever fetcher was
|
50
|
-
# doing to pull the job out of queue.
|
51
|
-
#
|
52
|
-
# @param [Redis] pipelined connection for requeing via Redis#pipelined
|
53
|
-
# @return [void]
|
54
|
-
def requeue(pipeline = nil)
|
55
|
-
if pipeline
|
56
|
-
pipeline.rpush(QueueName.expand(queue_name), job)
|
57
|
-
else
|
58
|
-
Sidekiq.redis { |conn| conn.rpush(QueueName.expand(queue_name), job) }
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
# Pushes job back to the head of the queue, so that job won't be tried
|
63
|
-
# immediately after it was requeued (in most cases).
|
64
|
-
#
|
65
|
-
# @note This is triggered when job is throttled. So it is same operation
|
66
|
-
# Sidekiq performs upon `Sidekiq::Worker.perform_async` call.
|
67
|
-
#
|
68
|
-
# @return [void]
|
69
|
-
def requeue_throttled
|
70
|
-
Sidekiq.redis { |conn| conn.lpush(QueueName.expand(queue_name), job) }
|
71
|
-
end
|
72
|
-
|
73
|
-
# Tells whenever job should be pushed back to queue (throttled) or not.
|
74
|
-
#
|
75
|
-
# @see Sidekiq::Throttled.throttled?
|
76
|
-
# @return [Boolean]
|
77
|
-
def throttled?
|
78
|
-
Throttled.throttled? job
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
@@ -1,94 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "sidekiq"
|
4
|
-
require "sidekiq/throttled/expirable_list"
|
5
|
-
require "sidekiq/throttled/fetch/unit_of_work"
|
6
|
-
require "sidekiq/throttled/queues_pauser"
|
7
|
-
require "sidekiq/throttled/queue_name"
|
8
|
-
|
9
|
-
module Sidekiq
|
10
|
-
module Throttled
|
11
|
-
# Throttled fetch strategy.
|
12
|
-
#
|
13
|
-
# @private
|
14
|
-
class Fetch
|
15
|
-
# Timeout to sleep between fetch retries in case of no job received,
|
16
|
-
# as well as timeout to wait for redis to give us something to work.
|
17
|
-
TIMEOUT = 2
|
18
|
-
|
19
|
-
# Initializes fetcher instance.
|
20
|
-
# @param options [Hash]
|
21
|
-
# @option options [Integer] :throttled_queue_cooldown (TIMEOUT)
|
22
|
-
# Min delay in seconds before queue will be polled again after
|
23
|
-
# throttled job.
|
24
|
-
# @option options [Boolean] :strict (false)
|
25
|
-
# @option options [Array<#to_s>] :queue
|
26
|
-
def initialize(options)
|
27
|
-
@paused = ExpirableList.new(options.fetch(:throttled_queue_cooldown, TIMEOUT))
|
28
|
-
|
29
|
-
@strict = options.fetch(:strict, false)
|
30
|
-
@queues = options.fetch(:queues).map { |q| QueueName.expand q }
|
31
|
-
|
32
|
-
raise ArgumentError, "empty :queues" if @queues.empty?
|
33
|
-
|
34
|
-
@queues.uniq! if @strict
|
35
|
-
end
|
36
|
-
|
37
|
-
# Retrieves job from redis.
|
38
|
-
#
|
39
|
-
# @return [Sidekiq::Throttled::UnitOfWork, nil]
|
40
|
-
def retrieve_work
|
41
|
-
work = brpop
|
42
|
-
return unless work
|
43
|
-
|
44
|
-
work = UnitOfWork.new(*work)
|
45
|
-
return work unless work.throttled?
|
46
|
-
|
47
|
-
work.requeue_throttled
|
48
|
-
@paused << QueueName.expand(work.queue_name)
|
49
|
-
|
50
|
-
nil
|
51
|
-
end
|
52
|
-
|
53
|
-
def bulk_requeue(units, _options)
|
54
|
-
return if units.empty?
|
55
|
-
|
56
|
-
Sidekiq.logger.debug { "Re-queueing terminated jobs" }
|
57
|
-
Sidekiq.redis do |conn|
|
58
|
-
conn.pipelined do |pipeline|
|
59
|
-
units.each { |unit| unit.requeue(pipeline) }
|
60
|
-
end
|
61
|
-
end
|
62
|
-
Sidekiq.logger.info("Pushed #{units.size} jobs back to Redis")
|
63
|
-
rescue => e
|
64
|
-
Sidekiq.logger.warn("Failed to requeue #{units.size} jobs: #{e}")
|
65
|
-
end
|
66
|
-
|
67
|
-
private
|
68
|
-
|
69
|
-
# Tries to pop pair of `queue` and job `message` out of sidekiq queues.
|
70
|
-
#
|
71
|
-
# @see http://redis.io/commands/brpop
|
72
|
-
# @return [Array(String, String), nil]
|
73
|
-
def brpop
|
74
|
-
queues = filter_queues(@strict ? @queues : @queues.shuffle.uniq)
|
75
|
-
|
76
|
-
if queues.empty?
|
77
|
-
sleep TIMEOUT
|
78
|
-
return
|
79
|
-
end
|
80
|
-
|
81
|
-
Sidekiq.redis { |conn| conn.brpop(*queues, :timeout => TIMEOUT) }
|
82
|
-
end
|
83
|
-
|
84
|
-
# Returns list of queues to try to fetch jobs from.
|
85
|
-
#
|
86
|
-
# @note It may return an empty array.
|
87
|
-
# @param [Array<String>] queues
|
88
|
-
# @return [Array<String>]
|
89
|
-
def filter_queues(queues)
|
90
|
-
QueuesPauser.instance.filter(queues) - @paused.to_a
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|