sidekiq 5.0.5 → 5.2.0

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.

Potentially problematic release.


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

Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/issue_template.md +3 -1
  3. data/.travis.yml +5 -4
  4. data/Changes.md +47 -0
  5. data/Ent-Changes.md +21 -0
  6. data/Gemfile +10 -28
  7. data/LICENSE +1 -1
  8. data/Pro-4.0-Upgrade.md +35 -0
  9. data/Pro-Changes.md +86 -0
  10. data/README.md +4 -2
  11. data/Rakefile +0 -4
  12. data/bin/sidekiqload +1 -1
  13. data/lib/sidekiq/api.rb +65 -29
  14. data/lib/sidekiq/cli.rb +54 -24
  15. data/lib/sidekiq/client.rb +32 -32
  16. data/lib/sidekiq/delay.rb +1 -0
  17. data/lib/sidekiq/exception_handler.rb +2 -4
  18. data/lib/sidekiq/fetch.rb +1 -1
  19. data/lib/sidekiq/job_logger.rb +2 -1
  20. data/lib/sidekiq/job_retry.rb +18 -5
  21. data/lib/sidekiq/launcher.rb +14 -9
  22. data/lib/sidekiq/logging.rb +9 -5
  23. data/lib/sidekiq/manager.rb +2 -3
  24. data/lib/sidekiq/middleware/server/active_record.rb +2 -1
  25. data/lib/sidekiq/processor.rb +33 -18
  26. data/lib/sidekiq/rails.rb +6 -0
  27. data/lib/sidekiq/redis_connection.rb +11 -3
  28. data/lib/sidekiq/scheduled.rb +35 -8
  29. data/lib/sidekiq/testing.rb +4 -4
  30. data/lib/sidekiq/util.rb +6 -2
  31. data/lib/sidekiq/version.rb +1 -1
  32. data/lib/sidekiq/web/action.rb +2 -2
  33. data/lib/sidekiq/web/application.rb +18 -2
  34. data/lib/sidekiq/web/helpers.rb +7 -5
  35. data/lib/sidekiq/web/router.rb +10 -10
  36. data/lib/sidekiq/web.rb +4 -4
  37. data/lib/sidekiq/worker.rb +7 -7
  38. data/lib/sidekiq.rb +20 -13
  39. data/sidekiq.gemspec +3 -8
  40. data/web/assets/javascripts/application.js +0 -0
  41. data/web/assets/stylesheets/application.css +0 -0
  42. data/web/assets/stylesheets/bootstrap.css +2 -2
  43. data/web/locales/en.yml +1 -0
  44. data/web/locales/es.yml +4 -3
  45. data/web/locales/ja.yml +5 -3
  46. data/web/views/_footer.erb +3 -0
  47. data/web/views/layout.erb +1 -1
  48. data/web/views/queues.erb +2 -0
  49. metadata +8 -89
@@ -15,7 +15,15 @@ module Sidekiq
15
15
  options[:id] = "Sidekiq-#{Sidekiq.server? ? "server" : "client"}-PID-#{$$}" if !options.has_key?(:id)
16
16
  options[:url] ||= determine_redis_provider
17
17
 
18
- size = options[:size] || (Sidekiq.server? ? (Sidekiq.options[:concurrency] + 5) : 5)
18
+ size = if options[:size]
19
+ options[:size]
20
+ elsif Sidekiq.server?
21
+ Sidekiq.options[:concurrency] + 5
22
+ elsif ENV['RAILS_MAX_THREADS']
23
+ Integer(ENV['RAILS_MAX_THREADS'])
24
+ else
25
+ 5
26
+ end
19
27
 
20
28
  verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server?
21
29
 
@@ -38,7 +46,7 @@ module Sidekiq
38
46
  # - enterprise's leader election
39
47
  # - enterprise's cron support
40
48
  def verify_sizing(size, concurrency)
41
- raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but really needs to have at least #{concurrency + 2}" if size <= concurrency
49
+ raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size <= concurrency
42
50
  end
43
51
 
44
52
  def build_client(options)
@@ -70,7 +78,7 @@ module Sidekiq
70
78
  opts.delete(:network_timeout)
71
79
  end
72
80
 
73
- opts[:driver] ||= 'ruby'.freeze
81
+ opts[:driver] ||= 'ruby'
74
82
 
75
83
  # Issue #3303, redis-rb will silently retry an operation.
76
84
  # This can lead to duplicate jobs if Sidekiq::Client's LPUSH
@@ -17,7 +17,7 @@ module Sidekiq
17
17
  # We need to go through the list one at a time to reduce the risk of something
