sidekiq 7.0.1 → 7.0.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
  SHA256:
3
- metadata.gz: 98aab60f0a9bd14122cf62b2f31c17e41b67feb8c9fb13865ef0a16002d38ed7
4
- data.tar.gz: 94038f0176ccb7785c6112cd41e915e4a1c7f66298a9670457a68e6b62f2710c
3
+ metadata.gz: 93289a9aed52b658b609cb6de99fefe182003d71bcc0102fa82fa33d53a70f5b
4
+ data.tar.gz: 1ca1b7ff222845a4a6f23ea00d77826c248a8e45db176eb9f2194afef70dface
5
5
  SHA512:
6
- metadata.gz: c41c724dc99aa3fdf49db46eeac0bec822bd6a67989792f4830900da7b74882c27837535a03a2ea6ed0575736fc895b23bb3440613d1f581e877bcbfe1c06934
7
- data.tar.gz: 482af09f25f72f9fa0261f02e4e224dc58a400519f167f8cb0ea0dda4960b24a41b105731a4fcc2b81ff15f158497b9bf1cc86d55272ed8fe4ca7ed9029bc047
6
+ metadata.gz: 680961f7df970445211487780291ecc1adb738fa834126428bbb814b0b7b03e912889c66020079f24aaf548a023c9621e601631d66dd6f7f467e9f8ac5085fee
7
+ data.tar.gz: cdd1e337afbba5aaea110227e8b91e1b965e05c6ff9a5b7f5202850e3725d4d6959137dd8f47075057230451e394dca3ca0cbe5c7d70ef33ac6284a21e7fbea4
data/Changes.md CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  [Sidekiq Changes](https://github.com/mperham/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md)
4
4
 
5
+ 7.0.3
6
+ ----------
7
+
8
+ - Don't warn about memory policy on Redis Enterprise [#5712]
9
+ - Don't allow Quiet/Stop on embedded Sidekiq instances [#5716]
10
+ - Fix `size: X` for configuring the default Redis pool size [#5702]
11
+ - Improve the display of queue weights on Busy page [#5642]
12
+ - Freeze CurrentAttributes on a job once initially set [#5692]
13
+
14
+ 7.0.2
15
+ ----------
16
+
17
+ - Improve compatibility with custom loggers [#5673]
18
+ - Add queue weights on Busy page [#5640]
19
+ - Add BID link on job_info page if job is part of a Batch [#5623]
20
+ - Allow custom extensions to add rows/links within Job detail pages [#5624]
21
+ ```ruby
22
+ Sidekiq::Web.custom_job_info_rows << AddAccountLink.new
23
+
24
+ class AddAccountLink
25
+ include CGI::Util
26
+ def add_pair(job)
27
+ # yield a (name, value) pair
28
+ # You can include HTML tags and CSS, Sidekiq does not do any
29
+ # escaping so beware user data injection! Note how we use CGI's
30
+ # `h` escape helper.
31
+ aid = job["account_id"]
32
+ yield "Account", "<a href='/accounts/#{h aid}'>#{h aid}</a>" if aid
33
+ end
34
+ end
35
+ ```
36
+
5
37
  7.0.1
6
38
  ----------
7
39
 
data/lib/sidekiq/api.rb CHANGED
@@ -418,6 +418,10 @@ module Sidekiq
418
418
  self["jid"]
419
419
  end
420
420
 
421
+ def bid
422
+ self["bid"]
423
+ end
424
+
421
425
  def enqueued_at
422
426
  self["enqueued_at"] ? Time.at(self["enqueued_at"]).utc : nil
423
427
  end
@@ -824,6 +828,24 @@ module Sidekiq
824
828
  class ProcessSet
825
829
  include Enumerable
826
830
 
831
+ def self.[](identity)
832
+ exists, (info, busy, beat, quiet, rss, rtt_us) = Sidekiq.redis { |conn|
833
+ conn.multi { |transaction|
834
+ transaction.sismember("processes", identity)
835
+ transaction.hmget(identity, "info", "busy", "beat", "quiet", "rss", "rtt_us")
836
+ }
837
+ }
838
+
839
+ return nil if exists == 0 || info.nil?
840
+
841
+ hash = Sidekiq.load_json(info)
842
+ Process.new(hash.merge("busy" => busy.to_i,
843
+ "beat" => beat.to_f,
844
+ "quiet" => quiet,
845
+ "rss" => rss.to_i,
846
+ "rtt_us" => rtt_us.to_i))
847
+ end
848
+
827
849
  # :nodoc:
828
850
  # @api private
829
851
  def initialize(clean_plz = true)
@@ -872,7 +894,7 @@ module Sidekiq
872
894
  end
873
895
  }
874
896
 
875
- result.each do |info, busy, at_s, quiet, rss, rtt|
897
+ result.each do |info, busy, beat, quiet, rss, rtt_us|
876
898
  # If a process is stopped between when we query Redis for `procs` and
877
899
  # when we query for `result`, we will have an item in `result` that is
878
900
  # composed of `nil` values.
@@ -880,10 +902,10 @@ module Sidekiq
880
902
 
881
903
  hash = Sidekiq.load_json(info)
882
904
  yield Process.new(hash.merge("busy" => busy.to_i,
883
- "beat" => at_s.to_f,
905
+ "beat" => beat.to_f,
884
906
  "quiet" => quiet,
885
907
  "rss" => rss.to_i,
886
- "rtt_us" => rtt.to_i))
908
+ "rtt_us" => rtt_us.to_i))
887
909
  end
888
910
  end
889
911
 
@@ -939,6 +961,7 @@ module Sidekiq
939
961
  # 'busy' => 10,
940
962
  # 'beat' => <last heartbeat>,
941
963
  # 'identity' => <unique string identifying the process>,
964
+ # 'embedded' => true,
942
965
  # }
943
966
  class Process
944
967
  # :nodoc:
@@ -967,11 +990,25 @@ module Sidekiq
967
990
  self["queues"]
968
991
  end
969
992
 
993
+ def weights
994
+ self["weights"]
995
+ end
996
+
997
+ def version
998
+ self["version"]
999
+ end
1000
+
1001
+ def embedded?
1002
+ self["embedded"]
1003
+ end
1004
+
970
1005
  # Signal this process to stop processing new jobs.
971
1006
  # It will continue to execute jobs it has already fetched.
972
1007
  # This method is *asynchronous* and it can take 5-10
973
1008
  # seconds for the process to quiet.
974
1009
  def quiet!
1010
+ raise "Can't quiet an embedded process" if embedded?
1011
+
975
1012
  signal("TSTP")
976
1013
  end
977
1014
 
@@ -980,6 +1017,8 @@ module Sidekiq
980
1017
  # This method is *asynchronous* and it can take 5-10
981
1018
  # seconds for the process to start shutting down.
982
1019
  def stop!
1020
+ raise "Can't stop an embedded process" if embedded?
1021
+
983
1022
  signal("TERM")
984
1023
  end
985
1024
 
@@ -21,12 +21,15 @@ module Sidekiq
21
21
  attr_reader :name
22
22
  attr_reader :queues
23
23
  attr_accessor :concurrency
24
+ attr_reader :mode
25
+ attr_reader :weights
24
26
 
25
27
  def initialize(name, config)
26
28
  @name = name
27
29
  @config = config
28
30
  @queues = ["default"]
29
31
  @concurrency = config[:concurrency]
32
+ @mode = :strict
30
33
  end
31
34
 
32
35
  def fetcher
@@ -41,15 +44,28 @@ module Sidekiq
41
44
  fetcher&.bulk_requeue([])
42
45
  end
43
46
 
47
+ # Sidekiq checks queues in three modes:
48
+ # - :strict - all queues have 0 weight and are checked strictly in order
49
+ # - :weighted - queues have arbitrary weight between 1 and N
50
+ # - :random - all queues have weight of 1
44
51
  def queues=(val)
52
+ @weights = {}
45
53
  @queues = Array(val).each_with_object([]) do |qstr, memo|
46
54
  arr = qstr
47
55
  arr = qstr.split(",") if qstr.is_a?(String)
48
56
  name, weight = arr
57
+ @weights[name] = weight.to_i
49
58
  [weight.to_i, 1].max.times do
50
59
  memo << name
51
60
  end
52
61
  end
62
+ @mode = if @weights.values.all?(&:zero?)
63
+ :strict
64
+ elsif @weights.values.all? { |x| x == 1 }
65
+ :random
66
+ else
67
+ :weighted
68
+ end
53
69
  end
54
70
 
55
71
  # Allow the middleware to be different per-capsule.
data/lib/sidekiq/cli.rb CHANGED
@@ -77,7 +77,8 @@ module Sidekiq # :nodoc:
77
77
  raise "You are connecting to Redis #{ver}, Sidekiq requires Redis 6.2.0 or greater" if ver < Gem::Version.new("6.2.0")
78
78
 
79
79
  maxmemory_policy = info["maxmemory_policy"]
80
- if maxmemory_policy != "noeviction"
80
+ if maxmemory_policy != "noeviction" && maxmemory_policy != ""
81
+ # Redis Enterprise Cloud returns "" for their policy 😳
81
82
  logger.warn <<~EOM
82
83
 
83
84
 
@@ -189,7 +189,7 @@ module Sidekiq
189
189
  def enqueue_to_in(queue, interval, klass, *args)
190
190
  int = interval.to_f
191
191
  now = Time.now.to_f
192
- ts = (int < 1_000_000_000 ? now + int : int)
192
+ ts = ((int < 1_000_000_000) ? now + int : int)
193
193
 
194
194
  item = {"class" => klass, "args" => args, "at" => ts, "queue" => queue}
195
195
  item.delete("at") if ts <= now
@@ -129,12 +129,12 @@ module Sidekiq
129
129
  def new_redis_pool(size, name = "unset")
130
130
  # connection pool is lazy, it will not create connections unless you actually need them
131
131
  # so don't be skimpy!
132
- RedisConnection.create(@redis_config.merge(size: size, logger: logger, pool_name: name))
132
+ RedisConnection.create({size: size, logger: logger, pool_name: name}.merge(@redis_config))
133
133
  end
134
134
 
135
135
  def redis_info
136
136
  redis do |conn|
137
- conn.info
137
+ conn.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
138
138
  rescue RedisClientAdapter::CommandError => ex
139
139
  # 2850 return fake version when INFO command has (probably) been renamed
140
140
  raise unless /unknown command/.match?(ex.message)
@@ -248,7 +248,6 @@ module Sidekiq
248
248
  return
249
249
  end
250
250
 
251
- logger.extend(Sidekiq::LoggingUtils)
252
251
  @logger = logger
253
252
  end
254
253
 
@@ -21,15 +21,15 @@ module Sidekiq
21
21
  }
22
22
 
23
23
  def self.mark!(label = nil)
24
- label ||= LABEL_MAKER.call
25
- Sidekiq::Deploy.new.mark(label: label)
24
+ Sidekiq::Deploy.new.mark!(label: label)
26
25
  end
27
26
 
28
27
  def initialize(pool = Sidekiq::RedisConnection.create)
29
28
  @pool = pool
30
29
  end
31
30
 
32
- def mark(at: Time.now, label: "")
31
+ def mark!(at: Time.now, label: nil)
32
+ label ||= LABEL_MAKER.call
33
33
  # we need to round the timestamp so that we gracefully
34
34
  # handle an very common error in marking deploys:
35
35
  # having every process mark its deploy, leading
data/lib/sidekiq/fetch.rb CHANGED
@@ -30,11 +30,9 @@ module Sidekiq # :nodoc:
30
30
  def initialize(cap)
31
31
  raise ArgumentError, "missing queue list" unless cap.queues
32
32
  @config = cap
33
- @strictly_ordered_queues = (config.queues.size == config.queues.uniq.size)
33
+ @strictly_ordered_queues = cap.mode == :strict
34
34
  @queues = config.queues.map { |q| "queue:#{q}" }
35
- if @strictly_ordered_queues
36
- @queues.uniq!
37
- end
35
+ @queues.uniq! if @strictly_ordered_queues
38
36
  end
39
37
 
40
38
  def retrieve_work
data/lib/sidekiq/job.rb CHANGED
@@ -258,7 +258,7 @@ module Sidekiq
258
258
  def at(interval)
259
259
  int = interval.to_f
260
260
  now = Time.now.to_f
261
- ts = (int < 1_000_000_000 ? now + int : int)
261
+ ts = ((int < 1_000_000_000) ? now + int : int)
262
262
  # Optimization to enqueue something now that is scheduled to go out now or in the past
263
263
  @opts["at"] = ts if ts > now
264
264
  self
@@ -325,7 +325,7 @@ module Sidekiq
325
325
  def perform_in(interval, *args)
326
326
  int = interval.to_f
327
327
  now = Time.now.to_f
328
- ts = (int < 1_000_000_000 ? now + int : int)
328
+ ts = ((int < 1_000_000_000) ? now + int : int)
329
329
 
330
330
  item = {"class" => self, "args" => args}
331
331
 
@@ -33,7 +33,7 @@ module Sidekiq
33
33
 
34
34
  Thread.current[:sidekiq_context] = h
35
35
  level = job_hash["log_level"]
36
- if level
36
+ if level && @logger.respond_to?(:log_at)
37
37
  @logger.log_at(level, &block)
38
38
  else
39
39
  yield
@@ -49,7 +49,7 @@ module Sidekiq
49
49
  # The default number of retries is 25 which works out to about 3 weeks
50
50
  # You can change the default maximum number of retries in your initializer:
51
51
  #
52
- # Sidekiq.options[:max_retries] = 7
52
+ # Sidekiq.default_configuration[:max_retries] = 7
53
53
  #
54
54
  # or limit the number of retries for a particular job and send retries to
55
55
  # a low priority queue with:
@@ -161,7 +161,7 @@ module Sidekiq
161
161
  fails = procd = 0
162
162
  kb = memory_usage(::Process.pid)
163
163
 
164
- _, exists, _, _, msg = redis { |conn|
164
+ _, exists, _, _, signal = redis { |conn|
165
165
  conn.multi { |transaction|
166
166
  transaction.sadd("processes", [key])
167
167
  transaction.exists(key)
@@ -180,9 +180,7 @@ module Sidekiq
180
180
  fire_event(:heartbeat) unless exists > 0
181
181
  fire_event(:beat, oneshot: false)
182
182
 
183
- return unless msg
184
-
185
- ::Process.kill(msg, ::Process.pid)
183
+ ::Process.kill(signal, ::Process.pid) if signal && !@embedded
186
184
  rescue => e
187
185
  # ignore all redis/network issues
188
186
  logger.error("heartbeat: #{e}")
@@ -250,12 +248,19 @@ module Sidekiq
250
248
  "pid" => ::Process.pid,
251
249
  "tag" => @config[:tag] || "",
252
250
  "concurrency" => @config.total_concurrency,
253
- "queues" => @config.capsules.values.map { |cap| cap.queues }.flatten.uniq,
251
+ "queues" => @config.capsules.values.flat_map { |cap| cap.queues }.uniq,
252
+ "weights" => to_weights,
254
253
  "labels" => @config[:labels].to_a,
255
- "identity" => identity
254
+ "identity" => identity,
255
+ "version" => Sidekiq::VERSION,
256
+ "embedded" => @embedded
256
257
  }
257
258
  end
258
259
 
260
+ def to_weights
261
+ @config.capsules.values.map(&:weights)
262
+ end
263
+
259
264
  def to_json
260
265
  # this data changes infrequently so dump it to a string
261
266
  # now so we don't need to dump it every heartbeat.
@@ -123,7 +123,7 @@ module Sidekiq
123
123
  def series_avg(metric = "ms")
124
124
  series[metric].each_with_object(Hash.new(0)) do |(bucket, value), result|
125
125
  completed = series.dig("p", bucket) - series.dig("f", bucket)
126
- result[bucket] = completed == 0 ? 0 : value.to_f / completed
126
+ result[bucket] = (completed == 0) ? 0 : value.to_f / completed
127
127
  end
128
128
  end
129
129
  end
@@ -22,13 +22,11 @@ module Sidekiq
22
22
  end
23
23
 
24
24
  def call(_, job, _, _)
25
- attrs = @strklass.constantize.attributes
26
- if attrs.any?
27
- if job.has_key?("cattr")
28
- job["cattr"].merge!(attrs)
29
- else
30
- job["cattr"] = attrs
31
- end
25
+ if !job.has_key?("cattr")
26
+ attrs = @strklass.constantize.attributes
27
+ # Retries can push the job N times, we don't
28
+ # want retries to reset cattr. #5692, #5090
29
+ job["cattr"] = attrs if attrs.any?
32
30
  end
33
31
  yield
34
32
  end
@@ -49,10 +49,25 @@ class Sidekiq::Monitor
49
49
  def processes
50
50
  puts "---- Processes (#{process_set.size}) ----"
51
51
  process_set.each_with_index do |process, index|
52
+ # Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
53
+ #
54
+ # Before:
55
+ # ["default", "critical"]
56
+ #
57
+ # After:
58
+ # {"default" => 1, "critical" => 10}
59
+ queues =
60
+ if process["weights"]
61
+ process["weights"].sort_by { |queue| queue[0] }.map { |queue| queue.join(": ") }
62
+ else
63
+ process["queues"].sort
64
+ end
65
+
52
66
  puts "#{process["identity"]} #{tags_for(process)}"
53
67
  puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
54
68
  puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
55
- puts " Queues: #{split_multiline(process["queues"].sort, pad: 11)}"
69
+ puts " Queues: #{split_multiline(queues, pad: 11)}"
70
+ puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
56
71
  puts "" unless (index + 1) == process_set.size
57
72
  end
58
73
  end
@@ -101,7 +116,7 @@ class Sidekiq::Monitor
101
116
  tags = [
102
117
  process["tag"],
103
118
  process["labels"],
104
- (process["quiet"] == "true" ? "quiet" : nil)
119
+ ((process["quiet"] == "true") ? "quiet" : nil)
105
120
  ].flatten.compact
106
121
  tags.any? ? "[#{tags.join("] [")}]" : nil
107
122
  end
@@ -3,7 +3,7 @@
3
3
  module Sidekiq
4
4
  module Paginator
5
5
  def page(key, pageidx = 1, page_size = 25, opts = nil)
6
- current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
6
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
7
7
  pageidx = current_page - 1
8
8
  total_size = 0
9
9
  items = []
@@ -45,7 +45,7 @@ module Sidekiq
45
45
  end
46
46
 
47
47
  def page_items(items, pageidx = 1, page_size = 25)
48
- current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
48
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
49
49
  pageidx = current_page - 1
50
50
  starting = pageidx * page_size
51
51
  items = items.to_a
data/lib/sidekiq/rails.rb CHANGED
@@ -11,7 +11,8 @@ module Sidekiq
11
11
  end
12
12
 
13
13
  def call
14
- @app.reloader.wrap do
14
+ params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
15
+ @app.reloader.wrap(**params) do
15
16
  yield
16
17
  end
17
18
  end
@@ -9,10 +9,12 @@ module Sidekiq
9
9
  CommandError = RedisClient::CommandError
10
10
 
11
11
  module CompatMethods
12
+ # TODO Deprecate and remove this
12
13
  def info
13
14
  @client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h }
14
15
  end
15
16
 
17
+ # TODO Deprecate and remove this
16
18
  def evalsha(sha, keys, argv)
17
19
  @client.call("EVALSHA", sha, keys.size, *keys, *argv)
18
20
  end
@@ -34,12 +36,7 @@ module Sidekiq
34
36
  CompatClient = RedisClient::Decorator.create(CompatMethods)
35
37
 
36
38
  class CompatClient
37
- # underscore methods are not official API
38
- def _client
39
- @client
40
- end
41
-
42
- def _config
39
+ def config
43
40
  @client.config
44
41
  end
45
42
 
@@ -54,7 +54,7 @@ module Sidekiq
54
54
  @lua_zpopbyscore_sha = conn.script(:load, LUA_ZPOPBYSCORE)
55
55
  end
56
56
 
57
- conn.evalsha(@lua_zpopbyscore_sha, keys, argv)
57
+ conn.call("EVALSHA", @lua_zpopbyscore_sha, keys.size, *keys, *argv)
58
58
  rescue RedisClient::CommandError => e
59
59
  raise unless e.message.start_with?("NOSCRIPT")
60
60
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sidekiq
4
- VERSION = "7.0.1"
4
+ VERSION = "7.0.3"
5
5
  MAJOR = 7
6
6
  end
@@ -82,11 +82,14 @@ module Sidekiq
82
82
 
83
83
  post "/busy" do
84
84
  if params["identity"]
85
- p = Sidekiq::Process.new("identity" => params["identity"])
86
- p.quiet! if params["quiet"]
87
- p.stop! if params["stop"]
85
+ pro = Sidekiq::ProcessSet[params["identity"]]
86
+
87
+ pro.quiet! if params["quiet"]
88
+ pro.stop! if params["stop"]
88
89
  else
89
90
  processes.each do |pro|
91
+ next if pro.embedded?
92
+
90
93
  pro.quiet! if params["quiet"]
91
94
  pro.stop! if params["stop"]
92
95
  end
@@ -137,7 +137,7 @@ module Sidekiq
137
137
  end
138
138
 
139
139
  def sort_direction_label
140
- params[:direction] == "asc" ? "&uarr;" : "&darr;"
140
+ (params[:direction] == "asc") ? "&uarr;" : "&darr;"
141
141
  end
142
142
 
143
143
  def workset
@@ -161,13 +161,21 @@ module Sidekiq
161
161
  end
162
162
  end
163
163
 
164
+ def busy_weights(capsule_weights)
165
+ # backwards compat with 7.0.0, remove in 7.1
166
+ cw = [capsule_weights].flatten
167
+ cw.map { |hash|
168
+ hash.map { |name, weight| (weight > 0) ? +name << ": " << weight.to_s : name }.join(", ")
169
+ }.join("; ")
170
+ end
171
+
164
172
  def stats
165
173
  @stats ||= Sidekiq::Stats.new
166
174
  end
167
175
 
168
176
  def redis_url
169
177
  Sidekiq.redis do |conn|
170
- conn._config.server_url
178
+ conn.config.server_url
171
179
  end
172
180
  end
173
181
 
@@ -184,7 +192,7 @@ module Sidekiq
184
192
  end
185
193
 
186
194
  def current_status
187
- workset.size == 0 ? "idle" : "active"
195
+ (workset.size == 0) ? "idle" : "active"
188
196
  end
189
197
 
190
198
  def relative_time(time)
@@ -217,7 +225,7 @@ module Sidekiq
217
225
  end
218
226
 
219
227
  def truncate(text, truncate_after_chars = 2000)
220
- truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text
228
+ (truncate_after_chars && text.size > truncate_after_chars) ? "#{text[0..truncate_after_chars]}..." : text
221
229
  end
222
230
 
223
231
  def display_args(args, truncate_after_chars = 2000)
@@ -324,7 +332,7 @@ module Sidekiq
324
332
  end
325
333
 
326
334
  def pollable?
327
- !(current_path == "" || current_path.starts_with?("metrics"))
335
+ !(current_path == "" || current_path.start_with?("metrics"))
328
336
  end
329
337
 
330
338
  def retry_or_delete_or_kill(job, params)
data/lib/sidekiq/web.rb CHANGED
@@ -48,6 +48,10 @@ module Sidekiq
48
48
  end
49
49
  alias_method :tabs, :custom_tabs
50
50
 
51
+ def custom_job_info_rows
52
+ @custom_job_info_rows ||= []
53
+ end
54
+
51
55
  def locales
52
56
  @locales ||= LOCALES
53
57
  end
data/sidekiq.gemspec CHANGED
@@ -22,29 +22,17 @@ Gem::Specification.new do |gem|
22
22
  "source_code_uri" => "https://github.com/mperham/sidekiq"
23
23
  }
24
24
 
25
- gem.add_dependency "redis-client", ">= 0.9.0"
25
+ gem.add_dependency "redis-client", ">= 0.11.0"
26
26
  gem.add_dependency "connection_pool", ">= 2.3.0"
27
27
  gem.add_dependency "rack", ">= 2.2.4"
28
28
  gem.add_dependency "concurrent-ruby", "< 2"
29
29
  gem.post_install_message = <<~EOM
30
30
 
31
- ####################################################
32
-
33
-
34
- █████████ █████ ██████████ ██████████ █████ ████ █████ ██████ ██████████ █████
35
- ███░░░░░███░░███ ░░███░░░░███ ░░███░░░░░█░░███ ███░ ░░███ ███░░░░███ ░███░░░░███ ███░░░███
36
- ░███ ░░░ ░███ ░███ ░░███ ░███ █ ░ ░███ ███ ░███ ███ ░░███ ░░░ ███ ███ ░░███
37
- ░░█████████ ░███ ░███ ░███ ░██████ ░███████ ░███ ░███ ░███ ███ ░███ ░███
38
- ░░░░░░░░███ ░███ ░███ ░███ ░███░░█ ░███░░███ ░███ ░███ ██░███ ███ ░███ ░███
39
- ███ ░███ ░███ ░███ ███ ░███ ░ █ ░███ ░░███ ░███ ░░███ ░░████ ███ ░░███ ███
40
- ░░█████████ █████ ██████████ ██████████ █████ ░░████ █████ ░░░██████░██ ███ ██ ░░░█████░
41
- ░░░░░░░░░ ░░░░░ ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░░ ░░ ░░░ ░░ ░░░░░░
42
-
31
+ Welcome to Sidekiq 7.0!
43
32
 
44
33
  1. Use `gem 'sidekiq', '<7'` in your Gemfile if you don't want this new version.
45
34
  2. Read the release notes at https://github.com/mperham/sidekiq/blob/main/docs/7.0-Upgrade.md
46
- 3. Search for open/closed issues at https://github.com/mperham/sidekiq/issues/
47
-
48
- ####################################################
35
+ 3. If you have problems, search for open/closed issues at https://github.com/mperham/sidekiq/issues/
36
+
49
37
  EOM
50
38
  end
@@ -31,6 +31,7 @@ function addListeners() {
31
31
  node.addEventListener("click", addDataToggleListeners)
32
32
  })
33
33
 
34
+ addShiftClickListeners()
34
35
  updateFuzzyTimes();
35
36
  setLivePollFromUrl();
36
37
 
@@ -71,6 +72,23 @@ function addDataToggleListeners(event) {
71
72
  }
72
73
  }
73
74
 
75
+ function addShiftClickListeners() {
76
+ let checkboxes = Array.from(document.querySelectorAll(".shift_clickable"));
77
+ let lastChecked = null;
78
+ checkboxes.forEach(checkbox => {
79
+ checkbox.addEventListener("click", (e) => {
80
+ if (e.shiftKey && lastChecked) {
81
+ let myIndex = checkboxes.indexOf(checkbox);
82
+ let lastIndex = checkboxes.indexOf(lastChecked);
83
+ let [min, max] = [myIndex, lastIndex].sort();
84
+ let newState = checkbox.checked;
85
+ checkboxes.slice(min, max).forEach(c => c.checked = newState);
86
+ }
87
+ lastChecked = checkbox;
88
+ });
89
+ });
90
+ }
91
+
74
92
  function updateFuzzyTimes() {
75
93
  var locale = document.body.getAttribute("data-locale");
76
94
  var parts = locale.split('-');
@@ -72,6 +72,10 @@ table {
72
72
  background-color: #31708f;
73
73
  }
74
74
 
75
+ .alert-warning {
76
+ background-color: #c47612;
77
+ }
78
+
75
79
  a:link, a:active, a:hover, a:visited {
76
80
  color: #ddd;
77
81
  }
@@ -676,8 +676,8 @@ div.interval-slider input {
676
676
  }
677
677
 
678
678
  .info-circle {
679
- color: #ccc;
680
- background-color: #000;
679
+ color: #333;
680
+ background-color: #ccc;
681
681
  border-radius: 50%;
682
682
  text-align: center;
683
683
  vertical-align: middle;
@@ -1,6 +1,6 @@
1
- <header>
1
+ <div class="header-container">
2
2
  <h3><%= t('Job') %></h3>
3
- </header>
3
+ </div>
4
4
 
5
5
  <div class="table_container">
6
6
  <table class="table table-bordered table-striped table-hover">
@@ -33,6 +33,14 @@
33
33
  <code><%= job.jid %></code>
34
34
  </td>
35
35
  </tr>
36
+ <% if job.bid %>
37
+ <tr>
38
+ <th>BID</th>
39
+ <td>
40
+ <a href="<%= root_path %>batches/<%= job.bid %>"><%= job.bid %>
41
+ </td>
42
+ </tr>
43
+ <% end %>
36
44
  <tr>
37
45
  <th><%= t('CreatedAt') %></th>
38
46
  <td><%= relative_time(job.created_at) %></td>
@@ -84,6 +92,14 @@
84
92
  <td><%= relative_time(job.at) if job['retry_count'] %></td>
85
93
  </tr>
86
94
  <% end %>
95
+ <% Sidekiq::Web.custom_job_info_rows.each do |helper| %>
96
+ <% helper.add_pair(job) do |name, value| %>
97
+ <tr>
98
+ <th><%= name %></th>
99
+ <td><%= value %></td>
100
+ </tr>
101
+ <% end %>
102
+ <% end %>
87
103
  </tbody>
88
104
  </table>
89
105
  </div>
@@ -1,3 +1,4 @@
1
+ <div>
1
2
  <% if @total_size > @count %>
2
3
  <ul class="pagination pull-right flip">
3
4
  <li class="<%= 'disabled' if @current_page == 1 %>">
@@ -21,3 +22,4 @@
21
22
  </li>
22
23
  </ul>
23
24
  <% end %>
25
+ </div>
data/web/views/busy.erb CHANGED
@@ -1,7 +1,5 @@
1
- <div class="row header">
2
- <div class="col-sm-4 pull-left flip">
3
- <h3><%= t('Status') %></h3>
4
- </div>
1
+ <div class="header-container">
2
+ <h1><%= t('Status') %></h1>
5
3
  </div>
6
4
 
7
5
  <div class="stats-wrapper">
@@ -29,11 +27,9 @@
29
27
  </div>
30
28
  </div>
31
29
 
32
- <div class="row header">
33
- <div class="col-sm-4 pull-left flip">
34
- <h3><%= t('Processes') %></h3>
35
- </div>
36
- <div class="col-sm-3 pull-right flip">
30
+ <div class="header-container">
31
+ <h1><%= t('Processes') %></h1>
32
+ <div>
37
33
  <form method="POST" class="warning-messages">
38
34
  <%= csrf_tag %>
39
35
  <div class="btn-group pull-right flip">
@@ -43,6 +39,7 @@
43
39
  </form>
44
40
  </div>
45
41
  </div>
42
+
46
43
  <div class="table_container">
47
44
  <table class="processes table table-hover table-bordered table-striped">
48
45
  <thead>
@@ -62,6 +59,9 @@
62
59
  <% process.labels.each do |label| %>
63
60
  <span class="label label-info"><%= label %></span>
64
61
  <% end %>
62
+ <% if process.embedded? %>
63
+ <span class="label label-default">embedded</span>
64
+ <% end %>
65
65
  <% if process.stopping? %>
66
66
  <span class="label label-danger">quiet</span>
67
67
  <% end %>
@@ -70,36 +70,47 @@
70
70
  <% end %>
71
71
  <br>
72
72
  <b><%= "#{t('Queues')}: " %></b>
73
- <%= process.queues.join(", ") %>
73
+ <% if process.weights %>
74
+ <%= busy_weights(process.weights) %>
75
+ <% else %>
76
+ <%= process.queues.sort.join(", ") %>
77
+ <% end %>
78
+ <% if process.version != Sidekiq::VERSION %>
79
+ <br>
80
+ <b><%= "#{t('Version')}: " %></b>
81
+ <% if process.version %>
82
+ <%= process.version %>
83
+ <% else %>
84
+ <%= t('Unknown') %>
85
+ <% end %>
86
+ <% end %>
74
87
  </td>
75
88
  <td><%= relative_time(Time.at(process['started_at'])) %></td>
76
89
  <td><%= format_memory(process['rss'].to_i) %></td>
77
90
  <td><%= process['concurrency'] %></td>
78
91
  <td><%= process['busy'] %></td>
79
92
  <td>
80
- <form method="POST">
81
- <%= csrf_tag %>
82
- <input type="hidden" name="identity" value="<%= process['identity'] %>"/>
93
+ <% unless process.embedded? %>
94
+ <form method="POST">
95
+ <%= csrf_tag %>
96
+ <input type="hidden" name="identity" value="<%= process['identity'] %>"/>
83
97
 
84
- <div class="btn-group pull-right flip">
85
- <% unless process.stopping? %><button class="btn btn-xs btn-warn" type="submit" name="quiet" value="1"><%= t('Quiet') %></button><% end %>
86
- <button class="btn btn-xs btn-danger" type="submit" name="stop" value="1"><%= t('Stop') %></button>
87
- </div>
88
- </form>
98
+ <div class="btn-group pull-right flip">
99
+ <% unless process.stopping? %><button class="btn btn-xs btn-warn" type="submit" name="quiet" value="1"><%= t('Quiet') %></button><% end %>
100
+ <button class="btn btn-xs btn-danger" type="submit" name="stop" value="1"><%= t('Stop') %></button>
101
+ </div>
102
+ </form>
103
+ <% end %>
89
104
  </td>
90
105
  </tr>
91
106
  <% end %>
92
107
  </table>
93
108
  </div>
94
109
 
95
- <div class="row header">
96
- <div class="col-sm-7">
97
- <h3><%= t('Jobs') %></h3>
98
- </div>
110
+ <div class="header-container">
111
+ <h1><%= t('Jobs') %></h1>
99
112
  <% if @workset.size > 0 && @total_size > @count %>
100
- <div class="col-sm-4">
101
- <%= erb :_paging, locals: { url: "#{root_path}busy" } %>
102
- </div>
113
+ <%= erb :_paging, locals: { url: "#{root_path}busy" } %>
103
114
  <% end %>
104
115
  </div>
105
116
 
@@ -16,7 +16,7 @@
16
16
  <% if job_result.totals["s"] > 0 %>
17
17
  <div class="header-container">
18
18
  <h1>
19
- <a href="<%= root_path %>metrics"><%= t(:metrics).to_s.titleize %></a> /
19
+ <a href="<%= root_path %>metrics"><%= t('Metrics') %></a> /
20
20
  <%= h @name %>
21
21
  </h1>
22
22
 
@@ -61,7 +61,7 @@
61
61
  <p><small>Data from <%= @query_result.starts_at %> to <%= @query_result.ends_at %></small></p>
62
62
  <% else %>
63
63
  <h1>
64
- <a href="<%= root_path %>/metrics"><%= t(:metrics).to_s.titleize %></a> /
64
+ <a href="<%= root_path %>/metrics"><%= t('Metrics') %></a> /
65
65
  <%= h @name %>
66
66
  </h1>
67
67
 
data/web/views/morgue.erb CHANGED
@@ -1,14 +1,10 @@
1
- <header class="row">
2
- <div class="col-sm-5">
3
- <h3><%= t('DeadJobs') %></h3>
4
- </div>
1
+ <div class="header-container">
2
+ <h1><%= t('DeadJobs') %></h1>
5
3
  <% if @dead.size > 0 && @total_size > @count %>
6
- <div class="col-sm-4">
7
- <%= erb :_paging, locals: { url: "#{root_path}morgue" } %>
8
- </div>
4
+ <%= erb :_paging, locals: { url: "#{root_path}morgue" } %>
9
5
  <% end %>
10
6
  <%= filtering('dead') %>
11
- </header>
7
+ </div>
12
8
 
13
9
  <% if @dead.size > 0 %>
14
10
  <form action="<%= root_path %>morgue" method="post">
@@ -33,7 +29,7 @@
33
29
  <tr>
34
30
  <td class="table-checkbox">
35
31
  <label>
36
- <input type='checkbox' name='key[]' value='<%= job_params(entry.item, entry.score) %>' />
32
+ <input type='checkbox' name='key[]' value='<%= job_params(entry.item, entry.score) %>' class='shift_clickable' />
37
33
  </label>
38
34
  </td>
39
35
  <td>
data/web/views/queue.erb CHANGED
@@ -1,17 +1,13 @@
1
- <header class="row">
2
- <div class="col-sm-5">
3
- <h3>
4
- <%= t('CurrentMessagesInQueue', :queue => h(@name)) %>
5
- <% if @queue.paused? %>
6
- <span class="label label-danger"><%= t('Paused') %></span>
7
- <% end %>
8
- <span class="badge badge-secondary"><%= number_with_delimiter(@total_size) %></span>
9
- </h3>
10
- </div>
11
- <div class="col-sm-4 pull-right flip">
12
- <%= erb :_paging, locals: { url: "#{root_path}queues/#{CGI.escape(@name)}" } %>
13
- </div>
14
- </header>
1
+ <div class="header-container">
2
+ <h1><%= t('CurrentMessagesInQueue', :queue => h(@name)) %>
3
+ <% if @queue.paused? %>
4
+ <span class="label label-danger"><%= t('Paused') %></span>
5
+ <% end %>
6
+ <span class="badge badge-secondary"><%= number_with_delimiter(@total_size) %></span>
7
+ </h1>
8
+ <%= erb :_paging, locals: { url: "#{root_path}queues/#{CGI.escape(@name)}" } %>
9
+ </div>
10
+
15
11
  <div class="table_container">
16
12
  <table class="queue table table-hover table-bordered table-striped">
17
13
  <thead>
data/web/views/queues.erb CHANGED
@@ -1,4 +1,6 @@
1
- <h3><%= t('Queues') %></h3>
1
+ <div class="header-container">
2
+ <h1><%= t('Queues') %></h1>
3
+ </div>
2
4
 
3
5
  <div class="table_container">
4
6
  <table class="queues table table-hover table-bordered table-striped">
@@ -1,14 +1,10 @@
1
- <header class="row">
2
- <div class="col-sm-5">
3
- <h3><%= t('Retries') %></h3>
4
- </div>
1
+ <div class="header-container">
2
+ <h1><%= t('Retries') %></h1>
5
3
  <% if @retries.size > 0 && @total_size > @count %>
6
- <div class="col-sm-4">
7
- <%= erb :_paging, locals: { url: "#{root_path}retries" } %>
8
- </div>
4
+ <%= erb :_paging, locals: { url: "#{root_path}retries" } %>
9
5
  <% end %>
10
6
  <%= filtering('retries') %>
11
- </header>
7
+ </div>
12
8
 
13
9
  <% if @retries.size > 0 %>
14
10
  <form action="<%= root_path %>retries" method="post">
@@ -34,7 +30,7 @@
34
30
  <tr>
35
31
  <td class="table-checkbox">
36
32
  <label>
37
- <input type='checkbox' name='key[]' value='<%= job_params(entry.item, entry.score) %>' />
33
+ <input type='checkbox' name='key[]' value='<%= job_params(entry.item, entry.score) %>' class='shift_clickable' />
38
34
  </label>
39
35
  </td>
40
36
  <td>
@@ -1,25 +1,22 @@
1
- <header class="row">
2
- <div class="col-sm-5">
3
- <h3><%= t('ScheduledJobs') %></h3>
4
- </div>
1
+ <div class="header-container">
2
+ <h1><%= t('ScheduledJobs') %></h1>
5
3
  <% if @scheduled.size > 0 && @total_size > @count %>
6
- <div class="col-sm-4">
7
- <%= erb :_paging, locals: { url: "#{root_path}scheduled" } %>
8
- </div>
4
+ <%= erb :_paging, locals: { url: "#{root_path}scheduled" } %>
9
5
  <% end %>
10
6
  <%= filtering('scheduled') %>
11
- </header>
7
+ </div>
12
8
 
13
9
  <% if @scheduled.size > 0 %>
14
-
15
10
  <form action="<%= root_path %>scheduled" method="post">
16
11
  <%= csrf_tag %>
17
12
  <div class="table_container">
18
13
  <table class="table table-striped table-bordered table-hover">
19
14
  <thead>
20
15
  <tr>
21
- <th class="checkbox-column">
22
- <input type="checkbox" class="check_all" />
16
+ <th class="table-checkbox checkbox-column">
17
+ <label>
18
+ <input type="checkbox" class="check_all" />
19
+ </label>
23
20
  </th>
24
21
  <th><%= t('When') %></th>
25
22
  <th><%= t('Queue') %></th>
@@ -29,8 +26,10 @@
29
26
  </thead>
30
27
  <% @scheduled.each do |entry| %>
31
28
  <tr>
32
- <td>
33
- <input type='checkbox' name='key[]' value='<%= job_params(entry.item, entry.score) %>' />
29
+ <td class="table-checkbox">
30
+ <label>
31
+ <input type='checkbox' name='key[]' value='<%= job_params(entry.item, entry.score) %>' class='shift_clickable' />
32
+ </label>
34
33
  </td>
35
34
  <td>
36
35
  <a href="<%= root_path %>scheduled/<%= job_params(entry.item, entry.score) %>"><%= relative_time(entry.at) %></a>
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: 7.0.1
4
+ version: 7.0.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: 2022-11-04 00:00:00.000000000 Z
11
+ date: 2023-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis-client
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 0.9.0
19
+ version: 0.11.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 0.9.0
26
+ version: 0.11.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: connection_pool
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -204,26 +204,14 @@ metadata:
204
204
  documentation_uri: https://github.com/mperham/sidekiq/wiki
205
205
  changelog_uri: https://github.com/mperham/sidekiq/blob/main/Changes.md
206
206
  source_code_uri: https://github.com/mperham/sidekiq
207
- post_install_message: |2
208
-
209
- ####################################################
210
-
211
-
212
- █████████ █████ ██████████ ██████████ █████ ████ █████ ██████ ██████████ █████
213
- ███░░░░░███░░███ ░░███░░░░███ ░░███░░░░░█░░███ ███░ ░░███ ███░░░░███ ░███░░░░███ ███░░░███
214
- ░███ ░░░ ░███ ░███ ░░███ ░███ █ ░ ░███ ███ ░███ ███ ░░███ ░░░ ███ ███ ░░███
215
- ░░█████████ ░███ ░███ ░███ ░██████ ░███████ ░███ ░███ ░███ ███ ░███ ░███
216
- ░░░░░░░░███ ░███ ░███ ░███ ░███░░█ ░███░░███ ░███ ░███ ██░███ ███ ░███ ░███
217
- ███ ░███ ░███ ░███ ███ ░███ ░ █ ░███ ░░███ ░███ ░░███ ░░████ ███ ░░███ ███
218
- ░░█████████ █████ ██████████ ██████████ █████ ░░████ █████ ░░░██████░██ ███ ██ ░░░█████░
219
- ░░░░░░░░░ ░░░░░ ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░░ ░░ ░░░ ░░ ░░░░░░
207
+ post_install_message: |2+
220
208
 
209
+ Welcome to Sidekiq 7.0!
221
210
 
222
211
  1. Use `gem 'sidekiq', '<7'` in your Gemfile if you don't want this new version.
223
212
  2. Read the release notes at https://github.com/mperham/sidekiq/blob/main/docs/7.0-Upgrade.md
224
- 3. Search for open/closed issues at https://github.com/mperham/sidekiq/issues/
213
+ 3. If you have problems, search for open/closed issues at https://github.com/mperham/sidekiq/issues/
225
214
 
226
- ####################################################
227
215
  rdoc_options: []
228
216
  require_paths:
229
217
  - lib
@@ -243,3 +231,4 @@ signing_key:
243
231
  specification_version: 4
244
232
  summary: Simple, efficient background processing for Ruby
245
233
  test_files: []
234
+ ...