sidekiq 4.2.10 → 6.5.7

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 (131) hide show
  1. checksums.yaml +5 -5
  2. data/Changes.md +573 -1
  3. data/LICENSE +3 -3
  4. data/README.md +25 -34
  5. data/bin/sidekiq +27 -3
  6. data/bin/sidekiqload +81 -74
  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/job_spec.rb.erb +6 -0
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +585 -285
  13. data/lib/sidekiq/cli.rb +256 -233
  14. data/lib/sidekiq/client.rb +86 -83
  15. data/lib/sidekiq/component.rb +65 -0
  16. data/lib/sidekiq/delay.rb +43 -0
  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 +13 -5
  21. data/lib/sidekiq/fetch.rb +50 -40
  22. data/lib/sidekiq/job.rb +13 -0
  23. data/lib/sidekiq/job_logger.rb +51 -0
  24. data/lib/sidekiq/job_retry.rb +282 -0
  25. data/lib/sidekiq/job_util.rb +71 -0
  26. data/lib/sidekiq/launcher.rb +184 -90
  27. data/lib/sidekiq/logger.rb +156 -0
  28. data/lib/sidekiq/manager.rb +43 -45
  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 +176 -91
  40. data/lib/sidekiq/rails.rb +41 -96
  41. data/lib/sidekiq/redis_client_adapter.rb +154 -0
  42. data/lib/sidekiq/redis_connection.rb +117 -48
  43. data/lib/sidekiq/ring_buffer.rb +29 -0
  44. data/lib/sidekiq/scheduled.rb +134 -44
  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 +80 -61
  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 -15
  52. data/lib/sidekiq/web/application.rb +129 -86
  53. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  54. data/lib/sidekiq/web/helpers.rb +170 -83
  55. data/lib/sidekiq/web/router.rb +23 -19
  56. data/lib/sidekiq/web.rb +69 -109
  57. data/lib/sidekiq/worker.rb +290 -41
  58. data/lib/sidekiq.rb +185 -77
  59. data/sidekiq.gemspec +23 -27
  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 +70 -91
  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 +242 -0
  69. data/web/assets/stylesheets/application.css +364 -144
  70. data/web/assets/stylesheets/bootstrap-rtl.min.css +9 -0
  71. data/web/assets/stylesheets/bootstrap.css +2 -2
  72. data/web/locales/ar.yml +87 -0
  73. data/web/locales/de.yml +14 -2
  74. data/web/locales/el.yml +43 -19
  75. data/web/locales/en.yml +15 -1
  76. data/web/locales/es.yml +22 -5
  77. data/web/locales/fa.yml +1 -0
  78. data/web/locales/fr.yml +10 -3
  79. data/web/locales/he.yml +79 -0
  80. data/web/locales/ja.yml +19 -4
  81. data/web/locales/lt.yml +83 -0
  82. data/web/locales/pl.yml +4 -4
  83. data/web/locales/pt-br.yml +27 -9
  84. data/web/locales/ru.yml +4 -0
  85. data/web/locales/ur.yml +80 -0
  86. data/web/locales/vi.yml +83 -0
  87. data/web/locales/zh-cn.yml +36 -11
  88. data/web/locales/zh-tw.yml +32 -7
  89. data/web/views/_footer.erb +5 -2
  90. data/web/views/_job_info.erb +3 -2
  91. data/web/views/_nav.erb +5 -19
  92. data/web/views/_paging.erb +1 -1
  93. data/web/views/_poll_link.erb +2 -5
  94. data/web/views/_summary.erb +7 -7
  95. data/web/views/busy.erb +62 -24
  96. data/web/views/dashboard.erb +24 -15
  97. data/web/views/dead.erb +3 -3
  98. data/web/views/layout.erb +14 -3
  99. data/web/views/metrics.erb +69 -0
  100. data/web/views/metrics_for_job.erb +87 -0
  101. data/web/views/morgue.erb +9 -6
  102. data/web/views/queue.erb +26 -12
  103. data/web/views/queues.erb +12 -2
  104. data/web/views/retries.erb +14 -7
  105. data/web/views/retry.erb +3 -3
  106. data/web/views/scheduled.erb +7 -4
  107. metadata +66 -206
  108. data/.github/contributing.md +0 -32
  109. data/.github/issue_template.md +0 -9
  110. data/.gitignore +0 -12
  111. data/.travis.yml +0 -18
  112. data/3.0-Upgrade.md +0 -70
  113. data/4.0-Upgrade.md +0 -53
  114. data/COMM-LICENSE +0 -95
  115. data/Ent-Changes.md +0 -173
  116. data/Gemfile +0 -29
  117. data/Pro-2.0-Upgrade.md +0 -138
  118. data/Pro-3.0-Upgrade.md +0 -44
  119. data/Pro-Changes.md +0 -628
  120. data/Rakefile +0 -12
  121. data/bin/sidekiqctl +0 -99
  122. data/code_of_conduct.md +0 -50
  123. data/lib/generators/sidekiq/templates/worker_spec.rb.erb +0 -6
  124. data/lib/generators/sidekiq/worker_generator.rb +0 -49
  125. data/lib/sidekiq/core_ext.rb +0 -119
  126. data/lib/sidekiq/exception_handler.rb +0 -31
  127. data/lib/sidekiq/logging.rb +0 -106
  128. data/lib/sidekiq/middleware/server/active_record.rb +0 -13
  129. data/lib/sidekiq/middleware/server/logging.rb +0 -31
  130. data/lib/sidekiq/middleware/server/retry_jobs.rb +0 -205
  131. data/lib/sidekiq/util.rb +0 -63
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
- require 'securerandom'
3
- require 'sidekiq/middleware/chain'
2
+
3
+ require "securerandom"
4
+ require "sidekiq/middleware/chain"
5
+ require "sidekiq/job_util"
4
6
 
