sidekiq 5.2.6 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +537 -8
  3. data/LICENSE.txt +9 -0
  4. data/README.md +47 -50
  5. data/bin/sidekiq +22 -3
  6. data/bin/sidekiqload +213 -115
  7. data/bin/sidekiqmon +11 -0
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +556 -351
  13. data/lib/sidekiq/capsule.rb +127 -0
  14. data/lib/sidekiq/cli.rb +203 -226
  15. data/lib/sidekiq/client.rb +121 -101
  16. data/lib/sidekiq/component.rb +68 -0
  17. data/lib/sidekiq/config.rb +274 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +49 -42
  21. data/lib/sidekiq/job.rb +374 -0
  22. data/lib/sidekiq/job_logger.rb +33 -7
  23. data/lib/sidekiq/job_retry.rb +131 -108
  24. data/lib/sidekiq/job_util.rb +105 -0
  25. data/lib/sidekiq/launcher.rb +203 -105
  26. data/lib/sidekiq/logger.rb +131 -0
  27. data/lib/sidekiq/manager.rb +43 -46
  28. data/lib/sidekiq/metrics/query.rb +153 -0
  29. data/lib/sidekiq/metrics/shared.rb +95 -0
  30. data/lib/sidekiq/metrics/tracking.rb +136 -0
  31. data/lib/sidekiq/middleware/chain.rb +113 -56
  32. data/lib/sidekiq/middleware/current_attributes.rb +56 -0
  33. data/lib/sidekiq/middleware/i18n.rb +7 -7
  34. data/lib/sidekiq/middleware/modules.rb +21 -0
  35. data/lib/sidekiq/monitor.rb +146 -0
  36. data/lib/sidekiq/paginator.rb +28 -16
  37. data/lib/sidekiq/processor.rb +108 -107
  38. data/lib/sidekiq/rails.rb +49 -38
  39. data/lib/sidekiq/redis_client_adapter.rb +96 -0
  40. data/lib/sidekiq/redis_connection.rb +38 -107
  41. data/lib/sidekiq/ring_buffer.rb +29 -0
  42. data/lib/sidekiq/scheduled.rb +111 -49
  43. data/lib/sidekiq/sd_notify.rb +149 -0
  44. data/lib/sidekiq/systemd.rb +24 -0
  45. data/lib/sidekiq/testing/inline.rb +6 -5
  46. data/lib/sidekiq/testing.rb +66 -84
  47. data/lib/sidekiq/transaction_aware_client.rb +44 -0
  48. data/lib/sidekiq/version.rb +3 -1
  49. data/lib/sidekiq/web/action.rb +15 -11
  50. data/lib/sidekiq/web/application.rb +123 -79
  51. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  52. data/lib/sidekiq/web/helpers.rb +137 -106
  53. data/lib/sidekiq/web/router.rb +23 -19
  54. data/lib/sidekiq/web.rb +56 -107
  55. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  56. data/lib/sidekiq.rb +92 -182
  57. data/sidekiq.gemspec +25 -16
  58. data/web/assets/images/apple-touch-icon.png +0 -0
  59. data/web/assets/javascripts/application.js +130 -61
  60. data/web/assets/javascripts/base-charts.js +106 -0
  61. data/web/assets/javascripts/chart.min.js +13 -0
  62. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  63. data/web/assets/javascripts/dashboard-charts.js +166 -0
  64. data/web/assets/javascripts/dashboard.js +36 -292
  65. data/web/assets/javascripts/metrics.js +264 -0
  66. data/web/assets/stylesheets/application-dark.css +147 -0
  67. data/web/assets/stylesheets/application-rtl.css +2 -95
  68. data/web/assets/stylesheets/application.css +102 -522
  69. data/web/locales/ar.yml +71 -65
  70. data/web/locales/cs.yml +62 -62
  71. data/web/locales/da.yml +60 -53
  72. data/web/locales/de.yml +65 -53
  73. data/web/locales/el.yml +43 -24
  74. data/web/locales/en.yml +84 -66
  75. data/web/locales/es.yml +70 -54
  76. data/web/locales/fa.yml +65 -65
  77. data/web/locales/fr.yml +83 -62
  78. data/web/locales/gd.yml +99 -0
  79. data/web/locales/he.yml +65 -64
  80. data/web/locales/hi.yml +59 -59
  81. data/web/locales/it.yml +53 -53
  82. data/web/locales/ja.yml +75 -64
  83. data/web/locales/ko.yml +52 -52
  84. data/web/locales/lt.yml +83 -0
  85. data/web/locales/nb.yml +61 -61
  86. data/web/locales/nl.yml +52 -52
  87. data/web/locales/pl.yml +45 -45
  88. data/web/locales/pt-br.yml +63 -55
  89. data/web/locales/pt.yml +51 -51
  90. data/web/locales/ru.yml +68 -63
  91. data/web/locales/sv.yml +53 -53
  92. data/web/locales/ta.yml +60 -60
  93. data/web/locales/uk.yml +62 -61
  94. data/web/locales/ur.yml +64 -64
  95. data/web/locales/vi.yml +83 -0
  96. data/web/locales/zh-cn.yml +43 -16
  97. data/web/locales/zh-tw.yml +42 -8
  98. data/web/views/_footer.erb +6 -3
  99. data/web/views/_job_info.erb +21 -4
  100. data/web/views/_metrics_period_select.erb +12 -0
  101. data/web/views/_nav.erb +1 -1
  102. data/web/views/_paging.erb +2 -0
  103. data/web/views/_poll_link.erb +3 -6
  104. data/web/views/_summary.erb +7 -7
  105. data/web/views/busy.erb +75 -25
  106. data/web/views/dashboard.erb +58 -18
  107. data/web/views/dead.erb +3 -3
  108. data/web/views/layout.erb +3 -1
  109. data/web/views/metrics.erb +82 -0
  110. data/web/views/metrics_for_job.erb +68 -0
  111. data/web/views/morgue.erb +14 -15
  112. data/web/views/queue.erb +33 -24
  113. data/web/views/queues.erb +13 -3
  114. data/web/views/retries.erb +16 -17
  115. data/web/views/retry.erb +3 -3
  116. data/web/views/scheduled.erb +17 -15
  117. metadata +69 -69
  118. data/.github/contributing.md +0 -32
  119. data/.github/issue_template.md +0 -11
  120. data/.gitignore +0 -15
  121. data/.travis.yml +0 -11
  122. data/3.0-Upgrade.md +0 -70
  123. data/4.0-Upgrade.md +0 -53
  124. data/5.0-Upgrade.md +0 -56
  125. data/COMM-LICENSE +0 -97
  126. data/Ent-Changes.md +0 -238
  127. data/Gemfile +0 -23
  128. data/LICENSE +0 -9
  129. data/Pro-2.0-Upgrade.md +0 -138
  130. data/Pro-3.0-Upgrade.md +0 -44
  131. data/Pro-4.0-Upgrade.md +0 -35
  132. data/Pro-Changes.md +0 -759
  133. data/Rakefile +0 -9
  134. data/bin/sidekiqctl +0 -20
  135. data/code_of_conduct.md +0 -50
  136. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  137. data/lib/sidekiq/core_ext.rb +0 -1
  138. data/lib/sidekiq/ctl.rb +0 -221
  139. data/lib/sidekiq/delay.rb +0 -42
  140. data/lib/sidekiq/exception_handler.rb +0 -29
  141. data/lib/sidekiq/extensions/action_mailer.rb +0 -57
  142. data/lib/sidekiq/extensions/active_record.rb +0 -40
  143. data/lib/sidekiq/extensions/class_methods.rb +0 -40
  144. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
  145. data/lib/sidekiq/logging.rb +0 -122
  146. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
  147. data/lib/sidekiq/util.rb +0 -66
  148. data/lib/sidekiq/worker.rb +0 -220
