inst-jobs 3.3.1 → 3.4.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/20261012154653_add_cancelled_at_to_delayed_jobs.rb +8 -0
- data/lib/delayed/backend/active_record.rb +21 -0
- data/lib/delayed/backend/base.rb +7 -2
- data/lib/delayed/daemon.rb +2 -2
- data/lib/delayed/settings.rb +2 -0
- data/lib/delayed/testing.rb +3 -1
- data/lib/delayed/util.rb +12 -0
- data/lib/delayed/version.rb +1 -1
- data/lib/delayed/work_queue/parent_process/server.rb +42 -2
- data/lib/delayed/worker/null_health_check.rb +2 -2
- data/lib/delayed/worker.rb +94 -12
- data/lib/delayed_job.rb +1 -0
- metadata +4 -254
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a4ab079657570b1e9ce94abdd938367ba912afa077ccad380f38f81862fe5a43
|
|
4
|
+
data.tar.gz: a0366132ef9d5cced326cdecef3446217857e668760fae47a72f664060fc380a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 04d51cfd152ee4a6e5145df09d1f8fec52d60c4eb74287e49d693a29f62498ca9a09655ac0dbb5ce6137817e309a7a47fb5918b737d41a2f78f64c6c8c43ff4e
|
|
7
|
+
data.tar.gz: ed4b8778c404ea2a5f57644e71b5b5bdb8be9f135056acd8eba46dcb7fec58eb39cc5bf5deb05cd4bf3e6754334d7fedb9fe5239291b276bebc1595946d97b45
|
|
@@ -23,6 +23,7 @@ module Delayed
|
|
|
23
23
|
# Contains the work object as a YAML field.
|
|
24
24
|
class Job < AbstractJob
|
|
25
25
|
include Delayed::Backend::Base
|
|
26
|
+
|
|
26
27
|
self.table_name = :delayed_jobs
|
|
27
28
|
|
|
28
29
|
attr_accessor :enqueue_result
|
|
@@ -214,6 +215,12 @@ module Delayed
|
|
|
214
215
|
where(locked_by: worker_name).update_all(locked_by: nil, locked_at: nil)
|
|
215
216
|
end
|
|
216
217
|
|
|
218
|
+
def self.cancel!
|
|
219
|
+
deleted = where("locked_by IS NULL OR locked_by=?", ON_HOLD_LOCKED_BY).delete_all
|
|
220
|
+
marked = update_all(cancelled_at: db_time_now)
|
|
221
|
+
[deleted, marked]
|
|
222
|
+
end
|
|
223
|
+
|
|
217
224
|
def self.strand_size(strand)
|
|
218
225
|
where(strand:).count
|
|
219
226
|
end
|
|
@@ -612,6 +619,18 @@ module Delayed
|
|
|
612
619
|
single_step_create
|
|
613
620
|
end
|
|
614
621
|
|
|
622
|
+
# Delete unlocked jobs; if the job is locked mark it for the job worker
|
|
623
|
+
# to cancel within a reasonable amount of time
|
|
624
|
+
def cancel!
|
|
625
|
+
if self.class.where(id:).where("locked_by IS NULL OR locked_by=?", ON_HOLD_LOCKED_BY).delete_all == 1
|
|
626
|
+
@destroyed = true
|
|
627
|
+
return true
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
update!(cancelled_at: self.class.db_time_now)
|
|
631
|
+
false
|
|
632
|
+
end
|
|
633
|
+
|
|
615
634
|
def fail!
|
|
616
635
|
attrs = attributes
|
|
617
636
|
attrs["original_job_id"] = attrs.delete("id") if Failed.columns_hash.key?("original_job_id")
|
|
@@ -633,6 +652,7 @@ module Delayed
|
|
|
633
652
|
|
|
634
653
|
class Failed < Job
|
|
635
654
|
include Delayed::Backend::Base
|
|
655
|
+
|
|
636
656
|
self.table_name = :failed_jobs
|
|
637
657
|
|
|
638
658
|
def self.cleanup_old_jobs(before_date, batch_size: 10_000)
|
|
@@ -644,6 +664,7 @@ module Delayed
|
|
|
644
664
|
"last_error",
|
|
645
665
|
"locked_at",
|
|
646
666
|
"failed_at",
|
|
667
|
+
"cancelled_at",
|
|
647
668
|
"locked_by",
|
|
648
669
|
"original_job_id",
|
|
649
670
|
"requeued_job_id")
|
data/lib/delayed/backend/base.rb
CHANGED
|
@@ -31,7 +31,7 @@ module Delayed
|
|
|
31
31
|
# :priority, :run_at, :queue, :strand, :singleton
|
|
32
32
|
# Example: Delayed::Job.enqueue(object, priority: 0, run_at: time, queue: queue)
|
|
33
33
|
def enqueue(object,
|
|
34
|
-
priority:
|
|
34
|
+
priority: nil,
|
|
35
35
|
run_at: nil,
|
|
36
36
|
expires_at: nil,
|
|
37
37
|
queue: Delayed::Settings.queue,
|
|
@@ -44,6 +44,7 @@ module Delayed
|
|
|
44
44
|
raise ArgumentError, "Cannot enqueue items which do not respond to perform"
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
priority ||= default_priority
|
|
47
48
|
strand ||= singleton if Settings.infer_strand_from_singleton
|
|
48
49
|
|
|
49
50
|
kwargs = Settings.default_job_options.merge(kwargs)
|
|
@@ -209,7 +210,7 @@ module Delayed
|
|
|
209
210
|
|
|
210
211
|
unless pid
|
|
211
212
|
job_pid = $1.to_i
|
|
212
|
-
running =
|
|
213
|
+
running = Delayed::Util.process_running?(job_pid)
|
|
213
214
|
end
|
|
214
215
|
|
|
215
216
|
if running
|
|
@@ -233,6 +234,10 @@ module Delayed
|
|
|
233
234
|
expires_at && (self.class.db_time_now >= expires_at)
|
|
234
235
|
end
|
|
235
236
|
|
|
237
|
+
def cancelled?
|
|
238
|
+
cancelled_at && (self.class.db_time_now >= cancelled_at)
|
|
239
|
+
end
|
|
240
|
+
|
|
236
241
|
def inferred_max_attempts
|
|
237
242
|
max_attempts || Delayed::Settings.max_attempts
|
|
238
243
|
end
|
data/lib/delayed/daemon.rb
CHANGED
|
@@ -12,8 +12,8 @@ module Delayed
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def status(print: true, pid: self.pid)
|
|
15
|
-
alive = pid &&
|
|
16
|
-
alive ||= :draining if pid &&
|
|
15
|
+
alive = pid && Delayed::Util.process_running?(pid) && :running
|
|
16
|
+
alive ||= :draining if pid && Delayed::Util.process_running?(-pid)
|
|
17
17
|
if alive
|
|
18
18
|
puts "Delayed jobs #{alive}, pool PID: #{pid}" if print
|
|
19
19
|
elsif print && print != :alive
|
data/lib/delayed/settings.rb
CHANGED
|
@@ -25,6 +25,7 @@ module Delayed
|
|
|
25
25
|
:silence_periodic_log,
|
|
26
26
|
:sleep_delay,
|
|
27
27
|
:sleep_delay_stagger,
|
|
28
|
+
:cancellation_check_interval,
|
|
28
29
|
:slow_exit_timeout,
|
|
29
30
|
:stranded_run_at_grace_period,
|
|
30
31
|
:worker_health_check_type,
|
|
@@ -130,6 +131,7 @@ module Delayed
|
|
|
130
131
|
self.max_attempts = 1
|
|
131
132
|
self.sleep_delay = 2.0
|
|
132
133
|
self.sleep_delay_stagger = 2.0
|
|
134
|
+
self.cancellation_check_interval = 10.0
|
|
133
135
|
self.fetch_batch_size = 5
|
|
134
136
|
self.select_random_from_batch = false
|
|
135
137
|
self.silence_periodic_log = false
|
data/lib/delayed/testing.rb
CHANGED
data/lib/delayed/util.rb
ADDED
data/lib/delayed/version.rb
CHANGED
|
@@ -9,6 +9,7 @@ module Delayed
|
|
|
9
9
|
attr_reader :clients, :listen_socket
|
|
10
10
|
|
|
11
11
|
include Delayed::Logging
|
|
12
|
+
|
|
12
13
|
SIGNALS = %i[INT TERM QUIT].freeze
|
|
13
14
|
|
|
14
15
|
def initialize(listen_socket, parent_pid: nil, config: Settings.parent_process)
|
|
@@ -23,6 +24,7 @@ module Delayed
|
|
|
23
24
|
|
|
24
25
|
@exit = false
|
|
25
26
|
@self_pipe = IO.pipe
|
|
27
|
+
@last_cancellation_check = Delayed::Job.db_time_now
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def connected_clients
|
|
@@ -78,6 +80,11 @@ module Delayed
|
|
|
78
80
|
check_for_work(forced_latency:)
|
|
79
81
|
end
|
|
80
82
|
unlock_timed_out_prefetched_jobs
|
|
83
|
+
|
|
84
|
+
return if @last_cancellation_check + Settings.cancellation_check_interval > Delayed::Job.db_time_now
|
|
85
|
+
|
|
86
|
+
@last_cancellation_check = Delayed::Job.db_time_now
|
|
87
|
+
check_for_cancellations
|
|
81
88
|
end
|
|
82
89
|
|
|
83
90
|
def handle_read(socket)
|
|
@@ -96,7 +103,7 @@ module Delayed
|
|
|
96
103
|
# and terminate the work queue process, to be restarted by the parent daemon.
|
|
97
104
|
def handle_accept
|
|
98
105
|
socket, _addr = @listen_socket.accept_nonblock
|
|
99
|
-
@clients[socket] = ClientState.new(false, socket) if socket
|
|
106
|
+
@clients[socket] = ClientState.new(false, nil, socket) if socket
|
|
100
107
|
rescue IO::WaitReadable
|
|
101
108
|
logger.error("Server attempted to read listen_socket but failed with IO::WaitReadable")
|
|
102
109
|
# ignore and just try accepting again next time through the loop
|
|
@@ -115,6 +122,7 @@ module Delayed
|
|
|
115
122
|
worker_name, worker_config = Marshal.load(socket)
|
|
116
123
|
client.name = worker_name
|
|
117
124
|
client.working = false
|
|
125
|
+
client.active_job_id = nil
|
|
118
126
|
(@waiting_clients[worker_config] ||= []) << client
|
|
119
127
|
rescue SystemCallError, IOError => e
|
|
120
128
|
logger.error("Receiving message from client (#{socket}) failed: #{e.inspect}")
|
|
@@ -135,6 +143,7 @@ module Delayed
|
|
|
135
143
|
next
|
|
136
144
|
end
|
|
137
145
|
client.working = true
|
|
146
|
+
client.active_job_id = job.id
|
|
138
147
|
begin
|
|
139
148
|
logger.debug("Sending prefetched job #{job.id} to #{client.name}")
|
|
140
149
|
client_timeout { Marshal.dump(job, client.socket) }
|
|
@@ -176,6 +185,7 @@ module Delayed
|
|
|
176
185
|
end
|
|
177
186
|
client = workers.find { |worker| worker.name == worker_name }
|
|
178
187
|
client.working = true
|
|
188
|
+
client.active_job_id = locked_jobs.id
|
|
179
189
|
jobs_to_send << [client, locked_jobs]
|
|
180
190
|
end
|
|
181
191
|
end
|
|
@@ -244,6 +254,36 @@ module Delayed
|
|
|
244
254
|
end
|
|
245
255
|
end
|
|
246
256
|
|
|
257
|
+
def check_for_cancellations
|
|
258
|
+
job_map = @clients.values.select(&:active_job_id).index_by(&:active_job_id)
|
|
259
|
+
prefetched_ids = @prefetched_jobs.values.flatten.map(&:id)
|
|
260
|
+
return if job_map.empty? && prefetched_ids.empty?
|
|
261
|
+
|
|
262
|
+
canceled_jobs = Delayed::Job.where(id: job_map.keys + prefetched_ids)
|
|
263
|
+
.where(cancelled_at: ..Delayed::Job.db_time_now)
|
|
264
|
+
.pluck(:id)
|
|
265
|
+
return if canceled_jobs.empty?
|
|
266
|
+
|
|
267
|
+
canceled_prefetched = canceled_jobs & prefetched_ids
|
|
268
|
+
unless canceled_prefetched.empty?
|
|
269
|
+
logger.debug("Deleting cancelled prefetched jobs #{canceled_prefetched.join(", ")}")
|
|
270
|
+
Delayed::Job.where(id: canceled_prefetched, locked_by: prefetch_owner).delete_all
|
|
271
|
+
@prefetched_jobs.each_value { |jobs| jobs.reject! { |job| canceled_prefetched.include?(job.id) } }
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
(canceled_jobs & job_map.keys).each do |job_id|
|
|
275
|
+
client = job_map[job_id]
|
|
276
|
+
pid = client.name.split(":").last.to_i
|
|
277
|
+
next unless pid.positive?
|
|
278
|
+
|
|
279
|
+
begin
|
|
280
|
+
Process.kill("USR1", pid)
|
|
281
|
+
rescue Errno::ESRCH
|
|
282
|
+
# ignore exited process
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
247
287
|
def drop_socket(socket)
|
|
248
288
|
# this socket went away
|
|
249
289
|
begin
|
|
@@ -274,7 +314,7 @@ module Delayed
|
|
|
274
314
|
Timeout.timeout(@client_timeout, &)
|
|
275
315
|
end
|
|
276
316
|
|
|
277
|
-
ClientState = Struct.new(:working, :socket, :name)
|
|
317
|
+
ClientState = Struct.new(:working, :active_job_id, :socket, :name)
|
|
278
318
|
end
|
|
279
319
|
end
|
|
280
320
|
end
|
data/lib/delayed/worker.rb
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
module Delayed
|
|
4
4
|
class TimeoutError < RuntimeError; end
|
|
5
5
|
|
|
6
|
+
# Use Exception so that user code that rescues regular errors won't accidentally catch this
|
|
7
|
+
# rubocop:disable Lint/InheritException
|
|
8
|
+
class CancelledError < Exception; end
|
|
9
|
+
# rubocop:enable Lint/InheritException
|
|
10
|
+
|
|
6
11
|
class RetriableError < RuntimeError
|
|
7
12
|
# this error is a special case. You _should_ raise
|
|
8
13
|
# it from inside the rescue block for another error,
|
|
@@ -20,7 +25,8 @@ module Delayed
|
|
|
20
25
|
|
|
21
26
|
class Worker
|
|
22
27
|
include Delayed::Logging
|
|
23
|
-
|
|
28
|
+
|
|
29
|
+
SIGNALS = %i[INT TERM QUIT USR1].freeze
|
|
24
30
|
|
|
25
31
|
attr_reader :config, :queue_name, :min_priority, :max_priority, :work_queue
|
|
26
32
|
|
|
@@ -68,6 +74,7 @@ module Delayed
|
|
|
68
74
|
@health_check_config = Settings.worker_health_check_config
|
|
69
75
|
@config = options
|
|
70
76
|
@job_count = 0
|
|
77
|
+
@active_job_mutex = Mutex.new
|
|
71
78
|
|
|
72
79
|
@signal_queue = []
|
|
73
80
|
|
|
@@ -128,14 +135,29 @@ module Delayed
|
|
|
128
135
|
break
|
|
129
136
|
when :QUIT
|
|
130
137
|
@exit = true
|
|
138
|
+
when :USR1
|
|
139
|
+
# Use the explicit check in case the job changed since the signal was sent
|
|
140
|
+
# Use dedicated thread to avoid blocking the signal handler while making DB queries
|
|
141
|
+
Thread.new { cancel_active_job_if_cancelled(work_thread) }
|
|
131
142
|
else
|
|
132
143
|
logger.error "Unknown signal '#{sig}' received"
|
|
133
144
|
end
|
|
134
145
|
end
|
|
135
146
|
end
|
|
136
147
|
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
if work_queue.is_a?(WorkQueue::InProcess)
|
|
149
|
+
cancellation_checker = Thread.new do
|
|
150
|
+
loop do
|
|
151
|
+
sleep(Settings.cancellation_check_interval)
|
|
152
|
+
cancel_active_job_if_cancelled(work_thread)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
Thread.handle_interrupt(CancelledError => :never) do
|
|
158
|
+
self.class.lifecycle.run_callbacks(:execute, self) do
|
|
159
|
+
run until exit?
|
|
160
|
+
end
|
|
139
161
|
end
|
|
140
162
|
|
|
141
163
|
logger.info "Stopping worker"
|
|
@@ -150,6 +172,11 @@ module Delayed
|
|
|
150
172
|
signal_processor.join
|
|
151
173
|
end
|
|
152
174
|
|
|
175
|
+
if cancellation_checker
|
|
176
|
+
cancellation_checker.kill
|
|
177
|
+
cancellation_checker.join
|
|
178
|
+
end
|
|
179
|
+
|
|
153
180
|
@self_pipe&.each(&:close)
|
|
154
181
|
@self_pipe = nil
|
|
155
182
|
end
|
|
@@ -179,8 +206,11 @@ module Delayed
|
|
|
179
206
|
end
|
|
180
207
|
|
|
181
208
|
if job
|
|
209
|
+
@active_job_mutex.synchronize { @active_root_job = job }
|
|
182
210
|
configure_for_job(job) do
|
|
183
211
|
@job_count += perform(job)
|
|
212
|
+
@active_job_mutex.synchronize { @active_root_job = nil }
|
|
213
|
+
discard_pending_cancellation
|
|
184
214
|
|
|
185
215
|
if @max_job_count.positive? && @job_count >= @max_job_count
|
|
186
216
|
logger.debug "Max job count of #{@max_job_count} exceeded, dying"
|
|
@@ -205,25 +235,30 @@ module Delayed
|
|
|
205
235
|
end
|
|
206
236
|
end
|
|
207
237
|
|
|
208
|
-
def perform(job)
|
|
238
|
+
def perform(job, in_batch: false)
|
|
209
239
|
begin
|
|
210
240
|
count = 1
|
|
211
241
|
raise Delayed::Backend::JobExpired, "job expired at #{job.expires_at}" if job.expired?
|
|
242
|
+
raise CancelledError, "job #{job.id} cancelled at #{job.cancelled_at}" if job.cancelled?
|
|
212
243
|
|
|
213
244
|
self.class.lifecycle.run_callbacks(:perform, self, job) do
|
|
214
245
|
self.process_name = "run:#{Settings.worker_procname_prefix}#{job.id}:#{job.name}"
|
|
215
246
|
logger.info("Processing #{log_job(job, :long)}")
|
|
216
247
|
runtime = Benchmark.realtime do
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
248
|
+
# cancellation is only delivered while the job's own code is running, so it can't
|
|
249
|
+
# interrupt our bookkeeping (destroying the job, or failing it in a rescue below)
|
|
250
|
+
Thread.handle_interrupt(CancelledError => :immediate) do
|
|
251
|
+
if job.batch?
|
|
252
|
+
# each job in the batch will have perform called on it, so we don't
|
|
253
|
+
# need a timeout around this
|
|
254
|
+
count = perform_batch(job)
|
|
255
|
+
else
|
|
256
|
+
job.invoke_job
|
|
257
|
+
end
|
|
223
258
|
end
|
|
224
259
|
job.destroy
|
|
225
260
|
end
|
|
226
|
-
logger.info("Completed #{log_job(job, :short)} #{format("%.0fms",
|
|
261
|
+
logger.info("Completed #{log_job(job, :short)} #{format("%.0fms", runtime * 1000)}")
|
|
227
262
|
end
|
|
228
263
|
rescue ::Delayed::RetriableError => e
|
|
229
264
|
can_retry = job.attempts + 1 < job.inferred_max_attempts
|
|
@@ -235,6 +270,16 @@ module Delayed
|
|
|
235
270
|
# There wasn't really a failure here so no callbacks and whatnot needed,
|
|
236
271
|
# still reschedule the job though.
|
|
237
272
|
job.reschedule(e)
|
|
273
|
+
rescue CancelledError => e
|
|
274
|
+
logger.debug "Job #{job.id} was cancelled"
|
|
275
|
+
self.class.lifecycle.run_callbacks(:error, self, job, e) do
|
|
276
|
+
job.last_error = e.message
|
|
277
|
+
# Do not call on_failure, as a cancelled job must not be simply unlocked
|
|
278
|
+
# and thus the behavior switching that method supports must be ignored
|
|
279
|
+
job.permanent_failure(e)
|
|
280
|
+
end
|
|
281
|
+
# cancellation applies to the whole batch, so don't move on to the next job in it
|
|
282
|
+
raise if in_batch
|
|
238
283
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
239
284
|
if e.is_a?(NoMemoryError)
|
|
240
285
|
GC.start # try and free up some memory before reporting the error
|
|
@@ -256,12 +301,49 @@ module Delayed
|
|
|
256
301
|
job.source = parent_job.source
|
|
257
302
|
job.create_and_lock!(name)
|
|
258
303
|
configure_for_job(job) do
|
|
259
|
-
|
|
304
|
+
# we're inside the batch's :immediate block; mask again so the sub-job's own bookkeeping
|
|
305
|
+
# is protected. a cancellation that arrives during it is raised once this block exits,
|
|
306
|
+
# which cancels the rest of the batch
|
|
307
|
+
Thread.handle_interrupt(CancelledError => :never) do
|
|
308
|
+
perform(job, in_batch: true)
|
|
309
|
+
end
|
|
260
310
|
end
|
|
261
311
|
end
|
|
262
312
|
batch.items.size
|
|
263
313
|
end
|
|
264
314
|
|
|
315
|
+
def cancel_active_job_if_cancelled(work_thread)
|
|
316
|
+
job = @active_root_job
|
|
317
|
+
return unless job
|
|
318
|
+
|
|
319
|
+
# this runs on a background thread; don't hold a connection from the pool between checks
|
|
320
|
+
cancelled_at = Delayed::Job.connection_pool.with_connection do
|
|
321
|
+
Delayed::Job.where(id: job.id).pick(:cancelled_at)
|
|
322
|
+
end
|
|
323
|
+
return unless cancelled_at && cancelled_at <= Delayed::Job.db_time_now
|
|
324
|
+
|
|
325
|
+
@active_job_mutex.synchronize do
|
|
326
|
+
return unless @active_root_job.equal?(job)
|
|
327
|
+
|
|
328
|
+
logger.debug "Active job #{job.id} has been cancelled"
|
|
329
|
+
work_thread.raise(CancelledError, "job #{job.id} cancelled at #{cancelled_at}")
|
|
330
|
+
end
|
|
331
|
+
rescue => e
|
|
332
|
+
logger.error("Error while checking for job cancellation: #{e.inspect}")
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# A cancellation that arrives after a job finishes is deferred by handle_interrupt,
|
|
336
|
+
# and would otherwise be raised as soon as the next job starts.
|
|
337
|
+
def discard_pending_cancellation
|
|
338
|
+
while Thread.pending_interrupt?(CancelledError)
|
|
339
|
+
begin
|
|
340
|
+
Thread.handle_interrupt(CancelledError => :immediate) { nil }
|
|
341
|
+
rescue CancelledError => e
|
|
342
|
+
logger.debug "Discarding cancellation for a job that already finished: #{e.message}"
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
265
347
|
def handle_failed_job(job, error)
|
|
266
348
|
job.last_error = "#{error.message}\n#{error.backtrace.join("\n")}"
|
|
267
349
|
logger.error("Failed with #{error.class} [#{error.message}] (#{job.attempts} attempts)")
|
data/lib/delayed_job.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inst-jobs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cody Cutrer
|
|
@@ -10,7 +10,7 @@ authors:
|
|
|
10
10
|
autorequire:
|
|
11
11
|
bindir: exe
|
|
12
12
|
cert_chain: []
|
|
13
|
-
date:
|
|
13
|
+
date: 2026-10-19 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: activerecord
|
|
@@ -116,258 +116,6 @@ dependencies:
|
|
|
116
116
|
- - ">="
|
|
117
117
|
- !ruby/object:Gem::Version
|
|
118
118
|
version: '6.0'
|
|
119
|
-
- !ruby/object:Gem::Dependency
|
|
120
|
-
name: bump
|
|
121
|
-
requirement: !ruby/object:Gem::Requirement
|
|
122
|
-
requirements:
|
|
123
|
-
- - ">="
|
|
124
|
-
- !ruby/object:Gem::Version
|
|
125
|
-
version: '0'
|
|
126
|
-
type: :development
|
|
127
|
-
prerelease: false
|
|
128
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
129
|
-
requirements:
|
|
130
|
-
- - ">="
|
|
131
|
-
- !ruby/object:Gem::Version
|
|
132
|
-
version: '0'
|
|
133
|
-
- !ruby/object:Gem::Dependency
|
|
134
|
-
name: database_cleaner
|
|
135
|
-
requirement: !ruby/object:Gem::Requirement
|
|
136
|
-
requirements:
|
|
137
|
-
- - "~>"
|
|
138
|
-
- !ruby/object:Gem::Version
|
|
139
|
-
version: '2.0'
|
|
140
|
-
type: :development
|
|
141
|
-
prerelease: false
|
|
142
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
143
|
-
requirements:
|
|
144
|
-
- - "~>"
|
|
145
|
-
- !ruby/object:Gem::Version
|
|
146
|
-
version: '2.0'
|
|
147
|
-
- !ruby/object:Gem::Dependency
|
|
148
|
-
name: database_cleaner-active_record
|
|
149
|
-
requirement: !ruby/object:Gem::Requirement
|
|
150
|
-
requirements:
|
|
151
|
-
- - "~>"
|
|
152
|
-
- !ruby/object:Gem::Version
|
|
153
|
-
version: '2.0'
|
|
154
|
-
type: :development
|
|
155
|
-
prerelease: false
|
|
156
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
157
|
-
requirements:
|
|
158
|
-
- - "~>"
|
|
159
|
-
- !ruby/object:Gem::Version
|
|
160
|
-
version: '2.0'
|
|
161
|
-
- !ruby/object:Gem::Dependency
|
|
162
|
-
name: debug
|
|
163
|
-
requirement: !ruby/object:Gem::Requirement
|
|
164
|
-
requirements:
|
|
165
|
-
- - ">="
|
|
166
|
-
- !ruby/object:Gem::Version
|
|
167
|
-
version: '0'
|
|
168
|
-
type: :development
|
|
169
|
-
prerelease: false
|
|
170
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
171
|
-
requirements:
|
|
172
|
-
- - ">="
|
|
173
|
-
- !ruby/object:Gem::Version
|
|
174
|
-
version: '0'
|
|
175
|
-
- !ruby/object:Gem::Dependency
|
|
176
|
-
name: diplomat
|
|
177
|
-
requirement: !ruby/object:Gem::Requirement
|
|
178
|
-
requirements:
|
|
179
|
-
- - "~>"
|
|
180
|
-
- !ruby/object:Gem::Version
|
|
181
|
-
version: 2.6.3
|
|
182
|
-
type: :development
|
|
183
|
-
prerelease: false
|
|
184
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
185
|
-
requirements:
|
|
186
|
-
- - "~>"
|
|
187
|
-
- !ruby/object:Gem::Version
|
|
188
|
-
version: 2.6.3
|
|
189
|
-
- !ruby/object:Gem::Dependency
|
|
190
|
-
name: mutex_m
|
|
191
|
-
requirement: !ruby/object:Gem::Requirement
|
|
192
|
-
requirements:
|
|
193
|
-
- - ">="
|
|
194
|
-
- !ruby/object:Gem::Version
|
|
195
|
-
version: '0'
|
|
196
|
-
type: :development
|
|
197
|
-
prerelease: false
|
|
198
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
199
|
-
requirements:
|
|
200
|
-
- - ">="
|
|
201
|
-
- !ruby/object:Gem::Version
|
|
202
|
-
version: '0'
|
|
203
|
-
- !ruby/object:Gem::Dependency
|
|
204
|
-
name: pg
|
|
205
|
-
requirement: !ruby/object:Gem::Requirement
|
|
206
|
-
requirements:
|
|
207
|
-
- - ">="
|
|
208
|
-
- !ruby/object:Gem::Version
|
|
209
|
-
version: '0'
|
|
210
|
-
type: :development
|
|
211
|
-
prerelease: false
|
|
212
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
213
|
-
requirements:
|
|
214
|
-
- - ">="
|
|
215
|
-
- !ruby/object:Gem::Version
|
|
216
|
-
version: '0'
|
|
217
|
-
- !ruby/object:Gem::Dependency
|
|
218
|
-
name: rack-test
|
|
219
|
-
requirement: !ruby/object:Gem::Requirement
|
|
220
|
-
requirements:
|
|
221
|
-
- - ">="
|
|
222
|
-
- !ruby/object:Gem::Version
|
|
223
|
-
version: '0'
|
|
224
|
-
type: :development
|
|
225
|
-
prerelease: false
|
|
226
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
227
|
-
requirements:
|
|
228
|
-
- - ">="
|
|
229
|
-
- !ruby/object:Gem::Version
|
|
230
|
-
version: '0'
|
|
231
|
-
- !ruby/object:Gem::Dependency
|
|
232
|
-
name: rake
|
|
233
|
-
requirement: !ruby/object:Gem::Requirement
|
|
234
|
-
requirements:
|
|
235
|
-
- - ">="
|
|
236
|
-
- !ruby/object:Gem::Version
|
|
237
|
-
version: '0'
|
|
238
|
-
type: :development
|
|
239
|
-
prerelease: false
|
|
240
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
241
|
-
requirements:
|
|
242
|
-
- - ">="
|
|
243
|
-
- !ruby/object:Gem::Version
|
|
244
|
-
version: '0'
|
|
245
|
-
- !ruby/object:Gem::Dependency
|
|
246
|
-
name: rspec
|
|
247
|
-
requirement: !ruby/object:Gem::Requirement
|
|
248
|
-
requirements:
|
|
249
|
-
- - "~>"
|
|
250
|
-
- !ruby/object:Gem::Version
|
|
251
|
-
version: '3.10'
|
|
252
|
-
type: :development
|
|
253
|
-
prerelease: false
|
|
254
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
255
|
-
requirements:
|
|
256
|
-
- - "~>"
|
|
257
|
-
- !ruby/object:Gem::Version
|
|
258
|
-
version: '3.10'
|
|
259
|
-
- !ruby/object:Gem::Dependency
|
|
260
|
-
name: rubocop-inst
|
|
261
|
-
requirement: !ruby/object:Gem::Requirement
|
|
262
|
-
requirements:
|
|
263
|
-
- - "~>"
|
|
264
|
-
- !ruby/object:Gem::Version
|
|
265
|
-
version: '1'
|
|
266
|
-
type: :development
|
|
267
|
-
prerelease: false
|
|
268
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
269
|
-
requirements:
|
|
270
|
-
- - "~>"
|
|
271
|
-
- !ruby/object:Gem::Version
|
|
272
|
-
version: '1'
|
|
273
|
-
- !ruby/object:Gem::Dependency
|
|
274
|
-
name: rubocop-rails
|
|
275
|
-
requirement: !ruby/object:Gem::Requirement
|
|
276
|
-
requirements:
|
|
277
|
-
- - "~>"
|
|
278
|
-
- !ruby/object:Gem::Version
|
|
279
|
-
version: '2.11'
|
|
280
|
-
type: :development
|
|
281
|
-
prerelease: false
|
|
282
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
283
|
-
requirements:
|
|
284
|
-
- - "~>"
|
|
285
|
-
- !ruby/object:Gem::Version
|
|
286
|
-
version: '2.11'
|
|
287
|
-
- !ruby/object:Gem::Dependency
|
|
288
|
-
name: rubocop-rake
|
|
289
|
-
requirement: !ruby/object:Gem::Requirement
|
|
290
|
-
requirements:
|
|
291
|
-
- - "~>"
|
|
292
|
-
- !ruby/object:Gem::Version
|
|
293
|
-
version: '0.6'
|
|
294
|
-
type: :development
|
|
295
|
-
prerelease: false
|
|
296
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
297
|
-
requirements:
|
|
298
|
-
- - "~>"
|
|
299
|
-
- !ruby/object:Gem::Version
|
|
300
|
-
version: '0.6'
|
|
301
|
-
- !ruby/object:Gem::Dependency
|
|
302
|
-
name: rubocop-rspec
|
|
303
|
-
requirement: !ruby/object:Gem::Requirement
|
|
304
|
-
requirements:
|
|
305
|
-
- - "~>"
|
|
306
|
-
- !ruby/object:Gem::Version
|
|
307
|
-
version: '3.0'
|
|
308
|
-
type: :development
|
|
309
|
-
prerelease: false
|
|
310
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
311
|
-
requirements:
|
|
312
|
-
- - "~>"
|
|
313
|
-
- !ruby/object:Gem::Version
|
|
314
|
-
version: '3.0'
|
|
315
|
-
- !ruby/object:Gem::Dependency
|
|
316
|
-
name: sinatra
|
|
317
|
-
requirement: !ruby/object:Gem::Requirement
|
|
318
|
-
requirements:
|
|
319
|
-
- - ">="
|
|
320
|
-
- !ruby/object:Gem::Version
|
|
321
|
-
version: '0'
|
|
322
|
-
type: :development
|
|
323
|
-
prerelease: false
|
|
324
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
325
|
-
requirements:
|
|
326
|
-
- - ">="
|
|
327
|
-
- !ruby/object:Gem::Version
|
|
328
|
-
version: '0'
|
|
329
|
-
- !ruby/object:Gem::Dependency
|
|
330
|
-
name: sinatra-contrib
|
|
331
|
-
requirement: !ruby/object:Gem::Requirement
|
|
332
|
-
requirements:
|
|
333
|
-
- - "~>"
|
|
334
|
-
- !ruby/object:Gem::Version
|
|
335
|
-
version: '3.1'
|
|
336
|
-
type: :development
|
|
337
|
-
prerelease: false
|
|
338
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
339
|
-
requirements:
|
|
340
|
-
- - "~>"
|
|
341
|
-
- !ruby/object:Gem::Version
|
|
342
|
-
version: '3.1'
|
|
343
|
-
- !ruby/object:Gem::Dependency
|
|
344
|
-
name: timecop
|
|
345
|
-
requirement: !ruby/object:Gem::Requirement
|
|
346
|
-
requirements:
|
|
347
|
-
- - "~>"
|
|
348
|
-
- !ruby/object:Gem::Version
|
|
349
|
-
version: '0.9'
|
|
350
|
-
type: :development
|
|
351
|
-
prerelease: false
|
|
352
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
353
|
-
requirements:
|
|
354
|
-
- - "~>"
|
|
355
|
-
- !ruby/object:Gem::Version
|
|
356
|
-
version: '0.9'
|
|
357
|
-
- !ruby/object:Gem::Dependency
|
|
358
|
-
name: webmock
|
|
359
|
-
requirement: !ruby/object:Gem::Requirement
|
|
360
|
-
requirements:
|
|
361
|
-
- - ">="
|
|
362
|
-
- !ruby/object:Gem::Version
|
|
363
|
-
version: '0'
|
|
364
|
-
type: :development
|
|
365
|
-
prerelease: false
|
|
366
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
367
|
-
requirements:
|
|
368
|
-
- - ">="
|
|
369
|
-
- !ruby/object:Gem::Version
|
|
370
|
-
version: '0'
|
|
371
119
|
description:
|
|
372
120
|
email:
|
|
373
121
|
- cody@instructure.com
|
|
@@ -419,6 +167,7 @@ files:
|
|
|
419
167
|
- db/migrate/20220203063200_remove_old_singleton_index.rb
|
|
420
168
|
- db/migrate/20220328152900_add_failed_jobs_indicies.rb
|
|
421
169
|
- db/migrate/20220519204546_add_requeued_job_id_to_failed_jobs.rb
|
|
170
|
+
- db/migrate/20261012154653_add_cancelled_at_to_delayed_jobs.rb
|
|
422
171
|
- exe/inst_jobs
|
|
423
172
|
- lib/delayed/backend/active_record.rb
|
|
424
173
|
- lib/delayed/backend/base.rb
|
|
@@ -444,6 +193,7 @@ files:
|
|
|
444
193
|
- lib/delayed/server/views/layout.erb
|
|
445
194
|
- lib/delayed/settings.rb
|
|
446
195
|
- lib/delayed/testing.rb
|
|
196
|
+
- lib/delayed/util.rb
|
|
447
197
|
- lib/delayed/version.rb
|
|
448
198
|
- lib/delayed/work_queue/in_process.rb
|
|
449
199
|
- lib/delayed/work_queue/parent_process.rb
|