5
7
  module Sidekiq
6
8
  class Client
9
+ include Sidekiq::JobUtil
7
10
 
8
11
  ##
9
12
  # Define client-side middleware:
@@ -12,14 +15,14 @@ module Sidekiq
12
15
  # client.middleware do |chain|
13
16
  # chain.use MyClientMiddleware
14
17
  # end
15
- # client.push('class' => 'SomeWorker', 'args' => [1,2,3])
18
+ # client.push('class' => 'SomeJob', 'args' => [1,2,3])
16
19
  #
17
20
  # All client instances default to the globally-defined
18
21
  # Sidekiq.client_middleware but you can change as necessary.
19
22
  #
20
23
  def middleware(&block)
21
24
  @chain ||= Sidekiq.client_middleware
22
- if block_given?
25
+ if block
23
26
  @chain = @chain.dup
24
27
  yield @chain
25
28
  end
@@ -38,7 +41,7 @@ module Sidekiq
38
41
  # Generally this is only needed for very large Sidekiq installs processing
39
42
  # thousands of jobs per second. I don't recommend sharding unless you
40
43
  # cannot scale any other way (e.g. splitting your app into smaller apps).
41
- def initialize(redis_pool=nil)
44
+ def initialize(redis_pool = nil)
42
45
  @redis_pool = redis_pool || Thread.current[:sidekiq_via_pool] || Sidekiq.redis_pool
43
46
  end
44
47
 
@@ -46,11 +49,17 @@ module Sidekiq
46
49
  # The main method used to push a job to Redis. Accepts a number of options:
47
50
  #
48
51
  # queue - the named queue to use, default 'default'
49
- # class - the worker class to call, required
52
+ # class - the job class to call, required
50
53
  # args - an array of simple arguments to the perform method, must be JSON-serializable
54
+ # at - timestamp to schedule the job (optional), must be Numeric (e.g. Time.now.to_f)
51
55
  # retry - whether to retry this job if it fails, default true or an integer number of retries