18
18
  # going wrong between the time jobs are popped from the scheduled queue and when
19
19
  # they are pushed onto a work queue and losing the jobs.
20
- while job = conn.zrangebyscore(sorted_set, '-inf'.freeze, now, :limit => [0, 1]).first do
20
+ while job = conn.zrangebyscore(sorted_set, '-inf', now, :limit => [0, 1]).first do
21
21
 
22
22
  # Pop item off the queue and add it to the work queue. If the job can't be popped from
23
23
  # the queue, it's because another process already popped it so we can move on to the
@@ -79,9 +79,7 @@ module Sidekiq
79
79
  # Most likely a problem with redis networking.
80
80
  # Punt and try again at the next interval
81
81
  logger.error ex.message
82
- ex.backtrace.each do |bt|
83
- logger.error(bt)
84
- end
82
+ handle_exception(ex)
85
83
  end
86
84
  end
87
85
 
@@ -95,13 +93,38 @@ module Sidekiq
95
93
  # if poll_interval_average hasn't been calculated yet, we can
96
94
  # raise an error trying to reach Redis.
97
95
  logger.error ex.message
98
- logger.error ex.backtrace.first
96
+ handle_exception(ex)
99
97
  sleep 5
100
98
  end
101
99
 
102
- # Calculates a random interval that is ±50% the desired average.
103
100
  def random_poll_interval
104
- poll_interval_average * rand + poll_interval_average.to_f / 2
101
+ # We want one Sidekiq process to schedule jobs every N seconds. We have M processes
102
+ # and **don't** want to coordinate.
103
+ #
104
+ # So in N*M second timespan, we want each process to schedule once. The basic loop is:
105
+ #
106
+ # * sleep a random amount within that N*M timespan
107
+ # * wake up and schedule
108
+ #
109
+ # We want to avoid one edge case: imagine a set of 2 processes, scheduling every 5 seconds,
110
+ # so N*M = 10. Each process decides to randomly sleep 8 seconds, now we've failed to meet
111
+ # that 5 second average. Thankfully each schedule cycle will sleep randomly so the next
112
+ # iteration could see each process sleep for 1 second, undercutting our average.
113
+ #
114
+ # So below 10 processes, we special case and ensure the processes sleep closer to the average.
115
+ # In the example above, each process should schedule every 10 seconds on average. We special
116
+ # case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds.
117
+ # As we run more processes, the scheduling interval average will approach an even spread
118
+ # between 0 and poll interval so we don't need this artifical boost.
119
+ #
120
+ if process_count < 10
121
+ # For small clusters, calculate a random interval that is ±50% the desired average.
122
+ poll_interval_average * rand + poll_interval_average.to_f / 2
123
+ else
124
+ # With 10+ processes, we should have enough randomness to get decent polling
125
+ # across the entire timespan
126
+ poll_interval_average * rand
127
+ end
105
128
  end
106
129
 
107
130
  # We do our best to tune the poll interval to the size of the active Sidekiq
@@ -125,9 +148,13 @@ module Sidekiq
125
148
  # This minimizes a single point of failure by dispersing check-ins but without taxing
126
149
  # Redis if you run many Sidekiq processes.
127
150
  def scaled_poll_interval
151
+ process_count * Sidekiq.options[:average_scheduled_poll_interval]
152
+ end
153
+
154
+ def process_count
128
155
  pcount = Sidekiq::ProcessSet.new.size
129
156
  pcount = 1 if pcount == 0
130
- pcount * Sidekiq.options[:average_scheduled_poll_interval]
157
+ pcount
131
158
  end
132
159
 
133
160
  def initial_wait
@@ -72,9 +72,7 @@ module Sidekiq
72
72
 
73
73
  class EmptyQueueError < RuntimeError; end
74
74
 
75
- class Client
76
- alias_method :raw_push_real, :raw_push
77
-
75
+ module TestingClient
78
76
  def raw_push(payloads)
79
77
  if Sidekiq::Testing.fake?
80
78
  payloads.each do |job|
@@ -92,11 +90,13 @@ module Sidekiq
92
90
  end
93
91
  true
94
92
  else
95
- raw_push_real(payloads)
93
+ super
96
94
  end
97
95
  end
98
96
  end
99
97
 
98
+ Sidekiq::Client.prepend TestingClient
99
+
100
100
  module Queues
101
101
  ##
102
102
  # The Queues class is only for testing the fake queue implementation.
data/lib/sidekiq/util.rb CHANGED
@@ -21,7 +21,7 @@ module Sidekiq
21
21
 
