sidekiq 5.2.10 → 6.5.6

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 (124) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +391 -1
  3. data/LICENSE +3 -3
  4. data/README.md +24 -35
  5. data/bin/sidekiq +27 -3
  6. data/bin/sidekiqload +79 -67
  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 +504 -307
  13. data/lib/sidekiq/cli.rb +190 -206
  14. data/lib/sidekiq/client.rb +77 -81
  15. data/lib/sidekiq/component.rb +65 -0
  16. data/lib/sidekiq/delay.rb +8 -7
  17. data/lib/sidekiq/extensions/action_mailer.rb +13 -22
  18. data/lib/sidekiq/extensions/active_record.rb +13 -10
  19. data/lib/sidekiq/extensions/class_methods.rb +14 -11
  20. data/lib/sidekiq/extensions/generic_proxy.rb +7 -5
  21. data/lib/sidekiq/fetch.rb +50 -40
  22. data/lib/sidekiq/job.rb +13 -0
  23. data/lib/sidekiq/job_logger.rb +33 -7
  24. data/lib/sidekiq/job_retry.rb +126 -106
  25. data/lib/sidekiq/job_util.rb +71 -0
  26. data/lib/sidekiq/launcher.rb +177 -83
  27. data/lib/sidekiq/logger.rb +156 -0
  28. data/lib/sidekiq/manager.rb +40 -41
  29. data/lib/sidekiq/metrics/deploy.rb +47 -0
  30. data/lib/sidekiq/metrics/query.rb +153 -0
  31. data/lib/sidekiq/metrics/shared.rb +94 -0
  32. data/lib/sidekiq/metrics/tracking.rb +134 -0
  33. data/lib/sidekiq/middleware/chain.rb +102 -46
  34. data/lib/sidekiq/middleware/current_attributes.rb +63 -0
  35. data/lib/sidekiq/middleware/i18n.rb +7 -7
  36. data/lib/sidekiq/middleware/modules.rb +21 -0
  37. data/lib/sidekiq/monitor.rb +133 -0
  38. data/lib/sidekiq/paginator.rb +20 -16
  39. data/lib/sidekiq/processor.rb +104 -97
  40. data/lib/sidekiq/rails.rb +47 -37
  41. data/lib/sidekiq/redis_client_adapter.rb +154 -0
  42. data/lib/sidekiq/redis_connection.rb +108 -77
  43. data/lib/sidekiq/ring_buffer.rb +29 -0
  44. data/lib/sidekiq/scheduled.rb +64 -35
  45. data/lib/sidekiq/sd_notify.rb +149 -0
  46. data/lib/sidekiq/systemd.rb +24 -0
  47. data/lib/sidekiq/testing/inline.rb +6 -5
  48. data/lib/sidekiq/testing.rb +68 -58
  49. data/lib/sidekiq/transaction_aware_client.rb +45 -0
  50. data/lib/sidekiq/version.rb +2 -1
  51. data/lib/sidekiq/web/action.rb +15 -11
  52. data/lib/sidekiq/web/application.rb +100 -77
  53. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  54. data/lib/sidekiq/web/helpers.rb +134 -94
  55. data/lib/sidekiq/web/router.rb +23 -19
  56. data/lib/sidekiq/web.rb +65 -105
  57. data/lib/sidekiq/worker.rb +253 -106
  58. data/lib/sidekiq.rb +170 -62
  59. data/sidekiq.gemspec +23 -16
  60. data/web/assets/images/apple-touch-icon.png +0 -0
  61. data/web/assets/javascripts/application.js +112 -61
  62. data/web/assets/javascripts/chart.min.js +13 -0
  63. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  64. data/web/assets/javascripts/dashboard.js +53 -89
  65. data/web/assets/javascripts/graph.js +16 -0
  66. data/web/assets/javascripts/metrics.js +262 -0
  67. data/web/assets/stylesheets/application-dark.css +143 -0
  68. data/web/assets/stylesheets/application-rtl.css +0 -4
  69. data/web/assets/stylesheets/application.css +88 -233
  70. data/web/locales/ar.yml +8 -2
  71. data/web/locales/de.yml +14 -2
  72. data/web/locales/el.yml +43 -19
  73. data/web/locales/en.yml +13 -1
  74. data/web/locales/es.yml +18 -2
  75. data/web/locales/fr.yml +10 -3
  76. data/web/locales/ja.yml +7 -1
  77. data/web/locales/lt.yml +83 -0
  78. data/web/locales/pl.yml +4 -4
  79. data/web/locales/pt-br.yml +27 -9
  80. data/web/locales/ru.yml +4 -0
  81. data/web/locales/vi.yml +83 -0
  82. data/web/views/_footer.erb +1 -1
  83. data/web/views/_job_info.erb +3 -2
  84. data/web/views/_nav.erb +1 -1
  85. data/web/views/_poll_link.erb +2 -5
  86. data/web/views/_summary.erb +7 -7
  87. data/web/views/busy.erb +56 -22
  88. data/web/views/dashboard.erb +23 -14
  89. data/web/views/dead.erb +3 -3
  90. data/web/views/layout.erb +3 -1
  91. data/web/views/metrics.erb +69 -0
  92. data/web/views/metrics_for_job.erb +87 -0
  93. data/web/views/morgue.erb +9 -6
  94. data/web/views/queue.erb +23 -10
  95. data/web/views/queues.erb +10 -2
  96. data/web/views/retries.erb +11 -8
  97. data/web/views/retry.erb +3 -3
  98. data/web/views/scheduled.erb +5 -2
  99. metadata +53 -64
  100. data/.circleci/config.yml +0 -61
  101. data/.github/contributing.md +0 -32
  102. data/.github/issue_template.md +0 -11
  103. data/.gitignore +0 -15
  104. data/.travis.yml +0 -11
  105. data/3.0-Upgrade.md +0 -70
  106. data/4.0-Upgrade.md +0 -53
  107. data/5.0-Upgrade.md +0 -56
  108. data/COMM-LICENSE +0 -97
  109. data/Ent-Changes.md +0 -238
  110. data/Gemfile +0 -19
  111. data/Pro-2.0-Upgrade.md +0 -138
  112. data/Pro-3.0-Upgrade.md +0 -44
  113. data/Pro-4.0-Upgrade.md +0 -35
  114. data/Pro-Changes.md +0 -759
  115. data/Rakefile +0 -9
  116. data/bin/sidekiqctl +0 -20
  117. data/code_of_conduct.md +0 -50
  118. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  119. data/lib/sidekiq/core_ext.rb +0 -1
  120. data/lib/sidekiq/ctl.rb +0 -221
  121. data/lib/sidekiq/exception_handler.rb +0 -29
  122. data/lib/sidekiq/logging.rb +0 -122
  123. data/lib/sidekiq/middleware/server/active_record.rb +0 -23
  124. data/lib/sidekiq/util.rb +0 -66
