sidekiq 6.5.12 → 7.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +303 -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 +204 -118
  7. data/bin/sidekiqmon +3 -0
  8. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +88 -0
  9. data/lib/generators/sidekiq/job_generator.rb +2 -0
  10. data/lib/sidekiq/api.rb +196 -138
  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 +4 -1
  15. data/lib/sidekiq/config.rb +305 -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 +23 -12
  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 +70 -20
  37. data/lib/sidekiq/middleware/modules.rb +2 -0
  38. data/lib/sidekiq/monitor.rb +18 -4
  39. data/lib/sidekiq/paginator.rb +2 -2
  40. data/lib/sidekiq/processor.rb +62 -57
  41. data/lib/sidekiq/rails.rb +21 -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 +15 -5
  51. data/lib/sidekiq/web/application.rb +89 -17
  52. data/lib/sidekiq/web/csrf_protection.rb +10 -7
  53. data/lib/sidekiq/web/helpers.rb +102 -42
  54. data/lib/sidekiq/web/router.rb +5 -2
  55. data/lib/sidekiq/web.rb +65 -17
  56. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  57. data/lib/sidekiq.rb +78 -274
  58. data/sidekiq.gemspec +12 -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 +192 -0
  62. data/web/assets/javascripts/dashboard.js +11 -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 +53 -298
  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 +78 -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 +53 -53
  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 +17 -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 +28 -7
  105. data/web/views/filtering.erb +7 -0
  106. data/web/views/layout.erb +6 -6
  107. data/web/views/metrics.erb +48 -26
  108. data/web/views/metrics_for_job.erb +43 -71
  109. data/web/views/morgue.erb +5 -9
  110. data/web/views/queue.erb +10 -14
  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 +53 -39
  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,11 +31,22 @@ 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
@@ -51,6 +63,10 @@ module Sidekiq
51
63
  end
52
64
  alias_method :tabs, :custom_tabs
53
65
 
66
+ def custom_job_info_rows
67
+ @custom_job_info_rows ||= []
68
+ end
69
+
54
70
  def locales
55
71
  @locales ||= LOCALES
56
72
  end
@@ -79,14 +95,6 @@ module Sidekiq
79
95
  send(:"#{attribute}=", value)
80
96
  end
81
97
 
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
98
  attr_accessor :app_url, :redis_pool
91
99
  attr_writer :locales, :views
92
100
  end
@@ -109,6 +117,7 @@ module Sidekiq
109
117
  end
110
118
 
111
119
  def call(env)
120
+ env[:csp_nonce] = SecureRandom.base64(16)
112
121
  app.call(env)
113
122
  end
114
123
 
@@ -133,11 +142,50 @@ module Sidekiq
133
142
  send(:"#{attribute}=", value)
134
143
  end
135
144
 
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
145
+ # Register a class as a Sidekiq Web UI extension. The class should
146
+ # provide one or more tabs which map to an index route. Options:
147
+ #
148
+ # @param extension [Class] Class which contains the HTTP actions, required
149
+ # @param name [String] the name of the extension, used to namespace assets
150
+ # @param tab [String | Array] labels(s) of the UI tabs
151
+ # @param index [String | Array] index route(s) for each tab
152
+ # @param root_dir [String] directory location to find assets, locales and views, typically `web/` within the gemfile
153
+ # @param asset_paths [Array] one or more directories under {root}/assets/{name} to be publicly served, e.g. ["js", "css", "img"]
154
+ # @param cache_for [Integer] amount of time to cache assets, default one day
155
+ #
156
+ # TODO name, tab and index will be mandatory in 8.0
157
+ #
158
+ # Web extensions will have a root `web/` directory with `locales/`, `assets/`
159
+ # and `views/` subdirectories.
160
+ def self.register(extension, name: nil, tab: nil, index: nil, root_dir: nil, cache_for: 86400, asset_paths: nil)
161
+ tab = Array(tab)
162
+ index = Array(index)
163
+ tab.zip(index).each do |tab, index|
164
+ tabs[tab] = index
165
+ end
166
+ if root_dir
167
+ locdir = File.join(root_dir, "locales")
168
+ locales << locdir if File.directory?(locdir)
169
+
170
+ if asset_paths && name
171
+ # if you have {root}/assets/{name}/js/scripts.js
172
+ # and {root}/assets/{name}/css/styles.css
173
+ # you would pass in:
174
+ # asset_paths: ["js", "css"]
175
+ # See script_tag and style_tag in web/helpers.rb
176
+ assdir = File.join(root_dir, "assets")
177
+ assurls = Array(asset_paths).map { |x| "/#{name}/#{x}" }
178
+ assetprops = {
179
+ urls: assurls,
180
+ root: assdir,
181
+ cascade: true
182
+ }
183
+ assetprops[:header_rules] = [[:all, {Rack::CACHE_CONTROL => "private, max-age=#{cache_for.to_i}"}]] if cache_for
184
+ middlewares << [[Rack::Static, assetprops], nil]
185
+ end
186
+ end
139
187
 
140
- def self.register(extension)
188
+ yield self if block_given?
141
189
  extension.registered(WebApplication)
142
190
  end
143
191
 
@@ -148,7 +196,7 @@ module Sidekiq
148
196
  m = middlewares
149
197
 
150
198
  rules = []
151
- rules = [[:all, {"cache-control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
199
+ rules = [[:all, {Rack::CACHE_CONTROL => "private, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"]
152
200
 
153
201
  ::Rack::Builder.new do
154
202
  use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"],
@@ -165,7 +213,7 @@ module Sidekiq
165
213
  Sidekiq::WebApplication.helpers WebHelpers
166
214
  Sidekiq::WebApplication.helpers Sidekiq::Paginator
167
215
 
168
- Sidekiq::WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1
216
+ Sidekiq::WebAction.class_eval <<-RUBY, Web::LAYOUT, -1 # standard:disable Style/EvalWithLocation
169
217
  def _render
170
218
  #{ERB.new(File.read(Web::LAYOUT)).src}
171
219
  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,29 @@ 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"
28
30
  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
+ }