sidekiq 5.2.7 → 8.0.5

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +845 -8
  3. data/LICENSE.txt +9 -0
  4. data/README.md +54 -54
  5. data/bin/multi_queue_bench +271 -0
  6. data/bin/sidekiq +22 -3
  7. data/bin/sidekiqload +219 -112
  8. data/bin/sidekiqmon +11 -0
  9. data/bin/webload +69 -0
  10. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +120 -0
  11. data/lib/generators/sidekiq/job_generator.rb +59 -0
  12. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  13. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  14. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  15. data/lib/sidekiq/api.rb +757 -373
  16. data/lib/sidekiq/capsule.rb +132 -0
  17. data/lib/sidekiq/cli.rb +210 -233
  18. data/lib/sidekiq/client.rb +145 -103
  19. data/lib/sidekiq/component.rb +128 -0
  20. data/lib/sidekiq/config.rb +315 -0
  21. data/lib/sidekiq/deploy.rb +64 -0
  22. data/lib/sidekiq/embedded.rb +64 -0
  23. data/lib/sidekiq/fetch.rb +49 -42
  24. data/lib/sidekiq/iterable_job.rb +56 -0
  25. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  26. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  27. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  28. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  29. data/lib/sidekiq/job/iterable.rb +306 -0
  30. data/lib/sidekiq/job.rb +385 -0
  31. data/lib/sidekiq/job_logger.rb +34 -7
  32. data/lib/sidekiq/job_retry.rb +164 -109
  33. data/lib/sidekiq/job_util.rb +113 -0
  34. data/lib/sidekiq/launcher.rb +208 -107
  35. data/lib/sidekiq/logger.rb +80 -0
  36. data/lib/sidekiq/manager.rb +42 -46
  37. data/lib/sidekiq/metrics/query.rb +184 -0
  38. data/lib/sidekiq/metrics/shared.rb +109 -0
  39. data/lib/sidekiq/metrics/tracking.rb +150 -0
  40. data/lib/sidekiq/middleware/chain.rb +113 -56
  41. data/lib/sidekiq/middleware/current_attributes.rb +119 -0
  42. data/lib/sidekiq/middleware/i18n.rb +7 -7
  43. data/lib/sidekiq/middleware/modules.rb +23 -0
  44. data/lib/sidekiq/monitor.rb +147 -0
  45. data/lib/sidekiq/paginator.rb +41 -16
  46. data/lib/sidekiq/processor.rb +146 -127
  47. data/lib/sidekiq/profiler.rb +72 -0
  48. data/lib/sidekiq/rails.rb +46 -43
  49. data/lib/sidekiq/redis_client_adapter.rb +113 -0
  50. data/lib/sidekiq/redis_connection.rb +79 -108
  51. data/lib/sidekiq/ring_buffer.rb +31 -0
  52. data/lib/sidekiq/scheduled.rb +112 -50
  53. data/lib/sidekiq/sd_notify.rb +149 -0
  54. data/lib/sidekiq/systemd.rb +26 -0
  55. data/lib/sidekiq/testing/inline.rb +6 -5
  56. data/lib/sidekiq/testing.rb +91 -90
  57. data/lib/sidekiq/transaction_aware_client.rb +51 -0
  58. data/lib/sidekiq/version.rb +7 -1
  59. data/lib/sidekiq/web/action.rb +125 -60
  60. data/lib/sidekiq/web/application.rb +363 -259
  61. data/lib/sidekiq/web/config.rb +120 -0
  62. data/lib/sidekiq/web/csrf_protection.rb +183 -0
  63. data/lib/sidekiq/web/helpers.rb +241 -120
  64. data/lib/sidekiq/web/router.rb +62 -71
  65. data/lib/sidekiq/web.rb +69 -161
  66. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  67. data/lib/sidekiq.rb +94 -182
  68. data/sidekiq.gemspec +26 -16
  69. data/web/assets/images/apple-touch-icon.png +0 -0
  70. data/web/assets/javascripts/application.js +150 -61
  71. data/web/assets/javascripts/base-charts.js +120 -0
  72. data/web/assets/javascripts/chart.min.js +13 -0
  73. data/web/assets/javascripts/chartjs-adapter-date-fns.min.js +7 -0
  74. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  75. data/web/assets/javascripts/dashboard-charts.js +194 -0
  76. data/web/assets/javascripts/dashboard.js +41 -293
  77. data/web/assets/javascripts/metrics.js +280 -0
  78. data/web/assets/stylesheets/style.css +766 -0
  79. data/web/locales/ar.yml +72 -65
  80. data/web/locales/cs.yml +63 -62
  81. data/web/locales/da.yml +61 -53
  82. data/web/locales/de.yml +66 -53
  83. data/web/locales/el.yml +44 -24
  84. data/web/locales/en.yml +94 -66
  85. data/web/locales/es.yml +92 -54
  86. data/web/locales/fa.yml +66 -65
  87. data/web/locales/fr.yml +83 -62
  88. data/web/locales/gd.yml +99 -0
  89. data/web/locales/he.yml +66 -64
  90. data/web/locales/hi.yml +60 -59
  91. data/web/locales/it.yml +93 -54
  92. data/web/locales/ja.yml +75 -64
  93. data/web/locales/ko.yml +53 -52
  94. data/web/locales/lt.yml +84 -0
  95. data/web/locales/nb.yml +62 -61
  96. data/web/locales/nl.yml +53 -52
  97. data/web/locales/pl.yml +46 -45
  98. data/web/locales/{pt-br.yml → pt-BR.yml} +84 -56
  99. data/web/locales/pt.yml +52 -51
  100. data/web/locales/ru.yml +69 -63
  101. data/web/locales/sv.yml +54 -53
  102. data/web/locales/ta.yml +61 -60
  103. data/web/locales/tr.yml +101 -0
  104. data/web/locales/uk.yml +86 -61
  105. data/web/locales/ur.yml +65 -64
  106. data/web/locales/vi.yml +84 -0
  107. data/web/locales/zh-CN.yml +106 -0
  108. data/web/locales/{zh-tw.yml → zh-TW.yml} +43 -9
  109. data/web/views/_footer.erb +31 -19
  110. data/web/views/_job_info.erb +94 -75
  111. data/web/views/_metrics_period_select.erb +15 -0
  112. data/web/views/_nav.erb +14 -21
  113. data/web/views/_paging.erb +23 -19
  114. data/web/views/_poll_link.erb +3 -6
  115. data/web/views/_summary.erb +23 -23
  116. data/web/views/busy.erb +139 -87
  117. data/web/views/dashboard.erb +82 -53
  118. data/web/views/dead.erb +31 -27
  119. data/web/views/filtering.erb +6 -0
  120. data/web/views/layout.erb +15 -29
  121. data/web/views/metrics.erb +84 -0
  122. data/web/views/metrics_for_job.erb +58 -0
  123. data/web/views/morgue.erb +60 -70
  124. data/web/views/profiles.erb +43 -0
  125. data/web/views/queue.erb +50 -39
  126. data/web/views/queues.erb +45 -29
  127. data/web/views/retries.erb +65 -75
  128. data/web/views/retry.erb +32 -27
  129. data/web/views/scheduled.erb +58 -52
  130. data/web/views/scheduled_job_info.erb +1 -1
  131. metadata +96 -76
  132. data/.circleci/config.yml +0 -61
  133. data/.github/contributing.md +0 -32
  134. data/.github/issue_template.md +0 -11
  135. data/.gitignore +0 -15
  136. data/.travis.yml +0 -11
  137. data/3.0-Upgrade.md +0 -70
  138. data/4.0-Upgrade.md +0 -53
  139. data/5.0-Upgrade.md +0 -56
  140. data/COMM-LICENSE +0 -97
  141. data/Ent-Changes.md +0 -238
  142. data/Gemfile +0 -23
  143. data/LICENSE +0 -9
  144. data/Pro-2.0-Upgrade.md +0 -138
  145. data/Pro-3.0-Upgrade.md +0 -44
  146. data/Pro-4.0-Upgrade.md +0 -35
  147. data/Pro-Changes.md +0 -759
  148. data/Rakefile +0 -9
  149. data/bin/sidekiqctl +0 -20
  150. data/code_of_conduct.md +0 -50
  151. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  152. data/lib/sidekiq/core_ext.rb +0 -1
  153. data/lib/sidekiq/ctl.rb +0 -221
  154. data/lib/sidekiq/delay.rb +0 -42
  155. data/lib/sidekiq/exception_handler.rb +0 -29
  156. data/lib/sidekiq/extensions/action_mailer.rb +0 -57
  157. data/lib/sidekiq/extensions/active_record.rb +0 -40
  158. data/lib/sidekiq/extensions/class_methods.rb +0 -40
  159. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
  160. data/lib/sidekiq/logging.rb +0 -122
  161. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
  162. data/lib/sidekiq/util.rb +0 -66
  163. data/lib/sidekiq/worker.rb +0 -220
  164. data/web/assets/stylesheets/application-rtl.css +0 -246
  165. data/web/assets/stylesheets/application.css +0 -1144
  166. data/web/assets/stylesheets/bootstrap-rtl.min.css +0 -9
  167. data/web/assets/stylesheets/bootstrap.css +0 -5
  168. data/web/locales/zh-cn.yml +0 -68
  169. data/web/views/_status.erb +0 -4
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/arguments"
4
+ require "active_support/current_attributes"
5
+
6
+ module Sidekiq
7
+ ##
8
+ # Automatically save and load any current attributes in the execution context
9
+ # so context attributes "flow" from Rails actions into any associated jobs.
10
+ # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
11
+ # per-request attribute. See +ActiveSupport::CurrentAttributes+.
12
+ #
13
+ # For multiple current attributes, pass an array of current attributes.
14
+ #
15
+ # @example
16
+ #
17
+ # # in your initializer
18
+ # require "sidekiq/middleware/current_attributes"
19
+ # Sidekiq::CurrentAttributes.persist("Myapp::Current")
20
+ # # or multiple current attributes
21
+ # Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
22
+ #
23
+ module CurrentAttributes
24
+ Serializer = ::ActiveJob::Arguments
25
+
26
+ class Save
27
+ include Sidekiq::ClientMiddleware
28
+
29
+ def initialize(cattrs)
30
+ @cattrs = cattrs
31
+ end
32
+
33
+ def call(_, job, _, _)
34
+ @cattrs.each do |(key, strklass)|
35
+ if !job.has_key?(key)
36
+ attrs = strklass.constantize.attributes
37
+ # Retries can push the job N times, we don't
38
+ # want retries to reset cattr. #5692, #5090
39
+ job[key] = Serializer.serialize(attrs) if attrs.any?
40
+ end
41
+ end
42
+ yield
43
+ end
44
+ end
45
+
46
+ class Load
47
+ include Sidekiq::ServerMiddleware
48
+
49
+ def initialize(cattrs)
50
+ @cattrs = cattrs
51
+ end
52
+
53
+ def call(_, job, _, &block)
54
+ klass_attrs = {}
55
+
56
+ @cattrs.each do |(key, strklass)|
57
+ next unless job.has_key?(key)
58
+
59
+ klass_attrs[strklass.constantize] = Serializer.deserialize(job[key]).to_h
60
+ end
61
+
62
+ wrap(klass_attrs.to_a, &block)
63
+ end
64
+
65
+ private
66
+
67
+ def wrap(klass_attrs, &block)
68
+ klass, attrs = klass_attrs.shift
69
+ return block.call unless klass
70
+
71
+ retried = false
72
+
73
+ begin
74
+ set_succeeded = false
75
+ klass.set(attrs) do
76
+ set_succeeded = true
77
+ wrap(klass_attrs, &block)
78
+ end
79
+ rescue NoMethodError
80
+ # Don't retry if the no method error didn't come from current attributes
81
+ raise if retried || set_succeeded
82
+
83
+ # It is possible that the `CurrentAttributes` definition
84
+ # was changed before the job started processing.
85
+ attrs = attrs.select { |attr| klass.respond_to?(attr) }
86
+ retried = true
87
+ retry
88
+ end
89
+ end
90
+ end
91
+
92
+ class << self
93
+ def persist(klass_or_array, config = Sidekiq.default_configuration)
94
+ cattrs = build_cattrs_hash(klass_or_array)
95
+
96
+ config.client_middleware.add Save, cattrs
97
+ config.server_middleware.prepend Load, cattrs
98
+ end
99
+
100
+ private
101
+
102
+ def build_cattrs_hash(klass_or_array)
103
+ if klass_or_array.is_a?(Array)
104
+ {}.tap do |hash|
105
+ klass_or_array.each_with_index do |klass, index|
106
+ hash[key_at(index)] = klass.to_s
107
+ end
108
+ end
109
+ else
110
+ {key_at(0) => klass_or_array.to_s}
111
+ end
112
+ end
113
+
114
+ def key_at(index)
115
+ (index == 0) ? "cattr" : "cattr_#{index}"
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  #
3
4
  # Simple middleware to save the current locale and restore it when the job executes.
