sidekiq 5.1.3 → 7.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +756 -8
  3. data/LICENSE.txt +9 -0
  4. data/README.md +48 -51
  5. data/bin/multi_queue_bench +271 -0
  6. data/bin/sidekiq +22 -3
  7. data/bin/sidekiqload +213 -115
  8. data/bin/sidekiqmon +11 -0
  9. data/lib/generators/sidekiq/job_generator.rb +59 -0
  10. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  11. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  12. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  13. data/lib/sidekiq/api.rb +640 -330
  14. data/lib/sidekiq/capsule.rb +132 -0
  15. data/lib/sidekiq/cli.rb +244 -257
  16. data/lib/sidekiq/client.rb +132 -103
  17. data/lib/sidekiq/component.rb +68 -0
  18. data/lib/sidekiq/config.rb +293 -0
  19. data/lib/sidekiq/deploy.rb +64 -0
  20. data/lib/sidekiq/embedded.rb +63 -0
  21. data/lib/sidekiq/fetch.rb +49 -42
  22. data/lib/sidekiq/iterable_job.rb +55 -0
  23. data/lib/sidekiq/job/interrupt_handler.rb +24 -0
  24. data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
  25. data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
  26. data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
  27. data/lib/sidekiq/job/iterable.rb +231 -0
  28. data/lib/sidekiq/job.rb +385 -0
  29. data/lib/sidekiq/job_logger.rb +49 -12
  30. data/lib/sidekiq/job_retry.rb +167 -103
  31. data/lib/sidekiq/job_util.rb +109 -0
  32. data/lib/sidekiq/launcher.rb +209 -102
  33. data/lib/sidekiq/logger.rb +131 -0
  34. data/lib/sidekiq/manager.rb +43 -46
  35. data/lib/sidekiq/metrics/query.rb +158 -0
  36. data/lib/sidekiq/metrics/shared.rb +97 -0
  37. data/lib/sidekiq/metrics/tracking.rb +148 -0
  38. data/lib/sidekiq/middleware/chain.rb +113 -56
  39. data/lib/sidekiq/middleware/current_attributes.rb +113 -0
  40. data/lib/sidekiq/middleware/i18n.rb +7 -7
  41. data/lib/sidekiq/middleware/modules.rb +23 -0
  42. data/lib/sidekiq/monitor.rb +147 -0
  43. data/lib/sidekiq/paginator.rb +28 -16
  44. data/lib/sidekiq/processor.rb +175 -112
  45. data/lib/sidekiq/rails.rb +54 -39
  46. data/lib/sidekiq/redis_client_adapter.rb +114 -0
  47. data/lib/sidekiq/redis_connection.rb +65 -86
  48. data/lib/sidekiq/ring_buffer.rb +31 -0
  49. data/lib/sidekiq/scheduled.rb +139 -48
  50. data/lib/sidekiq/sd_notify.rb +149 -0
  51. data/lib/sidekiq/systemd.rb +26 -0
  52. data/lib/sidekiq/testing/inline.rb +6 -5
  53. data/lib/sidekiq/testing.rb +95 -94
  54. data/lib/sidekiq/transaction_aware_client.rb +51 -0
  55. data/lib/sidekiq/version.rb +3 -1
  56. data/lib/sidekiq/web/action.rb +22 -12
  57. data/lib/sidekiq/web/application.rb +225 -76
  58. data/lib/sidekiq/web/csrf_protection.rb +183 -0
  59. data/lib/sidekiq/web/helpers.rb +215 -118
  60. data/lib/sidekiq/web/router.rb +23 -19
  61. data/lib/sidekiq/web.rb +114 -106
  62. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  63. data/lib/sidekiq.rb +95 -182
  64. data/sidekiq.gemspec +26 -23
  65. data/web/assets/images/apple-touch-icon.png +0 -0
  66. data/web/assets/javascripts/application.js +157 -61
  67. data/web/assets/javascripts/base-charts.js +106 -0
  68. data/web/assets/javascripts/chart.min.js +13 -0
  69. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  70. data/web/assets/javascripts/dashboard-charts.js +192 -0
  71. data/web/assets/javascripts/dashboard.js +35 -283
  72. data/web/assets/javascripts/metrics.js +298 -0
  73. data/web/assets/stylesheets/application-dark.css +147 -0
  74. data/web/assets/stylesheets/application-rtl.css +10 -93
  75. data/web/assets/stylesheets/application.css +169 -522
  76. data/web/assets/stylesheets/bootstrap.css +2 -2
  77. data/web/locales/ar.yml +71 -64
  78. data/web/locales/cs.yml +62 -62
  79. data/web/locales/da.yml +60 -53
  80. data/web/locales/de.yml +65 -53
  81. data/web/locales/el.yml +43 -24
  82. data/web/locales/en.yml +86 -65
  83. data/web/locales/es.yml +70 -54
  84. data/web/locales/fa.yml +65 -65
  85. data/web/locales/fr.yml +83 -62
  86. data/web/locales/gd.yml +99 -0
  87. data/web/locales/he.yml +65 -64
  88. data/web/locales/hi.yml +59 -59
  89. data/web/locales/it.yml +53 -53
  90. data/web/locales/ja.yml +75 -64
  91. data/web/locales/ko.yml +52 -52
  92. data/web/locales/lt.yml +83 -0
  93. data/web/locales/nb.yml +61 -61
  94. data/web/locales/nl.yml +52 -52
  95. data/web/locales/pl.yml +45 -45
  96. data/web/locales/pt-br.yml +83 -55
  97. data/web/locales/pt.yml +51 -51
  98. data/web/locales/ru.yml +68 -63
  99. data/web/locales/sv.yml +53 -53
  100. data/web/locales/ta.yml +60 -60
  101. data/web/locales/tr.yml +101 -0
  102. data/web/locales/uk.yml +62 -61
  103. data/web/locales/ur.yml +64 -64
  104. data/web/locales/vi.yml +83 -0
  105. data/web/locales/zh-cn.yml +43 -16
  106. data/web/locales/zh-tw.yml +42 -8
  107. data/web/views/_footer.erb +18 -3
  108. data/web/views/_job_info.erb +21 -4
  109. data/web/views/_metrics_period_select.erb +12 -0
  110. data/web/views/_nav.erb +4 -18
  111. data/web/views/_paging.erb +2 -0
  112. data/web/views/_poll_link.erb +3 -6
  113. data/web/views/_summary.erb +7 -7
  114. data/web/views/busy.erb +79 -29
  115. data/web/views/dashboard.erb +49 -19
  116. data/web/views/dead.erb +3 -3
  117. data/web/views/filtering.erb +7 -0
  118. data/web/views/layout.erb +9 -7
  119. data/web/views/metrics.erb +91 -0
  120. data/web/views/metrics_for_job.erb +59 -0
  121. data/web/views/morgue.erb +14 -15
  122. data/web/views/queue.erb +33 -23
  123. data/web/views/queues.erb +19 -5
  124. data/web/views/retries.erb +19 -16
  125. data/web/views/retry.erb +3 -3
  126. data/web/views/scheduled.erb +17 -15
  127. metadata +84 -129
  128. data/.github/contributing.md +0 -32
  129. data/.github/issue_template.md +0 -11
  130. data/.gitignore +0 -13
  131. data/.travis.yml +0 -14
  132. data/3.0-Upgrade.md +0 -70
  133. data/4.0-Upgrade.md +0 -53
  134. data/5.0-Upgrade.md +0 -56
  135. data/COMM-LICENSE +0 -95
  136. data/Ent-Changes.md +0 -216
  137. data/Gemfile +0 -8
  138. data/LICENSE +0 -9
  139. data/Pro-2.0-Upgrade.md +0 -138
  140. data/Pro-3.0-Upgrade.md +0 -44
  141. data/Pro-4.0-Upgrade.md +0 -35
  142. data/Pro-Changes.md +0 -729
  143. data/Rakefile +0 -8
  144. data/bin/sidekiqctl +0 -99
  145. data/code_of_conduct.md +0 -50
  146. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  147. data/lib/sidekiq/core_ext.rb +0 -1
  148. data/lib/sidekiq/delay.rb +0 -42
  149. data/lib/sidekiq/exception_handler.rb +0 -29
  150. data/lib/sidekiq/extensions/action_mailer.rb +0 -57
  151. data/lib/sidekiq/extensions/active_record.rb +0 -40
  152. data/lib/sidekiq/extensions/class_methods.rb +0 -40
  153. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
  154. data/lib/sidekiq/logging.rb +0 -122
  155. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
  156. data/lib/sidekiq/util.rb +0 -66
  157. data/lib/sidekiq/worker.rb +0 -204
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/current_attributes"
4
+
5
+ module Sidekiq
6
+ ##
7
+ # Automatically save and load any current attributes in the execution context
8
+ # so context attributes "flow" from Rails actions into any associated jobs.
9
+ # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
10
+ # per-request attribute. See +ActiveSupport::CurrentAttributes+.
11
+ #
12
+ # For multiple current attributes, pass an array of current attributes.
13
+ #
14
+ # @example
15
+ #
16
+ # # in your initializer
17
+ # require "sidekiq/middleware/current_attributes"
18
+ # Sidekiq::CurrentAttributes.persist("Myapp::Current")
19
+ # # or multiple current attributes
20
+ # Sidekiq::CurrentAttributes.persist(["Myapp::Current", "Myapp::OtherCurrent"])
21
+ #
22
+ module CurrentAttributes
23
+ class Save
24
+ include Sidekiq::ClientMiddleware
25
+
26
+ def initialize(cattrs)
27
+ @cattrs = cattrs
28
+ end
29
+
30
+ def call(_, job, _, _)
31
+ @cattrs.each do |(key, strklass)|
32
+ if !job.has_key?(key)
33
+ attrs = strklass.constantize.attributes
34
+ # Retries can push the job N times, we don't
35
+ # want retries to reset cattr. #5692, #5090
36
+ job[key] = attrs if attrs.any?
37
+ end
38
+ end
39
+ yield
40
+ end
41
+ end
42
+
43
+ class Load
44
+ include Sidekiq::ServerMiddleware
45
+
46
+ def initialize(cattrs)
47
+ @cattrs = cattrs
48
+ end
49
+
50
+ def call(_, job, _, &block)
51
+ klass_attrs = {}
52
+
53
+ @cattrs.each do |(key, strklass)|
54
+ next unless job.has_key?(key)
55
+
56
+ klass_attrs[strklass.constantize] = job[key]
57
+ end
58
+
59
+ wrap(klass_attrs.to_a, &block)
60
+ end
61
+
62
+ private
63
+
64
+ def wrap(klass_attrs, &block)
65
+ klass, attrs = klass_attrs.shift
66
+ return block.call unless klass
67
+
68
+ retried = false
69
+
70
+ begin
71
+ klass.set(attrs) do
72
+ wrap(klass_attrs, &block)
73
+ end
74
+ rescue NoMethodError
75
+ raise if retried
76
+
77
+ # It is possible that the `CurrentAttributes` definition
78
+ # was changed before the job started processing.
79
+ attrs = attrs.select { |attr| klass.respond_to?(attr) }
80
+ retried = true
81
+ retry
82
+ end
83
+ end
84
+ end
85
+
86
+ class << self
87
+ def persist(klass_or_array, config = Sidekiq.default_configuration)
88
+ cattrs = build_cattrs_hash(klass_or_array)
89
+
90
+ config.client_middleware.add Save, cattrs
91
+ config.server_middleware.add Load, cattrs
92
+ end
93
+
94
+ private
95
+
96
+ def build_cattrs_hash(klass_or_array)
97
+ if klass_or_array.is_a?(Array)
98
+ {}.tap do |hash|
99
+ klass_or_array.each_with_index do |klass, index|
100
+ hash[key_at(index)] = klass.to_s
101
+ end
102
+ end
103
+ else
104
+ {key_at(0) => klass_or_array.to_s}
105
+ end
106
+ end
107
+
108
+ def key_at(index)
109
+ (index == 0) ? "cattr" : "cattr_#{index}"
110
+ end
111
+ end
112
+ end
113
+ 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,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Sidekiq
3
4
  module Paginator
