sidekiq 5.0.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 (116) hide show
  1. checksums.yaml +7 -0
  2. data/.github/contributing.md +32 -0
  3. data/.github/issue_template.md +9 -0
  4. data/.gitignore +13 -0
  5. data/.travis.yml +18 -0
  6. data/3.0-Upgrade.md +70 -0
  7. data/4.0-Upgrade.md +53 -0
  8. data/5.0-Upgrade.md +56 -0
  9. data/COMM-LICENSE +95 -0
  10. data/Changes.md +1402 -0
  11. data/Ent-Changes.md +174 -0
  12. data/Gemfile +29 -0
  13. data/LICENSE +9 -0
  14. data/Pro-2.0-Upgrade.md +138 -0
  15. data/Pro-3.0-Upgrade.md +44 -0
  16. data/Pro-Changes.md +632 -0
  17. data/README.md +107 -0
  18. data/Rakefile +12 -0
  19. data/bin/sidekiq +18 -0
  20. data/bin/sidekiqctl +99 -0
  21. data/bin/sidekiqload +149 -0
  22. data/code_of_conduct.md +50 -0
  23. data/lib/generators/sidekiq/templates/worker.rb.erb +9 -0
  24. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +6 -0
  25. data/lib/generators/sidekiq/templates/worker_test.rb.erb +8 -0
  26. data/lib/generators/sidekiq/worker_generator.rb +49 -0
  27. data/lib/sidekiq.rb +228 -0
  28. data/lib/sidekiq/api.rb +871 -0
  29. data/lib/sidekiq/cli.rb +413 -0
  30. data/lib/sidekiq/client.rb +238 -0
  31. data/lib/sidekiq/core_ext.rb +119 -0
  32. data/lib/sidekiq/delay.rb +21 -0
  33. data/lib/sidekiq/exception_handler.rb +31 -0
  34. data/lib/sidekiq/extensions/action_mailer.rb +57 -0
  35. data/lib/sidekiq/extensions/active_record.rb +40 -0
  36. data/lib/sidekiq/extensions/class_methods.rb +40 -0
  37. data/lib/sidekiq/extensions/generic_proxy.rb +31 -0
  38. data/lib/sidekiq/fetch.rb +81 -0
  39. data/lib/sidekiq/job_logger.rb +27 -0
  40. data/lib/sidekiq/job_retry.rb +235 -0
  41. data/lib/sidekiq/launcher.rb +167 -0
  42. data/lib/sidekiq/logging.rb +106 -0
  43. data/lib/sidekiq/manager.rb +138 -0
  44. data/lib/sidekiq/middleware/chain.rb +150 -0
  45. data/lib/sidekiq/middleware/i18n.rb +42 -0
  46. data/lib/sidekiq/middleware/server/active_record.rb +22 -0
  47. data/lib/sidekiq/paginator.rb +43 -0
  48. data/lib/sidekiq/processor.rb +238 -0
  49. data/lib/sidekiq/rails.rb +60 -0
  50. data/lib/sidekiq/redis_connection.rb +106 -0
  51. data/lib/sidekiq/scheduled.rb +147 -0
  52. data/lib/sidekiq/testing.rb +324 -0
  53. data/lib/sidekiq/testing/inline.rb +29 -0
  54. data/lib/sidekiq/util.rb +63 -0
  55. data/lib/sidekiq/version.rb +4 -0
  56. data/lib/sidekiq/web.rb +213 -0
  57. data/lib/sidekiq/web/action.rb +89 -0
  58. data/lib/sidekiq/web/application.rb +331 -0
  59. data/lib/sidekiq/web/helpers.rb +286 -0
  60. data/lib/sidekiq/web/router.rb +100 -0
  61. data/lib/sidekiq/worker.rb +144 -0
  62. data/sidekiq.gemspec +32 -0
  63. data/web/assets/images/favicon.ico +0 -0
  64. data/web/assets/images/logo.png +0 -0
  65. data/web/assets/images/status.png +0 -0
  66. data/web/assets/javascripts/application.js +92 -0
  67. data/web/assets/javascripts/dashboard.js +298 -0
  68. data/web/assets/stylesheets/application-rtl.css +246 -0
  69. data/web/assets/stylesheets/application.css +1111 -0
  70. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  71. data/web/assets/stylesheets/bootstrap.css +5 -0
  72. data/web/locales/ar.yml +80 -0
  73. data/web/locales/cs.yml +78 -0
  74. data/web/locales/da.yml +68 -0
  75. data/web/locales/de.yml +69 -0
  76. data/web/locales/el.yml +68 -0
  77. data/web/locales/en.yml +79 -0
  78. data/web/locales/es.yml +69 -0
  79. data/web/locales/fa.yml +80 -0
  80. data/web/locales/fr.yml +78 -0
  81. data/web/locales/he.yml +79 -0
  82. data/web/locales/hi.yml +75 -0
  83. data/web/locales/it.yml +69 -0
  84. data/web/locales/ja.yml +78 -0
  85. data/web/locales/ko.yml +68 -0
  86. data/web/locales/nb.yml +77 -0
  87. data/web/locales/nl.yml +68 -0
  88. data/web/locales/pl.yml +59 -0
  89. data/web/locales/pt-br.yml +68 -0
  90. data/web/locales/pt.yml +67 -0
  91. data/web/locales/ru.yml +78 -0
  92. data/web/locales/sv.yml +68 -0
  93. data/web/locales/ta.yml +75 -0
  94. data/web/locales/uk.yml +76 -0
  95. data/web/locales/ur.yml +80 -0
  96. data/web/locales/zh-cn.yml +68 -0
  97. data/web/locales/zh-tw.yml +68 -0
  98. data/web/views/_footer.erb +17 -0
  99. data/web/views/_job_info.erb +88 -0
  100. data/web/views/_nav.erb +66 -0
  101. data/web/views/_paging.erb +23 -0
  102. data/web/views/_poll_link.erb +7 -0
  103. data/web/views/_status.erb +4 -0
  104. data/web/views/_summary.erb +40 -0
  105. data/web/views/busy.erb +94 -0
  106. data/web/views/dashboard.erb +75 -0
  107. data/web/views/dead.erb +34 -0
  108. data/web/views/layout.erb +40 -0
  109. data/web/views/morgue.erb +75 -0
  110. data/web/views/queue.erb +45 -0
  111. data/web/views/queues.erb +28 -0
  112. data/web/views/retries.erb +76 -0
  113. data/web/views/retry.erb +34 -0
  114. data/web/views/scheduled.erb +54 -0
  115. data/web/views/scheduled_job_info.erb +8 -0
  116. metadata +366 -0
