sidekiq 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (62) hide show
  1. data/.gitignore +1 -0
  2. data/Changes.md +11 -0
  3. data/Gemfile +6 -0
  4. data/lib/sidekiq.rb +1 -0
  5. data/lib/sidekiq/api.rb +184 -0
  6. data/lib/sidekiq/cli.rb +3 -2
  7. data/lib/sidekiq/client.rb +16 -10
  8. data/lib/sidekiq/core_ext.rb +30 -0
  9. data/lib/sidekiq/exception_handler.rb +9 -0
  10. data/lib/sidekiq/processor.rb +1 -1
  11. data/lib/sidekiq/testing/inline.rb +3 -3
  12. data/lib/sidekiq/util.rb +1 -11
  13. data/lib/sidekiq/version.rb +1 -1
  14. data/lib/sidekiq/web.rb +49 -1
  15. data/lib/sidekiq/worker.rb +3 -9
  16. data/sidekiq.gemspec +3 -0
  17. data/test/config.yml +1 -1
  18. data/test/test_api.rb +75 -0
  19. data/test/test_cli.rb +6 -1
  20. data/test/test_exception_handler.rb +16 -0
  21. data/test/test_testing_inline.rb +3 -3
  22. data/test/test_web.rb +27 -6
  23. data/web/assets/images/bootstrap/glyphicons-halflings-white.png +0 -0
  24. data/web/assets/images/bootstrap/glyphicons-halflings.png +0 -0
  25. data/web/assets/images/logo.png +0 -0
  26. data/web/assets/images/status-sd8051fd480.png +0 -0
  27. data/web/assets/images/status/active.png +0 -0
  28. data/web/assets/images/status/idle.png +0 -0
  29. data/web/assets/javascripts/application.js +44 -16
  30. data/web/assets/javascripts/vendor/bootstrap.js +0 -0
  31. data/web/assets/javascripts/vendor/jquery.js +0 -0
  32. data/web/assets/javascripts/vendor/jquery.timeago.js +0 -0
  33. data/web/assets/stylesheets/application.css +1 -3
  34. data/web/assets/stylesheets/layout.css +0 -0
  35. data/web/assets/stylesheets/partials/_base.scss +116 -0
  36. data/web/assets/stylesheets/partials/_colors.scss +46 -0
  37. data/web/assets/stylesheets/partials/_fonts.scss +3 -0
  38. data/web/assets/stylesheets/partials/_h5bp.scss +293 -0
  39. data/web/assets/stylesheets/partials/_layout.scss +197 -0
  40. data/web/assets/stylesheets/partials/_navbar.scss +100 -0
  41. data/web/assets/stylesheets/partials/_normalize.scss +504 -0
  42. data/web/assets/stylesheets/partials/_prettify.css +30 -0
  43. data/web/assets/stylesheets/partials/_variables.scss +28 -0
  44. data/web/assets/stylesheets/public.scss +74 -0
  45. data/web/assets/stylesheets/style.scss +69 -0
  46. data/web/assets/stylesheets/vendor/bootstrap-responsive.css +0 -0
  47. data/web/assets/stylesheets/vendor/bootstrap.css +0 -0
  48. data/web/assets/stylesheets/vendors.scss +2 -0
  49. data/web/views/_nav.slim +13 -0
  50. data/web/views/_paging.slim +0 -0
  51. data/web/views/_status.slim +3 -0
  52. data/web/views/_summary.slim +19 -8
  53. data/web/views/_workers.slim +12 -5
  54. data/web/views/index.slim +8 -9
  55. data/web/views/layout.slim +27 -19
  56. data/web/views/poll.slim +1 -0
  57. data/web/views/queue.slim +22 -9
  58. data/web/views/queues.slim +7 -7
  59. data/web/views/retries.slim +13 -10
  60. data/web/views/retry.slim +5 -5
  61. data/web/views/scheduled.slim +13 -10
  62. metadata +71 -2
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ Gemfile.lock
4
4
  dump.rdb
5
5
  .rbx
6
6
  coverage/
