sidekiq 6.5.12 → 7.3.9

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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +340 -20
  3. data/README.md +43 -35
  4. data/bin/multi_queue_bench +271 -0
  5. data/bin/sidekiq +3 -8
  6. data/bin/sidekiqload +213 -118
  7. data/bin/sidekiqmon +3 -0
  8. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
  9. data/lib/generators/sidekiq/job_generator.rb +2 -0
  10. data/lib/sidekiq/api.rb +243 -162
  11. data/lib/sidekiq/capsule.rb +132 -0
  12. data/lib/sidekiq/cli.rb +60 -75
  13. data/lib/sidekiq/client.rb +87 -38
  14. data/lib/sidekiq/component.rb +26 -1
  15. data/lib/sidekiq/config.rb +311 -0
  16. data/lib/sidekiq/deploy.rb +64 -0
  17. data/lib/sidekiq/embedded.rb +63 -0
  18. data/lib/sidekiq/fetch.rb +11 -14
  19. data/lib/sidekiq/iterable_job.rb +55 -0
  20. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  21. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  22. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  23. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  24. data/lib/sidekiq/job/iterable.rb +294 -0
  25. data/lib/sidekiq/job.rb +382 -10
  26. data/lib/sidekiq/job_logger.rb +8 -7
  27. data/lib/sidekiq/job_retry.rb +42 -19
  28. data/lib/sidekiq/job_util.rb +53 -15
  29. data/lib/sidekiq/launcher.rb +71 -65
  30. data/lib/sidekiq/logger.rb +2 -27
  31. data/lib/sidekiq/manager.rb +9 -11
  32. data/lib/sidekiq/metrics/query.rb +9 -4
  33. data/lib/sidekiq/metrics/shared.rb +21 -9
  34. data/lib/sidekiq/metrics/tracking.rb +40 -26
  35. data/lib/sidekiq/middleware/chain.rb +19 -18
  36. data/lib/sidekiq/middleware/current_attributes.rb +85 -20
  37. data/lib/sidekiq/middleware/modules.rb +2 -0
  38. data/lib/sidekiq/monitor.rb +18 -4
  39. data/lib/sidekiq/paginator.rb +8 -2
  40. data/lib/sidekiq/processor.rb +62 -57
  41. data/lib/sidekiq/rails.rb +27 -10
  42. data/lib/sidekiq/redis_client_adapter.rb +31 -71
  43. data/lib/sidekiq/redis_connection.rb +44 -115
  44. data/lib/sidekiq/ring_buffer.rb +2 -0
  45. data/lib/sidekiq/scheduled.rb +22 -23
  46. data/lib/sidekiq/systemd.rb +2 -0
  47. data/lib/sidekiq/testing.rb +37 -46
  48. data/lib/sidekiq/transaction_aware_client.rb +11 -5
  49. data/lib/sidekiq/version.rb +6 -1
  50. data/lib/sidekiq/web/action.rb +29 -7
  51. data/lib/sidekiq/web/application.rb +82 -28
  52. data/lib/sidekiq/web/csrf_protection.rb +10 -7
  53. data/lib/sidekiq/web/helpers.rb +110 -49
  54. data/lib/sidekiq/web/router.rb +5 -2
  55. data/lib/sidekiq/web.rb +70 -17
  56. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  57. data/lib/sidekiq.rb +78 -274
  58. data/sidekiq.gemspec +13 -10
  59. data/web/assets/javascripts/application.js +44 -0
  60. data/web/assets/javascripts/base-charts.js +106 -0
  61. data/web/assets/javascripts/dashboard-charts.js +194 -0
  62. data/web/assets/javascripts/dashboard.js +17 -233
  63. data/web/assets/javascripts/metrics.js +151 -115
  64. data/web/assets/stylesheets/application-dark.css +4 -0
  65. data/web/assets/stylesheets/application-rtl.css +10 -89
  66. data/web/assets/stylesheets/application.css +56 -296
  67. data/web/locales/ar.yml +70 -70
  68. data/web/locales/cs.yml +62 -62
  69. data/web/locales/da.yml +60 -53
  70. data/web/locales/de.yml +65 -65
  71. data/web/locales/el.yml +2 -7
  72. data/web/locales/en.yml +81 -71
  73. data/web/locales/es.yml +68 -68
  74. data/web/locales/fa.yml +65 -65
  75. data/web/locales/fr.yml +80 -67
  76. data/web/locales/gd.yml +98 -0
  77. data/web/locales/he.yml +65 -64
  78. data/web/locales/hi.yml +59 -59
  79. data/web/locales/it.yml +85 -54
  80. data/web/locales/ja.yml +67 -70
  81. data/web/locales/ko.yml +52 -52
  82. data/web/locales/lt.yml +66 -66
  83. data/web/locales/nb.yml +61 -61
  84. data/web/locales/nl.yml +52 -52
  85. data/web/locales/pl.yml +45 -45
  86. data/web/locales/pt-br.yml +78 -69
  87. data/web/locales/pt.yml +51 -51
  88. data/web/locales/ru.yml +67 -66
  89. data/web/locales/sv.yml +53 -53
  90. data/web/locales/ta.yml +60 -60
  91. data/web/locales/tr.yml +100 -0
  92. data/web/locales/uk.yml +85 -61
  93. data/web/locales/ur.yml +64 -64
  94. data/web/locales/vi.yml +67 -67
  95. data/web/locales/zh-cn.yml +20 -19
  96. data/web/locales/zh-tw.yml +10 -2
  97. data/web/views/_footer.erb +16 -2
  98. data/web/views/_job_info.erb +18 -2
  99. data/web/views/_metrics_period_select.erb +12 -0
  100. data/web/views/_paging.erb +2 -0
  101. data/web/views/_poll_link.erb +1 -1
  102. data/web/views/_summary.erb +7 -7
  103. data/web/views/busy.erb +46 -35
  104. data/web/views/dashboard.erb +32 -8
  105. data/web/views/filtering.erb +6 -0
  106. data/web/views/layout.erb +6 -6
  107. data/web/views/metrics.erb +47 -26
  108. data/web/views/metrics_for_job.erb +43 -71
  109. data/web/views/morgue.erb +7 -11
  110. data/web/views/queue.erb +11 -15
  111. data/web/views/queues.erb +9 -3
  112. data/web/views/retries.erb +5 -9
  113. data/web/views/scheduled.erb +12 -13
  114. metadata +66 -41
  115. data/lib/sidekiq/delay.rb +0 -43
  116. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  117. data/lib/sidekiq/extensions/active_record.rb +0 -43
  118. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  119. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  120. data/lib/sidekiq/metrics/deploy.rb +0 -47
  121. data/lib/sidekiq/worker.rb +0 -370
  122. data/web/assets/javascripts/graph.js +0 -16
  123. /data/{LICENSE → LICENSE.txt} +0 -0
