sr-sidekiq 4.1.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/3.0-Upgrade.md +70 -0
- data/4.0-Upgrade.md +50 -0
- data/COMM-LICENSE (sidekiq) +95 -0
- data/Changes.md +1241 -0
- data/Ent-Changes.md +112 -0
- data/Gemfile +29 -0
- data/LICENSE (sidekiq) +9 -0
- data/LICENSE (sr-sidekiq) +5 -0
- data/Pro-2.0-Upgrade.md +138 -0
- data/Pro-3.0-Upgrade.md +44 -0
- data/Pro-Changes.md +539 -0
- data/README.md +8 -0
- data/Rakefile +9 -0
- data/bin/sidekiq +18 -0
- data/bin/sidekiqctl +99 -0
- data/bin/sidekiqload +167 -0
- data/code_of_conduct.md +50 -0
- data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
- data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
- data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
- data/lib/generators/sidekiq/worker_generator.rb +49 -0
- data/lib/sidekiq.rb +237 -0
- data/lib/sidekiq/api.rb +844 -0
- data/lib/sidekiq/cli.rb +389 -0
- data/lib/sidekiq/client.rb +260 -0
- data/lib/sidekiq/core_ext.rb +106 -0
- data/lib/sidekiq/exception_handler.rb +31 -0
- data/lib/sidekiq/extensions/action_mailer.rb +57 -0
- data/lib/sidekiq/extensions/active_record.rb +40 -0
- data/lib/sidekiq/extensions/class_methods.rb +40 -0
- data/lib/sidekiq/extensions/generic_proxy.rb +25 -0
- data/lib/sidekiq/fetch.rb +81 -0
- data/lib/sidekiq/launcher.rb +160 -0
- data/lib/sidekiq/logging.rb +106 -0
- data/lib/sidekiq/manager.rb +137 -0
- data/lib/sidekiq/middleware/chain.rb +150 -0
- data/lib/sidekiq/middleware/i18n.rb +42 -0
- data/lib/sidekiq/middleware/server/active_record.rb +13 -0
- data/lib/sidekiq/middleware/server/logging.rb +40 -0
- data/lib/sidekiq/middleware/server/retry_jobs.rb +205 -0
- data/lib/sidekiq/paginator.rb +43 -0
- data/lib/sidekiq/processor.rb +186 -0
- data/lib/sidekiq/rails.rb +39 -0
- data/lib/sidekiq/redis_connection.rb +97 -0
- data/lib/sidekiq/scheduled.rb +146 -0
- data/lib/sidekiq/testing.rb +316 -0
- data/lib/sidekiq/testing/inline.rb +29 -0
- data/lib/sidekiq/util.rb +62 -0
- data/lib/sidekiq/version.rb +4 -0
- data/lib/sidekiq/web.rb +278 -0
- data/lib/sidekiq/web_helpers.rb +255 -0
- data/lib/sidekiq/worker.rb +121 -0
- data/sidekiq.gemspec +26 -0
- data/sr-sidekiq-4.1.3.gem +0 -0
- data/sr-sidekiq-4.1.4.gem +0 -0
- data/sr-sidekiq-4.1.5.gem +0 -0
- data/test/config.yml +9 -0
- data/test/env_based_config.yml +11 -0
- data/test/fake_env.rb +1 -0
- data/test/fixtures/en.yml +2 -0
- data/test/helper.rb +75 -0
- data/test/test_actors.rb +138 -0
- data/test/test_api.rb +528 -0
- data/test/test_cli.rb +406 -0
- data/test/test_client.rb +262 -0
- data/test/test_exception_handler.rb +56 -0
- data/test/test_extensions.rb +127 -0
- data/test/test_fetch.rb +50 -0
- data/test/test_launcher.rb +85 -0
- data/test/test_logging.rb +35 -0
- data/test/test_manager.rb +50 -0
- data/test/test_middleware.rb +158 -0
- data/test/test_processor.rb +201 -0
- data/test/test_rails.rb +22 -0
- data/test/test_redis_connection.rb +127 -0
- data/test/test_retry.rb +326 -0
- data/test/test_retry_exhausted.rb +149 -0
- data/test/test_scheduled.rb +115 -0
- data/test/test_scheduling.rb +50 -0
- data/test/test_sidekiq.rb +107 -0
- data/test/test_testing.rb +143 -0
- data/test/test_testing_fake.rb +357 -0
- data/test/test_testing_inline.rb +94 -0
- data/test/test_util.rb +13 -0
- data/test/test_web.rb +614 -0
- data/test/test_web_helpers.rb +54 -0
- data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
- data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
- data/web/assets/images/favicon.ico +0 -0
- data/web/assets/images/logo.png +0 -0
- data/web/assets/images/status-sd8051fd480.png +0 -0
- data/web/assets/images/status/active.png +0 -0
- data/web/assets/images/status/idle.png +0 -0
- data/web/assets/javascripts/application.js +88 -0
- data/web/assets/javascripts/dashboard.js +300 -0
- data/web/assets/javascripts/locales/README.md +27 -0
- data/web/assets/javascripts/locales/jquery.timeago.ar.js +96 -0
- data/web/assets/javascripts/locales/jquery.timeago.bg.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.bs.js +49 -0
- data/web/assets/javascripts/locales/jquery.timeago.ca.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.cs.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.cy.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.da.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.de.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.el.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.en-short.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.en.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.es.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.et.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.fa.js +22 -0
- data/web/assets/javascripts/locales/jquery.timeago.fi.js +28 -0
- data/web/assets/javascripts/locales/jquery.timeago.fr-short.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.fr.js +17 -0
- data/web/assets/javascripts/locales/jquery.timeago.he.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.hr.js +49 -0
- data/web/assets/javascripts/locales/jquery.timeago.hu.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.hy.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.id.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.it.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.ja.js +19 -0
- data/web/assets/javascripts/locales/jquery.timeago.ko.js +17 -0
- data/web/assets/javascripts/locales/jquery.timeago.lt.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.mk.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.nl.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.no.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.pl.js +31 -0
- data/web/assets/javascripts/locales/jquery.timeago.pt-br.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.pt.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.ro.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.rs.js +49 -0
- data/web/assets/javascripts/locales/jquery.timeago.ru.js +34 -0
- data/web/assets/javascripts/locales/jquery.timeago.sk.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.sl.js +44 -0
- data/web/assets/javascripts/locales/jquery.timeago.sv.js +18 -0
- data/web/assets/javascripts/locales/jquery.timeago.th.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.tr.js +16 -0
- data/web/assets/javascripts/locales/jquery.timeago.uk.js +34 -0
- data/web/assets/javascripts/locales/jquery.timeago.uz.js +19 -0
- data/web/assets/javascripts/locales/jquery.timeago.zh-cn.js +20 -0
- data/web/assets/javascripts/locales/jquery.timeago.zh-tw.js +20 -0
- data/web/assets/stylesheets/application.css +754 -0
- data/web/assets/stylesheets/bootstrap.css +9 -0
- data/web/locales/cs.yml +78 -0
- data/web/locales/da.yml +68 -0
- data/web/locales/de.yml +69 -0
- data/web/locales/el.yml +68 -0
- data/web/locales/en.yml +79 -0
- data/web/locales/es.yml +69 -0
- data/web/locales/fr.yml +78 -0
- data/web/locales/hi.yml +75 -0
- data/web/locales/it.yml +69 -0
- data/web/locales/ja.yml +78 -0
- data/web/locales/ko.yml +68 -0
- data/web/locales/nb.yml +77 -0
- data/web/locales/nl.yml +68 -0
- data/web/locales/pl.yml +59 -0
- data/web/locales/pt-br.yml +68 -0
- data/web/locales/pt.yml +67 -0
- data/web/locales/ru.yml +78 -0
- data/web/locales/sv.yml +68 -0
- data/web/locales/ta.yml +75 -0
- data/web/locales/uk.yml +76 -0
- data/web/locales/zh-cn.yml +68 -0
- data/web/locales/zh-tw.yml +68 -0
- data/web/views/_footer.erb +17 -0
- data/web/views/_job_info.erb +88 -0
- data/web/views/_nav.erb +66 -0
- data/web/views/_paging.erb +23 -0
- data/web/views/_poll_js.erb +5 -0
- data/web/views/_poll_link.erb +7 -0
- data/web/views/_status.erb +4 -0
- data/web/views/_summary.erb +40 -0
- data/web/views/busy.erb +94 -0
- data/web/views/dashboard.erb +75 -0
- data/web/views/dead.erb +34 -0
- data/web/views/layout.erb +32 -0
- data/web/views/morgue.erb +71 -0
- data/web/views/queue.erb +45 -0
- data/web/views/queues.erb +28 -0
- data/web/views/retries.erb +74 -0
- data/web/views/retry.erb +34 -0
- data/web/views/scheduled.erb +54 -0
- data/web/views/scheduled_job_info.erb +8 -0
- metadata +408 -0
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'time'
|
3
|
+
require 'logger'
|
4
|
+
require 'fcntl'
|
5
|
+
|
6
|
+
module Sidekiq
|
7
|
+
module Logging
|
8
|
+
|
9
|
+
class Pretty < Logger::Formatter
|
10
|
+
SPACE = " "
|
11
|
+
|
12
|
+
# Provide a call() method that returns the formatted message.
|
13
|
+
def call(severity, time, program_name, message)
|
14
|
+
"#{time.utc.iso8601(3)} #{::Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
def context
|
18
|
+
c = Thread.current[:sidekiq_context]
|
19
|
+
" #{c.join(SPACE)}" if c && c.any?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class WithoutTimestamp < Pretty
|
24
|
+
def call(severity, time, program_name, message)
|
25
|
+
"#{::Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.with_context(msg)
|
30
|
+
Thread.current[:sidekiq_context] ||= []
|
31
|
+
Thread.current[:sidekiq_context] << msg
|
32
|
+
yield
|
33
|
+
ensure
|
34
|
+
Thread.current[:sidekiq_context].pop
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.initialize_logger(log_target = STDOUT)
|
38
|
+
oldlogger = defined?(@logger) ? @logger : nil
|
39
|
+
@logger = Logger.new(log_target)
|
40
|
+
@logger.level = Logger::INFO
|
41
|
+
@logger.formatter = ENV['DYNO'] ? WithoutTimestamp.new : Pretty.new
|
42
|
+
oldlogger.close if oldlogger && !$TESTING # don't want to close testing's STDOUT logging
|
43
|
+
@logger
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.logger
|
47
|
+
defined?(@logger) ? @logger : initialize_logger
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.logger=(log)
|
51
|
+
@logger = (log ? log : Logger.new(File::NULL))
|
52
|
+
end
|
53
|
+
|
54
|
+
# This reopens ALL logfiles in the process that have been rotated
|
55
|
+
# using logrotate(8) (without copytruncate) or similar tools.
|
56
|
+
# A +File+ object is considered for reopening if it is:
|
57
|
+
# 1) opened with the O_APPEND and O_WRONLY flags
|
58
|
+
# 2) the current open file handle does not match its original open path
|
59
|
+
# 3) unbuffered (as far as userspace buffering goes, not O_SYNC)
|
60
|
+
# Returns the number of files reopened
|
61
|
+
def self.reopen_logs
|
62
|
+
to_reopen = []
|
63
|
+
append_flags = File::WRONLY | File::APPEND
|
64
|
+
|
65
|
+
ObjectSpace.each_object(File) do |fp|
|
66
|
+
begin
|
67
|
+
if !fp.closed? && fp.stat.file? && fp.sync && (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
|
68
|
+
to_reopen << fp
|
69
|
+
end
|
70
|
+
rescue IOError, Errno::EBADF
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
nr = 0
|
75
|
+
to_reopen.each do |fp|
|
76
|
+
orig_st = begin
|
77
|
+
fp.stat
|
78
|
+
rescue IOError, Errno::EBADF
|
79
|
+
next
|
80
|
+
end
|
81
|
+
|
82
|
+
begin
|
83
|
+
b = File.stat(fp.path)
|
84
|
+
next if orig_st.ino == b.ino && orig_st.dev == b.dev
|
85
|
+
rescue Errno::ENOENT
|
86
|
+
end
|
87
|
+
|
88
|
+
begin
|
89
|
+
File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) }
|
90
|
+
fp.sync = true
|
91
|
+
nr += 1
|
92
|
+
rescue IOError, Errno::EBADF
|
93
|
+
# not much we can do...
|
94
|
+
end
|
95
|
+
end
|
96
|
+
nr
|
97
|
+
rescue RuntimeError => ex
|
98
|
+
# RuntimeError: ObjectSpace is disabled; each_object will only work with Class, pass -X+O to enable
|
99
|
+
puts "Unable to reopen logs: #{ex.message}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def logger
|
103
|
+
Sidekiq::Logging.logger
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: utf-8
|
3
|
+
require 'sidekiq/util'
|
4
|
+
require 'sidekiq/processor'
|
5
|
+
require 'sidekiq/fetch'
|
6
|
+
require 'thread'
|
7
|
+
|
8
|
+
module Sidekiq
|
9
|
+
|
10
|
+
##
|
11
|
+
# The Manager is the central coordination point in Sidekiq, controlling
|
12
|
+
# the lifecycle of the Processors and feeding them jobs as necessary.
|
13
|
+
#
|
14
|
+
# Tasks:
|
15
|
+
#
|
16
|
+
# 1. start: Spin up Processors.
|
17
|
+
# 3. processor_died: Handle job failure, throw away Processor, create new one.
|
18
|
+
# 4. quiet: shutdown idle Processors.
|
19
|
+
# 5. stop: hard stop the Processors by deadline.
|
20
|
+
#
|
21
|
+
# Note that only the last task requires its own Thread since it has to monitor
|
22
|
+
# the shutdown process. The other tasks are performed by other threads.
|
23
|
+
#
|
24
|
+
class Manager
|
25
|
+
include Util
|
26
|
+
|
27
|
+
attr_reader :workers
|
28
|
+
attr_reader :options
|
29
|
+
|
30
|
+
def initialize(options={})
|
31
|
+
logger.debug { options.inspect }
|
32
|
+
@options = options
|
33
|
+
@count = options[:concurrency] || 25
|
34
|
+
raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
|
35
|
+
|
36
|
+
@done = false
|
37
|
+
@workers = Set.new
|
38
|
+
@count.times do
|
39
|
+
@workers << Processor.new(self)
|
40
|
+
end
|
41
|
+
@plock = Mutex.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def start
|
45
|
+
@workers.each do |x|
|
46
|
+
x.start
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def quiet
|
51
|
+
return if @done
|
52
|
+
@done = true
|
53
|
+
|
54
|
+
logger.info { "Terminating quiet workers" }
|
55
|
+
@workers.each { |x| x.terminate }
|
56
|
+
fire_event(:quiet, true)
|
57
|
+
end
|
58
|
+
|
59
|
+
# hack for quicker development / testing environment #2774
|
60
|
+
PAUSE_TIME = STDOUT.tty? ? 0.1 : 0.5
|
61
|
+
|
62
|
+
def stop(deadline)
|
63
|
+
quiet
|
64
|
+
fire_event(:shutdown, true)
|
65
|
+
|
66
|
+
# some of the shutdown events can be async,
|
67
|
+
# we don't have any way to know when they're done but
|
68
|
+
# give them a little time to take effect
|
69
|
+
sleep PAUSE_TIME
|
70
|
+
return if @workers.empty?
|
71
|
+
|
72
|
+
logger.info { "Pausing to allow workers to finish..." }
|
73
|
+
remaining = deadline - Time.now
|
74
|
+
while remaining > PAUSE_TIME
|
75
|
+
return if @workers.empty?
|
76
|
+
sleep PAUSE_TIME
|
77
|
+
remaining = deadline - Time.now
|
78
|
+
end
|
79
|
+
return if @workers.empty?
|
80
|
+
|
81
|
+
hard_shutdown
|
82
|
+
end
|
83
|
+
|
84
|
+
def processor_stopped(processor)
|
85
|
+
@plock.synchronize do
|
86
|
+
@workers.delete(processor)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def processor_died(processor, reason)
|
91
|
+
@plock.synchronize do
|
92
|
+
@workers.delete(processor)
|
93
|
+
unless @done
|
94
|
+
p = Processor.new(self)
|
95
|
+
@workers << p
|
96
|
+
p.start
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def stopped?
|
102
|
+
@done
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def hard_shutdown
|
108
|
+
# We've reached the timeout and we still have busy workers.
|
109
|
+
# They must die but their jobs shall live on.
|
110
|
+
cleanup = nil
|
111
|
+
@plock.synchronize do
|
112
|
+
cleanup = @workers.dup
|
113
|
+
end
|
114
|
+
|
115
|
+
if cleanup.size > 0
|
116
|
+
jobs = cleanup.map {|p| p.job }.compact
|
117
|
+
|
118
|
+
logger.warn { "Terminating #{cleanup.size} busy worker threads" }
|
119
|
+
logger.warn { "Work still in progress #{jobs.inspect}" }
|
120
|
+
|
121
|
+
# Re-enqueue unfinished jobs
|
122
|
+
# NOTE: You may notice that we may push a job back to redis before
|
123
|
+
# the worker thread is terminated. This is ok because Sidekiq's
|
124
|
+
# contract says that jobs are run AT LEAST once. Process termination
|
125
|
+
# is delayed until we're certain the jobs are back in Redis because
|
126
|
+
# it is worse to lose a job than to run it twice.
|
127
|
+
strategy = (@options[:fetch] || Sidekiq::BasicFetch)
|
128
|
+
strategy.bulk_requeue(jobs, @options)
|
129
|
+
end
|
130
|
+
|
131
|
+
cleanup.each do |processor|
|
132
|
+
processor.kill
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Sidekiq
|
3
|
+
# Middleware is code configured to run before/after
|
4
|
+
# a message is processed. It is patterned after Rack
|
5
|
+
# middleware. Middleware exists for the client side
|
6
|
+
# (pushing jobs onto the queue) as well as the server
|
7
|
+
# side (when jobs are actually processed).
|
8
|
+
#
|
9
|
+
# To add middleware for the client:
|
10
|
+
#
|
11
|
+
# Sidekiq.configure_client do |config|
|
12
|
+
# config.client_middleware do |chain|
|
13
|
+
# chain.add MyClientHook
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# To modify middleware for the server, just call
|
18
|
+
# with another block:
|
19
|
+
#
|
20
|
+
# Sidekiq.configure_server do |config|
|
21
|
+
# config.server_middleware do |chain|
|
22
|
+
# chain.add MyServerHook
|
23
|
+
# chain.remove ActiveRecord
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# To insert immediately preceding another entry:
|
28
|
+
#
|
29
|
+
# Sidekiq.configure_client do |config|
|
30
|
+
# config.client_middleware do |chain|
|
31
|
+
# chain.insert_before ActiveRecord, MyClientHook
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# To insert immediately after another entry:
|
36
|
+
#
|
37
|
+
# Sidekiq.configure_client do |config|
|
38
|
+
# config.client_middleware do |chain|
|
39
|
+
# chain.insert_after ActiveRecord, MyClientHook
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# This is an example of a minimal server middleware:
|
44
|
+
#
|
45
|
+
# class MyServerHook
|
46
|
+
# def call(worker_instance, msg, queue)
|
47
|
+
# puts "Before work"
|
48
|
+
# yield
|
49
|
+
# puts "After work"
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# This is an example of a minimal client middleware, note
|
54
|
+
# the method must return the result or the job will not push
|
55
|
+
# to Redis:
|
56
|
+
#
|
57
|
+
# class MyClientHook
|
58
|
+
# def call(worker_class, msg, queue, redis_pool)
|
59
|
+
# puts "Before push"
|
60
|
+
# result = yield
|
61
|
+
# puts "After push"
|
62
|
+
# result
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
module Middleware
|
67
|
+
class Chain
|
68
|
+
include Enumerable
|
69
|
+
attr_reader :entries
|
70
|
+
|
71
|
+
def initialize_copy(copy)
|
72
|
+
copy.instance_variable_set(:@entries, entries.dup)
|
73
|
+
end
|
74
|
+
|
75
|
+
def each(&block)
|
76
|
+
entries.each(&block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def initialize
|
80
|
+
@entries = []
|
81
|
+
yield self if block_given?
|
82
|
+
end
|
83
|
+
|
84
|
+
def remove(klass)
|
85
|
+
entries.delete_if { |entry| entry.klass == klass }
|
86
|
+
end
|
87
|
+
|
88
|
+
def add(klass, *args)
|
89
|
+
remove(klass) if exists?(klass)
|
90
|
+
entries << Entry.new(klass, *args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def prepend(klass, *args)
|
94
|
+
remove(klass) if exists?(klass)
|
95
|
+
entries.insert(0, Entry.new(klass, *args))
|
96
|
+
end
|
97
|
+
|
98
|
+
def insert_before(oldklass, newklass, *args)
|
99
|
+
i = entries.index { |entry| entry.klass == newklass }
|
100
|
+
new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
|
101
|
+
i = entries.index { |entry| entry.klass == oldklass } || 0
|
102
|
+
entries.insert(i, new_entry)
|
103
|
+
end
|
104
|
+
|
105
|
+
def insert_after(oldklass, newklass, *args)
|
106
|
+
i = entries.index { |entry| entry.klass == newklass }
|
107
|
+
new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
|
108
|
+
i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1
|
109
|
+
entries.insert(i+1, new_entry)
|
110
|
+
end
|
111
|
+
|
112
|
+
def exists?(klass)
|
113
|
+
any? { |entry| entry.klass == klass }
|
114
|
+
end
|
115
|
+
|
116
|
+
def retrieve
|
117
|
+
map(&:make_new)
|
118
|
+
end
|
119
|
+
|
120
|
+
def clear
|
121
|
+
entries.clear
|
122
|
+
end
|
123
|
+
|
124
|
+
def invoke(*args)
|
125
|
+
chain = retrieve.dup
|
126
|
+
traverse_chain = lambda do
|
127
|
+
if chain.empty?
|
128
|
+
yield
|
129
|
+
else
|
130
|
+
chain.shift.call(*args, &traverse_chain)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
traverse_chain.call
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Entry
|
138
|
+
attr_reader :klass
|
139
|
+
|
140
|
+
def initialize(klass, *args)
|
141
|
+
@klass = klass
|
142
|
+
@args = args
|
143
|
+
end
|
144
|
+
|
145
|
+
def make_new
|
146
|
+
@klass.new(*@args)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Simple middleware to save the current locale and restore it when the job executes.
|
4
|
+
# Use it by requiring it in your initializer:
|
5
|
+
#
|
6
|
+
# require 'sidekiq/middleware/i18n'
|
7
|
+
#
|
8
|
+
module Sidekiq::Middleware::I18n
|
9
|
+
# Get the current locale and store it in the message
|
10
|
+
# to be sent to Sidekiq.
|
11
|
+
class Client
|
12
|
+
def call(worker_class, msg, queue, redis_pool)
|
13
|
+
msg['locale'] ||= I18n.locale
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Pull the msg locale out and set the current thread to use it.
|
19
|
+
class Server
|
20
|
+
def call(worker, msg, queue)
|
21
|
+
I18n.locale = msg['locale'] || I18n.default_locale
|
22
|
+
yield
|
23
|
+
ensure
|
24
|
+
I18n.locale = I18n.default_locale
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
Sidekiq.configure_client do |config|
|
30
|
+
config.client_middleware do |chain|
|
31
|
+
chain.add Sidekiq::Middleware::I18n::Client
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Sidekiq.configure_server do |config|
|
36
|
+
config.client_middleware do |chain|
|
37
|
+
chain.add Sidekiq::Middleware::I18n::Client
|
38
|
+
end
|
39
|
+
config.server_middleware do |chain|
|
40
|
+
chain.add Sidekiq::Middleware::I18n::Server
|
41
|
+
end
|
42
|
+
end
|