22
22
  def safe_thread(name, &block)
23
23
  Thread.new do
24
- Thread.current['sidekiq_label'.freeze] = name
24
+ Thread.current['sidekiq_label'] = name
25
25
  watchdog(name, &block)
26
26
  end
27
27
  end
@@ -46,7 +46,10 @@ module Sidekiq
46
46
  @@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
47
47
  end
48
48
 
49
- def fire_event(event, reverse=false)
49
+ def fire_event(event, options={})
50
+ reverse = options[:reverse]
51
+ reraise = options[:reraise]
52
+
50
53
  arr = Sidekiq.options[:lifecycle_events][event]
51
54
  arr.reverse! if reverse
52
55
  arr.each do |block|
@@ -54,6 +57,7 @@ module Sidekiq
54
57
  block.call
55
58
  rescue => ex
56
59
  handle_exception(ex, { context: "Exception during Sidekiq lifecycle event.", event: event })
60
+ raise ex if reraise
57
61
  end
58
62
  end
59
63
  arr.clear
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Sidekiq
3
- VERSION = "5.0.5"
3
+ VERSION = "5.2.0"
4
4
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Sidekiq
4
4
  class WebAction
5
- RACK_SESSION = 'rack.session'.freeze
5
+ RACK_SESSION = 'rack.session'
6
6
 
7
7
  attr_accessor :env, :block, :type
8
8
 
@@ -77,7 +77,7 @@ module Sidekiq
77
77
  private
78
78
 
79
79
  def _erb(file, locals)
80
- locals.each {|k, v| define_singleton_method(k){ v } } if locals
80
+ locals.each {|k, v| define_singleton_method(k){ v } unless (singleton_methods.include? k)} if locals
81
81
 
82
82
  if file.kind_of?(String)
83
83
  ERB.new(file).result(binding)
@@ -4,9 +4,24 @@ module Sidekiq
4
4
  class WebApplication
5
5
  extend WebRouter
6
6
 
7
- CONTENT_LENGTH = "Content-Length".freeze
8
- CONTENT_TYPE = "Content-Type".freeze
7
+ CONTENT_LENGTH = "Content-Length"
8
+ CONTENT_TYPE = "Content-Type"
9
9
  REDIS_KEYS = %w(redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human)
10
+ CSP_HEADER = [
11
+ "default-src 'self' https: http:",
12
+ "child-src 'self'",
13
+ "connect-src 'self' https: http: wss: ws:",
14
+ "font-src 'self' https: http:",
15
+ "frame-src 'self'",
16
+ "img-src 'self' https: http: data:",
17
+ "manifest-src 'self'",
18
+ "media-src 'self'",
19
+ "object-src 'none'",
20
+ "script-src 'self' https: http:",
21
+ "style-src 'self' https: http: 'unsafe-inline'",
22
+ "worker-src 'self'",
23
+ "base-uri 'self'"
24
+ ].join('; ').freeze
10
25
 
11
26
  def initialize(klass)
12
27
  @klass = klass
@@ -279,6 +294,7 @@ module Sidekiq
279
294
  "Content-Type" => "text/html",
280
295
  "Cache-Control" => "no-cache",
281
296
  "Content-Language" => action.locale,
297
+ "Content-Security-Policy" => CSP_HEADER
282
298
  }
283
299
 
284
300
  [200, headers, [resp]]
@@ -80,7 +80,7 @@ module Sidekiq
80
80
 
81
81
  # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
82
82
  def user_preferred_languages
83
- languages = env['HTTP_ACCEPT_LANGUAGE'.freeze]
83
+ languages = env['HTTP_ACCEPT_LANGUAGE']
84
84
  languages.to_s.downcase.gsub(/\s+/, '').split(',').map do |language|
85
85
  locale, quality = language.split(';q=', 2)
86
86
  locale = nil if locale == '*' # Ignore wildcards
@@ -148,12 +148,14 @@ module Sidekiq
148
148
  end
149
149
 
150
150
  def redis_connection
151
- attrs = Sidekiq.redis { |conn| conn.connection }
152
- "redis://#{attrs[:location]}/#{attrs[:db]}"
151
+ Sidekiq.redis do |conn|
152
+ c = conn.connection
153
+ "redis://#{c[:location]}/#{c[:db]}"
154
+ end
153
155
  end
154
156
 
155
157
  def namespace
156
- @@ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
158
+ @ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil }
157
159
  end
158
160
 
159
161
  def redis_info
@@ -182,7 +184,7 @@ module Sidekiq
182
184
  end
183
185
 
184
186
  def parse_params(params)
