inst-jobs 2.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/db/migrate/20101216224513_create_delayed_jobs.rb +9 -7
- data/db/migrate/20110531144916_cleanup_delayed_jobs_indexes.rb +8 -13
- data/db/migrate/20110610213249_optimize_delayed_jobs.rb +8 -8
- data/db/migrate/20110831210257_add_delayed_jobs_next_in_strand.rb +25 -25
- data/db/migrate/20120510004759_delayed_jobs_delete_trigger_lock_for_update.rb +4 -8
- data/db/migrate/20120531150712_drop_psql_jobs_pop_fn.rb +1 -3
- data/db/migrate/20120607164022_delayed_jobs_use_advisory_locks.rb +11 -15
- data/db/migrate/20120607181141_index_jobs_on_locked_by.rb +1 -1
- data/db/migrate/20120608191051_add_jobs_run_at_index.rb +2 -2
- data/db/migrate/20120927184213_change_delayed_jobs_handler_to_text.rb +1 -1
- data/db/migrate/20140505215510_copy_failed_jobs_original_id.rb +2 -3
- data/db/migrate/20150807133223_add_max_concurrent_to_jobs.rb +9 -13
- data/db/migrate/20151210162949_improve_max_concurrent.rb +4 -8
- data/db/migrate/20161206323555_add_back_default_string_limits_jobs.rb +3 -2
- data/db/migrate/20181217155351_speed_up_max_concurrent_triggers.rb +13 -17
- data/db/migrate/20200330230722_add_id_to_get_delayed_jobs_index.rb +8 -8
- data/db/migrate/20200824222232_speed_up_max_concurrent_delete_trigger.rb +72 -77
- data/db/migrate/20200825011002_add_strand_order_override.rb +93 -97
- data/db/migrate/20210809145804_add_n_strand_index.rb +12 -0
- data/db/migrate/20210812210128_add_singleton_column.rb +200 -0
- data/db/migrate/20210917232626_add_delete_conflicting_singletons_before_unlock_trigger.rb +27 -0
- data/db/migrate/20210928174754_fix_singleton_condition_in_before_insert.rb +56 -0
- data/db/migrate/20210929204903_update_conflicting_singleton_function_to_use_index.rb +27 -0
- data/db/migrate/20211101190934_update_after_delete_trigger_for_singleton_index.rb +137 -0
- data/db/migrate/20211207094200_update_after_delete_trigger_for_singleton_transition_cases.rb +171 -0
- data/db/migrate/20211220112800_fix_singleton_race_condition_insert.rb +59 -0
- data/db/migrate/20211220113000_fix_singleton_race_condition_delete.rb +207 -0
- data/db/migrate/20220127091200_fix_singleton_unique_constraint.rb +31 -0
- data/db/migrate/20220128084800_update_insert_trigger_for_singleton_unique_constraint_change.rb +60 -0
- data/db/migrate/20220128084900_update_delete_trigger_for_singleton_unique_constraint_change.rb +209 -0
- data/db/migrate/20220203063200_remove_old_singleton_index.rb +31 -0
- data/db/migrate/20220328152900_add_failed_jobs_indicies.rb +12 -0
- data/exe/inst_jobs +3 -2
- data/lib/delayed/backend/active_record.rb +226 -168
- data/lib/delayed/backend/base.rb +119 -72
- data/lib/delayed/batch.rb +11 -9
- data/lib/delayed/cli.rb +98 -84
- data/lib/delayed/core_ext/kernel.rb +4 -2
- data/lib/delayed/daemon.rb +70 -74
- data/lib/delayed/job_tracking.rb +26 -25
- data/lib/delayed/lifecycle.rb +28 -23
- data/lib/delayed/log_tailer.rb +17 -17
- data/lib/delayed/logging.rb +13 -16
- data/lib/delayed/message_sending.rb +43 -52
- data/lib/delayed/performable_method.rb +6 -8
- data/lib/delayed/periodic.rb +72 -68
- data/lib/delayed/plugin.rb +2 -4
- data/lib/delayed/pool.rb +205 -168
- data/lib/delayed/rails_reloader_plugin.rb +30 -0
- data/lib/delayed/server/helpers.rb +6 -6
- data/lib/delayed/server.rb +51 -54
- data/lib/delayed/settings.rb +96 -81
- data/lib/delayed/testing.rb +21 -22
- data/lib/delayed/version.rb +1 -1
- data/lib/delayed/work_queue/in_process.rb +21 -17
- data/lib/delayed/work_queue/parent_process/client.rb +55 -53
- data/lib/delayed/work_queue/parent_process/server.rb +245 -207
- data/lib/delayed/work_queue/parent_process.rb +52 -53
- data/lib/delayed/worker/consul_health_check.rb +32 -33
- data/lib/delayed/worker/health_check.rb +35 -27
- data/lib/delayed/worker/null_health_check.rb +3 -1
- data/lib/delayed/worker/process_helper.rb +11 -12
- data/lib/delayed/worker.rb +257 -244
- data/lib/delayed/yaml_extensions.rb +12 -10
- data/lib/delayed_job.rb +37 -37
- data/lib/inst-jobs.rb +1 -1
- data/spec/active_record_job_spec.rb +152 -139
- data/spec/delayed/cli_spec.rb +7 -7
- data/spec/delayed/daemon_spec.rb +10 -9
- data/spec/delayed/message_sending_spec.rb +16 -9
- data/spec/delayed/periodic_spec.rb +14 -21
- data/spec/delayed/server_spec.rb +38 -38
- data/spec/delayed/settings_spec.rb +26 -25
- data/spec/delayed/work_queue/in_process_spec.rb +8 -9
- data/spec/delayed/work_queue/parent_process/client_spec.rb +17 -12
- data/spec/delayed/work_queue/parent_process/server_spec.rb +118 -42
- data/spec/delayed/work_queue/parent_process_spec.rb +21 -23
- data/spec/delayed/worker/consul_health_check_spec.rb +37 -50
- data/spec/delayed/worker/health_check_spec.rb +60 -52
- data/spec/delayed/worker_spec.rb +53 -24
- data/spec/sample_jobs.rb +45 -15
- data/spec/shared/delayed_batch.rb +74 -67
- data/spec/shared/delayed_method.rb +143 -102
- data/spec/shared/performable_method.rb +39 -38
- data/spec/shared/shared_backend.rb +801 -440
- data/spec/shared/testing.rb +14 -14
- data/spec/shared/worker.rb +157 -149
- data/spec/shared_jobs_specs.rb +13 -13
- data/spec/spec_helper.rb +57 -56
- metadata +183 -103
- data/lib/delayed/backend/redis/bulk_update.lua +0 -50
- data/lib/delayed/backend/redis/destroy_job.lua +0 -2
- data/lib/delayed/backend/redis/enqueue.lua +0 -29
- data/lib/delayed/backend/redis/fail_job.lua +0 -5
- data/lib/delayed/backend/redis/find_available.lua +0 -3
- data/lib/delayed/backend/redis/functions.rb +0 -59
- data/lib/delayed/backend/redis/get_and_lock_next_available.lua +0 -17
- data/lib/delayed/backend/redis/includes/jobs_common.lua +0 -203
- data/lib/delayed/backend/redis/job.rb +0 -535
- data/lib/delayed/backend/redis/set_running.lua +0 -5
- data/lib/delayed/backend/redis/tickle_strand.lua +0 -2
- data/spec/gemfiles/42.gemfile +0 -7
- data/spec/gemfiles/50.gemfile +0 -7
- data/spec/gemfiles/51.gemfile +0 -7
- data/spec/gemfiles/52.gemfile +0 -7
- data/spec/gemfiles/60.gemfile +0 -7
- data/spec/redis_job_spec.rb +0 -148
data/lib/delayed/backend/base.rb
CHANGED
@@ -12,7 +12,8 @@ module Delayed
|
|
12
12
|
end
|
13
13
|
|
14
14
|
module Base
|
15
|
-
|
15
|
+
ON_HOLD_BLOCKER = "blocker job"
|
16
|
+
ON_HOLD_LOCKED_BY = "on hold"
|
16
17
|
ON_HOLD_COUNT = 50
|
17
18
|
|
18
19
|
def self.included(base)
|
@@ -22,9 +23,7 @@ module Delayed
|
|
22
23
|
end
|
23
24
|
|
24
25
|
module ClassMethods
|
25
|
-
attr_accessor :batches
|
26
|
-
attr_accessor :batch_enqueue_args
|
27
|
-
attr_accessor :default_priority
|
26
|
+
attr_accessor :batches, :batch_enqueue_args, :default_priority
|
28
27
|
|
29
28
|
# Add a job to the queue
|
30
29
|
# The first argument should be an object that respond_to?(:perform)
|
@@ -32,29 +31,37 @@ module Delayed
|
|
32
31
|
# :priority, :run_at, :queue, :strand, :singleton
|
33
32
|
# Example: Delayed::Job.enqueue(object, priority: 0, run_at: time, queue: queue)
|
34
33
|
def enqueue(object,
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
34
|
+
priority: default_priority,
|
35
|
+
run_at: nil,
|
36
|
+
expires_at: nil,
|
37
|
+
queue: Delayed::Settings.queue,
|
38
|
+
strand: nil,
|
39
|
+
singleton: nil,
|
40
|
+
n_strand: nil,
|
41
|
+
max_attempts: Delayed::Settings.max_attempts,
|
42
|
+
**kwargs)
|
44
43
|
|
45
44
|
unless object.respond_to?(:perform)
|
46
|
-
raise ArgumentError,
|
45
|
+
raise ArgumentError, "Cannot enqueue items which do not respond to perform"
|
47
46
|
end
|
48
47
|
|
48
|
+
strand ||= singleton if Settings.infer_strand_from_singleton
|
49
|
+
|
49
50
|
kwargs = Settings.default_job_options.merge(kwargs)
|
50
51
|
kwargs[:payload_object] = object
|
51
52
|
kwargs[:priority] = priority
|
52
53
|
kwargs[:run_at] = run_at if run_at
|
53
54
|
kwargs[:strand] = strand
|
54
55
|
kwargs[:max_attempts] = max_attempts
|
55
|
-
|
56
|
+
if defined?(Marginalia) && Marginalia::Comment.components
|
57
|
+
kwargs[:source] =
|
58
|
+
Marginalia::Comment.construct_comment
|
59
|
+
end
|
56
60
|
kwargs[:expires_at] = expires_at
|
57
61
|
kwargs[:queue] = queue
|
62
|
+
kwargs[:singleton] = singleton
|
63
|
+
|
64
|
+
raise ArgumentError, "Only one of strand or n_strand can be used" if strand && n_strand
|
58
65
|
|
59
66
|
# If two parameters are given to n_strand, the first param is used
|
60
67
|
# as the strand name for looking up the Setting, while the second
|
@@ -79,15 +86,22 @@ module Delayed
|
|
79
86
|
kwargs.merge!(n_strand_options(full_strand_name, num_strands))
|
80
87
|
end
|
81
88
|
|
89
|
+
job = nil
|
90
|
+
|
82
91
|
if singleton
|
83
|
-
|
84
|
-
|
92
|
+
Delayed::Worker.lifecycle.run_callbacks(:create, kwargs) do
|
93
|
+
job = create(**kwargs)
|
94
|
+
end
|
85
95
|
elsif batches && strand.nil? && run_at.nil?
|
86
96
|
batch_enqueue_args = kwargs.slice(*self.batch_enqueue_args)
|
87
97
|
batches[batch_enqueue_args] << kwargs
|
88
98
|
return true
|
89
99
|
else
|
90
|
-
|
100
|
+
raise ArgumentError, "on_conflict can only be provided with singleton" if kwargs[:on_conflict]
|
101
|
+
|
102
|
+
Delayed::Worker.lifecycle.run_callbacks(:create, kwargs) do
|
103
|
+
job = create(**kwargs)
|
104
|
+
end
|
91
105
|
end
|
92
106
|
|
93
107
|
JobTracking.job_created(job)
|
@@ -118,10 +132,10 @@ module Delayed
|
|
118
132
|
|
119
133
|
def check_priorities(min_priority, max_priority)
|
120
134
|
if min_priority && min_priority < Delayed::MIN_PRIORITY
|
121
|
-
raise
|
135
|
+
raise ArgumentError, "min_priority #{min_priority} can't be less than #{Delayed::MIN_PRIORITY}"
|
122
136
|
end
|
123
|
-
if max_priority && max_priority > Delayed::MAX_PRIORITY
|
124
|
-
raise
|
137
|
+
if max_priority && max_priority > Delayed::MAX_PRIORITY # rubocop:disable Style/GuardClause
|
138
|
+
raise ArgumentError, "max_priority #{max_priority} can't be greater than #{Delayed::MAX_PRIORITY}"
|
125
139
|
end
|
126
140
|
end
|
127
141
|
|
@@ -134,13 +148,19 @@ module Delayed
|
|
134
148
|
|
135
149
|
def processes_locked_locally(name: nil)
|
136
150
|
name ||= Socket.gethostname rescue x
|
137
|
-
running_jobs.select
|
151
|
+
local_jobs = running_jobs.select do |job|
|
152
|
+
job.locked_by.start_with?("#{name}:")
|
153
|
+
end
|
154
|
+
local_jobs.map { |job| job.locked_by.split(":").last.to_i }
|
138
155
|
end
|
139
156
|
|
140
157
|
def unlock_orphaned_prefetched_jobs
|
141
|
-
horizon = db_time_now - Settings.parent_process[:prefetched_jobs_timeout] * 4
|
142
|
-
orphaned_jobs = running_jobs.select
|
158
|
+
horizon = db_time_now - (Settings.parent_process[:prefetched_jobs_timeout] * 4)
|
159
|
+
orphaned_jobs = running_jobs.select do |job|
|
160
|
+
job.locked_by.start_with?("prefetch:") && job.locked_at < horizon
|
161
|
+
end
|
143
162
|
return 0 if orphaned_jobs.empty?
|
163
|
+
|
144
164
|
unlock(orphaned_jobs)
|
145
165
|
end
|
146
166
|
|
@@ -153,16 +173,38 @@ module Delayed
|
|
153
173
|
pid_regex = pid || '(\d+)'
|
154
174
|
regex = Regexp.new("^#{Regexp.escape(name)}:#{pid_regex}$")
|
155
175
|
unlocked_jobs = 0
|
176
|
+
escaped_name = name.gsub("\\", "\\\\")
|
177
|
+
.gsub("%", "\\%")
|
178
|
+
.gsub("_", "\\_")
|
179
|
+
locked_by_like = "#{escaped_name}:%"
|
156
180
|
running = false if pid
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
181
|
+
jobs = running_jobs.limit(100)
|
182
|
+
jobs = pid ? jobs.where(locked_by: "#{name}:#{pid}") : jobs.where("locked_by LIKE ?", locked_by_like)
|
183
|
+
ignores = []
|
184
|
+
loop do
|
185
|
+
batch_scope = ignores.empty? ? jobs : jobs.where.not(id: ignores)
|
186
|
+
# if we don't reload this it's possible to keep getting the
|
187
|
+
# same array each loop even after the jobs have been deleted.
|
188
|
+
batch = batch_scope.reload.to_a
|
189
|
+
break if batch.empty?
|
190
|
+
|
191
|
+
batch.each do |job|
|
192
|
+
unless job.locked_by =~ regex
|
193
|
+
ignores << job.id
|
194
|
+
next
|
195
|
+
end
|
196
|
+
|
197
|
+
unless pid
|
198
|
+
job_pid = $1.to_i
|
199
|
+
running = Process.kill(0, job_pid) rescue false
|
200
|
+
end
|
201
|
+
|
202
|
+
if running
|
203
|
+
ignores << job.id
|
204
|
+
else
|
205
|
+
unlocked_jobs += 1
|
206
|
+
job.reschedule("process died")
|
207
|
+
end
|
166
208
|
end
|
167
209
|
end
|
168
210
|
unlocked_jobs
|
@@ -172,33 +214,37 @@ module Delayed
|
|
172
214
|
def failed?
|
173
215
|
failed_at
|
174
216
|
end
|
175
|
-
|
217
|
+
alias failed failed?
|
176
218
|
|
177
219
|
def expired?
|
178
220
|
expires_at && (self.class.db_time_now >= expires_at)
|
179
221
|
end
|
180
222
|
|
223
|
+
def inferred_max_attempts
|
224
|
+
max_attempts || Delayed::Settings.max_attempts
|
225
|
+
end
|
226
|
+
|
181
227
|
# Reschedule the job in the future (when a job fails).
|
182
228
|
# Uses an exponential scale depending on the number of failed attempts.
|
183
229
|
def reschedule(error = nil, time = nil)
|
184
230
|
begin
|
185
231
|
obj = payload_object
|
186
|
-
return_code = obj.on_failure(error) if obj
|
232
|
+
return_code = obj.on_failure(error) if obj.respond_to?(:on_failure)
|
187
233
|
rescue
|
188
234
|
# don't allow a failed deserialization to prevent rescheduling
|
189
235
|
end
|
190
236
|
|
191
237
|
self.attempts += 1 unless return_code == :unlock
|
192
238
|
|
193
|
-
if self.attempts >=
|
239
|
+
if self.attempts >= inferred_max_attempts
|
194
240
|
permanent_failure error || "max attempts reached"
|
195
241
|
elsif expired?
|
196
242
|
permanent_failure error || "job has expired"
|
197
243
|
else
|
198
|
-
time ||=
|
244
|
+
time ||= reschedule_at
|
199
245
|
self.run_at = time
|
200
|
-
|
201
|
-
|
246
|
+
unlock
|
247
|
+
save!
|
202
248
|
end
|
203
249
|
end
|
204
250
|
|
@@ -206,26 +252,24 @@ module Delayed
|
|
206
252
|
begin
|
207
253
|
# notify the payload_object of a permanent failure
|
208
254
|
obj = payload_object
|
209
|
-
obj.on_permanent_failure(error) if obj
|
255
|
+
obj.on_permanent_failure(error) if obj.respond_to?(:on_permanent_failure)
|
210
256
|
rescue
|
211
257
|
# don't allow a failed deserialization to prevent destroying the job
|
212
258
|
end
|
213
259
|
|
214
260
|
# optionally destroy the object
|
215
261
|
destroy_self = true
|
216
|
-
if Delayed::Worker.on_max_failures
|
217
|
-
destroy_self = Delayed::Worker.on_max_failures.call(self, error)
|
218
|
-
end
|
262
|
+
destroy_self = Delayed::Worker.on_max_failures.call(self, error) if Delayed::Worker.on_max_failures
|
219
263
|
|
220
264
|
if destroy_self
|
221
|
-
|
265
|
+
destroy
|
222
266
|
else
|
223
|
-
|
267
|
+
fail!
|
224
268
|
end
|
225
269
|
end
|
226
270
|
|
227
271
|
def payload_object
|
228
|
-
@payload_object ||= deserialize(self[
|
272
|
+
@payload_object ||= deserialize(self["handler"].untaint)
|
229
273
|
end
|
230
274
|
|
231
275
|
def name
|
@@ -241,7 +285,7 @@ module Delayed
|
|
241
285
|
|
242
286
|
def full_name
|
243
287
|
obj = payload_object rescue nil
|
244
|
-
if obj
|
288
|
+
if obj.respond_to?(:full_name)
|
245
289
|
obj.full_name
|
246
290
|
else
|
247
291
|
name
|
@@ -250,14 +294,14 @@ module Delayed
|
|
250
294
|
|
251
295
|
def payload_object=(object)
|
252
296
|
@payload_object = object
|
253
|
-
self[
|
254
|
-
self[
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
297
|
+
self["handler"] = object.to_yaml
|
298
|
+
self["tag"] = if object.respond_to?(:tag)
|
299
|
+
object.tag
|
300
|
+
elsif object.is_a?(Module)
|
301
|
+
"#{object}.perform"
|
302
|
+
else
|
303
|
+
"#{object.class}#perform"
|
304
|
+
end
|
261
305
|
end
|
262
306
|
|
263
307
|
# Moved into its own method so that new_relic can trace it.
|
@@ -284,15 +328,16 @@ module Delayed
|
|
284
328
|
end
|
285
329
|
|
286
330
|
def locked?
|
287
|
-
!!(
|
331
|
+
!!(locked_at || locked_by)
|
288
332
|
end
|
289
333
|
|
290
334
|
def reschedule_at
|
291
|
-
new_time = self.class.db_time_now + (attempts
|
335
|
+
new_time = self.class.db_time_now + (attempts**4) + 5
|
292
336
|
begin
|
293
337
|
if payload_object.respond_to?(:reschedule_at)
|
294
338
|
new_time = payload_object.reschedule_at(
|
295
|
-
|
339
|
+
self.class.db_time_now, attempts
|
340
|
+
)
|
296
341
|
end
|
297
342
|
rescue
|
298
343
|
# TODO: just swallow errors from reschedule_at ?
|
@@ -304,25 +349,26 @@ module Delayed
|
|
304
349
|
self.locked_by = ON_HOLD_LOCKED_BY
|
305
350
|
self.locked_at = self.class.db_time_now
|
306
351
|
self.attempts = ON_HOLD_COUNT
|
307
|
-
|
352
|
+
save!
|
308
353
|
end
|
309
354
|
|
310
355
|
def unhold!
|
311
356
|
self.locked_by = nil
|
312
357
|
self.locked_at = nil
|
313
358
|
self.attempts = 0
|
314
|
-
self.run_at = [self.class.db_time_now,
|
359
|
+
self.run_at = [self.class.db_time_now, run_at].max
|
315
360
|
self.failed_at = nil
|
316
|
-
|
361
|
+
save!
|
317
362
|
end
|
318
363
|
|
319
364
|
def on_hold?
|
320
|
-
|
365
|
+
locked_by == "on hold" && locked_at && self.attempts == ON_HOLD_COUNT
|
321
366
|
end
|
322
367
|
|
323
|
-
|
368
|
+
private
|
324
369
|
|
325
|
-
|
370
|
+
PARSE_OBJECT_FROM_YAML = %r{!ruby/\w+:([^\s]+)}.freeze
|
371
|
+
private_constant :PARSE_OBJECT_FROM_YAML
|
326
372
|
|
327
373
|
def deserialize(source)
|
328
374
|
handler = nil
|
@@ -336,13 +382,13 @@ module Delayed
|
|
336
382
|
return handler if handler.respond_to?(:perform)
|
337
383
|
|
338
384
|
raise DeserializationError,
|
339
|
-
|
385
|
+
"Job failed to load: Unknown handler. Try to manually require the appropriate file."
|
340
386
|
rescue TypeError, LoadError, NameError => e
|
341
387
|
raise DeserializationError,
|
342
|
-
|
388
|
+
"Job failed to load: #{e.message}. Try to manually require the required file."
|
343
389
|
rescue Psych::SyntaxError => e
|
344
|
-
|
345
|
-
|
390
|
+
raise DeserializationError,
|
391
|
+
"YAML parsing error: #{e.message}. Probably not recoverable."
|
346
392
|
end
|
347
393
|
|
348
394
|
def _yaml_deserialize(source)
|
@@ -350,12 +396,13 @@ module Delayed
|
|
350
396
|
end
|
351
397
|
|
352
398
|
def attempt_to_load_from_source(source)
|
353
|
-
|
354
|
-
|
355
|
-
|
399
|
+
return unless (md = PARSE_OBJECT_FROM_YAML.match(source))
|
400
|
+
|
401
|
+
md[1].constantize
|
356
402
|
end
|
357
403
|
|
358
|
-
|
404
|
+
public
|
405
|
+
|
359
406
|
def initialize_defaults
|
360
407
|
self.queue ||= Delayed::Settings.queue
|
361
408
|
self.run_at ||= self.class.db_time_now
|
data/lib/delayed/batch.rb
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
module Delayed
|
4
4
|
module Batch
|
5
|
-
|
5
|
+
PerformableBatch = Struct.new(:mode, :items) do
|
6
6
|
def initialize(mode, items)
|
7
7
|
raise "unsupported mode" unless mode == :serial
|
8
|
-
|
9
|
-
|
8
|
+
|
9
|
+
super
|
10
10
|
end
|
11
11
|
|
12
12
|
def display_name
|
@@ -25,14 +25,16 @@ module Delayed
|
|
25
25
|
end
|
26
26
|
|
27
27
|
class << self
|
28
|
-
def serial_batch(opts = {})
|
29
|
-
prepare_batches(:serial, opts)
|
28
|
+
def serial_batch(opts = {}, &block)
|
29
|
+
prepare_batches(:serial, opts, &block)
|
30
30
|
end
|
31
31
|
|
32
32
|
private
|
33
|
+
|
33
34
|
def prepare_batches(mode, opts)
|
34
35
|
raise "nested batching is not supported" if Delayed::Job.batches
|
35
|
-
|
36
|
+
|
37
|
+
Delayed::Job.batches = Hash.new { |h, k| h[k] = Set.new }
|
36
38
|
batch_enqueue_args = [:queue]
|
37
39
|
batch_enqueue_args << :priority unless opts[:priority]
|
38
40
|
Delayed::Job.batch_enqueue_args = batch_enqueue_args
|
@@ -42,9 +44,9 @@ module Delayed
|
|
42
44
|
Delayed::Job.batches = nil
|
43
45
|
batch_args = opts.slice(:priority)
|
44
46
|
batches.each do |enqueue_args, batch|
|
45
|
-
if batch.size
|
46
|
-
|
47
|
-
|
47
|
+
next if batch.size.zero?
|
48
|
+
|
49
|
+
if batch.size == 1
|
48
50
|
args = batch.first.merge(batch_args)
|
49
51
|
payload_object = args.delete(:payload_object)
|
50
52
|
Delayed::Job.enqueue(payload_object, **args)
|
data/lib/delayed/cli.rb
CHANGED
@@ -1,111 +1,125 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "optparse"
|
4
4
|
|
5
5
|
module Delayed
|
6
|
-
class CLI
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
class CLI
|
7
|
+
class << self
|
8
|
+
attr_accessor :instance
|
9
|
+
end
|
10
10
|
|
11
|
-
|
11
|
+
attr_reader :config
|
12
12
|
|
13
|
-
|
14
|
-
|
13
|
+
def initialize(args = ARGV)
|
14
|
+
self.class.instance = self
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
@args = args
|
17
|
+
# config that will be applied on Settings and passed to the created Pool
|
18
|
+
@config = {}
|
19
|
+
# CLI options that will be kept to this class
|
20
|
+
@options = {
|
21
|
+
config_file: Settings.default_worker_config_name,
|
22
|
+
pid_folder: Settings.expand_rails_path("tmp/pids"),
|
23
|
+
tail_logs: true # only in FG mode
|
24
|
+
}
|
25
|
+
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
27
|
+
def run
|
28
|
+
parse_cli_options!
|
29
|
+
load_and_apply_config!
|
30
|
+
|
31
|
+
command = @args.shift
|
32
|
+
case command
|
33
|
+
when "start"
|
34
|
+
exit 1 if daemon.status(print: :alive) == :running
|
35
|
+
daemon.daemonize!
|
36
|
+
start
|
37
|
+
when "stop"
|
38
|
+
daemon.stop(kill: @options[:kill])
|
39
|
+
when "run"
|
40
|
+
start
|
41
|
+
when "status"
|
42
|
+
if daemon.status
|
43
|
+
exit 0
|
44
|
+
else
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
when "restart"
|
48
|
+
daemon.stop(kill: @options[:kill])
|
49
|
+
daemon.daemonize!
|
50
|
+
start
|
51
|
+
when nil
|
52
|
+
puts option_parser.to_s
|
44
53
|
else
|
45
|
-
|
54
|
+
raise("Unknown command: #{command.inspect}")
|
46
55
|
end
|
47
|
-
when 'restart'
|
48
|
-
daemon.stop(kill: @options[:kill])
|
49
|
-
daemon.daemonize!
|
50
|
-
start
|
51
|
-
when nil
|
52
|
-
puts option_parser.to_s
|
53
|
-
else
|
54
|
-
raise("Unknown command: #{command.inspect}")
|
55
56
|
end
|
56
|
-
end
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
def parse_cli_options!
|
59
|
+
option_parser.parse!(@args)
|
60
|
+
@options
|
61
|
+
end
|
62
62
|
|
63
|
-
|
63
|
+
protected
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
def load_and_apply_config!
|
66
|
+
@config = Settings.worker_config(@options[:config_file])
|
67
|
+
Settings.apply_worker_config!(@config)
|
68
|
+
end
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
def option_parser
|
71
|
+
@option_parser ||= OptionParser.new do |opts|
|
72
|
+
opts.banner = "Usage #{$0} <command> <options>"
|
73
|
+
opts.separator %(\nWhere <command> is one of:
|
74
74
|
start start the jobs daemon
|
75
75
|
stop stop the jobs daemon
|
76
76
|
run start and run in the foreground
|
77
77
|
restart stop and then start the jobs daemon
|
78
78
|
status show daemon status
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
79
|
+
)
|
80
|
+
|
81
|
+
opts.separator "\n<options>"
|
82
|
+
opts.on("-c", "--config [CONFIG_PATH]", "Use alternate config file (default #{@options[:config_file]})") do |c|
|
83
|
+
@options[:config_file] = c
|
84
|
+
end
|
85
|
+
opts.on("-p", "--pid [PID_PATH]",
|
86
|
+
"Use alternate folder for PID files (default #{@options[:pid_folder]})") do |p|
|
87
|
+
@options[:pid_folder] = p
|
88
|
+
end
|
89
|
+
opts.on("--no-tail", "Don't tail the logs (only affects non-daemon mode)") { @options[:tail_logs] = false }
|
90
|
+
opts.on("--with-prejudice", "When stopping, interrupt jobs in progress, instead of letting them drain") do
|
91
|
+
@options[:kill] ||= true
|
92
|
+
end
|
93
|
+
opts.on("--with-extreme-prejudice",
|
94
|
+
"When stopping, immediately kill jobs in progress, instead of letting them drain") do
|
95
|
+
@options[:kill] = 9
|
96
|
+
end
|
97
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
98
|
+
puts opts
|
99
|
+
exit
|
100
|
+
end
|
101
|
+
end
|
88
102
|
end
|
89
|
-
end
|
90
103
|
|
91
|
-
|
92
|
-
|
93
|
-
|
104
|
+
def daemon
|
105
|
+
@daemon ||= Delayed::Daemon.new(@options[:pid_folder])
|
106
|
+
end
|
94
107
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
108
|
+
def start
|
109
|
+
load_rails
|
110
|
+
tail_rails_log unless daemon.daemonized?
|
111
|
+
Delayed::Pool.new(@config).start
|
112
|
+
end
|
100
113
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
114
|
+
def load_rails
|
115
|
+
require(Settings.expand_rails_path("config/environment.rb"))
|
116
|
+
Dir.chdir(Rails.root)
|
117
|
+
end
|
118
|
+
|
119
|
+
def tail_rails_log
|
120
|
+
return unless @options[:tail_logs]
|
105
121
|
|
106
|
-
|
107
|
-
|
108
|
-
Delayed::LogTailer.new.run
|
122
|
+
Delayed::LogTailer.new.run
|
123
|
+
end
|
109
124
|
end
|
110
125
|
end
|
111
|
-
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Kernel
|
2
|
-
def sender(
|
4
|
+
def sender(idx = 0)
|
3
5
|
frame_self = nil
|
4
6
|
# 3. one for the block, one for this method, one for the method calling this
|
5
7
|
# method, and _then_ we get to the self for who sent the message we want
|
6
|
-
RubyVM::DebugInspector.open { |dc| frame_self = dc.frame_self(3 +
|
8
|
+
RubyVM::DebugInspector.open { |dc| frame_self = dc.frame_self(3 + idx) }
|
7
9
|
frame_self
|
8
10
|
end
|
9
11
|
end
|