data/lib/sidekiq/web.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "erb"
4
+ require "securerandom"
4
5
 
5
6
  require "sidekiq"
6
7
  require "sidekiq/api"
@@ -30,14 +31,30 @@ module Sidekiq
30
31
  "Queues" => "queues",
31
32
  "Retries" => "retries",
32
33
  "Scheduled" => "scheduled",
33
- "Dead" => "morgue"
34
+ "Dead" => "morgue",
35
+ "Metrics" => "metrics"
34
36
  }
35
37
 
36
- if ENV["SIDEKIQ_METRICS_BETA"] == "1"
37
- DEFAULT_TABS["Metrics"] = "metrics"
38
+ if Gem::Version.new(Rack::RELEASE) < Gem::Version.new("3")
39
+ CONTENT_LANGUAGE = "Content-Language"
40
+ CONTENT_SECURITY_POLICY = "Content-Security-Policy"
41
+ LOCATION = "Location"
42
+ X_CASCADE = "X-Cascade"
43
+ X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options"
44
+ else
45
+ CONTENT_LANGUAGE = "content-language"
46
+ CONTENT_SECURITY_POLICY = "content-security-policy"
47
+ LOCATION = "location"
48
+ X_CASCADE = "x-cascade"
49
+ X_CONTENT_TYPE_OPTIONS = "x-content-type-options"
38
50
  end
39
51
 
40
52
  class << self
53
+ # Forward compatibility with 8.0
54
+ def configure
55
+ yield self
56
+ end
57
+
41
58
  def settings
42
59
  self
43
60
  end
@@ -51,6 +68,10 @@ module Sidekiq
51
68
  end
52
69
  alias_method :tabs, :custom_tabs
53
70
 
71
+ def custom_job_info_rows
72
+ @custom_job_info_rows ||= []
73
+ end
74
+
54
75
  def locales
