sidekiq-throttled 0.17.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 +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
@@ -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
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# internal
|
4
|
-
require "sidekiq/throttled/registry"
|
5
|
-
|
6
|
-
module Sidekiq
|
7
|
-
module Throttled
|
8
|
-
# Server middleware that notifies strategy that job was finished.
|
9
|
-
#
|
10
|
-
# @private
|
11
|
-
class Middleware
|
12
|
-
# Called within Sidekiq job processing
|
13
|
-
def call(_worker, msg, _queue)
|
14
|
-
yield
|
15
|
-
ensure
|
16
|
-
Registry.get msg["class"] do |strategy|
|
17
|
-
strategy.finalize!(msg["jid"], *msg["args"])
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sidekiq
|
4
|
-
module Throttled
|
5
|
-
module Patches
|
6
|
-
module Queue
|
7
|
-
def paused?
|
8
|
-
QueuesPauser.instance.paused? name
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.apply!
|
12
|
-
require "sidekiq/api"
|
13
|
-
::Sidekiq::Queue.send(:prepend, self)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sidekiq
|
4
|
-
module Throttled
|
5
|
-
# Queue name utility belt.
|
6
|
-
#
|
7
|
-
# @private
|
8
|
-
module QueueName
|
9
|
-
# RegExp used to stip out any redisr-namespace prefixes with `queue:`.
|
10
|
-
QUEUE_NAME_PREFIX_RE = %r{.*queue:}.freeze
|
11
|
-
private_constant :QUEUE_NAME_PREFIX_RE
|
12
|
-
|
13
|
-
class << self
|
14
|
-
# Strips redis-namespace and `queue:` prefix from given queue name.
|
15
|
-
#
|
16
|
-
# @example
|
17
|
-
#
|
18
|
-
# QueueName.normalize "queue:default"
|
19
|
-
# # => "default"
|
20
|
-
#
|
21
|
-
# QueueName.normalize "queue:queue:default"
|
22
|
-
# # => "default"
|
23
|
-
#
|
24
|
-
# QueueName.normalize "foo:bar:queue:default"
|
25
|
-
# # => "default"
|
26
|
-
#
|
27
|
-
# @param [#to_s]
|
28
|
-
# @return [String]
|
29
|
-
def normalize(queue)
|
30
|
-
-queue.to_s.sub(QUEUE_NAME_PREFIX_RE, "")
|
31
|
-
end
|
32
|
-
|
33
|
-
# Prepends `queue:` prefix to given `queue` name.
|
34
|
-
#
|
35
|
-
# @note It does not normalizes queue before expanding it, thus
|
36
|
-
# double-call of this method will potentially do some harm.
|
37
|
-
#
|
38
|
-
# @param [#to_s] queue Queue name
|
39
|
-
# @return [String]
|
40
|
-
def expand(queue)
|
41
|
-
-"queue:#{queue}"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,152 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "set"
|
4
|
-
require "singleton"
|
5
|
-
require "concurrent/timer_task"
|
6
|
-
|
7
|
-
require "sidekiq/throttled/patches/queue"
|
8
|
-
require "sidekiq/throttled/communicator"
|
9
|
-
require "sidekiq/throttled/queue_name"
|
10
|
-
|
11
|
-
module Sidekiq
|
12
|
-
module Throttled
|
13
|
-
# Singleton class used to pause queues from being processed.
|
14
|
-
# For the sake of efficiency it uses {Communicator} behind the scene
|
15
|
-
# to notify all processes about paused/resumed queues.
|
16
|
-
#
|
17
|
-
# @private
|
18
|
-
class QueuesPauser
|
19
|
-
include Singleton
|
20
|
-
|
21
|
-
# Redis key of Set with paused queues.
|
22
|
-
#
|
23
|
-
# @return [String]
|
24
|
-
PAUSED_QUEUES = "throttled:X:paused_queues"
|
25
|
-
private_constant :PAUSED_QUEUES
|
26
|
-
|
27
|
-
# {Communicator} message used to notify that queue needs to be paused.
|
28
|
-
#
|
29
|
-
# @return [String]
|
30
|
-
PAUSE_MESSAGE = "pause"
|
31
|
-
private_constant :PAUSE_MESSAGE
|
32
|
-
|
33
|
-
# {Communicator} message used to notify that queue needs to be resumed.
|
34
|
-
#
|
35
|
-
# @return [String]
|
36
|
-
RESUME_MESSAGE = "resume"
|
37
|
-
private_constant :RESUME_MESSAGE
|
38
|
-
|
39
|
-
# Initializes singleton instance.
|
40
|
-
def initialize
|
41
|
-
@paused_queues = Set.new
|
42
|
-
@communicator = Communicator.instance
|
43
|
-
@mutex = Mutex.new
|
44
|
-
end
|
45
|
-
|
46
|
-
# Configures Sidekiq server to keep actual list of paused queues.
|
47
|
-
#
|
48
|
-
# @private
|
49
|
-
# @return [void]
|
50
|
-
def setup!
|
51
|
-
Patches::Queue.apply!
|
52
|
-
|
53
|
-
Sidekiq.configure_server do |config|
|
54
|
-
config.on(:startup) { start_watcher }
|
55
|
-
config.on(:quiet) { stop_watcher }
|
56
|
-
|
57
|
-
@communicator.receive(PAUSE_MESSAGE, &method(:add))
|
58
|
-
@communicator.receive(RESUME_MESSAGE, &method(:delete))
|
59
|
-
@communicator.ready { sync! }
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# Returns queues list with paused queues being stripped out.
|
64
|
-
#
|
65
|
-
# @private
|
66
|
-
# @return [Array<String>]
|
67
|
-
def filter(queues)
|
68
|
-
@mutex.synchronize { queues - @paused_queues.to_a }
|
69
|
-
rescue => e
|
70
|
-
Sidekiq.logger.error { "[#{self.class}] Failed filter queues: #{e}" }
|
71
|
-
queues
|
72
|
-
end
|
73
|
-
|
74
|
-
# Returns list of paused queues.
|
75
|
-
#
|
76
|
-
# @return [Array<String>]
|
77
|
-
def paused_queues
|
78
|
-
Sidekiq.redis { |conn| conn.smembers(PAUSED_QUEUES).to_a }
|
79
|
-
end
|
80
|
-
|
81
|
-
# Pauses given `queue`.
|
82
|
-
#
|
83
|
-
# @param [#to_s] queue
|
84
|
-
# @return [void]
|
85
|
-
def pause!(queue)
|
86
|
-
queue = QueueName.normalize queue.to_s
|
87
|
-
|
88
|
-
Sidekiq.redis do |conn|
|
89
|
-
conn.sadd(PAUSED_QUEUES, queue)
|
90
|
-
@communicator.transmit(conn, PAUSE_MESSAGE, queue)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# Checks if given `queue` is paused.
|
95
|
-
#
|
96
|
-
# @param queue [#to_s]
|
97
|
-
# @return [Boolean]
|
98
|
-
def paused?(queue)
|
99
|
-
queue = QueueName.normalize queue.to_s
|
100
|
-
Sidekiq.redis { |conn| conn.sismember(PAUSED_QUEUES, queue) }
|
101
|
-
end
|
102
|
-
|
103
|
-
# Resumes given `queue`.
|
104
|
-
#
|
105
|
-
# @param [#to_s] queue
|
106
|
-
# @return [void]
|
107
|
-
def resume!(queue)
|
108
|
-
queue = QueueName.normalize queue.to_s
|
109
|
-
|
110
|
-
Sidekiq.redis do |conn|
|
111
|
-
conn.srem(PAUSED_QUEUES, queue)
|
112
|
-
@communicator.transmit(conn, RESUME_MESSAGE, queue)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
private
|
117
|
-
|
118
|
-
def add(queue)
|
119
|
-
@mutex.synchronize do
|
120
|
-
@paused_queues << QueueName.expand(queue)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def delete(queue)
|
125
|
-
@mutex.synchronize do
|
126
|
-
@paused_queues.delete QueueName.expand(queue)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def sync!
|
131
|
-
@mutex.synchronize do
|
132
|
-
@paused_queues.replace(paused_queues.map { |q| QueueName.expand q })
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def start_watcher
|
137
|
-
@mutex.synchronize do
|
138
|
-
@watcher ||= Concurrent::TimerTask.execute({
|
139
|
-
:run_now => true,
|
140
|
-
:execution_interval => 60
|
141
|
-
}) { sync! }
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
def stop_watcher
|
146
|
-
@mutex.synchronize do
|
147
|
-
defined?(@watcher) && @watcher&.shutdown
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sidekiq
|
4
|
-
module Throttled
|
5
|
-
module Utils
|
6
|
-
module_function
|
7
|
-
|
8
|
-
# Resolve constant from it's name
|
9
|
-
# @param name [#to_s] Constant name
|
10
|
-
# @return [Object, nil] Resolved constant or nil if failed.
|
11
|
-
def constantize(name)
|
12
|
-
name.to_s.sub(%r{^::}, "").split("::").inject(Object, &:const_get)
|
13
|
-
rescue NameError
|
14
|
-
Sidekiq.logger.warn { "Failed to constantize: #{name}" }
|
15
|
-
nil
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|