4
5
  # Use it by requiring it in your initializer:
@@ -9,19 +10,18 @@ module Sidekiq::Middleware::I18n
9
10
  # Get the current locale and store it in the message
10
11
  # to be sent to Sidekiq.
11
12
  class Client
12
- def call(worker_class, msg, queue, redis_pool)
13
- msg['locale'] ||= I18n.locale
13
+ include Sidekiq::ClientMiddleware
14
+ def call(_jobclass, job, _queue, _redis)
15
+ job["locale"] ||= I18n.locale
14
16
  yield
15
17
  end
16
18
  end
17
19
 
18
20
  # Pull the msg locale out and set the current thread to use it.
19
21
  class Server
20
- def call(worker, msg, queue)
21
- I18n.locale = msg['locale'] || I18n.default_locale
22
- yield
23
- ensure
24
- I18n.locale = I18n.default_locale
22
+ include Sidekiq::ServerMiddleware
23
+ def call(_jobclass, job, _queue, &block)
24
+ I18n.with_locale(job.fetch("locale", I18n.default_locale), &block)
25
25
  end
26
26
  end
27
27
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ # Server-side middleware must import this Module in order
5
+ # to get access to server resources during `call`.
6
+ module ServerMiddleware
7
+ attr_accessor :config
8
+ def redis_pool
9
+ config.redis_pool
10
+ end
11
+
12
+ def logger
13
+ config.logger
14
+ end
15
+
16
+ def redis(&block)
17
+ config.redis(&block)
18
+ end
19
+ end
20
+
21
+ # no difference for now
22
+ ClientMiddleware = ServerMiddleware
23
+ end
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "fileutils"
5
+ require "sidekiq/api"
6
+
7
+ class Sidekiq::Monitor
8
+ class Status
9
+ VALID_SECTIONS = %w[all version overview processes queues]
10
+ COL_PAD = 2
11
+
12
+ def display(section = nil)
13
+ section ||= "all"
14
+ unless VALID_SECTIONS.include? section
15
+ puts "I don't know how to check the status of '#{section}'!"
16
+ puts "Try one of these: #{VALID_SECTIONS.join(", ")}"
17
+ return
18
+ end
19
+ send(section)
20
+ end
21
+
22
+ def all
23
+ version
24
+ puts
25
+ overview
26
+ puts
27
+ processes
28
+ puts
29
+ queues
30
+ end
31
+
32
+ def version
33
+ puts "Sidekiq #{Sidekiq::VERSION}"
34
+ puts Time.now.utc
35
+ end
36
+
37
+ def overview
38
+ puts "---- Overview ----"
39
+ puts " Processed: #{delimit stats.processed}"
40
+ puts " Failed: #{delimit stats.failed}"
41
+ puts " Busy: #{delimit stats.workers_size}"
42
+ puts " Enqueued: #{delimit stats.enqueued}"
43
+ puts " Retries: #{delimit stats.retry_size}"
44
+ puts " Scheduled: #{delimit stats.scheduled_size}"
45
+ puts " Dead: #{delimit stats.dead_size}"
46
+ end
47
+
48
+ def processes
49
+ puts "---- Processes (#{process_set.size}) ----"
50
+ process_set.each_with_index do |process, index|
51
+ # Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
52
+ #
53
+ # Before:
54
+ # ["default", "critical"]
55
+ #
56
+ # After:
57
+ # {"default" => 1, "critical" => 10}
58
+ queues =
59
+ if process["weights"]
60
+ process["weights"].sort_by { |queue| queue[0] }.map { |capsule| capsule.map { |name, weight| (weight > 0) ? "#{name}: #{weight}" : name }.join(", ") }
61
+ else
62
+ process["queues"].sort
63
+ end
64
+
65
+ puts "#{process["identity"]} #{tags_for(process)}"
66
+ puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
67
+ puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
68
+ puts " Queues: #{split_multiline(queues, pad: 11)}"
69
+ puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
70
+ puts "" unless (index + 1) == process_set.size
71
+ end
72
+ end
73
+
74
+ def queues
75
+ puts "---- Queues (#{queue_data.size}) ----"
76
+ columns = {
77
+ name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
78
+ size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
79
+ latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
80
+ }
81
+ columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
82
+ puts
83
+ queue_data.each do |q|
84
+ columns.each do |col, (dir, width)|
85
+ print q.send(col).public_send(dir, width)
86
+ end
87
+ puts
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def delimit(number)
94
+ number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
95
+ end
96
+
97
+ def split_multiline(values, opts = {})
98
+ return "none" unless values
99
+ pad = opts[:pad] || 0
100
+ max_length = opts[:max_length] || (80 - pad)
101
+ out = []
102
+ line = +""
103
+ values.each do |value|
104
+ if (line.length + value.length) > max_length
105
+ out << line
106
+ line = " " * pad
107
+ end
108
+ line << value + ", "
109
+ end
110
+ out << line[0..-3]
111
+ out.join("\n")
112
+ end
113
+
114
+ def tags_for(process)
115
+ tags = [
116
+ process["tag"],
117
+ process["labels"],
118
+ ((process["quiet"] == "true") ? "quiet" : nil)
119
+ ].flatten.compact
120
+ tags.any? ? "[#{tags.join("] [")}]" : nil
121
+ end
122
+
123
+ def time_ago(timestamp)
124
+ seconds = Time.now - Time.at(timestamp)
125
+ return "just now" if seconds < 60
126
+ return "a minute ago" if seconds < 120
127
+ return "#{seconds.floor / 60} minutes ago" if seconds < 3600
128
+ return "an hour ago" if seconds < 7200
129
+ "#{seconds.floor / 60 / 60} hours ago"
130
+ end
131
+
132
+ QUEUE_STRUCT = Struct.new(:name, :size, :latency)
133
+ def queue_data
134
+ @queue_data ||= Sidekiq::Queue.all.map { |q|
135
+ QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency))
136
+ }
137
+ end
138
+
139
+ def process_set
140
+ @process_set ||= Sidekiq::ProcessSet.new
141
+ end
142
+
143
+ def stats
144
+ @stats ||= Sidekiq::Stats.new
145
+ end
146
+ end
147
+ end
@@ -1,9 +1,15 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Sidekiq
3
4
  module Paginator
