kulesa-sidekiq 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rvmrc +4 -0
- data/COMM-LICENSE +83 -0
- data/Changes.md +207 -0
- data/Gemfile +12 -0
- data/LICENSE +22 -0
- data/README.md +61 -0
- data/Rakefile +9 -0
- data/bin/client +7 -0
- data/bin/sidekiq +14 -0
- data/bin/sidekiqctl +74 -0
- data/config.ru +18 -0
- data/examples/chef/cookbooks/sidekiq/README.rdoc +11 -0
- data/examples/chef/cookbooks/sidekiq/recipes/default.rb +55 -0
- data/examples/chef/cookbooks/sidekiq/templates/default/monitrc.conf.erb +8 -0
- data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.erb +219 -0
- data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.yml.erb +22 -0
- data/examples/clockwork.rb +44 -0
- data/examples/config.yml +10 -0
- data/examples/monitrc.conf +6 -0
- data/examples/por.rb +27 -0
- data/examples/scheduling.rb +37 -0
- data/examples/sinkiq.rb +59 -0
- data/examples/web-ui.png +0 -0
- data/lib/sidekiq.rb +98 -0
- data/lib/sidekiq/capistrano.rb +35 -0
- data/lib/sidekiq/cli.rb +214 -0
- data/lib/sidekiq/client.rb +72 -0
- data/lib/sidekiq/extensions/action_mailer.rb +26 -0
- data/lib/sidekiq/extensions/active_record.rb +27 -0
- data/lib/sidekiq/extensions/generic_proxy.rb +21 -0
- data/lib/sidekiq/fetch.rb +76 -0
- data/lib/sidekiq/logging.rb +46 -0
- data/lib/sidekiq/manager.rb +163 -0
- data/lib/sidekiq/middleware/chain.rb +96 -0
- data/lib/sidekiq/middleware/client/unique_jobs.rb +36 -0
- data/lib/sidekiq/middleware/server/active_record.rb +13 -0
- data/lib/sidekiq/middleware/server/exception_handler.rb +38 -0
- data/lib/sidekiq/middleware/server/failure_jobs.rb +25 -0
- data/lib/sidekiq/middleware/server/logging.rb +31 -0
- data/lib/sidekiq/middleware/server/retry_jobs.rb +69 -0
- data/lib/sidekiq/middleware/server/timeout.rb +21 -0
- data/lib/sidekiq/middleware/server/unique_jobs.rb +17 -0
- data/lib/sidekiq/processor.rb +92 -0
- data/lib/sidekiq/rails.rb +21 -0
- data/lib/sidekiq/redis_connection.rb +27 -0
- data/lib/sidekiq/retry.rb +59 -0
- data/lib/sidekiq/testing.rb +44 -0
- data/lib/sidekiq/testing/inline.rb +37 -0
- data/lib/sidekiq/util.rb +40 -0
- data/lib/sidekiq/version.rb +3 -0
- data/lib/sidekiq/web.rb +185 -0
- data/lib/sidekiq/worker.rb +62 -0
- data/lib/sidekiq/yaml_patch.rb +21 -0
- data/myapp/.gitignore +15 -0
- data/myapp/Capfile +5 -0
- data/myapp/Gemfile +19 -0
- data/myapp/Rakefile +7 -0
- data/myapp/app/controllers/application_controller.rb +3 -0
- data/myapp/app/controllers/work_controller.rb +38 -0
- data/myapp/app/helpers/application_helper.rb +2 -0
- data/myapp/app/mailers/.gitkeep +0 -0
- data/myapp/app/mailers/user_mailer.rb +9 -0
- data/myapp/app/models/.gitkeep +0 -0
- data/myapp/app/models/post.rb +5 -0
- data/myapp/app/views/layouts/application.html.erb +14 -0
- data/myapp/app/views/user_mailer/greetings.html.erb +3 -0
- data/myapp/app/views/work/index.html.erb +1 -0
- data/myapp/app/workers/hard_worker.rb +10 -0
- data/myapp/config.ru +4 -0
- data/myapp/config/application.rb +59 -0
- data/myapp/config/boot.rb +6 -0
- data/myapp/config/database.yml +25 -0
- data/myapp/config/deploy.rb +15 -0
- data/myapp/config/environment.rb +5 -0
- data/myapp/config/environments/development.rb +38 -0
- data/myapp/config/environments/production.rb +67 -0
- data/myapp/config/environments/test.rb +37 -0
- data/myapp/config/initializers/backtrace_silencers.rb +7 -0
- data/myapp/config/initializers/inflections.rb +15 -0
- data/myapp/config/initializers/mime_types.rb +5 -0
- data/myapp/config/initializers/secret_token.rb +7 -0
- data/myapp/config/initializers/session_store.rb +8 -0
- data/myapp/config/initializers/sidekiq.rb +6 -0
- data/myapp/config/initializers/wrap_parameters.rb +14 -0
- data/myapp/config/locales/en.yml +5 -0
- data/myapp/config/routes.rb +10 -0
- data/myapp/db/migrate/20120123214055_create_posts.rb +10 -0
- data/myapp/db/seeds.rb +7 -0
- data/myapp/lib/assets/.gitkeep +0 -0
- data/myapp/lib/tasks/.gitkeep +0 -0
- data/myapp/log/.gitkeep +0 -0
- data/myapp/script/rails +6 -0
- data/sidekiq.gemspec +27 -0
- data/test/config.yml +9 -0
- data/test/fake_env.rb +0 -0
- data/test/helper.rb +16 -0
- data/test/test_cli.rb +168 -0
- data/test/test_client.rb +119 -0
- data/test/test_extensions.rb +69 -0
- data/test/test_manager.rb +51 -0
- data/test/test_middleware.rb +92 -0
- data/test/test_processor.rb +32 -0
- data/test/test_retry.rb +125 -0
- data/test/test_stats.rb +68 -0
- data/test/test_testing.rb +97 -0
- data/test/test_testing_inline.rb +75 -0
- data/test/test_web.rb +122 -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/javascripts/application.js +20 -0
- data/web/assets/javascripts/vendor/bootstrap.js +12 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-alert.js +91 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-button.js +98 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-carousel.js +154 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-collapse.js +136 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-dropdown.js +92 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-modal.js +210 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-popover.js +95 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-scrollspy.js +125 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-tab.js +130 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-tooltip.js +270 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-transition.js +51 -0
- data/web/assets/javascripts/vendor/bootstrap/bootstrap-typeahead.js +271 -0
- data/web/assets/javascripts/vendor/jquery.js +9266 -0
- data/web/assets/javascripts/vendor/jquery.timeago.js +148 -0
- data/web/assets/stylesheets/application.css +27 -0
- data/web/assets/stylesheets/vendor/bootstrap-responsive.css +567 -0
- data/web/assets/stylesheets/vendor/bootstrap.css +3365 -0
- data/web/views/index.slim +48 -0
- data/web/views/layout.slim +26 -0
- data/web/views/queue.slim +11 -0
- data/web/views/retries.slim +29 -0
- data/web/views/retry.slim +52 -0
- metadata +371 -0
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module Sidekiq
|
5
|
+
module Middleware
|
6
|
+
module Client
|
7
|
+
class UniqueJobs
|
8
|
+
HASH_KEY_EXPIRATION = 30 * 60
|
9
|
+
|
10
|
+
def call(worker_class, item, queue)
|
11
|
+
enabled = worker_class.get_sidekiq_options['unique']
|
12
|
+
if enabled
|
13
|
+
payload_hash = Digest::MD5.hexdigest(Sidekiq.dump_json(item))
|
14
|
+
unique = false
|
15
|
+
|
16
|
+
Sidekiq.redis do |conn|
|
17
|
+
conn.watch(payload_hash)
|
18
|
+
|
19
|
+
if conn.get(payload_hash)
|
20
|
+
conn.unwatch
|
21
|
+
else
|
22
|
+
unique = conn.multi do
|
23
|
+
conn.setex(payload_hash, HASH_KEY_EXPIRATION, 1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
yield if unique
|
28
|
+
else
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'sidekiq/util'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Middleware
|
5
|
+
module Server
|
6
|
+
class ExceptionHandler
|
7
|
+
include Util
|
8
|
+
def call(*args)
|
9
|
+
yield
|
10
|
+
rescue => ex
|
11
|
+
logger.warn ex
|
12
|
+
logger.warn ex.backtrace.join("\n")
|
13
|
+
send_to_airbrake(args[1], ex) if defined?(::Airbrake)
|
14
|
+
send_to_exceptional(args[1], ex) if defined?(::Exceptional)
|
15
|
+
send_to_exception_notifier(args[1], ex) if defined?(::ExceptionNotifier)
|
16
|
+
raise
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def send_to_airbrake(msg, ex)
|
22
|
+
::Airbrake.notify(ex, :parameters => msg)
|
23
|
+
end
|
24
|
+
|
25
|
+
def send_to_exceptional(msg, ex)
|
26
|
+
if ::Exceptional::Config.should_send_to_api?
|
27
|
+
::Exceptional.context(msg)
|
28
|
+
::Exceptional::Remote.error(::Exceptional::ExceptionData.new(ex))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def send_to_exception_notifier(msg, ex)
|
33
|
+
::ExceptionNotifier::Notifier.background_exception_notification(ex, :data => { :message => msg })
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Middleware
|
5
|
+
module Server
|
6
|
+
class FailureJobs
|
7
|
+
def call(*args)
|
8
|
+
yield
|
9
|
+
rescue => e
|
10
|
+
data = {
|
11
|
+
:failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S %Z"),
|
12
|
+
:payload => args[1],
|
13
|
+
:exception => e.class.to_s,
|
14
|
+
:error => e.to_s,
|
15
|
+
:backtrace => e.backtrace,
|
16
|
+
:worker => args[1]['class'],
|
17
|
+
:queue => args[2]
|
18
|
+
}
|
19
|
+
Sidekiq.redis {|conn| conn.rpush(:failed, Sidekiq.dump_json(data)) }
|
20
|
+
raise
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
module Middleware
|
3
|
+
module Server
|
4
|
+
class Logging
|
5
|
+
|
6
|
+
def call(*args)
|
7
|
+
Sidekiq::Logging.with_context("#{args[0].class.to_s} MSG-#{args[0].object_id.to_s(36)}") do
|
8
|
+
begin
|
9
|
+
start = Time.now
|
10
|
+
logger.info { "start" }
|
11
|
+
yield
|
12
|
+
logger.info { "done: #{elapsed(start)} sec" }
|
13
|
+
rescue
|
14
|
+
logger.info { "fail: #{elapsed(start)} sec" }
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def elapsed(start)
|
21
|
+
(Time.now - start).to_f.round(3)
|
22
|
+
end
|
23
|
+
|
24
|
+
def logger
|
25
|
+
Sidekiq.logger
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
require 'sidekiq/retry'
|
4
|
+
|
5
|
+
module Sidekiq
|
6
|
+
module Middleware
|
7
|
+
module Server
|
8
|
+
##
|
9
|
+
# Automatically retry jobs that fail in Sidekiq.
|
10
|
+
# A message looks like:
|
11
|
+
#
|
12
|
+
# { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'] }
|
13
|
+
#
|
14
|
+
# We'll add a bit more data to the message to support retries:
|
15
|
+
#
|
16
|
+
# * 'queue' - the queue to use
|
17
|
+
# * 'retry_count' - number of times we've retried so far.
|
18
|
+
# * 'error_message' - the message from the exception
|
19
|
+
# * 'error_class' - the exception class
|
20
|
+
# * 'failed_at' - the first time it failed
|
21
|
+
# * 'retried_at' - the last time it was retried
|
22
|
+
#
|
23
|
+
# We don't store the backtrace as that can add a lot of overhead
|
24
|
+
# to the message and everyone is using Airbrake, right?
|
25
|
+
class RetryJobs
|
26
|
+
include Sidekiq::Util
|
27
|
+
include Sidekiq::Retry
|
28
|
+
|
29
|
+
def call(worker, msg, queue)
|
30
|
+
yield
|
31
|
+
rescue => e
|
32
|
+
raise unless msg['retry']
|
33
|
+
|
34
|
+
msg['queue'] = queue
|
35
|
+
msg['error_message'] = e.message
|
36
|
+
msg['error_class'] = e.class.name
|
37
|
+
count = if msg['retry_count']
|
38
|
+
msg['retried_at'] = Time.now.utc
|
39
|
+
msg['retry_count'] += 1
|
40
|
+
else
|
41
|
+
msg['failed_at'] = Time.now.utc
|
42
|
+
msg['retry_count'] = 0
|
43
|
+
end
|
44
|
+
|
45
|
+
if msg['backtrace'] == true
|
46
|
+
msg['error_backtrace'] = e.backtrace
|
47
|
+
elsif msg['backtrace'].to_i != 0
|
48
|
+
msg['error_backtrace'] = e.backtrace[0..msg['backtrace'].to_i]
|
49
|
+
end
|
50
|
+
|
51
|
+
if count <= MAX_COUNT
|
52
|
+
delay = DELAY.call(count)
|
53
|
+
logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
|
54
|
+
retry_at = Time.now.to_f + delay
|
55
|
+
payload = Sidekiq.dump_json(msg)
|
56
|
+
Sidekiq.redis do |conn|
|
57
|
+
conn.zadd('retry', retry_at.to_s, payload)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
# Goodbye dear message, you (re)tried your best I'm sure.
|
61
|
+
logger.debug { "Dropping message after hitting the retry maximum: #{msg}" }
|
62
|
+
end
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Middleware
|
5
|
+
module Server
|
6
|
+
class Timeout
|
7
|
+
|
8
|
+
def call(worker, msg, queue)
|
9
|
+
if msg['timeout'] && msg['timeout'].to_i != 0
|
10
|
+
::Timeout.timeout(msg['timeout'].to_i) do
|
11
|
+
yield
|
12
|
+
end
|
13
|
+
else
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Sidekiq
|
4
|
+
module Middleware
|
5
|
+
module Server
|
6
|
+
class UniqueJobs
|
7
|
+
def call(*args)
|
8
|
+
yield
|
9
|
+
ensure
|
10
|
+
json = Sidekiq.dump_json(args[1])
|
11
|
+
hash = Digest::MD5.hexdigest(json)
|
12
|
+
Sidekiq.redis {|conn| conn.del(hash) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'celluloid'
|
2
|
+
require 'multi_json'
|
3
|
+
require 'sidekiq/util'
|
4
|
+
|
5
|
+
require 'sidekiq/middleware/server/active_record'
|
6
|
+
require 'sidekiq/middleware/server/exception_handler'
|
7
|
+
require 'sidekiq/middleware/server/retry_jobs'
|
8
|
+
require 'sidekiq/middleware/server/logging'
|
9
|
+
require 'sidekiq/middleware/server/timeout'
|
10
|
+
|
11
|
+
module Sidekiq
|
12
|
+
class Processor
|
13
|
+
include Util
|
14
|
+
include Celluloid
|
15
|
+
|
16
|
+
def self.default_middleware
|
17
|
+
Middleware::Chain.new do |m|
|
18
|
+
m.add Middleware::Server::ExceptionHandler
|
19
|
+
m.add Middleware::Server::Logging
|
20
|
+
m.add Middleware::Server::RetryJobs
|
21
|
+
m.add Middleware::Server::ActiveRecord
|
22
|
+
m.add Middleware::Server::Timeout
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(boss)
|
27
|
+
@boss = boss
|
28
|
+
end
|
29
|
+
|
30
|
+
def process(msg, queue)
|
31
|
+
klass = constantize(msg['class'])
|
32
|
+
worker = klass.new
|
33
|
+
defer do
|
34
|
+
stats(worker, msg, queue) do
|
35
|
+
Sidekiq.server_middleware.invoke(worker, msg, queue) do
|
36
|
+
worker.perform(*msg['args'])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
@boss.processor_done!(current_actor)
|
41
|
+
end
|
42
|
+
|
43
|
+
# See http://github.com/tarcieri/celluloid/issues/22
|
44
|
+
def inspect
|
45
|
+
"#<Processor #{to_s}>"
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
@str ||= "#{hostname}:#{process_id}-#{Thread.current.object_id}:default"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def stats(worker, msg, queue)
|
55
|
+
redis do |conn|
|
56
|
+
conn.multi do
|
57
|
+
conn.sadd('workers', self)
|
58
|
+
conn.setex("worker:#{self}:started", EXPIRY, Time.now.to_s)
|
59
|
+
hash = {:queue => queue, :payload => msg, :run_at => Time.now.strftime("%Y/%m/%d %H:%M:%S %Z")}
|
60
|
+
conn.setex("worker:#{self}", EXPIRY, Sidekiq.dump_json(hash))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
dying = false
|
65
|
+
begin
|
66
|
+
yield
|
67
|
+
rescue Exception
|
68
|
+
dying = true
|
69
|
+
redis do |conn|
|
70
|
+
conn.multi do
|
71
|
+
conn.incrby("stat:failed", 1)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
raise
|
75
|
+
ensure
|
76
|
+
redis do |conn|
|
77
|
+
conn.multi do
|
78
|
+
conn.srem("workers", self)
|
79
|
+
conn.del("worker:#{self}")
|
80
|
+
conn.del("worker:#{self}:started")
|
81
|
+
conn.incrby("stat:processed", 1)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
def hostname
|
89
|
+
@h ||= `hostname`.strip
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Sidekiq
|
2
|
+
def self.hook_rails!
|
3
|
+
return unless Sidekiq.options[:enable_rails_extensions]
|
4
|
+
if defined?(ActiveRecord)
|
5
|
+
ActiveRecord::Base.extend(Sidekiq::Extensions::ActiveRecord)
|
6
|
+
ActiveRecord::Base.send(:include, Sidekiq::Extensions::ActiveRecord)
|
7
|
+
end
|
8
|
+
|
9
|
+
if defined?(ActionMailer)
|
10
|
+
ActionMailer::Base.extend(Sidekiq::Extensions::ActionMailer)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Rails < ::Rails::Engine
|
15
|
+
config.autoload_paths << File.expand_path("#{config.root}/app/workers") if File.exist?("#{config.root}/app/workers")
|
16
|
+
|
17
|
+
initializer 'sidekiq' do
|
18
|
+
Sidekiq.hook_rails!
|
19
|
+
end
|
20
|
+
end if defined?(::Rails)
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'connection_pool'
|
2
|
+
require 'redis'
|
3
|
+
require 'redis/namespace'
|
4
|
+
|
5
|
+
module Sidekiq
|
6
|
+
class RedisConnection
|
7
|
+
def self.create(options={})
|
8
|
+
url = options[:url] || ENV['REDISTOGO_URL'] || 'redis://localhost:6379/0'
|
9
|
+
# need a connection for Fetcher and Retry
|
10
|
+
size = options[:size] || (Sidekiq.server? ? (Sidekiq.options[:concurrency] + 2) : 5)
|
11
|
+
|
12
|
+
ConnectionPool.new(:timeout => 1, :size => size) do
|
13
|
+
build_client(url, options[:namespace])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.build_client(url, namespace)
|
18
|
+
client = Redis.connect(:url => url)
|
19
|
+
if namespace
|
20
|
+
Redis::Namespace.new(namespace, :redis => client)
|
21
|
+
else
|
22
|
+
client
|
23
|
+
end
|
24
|
+
end
|
25
|
+
private_class_method :build_client
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'sidekiq'
|
2
|
+
require 'sidekiq/util'
|
3
|
+
require 'celluloid'
|
4
|
+
require 'multi_json'
|
5
|
+
|
6
|
+
module Sidekiq
|
7
|
+
##
|
8
|
+
# Sidekiq's retry support assumes a typical development lifecycle:
|
9
|
+
# 0. push some code changes with a bug in it
|
10
|
+
# 1. bug causes message processing to fail, sidekiq's middleware captures
|
11
|
+
# the message and pushes it onto a retry queue
|
12
|
+
# 2. sidekiq retries messages in the retry queue multiple times with
|
13
|
+
# an exponential delay, the message continues to fail
|
14
|
+
# 3. after a few days, a developer deploys a fix. the message is
|
15
|
+
# reprocessed successfully.
|
16
|
+
# 4. if 3 never happens, sidekiq will eventually give up and throw the
|
17
|
+
# message away.
|
18
|
+
module Retry
|
19
|
+
|
20
|
+
# delayed_job uses the same basic formula
|
21
|
+
MAX_COUNT = 25
|
22
|
+
DELAY = proc { |count| (count ** 4) + 15 }
|
23
|
+
POLL_INTERVAL = 15
|
24
|
+
|
25
|
+
##
|
26
|
+
# The Poller checks Redis every N seconds for messages in the retry
|
27
|
+
# set have passed their retry timestamp and should be retried. If so, it
|
28
|
+
# just pops the message back onto its original queue so the
|
29
|
+
# workers can pick it up like any other message.
|
30
|
+
class Poller
|
31
|
+
include Celluloid
|
32
|
+
include Sidekiq::Util
|
33
|
+
|
34
|
+
def poll
|
35
|
+
watchdog('retry poller thread died!') do
|
36
|
+
|
37
|
+
Sidekiq.redis do |conn|
|
38
|
+
# A message's "score" in Redis is the time at which it should be retried.
|
39
|
+
# Just check Redis for the set of messages with a timestamp before now.
|
40
|
+
messages = nil
|
41
|
+
now = Time.now.to_f.to_s
|
42
|
+
(messages, _) = conn.multi do
|
43
|
+
conn.zrangebyscore('retry', '-inf', now)
|
44
|
+
conn.zremrangebyscore('retry', '-inf', now)
|
45
|
+
end
|
46
|
+
|
47
|
+
messages.each do |message|
|
48
|
+
logger.debug { "Retrying #{message}" }
|
49
|
+
msg = Sidekiq.load_json(message)
|
50
|
+
conn.rpush("queue:#{msg['queue']}", message)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
after(POLL_INTERVAL) { poll }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|