@@ -0,0 +1,63 @@
1
+ require "active_support/current_attributes"
2
+
3
+ module Sidekiq
4
+ ##
5
+ # Automatically save and load any current attributes in the execution context
6
+ # so context attributes "flow" from Rails actions into any associated jobs.
7
+ # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
8
+ # per-request attribute. See +ActiveSupport::CurrentAttributes+.
9
+ #
10
+ # @example
11
+ #
12
+ # # in your initializer
13
+ # require "sidekiq/middleware/current_attributes"
14
+ # Sidekiq::CurrentAttributes.persist(Myapp::Current)
15
+ #
16
+ module CurrentAttributes
17
+ class Save
18
+ include Sidekiq::ClientMiddleware
19
+
20
+ def initialize(cattr)
21
+ @klass = cattr
22
+ end
23
+
24
+ def call(_, job, _, _)
25
+ attrs = @klass.attributes
26
+ if attrs.any?
27
+ if job.has_key?("cattr")
28
+ job["cattr"].merge!(attrs)
29
+ else
30
+ job["cattr"] = attrs
31
+ end
32
+ end
33
+ yield
34
+ end
35
+ end
36
+
37
+ class Load
38
+ include Sidekiq::ServerMiddleware
39
+
40
+ def initialize(cattr)
41
+ @klass = cattr
42
+ end
43
+
44
+ def call(_, job, _, &block)
45
+ if job.has_key?("cattr")
46
+ @klass.set(job["cattr"], &block)
47
+ else
48
+ yield
49
+ end
50
+ end
51
+ end
52
+
53
+ def self.persist(klass)
54
+ Sidekiq.configure_client do |config|
55
+ config.client_middleware.add Save, klass
56
+ end
57
+ Sidekiq.configure_server do |config|
58
+ config.client_middleware.add Save, klass
59
+ config.server_middleware.add Load, klass
60
+ end
61
+ end
62
+ end
63
+ 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,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,133 @@
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
+ puts "#{process["identity"]} #{tags_for(process)}"
53
+ puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
54
+ puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
55
+ puts " Queues: #{split_multiline(process["queues"].sort, pad: 11)}"
56
+ puts "" unless (index + 1) == process_set.size
57
+ end
58
+ end
59
+
60
+ def queues
61
+ puts "---- Queues (#{queue_data.size}) ----"
62
+ columns = {
63
+ name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
64
+ size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
65
+ latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
66
+ }
67
+ columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
68
+ puts
69
+ queue_data.each do |q|
70
+ columns.each do |col, (dir, width)|
71
+ print q.send(col).public_send(dir, width)
72
+ end
73
+ puts
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def delimit(number)
80
+ number.to_s.reverse.scan(/.{1,3}/).join(",").reverse
81
+ end
82
+
83
+ def split_multiline(values, opts = {})
84
+ return "none" unless values
85
+ pad = opts[:pad] || 0
86
+ max_length = opts[:max_length] || (80 - pad)
87
+ out = []
88
+ line = ""
89
+ values.each do |value|
90
+ if (line.length + value.length) > max_length
91
+ out << line
92
+ line = " " * pad
93
+ end
94
+ line << value + ", "
95
+ end
96
+ out << line[0..-3]
97
+ out.join("\n")
98
+ end
99
+
100
+ def tags_for(process)
101
+ tags = [
102
+ process["tag"],
103
+ process["labels"],
104
+ (process["quiet"] == "true" ? "quiet" : nil)
105
+ ].flatten.compact
106
+ tags.any? ? "[#{tags.join("] [")}]" : nil
107
+ end
108
+
109
+ def time_ago(timestamp)
110
+ seconds = Time.now - Time.at(timestamp)
111
+ return "just now" if seconds < 60
112
+ return "a minute ago" if seconds < 120
113
+ return "#{seconds.floor / 60} minutes ago" if seconds < 3600
114
+ return "an hour ago" if seconds < 7200
115
+ "#{seconds.floor / 60 / 60} hours ago"
116
+ end
117
+
118
+ QUEUE_STRUCT = Struct.new(:name, :size, :latency)
119
+ def queue_data
120
+ @queue_data ||= Sidekiq::Queue.all.map { |q|
121
+ QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency))
122
+ }
123
+ end
124
+
125
+ def process_set
126
+ @process_set ||= Sidekiq::ProcessSet.new
127
+ end
128
+
129
+ def stats
130
+ @stats ||= Sidekiq::Stats.new
131
+ end
132
+ end
133
+ end
@@ -1,8 +1,8 @@
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)
5
+ def page(key, pageidx = 1, page_size = 25, opts = nil)
6
6
  current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