52
56
  # backtrace - whether to save any error backtrace, default false
53
57
  #
58
+ # If class is set to the class name, the jobs' options will be based on Sidekiq's default
59
+ # job options. Otherwise, they will be based on the job class's options.
60
+ #
61
+ # Any options valid for a job class's sidekiq_options are also available here.
62
+ #
54
63
  # All options must be strings, not symbols. NB: because we are serializing to JSON, all
55
64
  # symbols in 'args' will be converted to strings. Note that +backtrace: true+ can take quite a bit of
56
65
  # space in Redis; a large volume of failing jobs can start Redis swapping if you aren't careful.
@@ -58,22 +67,25 @@ module Sidekiq
58
67
  # Returns a unique Job ID. If middleware stops the job, nil will be returned instead.
59
68
  #
60
69
  # Example:
61
- # push('queue' => 'my_queue', 'class' => MyWorker, 'args' => ['foo', 1, :bat => 'bar'])
70
+ # push('queue' => 'my_queue', 'class' => MyJob, 'args' => ['foo', 1, :bat => 'bar'])
62
71
  #
63
72
  def push(item)
64
73
  normed = normalize_item(item)
65
- payload = process_single(item['class'], normed)
66
-
74
+ payload = middleware.invoke(item["class"], normed, normed["queue"], @redis_pool) do
75
+ normed
76
+ end
67
77
  if payload
78
+ verify_json(payload)
68
79
  raw_push([payload])
69
- payload['jid']
80
+ payload["jid"]
70
81
  end
71
82
  end
72
83
 
73
84
  ##
74
- # Push a large number of jobs to Redis. In practice this method is only
75
- # useful if you are pushing thousands of jobs or more. This method
76
- # cuts out the redis network round trip latency.
85
+ # Push a large number of jobs to Redis. This method cuts out the redis
86
+ # network round trip latency. I wouldn't recommend pushing more than
87
+ # 1000 per call but YMMV based on network quality, size of job args, etc.
88
+ # A large number of jobs can cause a bit of Redis command processing latency.
77
89
  #
78
90
  # Takes the same arguments as #push except that args is expected to be
79
91
  # an Array of Arrays. All other keys are duplicated for each job. Each job
@@ -83,19 +95,30 @@ module Sidekiq
83
95
  # Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less
84
96
  # than the number given if the middleware stopped processing for one or more jobs.
85
97
  def push_bulk(items)
86
- arg = items['args'].first
87
- return [] unless arg # no jobs to push
88
- raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" if !arg.is_a?(Array)
98
+ args = items["args"]
99
+ raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless args.is_a?(Array) && args.all?(Array)
100
+ return [] if args.empty? # no jobs to push
101
+
102
+ at = items.delete("at")
103
+ raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all? { |entry| entry.is_a?(Numeric) })
104
+ raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size
105
+
106
+ jid = items.delete("jid")
107
+ raise ArgumentError, "Explicitly passing 'jid' when pushing more than one job is not supported" if jid && args.size > 1
89
108
 
90
109
  normed = normalize_item(items)
91
- payloads = items['args'].map do |args|
92
- copy = normed.merge('args' => args, 'jid' => SecureRandom.hex(12), 'enqueued_at' => Time.now.to_f)
93
- result = process_single(items['class'], copy)
94
- result ? result : nil
95
- end.compact
96
-
97
- raw_push(payloads) if !payloads.empty?
98
- payloads.collect { |payload| payload['jid'] }
110
+ payloads = args.map.with_index { |job_args, index|
111
+ copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12))
112
+ copy["at"] = (at.is_a?(Array) ? at[index] : at) if at
113
+ result = middleware.invoke(items["class"], copy, copy["queue"], @redis_pool) do
114
+ verify_json(copy)
115
+ copy
116
+ end
117
+ result || nil
118
+ }.compact
119
+
120
+ raw_push(payloads) unless payloads.empty?
121
+ payloads.collect { |payload| payload["jid"] }
99
122
  end