@@ -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,21 @@
1
+ module Sidekiq
2
+ # Server-side middleware must import this Module in order
3
+ # to get access to server resources during `call`.
4
+ module ServerMiddleware
5
+ attr_accessor :config
6
+ def redis_pool
7
+ config.redis_pool
8
+ end
9
+
10
+ def logger
11
+ config.logger
12
+ end
13
+
14
+ def redis(&block)
15
+ config.redis(&block)
16
+ end
17
+ end
18
+
19
+ # no difference for now
20
+ ClientMiddleware = ServerMiddleware
21
+ end
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "fileutils"
4
+ require "sidekiq/api"
5
+
6
+ class Sidekiq::Monitor
7
+ class Status
8
+ VALID_SECTIONS = %w[all version overview processes queues]
9
+ COL_PAD = 2
10
+
11
+ def display(section = nil)
12
+ section ||= "all"
13
+ unless VALID_SECTIONS.include? section
14
+ puts "I don't know how to check the status of '#{section}'!"
15
+ puts "Try one of these: #{VALID_SECTIONS.join(", ")}"
16
+ return
17
+ end
18
+ send(section)
19
+ end
20
+
21
+ def all
22
+ version
23
+ puts
24
+ overview
25
+ puts
26
+ processes
27
+ puts
28
+ queues
29
+ end
30
+
31
+ def version
32
+ puts "Sidekiq #{Sidekiq::VERSION}"
33
+ puts Time.now.utc
34
+ end
35
+
36
+ def overview
37
+ puts "---- Overview ----"
38
+ puts " Processed: #{delimit stats.processed}"
39
+ puts " Failed: #{delimit stats.failed}"
40
+ puts " Busy: #{delimit stats.workers_size}"
41
+ puts " Enqueued: #{delimit stats.enqueued}"
42
+ puts " Retries: #{delimit stats.retry_size}"
43
+ puts " Scheduled: #{delimit stats.scheduled_size}"
44
+ puts " Dead: #{delimit stats.dead_size}"
45
+ end
46
+
47
+ def processes
48
+ puts "---- Processes (#{process_set.size}) ----"
49
+ process_set.each_with_index do |process, index|
50
+ # Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
51
+ #
52
+ # Before:
53
+ # ["default", "critical"]
54
+ #
55
+ # After:
56
+ # {"default" => 1, "critical" => 10}
57
+ queues =
58
+ if process["weights"]
59
+ process["weights"].sort_by { |queue| queue[0] }.map { |capsule| capsule.map { |name, weight| (weight > 0) ? "#{name}: #{weight}" : name }.join(", ") }
60
+ else
61
+ process["queues"].sort
62
+ end
63
+
64
+ puts "#{process["identity"]} #{tags_for(process)}"
65
+ puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
66
+ puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
67
+ puts " Queues: #{split_multiline(queues, pad: 11)}"
68
+ puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
69
+ puts "" unless (index + 1) == process_set.size
70
+ end
71
+ end
72
+
73
+ def queues
74
+ puts "---- Queues (#{queue_data.size}) ----"
75
+ columns = {
76
+ name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
77
+ size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
78
+ latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
79
+ }
80
+ columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
81
+ puts
82
+ queue_data.each do |q|
83
+ columns.each do |col, (dir, width)|
84
+ print q.send(col).public_send(dir, width)
85
+ end
86
+ puts
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def delimit(number)
93
+ number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
94
+ end
95
+
96
+ def split_multiline(values, opts = {})
97
+ return "none" unless values
98
+ pad = opts[:pad] || 0
99
+ max_length = opts[:max_length] || (80 - pad)
100
+ out = []
101
+ line = ""
102
+ values.each do |value|
103
+ if (line.length + value.length) > max_length
104
+ out << line
105
+ line = " " * pad
106
+ end
107
+ line << value + ", "
108
+ end
109
+ out << line[0..-3]
110
+ out.join("\n")
111
+ end
112
+
113
+ def tags_for(process)
114
+ tags = [
115
+ process["tag"],
116
+ process["labels"],
117
+ ((process["quiet"] == "true") ? "quiet" : nil)
118
+ ].flatten.compact
119
+ tags.any? ? "[#{tags.join("] [")}]" : nil
120
+ end
121
+
122
+ def time_ago(timestamp)
123
+ seconds = Time.now - Time.at(timestamp)
124
+ return "just now" if seconds < 60
125
+ return "a minute ago" if seconds < 120
126
+ return "#{seconds.floor / 60} minutes ago" if seconds < 3600
127
+ return "an hour ago" if seconds < 7200
128
+ "#{seconds.floor / 60 / 60} hours ago"
129
+ end
130
+
131
+ QUEUE_STRUCT = Struct.new(:name, :size, :latency)
132
+ def queue_data
133
+ @queue_data ||= Sidekiq::Queue.all.map { |q|
134
+ QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency))
135
+ }
136
+ end
137
+
138
+ def process_set
139
+ @process_set ||= Sidekiq::ProcessSet.new
140
+ end
141
+
142
+ def stats
143
+ @stats ||= Sidekiq::Stats.new
144
+ end
145
+ end
146
+ 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: true)
23
23
  else
