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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.adoc +314 -0
  3. data/lib/sidekiq/throttled/config.rb +44 -0
  4. data/lib/sidekiq/throttled/cooldown.rb +55 -0
  5. data/lib/sidekiq/throttled/expirable_set.rb +70 -0
  6. data/lib/sidekiq/throttled/job.rb +4 -4
  7. data/lib/sidekiq/throttled/middlewares/server.rb +28 -0
  8. data/lib/sidekiq/throttled/patches/basic_fetch.rb +53 -0
  9. data/lib/sidekiq/throttled/registry.rb +4 -7
  10. data/lib/sidekiq/throttled/strategy/concurrency.rb +4 -6
  11. data/lib/sidekiq/throttled/strategy/threshold.rb +4 -6
  12. data/lib/sidekiq/throttled/strategy.rb +10 -10
  13. data/lib/sidekiq/throttled/strategy_collection.rb +2 -3
  14. data/lib/sidekiq/throttled/version.rb +1 -1
  15. data/lib/sidekiq/throttled/web.rb +2 -45
  16. data/lib/sidekiq/throttled/worker.rb +1 -1
  17. data/lib/sidekiq/throttled.rb +45 -57
  18. metadata +25 -70
  19. data/.coveralls.yml +0 -1
  20. data/.github/dependabot.yml +0 -12
  21. data/.github/workflows/ci.yml +0 -52
  22. data/.gitignore +0 -12
  23. data/.rspec +0 -5
  24. data/.rubocop.yml +0 -20
  25. data/.rubocop_todo.yml +0 -68
  26. data/.travis.yml +0 -37
  27. data/.yardopts +0 -1
  28. data/Appraisals +0 -9
  29. data/CHANGES.md +0 -300
  30. data/Gemfile +0 -34
  31. data/Guardfile +0 -25
  32. data/README.md +0 -297
  33. data/Rakefile +0 -27
  34. data/gemfiles/sidekiq_6.4.gemfile +0 -33
  35. data/gemfiles/sidekiq_6.5.gemfile +0 -33
  36. data/lib/sidekiq/throttled/communicator/callbacks.rb +0 -72
  37. data/lib/sidekiq/throttled/communicator/exception_handler.rb +0 -25
  38. data/lib/sidekiq/throttled/communicator/listener.rb +0 -109
  39. data/lib/sidekiq/throttled/communicator.rb +0 -116
  40. data/lib/sidekiq/throttled/configuration.rb +0 -50
  41. data/lib/sidekiq/throttled/expirable_list.rb +0 -70
  42. data/lib/sidekiq/throttled/fetch/unit_of_work.rb +0 -83
  43. data/lib/sidekiq/throttled/fetch.rb +0 -94
  44. data/lib/sidekiq/throttled/middleware.rb +0 -22
  45. data/lib/sidekiq/throttled/patches/queue.rb +0 -18
  46. data/lib/sidekiq/throttled/queue_name.rb +0 -46
  47. data/lib/sidekiq/throttled/queues_pauser.rb +0 -152
  48. data/lib/sidekiq/throttled/testing.rb +0 -12
  49. data/lib/sidekiq/throttled/utils.rb +0 -19
  50. data/lib/sidekiq/throttled/web/queues.html.erb +0 -49
  51. data/lib/sidekiq/throttled/web/summary_fix.js +0 -10
  52. data/lib/sidekiq/throttled/web/summary_fix.rb +0 -35
  53. data/rubocop/layout.yml +0 -24
  54. data/rubocop/lint.yml +0 -41
  55. data/rubocop/metrics.yml +0 -4
  56. data/rubocop/performance.yml +0 -25
  57. data/rubocop/rspec.yml +0 -3
  58. data/rubocop/style.yml +0 -84
  59. data/sidekiq-throttled.gemspec +0 -36
  60. /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,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "sidekiq/throttled/registry"
4
-
5
- RSpec.configure do |config|
6
- config.before :example do
7
- Sidekiq::Throttled::Registry.instance_eval do
8
- @strategies.clear
9
- @aliases.clear
10
- end
11
- end
12
- 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