sidekiq 5.2.7 → 7.0.2

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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +465 -5
  3. data/LICENSE.txt +9 -0
  4. data/README.md +32 -42
  5. data/bin/sidekiq +22 -3
  6. data/bin/sidekiqload +80 -77
  7. data/bin/sidekiqmon +8 -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 +506 -351
  13. data/lib/sidekiq/capsule.rb +110 -0
  14. data/lib/sidekiq/cli.rb +202 -226
  15. data/lib/sidekiq/client.rb +104 -95
  16. data/lib/sidekiq/component.rb +66 -0
  17. data/lib/sidekiq/config.rb +270 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +49 -40
  21. data/lib/sidekiq/job.rb +378 -0
  22. data/lib/sidekiq/job_logger.rb +33 -7
  23. data/lib/sidekiq/job_retry.rb +127 -107
  24. data/lib/sidekiq/job_util.rb +71 -0
  25. data/lib/sidekiq/launcher.rb +197 -103
  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 +134 -0
  31. data/lib/sidekiq/middleware/chain.rb +104 -50
  32. data/lib/sidekiq/middleware/current_attributes.rb +58 -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 +148 -0
  36. data/lib/sidekiq/paginator.rb +28 -16
  37. data/lib/sidekiq/processor.rb +105 -107
  38. data/lib/sidekiq/rails.rb +41 -37
  39. data/lib/sidekiq/redis_client_adapter.rb +115 -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 +108 -79
  51. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  52. data/lib/sidekiq/web/helpers.rb +128 -105
  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 +45 -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 +236 -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 +52 -52
  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 +69 -62
  78. data/web/locales/he.yml +65 -64
  79. data/web/locales/hi.yml +59 -59
  80. data/web/locales/it.yml +53 -53
  81. data/web/locales/ja.yml +73 -64
  82. data/web/locales/ko.yml +52 -52
  83. data/web/locales/lt.yml +83 -0
  84. data/web/locales/nb.yml +61 -61
  85. data/web/locales/nl.yml +52 -52
  86. data/web/locales/pl.yml +45 -45
  87. data/web/locales/pt-br.yml +63 -55
  88. data/web/locales/pt.yml +51 -51
  89. data/web/locales/ru.yml +68 -63
  90. data/web/locales/sv.yml +53 -53
  91. data/web/locales/ta.yml +60 -60
  92. data/web/locales/uk.yml +62 -61
  93. data/web/locales/ur.yml +64 -64
  94. data/web/locales/vi.yml +83 -0
  95. data/web/locales/zh-cn.yml +43 -16
  96. data/web/locales/zh-tw.yml +42 -8
  97. data/web/views/_footer.erb +6 -3
  98. data/web/views/_job_info.erb +19 -2
  99. data/web/views/_nav.erb +1 -1
  100. data/web/views/_poll_link.erb +3 -6
  101. data/web/views/_summary.erb +7 -7
  102. data/web/views/busy.erb +74 -22
  103. data/web/views/dashboard.erb +58 -18
  104. data/web/views/dead.erb +3 -3
  105. data/web/views/layout.erb +3 -1
  106. data/web/views/metrics.erb +80 -0
  107. data/web/views/metrics_for_job.erb +69 -0
  108. data/web/views/morgue.erb +10 -7
  109. data/web/views/queue.erb +23 -10
  110. data/web/views/queues.erb +10 -2
  111. data/web/views/retries.erb +12 -9
  112. data/web/views/retry.erb +3 -3
  113. data/web/views/scheduled.erb +6 -3
  114. metadata +84 -69
  115. data/.circleci/config.yml +0 -61
  116. data/.github/contributing.md +0 -32
  117. data/.github/issue_template.md +0 -11
  118. data/.gitignore +0 -15
  119. data/.travis.yml +0 -11
  120. data/3.0-Upgrade.md +0 -70
  121. data/4.0-Upgrade.md +0 -53
  122. data/5.0-Upgrade.md +0 -56
  123. data/COMM-LICENSE +0 -97
  124. data/Ent-Changes.md +0 -238
  125. data/Gemfile +0 -23
  126. data/LICENSE +0 -9
  127. data/Pro-2.0-Upgrade.md +0 -138
  128. data/Pro-3.0-Upgrade.md +0 -44
  129. data/Pro-4.0-Upgrade.md +0 -35
  130. data/Pro-Changes.md +0 -759
  131. data/Rakefile +0 -9
  132. data/bin/sidekiqctl +0 -20
  133. data/code_of_conduct.md +0 -50
  134. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  135. data/lib/sidekiq/core_ext.rb +0 -1
  136. data/lib/sidekiq/ctl.rb +0 -221
  137. data/lib/sidekiq/delay.rb +0 -42
  138. data/lib/sidekiq/exception_handler.rb +0 -29
  139. data/lib/sidekiq/extensions/action_mailer.rb +0 -57
  140. data/lib/sidekiq/extensions/active_record.rb +0 -40
  141. data/lib/sidekiq/extensions/class_methods.rb +0 -40
  142. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
  143. data/lib/sidekiq/logging.rb +0 -122
  144. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
  145. data/lib/sidekiq/util.rb +0 -66
  146. 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,148 @@
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
+ rescue => e
20
+ abort "Couldn't get status: #{e}"
21
+ end
22
+
23
+ def all
24
+ version
25
+ puts
26
+ overview
27
+ puts
28
+ processes
29
+ puts
30
+ queues
31
+ end
32
+
33
+ def version
34
+ puts "Sidekiq #{Sidekiq::VERSION}"
35
+ puts Time.now.utc
36
+ end
37
+
38
+ def overview
39
+ puts "---- Overview ----"
40
+ puts " Processed: #{delimit stats.processed}"
41
+ puts " Failed: #{delimit stats.failed}"
42
+ puts " Busy: #{delimit stats.workers_size}"
43
+ puts " Enqueued: #{delimit stats.enqueued}"
44
+ puts " Retries: #{delimit stats.retry_size}"
45
+ puts " Scheduled: #{delimit stats.scheduled_size}"
46
+ puts " Dead: #{delimit stats.dead_size}"
47
+ end
48
+
49
+ def processes
50
+ puts "---- Processes (#{process_set.size}) ----"
51
+ process_set.each_with_index do |process, index|
52
+ # Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
53
+ #
54
+ # Before:
55
+ # ["default", "critical"]
56
+ #
57
+ # After:
58
+ # {"default" => 1, "critical" => 10}
59
+ queues =
60
+ if process["weights"]
61
+ process["weights"].sort_by { |queue| queue[0] }.map { |queue| queue.join(": ") }
62
+ else
63
+ process["queues"].sort
64
+ end
65
+
66
+ puts "#{process["identity"]} #{tags_for(process)}"
67
+ puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
68
+ puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
69
+ puts " Queues: #{split_multiline(queues, pad: 11)}"
70
+ puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
71
+ puts "" unless (index + 1) == process_set.size
72
+ end
73
+ end
74
+
75
+ def queues
76
+ puts "---- Queues (#{queue_data.size}) ----"
77
+ columns = {
78
+ name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
79
+ size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
80
+ latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
81
+ }
82
+ columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
83
+ puts
84
+ queue_data.each do |q|
85
+ columns.each do |col, (dir, width)|
86
+ print q.send(col).public_send(dir, width)
87
+ end
88
+ puts
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def delimit(number)
95
+ number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
96
+ end
97
+
98
+ def split_multiline(values, opts = {})
99
+ return "none" unless values
100
+ pad = opts[:pad] || 0
101
+ max_length = opts[:max_length] || (80 - pad)
102
+ out = []
103
+ line = ""
104
+ values.each do |value|
105
+ if (line.length + value.length) > max_length
106
+ out << line
107
+ line = " " * pad
108
+ end
109
+ line << value + ", "
110
+ end
111
+ out << line[0..-3]
112
+ out.join("\n")
113
+ end
114
+
115
+ def tags_for(process)
116
+ tags = [
117
+ process["tag"],
118
+ process["labels"],
119
+ ((process["quiet"] == "true") ? "quiet" : nil)
120
+ ].flatten.compact
121
+ tags.any? ? "[#{tags.join("] [")}]" : nil
122
+ end
123
+
124
+ def time_ago(timestamp)
125
+ seconds = Time.now - Time.at(timestamp)
126
+ return "just now" if seconds < 60
127
+ return "a minute ago" if seconds < 120
128
+ return "#{seconds.floor / 60} minutes ago" if seconds < 3600
129
+ return "an hour ago" if seconds < 7200
130
+ "#{seconds.floor / 60 / 60} hours ago"
131
+ end
132
+
133
+ QUEUE_STRUCT = Struct.new(:name, :size, :latency)
134
+ def queue_data
135
+ @queue_data ||= Sidekiq::Queue.all.map { |q|
136
+ QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency))
137
+ }
138
+ end
139
+
140
+ def process_set
141
+ @process_set ||= Sidekiq::ProcessSet.new
142
+ end
143
+
144
+ def stats
145
+ @stats ||= Sidekiq::Stats.new
146
+ end
147
+ end
148
+ 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.zrevrange(key, starting, ending, 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,64 @@ module Sidekiq
143
146
  end
144
147
  end
145
148
 
146
- def process(work)
147
- jobstr = work.job
148
- queue = work.queue_name
149
+ def process(uow)
150
+ jobstr = uow.job
151
+ queue = uow.queue_name
149
152
 
150
153
  # Treat malformed JSON as a special case: job goes straight to the morgue.
151
154
  job_hash = nil
152
155
  begin
153
156
  job_hash = Sidekiq.load_json(jobstr)
154
157
  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
158
+ handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
159
+ now = Time.now.to_f
160
+ redis do |conn|
161
+ conn.multi do |xa|
162
+ xa.zadd("dead", now.to_s, jobstr)
163
+ xa.zremrangebyscore("dead", "-inf", now - @capsule.config[:dead_timeout_in_seconds])
164
+ xa.zremrangebyrank("dead", 0, - @capsule.config[:dead_max_jobs])
165
+ end
166
+ end
167
+ return uow.acknowledge
159
168
  end
160
169
 
161
- ack = true
170
+ ack = false
162
171
  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']))
172
+ dispatch(job_hash, queue, jobstr) do |inst|
173
+ config.server_middleware.invoke(inst, job_hash, queue) do
174
+ execute_job(inst, job_hash["args"])
166
175
  end
167
176
  end
177
+ ack = true
168
178
  rescue Sidekiq::Shutdown
169
179
  # Had to force kill this job because it didn't finish
170
180
  # within the timeout. Don't acknowledge the work since
171
181
  # we didn't properly finish it.
172
- ack = false
173
182
  rescue Sidekiq::JobRetry::Handled => h
174
183
  # this is the common case: job raised error and Sidekiq::JobRetry::Handled
175
184
  # 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 })
185
+ ack = true
186
+ e = h.cause || h
187
+ handle_exception(e, {context: "Job raised exception", job: job_hash})
178
188
  raise e
179
189
  rescue Exception => ex
180
190
  # Unexpected error! This is very bad and indicates an exception that got past
181
191
  # the retry subsystem (e.g. network partition). We won't acknowledge the job
182
192
  # 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
193
+ handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
194
+ raise ex
186
195
  ensure
187
- work.acknowledge if ack
196
+ if ack
197
+ # We don't want a shutdown signal to interrupt job acknowledgment.
198
+ Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
199
+ uow.acknowledge
200
+ end
201
+ end
188
202
  end
189
203
  end
190
204
 
191
- def execute_job(worker, cloned_args)
192
- worker.perform(*cloned_args)
205
+ def execute_job(inst, cloned_args)
206
+ inst.perform(*cloned_args)
193
207
  end
194
208
 
195
209
  # Ruby doesn't provide atomic counters out of the box so we'll
@@ -201,50 +215,53 @@ module Sidekiq
201
215
  @lock = Mutex.new
202
216
  end
203
217
 
204
- def incr(amount=1)
205
- @lock.synchronize { @value = @value + amount }
218
+ def incr(amount = 1)
219
+ @lock.synchronize { @value += amount }
206
220
  end
207
221
 
208
222
  def reset
209
- @lock.synchronize { val = @value; @value = 0; val }
223
+ @lock.synchronize {
224
+ val = @value
225
+ @value = 0
226
+ val
227
+ }
210
228
  end
211
229
  end
212
230
 
213
231
  # jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
214
- class SharedWorkerState
232
+ class SharedWorkState
215
233
  def initialize
216
- @worker_state = {}
234
+ @work_state = {}
217
235
  @lock = Mutex.new
218
236
  end
219
237
 
220
238
  def set(tid, hash)
221
- @lock.synchronize { @worker_state[tid] = hash }
239
+ @lock.synchronize { @work_state[tid] = hash }
222
240
  end
223
241
 
224
242
  def delete(tid)
225
- @lock.synchronize { @worker_state.delete(tid) }
243
+ @lock.synchronize { @work_state.delete(tid) }
226
244
  end
227
245
 
228
246
  def dup
229
- @lock.synchronize { @worker_state.dup }
247
+ @lock.synchronize { @work_state.dup }
230
248
  end
231
249
 
232
250
  def size
233
- @lock.synchronize { @worker_state.size }
251
+ @lock.synchronize { @work_state.size }
234
252
  end
235
253
 
236
254
  def clear
237
- @lock.synchronize { @worker_state.clear }
255
+ @lock.synchronize { @work_state.clear }
238
256
  end
239
257
  end
240
258
 
241
259
  PROCESSED = Counter.new
242
260
  FAILURE = Counter.new
243
- WORKER_STATE = SharedWorkerState.new
261
+ WORK_STATE = SharedWorkState.new
244
262
 
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 })
263
+ def stats(jobstr, queue)
264
+ WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
248
265
 
249
266
  begin
250
267
  yield
@@ -252,28 +269,9 @@ module Sidekiq
252
269
  FAILURE.incr
253
270
  raise
254
271
  ensure
255
- WORKER_STATE.delete(tid)
272
+ WORK_STATE.delete(tid)
256
273
  PROCESSED.incr
257
274
  end
258
275
  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
276
  end
279
277
  end