sidekiq 7.3.7 → 8.0.0.beta1

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +30 -0
  3. data/README.md +16 -13
  4. data/bin/sidekiqload +10 -10
  5. data/bin/webload +69 -0
  6. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +5 -18
  7. data/lib/sidekiq/api.rb +108 -36
  8. data/lib/sidekiq/capsule.rb +1 -1
  9. data/lib/sidekiq/cli.rb +15 -19
  10. data/lib/sidekiq/client.rb +13 -16
  11. data/lib/sidekiq/component.rb +30 -2
  12. data/lib/sidekiq/config.rb +18 -15
  13. data/lib/sidekiq/embedded.rb +1 -0
  14. data/lib/sidekiq/job/iterable.rb +3 -5
  15. data/lib/sidekiq/job_retry.rb +2 -2
  16. data/lib/sidekiq/job_util.rb +5 -1
  17. data/lib/sidekiq/launcher.rb +1 -1
  18. data/lib/sidekiq/logger.rb +6 -10
  19. data/lib/sidekiq/manager.rb +0 -1
  20. data/lib/sidekiq/metrics/query.rb +1 -3
  21. data/lib/sidekiq/middleware/current_attributes.rb +5 -17
  22. data/lib/sidekiq/paginator.rb +14 -1
  23. data/lib/sidekiq/processor.rb +21 -14
  24. data/lib/sidekiq/profiler.rb +59 -0
  25. data/lib/sidekiq/rails.rb +12 -2
  26. data/lib/sidekiq/redis_client_adapter.rb +0 -1
  27. data/lib/sidekiq/testing.rb +2 -2
  28. data/lib/sidekiq/version.rb +2 -2
  29. data/lib/sidekiq/web/action.rb +101 -85
  30. data/lib/sidekiq/web/application.rb +339 -333
  31. data/lib/sidekiq/web/config.rb +116 -0
  32. data/lib/sidekiq/web/helpers.rb +45 -20
  33. data/lib/sidekiq/web/router.rb +60 -76
  34. data/lib/sidekiq/web.rb +51 -156
  35. data/sidekiq.gemspec +6 -4
  36. data/web/assets/javascripts/application.js +6 -13
  37. data/web/assets/javascripts/base-charts.js +30 -18
  38. data/web/assets/javascripts/dashboard-charts.js +2 -0
  39. data/web/assets/javascripts/dashboard.js +6 -0
  40. data/web/assets/javascripts/metrics.js +1 -1
  41. data/web/assets/stylesheets/style.css +750 -0
  42. data/web/locales/ar.yml +1 -0
  43. data/web/locales/cs.yml +1 -0
  44. data/web/locales/da.yml +1 -0
  45. data/web/locales/de.yml +1 -0
  46. data/web/locales/el.yml +1 -0
  47. data/web/locales/en.yml +9 -0
  48. data/web/locales/es.yml +24 -2
  49. data/web/locales/fa.yml +1 -0
  50. data/web/locales/fr.yml +1 -0
  51. data/web/locales/gd.yml +1 -0
  52. data/web/locales/he.yml +1 -0
  53. data/web/locales/hi.yml +1 -0
  54. data/web/locales/it.yml +1 -0
  55. data/web/locales/ja.yml +1 -0
  56. data/web/locales/ko.yml +1 -0
  57. data/web/locales/lt.yml +1 -0
  58. data/web/locales/nb.yml +1 -0
  59. data/web/locales/nl.yml +1 -0
  60. data/web/locales/pl.yml +1 -0
  61. data/web/locales/{pt-br.yml → pt-BR.yml} +2 -1
  62. data/web/locales/pt.yml +1 -0
  63. data/web/locales/ru.yml +1 -0
  64. data/web/locales/sv.yml +1 -0
  65. data/web/locales/ta.yml +1 -0
  66. data/web/locales/tr.yml +1 -0
  67. data/web/locales/uk.yml +1 -0
  68. data/web/locales/ur.yml +1 -0
  69. data/web/locales/vi.yml +1 -0
  70. data/web/locales/{zh-cn.yml → zh-CN.yml} +85 -73
  71. data/web/locales/{zh-tw.yml → zh-TW.yml} +2 -1
  72. data/web/views/_footer.erb +31 -34
  73. data/web/views/_job_info.erb +91 -89
  74. data/web/views/_metrics_period_select.erb +1 -1
  75. data/web/views/_nav.erb +14 -21
  76. data/web/views/_paging.erb +23 -21
  77. data/web/views/_poll_link.erb +2 -2
  78. data/web/views/_summary.erb +16 -16
  79. data/web/views/busy.erb +124 -122
  80. data/web/views/dashboard.erb +62 -64
  81. data/web/views/dead.erb +31 -27
  82. data/web/views/filtering.erb +3 -3
  83. data/web/views/layout.erb +5 -21
  84. data/web/views/metrics.erb +83 -80
  85. data/web/views/metrics_for_job.erb +39 -42
  86. data/web/views/morgue.erb +61 -70
  87. data/web/views/profiles.erb +43 -0
  88. data/web/views/queue.erb +54 -52
  89. data/web/views/queues.erb +43 -41
  90. data/web/views/retries.erb +66 -75
  91. data/web/views/retry.erb +32 -27
  92. data/web/views/scheduled.erb +58 -54
  93. data/web/views/scheduled_job_info.erb +1 -1
  94. metadata +46 -22
  95. data/web/assets/stylesheets/application-dark.css +0 -147
  96. data/web/assets/stylesheets/application-rtl.css +0 -163
  97. data/web/assets/stylesheets/application.css +0 -759
  98. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  99. data/web/assets/stylesheets/bootstrap.css +0 -5
  100. data/web/views/_status.erb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ea55d72538bcb50922f70f95398326d17853da6a3c2948c9e2812284e8aba22