100
123
 
101
124
  # Allows sharding of jobs across any number of Redis instances. All jobs
@@ -103,8 +126,8 @@ module Sidekiq
103
126
  #
104
127
  # pool = ConnectionPool.new { Redis.new }
105
128
  # Sidekiq::Client.via(pool) do
106
- # SomeWorker.perform_async(1,2,3)
107
- # SomeOtherWorker.perform_async(1,2,3)
129
+ # SomeJob.perform_async(1,2,3)
130
+ # SomeOtherJob.perform_async(1,2,3)
108
131
  # end
109
132
  #
110
133
  # Generally this is only needed for very large Sidekiq installs processing
@@ -113,15 +136,13 @@ module Sidekiq
113
136
  def self.via(pool)
114
137
  raise ArgumentError, "No pool given" if pool.nil?
115
138
  current_sidekiq_pool = Thread.current[:sidekiq_via_pool]
116
- raise RuntimeError, "Sidekiq::Client.via is not re-entrant" if current_sidekiq_pool && current_sidekiq_pool != pool
117
139
  Thread.current[:sidekiq_via_pool] = pool
118
140
  yield
119
141
  ensure
120
- Thread.current[:sidekiq_via_pool] = nil
142
+ Thread.current[:sidekiq_via_pool] = current_sidekiq_pool
121
143
  end
122
144
 
123
145
  class << self
124
-
125
146
  def push(item)
126
147
  new.push(item)
127
148
  end
@@ -131,40 +152,40 @@ module Sidekiq
131
152
  end
132
153
 
133
154
  # Resque compatibility helpers. Note all helpers
134
- # should go through Worker#client_push.
155
+ # should go through Sidekiq::Job#client_push.
135
156
  #
136
157
  # Example usage:
137
- # Sidekiq::Client.enqueue(MyWorker, 'foo', 1, :bat => 'bar')
158
+ # Sidekiq::Client.enqueue(MyJob, 'foo', 1, :bat => 'bar')
138
159
  #
139
160
  # Messages are enqueued to the 'default' queue.
140
161
  #
141
162
  def enqueue(klass, *args)
142
- klass.client_push('class' => klass, 'args' => args)
163
+ klass.client_push("class" => klass, "args" => args)
143
164
  end
144
165
 
145
166
  # Example usage:
146
- # Sidekiq::Client.enqueue_to(:queue_name, MyWorker, 'foo', 1, :bat => 'bar')
167
+ # Sidekiq::Client.enqueue_to(:queue_name, MyJob, 'foo', 1, :bat => 'bar')
147
168
  #
148
169
  def enqueue_to(queue, klass, *args)
149
- klass.client_push('queue' => queue, 'class' => klass, 'args' => args)
170
+ klass.client_push("queue" => queue, "class" => klass, "args" => args)
150
171
  end
151
172
 
152
173
  # Example usage:
153
- # Sidekiq::Client.enqueue_to_in(:queue_name, 3.minutes, MyWorker, 'foo', 1, :bat => 'bar')
174
+ # Sidekiq::Client.enqueue_to_in(:queue_name, 3.minutes, MyJob, 'foo', 1, :bat => 'bar')
154
175
  #
155
176
  def enqueue_to_in(queue, interval, klass, *args)
156
177
  int = interval.to_f
157
178
  now = Time.now.to_f
158
179
  ts = (int < 1_000_000_000 ? now + int : int)
159
180
 
160
- item = { 'class' => klass, 'args' => args, 'at' => ts, 'queue' => queue }
161
- item.delete('at'.freeze) if ts <= now
181
+ item = {"class" => klass, "args" => args, "at" => ts, "queue" => queue}
182
+ item.delete("at") if ts <= now
162
183
 
163
184
  klass.client_push(item)
164
185
  end
165
186
 
166
187
  # Example usage:
167
- # Sidekiq::Client.enqueue_in(3.minutes, MyWorker, 'foo', 1, :bat => 'bar')
188
+ # Sidekiq::Client.enqueue_in(3.minutes, MyJob, 'foo', 1, :bat => 'bar')
168
189
  #
169
190
  def enqueue_in(interval, klass, *args)
170
191
  klass.perform_in(interval, *args)
@@ -175,61 +196,43 @@ module Sidekiq
175
196
 
176
197
  def raw_push(payloads)
177
198
  @redis_pool.with do |conn|
178
- conn.multi do
179
- atomic_push(conn, payloads)
199
+ retryable = true
200
+ begin
201
+ conn.pipelined do |pipeline|
202
+ atomic_push(pipeline, payloads)
203
+ end
204
+ rescue RedisConnection.adapter::BaseError => ex
205
+ # 2550 Failover can cause the server to become a replica, need
206
+ # to disconnect and reopen the socket to get back to the primary.
207
+ # 4495 Use the same logic if we have a "Not enough replicas" error from the primary
208
+ # 4985 Use the same logic when a blocking command is force-unblocked
209
+ # The retry logic is copied from sidekiq.rb
210
+ if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/
211
+ conn.disconnect!
212
+ retryable = false
213
+ retry
214
+ end
215
+ raise
180
216
  end
181
217
  end
182
218
  true
183
219
  end
184
220
 
185
221
  def atomic_push(conn, payloads)
186
- if payloads.first['at']
187
- conn.zadd('schedule'.freeze, payloads.map do |hash|
188
- at = hash.delete('at'.freeze).to_s
222
+ if payloads.first.key?("at")
223
+ conn.zadd("schedule", payloads.flat_map { |hash|
224
+ at = hash.delete("at").to_s
189
225
  [at, Sidekiq.dump_json(hash)]
190
- end)
226
+ })
191
227
  else
192
- q = payloads.first['queue']
228
+ queue = payloads.first["queue"]
193
229
  now = Time.now.to_f
194
- to_push = payloads.map do |entry|
195
- entry['enqueued_at'.freeze] = now
230
+ to_push = payloads.map { |entry|
231
+ entry["enqueued_at"] = now
196
232
  Sidekiq.dump_json(entry)
197
- end
198
- conn.sadd('queues'.freeze, q)
199
- conn.lpush("queue:#{q}", to_push)
200
- end
201
- end
202
-
203
- def process_single(worker_class, item)
204
- queue = item['queue']
205
-
206
- middleware.invoke(worker_class, item, queue, @redis_pool) do
207
- item
208
- end
209
- end
210
-
211
- def normalize_item(item)
212
- raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: { 'class' => SomeWorker, 'args' => ['bob', 1, :foo => 'bar'] }") unless item.is_a?(Hash) && item.has_key?('class'.freeze) && item.has_key?('args'.freeze)
213
- raise(ArgumentError, "Job args must be an Array") unless item['args'].is_a?(Array)
214
- raise(ArgumentError, "Job class must be either a Class or String representation of the class name") unless item['class'.freeze].is_a?(Class) || item['class'.freeze].is_a?(String)
215
- #raise(ArgumentError, "Arguments must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices") unless JSON.load(JSON.dump(item['args'])) == item['args']
216
-
217
- normalized_hash(item['class'.freeze])
218
- .each{ |key, value| item[key] = value if item[key].nil? }
219
-
220
- item['class'.freeze] = item['class'.freeze].to_s
221
- item['queue'.freeze] = item['queue'.freeze].to_s
222
- item['jid'.freeze] ||= SecureRandom.hex(12)
223
- item['created_at'.freeze] ||= Time.now.to_f
224
- item
225
- end
226
-
227
- def normalized_hash(item_class)
228
- if item_class.is_a?(Class)
229
- raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") if !item_class.respond_to?('get_sidekiq_options'.freeze)
230
- item_class.get_sidekiq_options
231
- else
232
- Sidekiq.default_worker_options
233
+ }
234
+ conn.sadd("queues", [queue])
235
+ conn.lpush("queue:#{queue}", to_push)
233
236
  end