4
-
5
- def page(key, pageidx=1, page_size=25, opts=nil)
6
- current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
5
+ def page(key, pageidx = 1, page_size = 25, opts = nil)
6
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
7
7
  pageidx = current_page - 1
8
8
  total_size = 0
9
9
  items = []
@@ -12,26 +12,31 @@ module Sidekiq
12
12
 
13
13
  Sidekiq.redis do |conn|
14
14
  type = conn.type(key)
15
+ rev = opts && opts[:reverse]
15
16
 
16
17
  case type
17
- when 'zset'
18
- rev = opts && opts[:reverse]
19
- total_size, items = conn.multi do
20
- conn.zcard(key)
18
+ when "zset"
19
+ total_size, items = conn.multi { |transaction|
20
+ transaction.zcard(key)
21
21
  if rev
22
- conn.zrevrange(key, starting, ending, :with_scores => true)
22
+ transaction.zrange(key, starting, ending, "REV", "withscores")
23
23
  else
24
- conn.zrange(key, starting, ending, :with_scores => true)
24
+ transaction.zrange(key, starting, ending, "withscores")
25
25
  end
26
- end
26
+ }
27
27
  [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
28
+ when "list"
29
+ total_size, items = conn.multi { |transaction|
30
+ transaction.llen(key)
31
+ if rev
32
+ transaction.lrange(key, -ending - 1, -starting - 1)
33
+ else
34
+ transaction.lrange(key, starting, ending)
35
+ end
36
+ }
37
+ items.reverse! if rev
33
38
  [current_page, total_size, items]
34
- when 'none'
39
+ when "none"
35
40
  [1, 0, []]
36
41
  else
37
42
  raise "can't page a #{type}"
@@ -39,5 +44,12 @@ module Sidekiq
39
44
  end
40
45
  end
41
46
 
47
+ def page_items(items, pageidx = 1, page_size = 25)
48
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
49
+ pageidx = current_page - 1
50
+ starting = pageidx * page_size
51
+ items = items.to_a
52
+ [current_page, items.size, items[starting, page_size]]
53
+ end
42
54
  end
43
55
  end