24
- conn.zrange(key, starting, ending, :with_scores => true)
24
+ transaction.zrange(key, starting, ending, withscores: true)
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
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/util'
3
- require 'sidekiq/fetch'
4
- require 'sidekiq/job_logger'
5
- require 'sidekiq/job_retry'
6
- require 'thread'
2
+
3
+ require "sidekiq/fetch"
4
+ require "sidekiq/job_logger"
5
+ require "sidekiq/job_retry"
7
6
 
8
7
  module Sidekiq
9
8
  ##
@@ -11,45 +10,45 @@ module Sidekiq
11
10
  #
12
11
  # 1. fetches a job from Redis
13
12
  # 2. executes the job
14
- # a. instantiate the Worker
13
+ # a. instantiate the job class
15
14
  # b. run the middleware chain
16
15
  # c. call #perform
17
16
  #
18
- # A Processor can exit due to shutdown (processor_stopped)
19
- # or due to an error during job execution (processor_died)
17
+ # A Processor can exit due to shutdown or due to
18
+ # an error during job execution.
20
19
  #
21
20
  # If an error occurs in the job execution, the
22
21
  # Processor calls the Manager to create a new one
23
22
  # to replace itself and exits.
24
23
  #
25
24
  class Processor
26
-
27
- include Util
25
+ include Sidekiq::Component
28
26
 