@@ -0,0 +1,138 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+ require 'sidekiq/util'
4
+ require 'sidekiq/processor'
5
+ require 'sidekiq/fetch'
6
+ require 'thread'
7
+ require 'set'
8
+
9
+ module Sidekiq
10
+
11
+ ##
12
+ # The Manager is the central coordination point in Sidekiq, controlling
13
+ # the lifecycle of the Processors.
14
+ #
15
+ # Tasks:
16
+ #
17
+ # 1. start: Spin up Processors.
18
+ # 3. processor_died: Handle job failure, throw away Processor, create new one.
19
+ # 4. quiet: shutdown idle Processors.
20
+ # 5. stop: hard stop the Processors by deadline.
21
+ #
22
+ # Note that only the last task requires its own Thread since it has to monitor
23
+ # the shutdown process. The other tasks are performed by other threads.
24
+ #
25
+ class Manager
26
+ include Util
27
+
28
+ attr_reader :workers
29
+ attr_reader :options
30
+
31
+ def initialize(options={})
32
+ logger.debug { options.inspect }
33
+ @options = options
34
+ @count = options[:concurrency] || 25
35
+ raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1
36
+
37
+ @done = false
38
+ @workers = Set.new
39
+ @count.times do
40
+ @workers << Processor.new(self)
41
+ end
42
+ @plock = Mutex.new
43
+ end
44
+
45
+ def start
46
+ @workers.each do |x|
47
+ x.start
48
+ end
49
+ end
50
+
51
+ def quiet
52
+ return if @done
53
+ @done = true
54
+
55
+ logger.info { "Terminating quiet workers" }
56
+ @workers.each { |x| x.terminate }
57
+ fire_event(:quiet, true)
58
+ end
59
+
60
+ # hack for quicker development / testing environment #2774
61
+ PAUSE_TIME = STDOUT.tty? ? 0.1 : 0.5
62
+
63
+ def stop(deadline)
64
+ quiet
65
+ fire_event(:shutdown, true)
66
+
67
+ # some of the shutdown events can be async,
68
+ # we don't have any way to know when they're done but
69
+ # give them a little time to take effect
70
+ sleep PAUSE_TIME
71
+ return if @workers.empty?
72
+
73
+ logger.info { "Pausing to allow workers to finish..." }
74
+ remaining = deadline - Time.now
75
+ while remaining > PAUSE_TIME
76
+ return if @workers.empty?
77
+ sleep PAUSE_TIME
78
+ remaining = deadline - Time.now
79
+ end
80
+ return if @workers.empty?
81
+
82
+ hard_shutdown
83
+ end
84
+
85
+ def processor_stopped(processor)
86
+ @plock.synchronize do
87
+ @workers.delete(processor)
88
+ end
89
+ end
90
+
91
+ def processor_died(processor, reason)
92
+ @plock.synchronize do
93
+ @workers.delete(processor)
94
+ unless @done
95
+ p = Processor.new(self)
96
+ @workers << p
97
+ p.start
98
+ end
99
+ end
100
+ end
101
+
102
+ def stopped?
103
+ @done
104
+ end
105
+
106
+ private
107
+
108
+ def hard_shutdown
109
+ # We've reached the timeout and we still have busy workers.
110
+ # They must die but their jobs shall live on.
111
+ cleanup = nil
112
+ @plock.synchronize do
113
+ cleanup = @workers.dup
114
+ end
115
+
116
+ if cleanup.size > 0
117
+ jobs = cleanup.map {|p| p.job }.compact
118
+
119
+ logger.warn { "Terminating #{cleanup.size} busy worker threads" }
120
+ logger.warn { "Work still in progress #{jobs.inspect}" }
121
+
122
+ # Re-enqueue unfinished jobs
123
+ # NOTE: You may notice that we may push a job back to redis before
124
+ # the worker thread is terminated. This is ok because Sidekiq's
125
+ # contract says that jobs are run AT LEAST once. Process termination
126
+ # is delayed until we're certain the jobs are back in Redis because
127
+ # it is worse to lose a job than to run it twice.
128
+ strategy = (@options[:fetch] || Sidekiq::BasicFetch)
129
+ strategy.bulk_requeue(jobs, @options)
130
+ end
131
+
132
+ cleanup.each do |processor|
133
+ processor.kill
134
+ end
135
+ end
136
+
137
+ end
138
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+ module Sidekiq
3
+ # Middleware is code configured to run before/after
4
+ # a message is processed. It is patterned after Rack
5
+ # middleware. Middleware exists for the client side
6
+ # (pushing jobs onto the queue) as well as the server
7
+ # side (when jobs are actually processed).
8
+ #
9
+ # To add middleware for the client:
10
+ #
11
+ # Sidekiq.configure_client do |config|
12
+ # config.client_middleware do |chain|
13
+ # chain.add MyClientHook
14
+ # end
15
+ # end
16
+ #
17
+ # To modify middleware for the server, just call
18
+ # with another block:
19
+ #
20
+ # Sidekiq.configure_server do |config|
21
+ # config.server_middleware do |chain|
22
+ # chain.add MyServerHook
23
+ # chain.remove ActiveRecord
24
+ # end
25
+ # end
26
+ #
27
+ # To insert immediately preceding another entry:
28
+ #
29
+ # Sidekiq.configure_client do |config|
30
+ # config.client_middleware do |chain|
31
+ # chain.insert_before ActiveRecord, MyClientHook
32
+ # end
33
+ # end
34
+ #
35
+ # To insert immediately after another entry:
36
+ #
37
+ # Sidekiq.configure_client do |config|
38
+ # config.client_middleware do |chain|
39
+ # chain.insert_after ActiveRecord, MyClientHook
40
+ # end
41
+ # end
42
+ #
43
+ # This is an example of a minimal server middleware:
44
+ #
45
+ # class MyServerHook
46
+ # def call(worker_instance, msg, queue)
47
+ # puts "Before work"
48
+ # yield
49
+ # puts "After work"
50
+ # end
51
+ # end
52
+ #
53
+ # This is an example of a minimal client middleware, note
54
+ # the method must return the result or the job will not push
55
+ # to Redis:
56
+ #
57
+ # class MyClientHook
58
+ # def call(worker_class, msg, queue, redis_pool)
59
+ # puts "Before push"
60
+ # result = yield
61
+ # puts "After push"
62
+ # result
63
+ # end
64
+ # end
65
+ #
66
+ module Middleware
67
+ class Chain
68
+ include Enumerable
69
+ attr_reader :entries
70
+
71
+ def initialize_copy(copy)
72
+ copy.instance_variable_set(:@entries, entries.dup)
73
+ end
74
+
75
+ def each(&block)
76
+ entries.each(&block)
77
+ end
78
+
79
+ def initialize
80
+ @entries = []
81
+ yield self if block_given?
82
+ end
83
+
84
+ def remove(klass)
85
+ entries.delete_if { |entry| entry.klass == klass }
86
+ end
87
+
88
+ def add(klass, *args)
89
+ remove(klass) if exists?(klass)
90
+ entries << Entry.new(klass, *args)
91
+ end
92
+
93
+ def prepend(klass, *args)
94
+ remove(klass) if exists?(klass)
95
+ entries.insert(0, Entry.new(klass, *args))
96
+ end
97
+
98
+ def insert_before(oldklass, newklass, *args)
99
+ i = entries.index { |entry| entry.klass == newklass }
100
+ new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
101
+ i = entries.index { |entry| entry.klass == oldklass } || 0
102
+ entries.insert(i, new_entry)
103
+ end
104
+
105
+ def insert_after(oldklass, newklass, *args)
106
+ i = entries.index { |entry| entry.klass == newklass }
107
+ new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
108
+ i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1
109
+ entries.insert(i+1, new_entry)
110
+ end
111
+
112
+ def exists?(klass)
113
+ any? { |entry| entry.klass == klass }
114
+ end
115
+
116
+ def retrieve
117
+ map(&:make_new)
118
+ end
119
+
120
+ def clear
121
+ entries.clear
122
+ end
123
+
124
+ def invoke(*args)
125
+ chain = retrieve.dup
126
+ traverse_chain = lambda do
127
+ if chain.empty?
128
+ yield
129
+ else
130
+ chain.shift.call(*args, &traverse_chain)
131
+ end
132
+ end
133
+ traverse_chain.call
134
+ end
135
+ end
136
+
137
+ class Entry
138
+ attr_reader :klass
139
+
140
+ def initialize(klass, *args)
141
+ @klass = klass
142
+ @args = args
143
+ end
144
+
145
+ def make_new
146
+ @klass.new(*@args)
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Simple middleware to save the current locale and restore it when the job executes.
4
+ # Use it by requiring it in your initializer:
5
+ #
6
+ # require 'sidekiq/middleware/i18n'
7
+ #
8
+ module Sidekiq::Middleware::I18n
9
+ # Get the current locale and store it in the message
10
+ # to be sent to Sidekiq.
11
+ class Client
12
+ def call(worker_class, msg, queue, redis_pool)
13
+ msg['locale'] ||= I18n.locale
14
+ yield
15
+ end
16
+ end
17
+
18
+ # Pull the msg locale out and set the current thread to use it.
19
+ 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
25
+ end
26
+ end
27
+ end
28
+
29
+ Sidekiq.configure_client do |config|
30
+ config.client_middleware do |chain|
31
+ chain.add Sidekiq::Middleware::I18n::Client
32
+ end
33
+ end
34
+
35
+ Sidekiq.configure_server do |config|
36
+ config.client_middleware do |chain|
37
+ chain.add Sidekiq::Middleware::I18n::Client
38
+ end
39
+ config.server_middleware do |chain|
40
+ chain.add Sidekiq::Middleware::I18n::Server
41
+ end
42
+ end
@@ -0,0 +1,22 @@
1
+ module Sidekiq
2
+ module Middleware
3
+ module Server
4
+ class ActiveRecord
5
+
6
+ def initialize
7
+ # With Rails 5+ we must use the Reloader **always**.
8
+ # The reloader handles code loading and db connection management.
9
+ if ::Rails::VERSION::MAJOR >= 5
10
+ raise ArgumentError, "Rails 5 no longer needs or uses the ActiveRecord middleware."
11
+ end
12
+ end
13
+
14
+ def call(*args)
15
+ yield
16
+ ensure
17
+ ::ActiveRecord::Base.clear_active_connections!
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ module Sidekiq
3
+ 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
7
+ pageidx = current_page - 1
8
+ total_size = 0
9
+ items = []
10
+ starting = pageidx * page_size
11
+ ending = starting + page_size - 1
12
+
13
+ Sidekiq.redis do |conn|
14
+ type = conn.type(key)
15
+
16
+ case type
17
+ when 'zset'
18
+ rev = opts && opts[:reverse]
19
+ total_size, items = conn.multi do
20
+ conn.zcard(key)
21
+ if rev
22
+ conn.zrevrange(key, starting, ending, :with_scores => true)
23
+ else
24
+ conn.zrange(key, starting, ending, :with_scores => true)
25
+ end
26
+ end
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
33
+ [current_page, total_size, items]
34
+ when 'none'
35
+ [1, 0, []]
36
+ else
37
+ raise "can't page a #{type}"
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,238 @@
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'
7
+ require 'concurrent/map'
8
+ require 'concurrent/atomic/atomic_fixnum'
9
+
10
+ module Sidekiq
11
+ ##
12
+ # The Processor is a standalone thread which:
13
+ #
14
+ # 1. fetches a job from Redis
15
+ # 2. executes the job
16
+ # a. instantiate the Worker
17
+ # b. run the middleware chain
18
+ # c. call #perform
19
+ #
20
+ # A Processor can exit due to shutdown (processor_stopped)
21
+ # or due to an error during job execution (processor_died)
22
+ #
23
+ # If an error occurs in the job execution, the
24
+ # Processor calls the Manager to create a new one
25
+ # to replace itself and exits.
26
+ #
27
+ class Processor
28
+
29
+ include Util
30
+
31
+ attr_reader :thread
32
+ attr_reader :job
33
+
34
+ def initialize(mgr)
35
+ @mgr = mgr
36
+ @down = false
37
+ @done = false
38
+ @job = nil
39
+ @thread = nil
40
+ @strategy = (mgr.options[:fetch] || Sidekiq::BasicFetch).new(mgr.options)
41
+ @reloader = Sidekiq.options[:reloader]
42
+ @logging = Sidekiq::JobLogger.new
43
+ @retrier = Sidekiq::JobRetry.new
44
+ end
45
+
46
+ def terminate(wait=false)
47
+ @done = true
48
+ return if !@thread
49
+ @thread.value if wait
50
+ end
51
+
52
+ def kill(wait=false)
53
+ @done = true
54
+ return if !@thread
55
+ # unlike the other actors, terminate does not wait
56
+ # for the thread to finish because we don't know how
57
+ # long the job will take to finish. Instead we
58
+ # provide a `kill` method to call after the shutdown
59
+ # timeout passes.
60
+ @thread.raise ::Sidekiq::Shutdown
61
+ @thread.value if wait
62
+ end
63
+
64
+ def start
65
+ @thread ||= safe_thread("processor", &method(:run))
66
+ end
67
+
68
+ private unless $TESTING
69
+
70
+ def run
71
+ begin
72
+ while !@done
73
+ process_one
74
+ end
75
+ @mgr.processor_stopped(self)
76
+ rescue Sidekiq::Shutdown
77
+ @mgr.processor_stopped(self)
78
+ rescue Exception => ex
79
+ @mgr.processor_died(self, ex)
80
+ end
81
+ end
82
+
83
+ def process_one
84
+ @job = fetch
85
+ process(@job) if @job
86
+ @job = nil
87
+ end
88
+
89
+ def get_one
90
+ begin
91
+ work = @strategy.retrieve_work
92
+ (logger.info { "Redis is online, #{Time.now - @down} sec downtime" }; @down = nil) if @down
93
+ work
94
+ rescue Sidekiq::Shutdown
95
+ rescue => ex
96
+ handle_fetch_exception(ex)
97
+ end
98
+ end
99
+
100
+ def fetch
101
+ j = get_one
102
+ if j && @done
103
+ j.requeue
104
+ nil
105
+ else
106
+ j
107
+ end
108
+ end
109
+
110
+ def handle_fetch_exception(ex)
111
+ if !@down
112
+ @down = Time.now
113
+ logger.error("Error fetching job: #{ex}")
114
+ ex.backtrace.each do |bt|
115
+ logger.error(bt)
116
+ end
117
+ end
118
+ sleep(1)
119
+ nil
120
+ end
121
+
122
+ def dispatch(job_hash, queue)
123
+ # since middleware can mutate the job hash
124
+ # we clone here so we report the original
125
+ # job structure to the Web UI
126
+ pristine = cloned(job_hash)
127
+
128
+ # If we're using a wrapper class, like ActiveJob, use the "wrapped"
129
+ # attribute to expose the underlying thing.
130
+ klass = job_hash['wrapped'.freeze] || job_hash["class".freeze]
131
+ ctx = "#{klass} JID-#{job_hash['jid'.freeze]}#{" BID-#{job_hash['bid'.freeze]}" if job_hash['bid'.freeze]}"
132
+
133
+ Sidekiq::Logging.with_context(ctx) do
134
+ @retrier.global(job_hash, queue) do
135
+ @logging.call(job_hash, queue) do
136
+ stats(pristine, queue) do
137
+ # Rails 5 requires a Reloader to wrap code execution. In order to
138
+ # constantize the worker and instantiate an instance, we have to call
139
+ # the Reloader. It handles code loading, db connection management, etc.
140
+ # Effectively this block denotes a "unit of work" to Rails.
141
+ @reloader.call do
142
+ klass = job_hash['class'.freeze].constantize
143
+ worker = klass.new
144
+ worker.jid = job_hash['jid'.freeze]
145
+ @retrier.local(worker, job_hash, queue) do
146
+ yield worker
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ def process(work)
156
+ jobstr = work.job
157
+ queue = work.queue_name
158
+
159
+ ack = false
160
+ begin
161
+ # Treat malformed JSON as a special case: job goes straight to the morgue.
162
+ job_hash = nil
163
+ begin
164
+ job_hash = Sidekiq.load_json(jobstr)
165
+ rescue => ex
166
+ handle_exception(ex, { :context => "Invalid JSON for job", :jobstr => jobstr })
167
+ send_to_morgue(jobstr)
168
+ ack = true
169
+ raise
170
+ end
171
+
172
+ ack = true
173
+ dispatch(job_hash, queue) do |worker|
174
+ Sidekiq.server_middleware.invoke(worker, job_hash, queue) do
175
+ execute_job(worker, cloned(job_hash['args'.freeze]))
176
+ end
177
+ end
178
+ rescue Sidekiq::Shutdown
179
+ # Had to force kill this job because it didn't finish
180
+ # within the timeout. Don't acknowledge the work since
181
+ # we didn't properly finish it.
182
+ ack = false
183
+ rescue Exception => ex
184
+ e = ex.is_a?(::Sidekiq::JobRetry::Skip) && ex.cause ? ex.cause : ex
185
+ handle_exception(e, { :context => "Job raised exception", :job => job_hash, :jobstr => jobstr })
186
+ raise e
187
+ ensure
188
+ work.acknowledge if ack
189
+ end
190
+ end
191
+
192
+ def send_to_morgue(msg)
193
+ now = Time.now.to_f
194
+ Sidekiq.redis do |conn|
195
+ conn.multi do
196
+ conn.zadd('dead', now, msg)
197
+ conn.zremrangebyscore('dead', '-inf', now - DeadSet.timeout)
198
+ conn.zremrangebyrank('dead', 0, -DeadSet.max_jobs)
199
+ end
200
+ end
201
+ end
202
+
203
+ def execute_job(worker, cloned_args)
204
+ worker.perform(*cloned_args)
205
+ end
206
+
207
+ def thread_identity
208
+ @str ||= Thread.current.object_id.to_s(36)
209
+ end
210
+
211
+ WORKER_STATE = Concurrent::Map.new
212
+ PROCESSED = Concurrent::AtomicFixnum.new
213
+ FAILURE = Concurrent::AtomicFixnum.new
214
+
215
+ def stats(job_hash, queue)
216
+ tid = thread_identity
217
+ WORKER_STATE[tid] = {:queue => queue, :payload => job_hash, :run_at => Time.now.to_i }
218
+
219
+ begin
220
+ yield
221
+ rescue Exception
222
+ FAILURE.increment
223
+ raise
224
+ ensure
225
+ WORKER_STATE.delete(tid)
226
+ PROCESSED.increment
227
+ end
228
+ end
229
+
230
+ # Deep clone the arguments passed to the worker so that if
231
+ # the job fails, what is pushed back onto Redis hasn't
232
+ # been mutated by the worker.
233
+ def cloned(thing)
234
+ Marshal.load(Marshal.dump(thing))
235
+ end
236
+
237
+ end
238
+ end