7
7
  pageidx = current_page - 1
8
8
  total_size = 0
@@ -12,32 +12,36 @@ 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}"
38
43
  end
39
44
  end
40
45
  end
41
-
42
46
  end
43
47
  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
31
29
 
32
- def initialize(mgr)
33
- @mgr = mgr
30
+ def initialize(options, &block)
31
+ @callback = block
34
32
  @down = false
35
33
  @done = false
36
34
  @job = nil
37
35
  @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
36
+ @config = options
37
+ @strategy = options[:fetch]
38
+ @reloader = options[:reloader] || proc { |&block| block.call }
39
+ @job_logger = (options[:job_logger] || Sidekiq::JobLogger).new
40
+ @retrier = Sidekiq::JobRetry.new(options)
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
@@ -66,33 +65,30 @@ module Sidekiq
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
+ process_one until @done
69
+ @callback.call(self)
70
+ rescue Sidekiq::Shutdown
71
+ @callback.call(self)
72
+ rescue Exception => ex
73
+ @callback.call(self, ex)
79
74
  end
80
75
 
81
- def process_one
76
+ def process_one(&block)
82
77
  @job = fetch
83
78
  process(@job) if @job
84
79
  @job = nil
85
80
  end
86
81
 
87
82
  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)
83
+ uow = @strategy.retrieve_work
84
+ if @down
85
+ logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" }
86
+ @down = nil
95
87
  end
