kulesa-sidekiq 1.2.2

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 (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