29
27
  attr_reader :thread
30
28
  attr_reader :job
29
+ attr_reader :capsule
31
30
 
32
- def initialize(mgr)
33
- @mgr = mgr
31
+ def initialize(capsule, &block)
32
+ @config = @capsule = capsule
33
+ @callback = block
34
34
  @down = false
35
35
  @done = false
36
36
  @job = nil
37
37
  @thread = nil
38
- @strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
39
- @reloader = Sidekiq.options[:reloader]
40
- @logging = (mgr.options[:job_logger] || Sidekiq::JobLogger).new
41
- @retrier = Sidekiq::JobRetry.new
38
+ @reloader = Sidekiq.default_configuration[:reloader]
39
+ @job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(logger)
40
+ @retrier = Sidekiq::JobRetry.new(capsule)
42
41
  end
43
42
 
44
- def terminate(wait=false)
43
+ def terminate(wait = false)
45
44
  @done = true
46
- return if !@thread
45
+ return unless @thread
47
46
  @thread.value if wait
48
47
  end
49
48
 
50
- def kill(wait=false)
49
+ def kill(wait = false)
51
50
  @done = true
52
- return if !@thread
51
+ return unless @thread
53
52
  # unlike the other actors, terminate does not wait
54
53
  # for the thread to finish because we don't know how
55
54
  # long the job will take to finish. Instead we
@@ -60,39 +59,40 @@ module Sidekiq
60
59
  end
61
60
 
62
61
  def start
63
- @thread ||= safe_thread("processor", &method(:run))
62
+ @thread ||= safe_thread("#{config.name}/processor", &method(:run))
64
63
  end
65
64
 
66
65
  private unless $TESTING
67
66
 
68
67
  def run
69
- begin
70
- while !@done
71
- process_one
72
- end
73
- @mgr.processor_stopped(self)
74
- rescue Sidekiq::Shutdown
75
- @mgr.processor_stopped(self)
76
- rescue Exception => ex
77
- @mgr.processor_died(self, ex)
78
- end
68
+ # By setting this thread-local, Sidekiq.redis will access +Sidekiq::Capsule#redis_pool+
69
+ # instead of the global pool in +Sidekiq::Config#redis_pool+.
70
+ Thread.current[:sidekiq_capsule] = @capsule
71
+
72
+ process_one until @done
73
+ @callback.call(self)
74
+ rescue Sidekiq::Shutdown
75
+ @callback.call(self)
76
+ rescue Exception => ex
77
+ @callback.call(self, ex)
79
78
  end
80
79
 
81
- def process_one
80
+ def process_one(&block)
82
81
  @job = fetch
83
82
  process(@job) if @job
84
83
  @job = nil
85
84
  end
86
85
 
87
86
  def get_one
88
- begin
89
- work = @strategy.retrieve_work
90
- (logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }; @down = nil) if @down
91
- work
92
- rescue Sidekiq::Shutdown
93
- rescue => ex
94
- handle_fetch_exception(ex)
87
+ uow = capsule.fetcher.retrieve_work
88
+ if @down
89
+ logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
90
+ @down = nil
95
91
  end
92
+ uow
93
+ rescue Sidekiq::Shutdown
94
+ rescue => ex
95
+ handle_fetch_exception(ex)
96
96
  end
97
97
 
98
98
  def fetch
@@ -106,7 +106,7 @@ module Sidekiq
106
106
  end
107
107
 
