kulesa-sidekiq 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/.gitignore +6 -0
  2. data/.rvmrc +4 -0
  3. data/COMM-LICENSE +83 -0
  4. data/Changes.md +207 -0
  5. data/Gemfile +12 -0
  6. data/LICENSE +22 -0
  7. data/README.md +61 -0
  8. data/Rakefile +9 -0
  9. data/bin/client +7 -0
  10. data/bin/sidekiq +14 -0
  11. data/bin/sidekiqctl +74 -0
  12. data/config.ru +18 -0
  13. data/examples/chef/cookbooks/sidekiq/README.rdoc +11 -0
  14. data/examples/chef/cookbooks/sidekiq/recipes/default.rb +55 -0
  15. data/examples/chef/cookbooks/sidekiq/templates/default/monitrc.conf.erb +8 -0
  16. data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.erb +219 -0
  17. data/examples/chef/cookbooks/sidekiq/templates/default/sidekiq.yml.erb +22 -0
  18. data/examples/clockwork.rb +44 -0
  19. data/examples/config.yml +10 -0
  20. data/examples/monitrc.conf +6 -0
  21. data/examples/por.rb +27 -0
  22. data/examples/scheduling.rb +37 -0
  23. data/examples/sinkiq.rb +59 -0
  24. data/examples/web-ui.png +0 -0
  25. data/lib/sidekiq.rb +98 -0
  26. data/lib/sidekiq/capistrano.rb +35 -0
  27. data/lib/sidekiq/cli.rb +214 -0
  28. data/lib/sidekiq/client.rb +72 -0
  29. data/lib/sidekiq/extensions/action_mailer.rb +26 -0
  30. data/lib/sidekiq/extensions/active_record.rb +27 -0
  31. data/lib/sidekiq/extensions/generic_proxy.rb +21 -0
  32. data/lib/sidekiq/fetch.rb +76 -0
  33. data/lib/sidekiq/logging.rb +46 -0
  34. data/lib/sidekiq/manager.rb +163 -0
  35. data/lib/sidekiq/middleware/chain.rb +96 -0
  36. data/lib/sidekiq/middleware/client/unique_jobs.rb +36 -0
  37. data/lib/sidekiq/middleware/server/active_record.rb +13 -0
  38. data/lib/sidekiq/middleware/server/exception_handler.rb +38 -0
  39. data/lib/sidekiq/middleware/server/failure_jobs.rb +25 -0
  40. data/lib/sidekiq/middleware/server/logging.rb +31 -0
  41. data/lib/sidekiq/middleware/server/retry_jobs.rb +69 -0
  42. data/lib/sidekiq/middleware/server/timeout.rb +21 -0
  43. data/lib/sidekiq/middleware/server/unique_jobs.rb +17 -0
  44. data/lib/sidekiq/processor.rb +92 -0
  45. data/lib/sidekiq/rails.rb +21 -0
  46. data/lib/sidekiq/redis_connection.rb +27 -0
  47. data/lib/sidekiq/retry.rb +59 -0
  48. data/lib/sidekiq/testing.rb +44 -0
  49. data/lib/sidekiq/testing/inline.rb +37 -0
  50. data/lib/sidekiq/util.rb +40 -0
  51. data/lib/sidekiq/version.rb +3 -0
  52. data/lib/sidekiq/web.rb +185 -0
  53. data/lib/sidekiq/worker.rb +62 -0
  54. data/lib/sidekiq/yaml_patch.rb +21 -0
  55. data/myapp/.gitignore +15 -0
  56. data/myapp/Capfile +5 -0
  57. data/myapp/Gemfile +19 -0
  58. data/myapp/Rakefile +7 -0
  59. data/myapp/app/controllers/application_controller.rb +3 -0
  60. data/myapp/app/controllers/work_controller.rb +38 -0
  61. data/myapp/app/helpers/application_helper.rb +2 -0
  62. data/myapp/app/mailers/.gitkeep +0 -0
  63. data/myapp/app/mailers/user_mailer.rb +9 -0
  64. data/myapp/app/models/.gitkeep +0 -0
  65. data/myapp/app/models/post.rb +5 -0
  66. data/myapp/app/views/layouts/application.html.erb +14 -0
  67. data/myapp/app/views/user_mailer/greetings.html.erb +3 -0
  68. data/myapp/app/views/work/index.html.erb +1 -0
  69. data/myapp/app/workers/hard_worker.rb +10 -0
  70. data/myapp/config.ru +4 -0
  71. data/myapp/config/application.rb +59 -0
  72. data/myapp/config/boot.rb +6 -0
  73. data/myapp/config/database.yml +25 -0
  74. data/myapp/config/deploy.rb +15 -0
  75. data/myapp/config/environment.rb +5 -0
  76. data/myapp/config/environments/development.rb +38 -0
  77. data/myapp/config/environments/production.rb +67 -0
  78. data/myapp/config/environments/test.rb +37 -0
  79. data/myapp/config/initializers/backtrace_silencers.rb +7 -0
  80. data/myapp/config/initializers/inflections.rb +15 -0
  81. data/myapp/config/initializers/mime_types.rb +5 -0
  82. data/myapp/config/initializers/secret_token.rb +7 -0
  83. data/myapp/config/initializers/session_store.rb +8 -0
  84. data/myapp/config/initializers/sidekiq.rb +6 -0
  85. data/myapp/config/initializers/wrap_parameters.rb +14 -0
  86. data/myapp/config/locales/en.yml +5 -0
  87. data/myapp/config/routes.rb +10 -0
  88. data/myapp/db/migrate/20120123214055_create_posts.rb +10 -0
  89. data/myapp/db/seeds.rb +7 -0
  90. data/myapp/lib/assets/.gitkeep +0 -0
  91. data/myapp/lib/tasks/.gitkeep +0 -0
  92. data/myapp/log/.gitkeep +0 -0
  93. data/myapp/script/rails +6 -0
  94. data/sidekiq.gemspec +27 -0
  95. data/test/config.yml +9 -0
  96. data/test/fake_env.rb +0 -0
  97. data/test/helper.rb +16 -0
  98. data/test/test_cli.rb +168 -0
  99. data/test/test_client.rb +119 -0
  100. data/test/test_extensions.rb +69 -0
  101. data/test/test_manager.rb +51 -0
  102. data/test/test_middleware.rb +92 -0
  103. data/test/test_processor.rb +32 -0
  104. data/test/test_retry.rb +125 -0
  105. data/test/test_stats.rb +68 -0
  106. data/test/test_testing.rb +97 -0
  107. data/test/test_testing_inline.rb +75 -0
  108. data/test/test_web.rb +122 -0
  109. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  110. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  111. data/web/assets/javascripts/application.js +20 -0
  112. data/web/assets/javascripts/vendor/bootstrap.js +12 -0
  113. data/web/assets/javascripts/vendor/bootstrap/bootstrap-alert.js +91 -0
  114. data/web/assets/javascripts/vendor/bootstrap/bootstrap-button.js +98 -0
  115. data/web/assets/javascripts/vendor/bootstrap/bootstrap-carousel.js +154 -0
  116. data/web/assets/javascripts/vendor/bootstrap/bootstrap-collapse.js +136 -0
  117. data/web/assets/javascripts/vendor/bootstrap/bootstrap-dropdown.js +92 -0
  118. data/web/assets/javascripts/vendor/bootstrap/bootstrap-modal.js +210 -0
  119. data/web/assets/javascripts/vendor/bootstrap/bootstrap-popover.js +95 -0
  120. data/web/assets/javascripts/vendor/bootstrap/bootstrap-scrollspy.js +125 -0
  121. data/web/assets/javascripts/vendor/bootstrap/bootstrap-tab.js +130 -0
  122. data/web/assets/javascripts/vendor/bootstrap/bootstrap-tooltip.js +270 -0
  123. data/web/assets/javascripts/vendor/bootstrap/bootstrap-transition.js +51 -0
  124. data/web/assets/javascripts/vendor/bootstrap/bootstrap-typeahead.js +271 -0
  125. data/web/assets/javascripts/vendor/jquery.js +9266 -0
  126. data/web/assets/javascripts/vendor/jquery.timeago.js +148 -0
  127. data/web/assets/stylesheets/application.css +27 -0
  128. data/web/assets/stylesheets/vendor/bootstrap-responsive.css +567 -0
  129. data/web/assets/stylesheets/vendor/bootstrap.css +3365 -0
  130. data/web/views/index.slim +48 -0
  131. data/web/views/layout.slim +26 -0
  132. data/web/views/queue.slim +11 -0
  133. data/web/views/retries.slim +29 -0
  134. data/web/views/retry.slim +52 -0
  135. 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,13 @@
1
+ module Sidekiq
2
+ module Middleware
3
+ module Server
4
+ class ActiveRecord
5
+ def call(*args)
6
+ yield
7
+ ensure
8
+ ::ActiveRecord::Base.clear_active_connections! if defined?(::ActiveRecord)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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