55
76
  @locales ||= LOCALES
56
77
  end
@@ -79,14 +100,6 @@ module Sidekiq
79
100
  send(:"#{attribute}=", value)
80
101
  end
81
102
 
82
- def sessions=(val)
83
- puts "WARNING: Sidekiq::Web.sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
84
- end
85
-
86
- def session_secret=(val)
87
- puts "WARNING: Sidekiq::Web.session_secret= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}"
88
- end
89
-
90
103
  attr_accessor :app_url, :redis_pool
91
104
  attr_writer :locales, :views
92
105
  end
@@ -109,6 +122,7 @@ module Sidekiq
109
122
  end
110
123
 
111
124
  def call(env)
125
+ env[:csp_nonce] = SecureRandom.base64(16)
112
126
  app.call(env)
113
127
  end
114
128
 
@@ -133,11 +147,50 @@ module Sidekiq
133
147
  send(:"#{attribute}=", value)
134
148
  end
135
149
 
136
- def sessions=(val)
137
- puts "Sidekiq::Web#sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller[2..2].first}"
138
- end
150
+ # Register a class as a Sidekiq Web UI extension. The class should
151
+ # provide one or more tabs which map to an index route. Options:
152
+ #
153
+ # @param extension [Class] Class which contains the HTTP actions, required
154
+ # @param name [String] the name of the extension, used to namespace assets
155
+ # @param tab [String | Array] labels(s) of the UI tabs
156
+ # @param index [String | Array] index route(s) for each tab
157
+ # @param root_dir [String] directory location to find assets, locales and views, typically `web/` within the gemfile
158
+ # @param asset_paths [Array] one or more directories under {root}/assets/{name} to be publicly served, e.g. ["js", "css", "img"]
159
+ # @param cache_for [Integer] amount of time to cache assets, default one day
160
+ #
161
+ # TODO name, tab and index will be mandatory in 8.0
162
+ #
163
+ # Web extensions will have a root `web/` directory with `locales/`, `assets/`
164
+ # and `views/` subdirectories.
165
+ def self.register(extension, name: nil, tab: nil, index: nil, root_dir: nil, cache_for: 86400, asset_paths: nil)
166
+ tab = Array(tab)
167
+ index = Array(index)
168
+ tab.zip(index).each do |tab, index|
169
+ tabs[tab] = index
170
+ end
171
+ if root_dir
172
+ locdir = File.join(root_dir, "locales")
173
+ locales << locdir if File.directory?(locdir)
174
+
175
+ if asset_paths && name
176
+ # if you have {root}/assets/{name}/js/scripts.js
177
+ # and {root}/assets/{name}/css/styles.css
178
+ # you would pass in:
179
+ # asset_paths: ["js", "css"]
180
+ # See script_tag and style_tag in web/helpers.rb
181
+ assdir = File.join(root_dir, "assets")
182
+ assurls = Array(asset_paths).map { |x| "/#{name}/#{x}" }
183
+ assetprops = {
184
+ urls: assurls,
185
+ root: assdir,
186
+ cascade: true
187
+ }
188
+ assetprops[:header_rules] = [[:all, {Rack::CACHE_CONTROL => "private, max-age=#{cache_for.to_i}"}]] if cache_for
189
+ middlewares << [[Rack::Static, assetprops], nil]
190
+ end
191
+ end
139
192
 
140
- def self.register(extension)
193
+ yield self if block_given?
141
194
  extension.registered(WebApplication)
142
195
  end
143
196
 
@@ -148,7 +201,7 @@ module Sidekiq
148
201
  m = middlewares
149
202
 
150
203
  rules = []