234
237
  end
235
238
  end
@@ -0,0 +1,65 @@
1
+ module Sidekiq
2
+ ##
3
+ # Sidekiq::Component assumes a config instance is available at @config
4
+ module Component # :nodoc:
5
+ attr_reader :config
6
+
7
+ def watchdog(last_words)
8
+ yield
9
+ rescue Exception => ex
10
+ handle_exception(ex, {context: last_words})
11
+ raise ex
12
+ end
13
+
14
+ def safe_thread(name, &block)
15
+ Thread.new do
16
+ Thread.current.name = name
17
+ watchdog(name, &block)
18
+ end
19
+ end
20
+
21
+ def logger
22
+ config.logger
23
+ end
24
+
25
+ def redis(&block)
26
+ config.redis(&block)
27
+ end
28
+
29
+ def tid
30
+ Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36)
31
+ end
32
+
33
+ def hostname
34
+ ENV["DYNO"] || Socket.gethostname
35
+ end
36
+
37
+ def process_nonce
38
+ @@process_nonce ||= SecureRandom.hex(6)
39
+ end
40
+
41
+ def identity
42
+ @@identity ||= "#{hostname}:#{::Process.pid}:#{process_nonce}"
43
+ end
44
+
45
+ def handle_exception(ex, ctx = {})
46
+ config.handle_exception(ex, ctx)
47
+ end
48
+
49
+ def fire_event(event, options = {})
50
+ oneshot = options.fetch(:oneshot, true)
51
+ reverse = options[:reverse]
52
+ reraise = options[:reraise]
53
+
54
+ arr = config[:lifecycle_events][event]
55
+ arr.reverse! if reverse
56
+ arr.each do |block|
57
+ block.call
58
+ rescue => ex
59
+ handle_exception(ex, {context: "Exception during Sidekiq lifecycle event.", event: event})
60
+ raise ex if reraise
61
+ end
62
+ arr.clear if oneshot # once we've fired an event, we never fire it again
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq # :nodoc:
4
+ module Extensions
5
+ def self.enable_delay!
6
+ warn "Sidekiq's Delayed Extensions will be removed in Sidekiq 7.0", uplevel: 1
7
+
8
+ if defined?(::ActiveSupport)
9
+ require "sidekiq/extensions/active_record"
10
+ require "sidekiq/extensions/action_mailer"
11
+
12
+ # Need to patch Psych so it can autoload classes whose names are serialized
13
+ # in the delayed YAML.
14
+ Psych::Visitors::ToRuby.prepend(Sidekiq::Extensions::PsychAutoload)
15
+
16
+ ActiveSupport.on_load(:active_record) do
17
+ include Sidekiq::Extensions::ActiveRecord
18
+ end
19
+ ActiveSupport.on_load(:action_mailer) do
20
+ extend Sidekiq::Extensions::ActionMailer
21
+ end
22
+ end
23
+
24
+ require "sidekiq/extensions/class_methods"
25
+ Module.__send__(:include, Sidekiq::Extensions::Klass)
26
+ end
27
+
28
+ module PsychAutoload
29
+ def resolve_class(klass_name)
30
+ return nil if !klass_name || klass_name.empty?
31
+ # constantize
32
+ names = klass_name.split("::")
33
+ names.shift if names.empty? || names.first.empty?
34
+
35
+ names.inject(Object) do |constant, name|
36
+ constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
37
+ end
38
+ rescue NameError
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/extensions/generic_proxy'
2
+
3
+ require "sidekiq/extensions/generic_proxy"
3
4
 
4
5
  module Sidekiq
5
6
  module Extensions
6
7
  ##
7
- # Adds 'delay', 'delay_for' and `delay_until` methods to ActionMailer to offload arbitrary email
8
- # delivery to Sidekiq. Example:
8
+ # Adds +delay+, +delay_for+ and +delay_until+ methods to ActionMailer to offload arbitrary email
9
+ # delivery to Sidekiq.
9
10
  #