185
- score, jid = params.split("-")
187
+ score, jid = params.split("-", 2)
186
188
  [score.to_f, jid]
187
189
  end
188
190
 
@@ -3,16 +3,16 @@ require 'rack'
3
3
 
4
4
  module Sidekiq
5
5
  module WebRouter
6
- GET = 'GET'.freeze
7
- DELETE = 'DELETE'.freeze
8
- POST = 'POST'.freeze
9
- PUT = 'PUT'.freeze
10
- PATCH = 'PATCH'.freeze
11
- HEAD = 'HEAD'.freeze
6
+ GET = 'GET'
7
+ DELETE = 'DELETE'
8
+ POST = 'POST'
9
+ PUT = 'PUT'
10
+ PATCH = 'PATCH'
11
+ HEAD = 'HEAD'
12
12
 
13
- ROUTE_PARAMS = 'rack.route_params'.freeze
14
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
15
- PATH_INFO = 'PATH_INFO'.freeze
13
+ ROUTE_PARAMS = 'rack.route_params'
14
+ REQUEST_METHOD = 'REQUEST_METHOD'
15
+ PATH_INFO = 'PATH_INFO'
16
16
 
17
17
  def get(path, &block)
18
18
  route(GET, path, &block)
@@ -64,7 +64,7 @@ module Sidekiq
64
64
  class WebRoute
65
65
  attr_accessor :request_method, :pattern, :block, :name
66
66
 
67
- NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/.freeze
67
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^\.:$\/]+)/
68
68
 
69
69
  def initialize(request_method, pattern, block)
70
70
  @request_method = request_method
data/lib/sidekiq/web.rb CHANGED
@@ -19,10 +19,10 @@ require 'rack/session/cookie'
19
19
  module Sidekiq
20
20
  class Web
21
21
  ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web")
22
- VIEWS = "#{ROOT}/views".freeze
23
- LOCALES = ["#{ROOT}/locales".freeze]
24
- LAYOUT = "#{VIEWS}/layout.erb".freeze
25
- ASSETS = "#{ROOT}/assets".freeze
22
+ VIEWS = "#{ROOT}/views"
23
+ LOCALES = ["#{ROOT}/locales"]
24
+ LAYOUT = "#{VIEWS}/layout.erb"
25
+ ASSETS = "#{ROOT}/assets"
26
26
 