88
+ uow
89
+ rescue Sidekiq::Shutdown
90
+ rescue => ex
91
+ handle_fetch_exception(ex)
96
92
  end
97
93
 
98
94
  def fetch
@@ -106,7 +102,7 @@ module Sidekiq
106
102
  end
107
103
 
108
104
  def handle_fetch_exception(ex)
109
- if !@down
105
+ unless @down
110
106
  @down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
111
107
  logger.error("Error fetching job: #{ex}")
112
108
  handle_exception(ex)
@@ -115,26 +111,29 @@ module Sidekiq
115
111
  nil
116
112
  end
117
113
 
118
- def dispatch(job_hash, queue)
114
+ def dispatch(job_hash, queue, jobstr)
119
115
  # since middleware can mutate the job hash
120
- # we clone here so we report the original
116
+ # we need to clone it to report the original
121
117
  # 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
118
+ # or to push back to redis when retrying.
119
+ # To avoid costly and, most of the time, useless cloning here,
120
+ # we pass original String of JSON to respected methods
121
+ # to re-parse it there if we need access to the original, untouched job
122
+
123
+ @job_logger.prepare(job_hash) do
124
+ @retrier.global(jobstr, queue) do
125
+ @job_logger.call(job_hash, queue) do
126
+ stats(jobstr, queue) do
128
127
  # Rails 5 requires a Reloader to wrap code execution. In order to
129
128
  # constantize the worker and instantiate an instance, we have to call
130
129
  # the Reloader. It handles code loading, db connection management, etc.
131
130
  # Effectively this block denotes a "unit of work" to Rails.
132
131
  @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
132
+ klass = constantize(job_hash["class"])
133
+ inst = klass.new
134
+ inst.jid = job_hash["jid"]
135
+ @retrier.local(inst, jobstr, queue) do
136
+ yield inst
138
137
  end
139
138
  end
140
139
  end
@@ -143,53 +142,64 @@ module Sidekiq
143
142
  end
144
143
  end
145
144
 
146
- def process(work)
147
- jobstr = work.job
148
- queue = work.queue_name
145
+ def process(uow)
146
+ jobstr = uow.job
147
+ queue = uow.queue_name
149
148
 
150
149
  # Treat malformed JSON as a special case: job goes straight to the morgue.
151
150
  job_hash = nil
152
151
  begin
153
152
  job_hash = Sidekiq.load_json(jobstr)
154
153
  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
154
+ handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr})
155
+ now = Time.now.to_f
156
+ config.redis do |conn|
157
+ conn.multi do |xa|
158
+ xa.zadd("dead", now.to_s, jobstr)
159
+ xa.zremrangebyscore("dead", "-inf", now - config[:dead_timeout_in_seconds])
160
+ xa.zremrangebyrank("dead", 0, - config[:dead_max_jobs])
161
+ end
162
+ end
163
+ return uow.acknowledge
159
164
  end
160
165
 
161
- ack = true
166
+ ack = false
162
167
  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']))
168
+ dispatch(job_hash, queue, jobstr) do |inst|
169
+ @config.server_middleware.invoke(inst, job_hash, queue) do
170
+ execute_job(inst, job_hash["args"])
166
171
  end
167
172
  end
173
+ ack = true
168
174
  rescue Sidekiq::Shutdown
169
175
  # Had to force kill this job because it didn't finish
170
176
  # within the timeout. Don't acknowledge the work since
171
177
  # we didn't properly finish it.
172
- ack = false
173
178
  rescue Sidekiq::JobRetry::Handled => h
174
179
  # this is the common case: job raised error and Sidekiq::JobRetry::Handled