108
108
  def handle_fetch_exception(ex)
109
- if !@down
109
+ unless @down
110
110
  @down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
111
111
  logger.error("Error fetching job: #{ex}")
112
112
  handle_exception(ex)
@@ -115,26 +115,29 @@ module Sidekiq
115
115
  nil
116
116
  end
117
117
 
118
- def dispatch(job_hash, queue)
118
+ def dispatch(job_hash, queue, jobstr)
119
119
  # since middleware can mutate the job hash
120
- # we clone here so we report the original
120
+ # we need to clone it to report the original
121
121
  # job structure to the Web UI
122
- pristine = cloned(job_hash)
123
-
124
- Sidekiq::Logging.with_job_hash_context(job_hash) do
125
- @retrier.global(pristine, queue) do
126
- @logging.call(job_hash, queue) do
127
- stats(pristine, queue) do
122
+ # or to push back to redis when retrying.
123
+ # To avoid costly and, most of the time, useless cloning here,
124
+ # we pass original String of JSON to respected methods
125
+ # to re-parse it there if we need access to the original, untouched job
126
+
127
+ @job_logger.prepare(job_hash) do
128
+ @retrier.global(jobstr, queue) do
129
+ @job_logger.call(job_hash, queue) do
130
+ stats(jobstr, queue) do
128
131
  # Rails 5 requires a Reloader to wrap code execution. In order to
129
132
  # constantize the worker and instantiate an instance, we have to call
130
133
  # the Reloader. It handles code loading, db connection management, etc.
131
134
  # Effectively this block denotes a "unit of work" to Rails.
132
135
  @reloader.call do
133
- klass = constantize(job_hash['class'])
134
- worker = klass.new
135
- worker.jid = job_hash['jid']
136
- @retrier.local(worker, pristine, queue) do
137
- yield worker
136
+ klass = Object.const_get(job_hash["class"])
137
+ inst = klass.new
138
+ inst.jid = job_hash["jid"]
139
+ @retrier.local(inst, jobstr, queue) do
140
+ yield inst
138
141
  end
139
142
  end
140
143
  end
@@ -143,53 +146,67 @@ module Sidekiq
143
146
  end
144
147
  end
145
148
 
146
- def process(work)
147
- jobstr = work.job
148
- queue = work.queue_name
149
+ IGNORE_SHUTDOWN_INTERRUPTS = {Sidekiq::Shutdown => :never}
150
+ private_constant :IGNORE_SHUTDOWN_INTERRUPTS
151
+
152
+ def process(uow)
153
+ jobstr = uow.job
154
+ queue = uow.queue_name
149
155
 
150
156
  # Treat malformed JSON as a special case: job goes straight to the morgue.
151
157
  job_hash = nil
152
158
  begin
153
159
  job_hash = Sidekiq.load_json(jobstr)
154
160
  rescue => ex
155
- handle_exception(ex, { :context => "Invalid JSON for job", :jobstr => jobstr })
156
- # we can't notify because the job isn't a valid hash payload.
157
- DeadSet.new.kill(jobstr, notify_failure: false)
158
- return work.acknowledge
161
+ handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
162
+ now = Time.now.to_f
163
+ redis do |conn|
164
+ conn.multi do |xa|
165
+ xa.zadd("dead", now.to_s, jobstr)
166
+ xa.zremrangebyscore("dead", "-inf", now - @capsule.config[:dead_timeout_in_seconds])
167
+ xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
168
+ end
169
+ end
170
+ return uow.acknowledge
159
171
  end
160
172
 
161
- ack = true
173
+ ack = false
162
174
  begin
163
- dispatch(job_hash, queue) do |worker|
164
- Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
165
- execute_job(worker, cloned(job_hash['args']))
175
+ dispatch(job_hash, queue, jobstr) do |inst|
176
+ config.server_middleware.invoke(inst, job_hash, queue) do
177
+ execute_job(inst, job_hash["args"])
166
178
  end
167
179
  end
180
+ ack = true
168
181
  rescue Sidekiq::Shutdown
169
182
  # Had to force kill this job because it didn't finish
170
183
  # within the timeout. Don't acknowledge the work since
171
184
  # we didn't properly finish it.
172
- ack = false
173
185
  rescue Sidekiq::JobRetry::Handled => h
174
186
  # this is the common case: job raised error and Sidekiq::JobRetry::Handled
175
187
  # signals that we created a retry successfully. We can acknowlege the job.