5
+ TYPE_CACHE = {
6
+ "dead" => "zset",
7
+ "retry" => "zset",
8
+ "schedule" => "zset"
9
+ }
4
10
 
5
- def page(key, pageidx=1, page_size=25, opts=nil)
6
- current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
11
+ def page(key, pageidx = 1, page_size = 25, opts = nil)
12
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
7
13
  pageidx = current_page - 1
8
14
  total_size = 0
9
15
  items = []
@@ -11,27 +17,39 @@ module Sidekiq
11
17
  ending = starting + page_size - 1
12
18
 
13
19
  Sidekiq.redis do |conn|
14
- type = conn.type(key)
20
+ # horrible, think you can make this cleaner?
21
+ type = TYPE_CACHE[key]
22
+ if type
23
+ elsif key.start_with?("queue:")
24
+ type = TYPE_CACHE[key] = "list"
25
+ else
26
+ type = TYPE_CACHE[key] = conn.type(key)
27
+ end
28
+ rev = opts && opts[:reverse]
15
29
 
16
30
  case type
17
- when 'zset'
18
- rev = opts && opts[:reverse]
19
- total_size, items = conn.multi do
20
- conn.zcard(key)
31
+ when "zset"
32
+ total_size, items = conn.multi { |transaction|
33
+ transaction.zcard(key)
21
34
  if rev
22
- conn.zrevrange(key, starting, ending, :with_scores => true)
35
+ transaction.zrange(key, starting, ending, "REV", "withscores")
23
36
  else
24
- conn.zrange(key, starting, ending, :with_scores => true)
37
+ transaction.zrange(key, starting, ending, "withscores")
25
38
  end
26
- end
39
+ }
27
40
  [current_page, total_size, items]
28
- when 'list'
29
- total_size, items = conn.multi do
30
- conn.llen(key)
31
- conn.lrange(key, starting, ending)
32
- end
41
+ when "list"
42
+ total_size, items = conn.multi { |transaction|
43
+ transaction.llen(key)
44
+ if rev
45
+ transaction.lrange(key, -ending - 1, -starting - 1)
46
+ else
47
+ transaction.lrange(key, starting, ending)
48
+ end
49
+ }
50
+ items.reverse! if rev
33
51
  [current_page, total_size, items]
34
- when 'none'
52
+ when "none"
35
53
  [1, 0, []]
36
54
  else
37
55
  raise "can't page a #{type}"
@@ -39,5 +57,12 @@ module Sidekiq
39
57
  end
40
58
  end
41
59
 
60
+ def page_items(items, pageidx = 1, page_size = 25)
61
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
62
+ pageidx = current_page - 1
63
+ starting = pageidx * page_size
64
+ items = items.to_a
65
+ [current_page, items.size, items[starting, page_size]]
66
+ end
42
67
  end
43
68
  end