151
- rules = [[:all, {"cache-control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
204
+ rules = [[:all, {Rack::CACHE_CONTROL => "private, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
152
205
 
153
206
  ::Rack::Builder.new do
154
207
  use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
@@ -165,7 +218,7 @@ module Sidekiq
165
218
  Sidekiq::WebApplication.helpers WebHelpers
166
219
  Sidekiq::WebApplication.helpers Sidekiq::Paginator
167
220
 
168
- Sidekiq::WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
221
+ Sidekiq::WebAction.class_eval <<-RUBY, Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
169
222
  def _render
170
223
  #{ERB.new(File.read(Web::LAYOUT)).src}
171
224
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ # Sidekiq::Job is a new alias for Sidekiq::Worker as of Sidekiq 6.3.0.
5
+ # Use `include Sidekiq::Job` rather than `include Sidekiq::Worker`.
6
+ #
7
+ # The term "worker" is too generic and overly confusing, used in several
8
+ # different contexts meaning different things. Many people call a Sidekiq
9
+ # process a "worker". Some people call the thread that executes jobs a
10
+ # "worker". This change brings Sidekiq closer to ActiveJob where your job
11
+ # classes extend ApplicationJob.
12
+ Worker = Job
13
+ end
data/lib/sidekiq.rb CHANGED
@@ -1,15 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sidekiq/version"
4
- fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.5.0." if RUBY_PLATFORM != "java" && Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.5.0")
4
+ fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.7.0." if RUBY_PLATFORM != "java" && Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7.0")
5
5
 
6
+ begin
7
+ require "sidekiq-ent/version"
8
+ fail <<~EOM if Gem::Version.new(Sidekiq::Enterprise::VERSION).segments[0] != Sidekiq::MAJOR
9
+
10
+ Sidekiq Enterprise #{Sidekiq::Enterprise::VERSION} does not work with Sidekiq #{Sidekiq::VERSION}.
11
+ Starting with Sidekiq 7, major versions are synchronized so Sidekiq Enterprise 7 works with Sidekiq 7.
12
+ Use `bundle up sidekiq-ent` to upgrade.
13
+
14
+ EOM
15
+ rescue LoadError
16
+ end
17
+
18
+ begin
19
+ require "sidekiq/pro/version"
20
+ fail <<~EOM if Gem::Version.new(Sidekiq::Pro::VERSION).segments[0] != Sidekiq::MAJOR
21
+
22
+ Sidekiq Pro #{Sidekiq::Pro::VERSION} does not work with Sidekiq #{Sidekiq::VERSION}.
23
+ Starting with Sidekiq 7, major versions are synchronized so Sidekiq Pro 7 works with Sidekiq 7.
24
+ Use `bundle up sidekiq-pro` to upgrade.
25
+
26
+ EOM
27
+ rescue LoadError
28
+ end
29
+
30
+ require "sidekiq/config"
6
31
  require "sidekiq/logger"
7
32
  require "sidekiq/client"
8
33
  require "sidekiq/transaction_aware_client"
9
- require "sidekiq/worker"
10
34
  require "sidekiq/job"
11
- require "sidekiq/redis_connection"
12
- require "sidekiq/delay"
35
+ require "sidekiq/iterable_job"
36
+ require "sidekiq/worker_compatibility_alias"
37
+ require "sidekiq/redis_client_adapter"
13
38
 
14
39
  require "json"
15
40
 
@@ -17,320 +42,99 @@ module Sidekiq
17
42
  NAME = "Sidekiq"
18
43
  LICENSE = "See LICENSE and the LGPL-3.0 for licensing details."
19
44
 
20
- DEFAULTS = {
21
- queues: [],
22
- labels: [],
23
- concurrency: 10,
24
- require: ".",
25
- strict: true,
26
- environment: nil,
27
- timeout: 25,
28
- poll_interval_average: nil,
29
- average_scheduled_poll_interval: 5,
30
- on_complex_arguments: :warn,
31
- error_handlers: [],
32
- death_handlers: [],
33
- lifecycle_events: {
34
- startup: [],
35
- quiet: [],
36
- shutdown: [],
37
- # triggers when we fire the first heartbeat on startup OR repairing a network partition
38
- heartbeat: [],
39
- # triggers on EVERY heartbeat call, every 10 seconds
40
- beat: []
41
- },
42
- dead_max_jobs: 10_000,
43
- dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months
44
- reloader: proc { |&block| block.call }
45
- }
46
-
47
- FAKE_INFO = {
48
- "redis_version" => "9.9.9",
49
- "uptime_in_days" => "9999",
50
- "connected_clients" => "9999",
51
- "used_memory_human" => "9P",
52
- "used_memory_peak_human" => "9P"
53
- }
54
-
55
45
  def self.❨╯°□°❩╯︵┻━┻
56
- puts "Calm down, yo."
57
- end
58
-
59
- # config.concurrency = 5
60
- def self.concurrency=(val)
61
- self[:concurrency] = Integer(val)
62
- end
63
-
64
- # config.queues = %w( high default low ) # strict
65
- # config.queues = %w( high,3 default,2 low,1 ) # weighted
66
- # config.queues = %w( feature1,1 feature2,1 feature3,1 ) # random
67
- #
68
- # With weighted priority, queue will be checked first (weight / total) of the time.
69
- # high will be checked first (3/6) or 50% of the time.
70
- # I'd recommend setting weights between 1-10. Weights in the hundreds or thousands
71
- # are ridiculous and unnecessarily expensive. You can get random queue ordering
72
- # by explicitly setting all weights to 1.
73
- def self.queues=(val)
74
- self[:queues] = Array(val).each_with_object([]) do |qstr, memo|
75
- name, weight = qstr.split(",")
76
- self[:strict] = false if weight.to_i > 0
77
- [weight.to_i, 1].max.times do
78
- memo << name
79
- end
80
- end
81
- end
82
-
83
- ### Private APIs
84
- def self.default_error_handler(ex, ctx)
85
- logger.warn(dump_json(ctx)) unless ctx.empty?
86
- logger.warn("#{ex.class.name}: #{ex.message}")
87
- logger.warn(ex.backtrace.join("\n")) unless ex.backtrace.nil?
88
- end
89
-
90
- # DEFAULT_ERROR_HANDLER is a constant that allows the default error handler to
91
- # be referenced. It must be defined here, after the default_error_handler
92
- # method is defined.
93
- DEFAULT_ERROR_HANDLER = method(:default_error_handler)
94
-
95
- @config = DEFAULTS.dup
96
- def self.options
97
- logger.warn "`config.options[:key] = value` is deprecated, use `config[:key] = value`: #{caller(1..2)}"
98
- @config
99
- end
100
-
101
- def self.options=(opts)
102
- logger.warn "config.options = hash` is deprecated, use `config.merge!(hash)`: #{caller(1..2)}"
103
- @config = opts
104
- end
105
-
106
- def self.[](key)
107
- @config[key]
108
- end
109
-
110
- def self.[]=(key, val)
111
- @config[key] = val
112
- end
113
-
114
- def self.merge!(hash)
115
- @config.merge!(hash)
116
- end
117
-
118
- def self.fetch(*args, &block)
119
- @config.fetch(*args, &block)
120
- end
121
-
122
- def self.handle_exception(ex, ctx = {})
123
- self[:error_handlers].each do |handler|
124
- handler.call(ex, ctx)
125
- rescue => ex
126
- logger.error "!!! ERROR HANDLER THREW AN ERROR !!!"
127
- logger.error ex
128
- logger.error ex.backtrace.join("\n") unless ex.backtrace.nil?
129
- end
130
- end
131
- ###
132
-
133
- ##
134
- # Configuration for Sidekiq server, use like:
135
- #
136
- # Sidekiq.configure_server do |config|
137
- # config.server_middleware do |chain|
138
- # chain.add MyServerHook
139
- # end
140
- # end
141
- def self.configure_server
142
- yield self if server?
143
- end
144
-
145
- ##
146
- # Configuration for Sidekiq client, use like:
147
- #
148
- # Sidekiq.configure_client do |config|
149
- # config.redis = { size: 1, url: 'redis://myhost:8877/0' }
150
- # end
151
- def self.configure_client
152
- yield self unless server?
46
+ puts "Take a deep breath and count to ten..."
153
47
  end
154
48
 
155
49
  def self.server?
156
50
  defined?(Sidekiq::CLI)
157
51
  end
158
52
 
159
- def self.redis
160
- raise ArgumentError, "requires a block" unless block_given?
161
- redis_pool.with do |conn|
162
- retryable = true
163
- begin
164
- yield conn
165
- rescue RedisConnection.adapter::BaseError => ex
166
- # 2550 Failover can cause the server to become a replica, need
167
- # to disconnect and reopen the socket to get back to the primary.
168
- # 4495 Use the same logic if we have a "Not enough replicas" error from the primary
169
- # 4985 Use the same logic when a blocking command is force-unblocked
170
- # The same retry logic is also used in client.rb
171
- if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
172
- conn.disconnect!
173
- retryable = false
174
- retry
175
- end
176
- raise
177
- end
178
- end
179
- end
180
-
181
- def self.redis_info
182
- redis do |conn|
183
- # admin commands can't go through redis-namespace starting
184
- # in redis-namespace 2.0
185
- if conn.respond_to?(:namespace)
186
- conn.redis.info
187
- else
188
- conn.info
189
- end
190
- rescue RedisConnection.adapter::CommandError => ex
191
- # 2850 return fake version when INFO command has (probably) been renamed
192
- raise unless /unknown command/.match?(ex.message)
193
- FAKE_INFO
194
- end
53
+ def self.load_json(string)
54
+ JSON.parse(string)
195
55
  end
196
56
 
197
- def self.redis_pool
198
- @redis ||= RedisConnection.create
57
+ def self.dump_json(object)
58
+ JSON.generate(object)
199
59
  end
200
60
 
201
- def self.redis=(hash)
202
- @redis = if hash.is_a?(ConnectionPool)
203
- hash
204
- else
205
- RedisConnection.create(hash)
206
- end
61
+ def self.pro?
62
+ defined?(Sidekiq::Pro)
207
63
  end
208
64
 
209
- def self.client_middleware
210
- @client_chain ||= Middleware::Chain.new(self)
211
- yield @client_chain if block_given?
212
- @client_chain
65
+ def self.ent?
66
+ defined?(Sidekiq::Enterprise)
213
67
  end
214
68
 
215
- def self.server_middleware
216
- @server_chain ||= default_server_middleware
217
- yield @server_chain if block_given?
218
- @server_chain
69
+ def self.redis_pool
70
+ (Thread.current[:sidekiq_capsule] || default_configuration).redis_pool
219
71
  end
220
72
 
221
- def self.default_server_middleware
222
- Middleware::Chain.new(self)
73
+ def self.redis(&block)
74
+ (Thread.current[:sidekiq_capsule] || default_configuration).redis(&block)
223
75
  end
224
76
 
225
- def self.default_worker_options=(hash) # deprecated
226
- @default_job_options = default_job_options.merge(hash.transform_keys(&:to_s))
77
+ def self.strict_args!(mode = :raise)
78
+ Sidekiq::Config::DEFAULTS[:on_complex_arguments] = mode
227
79
  end
228
80
 
229
81
  def self.default_job_options=(hash)
230
82
  @default_job_options = default_job_options.merge(hash.transform_keys(&:to_s))
231
83
  end
232
84
 
233
- def self.default_worker_options # deprecated
234
- @default_job_options ||= {"retry" => true, "queue" => "default"}
235
- end
236
-
237
85
  def self.default_job_options
238
86
  @default_job_options ||= {"retry" => true, "queue" => "default"}
239
87
  end
240
88
 
241
- ##
242
- # Death handlers are called when all retries for a job have been exhausted and
243
- # the job dies. It's the notification to your application
244
- # that this job will not succeed without manual intervention.
245
- #
246
- # Sidekiq.configure_server do |config|
247
- # config.death_handlers << ->(job, ex) do
248
- # end
249
- # end
250
- def self.death_handlers
251
- self[:death_handlers]
252
- end
253
-
254
- def self.load_json(string)
255
- JSON.parse(string)
256
- end
257
-
258
- def self.dump_json(object)
259
- JSON.generate(object)
260
- end
261
-
262
- def self.log_formatter
263
- @log_formatter ||= if ENV["DYNO"]
264
- Sidekiq::Logger::Formatters::WithoutTimestamp.new
265
- else
266
- Sidekiq::Logger::Formatters::Pretty.new
267
- end
268
- end
269
-
270
- def self.log_formatter=(log_formatter)
271
- @log_formatter = log_formatter
272
- logger.formatter = log_formatter
89
+ def self.default_configuration
90
+ @config ||= Sidekiq::Config.new
273
91
  end
274
92
 
275
93
  def self.logger
276
- @logger ||= Sidekiq::Logger.new($stdout, level: :info)
277
- end
278
-
279
- def self.logger=(logger)
280
- if logger.nil?
281
- self.logger.level = Logger::FATAL
282
- return self.logger
283
- end
284
-
285
- logger.extend(Sidekiq::LoggingUtils)
286
-
287
- @logger = logger
288
- end
289
-
290
- def self.pro?
291
- defined?(Sidekiq::Pro)
94
+ default_configuration.logger
292
95
  end
293
96
 
294
- def self.ent?
295
- defined?(Sidekiq::Enterprise)
97
+ def self.configure_server(&block)
98
+ (@config_blocks ||= []) << block
99
+ yield default_configuration if server?
296
100
  end
297
101
 
298
- # How frequently Redis should be checked by a random Sidekiq process for
299
- # scheduled and retriable jobs. Each individual process will take turns by
300
- # waiting some multiple of this value.
301
- #
302
- # See sidekiq/scheduled.rb for an in-depth explanation of this value
303
- def self.average_scheduled_poll_interval=(interval)
304
- self[:average_scheduled_poll_interval] = interval
102
+ def self.freeze!
103
+ @frozen = true
104
+ @config_blocks = nil
105
+ default_configuration.freeze!
305
106
  end
306
107
 
307
- # Register a proc to handle any error which occurs within the Sidekiq process.
108
+ # Creates a Sidekiq::Config instance that is more tuned for embedding
109
+ # within an arbitrary Ruby process. Notably it reduces concurrency by
110
+ # default so there is less contention for CPU time with other threads.
308
111
  #
309
- # Sidekiq.configure_server do |config|
310
- # config.error_handlers << proc {|ex,ctx_hash| MyErrorService.notify(ex, ctx_hash) }
112
+ # instance = Sidekiq.configure_embed do |config|
113
+ # config.queues = %w[critical default low]
311
114
  # end
115
+ # instance.run
116
+ # sleep 10
117
+ # instance.stop
312
118
  #
313
- # The default error handler logs errors to Sidekiq.logger.
314
- def self.error_handlers
315
- self[:error_handlers]
316
- end
317
-
318
- # Register a block to run at a point in the Sidekiq lifecycle.
319
- # :startup, :quiet or :shutdown are valid events.
119
+ # NB: it is really easy to overload a Ruby process with threads due to the GIL.
120
+ # I do not recommend setting concurrency higher than 2-3.
320
121
  #
321
- # Sidekiq.configure_server do |config|
322
- # config.on(:shutdown) do
323
- # puts "Goodbye cruel world!"
324
- # end
325
- # end
326
- def self.on(event, &block)
327
- raise ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
328
- raise ArgumentError, "Invalid event name: #{event}" unless self[:lifecycle_events].key?(event)
329
- self[:lifecycle_events][event] << block
122
+ # NB: Sidekiq only supports one instance in memory. You will get undefined behavior
123
+ # if you try to embed Sidekiq twice in the same process.
124
+ def self.configure_embed(&block)
125
+ raise "Sidekiq global configuration is frozen, you must create all embedded instances BEFORE calling `run`" if @frozen
126
+
127
+ require "sidekiq/embedded"
128
+ cfg = default_configuration
129
+ cfg.concurrency = 2
130
+ @config_blocks&.each { |block| block.call(cfg) }
131
+ yield cfg
132
+
133
+ Sidekiq::Embedded.new(cfg)
330
134
  end
331
135
 
332
- def self.strict_args!(mode = :raise)
333
- self[:on_complex_arguments] = mode
136
+ def self.configure_client
137
+ yield default_configuration unless server?
334
138
  end
335
139
 
336
140
  # We are shutting down Sidekiq but what about threads that
data/sidekiq.gemspec CHANGED
@@ -2,27 +2,30 @@ require_relative "lib/sidekiq/version"
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.authors = ["Mike Perham"]
5
- gem.email = ["mperham@gmail.com"]
5
+ gem.email = ["info@contribsys.com"]
6
6
  gem.summary = "Simple, efficient background processing for Ruby"
7
7
  gem.description = "Simple, efficient background processing for Ruby."
8
8
  gem.homepage = "https://sidekiq.org"
9
9
  gem.license = "LGPL-3.0"
10
10
 
11
11
  gem.executables = ["sidekiq", "sidekiqmon"]
12
- gem.files = ["sidekiq.gemspec", "README.md", "Changes.md", "LICENSE"] + `git ls-files | grep -E '^(bin|lib|web)'`.split("\n")
12
+ gem.files = %w[sidekiq.gemspec README.md Changes.md LICENSE.txt] + `git ls-files | grep -E '^(bin|lib|web)'`.split("\n")
13
13
  gem.name = "sidekiq"
14
14
  gem.version = Sidekiq::VERSION
15
- gem.required_ruby_version = ">= 2.5.0"
15
+ gem.required_ruby_version = ">= 2.7.0"
16
16
 
17
17
  gem.metadata = {
18
18
  "homepage_uri" => "https://sidekiq.org",
19
- "bug_tracker_uri" => "https://github.com/mperham/sidekiq/issues",
20
- "documentation_uri" => "https://github.com/mperham/sidekiq/wiki",
21
- "changelog_uri" => "https://github.com/mperham/sidekiq/blob/main/Changes.md",
22
- "source_code_uri" => "https://github.com/mperham/sidekiq"
19
+ "bug_tracker_uri" => "https://github.com/sidekiq/sidekiq/issues",
20
+ "documentation_uri" => "https://github.com/sidekiq/sidekiq/wiki",
21
+ "changelog_uri" => "https://github.com/sidekiq/sidekiq/blob/main/Changes.md",
22
+ "source_code_uri" => "https://github.com/sidekiq/sidekiq",
23
+ "rubygems_mfa_required" => "true"
23
24
  }
24
25
 
25
- gem.add_dependency "redis", ["<5", ">= 4.5.0"]
26
- gem.add_dependency "connection_pool", ["<3", ">= 2.2.5"]
27
- gem.add_dependency "rack", "~> 2.0"
26
+ gem.add_dependency "redis-client", ">= 0.22.2"
27
+ gem.add_dependency "connection_pool", ">= 2.3.0"
28
+ gem.add_dependency "rack", ">= 2.2.4"
29
+ gem.add_dependency "logger"
30
+ gem.add_dependency "base64"
28
31
  end
@@ -31,7 +31,10 @@ function addListeners() {
31
31
  node.addEventListener("click", addDataToggleListeners)
32
32
  })
33
33
 
34
+ addShiftClickListeners()
34
35
  updateFuzzyTimes();
36
+ updateNumbers();
37
+ updateProgressBars();
35
38
  setLivePollFromUrl();
36
39
 
37
40
  var buttons = document.querySelectorAll(".live-poll");
@@ -45,6 +48,8 @@ function addListeners() {
45
48
  scheduleLivePoll();
46
49
  }
47
50
  }
51
+
52
+ document.getElementById("locale-select").addEventListener("change", updateLocale);
48
53
  }
49
54
 
50
55
  function addPollingListeners(_event) {
@@ -71,6 +76,23 @@ function addDataToggleListeners(event) {
71
76
  }
72
77
  }
73
78
 
79
+ function addShiftClickListeners() {
80
+ let checkboxes = Array.from(document.querySelectorAll(".shift_clickable"));
81
+ let lastChecked = null;
82
+ checkboxes.forEach(checkbox => {
83
+ checkbox.addEventListener("click", (e) => {
84
+ if (e.shiftKey && lastChecked) {
85
+ let myIndex = checkboxes.indexOf(checkbox);
86
+ let lastIndex = checkboxes.indexOf(lastChecked);
87
+ let [min, max] = [myIndex, lastIndex].sort();
88
+ let newState = checkbox.checked;
89
+ checkboxes.slice(min, max).forEach(c => c.checked = newState);
90
+ }
91
+ lastChecked = checkbox;
92
+ });
93
+ });
94
+ }
95
+
74
96
  function updateFuzzyTimes() {
75
97
  var locale = document.body.getAttribute("data-locale");
76
98
  var parts = locale.split('-');
@@ -84,6 +106,20 @@ function updateFuzzyTimes() {
84
106
  t.cancel();
85
107
  }
86
108
 
109
+ function updateNumbers() {
110
+ document.querySelectorAll("[data-nwp]").forEach(node => {
111
+ let number = parseFloat(node.textContent);
112
+ let precision = parseInt(node.dataset["nwp"] || 0);
113
+ if (typeof number === "number") {
114
+ let formatted = number.toLocaleString(undefined, {
115
+ minimumFractionDigits: precision,
116
+ maximumFractionDigits: precision,
117
+ });
118
+ node.textContent = formatted;
119
+ }
120
+ });
121
+ }
122
+
87
123
  function setLivePollFromUrl() {
88
124
  var url_params = new URL(window.location.href).searchParams
89
125
 
@@ -142,3 +178,11 @@ function replacePage(text) {
142
178
  function showError(error) {
143
179
  console.error(error)
144
180
  }
181
+
182
+ function updateLocale(event) {
183
+ event.target.form.submit();
184
+ }
185
+
186
+ function updateProgressBars() {
187
+ document.querySelectorAll('.progress-bar').forEach(bar => { bar.style.width = bar.dataset.width + "%"})
188
+ }