176
- e = h.cause ? h.cause : h
177
- handle_exception(e, { :context => "Job raised exception", :job => job_hash, :jobstr => jobstr })
188
+ ack = true
189
+ e = h.cause || h
190
+ handle_exception(e, {context: "Job raised exception", job: job_hash})
178
191
  raise e
179
192
  rescue Exception => ex
180
193
  # Unexpected error! This is very bad and indicates an exception that got past
181
194
  # the retry subsystem (e.g. network partition). We won't acknowledge the job
182
195
  # so it can be rescued when using Sidekiq Pro.
183
- ack = false
184
- handle_exception(ex, { :context => "Internal exception!", :job => job_hash, :jobstr => jobstr })
185
- raise e
196
+ handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
197
+ raise ex
186
198
  ensure
187
- work.acknowledge if ack
199
+ if ack
200
+ # We don't want a shutdown signal to interrupt job acknowledgment.
201
+ Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
202
+ uow.acknowledge
203
+ end
204
+ end
188
205
  end
189
206
  end
190
207
 
191
- def execute_job(worker, cloned_args)
192
- worker.perform(*cloned_args)
208
+ def execute_job(inst, cloned_args)
209
+ inst.perform(*cloned_args)
193
210
  end
194
211
 
195
212
  # Ruby doesn't provide atomic counters out of the box so we'll
@@ -201,50 +218,53 @@ module Sidekiq
201
218
  @lock = Mutex.new
202
219
  end
203
220
 
204
- def incr(amount=1)
205
- @lock.synchronize { @value = @value + amount }
221
+ def incr(amount = 1)
222
+ @lock.synchronize { @value += amount }
206
223
  end
207
224
 
208
225
  def reset
209
- @lock.synchronize { val = @value; @value = 0; val }
226
+ @lock.synchronize {
227
+ val = @value
228
+ @value = 0
229
+ val
230
+ }
210
231
  end
211
232
  end
212
233
 
213
234
  # jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
214
- class SharedWorkerState
235
+ class SharedWorkState
215
236
  def initialize
216
- @worker_state = {}
237
+ @work_state = {}
217
238
  @lock = Mutex.new
218
239
  end
219
240
 
220
241
  def set(tid, hash)
221
- @lock.synchronize { @worker_state[tid] = hash }
242
+ @lock.synchronize { @work_state[tid] = hash }
222
243
  end
223
244
 
224
245
  def delete(tid)
225
- @lock.synchronize { @worker_state.delete(tid) }
246
+ @lock.synchronize { @work_state.delete(tid) }
226
247
  end
227
248
 
228
249
  def dup
229
- @lock.synchronize { @worker_state.dup }
250
+ @lock.synchronize { @work_state.dup }
230
251
  end
231
252
 
232
253
  def size
233
- @lock.synchronize { @worker_state.size }
254
+ @lock.synchronize { @work_state.size }
234
255
  end
235
256
 
236
257
  def clear
237
- @lock.synchronize { @worker_state.clear }
258
+ @lock.synchronize { @work_state.clear }
238
259
  end
239
260
  end
240
261
 
241
262
  PROCESSED = Counter.new
242
263
  FAILURE = Counter.new
243
- WORKER_STATE = SharedWorkerState.new
264
+ WORK_STATE = SharedWorkState.new
244
265
 
245
- def stats(job_hash, queue)
246
- tid = Sidekiq::Logging.tid
247
- WORKER_STATE.set(tid, {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i })
266
+ def stats(jobstr, queue)
267
+ WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
248
268
 
249
269
  begin
250
270
  yield
@@ -252,28 +272,9 @@ module Sidekiq
252
272
  FAILURE.incr
253
273
  raise
254
274
  ensure
255
- WORKER_STATE.delete(tid)
275
+ WORK_STATE.delete(tid)
256
276
  PROCESSED.incr
257
277
  end
258
278
  end
259
-
260
- # Deep clone the arguments passed to the worker so that if
261
- # the job fails, what is pushed back onto Redis hasn't
262
- # been mutated by the worker.
263
- def cloned(thing)
264
- Marshal.load(Marshal.dump(thing))
265
- end
266
-
267
- def constantize(str)
268
- names = str.split('::')
269
- names.shift if names.empty? || names.first.empty?
270
-
271
- names.inject(Object) do |constant, name|
272
- # the false flag limits search for name to under the constant namespace
273
- # which mimics Rails' behaviour
274
- constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
275
- end
276
- end
277
-
278
279
  end
279
280
  end