4
- data.tar.gz: d18ff9909dded3154993fde2413f33630990cdfa42134fd640f7f714f0fd03ac
3
+ metadata.gz: f7280923fe38a56b42bb33c639f9e0a365c1f585f928e8cdfd3ce06fe989fc7e
4
+ data.tar.gz: e8c6a01e393390f1a3ab3a48c04b3621efd5621c6b184240c3e8316602e24e9c
5
5
  SHA512:
6
- metadata.gz: 40d7243cfbce6285d5560c81c55fcae66eacf1251e130bc2b8f8bbf58feadbb1538935760f8dbd07279f9076a92c7f1b276e28daf1fa3593f984fcbc6b8622eb
7
- data.tar.gz: cbc6c05c11fb9ac4be45da59400e1bc31da98c14a8054318680c2498c34797199d32719eb1985a3061d61c54ce244bde5eaa3e9284b9484772766d2eb4b50cd0
6
+ metadata.gz: 076a8f70170dfdc6ec78430ca41b182ed986453bdfbcfe24b7dd5ef1d8f2321b560cb94201c2f2c3a30c2ce42a31e38ce14151f0ebbee90bc97a2463b427747a
7
+ data.tar.gz: 8cc4828931a73da67ae40edb14508b82624781b9732280ce9e8db615c8bed1039b89b8b50db54c35bbffd4a010f904598be2bb4aa037228bbd4b4b325b2b9421
data/Changes.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  [Sidekiq Changes](https://github.com/sidekiq/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/sidekiq/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/sidekiq/sidekiq/blob/main/Ent-Changes.md)
4
4
 
5
+ HEAD / main
6
+ ----------
7
+
8
+ - **WARNING** The underlying class name for Active Jobs has changed from `ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper` to `Sidekiq::ActiveJob::Wrapper`.
9
+ - **WARNING** The `created_at` and `enqueued_at` attributes are now stored as
10
+ integer milliseconds, rather than epoch floats. This is meant to avoid precision
11
+ issues with JSON and JavaScript's 53-bit Floats. Example:
12
+ `"created_at" => 1234567890.123456` -> `"created_at" => 1234567890123`.
13
+ - **NEW FEATURE** Job Profiling is now supported with [Vernier](https://vernier.prof)
14
+ which makes it really easy to performance tune your slow jobs.
15
+ The Web UI contains a new **Profiles** tab to view any collected profile data.
16
+ Please read the new [Profiling](https://github.com/sidekiq/sidekiq/wiki/Profiling) wiki page for details.
17
+ - CurrentAttribute support now uses `ActiveJob::Arguments` to serialize the context object, supporting Symbols and GlobalID.
18
+ The change should be backwards compatible. [#6510]
19
+ - Freshen up `Sidekiq::Web` to simplify the code and improve security [#6532]
20
+ The CSS has been rewritten from scratch to remove the Bootstrap framework.
21
+ - Default error logging has been modified to use Ruby's `Exception#detailed_message` and `#full_message` APIs.
22
+ - CI now runs against Redis, Dragonfly and Valkey.
23
+ - The Web UI's language picker now shows options in the native language
24
+ - Remove global variable usage within the codebase
25
+ - Adjust Sidekiq's default thread priority to -1 for a 50ms timeslice.
26
+ This can help avoid TimeoutErrors when Sidekiq is overloaded. [#6543]
27
+ - Support: Redis 7.2+, Ruby 3.2+, Rails 7.0+
28
+
29
+ 7.3.8
30
+ ----------
31
+
32
+ - Fix dead tag links [#6554]
33
+ - Massive Web UI performance improvement, some pages up to 15x faster [#6555]
34
+
5
35
  7.3.7
6
36
  ----------
7
37
 
data/README.md CHANGED
@@ -4,21 +4,23 @@ Sidekiq
4
4
  [![Gem Version](https://badge.fury.io/rb/sidekiq.svg)](https://rubygems.org/gems/sidekiq)
5
5
  ![Build](https://github.com/sidekiq/sidekiq/workflows/CI/badge.svg)
6
6
 
7
- Simple, efficient background processing for Ruby.
7
+ Simple, efficient background jobs for Ruby.
8
8
 
9
9
  Sidekiq uses threads to handle many jobs at the same time in the
10
- same process. It does not require Rails but will integrate tightly with
11
- Rails to make background processing dead simple.
10
+ same process. Sidekiq can be used by any Ruby application.
12
11
 
13
12
 
14
13
  Requirements
15
14
  -----------------
16
15
 
17
- - Redis: Redis 6.2+ or Dragonfly 1.13+
18
- - Ruby: MRI 2.7+ or JRuby 9.3+.
16
+ - Redis: Redis 7.2+, Valkey 7.2+ or Dragonfly 1.13+
17
+ - Ruby: MRI 3.2+ or JRuby 9.4+.
19
18
 
20
- Sidekiq 7.0 supports Rails 6.0+ but does not require it.
21
- As of 7.2, Sidekiq supports Dragonfly as an alternative to Redis for data storage.
19
+ Sidekiq 8.0 supports Rails and Active Job 7.0+.
20
+
21
+ Sidekiq supports [Valkey](https://valkey.io) and [Dragonfly](https://www.dragonflydb.io) as Redis alternatives.
22
+ Redis 7.2.4 is considered to be the canonical implementation.
23
+ Incompatibilities with that version are considered bugs.
22
24
 
23
25
  Installation
24
26
  -----------------
@@ -42,6 +44,7 @@ The benchmark in `bin/sidekiqload` creates 500,000 no-op jobs and drains them as
42
44
  This requires a lot of Redis network I/O and JSON parsing.
43
45
  This benchmark is IO-bound so we increase the concurrency to 25.
44
46
  If your application is sending lots of emails or performing other network-intensive work, you could see a similar benefit but be careful not to saturate the CPU.
47
+ Real world applications will rarely if ever need to use concurrency greater than 10.
45
48
 
46
49
  Version | Time to process 500k jobs | Throughput (jobs/sec) | Ruby | Concurrency | Job Type
47
50
  -----------------|------|---------|---------|------------------------|---
@@ -62,7 +65,7 @@ Want to Upgrade?
62
65
  Use `bundle up sidekiq` to upgrade Sidekiq and all its dependencies.
63
66
  Upgrade notes between each major version can be found in the `docs/` directory.
64
67
 
65
- I also sell Sidekiq Pro and Sidekiq Enterprise, extensions to Sidekiq which provide more
68
+ I also sell [Sidekiq Pro](https://billing.contribsys.com/spro/) and [Sidekiq Enterprise](https://billing.contribsys.com/sent/new.cgi), extensions to Sidekiq which provide more
66
69
  features, a commercial-friendly license and allow you to support high
67
70
  quality open source development all at the same time. Please see the
68
71
  [Sidekiq](https://sidekiq.org/) homepage for more detail.
@@ -71,7 +74,7 @@ quality open source development all at the same time. Please see the
71
74
  Problems?
72
75
  -----------------
73
76
 
74
- **Please do not directly email any Sidekiq committers with questions or problems.**
77
+ **Do not directly email any Sidekiq committers with questions or problems.**
75
78
  A community is best served when discussions are held in public.
76
79
 
77
80
  If you have a problem, please review the [FAQ](https://github.com/sidekiq/sidekiq/wiki/FAQ) and [Troubleshooting](https://github.com/sidekiq/sidekiq/wiki/Problems-and-Troubleshooting) wiki pages.
@@ -86,21 +89,21 @@ Useful resources:
86
89
  * Occasional announcements are made to the [@sidekiq](https://ruby.social/@sidekiq) Mastodon account.
87
90
  * The [Sidekiq tag](https://stackoverflow.com/questions/tagged/sidekiq) on Stack Overflow has lots of useful Q & A.
88
91
 
89
- Every Thursday morning is Sidekiq office hour: I video chat and answer questions.
92
+ Every Thursday morning is Sidekiq Office Hour: I video chat and answer questions.
90
93
  See the [Sidekiq support page](https://sidekiq.org/support.html) for details.
91
94
 
92
95
  Contributing
93
96
  -----------------
94
97
 
95
- Please see [the contributing guidelines](https://github.com/sidekiq/sidekiq/blob/main/.github/contributing.md).
98
+ See [the contributing guidelines](https://github.com/sidekiq/sidekiq/blob/main/.github/contributing.md).
96
99
 
97
100
  License
98
101
  -----------------
99
102
 
100
- Please see [LICENSE.txt](https://github.com/sidekiq/sidekiq/blob/main/LICENSE.txt) for licensing details.
103
+ See [LICENSE.txt](https://github.com/sidekiq/sidekiq/blob/main/LICENSE.txt) for licensing details.
101
104
  The license for Sidekiq Pro and Sidekiq Enterprise can be found in [COMM-LICENSE.txt](https://github.com/sidekiq/sidekiq/blob/main/COMM-LICENSE.txt).
102
105
 
103
106
  Author
104
107
  -----------------
105
108
 
106
- Mike Perham, [@getajobmike](https://ruby.social/@getajobmike) / [@sidekiq](https://ruby.social/@sidekiq), [https://www.mikeperham.com](https://www.mikeperham.com) / [https://www.contribsys.com](https://www.contribsys.com)
109
+ Mike Perham, [mastodon](https://ruby.social/@getajobmike), [https://www.mikeperham.com](https://www.mikeperham.com) / [https://www.contribsys.com](https://www.contribsys.com)
data/bin/sidekiqload CHANGED
@@ -28,7 +28,7 @@ require "ruby-prof" if ENV["PROFILE"]
28
28
  require "bundler/setup"
29
29
  Bundler.require(:default, :load_test)
30
30
 
31
- latency = Integer(ENV["LATENCY"] || 1)
31
+ latency = Integer(ENV["LATENCY"] || 0)
32
32
  if latency > 0
33
33
  # brew tap shopify/shopify
34
34
  # brew install toxiproxy
@@ -86,7 +86,7 @@ class Loader
86
86
  def initialize
87
87
  @iter = ENV["GC"] ? 10 : 500
88
88
  @count = Integer(ENV["COUNT"] || 1_000)
89
- @latency = Integer(ENV["LATENCY"] || 1)
89
+ @latency = Integer(ENV["LATENCY"] || 0)
90
90
  end
91
91
 
92
92
  def configure
@@ -137,7 +137,7 @@ class Loader
137
137
  end
138
138
 
139
139
  def setup
140
- Sidekiq.logger.error("Setup RSS: #{Process.rss}")
140
+ Sidekiq.logger.warn("Setup RSS: #{Process.rss}")
141
141
  Sidekiq.redis { |c| c.flushdb }
142
142
  start = Time.now
143
143
  if ENV["AJ"]
@@ -167,9 +167,9 @@ class Loader
167
167
  if total == 0
168
168
  ending = Time.now - @start
169
169
  size = @iter * @count
170
- Sidekiq.logger.error("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
171
- Sidekiq.logger.error("Ending RSS: #{Process.rss}")
172
- Sidekiq.logger.error("Now here's the latency for three jobs")
170
+ Sidekiq.logger.warn("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
171
+ Sidekiq.logger.warn("Ending RSS: #{Process.rss}")
172
+ Sidekiq.logger.warn("Now here's the latency for three jobs")
173
173
 
174
174
  if ENV["AJ"]
175
175
  LoadJob.perform_later("", 1, {}, Time.now.to_f)
@@ -191,7 +191,7 @@ class Loader
191
191
  end
192
192
 
193
193
  def with_latency(latency, &block)
194
- Sidekiq.logger.error "Simulating #{latency}ms of latency between Sidekiq and redis"
194
+ Sidekiq.logger.warn "Simulating #{latency}ms of latency between Sidekiq and redis"
195
195
  if latency > 0
196
196
  Toxiproxy[:redis].downstream(:latency, latency: latency).apply(&block)
197
197
  else
@@ -204,8 +204,8 @@ class Loader
204
204
  monitor
205
205
 
206
206
  if ENV["PROFILE"]
207
- RubyProf.exclude_threads = [@monitor]
208
- RubyProf.start
207
+ $profile = RubyProf::Profile.new(exclude_threads: [@monitor])
208
+ $profile.start
209
209
  elsif ENV["GC"]
210
210
  GC.start
211
211
  GC.compact
@@ -236,7 +236,7 @@ class Loader
236
236
  Sidekiq.logger.error("GC End RSS: #{Process.rss}") if ENV["GC"]
237
237
  if ENV["PROFILE"]
238
238
  Sidekiq.logger.error("Profiling...")
239
- result = RubyProf.stop
239
+ result = $profile.stop
240
240
  printer = RubyProf::GraphHtmlPrinter.new(result)
241
241
  printer.print(File.new("output.html", "w"), min_percent: 1)
242
242
  end
data/bin/webload ADDED
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler'
4
+ Bundler.setup
5
+
6
+ # This skeleton allows you to run Sidekiq::Web page rendering
7
+ # through Vernier for tuning.
8
+ require "sidekiq/web"
9
+ require "rack/test"
10
+ require "vernier"
11
+
12
+ Sidekiq::Web.configure do |config|
13
+ config.middlewares.clear # remove csrf
14
+ end
15
+
16
+ class SomeJob
17
+ include Sidekiq::Job
18
+ end
19
+
20
+ class BenchWeb
21
+ include Rack::Test::Methods
22
+
23
+ def app
24
+ Sidekiq::Web.new
25
+ end
26
+
27
+ def warmup(page = "/scheduled")
28
+ # Sidekiq.redis {|c| c.flushdb }
29
+
30
+ # 100.times do |idx|
31
+ # SomeJob.perform_at(idx, 1, 3, "mike", {"foo" => "bar"})
32
+ # end
33
+
34
+ 100.times do
35
+ get page
36
+ end
37
+ end
38
+
39
+ def load(page = "/scheduled", count = 10_000)
40
+ profile do
41
+ count.times do
42
+ get page
43
+ raise last_response.inspect unless last_response.status == 200
44
+ end
45
+ end
46
+ end
47
+
48
+ def profile(&)
49
+ if ENV["PROF"]
50
+ Vernier.profile(out: "profile.json.gz", &)
51
+ else
52
+ yield
53
+ end
54
+ end
55
+ end
56
+
57
+ def timer(name="block", count = 10_000)
58
+ a = Time.now
59
+ yield count
60
+ b = Time.now
61
+ puts "#{name} in #{b - a} sec"
62
+ end
63
+
64
+ page = "/busy"
65
+ b = BenchWeb.new
66
+ b.warmup(page)
67
+ timer(page) do |count|
68
+ b.load(page, count)
69
+ end
@@ -1,21 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Sidekiq
4
- module ActiveJob
5
- # @api private
6
- class Wrapper
7
- include Sidekiq::Job
8
-
9
- def perform(job_data)
10
- ::ActiveJob::Base.execute(job_data.merge("provider_job_id" => jid))
11
- end
12
- end
13
- end
14
- end
15
-
16
3
  module ActiveJob
17
4
  module QueueAdapters
18
- # Explicitly remove the implementation existing in older rails'.
5
+ # Explicitly remove the implementation existing in older Rails.
19
6
  remove_const(:SidekiqAdapter) if const_defined?(:SidekiqAdapter)
20
7
 
21
8
  # Sidekiq adapter for Active Job
@@ -33,7 +20,7 @@ module ActiveJob
33
20
 
34
21
  # @api private
35
22
  def enqueue(job)
36
- job.provider_job_id = JobWrapper.set(
23
+ job.provider_job_id = Sidekiq::ActiveJob::Wrapper.set(
37
24
  wrapped: job.class,
38
25
  queue: job.queue_name
39
26
  ).perform_async(job.serialize)
@@ -41,7 +28,7 @@ module ActiveJob
41
28
 
42
29
  # @api private
43
30
  def enqueue_at(job, timestamp)
44
- job.provider_job_id = JobWrapper.set(
31
+ job.provider_job_id = Sidekiq::ActiveJob::Wrapper.set(
45
32
  wrapped: job.class,
46
33
  queue: job.queue_name
47
34
  ).perform_at(timestamp, job.serialize)
@@ -56,7 +43,7 @@ module ActiveJob
56
43
 
57
44
  if immediate_jobs.any?
58
45
  jids = Sidekiq::Client.push_bulk(
59
- "class" => JobWrapper,
46
+ "class" => Sidekiq::ActiveJob::Wrapper,
60
47
  "wrapped" => job_class,
61
48
  "queue" => queue,
62
49
  "args" => immediate_jobs.map { |job| [job.serialize] }
@@ -66,7 +53,7 @@ module ActiveJob
66
53
 
67
54
  if scheduled_jobs.any?
68
55
  jids = Sidekiq::Client.push_bulk(
69
- "class" => JobWrapper,
56
+ "class" => Sidekiq::ActiveJob::Wrapper,
70
57
  "wrapped" => job_class,
71
58
  "queue" => queue,
72
59
  "args" => scheduled_jobs.map { |job| [job.serialize] },
data/lib/sidekiq/api.rb CHANGED
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "sidekiq"
4
-
5
3
  require "zlib"
6
- require "set"
7
4
 
5
+ require "sidekiq"
8
6
  require "sidekiq/metrics/query"
9
7
 
10
8
  #
@@ -101,11 +99,22 @@ module Sidekiq
101
99
  rescue
102
100
  {}
103
101
  end
104
- now = Time.now.to_f
105
- thence = job["enqueued_at"] || now
106
- now - thence
102
+
103
+ enqueued_at = job["enqueued_at"]
104
+ if enqueued_at
105
+ if enqueued_at.is_a?(Float)
106
+ # old format
107
+ now = Time.now.to_f
108
+ now - enqueued_at
109
+ else
110
+ now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
111
+ (now - enqueued_at) / 1000.0
112
+ end
113
+ else
114
+ 0.0
115
+ end
107
116
  else
108
- 0
117
+ 0.0
109
118
  end
110
119
 
111
120
  @stats = {
@@ -265,11 +274,22 @@ module Sidekiq
265
274
  entry = Sidekiq.redis { |conn|
266
275
  conn.lindex(@rname, -1)
267
276
  }
268
- return 0 unless entry
277
+ return 0.0 unless entry
278
+
269
279
  job = Sidekiq.load_json(entry)
270
- now = Time.now.to_f
271
- thence = job["enqueued_at"] || now
272
- now - thence
280
+ enqueued_at = job["enqueued_at"]
281
+ if enqueued_at
282
+ if enqueued_at.is_a?(Float)
283
+ # old format
284
+ now = Time.now.to_f
285
+ now - enqueued_at
286
+ else
287
+ now = ::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond)
288
+ (now - enqueued_at) / 1000.0
289
+ end
290
+ else
291
+ 0.0
292
+ end
273
293
  end
274
294
 
275
295
  def each
@@ -422,11 +442,13 @@ module Sidekiq
422
442
  end
423
443
 
424
444
  def enqueued_at
425
- self["enqueued_at"] ? Time.at(self["enqueued_at"]).utc : nil
445
+ if self["enqueued_at"]
446
+ time_from_timestamp(self["enqueued_at"])
447
+ end
426
448
  end
427
449
 
428
450
  def created_at
429
- Time.at(self["created_at"] || self["enqueued_at"] || 0).utc
451
+ time_from_timestamp(self["created_at"] || self["enqueued_at"] || 0)
430
452
  end
431
453
 
432
454
  def tags
@@ -444,8 +466,17 @@ module Sidekiq
444
466
  end
445
467
 
446
468
  def latency
447
- now = Time.now.to_f
448
- now - (@item["enqueued_at"] || @item["created_at"] || now)
469
+ timestamp = @item["enqueued_at"] || @item["created_at"]
470
+ if timestamp
471
+ if timestamp.is_a?(Float)
472
+ # old format
473
+ Time.now.to_f - timestamp
474
+ else
475
+ (::Process.clock_gettime(::Process::CLOCK_REALTIME, :millisecond) - timestamp) / 1000.0
476
+ end
477
+ else
478
+ 0.0
479
+ end
449
480
  end
450
481
 
451
482
  # Remove this job from the queue
@@ -494,6 +525,15 @@ module Sidekiq
494
525
  uncompressed = Zlib::Inflate.inflate(strict_base64_decoded)
495
526
  Sidekiq.load_json(uncompressed)
496
527
  end
528
+
529
+ def time_from_timestamp(timestamp)
530
+ if timestamp.is_a?(Float)
531
+ # old format, timestamps were stored as fractional seconds since the epoch
532
+ Time.at(timestamp).utc
533
+ else
534
+ Time.at(timestamp / 1000, timestamp % 1000, :millisecond)
535
+ end
536
+ end
497
537
  end
498
538
 
499
539
  # Represents a job within a Redis sorted set where the score
@@ -1142,7 +1182,7 @@ module Sidekiq
1142
1182
  end
1143
1183
  end
1144
1184
 
1145
- results.sort_by { |(_, _, hsh)| hsh.raw("run_at") }.each(&block)
1185
+ results.sort_by { |(_, _, work)| work.run_at }.each(&block)
1146
1186
  end
1147
1187
 
1148
1188
  # Note that #size is only as accurate as Sidekiq's heartbeat,
@@ -1172,13 +1212,14 @@ module Sidekiq
1172
1212
  #
1173
1213
  # @param jid [String] the job identifier
1174
1214
  # @return [Sidekiq::Work] the work or nil
1175
- def find_work_by_jid(jid)
1215
+ def find_work(jid)
1176
1216
  each do |_process_id, _thread_id, work|
1177
1217
  job = work.job
1178
1218
  return work if job.jid == jid
1179
1219
  end
1180
1220
  nil
1181
1221
  end
1222
+ alias_method :find_work_by_jid, :find_work
1182
1223
  end
1183
1224
 
1184
1225
  # Sidekiq::Work represents a job which is currently executing.
@@ -1208,33 +1249,64 @@ module Sidekiq
1208
1249
  def payload
1209
1250
  @hsh["payload"]
1210
1251
  end
1252
+ end
1211
1253
 
1212
- # deprecated
1213
- def [](key)
1214
- kwargs = {uplevel: 1}
1215
- kwargs[:category] = :deprecated if RUBY_VERSION > "3.0" # TODO
1216
- warn("Direct access to `Sidekiq::Work` attributes is deprecated, please use `#payload`, `#queue`, `#run_at` or `#job` instead", **kwargs)
1254
+ # Since "worker" is a nebulous term, we've deprecated the use of this class name.
1255
+ # Is "worker" a process, a type of job, a thread? Undefined!
1256
+ # WorkSet better describes the data.
1257
+ Workers = WorkSet
1217
1258
 
1218
- @hsh[key]
1219
- end
1259
+ class ProfileSet
1260
+ include Enumerable
1220
1261
 
1221
- # :nodoc:
1222
- # @api private
1223
- def raw(name)
1224
- @hsh[name]
1262
+ # This is a point in time/snapshot API, you'll need to instantiate a new instance
1263
+ # if you want to fetch newer records.
1264
+ def initialize
1265
+ @records = Sidekiq.redis do |c|
1266
+ # This throws away expired profiles
1267
+ c.zremrangebyscore("profiles", "-inf", Time.now.to_f.to_s)
1268
+ # retreive records, newest to oldest
1269
+ c.zrange("profiles", "+inf", 0, "byscore", "rev")
1270
+ end
1225
1271
  end
1226
1272
 
1227
- def method_missing(*all)
1228
- @hsh.send(*all)
1273
+ def size
1274
+ @records.size
1229
1275
  end
1230
1276
 
1231
- def respond_to_missing?(name, *args)
1232
- @hsh.respond_to?(name)
1277
+ def each(&block)
1278
+ fetch_keys = %w[started_at jid type token size elapsed].freeze
1279
+ arrays = Sidekiq.redis do |c|
1280
+ c.pipelined do |p|
1281
+ @records.each do |key|
1282
+ p.hmget(key, *fetch_keys)
1283
+ end
1284
+ end
1285
+ end
1286
+
1287
+ arrays.compact.map { |arr| ProfileRecord.new(arr) }.each(&block)
1233
1288
  end
1234
1289
  end
1235
1290
 
1236
- # Since "worker" is a nebulous term, we've deprecated the use of this class name.
1237
- # Is "worker" a process, a type of job, a thread? Undefined!
1238
- # WorkSet better describes the data.
1239
- Workers = WorkSet
1291
+ class ProfileRecord
1292
+ attr_reader :started_at, :jid, :type, :token, :size, :elapsed
1293
+
1294
+ def initialize(arr)
1295
+ # Must be same order as fetch_keys above
1296
+ @started_at = Time.at(Integer(arr[0]))
1297
+ @jid = arr[1]
1298
+ @type = arr[2]
1299
+ @token = arr[3]
1300
+ @size = Integer(arr[4])
1301
+ @elapsed = Float(arr[5])
1302
+ end
1303
+
1304
+ def key
1305
+ "#{token}-#{jid}"
1306
+ end
1307
+
1308
+ def data
1309
+ Sidekiq.redis { |c| c.hget(key, "data") }
1310
+ end
1311
+ end
1240
1312
  end
@@ -27,7 +27,7 @@ module Sidekiq
27
27
  attr_reader :mode
28
28
  attr_reader :weights
29
29
 
30
- def_delegators :@config, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig
30
+ def_delegators :@config, :[], :[]=, :fetch, :key?, :has_key?, :merge!, :dig, :thread_priority
31
31
 
32
32
  def initialize(name, config)
33
33
  @name = name
data/lib/sidekiq/cli.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  $stdout.sync = true
4
4
 
5
5
  require "yaml"
6
- require "singleton"
7
6
  require "optparse"
8
7
  require "erb"
9
8
  require "fileutils"
@@ -17,7 +16,6 @@ require "sidekiq/launcher"
17
16
  module Sidekiq # :nodoc:
18
17
  class CLI
19
18
  include Sidekiq::Component
20
- include Singleton unless $TESTING
21
19
 
22
20
  attr_accessor :launcher
23
21
  attr_accessor :environment
@@ -31,6 +29,10 @@ module Sidekiq # :nodoc:
31
29
  validate!
32
30
  end
33
31
 
32
+ def self.instance
33
+ @instance ||= new
34
+ end
35
+
34
36
  def jruby?
35
37
  defined?(::JRUBY_VERSION)
36
38
  end
@@ -74,7 +76,7 @@ module Sidekiq # :nodoc:
74
76
  # fire startup and start multithreading.
75
77
  info = @config.redis_info
76
78
  ver = Gem::Version.new(info["redis_version"])
77
- raise "You are connecting to Redis #{ver}, Sidekiq requires Redis 6.2.0 or greater" if ver < Gem::Version.new("6.2.0")
79
+ raise "You are connected to Redis #{ver}, Sidekiq requires Redis 7.2.0 or greater" if ver < Gem::Version.new("7.2.0")
78
80
 
79
81
  maxmemory_policy = info["maxmemory_policy"]
80
82
  if maxmemory_policy != "noeviction" && maxmemory_policy != ""
@@ -298,29 +300,18 @@ module Sidekiq # :nodoc:
298
300
 
299
301
  if File.directory?(@config[:require])
300
302
  require "rails"
301
- if ::Rails::VERSION::MAJOR < 6
302
- warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 6+"
303
+ if ::Rails::VERSION::MAJOR < 7
304
+ warn "Sidekiq #{Sidekiq::VERSION} only supports Rails 7+"
303
305
  end
304
306
  require "sidekiq/rails"
305
307
  require File.expand_path("#{@config[:require]}/config/environment.rb")
306
- @config[:tag] ||= default_tag
308
+ @config[:tag] ||= default_tag(::Rails.root)
307
309
  else
308
310
  require @config[:require]
311
+ @config[:tag] ||= default_tag
309
312
  end
310
313
  end
311
314
 
312
- def default_tag
313
- dir = ::Rails.root
314
- name = File.basename(dir)
315
- prevdir = File.dirname(dir) # Capistrano release directory?
316
- if name.to_i != 0 && prevdir
317
- if File.basename(prevdir) == "releases"
318
- return File.basename(File.dirname(prevdir))
319
- end
320
- end
321
- name
322
- end
323
-
324
315
  def validate!
325
316
  if !File.exist?(@config[:require]) ||
326
317
  (File.directory?(@config[:require]) && !File.exist?("#{@config[:require]}/config/application.rb"))
@@ -395,7 +386,12 @@ module Sidekiq # :nodoc:
395
386
  end
396
387
 
397
388
  def initialize_logger
398
- @config.logger.level = ::Logger::DEBUG if @config[:verbose]
389
+ if @config[:verbose] || ENV["DEBUG_INVOCATION"] == "1"
390
+ # DEBUG_INVOCATION is a systemd-ism triggered by
391
+ # RestartMode=debug. We turn on debugging when the
392
+ # sidekiq process crashes and is restarted with this flag.
393
+ @config.logger.level = ::Logger::DEBUG
394
+ end
399
395
  end
400
396
 
401
397
  def parse_config(path)