7
+ .sass-cache/
data/Changes.md CHANGED
@@ -1,3 +1,14 @@
1
+ 2.5.0
2
+ -----------
3
+
4
+ - REDESIGNED WEB UI! [unity, cavneb]
5
+ - Support Honeybadger for error delivery
6
+ - Inline testing runs the client middleware before executing jobs [#465]
7
+ - Web UI can now remove jobs from queue. [#466, dleung]
8
+ - Web UI can now show the full message, not just 100 chars [#464, dleung]
9
+ - Add APIs for manipulating the retry and job queues. See sidekiq/api. [#457]
10
+
11
+
1
12
  2.4.0
2
13
  -----------
3
14
 
data/Gemfile CHANGED
@@ -10,3 +10,9 @@ gem 'sqlite3'
10
10
  group :test do
11
11
  gem 'simplecov', :require => false
12
12
  end
13
+
14
+ group :development do
15
+ gem 'sprockets-sass'
16
+ gem 'compass'
17
+ gem 'shotgun'
18
+ end
@@ -5,6 +5,7 @@ require 'sidekiq/worker'
5
5
  require 'sidekiq/redis_connection'
6
6
  require 'sidekiq/util'
7
7
  require 'sidekiq/stats'
8
+ require 'sidekiq/api'
8
9
 
9
10
  require 'sidekiq/extensions/class_methods'
10
11
  require 'sidekiq/extensions/action_mailer'
@@ -0,0 +1,184 @@
1
+ require 'sidekiq'
2
+
3
+ module Sidekiq
4
+
5
+ ##
6
+ # Encapsulates a queue within Sidekiq.
7
+ # Allows enumeration of all jobs within the queue
8
+ # and deletion of jobs.
9
+ #
10
+ # queue = Sidekiq::Queue.new("mailer")
11
+ # queue.each do |job|
12
+ # job.klass # => 'MyWorker'
13
+ # job.args # => [1, 2, 3]
14
+ # job.delete if job.jid == 'abcdef1234567890'
15
+ # end
16
+ #
17
+ class Queue
18
+ include Enumerable
19
+
20
+ attr_reader :name
21
+
22
+ def initialize(name="default")
23
+ @name = name
24
+ @rname = "queue:#{name}"
25
+ end
26
+
27
+ def size
28
+ Sidekiq.redis { |con| con.llen(@rname) }
29
+ end
30
+
31
+ def each(&block)
32
+ page = 0
33
+ page_size = 50
34
+
35
+ loop do
36
+ entries = Sidekiq.redis do |conn|
37
+ conn.lrange @rname, page * page_size, (page * page_size) + page_size - 1
38
+ end
39
+ break if entries.empty?
40
+ page += 1
41
+ entries.each do |entry|
42
+ block.call Job.new(entry, @name)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ ##
49
+ # Encapsulates a pending job within a Sidekiq queue or
50
+ # sorted set.
51
+ #
52
+ # The job should be considered immutable but may be
53
+ # removed from the queue via Job#delete.
54
+ #
55
+ class Job
56
+ attr_reader :item
57
+
58
+ def initialize(item, queue_name=nil)
59
+ @value = item
60
+ @item = Sidekiq.load_json(item)
61
+ @queue = queue_name || @item['queue']
62
+ end
63
+
64
+ def klass
65
+ @item['class']
66
+ end
67
+
68
+ def args
69
+ @item['args']
70
+ end
71
+
72
+ def jid
73
+ @item['jid']
74
+ end
75
+
76
+ def queue
77
+ @queue
78
+ end
79
+
80
+ ##
81
+ # Remove this job from the queue.
82
+ def delete
83
+ count = Sidekiq.redis do |conn|
84
+ conn.lrem("queue:#{@queue}", 0, @value)
85
+ end
86
+ count != 0
87
+ end
88
+
89
+ def [](name)
90
+ @item.send(:[], name)
91
+ end
92
+ end
93
+
94
+ class SortedEntry < Job
95
+ attr_reader :score
96
+
97
+ def initialize(parent, score, item)
98
+ super(item)
99
+ @score = score
100
+ @parent = parent
101
+ end
102
+
103
+ def at
104
+ Time.at(@score)
105
+ end
106
+
107
+ def delete
108
+ @parent.delete(@score)
109
+ end
110
+ end
111
+
112
+ class SortedSet
113
+ include Enumerable
114
+
115
+ def initialize(name)
116
+ @zset = name
117
+ end
118
+
119
+ def size
120
+ Sidekiq.redis {|c| c.zcard(@zset) }
121
+ end
122
+
123
+ def each(&block)
124
+ # page thru the sorted set backwards so deleting entries doesn't screw up indexing
125
+ page = -1
126
+ page_size = 50
127
+
128
+ loop do
129
+ elements = Sidekiq.redis do |conn|
130
+ conn.zrange @zset, page * page_size, (page * page_size) + (page_size - 1), :with_scores => true
131
+ end
132
+ break if elements.empty?
133
+ page -= 1
134
+ elements.each do |element, score|
135
+ block.call SortedEntry.new(self, score, element)
136
+ end
137
+ end
138
+ end
139
+
140
+ def delete(score)
141
+ count = Sidekiq.redis do |conn|
142
+ conn.zremrangebyscore(@zset, score, score)
143
+ end
144
+ count != 0
145
+ end
146
+ end
147
+
148
+ ##
149
+ # Allows enumeration of scheduled jobs within Sidekiq.
150
+ # Based on this, you can search/filter for jobs. Here's an
151
+ # example where I'm selecting all jobs of a certain type
152
+ # and deleting them from the retry queue.
153
+ #
154
+ # r = Sidekiq::ScheduledSet.new
155
+ # r.select do |retri|
156
+ # retri.klass == 'Sidekiq::Extensions::DelayedClass' &&
157
+ # retri.args[0] == 'User' &&
158
+ # retri.args[1] == 'setup_new_subscriber'
159
+ # end.map(&:delete)
160
+ class ScheduledSet < SortedSet
161
+ def initialize
162
+ super 'schedule'
163
+ end
164
+ end
165
+
166
+ ##
167
+ # Allows enumeration of retries within Sidekiq.
168
+ # Based on this, you can search/filter for jobs. Here's an
169
+ # example where I'm selecting all jobs of a certain type
170
+ # and deleting them from the retry queue.
171
+ #
172
+ # r = Sidekiq::RetrySet.new
173
+ # r.select do |retri|
174
+ # retri.klass == 'Sidekiq::Extensions::DelayedClass' &&
175
+ # retri.args[0] == 'User' &&
176
+ # retri.args[1] == 'setup_new_subscriber'
177
+ # end.map(&:delete)
178
+ class RetrySet < SortedSet
179
+ def initialize
180
+ super 'retry'
181
+ end
182
+ end
183
+
184
+ end
@@ -32,6 +32,7 @@ require 'yaml'
32
32
  require 'singleton'
33
33
  require 'optparse'
34
34
  require 'celluloid'
35
+ require 'erb'
35
36
 
36
37
  require 'sidekiq'
37
38
  require 'sidekiq/util'
@@ -149,7 +150,7 @@ module Sidekiq
149
150
 
150
151
  @parser = OptionParser.new do |o|
151
152
  o.on "-q", "--queue QUEUE[,WEIGHT]...", "Queues to process with optional weights" do |arg|
152
- queues_and_weights = arg.scan(/([\w-]+),?(\d*)/)
153
+ queues_and_weights = arg.scan(/([\w\.-]+),?(\d*)/)
153
154
  queues_and_weights.each {|queue_and_weight| parse_queues(opts, *queue_and_weight)}
154
155
  opts[:strict] = queues_and_weights.collect(&:last).none? {|weight| weight != ''}
155
156
  end
@@ -208,7 +209,7 @@ module Sidekiq
208
209
  def parse_config(cli)
209
210
  opts = {}
210
211
  if cli[:config_file] && File.exist?(cli[:config_file])
211
- opts = YAML.load_file cli[:config_file]
212
+ opts = YAML.load(ERB.new(IO.read(cli[:config_file])).result)
212
213
  queues = opts.delete(:queues) || []
213
214
  queues.each { |name, weight| parse_queues(opts, name, weight) }
214
215
  end
@@ -40,16 +40,7 @@ module Sidekiq
40
40
  normed, payload = process_single(item['class'], normed)
41
41
 
42
42
  pushed = false
43
- Sidekiq.redis do |conn|
44
- if normed['at']
45
- pushed = conn.zadd('schedule', normed['at'].to_s, payload)
46
- else
47
- _, pushed = conn.multi do
48
- conn.sadd('queues', normed['queue'])
49
- conn.rpush("queue:#{normed['queue']}", payload)
50
- end
51
- end
52
- end if normed
43
+ pushed = raw_push(normed, payload) if normed
53
44
  pushed ? normed['jid'] : nil
54
45
  end
55
46
 
@@ -104,6 +95,21 @@ module Sidekiq
104
95
 
105
96
  private
106
97
 
98
+ def self.raw_push(normed, payload) # :nodoc:
99
+ pushed = false
100
+ Sidekiq.redis do |conn|
101
+ if normed['at']
102
+ pushed = conn.zadd('schedule', normed['at'].to_s, payload)
103
+ else
104
+ _, pushed = conn.multi do
105
+ conn.sadd('queues', normed['queue'])
106
+ conn.rpush("queue:#{normed['queue']}", payload)
107
+ end
108
+ end
109
+ end
110
+ pushed
111
+ end
112
+
107
113
  def self.process_single(worker_class, item)
108
114
  queue = item['queue']
109
115
 
@@ -51,4 +51,34 @@ rescue LoadError
51
51
  end
52
52
  end
53
53
 
54
+ begin
55
+ require 'active_support/core_ext/hash/keys'
56
+ rescue LoadError
57
+ class Hash
58
+ def stringify_keys(hash)
59
+ hash.keys.each do |key|
60
+ hash[key.to_s] = hash.delete(key)
61
+ end
62
+ hash
63
+ end
64
+ end if !{}.responds_to(:stringify_keys)
65
+ end
66
+
67
+ begin
68
+ require 'active_support/core_ext/string/inflections'
69
+ rescue LoadError
70
+ class String
71
+ def constantize
72
+ names = self.split('::')
73
+ names.shift if names.empty? || names.first.empty?
74
+
75
+ constant = Object
76
+ names.each do |name|
77
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
78
+ end
79
+ constant
80
+ end
81
+ end if !"".responds_to(:constantize)
82
+ end
83
+
54
84
 
@@ -5,7 +5,12 @@ module Sidekiq
5
5
  Sidekiq.logger.warn msg
6
6
  Sidekiq.logger.warn ex
7
7
  Sidekiq.logger.warn ex.backtrace.join("\n")
8
+ # This list of services is getting a bit ridiculous.
9
+ # For future error services, please add your own
10
+ # middleware like BugSnag does:
11
+ # https://github.com/bugsnag/bugsnag-ruby/blob/master/lib/bugsnag/sidekiq.rb
8
12
  send_to_airbrake(msg, ex) if defined?(::Airbrake)
13
+ send_to_honeybadger(msg, ex) if defined?(::Honeybadger)
9
14
  send_to_exceptional(msg, ex) if defined?(::Exceptional)
10
15
  send_to_exception_notifier(msg, ex) if defined?(::ExceptionNotifier)
11
16
  end
@@ -16,6 +21,10 @@ module Sidekiq
16
21
  ::Airbrake.notify_or_ignore(ex, :parameters => msg)
17
22
  end
18
23
 
24
+ def send_to_honeybadger(msg, ex)
25
+ ::Honeybadger.notify_or_ignore(ex, :parameters => msg)
26
+ end
27
+
19
28
  def send_to_exceptional(msg, ex)
20
29
  if ::Exceptional::Config.should_send_to_api?
21
30
  ::Exceptional.context(msg)
@@ -34,7 +34,7 @@ module Sidekiq
34
34
  defer do
35
35
  begin
36
36
  msg = Sidekiq.load_json(msgstr)
37
- klass = constantize(msg['class'])
37
+ klass = msg['class'].constantize
38
38
  worker = klass.new
39
39
 
40
40
  stats(worker, msg, queue) do
@@ -27,9 +27,9 @@ module Sidekiq
27
27
  # assert_equal 1, $external_variable
28
28
  #
29
29
  singleton_class.class_eval do
30
- alias_method :push_old, :push
31
- def push(hash)
32
- hash['class'].new.perform(*Sidekiq.load_json(Sidekiq.dump_json(hash['args'])))
30
+ alias_method :raw_push_old, :raw_push
31
+ def raw_push(hash, _)
32
+ hash['class'].constantize.new.perform(*Sidekiq.load_json(Sidekiq.dump_json(hash['args'])))
33
33
  true
34
34
  end
35
35
  end
@@ -1,5 +1,6 @@
1
1
  require 'socket'
2
2
  require 'sidekiq/exception_handler'
3
+ require 'sidekiq/core_ext'
3
4
 
4
5
  module Sidekiq
5
6
  ##
@@ -10,17 +11,6 @@ module Sidekiq
10
11
 
11
12
  EXPIRY = 60 * 60 * 24
12
13
 
13
- def constantize(camel_cased_word)
14
- names = camel_cased_word.split('::')
15
- names.shift if names.empty? || names.first.empty?
16
-
17
- constant = Object
18
- names.each do |name|
19
- constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
20
- end
21
- constant
22
- end
23
-
24
14
  def watchdog(last_words)
25
15
  yield
26
16
  rescue Exception => ex
@@ -1,3 +1,3 @@
1
1
  module Sidekiq
2
- VERSION = "2.4.0"
2
+ VERSION = "2.5.0"
3
3
  end
@@ -1,6 +1,9 @@
1
1
  require 'sinatra/base'
2
2
  require 'slim'
3
3
  require 'sprockets'
4
+ require 'sprockets-sass'
5
+ require 'sass'
6
+ require 'compass'
4
7
  require 'sidekiq/paginator'
5
8
 
6
9
  module Sidekiq
@@ -16,6 +19,33 @@ module Sidekiq
16
19
  @environment.append_path 'assets/stylesheets'
17
20
  @environment.append_path 'assets/stylesheets/vendor'
18
21
  @environment.append_path 'assets/images'
22
+
23
+ Compass.configuration do |config|
24
+
25
+ config.project_path = "#{@root}/assets"
26
+
27
+ config.images_dir = 'images'
28
+ config.sass_dir = 'stylesheets'
29
+ config.css_dir = 'stylesheets'
30
+ config.javascripts_dir = 'javascripts'
31
+ config.fonts_dir = 'stylesheets/fonts'
32
+
33
+ config.http_images_path = '/assets'
34
+ config.http_generated_images_path = '/assets'
35
+ config.http_javascripts_path = '/assets'
36
+ config.http_stylesheets_path = '/assets'
37
+
38
+ # You can select your preferred output style here (can be overridden via the command line):
39
+ output_style = :compressed
40
+
41
+ # To enable relative paths to assets via compass helper functions. Uncomment:
42
+ relative_assets = true
43
+
44
+ # To disable debugging comments that display the original location of your selectors. Uncomment:
45
+ line_comments = false
46
+ end
47
+
48
+
19
49
  end
20
50
 
21
51
  def call(env)
@@ -36,8 +66,10 @@ module Sidekiq
36
66
  set :views, "#{dir}/views"
37
67
  set :root, "#{dir}/public"
38
68
  set :slim, :pretty => true
69
+
39
70
  use SprocketsMiddleware, :root => dir
40
71
 
72
+
41
73
  helpers do
42
74
 
43
75
  def reset_worker_list
@@ -99,6 +131,10 @@ module Sidekiq
99
131
  "#{env['SCRIPT_NAME']}/"
100
132
  end
101
133
 
134
+ def current_path
135
+ @current_path ||= request.path_info.gsub(/^\//,'')
136
+ end
137
+
102
138
  def current_status
103
139
  return 'idle' if workers.size == 0
104
140
  return 'active'
@@ -165,6 +201,13 @@ module Sidekiq
165
201
  redirect "#{root_path}queues"
166
202
  end
167
203
 
204
+ post "/queues/:name/delete" do
205
+ Sidekiq.redis do |conn|
206
+ conn.lrem("queue:#{params[:name]}", 0, params[:key_val])
207
+ end
208
+ redirect "#{root_path}queues/#{params[:name]}"
209
+ end
210
+
168
211
  get "/retries/:score" do
169
212
  halt 404 unless params[:score]
170
213
  @score = params[:score].to_f
@@ -241,7 +284,12 @@ module Sidekiq
241
284
  end
242
285
 
243
286
  def self.tabs
244
- @tabs ||= ["Queues", "Retries", "Scheduled"]
287
+ @tabs ||= {
288
+ "Workers" =>'',
289
+ "Queues" =>'queues',
290
+ "Retries" =>'retries',
291
+ "Scheduled" =>'scheduled'
292
+ }
245
293
  end
246
294
 
247
295
  end