11
+ # @example
10
12
  # UserMailer.delay.send_welcome_email(new_user)
11
13
  # UserMailer.delay_for(5.days).send_welcome_email(new_user)
12
14
  # UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user)
@@ -19,39 +21,28 @@ module Sidekiq
19
21
  # The email method can return nil, which causes ActionMailer to return
20
22
  # an undeliverable empty message.
21
23
  if msg
22
- deliver(msg)
23
- else
24
- raise "#{target.name}##{method_name} returned an undeliverable mail object"
25
- end
26
- end
27
-
28
- private
29
-
30
- def deliver(msg)
31
- if msg.respond_to?(:deliver_now)
32
- # Rails 4.2/5.0
33
24
  msg.deliver_now
34
25
  else
35
- # Rails 3.2/4.0/4.1
36
- msg.deliver
26
+ raise "#{target.name}##{method_name} returned an undeliverable mail object"
37
27
  end
38
28
  end
39
29
  end
40
30
 
41
31
  module ActionMailer
42
- def sidekiq_delay(options={})
32
+ def sidekiq_delay(options = {})
43
33
  Proxy.new(DelayedMailer, self, options)
44
34
  end
45
- def sidekiq_delay_for(interval, options={})
46
- Proxy.new(DelayedMailer, self, options.merge('at' => Time.now.to_f + interval.to_f))
35
+
36
+ def sidekiq_delay_for(interval, options = {})
37
+ Proxy.new(DelayedMailer, self, options.merge("at" => Time.now.to_f + interval.to_f))
47
38
  end
48
- def sidekiq_delay_until(timestamp, options={})
49
- Proxy.new(DelayedMailer, self, options.merge('at' => timestamp.to_f))
39
+
40
+ def sidekiq_delay_until(timestamp, options = {})
41
+ Proxy.new(DelayedMailer, self, options.merge("at" => timestamp.to_f))
50
42
  end
51
43
  alias_method :delay, :sidekiq_delay
52
44
  alias_method :delay_for, :sidekiq_delay_for
53
45
  alias_method :delay_until, :sidekiq_delay_until
54
46
  end
55
-
56
47
  end
57
48
  end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/extensions/generic_proxy'
2
+
3
+ require "sidekiq/extensions/generic_proxy"
3
4
 
4
5
  module Sidekiq
5
6
  module Extensions
6
7
  ##
7
- # Adds 'delay', 'delay_for' and `delay_until` methods to ActiveRecord to offload instance method
8
- # execution to Sidekiq. Examples:
8
+ # Adds +delay+, +delay_for+ and +delay_until+ methods to ActiveRecord to offload instance method
9
+ # execution to Sidekiq.
9
10
  #
10
- # User.recent_signups.each { |user| user.delay.mark_as_awesome }
11
+ # @example
12
+ # User.recent_signups.each { |user| user.delay.mark_as_awesome }
11
13
  #
12
14
  # Please note, this is not recommended as this will serialize the entire
13
15
  # object to Redis. Your Sidekiq jobs should pass IDs, not entire instances.
@@ -22,19 +24,20 @@ module Sidekiq
22
24
  end
23
25
 
24
26
  module ActiveRecord
25
- def sidekiq_delay(options={})
27
+ def sidekiq_delay(options = {})
26
28
  Proxy.new(DelayedModel, self, options)
27
29
  end
28
- def sidekiq_delay_for(interval, options={})
29
- Proxy.new(DelayedModel, self, options.merge('at' => Time.now.to_f + interval.to_f))
30
+
31
+ def sidekiq_delay_for(interval, options = {})
32
+ Proxy.new(DelayedModel, self, options.merge("at" => Time.now.to_f + interval.to_f))
30
33
  end