27
27
  DEFAULT_TABS = {
28
28
  "Dashboard" => '',
@@ -47,7 +47,7 @@ module Sidekiq
47
47
  end
48
48
 
49
49
  def perform_async(*args)
50
- @klass.client_push(@opts.merge('args'.freeze => args, 'class'.freeze => @klass))
50
+ @klass.client_push(@opts.merge('args' => args, 'class' => @klass))
51
51
  end
52
52
 
53
53
  # +interval+ must be a timestamp, numeric or something that acts
@@ -57,9 +57,9 @@ module Sidekiq
57
57
  now = Time.now.to_f
58
58
  ts = (int < 1_000_000_000 ? now + int : int)
59
59
 
60
- payload = @opts.merge('class'.freeze => @klass, 'args'.freeze => args, 'at'.freeze => ts)
60
+ payload = @opts.merge('class' => @klass, 'args' => args, 'at' => ts)
61
61
  # Optimization to enqueue something now that is scheduled to go out now or in the past
62
- payload.delete('at'.freeze) if ts <= now
62
+ payload.delete('at') if ts <= now
63
63
  @klass.client_push(payload)
64
64
  end
65
65
  alias_method :perform_at, :perform_in
@@ -84,7 +84,7 @@ module Sidekiq
84
84
  end
85
85
 
86
86
  def perform_async(*args)
87
- client_push('class'.freeze => self, 'args'.freeze => args)
87
+ client_push('class' => self, 'args' => args)
88
88
  end
89
89
 
90
90
  # +interval+ must be a timestamp, numeric or something that acts
@@ -94,10 +94,10 @@ module Sidekiq
94
94
  now = Time.now.to_f
95
95
  ts = (int < 1_000_000_000 ? now + int : int)
96
96
 
97
- item = { 'class'.freeze => self, 'args'.freeze => args, 'at'.freeze => ts }
97
+ item = { 'class' => self, 'args' => args, 'at' => ts }
98
98
 
99
99
  # Optimization to enqueue something now that is scheduled to go out now or in the past
100
- item.delete('at'.freeze) if ts <= now
100
+ item.delete('at') if ts <= now
101
101
 
102
102
  client_push(item)
103
103
  end
@@ -134,7 +134,7 @@ module Sidekiq
134
134
  end
135
135
 
136
136
  def client_push(item) # :nodoc:
137
- pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'.freeze] || Sidekiq.redis_pool
137
+ pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'] || Sidekiq.redis_pool
138
138
  # stringify
139
139
  item.keys.each do |key|
140
140
  item[key.to_s] = item.delete(key)
data/lib/sidekiq.rb CHANGED
@@ -1,7 +1,6 @@
1
- # encoding: utf-8
2
1
  # frozen_string_literal: true
3
2
  require 'sidekiq/version'
4
- fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.2.2." if RUBY_PLATFORM != 'java' && RUBY_VERSION < '2.2.2'
3
+ fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.2.2." if RUBY_PLATFORM != 'java' && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2.2')
5
4
 
6
5
  require 'sidekiq/logging'
7
6
  require 'sidekiq/client'
@@ -12,19 +11,20 @@ require 'sidekiq/delay'
12
11
  require 'json'
13
12
 
14
13
  module Sidekiq
15
- NAME = 'Sidekiq'.freeze
14
+ NAME = 'Sidekiq'
16
15
  LICENSE = 'See LICENSE and the LGPL-3.0 for licensing details.'
17
16
 
18
17
  DEFAULTS = {
19
18
  queues: [],
20
19
  labels: [],
21
- concurrency: 25,
20
+ concurrency: 10,
22
21
  require: '.',
23
22
  environment: nil,
24
23
  timeout: 8,
25
24
  poll_interval_average: nil,
26
- average_scheduled_poll_interval: 15,
25
+ average_scheduled_poll_interval: 5,
27
26
  error_handlers: [],
27
+ death_handlers: [],
28
28
  lifecycle_events: {
29
29
  startup: [],
30
30
  quiet: [],
@@ -47,7 +47,7 @@ module Sidekiq
47
47
  "connected_clients" => "9999",
48
48
  "used_memory_human" => "9P",
49
49
  "used_memory_peak_human" => "9P"
50
- }.freeze
50
+ }
51
51
 
52
52
  def self.❨╯°□°❩╯︵┻━┻
53
53
  puts "Calm down, yo."
@@ -156,16 +156,23 @@ module Sidekiq
156
156
  defined?(@default_worker_options) ? @default_worker_options : DEFAULT_WORKER_OPTIONS
157
157
  end
158
158
 
159
+ def self.default_retries_exhausted=(prok)
160
+ logger.info { "default_retries_exhausted is deprecated, please use `config.death_handlers << -> {|job, ex| }`" }
161
+ return nil unless prok
162
+ death_handlers << prok
163
+ end
164
+
165
+ ##
166
+ # Death handlers are called when all retries for a job have been exhausted and
167
+ # the job dies. It's the notification to your application
168
+ # that this job will not succeed without manual intervention.
169
+ #
159
170
  # Sidekiq.configure_server do |config|
160
- # config.default_retries_exhausted = -> (job, ex) do
171
+ # config.death_handlers << ->(job, ex) do
161
172
  # end
162
173
  # end
163
- def self.default_retries_exhausted=(prok)
164
- @default_retries_exhausted = prok
165
- end
166
- @default_retries_exhausted = ->(job, ex) { }
167
- def self.default_retries_exhausted
168
- @default_retries_exhausted
174
+ def self.death_handlers
175
+ options[:death_handlers]
169
176
  end
170
177
 
171
178
  def self.load_json(string)
data/sidekiq.gemspec CHANGED
@@ -17,12 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.version = Sidekiq::VERSION
18
18
  gem.required_ruby_version = ">= 2.2.2"
19
19
 
20
- gem.add_dependency 'redis', '>= 3.3.4', '< 5'
21
- gem.add_dependency 'connection_pool', '~> 2.2', '>= 2.2.0'
22
- gem.add_dependency 'concurrent-ruby', '~> 1.0'
23
- gem.add_dependency 'rack-protection', '>= 1.5.0'
24
- gem.add_development_dependency 'redis-namespace', '~> 1.5', '>= 1.5.2'
25
- gem.add_development_dependency 'minitest', '~> 5.10', '>= 5.10.1'
26
- gem.add_development_dependency 'rake', '~> 10.0'
27
- gem.add_development_dependency 'rails', '>= 3.2.0'
20
+ gem.add_dependency 'redis', '>= 3.3.5', '< 5'
21
+ gem.add_dependency 'connection_pool', '~> 2.2', '>= 2.2.2'
22
+ gem.add_dependency 'rack-protection', '>= 1.5.0'
28
23
  end
File without changes
File without changes