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,26 @@
1
+ require 'sidekiq/extensions/generic_proxy'
2
+
3
+ module Sidekiq
4
+ module Extensions
5
+ ##
6
+ # Adds a 'delay' method to ActionMailer to offload arbitrary email
7
+ # delivery to Sidekiq. Example:
8
+ #
9
+ # UserMailer.delay.send_welcome_email(new_user)
10
+ class DelayedMailer
11
+ include Sidekiq::Worker
12
+
13
+ def perform(yml)
14
+ (target, method_name, args) = YAML.load(yml)
15
+ target.send(method_name, *args).deliver
16
+ end
17
+ end
18
+
19
+ module ActionMailer
20
+ def delay
21
+ Proxy.new(DelayedMailer, self)
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ require 'sidekiq/extensions/generic_proxy'
2
+
3
+ module Sidekiq
4
+ module Extensions
5
+ ##
6
+ # Adds a 'delay' method to ActiveRecord to offload arbitrary method
7
+ # execution to Sidekiq. Examples:
8
+ #
9
+ # User.delay.delete_inactive
10
+ # User.recent_signups.each { |user| user.delay.mark_as_awesome }
11
+ class DelayedModel
12
+ include Sidekiq::Worker
13
+
14
+ def perform(yml)
15
+ (target, method_name, args) = YAML.load(yml)
16
+ target.send(method_name, *args)
17
+ end
18
+ end
19
+
20
+ module ActiveRecord
21
+ def delay
22
+ Proxy.new(DelayedModel, self)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ module Sidekiq
2
+ module Extensions
3
+ class Proxy < (RUBY_VERSION < '1.9' ? Object : BasicObject)
4
+ def initialize(performable, target)
5
+ @performable = performable
6
+ @target = target
7
+ end
8
+
9
+ def method_missing(name, *args)
10
+ # Sidekiq has a limitation in that its message must be JSON.
11
+ # JSON can't round trip real Ruby objects so we use YAML to
12
+ # serialize the objects to a String. The YAML will be converted
13
+ # to JSON and then deserialized on the other side back into a
14
+ # Ruby object.
15
+ obj = [@target, name, args]
16
+ @performable.perform_async(::YAML.dump(obj))
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,76 @@
1
+ require 'sidekiq'
2
+ require 'celluloid'
3
+
4
+ module Sidekiq
5
+ ##
6
+ # The Fetcher blocks on Redis, waiting for a message to process
7
+ # from the queues. It gets the message and hands it to the Manager
8
+ # to assign to a ready Processor.
9
+ class Fetcher
10
+ include Celluloid
11
+ include Sidekiq::Util
12
+
13
+ TIMEOUT = 1
14
+
15
+ def initialize(mgr, queues)
16
+ @mgr = mgr
17
+ @queues = queues.map { |q| "queue:#{q}" }
18
+ @unique_queues = @queues.uniq
19
+ end
20
+
21
+ # Fetching is straightforward: the Manager makes a fetch
22
+ # request for each idle processor when Sidekiq starts and
23
+ # then issues a new fetch request every time a Processor
24
+ # finishes a message.
25
+ #
26
+ # Because we have to shut down cleanly, we can't block
27
+ # forever and we can't loop forever. Instead we reschedule
28
+ # a new fetch if the current fetch turned up nothing.
29
+ def fetch
30
+ watchdog('Fetcher#fetch died') do
31
+ return if Sidekiq::Fetcher.done?
32
+
33
+ begin
34
+ queue = nil
35
+ msg = nil
36
+ Sidekiq.redis { |conn| queue, msg = conn.blpop(*queues_cmd) }
37
+
38
+ if msg
39
+ @mgr.assign!(msg, queue.gsub(/.*queue:/, ''))
40
+ else
41
+ after(0) { fetch }
42
+ end
43
+ rescue => ex
44
+ logger.error("Error fetching message: #{ex}")
45
+ logger.error(ex.backtrace.first)
46
+ sleep(TIMEOUT)
47
+ after(0) { fetch }
48
+ end
49
+ end
50
+ end
51
+
52
+ # Ugh. Say hello to a bloody hack.
53
+ # Can't find a clean way to get the fetcher to just stop processing
54
+ # its mailbox when shutdown starts.
55
+ def self.done!
56
+ @done = true
57
+ end
58
+
59
+ def self.done?
60
+ @done
61
+ end
62
+
63
+ private
64
+
65
+ # Creating the Redis#blpop command takes into account any
66
+ # configured queue weights. By default Redis#blpop returns
67
+ # data from the first queue that has pending elements. We
68
+ # recreate the queue command each time we invoke Redis#blpop
69
+ # to honor weights and avoid queue starvation.
70
+ def queues_cmd
71
+ queues = @queues.sample(@unique_queues.size).uniq
72
+ queues.concat(@unique_queues - queues)
73
+ queues << TIMEOUT
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,46 @@
1
+ require 'time'
2
+ require 'logger'
3
+
4
+ module Sidekiq
5
+ module Logging
6
+
7
+ class Pretty < Logger::Formatter
8
+ # Provide a call() method that returns the formatted message.
9
+ def call(severity, time, program_name, message)
10
+ "#{time.utc.iso8601} #{Process.pid} TID-#{Thread.current.object_id.to_s(36)}#{context} #{severity}: #{message}\n"
11
+ end
12
+
13
+ def context
14
+ c = Thread.current[:sidekiq_context]
15
+ c ? " #{c}" : ''
16
+ end
17
+ end
18
+
19
+ def self.with_context(msg)
20
+ begin
21
+ Thread.current[:sidekiq_context] = msg
22
+ yield
23
+ ensure
24
+ Thread.current[:sidekiq_context] = nil
25
+ end
26
+ end
27
+
28
+ def self.logger
29
+ @logger ||= begin
30
+ log = Logger.new(STDOUT)
31
+ log.level = Logger::INFO
32
+ log.formatter = Pretty.new
33
+ log
34
+ end
35
+ end
36
+
37
+ def self.logger=(log)
38
+ @logger = (log ? log : Logger.new('/dev/null'))
39
+ end
40
+
41
+ def logger
42
+ Sidekiq::Logging.logger
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,163 @@
1
+ require 'celluloid'
2
+ require 'multi_json'
3
+
4
+ require 'sidekiq/util'
5
+ require 'sidekiq/processor'
6
+ require 'sidekiq/fetch'
7
+
8
+ module Sidekiq
9
+
10
+ ##
11
+ # The main router in the system. This
12
+ # manages the processor state and accepts messages
13
+ # from Redis to be dispatched to an idle processor.
14
+ #
15
+ class Manager
16
+ include Util
17
+ include Celluloid
18
+
19
+ trap_exit :processor_died
20
+
21
+ def initialize(options={})
22
+ logger.info "Booting sidekiq #{Sidekiq::VERSION} with Redis at #{redis {|x| x.client.id}}"
23
+ logger.info "Running in #{RUBY_DESCRIPTION}"
24
+ logger.debug { options.inspect }
25
+ @count = options[:concurrency] || 25
26
+ @done_callback = nil
27
+
28
+ @in_progress = {}
29
+ @done = false
30
+ @busy = []
31
+ @fetcher = Fetcher.new(current_actor, options[:queues])
32
+ @ready = @count.times.map { Processor.new_link(current_actor) }
33
+ procline
34
+ end
35
+
36
+ def stop(options={})
37
+ watchdog('Manager#stop died') do
38
+ shutdown = options[:shutdown]
39
+ timeout = options[:timeout]
40
+
41
+ @done = true
42
+ Sidekiq::Fetcher.done!
43
+ @fetcher.terminate! if @fetcher.alive?
44
+
45
+ logger.info { "Shutting down #{@ready.size} quiet workers" }
46
+ @ready.each { |x| x.terminate if x.alive? }
47
+ @ready.clear
48
+
49
+ logger.debug { "Clearing workers in redis" }
50
+ Sidekiq.redis do |conn|
51
+ workers = conn.smembers('workers')
52
+ workers.each do |name|
53
+ conn.srem('workers', name) if name =~ /:#{process_id}-/
54
+ end
55
+ end
56
+
57
+ return after(0) { signal(:shutdown) } if @busy.empty?
58
+ logger.info { "Pausing up to #{timeout} seconds to allow workers to finish..." }
59
+ hard_shutdown_in timeout if shutdown
60
+ end
61
+ end
62
+
63
+ def start
64
+ @ready.each { dispatch }
65
+ end
66
+
67
+ def when_done(&blk)
68
+ @done_callback = blk
69
+ end
70
+
71
+ def processor_done(processor)
72
+ watchdog('Manager#processor_done died') do
73
+ @done_callback.call(processor) if @done_callback
74
+ @in_progress.delete(processor.object_id)
75
+ @busy.delete(processor)
76
+ if stopped?
77
+ processor.terminate if processor.alive?
78
+ signal(:shutdown) if @busy.empty?
79
+ else
80
+ @ready << processor if processor.alive?
81
+ end
82
+ dispatch
83
+ end
84
+ end
85
+
86
+ def processor_died(processor, reason)
87
+ watchdog("Manager#processor_died died") do
88
+ @in_progress.delete(processor.object_id)
89
+ @busy.delete(processor)
90
+
91
+ unless stopped?
92
+ @ready << Processor.new_link(current_actor)
93
+ dispatch
94
+ else
95
+ signal(:shutdown) if @busy.empty?
96
+ end
97
+ end
98
+ end
99
+
100
+ def assign(msg, queue)
101
+ watchdog("Manager#assign died") do
102
+ if stopped?
103
+ # Race condition between Manager#stop if Fetcher
104
+ # is blocked on redis and gets a message after
105
+ # all the ready Processors have been stopped.
106
+ # Push the message back to redis.
107
+ Sidekiq.redis do |conn|
108
+ conn.lpush("queue:#{queue}", msg)
109
+ end
110
+ else
111
+ processor = @ready.pop
112
+ @in_progress[processor.object_id] = [msg, queue]
113
+ @busy << processor
114
+ processor.process!(Sidekiq.load_json(msg), queue)
115
+ end
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def hard_shutdown_in(delay)
122
+ after(delay) do
123
+ watchdog("Manager#watch_for_shutdown died") do
124
+ # We've reached the timeout and we still have busy workers.
125
+ # They must die but their messages shall live on.
126
+ logger.info("Still waiting for #{@busy.size} busy workers")
127
+
128
+ Sidekiq.redis do |conn|
129
+ @busy.each do |processor|
130
+ # processor is an actor proxy and we can't call any methods
131
+ # that would go to the actor (since it's busy). Instead
132
+ # we'll use the object_id to track the worker's data here.
133
+ msg, queue = @in_progress[processor.object_id]
134
+ conn.lpush("queue:#{queue}", msg)
135
+ end
136
+ end
137
+ logger.info("Pushed #{@busy.size} messages back to Redis")
138
+
139
+ after(0) { signal(:shutdown) }
140
+ end
141
+ end
142
+ end
143
+
144
+ def dispatch
145
+ return if stopped?
146
+ # This is a safety check to ensure we haven't leaked
147
+ # processors somehow.
148
+ raise "BUG: No processors, cannot continue!" if @ready.empty? && @busy.empty?
149
+ raise "No ready processor!?" if @ready.empty?
150
+
151
+ @fetcher.fetch!
152
+ end
153
+
154
+ def stopped?
155
+ @done
156
+ end
157
+
158
+ def procline
159
+ $0 = "sidekiq #{Sidekiq::VERSION} [#{@busy.size} of #{@count} busy]#{stopped? ? ' stopping' : ''}"
160
+ after(5) { procline }
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,96 @@
1
+ module Sidekiq
2
+ # Middleware is code configured to run before/after
3
+ # a message is processed. It is patterned after Rack
4
+ # middleware. Middleware exists for the client side
5
+ # (pushing jobs onto the queue) as well as the server
6
+ # side (when jobs are actually processed).
7
+ #
8
+ # To add middleware for the client:
9
+ #
10
+ # Sidekiq.client_middleware do |chain|
11
+ # chain.add MyClientHook
12
+ # end
13
+ #
14
+ # To modify middleware for the server, just call
15
+ # with another block:
16
+ #
17
+ # Sidekiq.server_middleware do |chain|
18
+ # chain.add MyServerHook
19
+ # chain.remove ActiveRecord
20
+ # end
21
+ #
22
+ # This is an example of a minimal server middleware:
23
+ #
24
+ # class MyServerHook
25
+ # def call(worker, msg, queue)
26
+ # puts "Before work"
27
+ # yield
28
+ # puts "After work"
29
+ # end
30
+ # end
31
+ #
32
+ # This is an example of a minimal client middleware:
33
+ #
34
+ # class MyClientHook
35
+ # def call(msg, queue)
36
+ # puts "Before push"
37
+ # yield
38
+ # puts "After push"
39
+ # end
40
+ # end
41
+ #
42
+ module Middleware
43
+ class Chain
44
+ attr_reader :entries
45
+
46
+ def initialize
47
+ @entries = []
48
+ yield self if block_given?
49
+ end
50
+
51
+ def remove(klass)
52
+ entries.delete_if { |entry| entry.klass == klass }
53
+ end
54
+
55
+ def add(klass, *args)
56
+ entries << Entry.new(klass, *args) unless exists?(klass)
57
+ end
58
+
59
+ def exists?(klass)
60
+ entries.any? { |entry| entry.klass == klass }
61
+ end
62
+
63
+ def retrieve
64
+ entries.map(&:make_new)
65
+ end
66
+
67
+ def clear
68
+ entries.clear
69
+ end
70
+
71
+ def invoke(*args, &final_action)
72
+ chain = retrieve.dup
73
+ traverse_chain = lambda do
74
+ if chain.empty?
75
+ final_action.call
76
+ else
77
+ chain.shift.call(*args, &traverse_chain)
78
+ end
79
+ end
80
+ traverse_chain.call
81
+ end
82
+ end
83
+
84
+ class Entry
85
+ attr_reader :klass
86
+ def initialize(klass, *args)
87
+ @klass = klass
88
+ @args = args
89
+ end
90
+
91
+ def make_new
92
+ @klass.new(*@args)
93
+ end
94
+ end
95
+ end
96
+ end