31
- def sidekiq_delay_until(timestamp, options={})
32
- Proxy.new(DelayedModel, self, options.merge('at' => timestamp.to_f))
34
+
35
+ def sidekiq_delay_until(timestamp, options = {})
36
+ Proxy.new(DelayedModel, self, options.merge("at" => timestamp.to_f))
33
37
  end
34
38
  alias_method :delay, :sidekiq_delay
35
39
  alias_method :delay_for, :sidekiq_delay_for
36
40
  alias_method :delay_until, :sidekiq_delay_until
37
41
  end
38
-
39
42
  end
40
43
  end
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
- require 'sidekiq/extensions/generic_proxy'
2
+
3
+ require "sidekiq/extensions/generic_proxy"
3
4
 
4
5
  module Sidekiq
5
6
  module Extensions
6
7
  ##
7
- # Adds 'delay', 'delay_for' and `delay_until` methods to all Classes to offload class method
8
- # execution to Sidekiq. Examples:
8
+ # Adds `delay`, `delay_for` and `delay_until` methods to all Classes to offload class method
9
+ # execution to Sidekiq.
9
10
  #
10
- # User.delay.delete_inactive
11
- # Wikipedia.delay.download_changes_for(Date.today)
11
+ # @example
12
+ # User.delay.delete_inactive
13
+ # Wikipedia.delay.download_changes_for(Date.today)
12
14
  #
13
15
  class DelayedClass
14
16
  include Sidekiq::Worker
@@ -20,20 +22,21 @@ module Sidekiq
20
22
  end
21
23
 
22
24
  module Klass
23
- def sidekiq_delay(options={})
25
+ def sidekiq_delay(options = {})
24
26
  Proxy.new(DelayedClass, self, options)
25
27
  end
26
- def sidekiq_delay_for(interval, options={})
27
- Proxy.new(DelayedClass, self, options.merge('at' => Time.now.to_f + interval.to_f))
28
+
29
+ def sidekiq_delay_for(interval, options = {})
30
+ Proxy.new(DelayedClass, self, options.merge("at" => Time.now.to_f + interval.to_f))
28
31
  end
29
- def sidekiq_delay_until(timestamp, options={})
30
- Proxy.new(DelayedClass, self, options.merge('at' => timestamp.to_f))
32
+
33
+ def sidekiq_delay_until(timestamp, options = {})
34
+ Proxy.new(DelayedClass, self, options.merge("at" => timestamp.to_f))
31
35
  end
32
36
  alias_method :delay, :sidekiq_delay
33
37
  alias_method :delay_for, :sidekiq_delay_for
34
38
  alias_method :delay_until, :sidekiq_delay_until
35
39
  end
36
-
37
40
  end
38
41
  end
39
42
 
@@ -1,13 +1,16 @@
1
1
  # frozen_string_literal: true
2
- require 'yaml'
2
+
3
+ require "yaml"
3
4
 
4
5
  module Sidekiq
5
6
  module Extensions
7
+ SIZE_LIMIT = 8_192
8
+
6
9
  class Proxy < BasicObject
7
- def initialize(performable, target, options={})
10
+ def initialize(performable, target, options = {})
8
11
  @performable = performable
9
12
  @target = target
10
- @opts = options
13
+ @opts = options.transform_keys(&:to_s)
11
14
  end
12
15
 
13
16
  def method_missing(name, *args)
@@ -17,9 +20,14 @@ module Sidekiq
17
20
  # to JSON and then deserialized on the other side back into a
18
21
  # Ruby object.
19
22
  obj = [@target, name, args]
20
- @performable.client_push({ 'class' => @performable, 'args' => [::YAML.dump(obj)] }.merge(@opts))
23
+ marshalled = ::YAML.dump(obj)
24
+ if marshalled.size > SIZE_LIMIT
25
+ ::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" }
26
+ end
27
+ @performable.client_push({"class" => @performable,
28
+ "args" => [marshalled],
29
+ "display_class" => "#{@target}.#{name}"}.merge(@opts))
21
30
  end
22
31
  end
23
-
24
32
  end
25
33
  end