175
180
  # 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 })
181
+ ack = true
182
+ e = h.cause || h
183
+ handle_exception(e, {context: "Job raised exception", job: job_hash})
178
184
  raise e
179
185
  rescue Exception => ex
180
186
  # Unexpected error! This is very bad and indicates an exception that got past
181
187
  # the retry subsystem (e.g. network partition). We won't acknowledge the job
182
188
  # 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
189
+ handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr})
190
+ raise ex
186
191
  ensure
187
- work.acknowledge if ack
192
+ if ack
193
+ # We don't want a shutdown signal to interrupt job acknowledgment.
194
+ Thread.handle_interrupt(Sidekiq::Shutdown => :never) do
195
+ uow.acknowledge
196
+ end
197
+ end
188
198
  end
189
199
  end
190
200
 
191
- def execute_job(worker, cloned_args)
192
- worker.perform(*cloned_args)
201
+ def execute_job(inst, cloned_args)
202
+ inst.perform(*cloned_args)
193
203
  end
194
204
 
195
205
  # Ruby doesn't provide atomic counters out of the box so we'll
@@ -201,50 +211,53 @@ module Sidekiq
201
211
  @lock = Mutex.new
202
212
  end
203
213
 
204
- def incr(amount=1)
205
- @lock.synchronize { @value = @value + amount }
214
+ def incr(amount = 1)
215
+ @lock.synchronize { @value += amount }
206
216
  end
207
217
 
208
218
  def reset
209
- @lock.synchronize { val = @value; @value = 0; val }
219
+ @lock.synchronize {
220
+ val = @value
221
+ @value = 0
222
+ val
223
+ }
210
224
  end
211
225
  end
212
226
 
213
227
  # jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here
214
- class SharedWorkerState
228
+ class SharedWorkState
215
229
  def initialize
216
- @worker_state = {}
230
+ @work_state = {}
217
231
  @lock = Mutex.new
218
232
  end
219
233
 
220
234
  def set(tid, hash)
221
- @lock.synchronize { @worker_state[tid] = hash }
235
+ @lock.synchronize { @work_state[tid] = hash }
222
236
  end
223
237
 
224
238
  def delete(tid)
225
- @lock.synchronize { @worker_state.delete(tid) }
239
+ @lock.synchronize { @work_state.delete(tid) }
226
240
  end
227
241
 
228
242
  def dup
229
- @lock.synchronize { @worker_state.dup }
243
+ @lock.synchronize { @work_state.dup }
230
244
  end
231
245
 
232
246
  def size
233
- @lock.synchronize { @worker_state.size }
247
+ @lock.synchronize { @work_state.size }
234
248
  end
235
249
 
236
250
  def clear
237
- @lock.synchronize { @worker_state.clear }
251
+ @lock.synchronize { @work_state.clear }
238
252
  end
239
253
  end
240
254
 
241
255
  PROCESSED = Counter.new
242
256
  FAILURE = Counter.new
243
- WORKER_STATE = SharedWorkerState.new
257
+ WORK_STATE = SharedWorkState.new
244
258
 
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 })
259
+ def stats(jobstr, queue)
260
+ WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i})
248
261
 
249
262
  begin
250
263
  yield
@@ -252,28 +265,22 @@ module Sidekiq
252
265
  FAILURE.incr
253
266
  raise
254
267
  ensure
255
- WORKER_STATE.delete(tid)
268
+ WORK_STATE.delete(tid)
256
269
  PROCESSED.incr
257
270
  end
258
271
  end
259
272
 
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
273
  def constantize(str)
268
- names = str.split('::')
274
+ return Object.const_get(str) unless str.include?("::")
275
+
276
+ names = str.split("::")
269
277
  names.shift if names.empty? || names.first.empty?
270
278
 
271
279
  names.inject(Object) do |constant, name|
272
280
  # the false flag limits search for name to under the constant namespace
273
281
  # which mimics Rails' behaviour
274
- constant.const_defined?(name, false) ? constant.const_get(name, false) : constant.const_missing(name)
282
+ constant.const_get(name, false)
275
283
  end
276
284
  end
277
-
278
285
  end
279
286
  end