sidekiq 5.2.2 → 5.2.3

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 985b2f1c0778b1e4d2587eb2f182fc58723277c7
4
- data.tar.gz: 2d20a6e15e248bea802b1ca9438c9507a1f94ab4
3
+ metadata.gz: 0f67e98eb8e3fff8ee4d57f08d5668e41b320fa4
4
+ data.tar.gz: a11f770fd95fd45b3c0be43beee5c83417811fd4
5
5
  SHA512:
6
- metadata.gz: 4e4be1e23c16eb53d43424a0a07e6b69ab2001ffbbaf28c7c09db1dac89d9f1e9ac3513c70ac5abd398efb12c8a712da20f563df5fd055c6855eb5d2d7abea05
7
- data.tar.gz: 8947fc9287e9a1336ba57b6ef380e5f76d7ea7382fff3930acd914c6c08fed9c08940780c1537bbedf349ed29257b9c249f8356eed095d8537a4195e7426b676
6
+ metadata.gz: 3e6739681028126924c049e41bbfcdc92bea0d1ee4fb965f4e332da4cca9f10352f5cf18e05345a843d19b6111510f55c88aa97f4e21585bf66b479b7ae2f9bc
7
+ data.tar.gz: 07f0432c69395be2933f152acb09c8fa4fe43be104011df237b0b4db56073b4725eb3fa3073872376f09d072ea15608f79a32b3dc28104ba18665082ad64adf4
data/Changes.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  [Sidekiq Changes](https://github.com/mperham/sidekiq/blob/master/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/master/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/master/Ent-Changes.md)
4
4
 
5
+ 5.2.3
6
+ ---------
7
+
8
+ - Warning message on invalid REDIS\_PROVIDER [#3970]
9
+ - Add `sidekiqctl status` command [#4003, dzunk]
10
+ - Update elapsed time calculatons to use monotonic clock [#3999]
11
+ - Fix a few issues with mobile Web UI styling [#3973, navied]
12
+ - Jobs with `retry: false` now go through the global `death_handlers`,
13
+ meaning you can take action on failed ephemeral jobs. [#3980, Benjamin-Dobell]
14
+ - Fix race condition in defining Workers. [#3997, mattbooks]
15
+
5
16
  5.2.2
6
17
  ---------
7
18
 
@@ -7,6 +7,9 @@ Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how t
7
7
  HEAD
8
8
  -------------
9
9
 
10
+ - Fix elapsed time calculations to use monotonic clock [#4000, sj26]
11
+ - Fix edge case where flapping leadership would cause old periodic
12
+ jobs to be fired once [#3974]
10
13
  - Add support for sidekiqswarm memory monitoring on FreeBSD [#3884]
11
14
 
12
15
  1.7.1
@@ -4,6 +4,11 @@
4
4
 
5
5
  Please see [http://sidekiq.org/](http://sidekiq.org/) for more details and how to buy.
6
6
 
7
+ HEAD
8
+ ---------
9
+
10
+ - Add Japanese and Russian translations, see [issue #3951](https://github.com/mperham/sidekiq/issues/3951) to add your own language
11
+
7
12
  4.0.4
8
13
  ---------
9
14
 
@@ -1,20 +1,27 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'fileutils'
4
+ require 'sidekiq/api'
4
5
 
5
6
  class Sidekiqctl
6
7
  DEFAULT_KILL_TIMEOUT = 10
8
+ CMD = File.basename($0)
7
9
 
8
10
  attr_reader :stage, :pidfile, :kill_timeout
9
11
 
10
12
  def self.print_usage
11
- puts "#{File.basename($0)} - stop a Sidekiq process from the command line."
13
+ puts "#{CMD} - control Sidekiq from the command line."
14
+ puts
15
+ puts "Usage: #{CMD} quiet <pidfile> <kill_timeout>"
16
+ puts " #{CMD} stop <pidfile> <kill_timeout>"
17
+ puts " #{CMD} status <section>"
12
18
  puts
13
- puts "Usage: #{File.basename($0)} <command> <pidfile> <kill_timeout>"
14
- puts " where <command> is either 'quiet' or 'stop'"
15
19
  puts " <pidfile> is path to a pidfile"
16
20
  puts " <kill_timeout> is number of seconds to wait until Sidekiq exits"
17
21
  puts " (default: #{Sidekiqctl::DEFAULT_KILL_TIMEOUT}), after which Sidekiq will be KILL'd"
22
+ puts
23
+ puts " <section> (optional) view a specific section of the status output"
24
+ puts " Valid sections are: #{Sidekiqctl::Status::VALID_SECTIONS.join(', ')}"
18
25
  puts
19
26
  puts "Be sure to set the kill_timeout LONGER than Sidekiq's -t timeout. If you want"
20
27
  puts "to wait 60 seconds for jobs to finish, use `sidekiq -t 60` and `sidekiqctl stop"
@@ -85,6 +92,137 @@ class Sidekiqctl
85
92
  done 'Sidekiq shut down forcefully.'
86
93
  end
87
94
  alias_method :shutdown, :stop
95
+
96
+ class Status
97
+ VALID_SECTIONS = %w[all version overview processes queues]
98
+ def display(section = nil)
99
+ section ||= 'all'
100
+ unless VALID_SECTIONS.include? section
101
+ puts "I don't know how to check the status of '#{section}'!"
102
+ puts "Try one of these: #{VALID_SECTIONS.join(', ')}"
103
+ return
104
+ end
105
+ send(section)
106
+ rescue StandardError => e
107
+ puts "Couldn't get status: #{e}"
108
+ end
109
+
110
+ def all
111
+ version
112
+ puts
113
+ overview
114
+ puts
115
+ processes
116
+ puts
117
+ queues
118
+ end
119
+
120
+ def version
121
+ puts "Sidekiq #{Sidekiq::VERSION}"
122
+ puts Time.now
123
+ end
124
+
125
+ def overview
126
+ puts '---- Overview ----'
127
+ puts " Processed: #{delimit stats.processed}"
128
+ puts " Failed: #{delimit stats.failed}"
129
+ puts " Busy: #{delimit stats.workers_size}"
130
+ puts " Enqueued: #{delimit stats.enqueued}"
131
+ puts " Retries: #{delimit stats.retry_size}"
132
+ puts " Scheduled: #{delimit stats.scheduled_size}"
133
+ puts " Dead: #{delimit stats.dead_size}"
134
+ end
135
+
136
+ def processes
137
+ puts "---- Processes (#{process_set.size}) ----"
138
+ process_set.each_with_index do |process, index|
139
+ puts "#{process['identity']} #{tags_for(process)}"
140
+ puts " Started: #{Time.at(process['started_at'])} (#{time_ago(process['started_at'])})"
141
+ puts " Threads: #{process['concurrency']} (#{process['busy']} busy)"
142
+ puts " Queues: #{split_multiline(process['queues'].sort, pad: 11)}"
143
+ puts '' unless (index+1) == process_set.size
144
+ end
145
+ end
146
+
147
+ COL_PAD = 2
148
+ def queues
149
+ puts "---- Queues (#{queue_data.size}) ----"
150
+ columns = {
151
+ name: [:ljust, (['name'] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
152
+ size: [:rjust, (['size'] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
153
+ latency: [:rjust, (['latency'] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
154
+ }
155
+ columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
156
+ puts
157
+ queue_data.each do |q|
158
+ columns.each do |col, (dir, width)|
159
+ print q.send(col).public_send(dir, width)
160
+ end
161
+ puts
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ def delimit(number)
168
+ number.to_s.reverse.scan(/.{1,3}/).join(',').reverse
169
+ end
170
+
171
+ def split_multiline(values, opts = {})
172
+ return 'none' unless values
173
+ pad = opts[:pad] || 0
174
+ max_length = opts[:max_length] || (80 - pad)
175
+ out = []
176
+ line = ''
177
+ values.each do |value|
178
+ if (line.length + value.length) > max_length
179
+ out << line
180
+ line = ' ' * pad
181
+ end
182
+ line << value + ', '
183
+ end
184
+ out << line[0..-3]
185
+ out.join("\n")
186
+ end
187
+
188
+ def tags_for(process)
189
+ tags = [
190
+ process['tag'],
191
+ process['labels'],
192
+ (process['quiet'] == 'true' ? 'quiet' : nil)
193
+ ].flatten.compact
194
+ tags.any? ? "[#{tags.join('] [')}]" : nil
195
+ end
196
+
197
+ def time_ago(timestamp)
198
+ seconds = Time.now - Time.at(timestamp)
199
+ return 'just now' if seconds < 60
200
+ return 'a minute ago' if seconds < 120
201
+ return "#{seconds.floor / 60} minutes ago" if seconds < 3600
202
+ return 'an hour ago' if seconds < 7200
203
+ "#{seconds.floor / 60 / 60} hours ago"
204
+ end
205
+
206
+ QUEUE_STRUCT = Struct.new(:name, :size, :latency)
207
+ def queue_data
208
+ @queue_data ||= Sidekiq::Queue.all.map do |q|
209
+ QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf('%#.2f', q.latency))
210
+ end
211
+ end
212
+
213
+ def process_set
214
+ @process_set ||= Sidekiq::ProcessSet.new
215
+ end
216
+
217
+ def stats
218
+ @stats ||= Sidekiq::Stats.new
219
+ end
220
+ end
221
+ end
222
+
223
+ if ARGV[0] == 'status'
224
+ Sidekiqctl::Status.new.display(ARGV[1])
225
+ exit
88
226
  end
89
227
 
90
228
  if ARGV.length < 2
@@ -3,7 +3,7 @@ module Sidekiq
3
3
  class JobLogger
4
4
 
5
5
  def call(item, queue)
6
- start = Time.now
6
+ start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
7
7
  logger.info("start")
8
8
  yield
9
9
  logger.info("done: #{elapsed(start)} sec")
@@ -15,7 +15,7 @@ module Sidekiq
15
15
  private
16
16
 
17
17
  def elapsed(start)
18
- (Time.now - start).round(3)
18
+ (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start).round(3)
19
19
  end
20
20
 
21
21
  def logger
@@ -80,8 +80,18 @@ module Sidekiq
80
80
  # ignore, will be pushed back onto queue during hard_shutdown
81
81
  raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e)
82
82
 
83
- raise e unless msg['retry']
84
- attempt_retry(nil, msg, queue, e)
83
+ if msg['retry']
84
+ attempt_retry(nil, msg, queue, e)
85
+ else
86
+ Sidekiq.death_handlers.each do |handler|
87
+ begin
88
+ handler.call(msg, e)
89
+ rescue => handler_ex
90
+ handle_exception(handler_ex, { context: "Error calling death handler", job: msg })
91
+ end
92
+ end
93
+ end
94
+
85
95
  raise e
86
96
  end
87
97
 
@@ -158,7 +168,8 @@ module Sidekiq
158
168
 
159
169
  if count < max_retry_attempts
160
170
  delay = delay_for(worker, count, exception)
161
- logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
171
+ # Logging here can break retries if the logging device raises ENOSPC #3979
172
+ #logger.debug { "Failure! Retry #{count} in #{delay} seconds" }
162
173
  retry_at = Time.now.to_f + delay
163
174
  payload = Sidekiq.dump_json(msg)
164
175
  Sidekiq.redis do |conn|
@@ -171,7 +182,6 @@ module Sidekiq
171
182
  end
172
183
 
173
184
  def retries_exhausted(worker, msg, exception)
174
- logger.debug { "Retries exhausted for job" }
175
185
  begin
176
186
  block = worker && worker.sidekiq_retries_exhausted_block
177
187
  block.call(msg, exception) if block
@@ -191,7 +201,7 @@ module Sidekiq
191
201
  end
192
202
 
193
203
  def send_to_morgue(msg)
194
- Sidekiq.logger.info { "Adding dead #{msg['class']} job #{msg['jid']}" }
204
+ logger.info { "Adding dead #{msg['class']} job #{msg['jid']}" }
195
205
  payload = Sidekiq.dump_json(msg)
196
206
  DeadSet.new.kill(payload, notify_failure: false)
197
207
  end
@@ -40,7 +40,7 @@ module Sidekiq
40
40
  # return until all work is complete and cleaned up.
41
41
  # It can take up to the timeout to complete.
42
42
  def stop
43
- deadline = Time.now + @options[:timeout]
43
+ deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @options[:timeout]
44
44
 
45
45
  @done = true
46
46
  @manager.quiet
@@ -30,7 +30,7 @@ module Sidekiq
30
30
  def initialize(options={})
31
31
  logger.debug { options.inspect }
32
32
  @options = options
33
- @count = options[:concurrency] || 25
33
+ @count = options[:concurrency] || 10
34
34
  raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
35
35
 
36
36
  @done = false
@@ -70,11 +70,11 @@ module Sidekiq
70
70
  return if @workers.empty?
71
71
 
72
72
  logger.info { "Pausing to allow workers to finish..." }
73
- remaining = deadline - Time.now
73
+ remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
74
74
  while remaining > PAUSE_TIME
75
75
  return if @workers.empty?
76
76
  sleep PAUSE_TIME
77
- remaining = deadline - Time.now
77
+ remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
78
78
  end
79
79
  return if @workers.empty?
80
80
 
@@ -87,7 +87,7 @@ module Sidekiq
87
87
  def get_one
88
88
  begin
89
89
  work = @strategy.retrieve_work
90
- (logger.info { "Redis is online, #{Time.now - @down} sec downtime" }; @down = nil) if @down
90
+ (logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }; @down = nil) if @down
91
91
  work
92
92
  rescue Sidekiq::Shutdown
93
93
  rescue => ex
@@ -107,7 +107,7 @@ module Sidekiq
107
107
 
108
108
  def handle_fetch_exception(ex)
109
109
  if !@down
110
- @down = Time.now
110
+ @down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
111
111
  logger.error("Error fetching job: #{ex}")
112
112
  handle_exception(ex)
113
113
  end
@@ -115,6 +115,25 @@ module Sidekiq
115
115
  # REDIS_PROVIDER=MY_REDIS_URL
116
116
  # and Sidekiq will find your custom URL variable with no custom
117
117
  # initialization code at all.
118
+ p = ENV['REDIS_PROVIDER']
119
+ if p && p =~ /\:/
120
+ Sidekiq.logger.error <<-EOM
121
+
122
+ #################################################################################
123
+
124
+ REDIS_PROVIDER should be set to the **name** of the variable which contains the Redis URL, not a URL itself.
125
+ Platforms like Heroku sell addons that publish a *_URL variable. You tell Sidekiq with REDIS_PROVIDER, e.g.:
126
+
127
+ REDIS_PROVIDER=REDISTOGO_URL
128
+ REDISTOGO_URL=redis://somehost.example.com:6379/4
129
+
130
+ Use REDIS_URL if you wish to point Sidekiq to a URL directly.
131
+
132
+ This configuration error will crash starting in Sidekiq 5.3.
133
+
134
+ #################################################################################
135
+ EOM
136
+ end
118
137
  ENV[
119
138
  ENV['REDIS_PROVIDER'] || 'REDIS_URL'
120
139
  ]
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Sidekiq
3
- VERSION = "5.2.2"
3
+ VERSION = "5.2.3"
4
4
  end
@@ -66,6 +66,7 @@ module Sidekiq
66
66
  end
67
67
 
68
68
  module ClassMethods
69
+ ACCESSOR_MUTEX = Mutex.new
69
70
 
70
71
  def delay(*args)
71
72
  raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async"
@@ -148,10 +149,18 @@ module Sidekiq
148
149
  instance_writer = true
149
150
 
150
151
  attrs.each do |name|
152
+ synchronized_getter = "__synchronized_#{name}"
153
+
151
154
  singleton_class.instance_eval do
152
155
  undef_method(name) if method_defined?(name) || private_method_defined?(name)
153
156
  end
154
- define_singleton_method(name) { nil }
157
+
158
+ define_singleton_method(synchronized_getter) { nil }
159
+ singleton_class.class_eval do
160
+ private(synchronized_getter)
161
+ end
162
+
163
+ define_singleton_method(name) { ACCESSOR_MUTEX.synchronize { send synchronized_getter } }
155
164
 
156
165
  ivar = "@#{name}"
157
166
 
@@ -161,8 +170,10 @@ module Sidekiq
161
170
  end
162
171
  define_singleton_method("#{name}=") do |val|
163
172
  singleton_class.class_eval do
164
- undef_method(name) if method_defined?(name) || private_method_defined?(name)
165
- define_method(name) { val }
173
+ ACCESSOR_MUTEX.synchronize do
174
+ undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter)
175
+ define_method(synchronized_getter) { val }
176
+ end
166
177
  end
167
178
 
168
179
  if singleton_class?
@@ -298,8 +298,18 @@ var debounce = function(fn, timeout)
298
298
  }
299
299
  };
300
300
 
301
- window.onresize = debounce(function() {
302
- clearInterval(poller);
303
- resetGraphs();
304
- renderGraphs();
305
- }, 125);
301
+ window.onresize = function() {
302
+ var prevWidth = window.innerWidth;
303
+
304
+ return debounce(function () {
305
+ var currWidth = window.innerWidth;
306
+
307
+ if (prevWidth !== currWidth) {
308
+ prevWidth = currWidth;
309
+
310
+ clearInterval(poller);
311
+ resetGraphs();
312
+ renderGraphs();
313
+ }
314
+ }, 125);
315
+ }();
@@ -126,7 +126,7 @@ header.row .pagination {
126
126
  text-align: center;
127
127
  width: 14%;
128
128
  }
129
- @media (max-width: 767px) and (min-width: 400px) {
129
+ @media (max-width: 767px) and (min-width: 200px) {
130
130
  .summary_bar ul li {
131
131
  width: 100%;
132
132
  }
@@ -219,6 +219,29 @@ table .table-checkbox label {
219
219
  color: #585454;
220
220
  }
221
221
 
222
+
223
+ .nav.navbar-nav{
224
+ display: flex;
225
+ width: 100%;
226
+ }
227
+
228
+ .navbar-livereload{
229
+ margin-left: auto;
230
+ white-space: nowrap;
231
+ }
232
+
233
+ .navbar-livereload .poll-wrapper a:last-child{
234
+ margin-left: 8px;
235
+ }
236
+
237
+ .navbar-right{
238
+ margin-right: 0;
239
+ }
240
+
241
+ .navbar-collapse.collapse{
242
+ overflow-x: auto !important;
243
+ }
244
+
222
245
  @media (max-width: 768px) {
223
246
  .navbar .navbar-header .navbar-livereload {
224
247
  border: none;
@@ -234,13 +257,19 @@ table .table-checkbox label {
234
257
  display: none;
235
258
  }
236
259
 
260
+ .nav.navbar-nav{
261
+ display: block;
262
+ width: auto;
263
+ }
264
+
237
265
  .navbar.navbar-fixed-top ul {
238
266
  margin-right: -15px!important;
239
267
  }
240
268
 
241
269
  .navbar .nav a {
242
270
  text-align: center;
243
- }
271
+ }
272
+
244
273
  }
245
274
 
246
275
  @media (width: 768px) {
@@ -645,6 +674,10 @@ div.interval-slider input {
645
674
  margin-right: 0;
646
675
  }
647
676
 
677
+ .navbar #navbar-menu{
678
+ display: none;
679
+ }
680
+
648
681
  .poll-wrapper {
649
682
  width: 100%;
650
683
  text-align: center;
@@ -9,7 +9,7 @@
9
9
  <div class="navbar-toggle collapsed navbar-livereload">
10
10
  <%= erb :_poll_link %>
11
11
  <% if Sidekiq::Web.app_url %>
12
- <a class="btn btn-inverse" href="<%= Sidekiq::Web.app_url %>">Back to App</a>
12
+ <a class="btn btn-inverse" href="<%= Sidekiq::Web.app_url %>"><%= t('BackToApp') %></a>
13
13
  <% end %>
14
14
  </div>
15
15
  <a class="navbar-brand" href="<%= root_path %>">
@@ -32,27 +32,13 @@
32
32
  <% end %>
33
33
  <% end %>
34
34
 
35
- <li class="dropdown" data-navbar="dropdown">
36
- <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
37
- <%= t('Plugins') %> <span class="caret"></span>
38
- </a>
39
- <ul class="dropdown-menu" role="menu">
40
- <% Sidekiq::Web.custom_tabs.each do |title, url| %>
41
- <li>
42
- <a href="<%= root_path %><%= url %>"><%= t(title) %></a>
43
- </li>
44
- <% end %>
45
- </ul>
46
- </li>
47
-
48
35
  <% Sidekiq::Web.custom_tabs.each do |title, url| %>
49
36
  <li class="<%= current_path.start_with?(url) ? 'active' : '' %>" data-navbar="custom-tab">
50
37
  <a href="<%= root_path %><%= url %>"><%= t(title) %></a>
51
38
  </li>
52
39
  <% end %>
53
- </ul>
54
- <ul class="nav navbar-nav navbar-right navbar-livereload" data-navbar="static">
55
- <li>
40
+
41
+ <li class="navbar-livereload">
56
42
  <div class="poll-wrapper">
57
43
  <%= erb :_poll_link %>
58
44
  <% if Sidekiq::Web.app_url %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.2
4
+ version: 5.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-17 00:00:00.000000000 Z
11
+ date: 2018-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis