workhorse 1.5.2 → 2.0.0.rc1
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/.github/workflows/ruby.yml +137 -1
- data/CHANGELOG.md +150 -0
- data/Gemfile +16 -1
- data/README.md +316 -72
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/bin/rubocop +5 -1
- data/lib/generators/workhorse/install_generator.rb +10 -1
- data/lib/generators/workhorse/templates/config/initializers/workhorse.rb +55 -0
- data/lib/generators/workhorse/templates/create_table_jobs.rb +15 -2
- data/lib/generators/workhorse/templates/create_table_workhorse_schedules.rb +42 -0
- data/lib/workhorse/daemon/shell_handler.rb +4 -1
- data/lib/workhorse/daemon.rb +57 -7
- data/lib/workhorse/db_job.rb +98 -8
- data/lib/workhorse/enqueuer.rb +51 -8
- data/lib/workhorse/jobs/cleanup_succeeded_jobs.rb +26 -8
- data/lib/workhorse/jobs/detect_late_schedules_job.rb +59 -0
- data/lib/workhorse/notifiers/base.rb +55 -0
- data/lib/workhorse/notifiers/file_system.rb +66 -0
- data/lib/workhorse/notifiers/none.rb +8 -0
- data/lib/workhorse/notifiers/redis.rb +227 -0
- data/lib/workhorse/performer.rb +29 -2
- data/lib/workhorse/poller.rb +303 -21
- data/lib/workhorse/pool.rb +12 -6
- data/lib/workhorse/schedule.rb +288 -0
- data/lib/workhorse/schedules.rb +197 -0
- data/lib/workhorse/worker.rb +102 -31
- data/lib/workhorse.rb +136 -0
- data/test/lib/db_schema.rb +36 -3
- data/test/lib/jobs.rb +29 -0
- data/test/lib/test_helper.rb +113 -20
- data/test/workhorse/daemon_test.rb +33 -0
- data/test/workhorse/db_job_test.rb +2 -4
- data/test/workhorse/notifier_test.rb +487 -0
- data/test/workhorse/performer_test.rb +7 -9
- data/test/workhorse/poller_test.rb +97 -23
- data/test/workhorse/schedule_test.rb +967 -0
- data/test/workhorse/worker_test.rb +201 -76
- data/workhorse.gemspec +6 -5
- metadata +29 -3
|
@@ -1,18 +1,107 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
2
|
|
|
3
3
|
class Workhorse::WorkerTest < WorkhorseTest
|
|
4
|
+
# Guards the interleaving that deadlocks the worker if #shutdown holds the
|
|
5
|
+
# mutex while waiting for the poller thread: that thread may be in #perform
|
|
6
|
+
# waiting for the same mutex, having just committed the lock on a job.
|
|
7
|
+
def test_shutdown_while_the_poller_posts_a_job
|
|
8
|
+
job = Workhorse.enqueue BasicJob.new(sleep_time: 0)
|
|
9
|
+
w = Workhorse::Worker.new(polling_interval: 60, pool_size: 1)
|
|
10
|
+
|
|
11
|
+
# As the poller would have, in a transaction that has already committed.
|
|
12
|
+
job.mark_locked!(w.id)
|
|
13
|
+
|
|
14
|
+
at_perform = Concurrent::Event.new
|
|
15
|
+
posted = Concurrent::Event.new
|
|
16
|
+
|
|
17
|
+
w.poller.define_singleton_method(:poll) do
|
|
18
|
+
next if @posted_once
|
|
19
|
+
|
|
20
|
+
@posted_once = true
|
|
21
|
+
at_perform.set
|
|
22
|
+
# Give the shutdown below time to get as far as waiting for this thread.
|
|
23
|
+
Kernel.sleep 0.3
|
|
24
|
+
worker.perform(job.id)
|
|
25
|
+
posted.set
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
w.start
|
|
29
|
+
|
|
30
|
+
assert at_perform.wait(5), 'poller did not reach the job posting'
|
|
31
|
+
|
|
32
|
+
Timeout.timeout(15) do
|
|
33
|
+
w.shutdown
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
assert posted.wait(5), 'poller thread never returned from #perform'
|
|
37
|
+
|
|
38
|
+
job.reload
|
|
39
|
+
|
|
40
|
+
assert_equal 'waiting', job.state
|
|
41
|
+
assert_nil job.locked_at
|
|
42
|
+
assert_nil job.locked_by
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# #shutdown promises that running jobs have finished once it returns. Both
|
|
46
|
+
# TERM and INT are sent by the daemon's stop, so a repeat call has to keep
|
|
47
|
+
# that promise too rather than returning while the first one is still
|
|
48
|
+
# waiting for the pool.
|
|
49
|
+
def test_a_repeated_shutdown_waits_for_the_running_job
|
|
50
|
+
job = Workhorse.enqueue BasicJob.new(sleep_time: 2)
|
|
51
|
+
w = Workhorse::Worker.new(polling_interval: 0.2, pool_size: 1)
|
|
52
|
+
w.start
|
|
53
|
+
|
|
54
|
+
with_retries(60, interval: 0.05) { assert_equal 'started', job.reload.state }
|
|
55
|
+
|
|
56
|
+
states = Concurrent::Array.new
|
|
57
|
+
first = Thread.new { w.shutdown }
|
|
58
|
+
sleep 0.3
|
|
59
|
+
second = Thread.new do
|
|
60
|
+
w.shutdown
|
|
61
|
+
states << job.reload.state
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
Timeout.timeout(15) { second.join }
|
|
65
|
+
|
|
66
|
+
assert_equal ['succeeded'], states.to_a,
|
|
67
|
+
'a repeated shutdown must not return while a job is still running'
|
|
68
|
+
assert_equal :shutdown, w.state
|
|
69
|
+
ensure
|
|
70
|
+
first&.join
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_shutdown_of_a_worker_that_was_never_started
|
|
74
|
+
w = Workhorse::Worker.new(polling_interval: 60)
|
|
75
|
+
|
|
76
|
+
Timeout.timeout(5) { w.shutdown }
|
|
77
|
+
|
|
78
|
+
assert_equal :initialized, w.state
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# A job that another worker already reset - or that never got locked - must
|
|
82
|
+
# be left alone rather than reset a second time.
|
|
83
|
+
def test_a_job_that_is_no_longer_locked_is_not_released
|
|
84
|
+
job = Workhorse.enqueue BasicJob.new(sleep_time: 0)
|
|
85
|
+
w = Workhorse::Worker.new(polling_interval: 60, pool_size: 1)
|
|
86
|
+
w.start
|
|
87
|
+
w.shutdown
|
|
88
|
+
|
|
89
|
+
job.update_columns(state: 'succeeded')
|
|
90
|
+
|
|
91
|
+
w.send(:release, job.id)
|
|
92
|
+
|
|
93
|
+
assert_equal 'succeeded', job.reload.state
|
|
94
|
+
end
|
|
95
|
+
|
|
4
96
|
def test_idle
|
|
5
97
|
with_worker(pool_size: 5, polling_interval: 0.2) do |w|
|
|
6
98
|
assert_equal 5, w.idle
|
|
7
99
|
|
|
8
|
-
|
|
9
|
-
Workhorse.enqueue BasicJob.new(sleep_time:
|
|
100
|
+
# Long enough to be seen running at the 0.1s the retries look.
|
|
101
|
+
Workhorse.enqueue BasicJob.new(sleep_time: 1)
|
|
10
102
|
|
|
11
|
-
|
|
12
|
-
assert_equal
|
|
13
|
-
|
|
14
|
-
sleep 0.2
|
|
15
|
-
assert_equal 5, w.idle
|
|
103
|
+
with_retries { assert_equal 4, w.idle }
|
|
104
|
+
with_retries { assert_equal 5, w.idle }
|
|
16
105
|
end
|
|
17
106
|
end
|
|
18
107
|
|
|
@@ -30,42 +119,37 @@ class Workhorse::WorkerTest < WorkhorseTest
|
|
|
30
119
|
end
|
|
31
120
|
|
|
32
121
|
def test_perform
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
122
|
+
# Enqueued before the worker starts, as a running one may pick the job up
|
|
123
|
+
# before its state can be looked at.
|
|
124
|
+
Workhorse.enqueue BasicJob.new(sleep_time: 0.1)
|
|
125
|
+
assert_equal 'waiting', Workhorse::DbJob.first.state
|
|
37
126
|
|
|
38
|
-
|
|
127
|
+
work_until(pool_size: 5, polling_interval: 0.2) do
|
|
128
|
+
assert_equal 'succeeded', Workhorse::DbJob.first.state
|
|
39
129
|
end
|
|
40
|
-
|
|
41
|
-
assert_equal 'succeeded', Workhorse::DbJob.first.state
|
|
42
130
|
end
|
|
43
131
|
|
|
44
132
|
def test_params
|
|
45
133
|
BasicJob.results.clear
|
|
46
134
|
|
|
47
135
|
Workhorse.enqueue BasicJob.new(some_param: 5, sleep_time: 0)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
assert_equal 'succeeded', Workhorse::DbJob.first.state
|
|
136
|
+
work_until(pool_size: 5, polling_interval: 0.2) { assert_equal 'succeeded', Workhorse::DbJob.first.state }
|
|
51
137
|
|
|
52
138
|
assert_equal 1, BasicJob.results.count
|
|
53
139
|
assert_equal 5, BasicJob.results.first
|
|
54
140
|
end
|
|
55
141
|
|
|
56
142
|
def test_term
|
|
57
|
-
with_worker(polling_interval: 0.2) do |w|
|
|
143
|
+
with_worker(polling_interval: 0.2, auto_terminate: true) do |w|
|
|
58
144
|
Process.kill 'TERM', Process.pid
|
|
59
|
-
|
|
60
|
-
w.assert_state! :shutdown
|
|
145
|
+
with_retries { w.assert_state! :shutdown }
|
|
61
146
|
end
|
|
62
147
|
end
|
|
63
148
|
|
|
64
149
|
def test_int
|
|
65
|
-
with_worker(polling_interval: 0.2) do |w|
|
|
150
|
+
with_worker(polling_interval: 0.2, auto_terminate: true) do |w|
|
|
66
151
|
Process.kill 'INT', Process.pid
|
|
67
|
-
|
|
68
|
-
w.assert_state! :shutdown
|
|
152
|
+
with_retries { w.assert_state! :shutdown }
|
|
69
153
|
end
|
|
70
154
|
end
|
|
71
155
|
|
|
@@ -82,6 +166,36 @@ class Workhorse::WorkerTest < WorkhorseTest
|
|
|
82
166
|
FileUtils.rm_f Workhorse::Worker.shutdown_file_for(Process.pid)
|
|
83
167
|
end
|
|
84
168
|
|
|
169
|
+
# A fresh checkout or deployment has no tmp/pids. The soft restart touches
|
|
170
|
+
# its shutdown file there after it has stopped accepting jobs, so failing on
|
|
171
|
+
# it used to leave a worker that took no jobs and never exited.
|
|
172
|
+
def test_soft_restart_without_a_pids_directory
|
|
173
|
+
FileUtils.rm_rf(Rails.root.join('tmp', 'pids'))
|
|
174
|
+
|
|
175
|
+
with_worker(pool_size: 1, polling_interval: 0.2) do |w|
|
|
176
|
+
Process.kill 'USR1', Process.pid
|
|
177
|
+
|
|
178
|
+
with_retries { assert_equal :shutdown, w.state }
|
|
179
|
+
assert File.exist?(Workhorse::Worker.shutdown_file_for(Process.pid))
|
|
180
|
+
end
|
|
181
|
+
ensure
|
|
182
|
+
FileUtils.mkdir_p(Rails.root.join('tmp', 'pids'))
|
|
183
|
+
FileUtils.rm_f Workhorse::Worker.shutdown_file_for(Process.pid)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_heartbeat_without_a_pids_directory
|
|
187
|
+
FileUtils.rm_rf(Rails.root.join('tmp', 'pids'))
|
|
188
|
+
ENV['WORKHORSE_DAEMON_WORKER_ID'] = '42'
|
|
189
|
+
|
|
190
|
+
Workhorse::Worker.new.heartbeat!
|
|
191
|
+
|
|
192
|
+
assert File.exist?(Workhorse::Worker.heartbeat_file_for('42'))
|
|
193
|
+
ensure
|
|
194
|
+
ENV.delete('WORKHORSE_DAEMON_WORKER_ID')
|
|
195
|
+
FileUtils.mkdir_p(Rails.root.join('tmp', 'pids'))
|
|
196
|
+
FileUtils.rm_f(Workhorse::Worker.heartbeat_file_for('42'))
|
|
197
|
+
end
|
|
198
|
+
|
|
85
199
|
def test_soft_restart_when_busy_waits_for_job
|
|
86
200
|
with_worker(pool_size: 1, polling_interval: 0.2) do |w|
|
|
87
201
|
Workhorse.enqueue BasicJob.new(sleep_time: 0.5)
|
|
@@ -144,7 +258,7 @@ class Workhorse::WorkerTest < WorkhorseTest
|
|
|
144
258
|
end
|
|
145
259
|
|
|
146
260
|
def test_soft_restart_ignored_during_shutdown
|
|
147
|
-
with_worker(pool_size: 1, polling_interval: 0.2) do |w|
|
|
261
|
+
with_worker(pool_size: 1, polling_interval: 0.2, auto_terminate: true) do |w|
|
|
148
262
|
Process.kill 'TERM', Process.pid
|
|
149
263
|
with_retries { assert_equal :shutdown, w.state }
|
|
150
264
|
|
|
@@ -160,80 +274,61 @@ class Workhorse::WorkerTest < WorkhorseTest
|
|
|
160
274
|
|
|
161
275
|
def test_no_queues
|
|
162
276
|
enqueue_in_multiple_queues
|
|
163
|
-
work 0.2, polling_interval: 0.2
|
|
164
277
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
assert_equal 'succeeded', jobs[2].state
|
|
278
|
+
work_until(pool_size: 5, polling_interval: 0.2) do
|
|
279
|
+
assert_equal %w[succeeded succeeded succeeded], jobs_by_queue.map(&:state)
|
|
280
|
+
end
|
|
169
281
|
end
|
|
170
282
|
|
|
171
283
|
def test_nil_queue
|
|
172
284
|
enqueue_in_multiple_queues
|
|
173
|
-
work 0.2, queues: [nil], polling_interval: 0.2
|
|
174
285
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
286
|
+
# A job the worker wrongly took would be taken in the same poll as the
|
|
287
|
+
# one it rightly took, so is locked by the time that one has succeeded.
|
|
288
|
+
work_until(queues: [nil], pool_size: 5, polling_interval: 0.2) do
|
|
289
|
+
assert_equal %w[succeeded waiting waiting], jobs_by_queue.map(&:state)
|
|
290
|
+
end
|
|
179
291
|
end
|
|
180
292
|
|
|
181
293
|
def test_queues_with_nil
|
|
182
294
|
enqueue_in_multiple_queues
|
|
183
|
-
work 0.2, queues: [nil, :q1], polling_interval: 0.2
|
|
184
295
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
assert_equal 'waiting', jobs[2].state
|
|
296
|
+
work_until(queues: [nil, :q1], pool_size: 5, polling_interval: 0.2) do
|
|
297
|
+
assert_equal %w[succeeded succeeded waiting], jobs_by_queue.map(&:state)
|
|
298
|
+
end
|
|
189
299
|
end
|
|
190
300
|
|
|
191
301
|
def test_queues_without_nil
|
|
192
302
|
enqueue_in_multiple_queues
|
|
193
|
-
work 0.2, queues: %i[q1 q2], polling_interval: 0.2
|
|
194
303
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
assert_equal 'succeeded', jobs[2].state
|
|
304
|
+
work_until(queues: %i[q1 q2], pool_size: 5, polling_interval: 0.2) do
|
|
305
|
+
assert_equal %w[waiting succeeded succeeded], jobs_by_queue.map(&:state)
|
|
306
|
+
end
|
|
199
307
|
end
|
|
200
308
|
|
|
201
309
|
def test_queue_not_parallel
|
|
202
310
|
Workhorse::DbJob.delete_all
|
|
203
|
-
Workhorse.enqueue BasicJob.new(sleep_time:
|
|
204
|
-
Workhorse.enqueue BasicJob.new(sleep_time:
|
|
311
|
+
first = Workhorse.enqueue BasicJob.new(sleep_time: 2), queue: :q1
|
|
312
|
+
second = Workhorse.enqueue BasicJob.new(sleep_time: 2), queue: :q1
|
|
205
313
|
|
|
206
|
-
|
|
207
|
-
jobs = Workhorse::DbJob.all.to_a
|
|
208
|
-
assert_equal 'succeeded', jobs[0].state
|
|
209
|
-
assert_equal 'waiting', jobs[1].state
|
|
314
|
+
assert_queued_one_at_a_time [first], [second]
|
|
210
315
|
end
|
|
211
316
|
|
|
212
317
|
def test_multiple_queued_same_queue
|
|
213
318
|
# One queue
|
|
214
|
-
Workhorse.enqueue BasicJob.new(sleep_time:
|
|
215
|
-
Workhorse.enqueue BasicJob.new(sleep_time:
|
|
319
|
+
first = Workhorse.enqueue BasicJob.new(sleep_time: 2), queue: :q1
|
|
320
|
+
second = Workhorse.enqueue BasicJob.new(sleep_time: 2), queue: :q1
|
|
216
321
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
jobs = Workhorse::DbJob.all.to_a
|
|
220
|
-
assert_equal 'succeeded', jobs[0].state
|
|
221
|
-
assert_equal 'waiting', jobs[1].state
|
|
322
|
+
assert_queued_one_at_a_time [first], [second]
|
|
222
323
|
|
|
223
324
|
# Two queues
|
|
224
325
|
Workhorse::DbJob.delete_all
|
|
225
|
-
Workhorse.enqueue BasicJob.new(sleep_time:
|
|
226
|
-
Workhorse.enqueue BasicJob.new(sleep_time:
|
|
227
|
-
Workhorse.enqueue BasicJob.new(sleep_time:
|
|
228
|
-
Workhorse.enqueue BasicJob.new(sleep_time:
|
|
229
|
-
|
|
230
|
-
work 0.2, polling_interval: 0.2
|
|
326
|
+
q1_first = Workhorse.enqueue BasicJob.new(sleep_time: 2), queue: :q1
|
|
327
|
+
q1_second = Workhorse.enqueue BasicJob.new(sleep_time: 2), queue: :q1
|
|
328
|
+
q2_first = Workhorse.enqueue BasicJob.new(sleep_time: 2), queue: :q2
|
|
329
|
+
q2_second = Workhorse.enqueue BasicJob.new(sleep_time: 2), queue: :q2
|
|
231
330
|
|
|
232
|
-
|
|
233
|
-
assert_equal 'succeeded', jobs[0].state
|
|
234
|
-
assert_equal 'waiting', jobs[1].state
|
|
235
|
-
assert_equal 'succeeded', jobs[2].state
|
|
236
|
-
assert_equal 'waiting', jobs[3].state
|
|
331
|
+
assert_queued_one_at_a_time [q1_first, q2_first], [q1_second, q2_second]
|
|
237
332
|
end
|
|
238
333
|
|
|
239
334
|
def test_order_with_priorities
|
|
@@ -246,7 +341,9 @@ class Workhorse::WorkerTest < WorkhorseTest
|
|
|
246
341
|
Workhorse.enqueue BasicJob.new(some_param: 1, sleep_time: 0), priority: 0
|
|
247
342
|
|
|
248
343
|
BasicJob.results.clear
|
|
249
|
-
|
|
344
|
+
work_until(pool_size: 1, polling_interval: 0.1) do
|
|
345
|
+
assert_equal 6, BasicJob.results.size
|
|
346
|
+
end
|
|
250
347
|
assert_equal (1..6).to_a, BasicJob.results
|
|
251
348
|
end
|
|
252
349
|
|
|
@@ -260,17 +357,23 @@ class Workhorse::WorkerTest < WorkhorseTest
|
|
|
260
357
|
end
|
|
261
358
|
|
|
262
359
|
def test_perform_at
|
|
263
|
-
Workhorse.enqueue BasicJob.new(sleep_time: 0), perform_at: Time.now
|
|
264
|
-
Workhorse.enqueue BasicJob.new(sleep_time: 0), perform_at: Time.now + 600
|
|
265
|
-
work 0.1, polling_interval: 0.1
|
|
360
|
+
due = Workhorse.enqueue BasicJob.new(sleep_time: 0), perform_at: Time.now
|
|
361
|
+
later = Workhorse.enqueue BasicJob.new(sleep_time: 0), perform_at: Time.now + 600
|
|
266
362
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
363
|
+
work_until(polling_interval: 0.1) do
|
|
364
|
+
assert_equal 'succeeded', due.reload.state
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
assert_equal 'waiting', later.reload.state
|
|
270
368
|
end
|
|
271
369
|
|
|
272
370
|
def test_controlled_shutdown
|
|
273
|
-
|
|
371
|
+
# Relative to what this process already uses, as the daemon's workers are
|
|
372
|
+
# forks of it: a fixed 100MB is exceeded from the start once the Oracle
|
|
373
|
+
# client library is loaded, and the worker then stops before its first job.
|
|
374
|
+
# MemHungryJob allocates 250MB, so it still crosses the line.
|
|
375
|
+
baseline_mb = `ps -p #{Process.pid} -o rss=`.strip.to_i / 1024
|
|
376
|
+
Workhorse.max_worker_memory_mb = baseline_mb + 100
|
|
274
377
|
with_daemon do
|
|
275
378
|
pid = with_retries do
|
|
276
379
|
pid = daemon.workers.first.pid
|
|
@@ -368,4 +471,26 @@ class Workhorse::WorkerTest < WorkhorseTest
|
|
|
368
471
|
Workhorse.enqueue BasicJob.new(some_param: :q1), queue: :q1
|
|
369
472
|
Workhorse.enqueue BasicJob.new(some_param: :q2), queue: :q2
|
|
370
473
|
end
|
|
474
|
+
|
|
475
|
+
# Jobs ordered by queue, the job without a queue first. Sorted here rather
|
|
476
|
+
# than with ORDER BY, which puts NULL first on MySQL and last on Oracle.
|
|
477
|
+
# Within a queue, in the order they were enqueued.
|
|
478
|
+
def jobs_by_queue
|
|
479
|
+
return Workhorse::DbJob.all.sort_by { |job| [job.queue.nil? ? 0 : 1, job.queue.to_s, job.id] }
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# Asserts that while the first job of each queue runs, the ones queued behind
|
|
483
|
+
# it wait. Looked at while they run rather than after a fixed time: the job
|
|
484
|
+
# behind is rightly taken as soon as the one ahead has finished.
|
|
485
|
+
def assert_queued_one_at_a_time(firsts, followers)
|
|
486
|
+
with_worker(pool_size: 5, polling_interval: 0.2, auto_terminate: false) do
|
|
487
|
+
with_retries { assert_equal(%w[started] * firsts.size, firsts.map { |job| job.reload.state }) }
|
|
488
|
+
|
|
489
|
+
# Room for further polls, each a chance to take a follower wrongly, and
|
|
490
|
+
# well short of the 2s the jobs ahead take.
|
|
491
|
+
sleep 0.5
|
|
492
|
+
|
|
493
|
+
assert_equal(%w[waiting] * followers.size, followers.map { |job| job.reload.state })
|
|
494
|
+
end
|
|
495
|
+
end
|
|
371
496
|
end
|
data/workhorse.gemspec
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: workhorse
|
|
2
|
+
# stub: workhorse 2.0.0.rc1 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "workhorse".freeze
|
|
6
|
-
s.version = "
|
|
6
|
+
s.version = "2.0.0.rc1".freeze
|
|
7
7
|
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
9
9
|
s.require_paths = ["lib".freeze]
|
|
10
10
|
s.authors = ["Sitrox".freeze]
|
|
11
|
-
s.date = "2026-
|
|
12
|
-
s.files = [".github/workflows/ruby.yml".freeze, ".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, "CHANGELOG.md".freeze, "FAQ.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "RUBY_VERSION".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/rubocop".freeze, "lib/active_job/queue_adapters/workhorse_adapter.rb".freeze, "lib/generators/workhorse/install_generator.rb".freeze, "lib/generators/workhorse/templates/bin/workhorse.rb".freeze, "lib/generators/workhorse/templates/config/initializers/workhorse.rb".freeze, "lib/generators/workhorse/templates/create_table_jobs.rb".freeze, "lib/workhorse.rb".freeze, "lib/workhorse/active_job_extension.rb".freeze, "lib/workhorse/daemon.rb".freeze, "lib/workhorse/daemon/shell_handler.rb".freeze, "lib/workhorse/db_job.rb".freeze, "lib/workhorse/enqueuer.rb".freeze, "lib/workhorse/jobs/cleanup_succeeded_jobs.rb".freeze, "lib/workhorse/jobs/detect_stale_jobs_job.rb".freeze, "lib/workhorse/jobs/run_active_job.rb".freeze, "lib/workhorse/jobs/run_rails_op.rb".freeze, "lib/workhorse/performer.rb".freeze, "lib/workhorse/poller.rb".freeze, "lib/workhorse/pool.rb".freeze, "lib/workhorse/scoped_env.rb".freeze, "lib/workhorse/worker.rb".freeze, "test/active_job/queue_adapters/workhorse_adapter_test.rb".freeze, "test/lib/db_schema.rb".freeze, "test/lib/jobs.rb".freeze, "test/lib/test_helper.rb".freeze, "test/workhorse/daemon_test.rb".freeze, "test/workhorse/db_job_test.rb".freeze, "test/workhorse/enqueuer_test.rb".freeze, "test/workhorse/performer_test.rb".freeze, "test/workhorse/poller_test.rb".freeze, "test/workhorse/pool_test.rb".freeze, "test/workhorse/worker_test.rb".freeze, "test/workhorse/yjit_test.rb".freeze, "workhorse.gemspec".freeze]
|
|
11
|
+
s.date = "2026-09-30"
|
|
12
|
+
s.files = [".github/workflows/ruby.yml".freeze, ".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, "CHANGELOG.md".freeze, "FAQ.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "RUBY_VERSION".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/rubocop".freeze, "lib/active_job/queue_adapters/workhorse_adapter.rb".freeze, "lib/generators/workhorse/install_generator.rb".freeze, "lib/generators/workhorse/templates/bin/workhorse.rb".freeze, "lib/generators/workhorse/templates/config/initializers/workhorse.rb".freeze, "lib/generators/workhorse/templates/create_table_jobs.rb".freeze, "lib/generators/workhorse/templates/create_table_workhorse_schedules.rb".freeze, "lib/workhorse.rb".freeze, "lib/workhorse/active_job_extension.rb".freeze, "lib/workhorse/daemon.rb".freeze, "lib/workhorse/daemon/shell_handler.rb".freeze, "lib/workhorse/db_job.rb".freeze, "lib/workhorse/enqueuer.rb".freeze, "lib/workhorse/jobs/cleanup_succeeded_jobs.rb".freeze, "lib/workhorse/jobs/detect_late_schedules_job.rb".freeze, "lib/workhorse/jobs/detect_stale_jobs_job.rb".freeze, "lib/workhorse/jobs/run_active_job.rb".freeze, "lib/workhorse/jobs/run_rails_op.rb".freeze, "lib/workhorse/notifiers/base.rb".freeze, "lib/workhorse/notifiers/file_system.rb".freeze, "lib/workhorse/notifiers/none.rb".freeze, "lib/workhorse/notifiers/redis.rb".freeze, "lib/workhorse/performer.rb".freeze, "lib/workhorse/poller.rb".freeze, "lib/workhorse/pool.rb".freeze, "lib/workhorse/schedule.rb".freeze, "lib/workhorse/schedules.rb".freeze, "lib/workhorse/scoped_env.rb".freeze, "lib/workhorse/worker.rb".freeze, "test/active_job/queue_adapters/workhorse_adapter_test.rb".freeze, "test/lib/db_schema.rb".freeze, "test/lib/jobs.rb".freeze, "test/lib/test_helper.rb".freeze, "test/workhorse/daemon_test.rb".freeze, "test/workhorse/db_job_test.rb".freeze, "test/workhorse/enqueuer_test.rb".freeze, "test/workhorse/notifier_test.rb".freeze, "test/workhorse/performer_test.rb".freeze, "test/workhorse/poller_test.rb".freeze, "test/workhorse/pool_test.rb".freeze, "test/workhorse/schedule_test.rb".freeze, "test/workhorse/worker_test.rb".freeze, "test/workhorse/yjit_test.rb".freeze, "workhorse.gemspec".freeze]
|
|
13
13
|
s.homepage = "https://github.com/sitrox/workhorse".freeze
|
|
14
14
|
s.licenses = ["MIT".freeze]
|
|
15
15
|
s.rubygems_version = "3.5.18".freeze
|
|
16
16
|
s.summary = "Multi-threaded job backend with database queuing for ruby.".freeze
|
|
17
|
-
s.test_files = ["test/active_job/queue_adapters/workhorse_adapter_test.rb".freeze, "test/lib/db_schema.rb".freeze, "test/lib/jobs.rb".freeze, "test/lib/test_helper.rb".freeze, "test/workhorse/daemon_test.rb".freeze, "test/workhorse/db_job_test.rb".freeze, "test/workhorse/enqueuer_test.rb".freeze, "test/workhorse/performer_test.rb".freeze, "test/workhorse/poller_test.rb".freeze, "test/workhorse/pool_test.rb".freeze, "test/workhorse/worker_test.rb".freeze, "test/workhorse/yjit_test.rb".freeze]
|
|
17
|
+
s.test_files = ["test/active_job/queue_adapters/workhorse_adapter_test.rb".freeze, "test/lib/db_schema.rb".freeze, "test/lib/jobs.rb".freeze, "test/lib/test_helper.rb".freeze, "test/workhorse/daemon_test.rb".freeze, "test/workhorse/db_job_test.rb".freeze, "test/workhorse/enqueuer_test.rb".freeze, "test/workhorse/notifier_test.rb".freeze, "test/workhorse/performer_test.rb".freeze, "test/workhorse/poller_test.rb".freeze, "test/workhorse/pool_test.rb".freeze, "test/workhorse/schedule_test.rb".freeze, "test/workhorse/worker_test.rb".freeze, "test/workhorse/yjit_test.rb".freeze]
|
|
18
18
|
|
|
19
19
|
s.specification_version = 4
|
|
20
20
|
|
|
21
21
|
s.add_runtime_dependency(%q<activesupport>.freeze, [">= 7.0.0".freeze])
|
|
22
22
|
s.add_runtime_dependency(%q<activerecord>.freeze, [">= 7.0.0".freeze])
|
|
23
23
|
s.add_runtime_dependency(%q<concurrent-ruby>.freeze, [">= 0".freeze])
|
|
24
|
+
s.add_runtime_dependency(%q<fugit>.freeze, [">= 0".freeze])
|
|
24
25
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: workhorse
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0.rc1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sitrox
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-09-30 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: fugit
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
54
68
|
executables: []
|
|
55
69
|
extensions: []
|
|
56
70
|
extra_rdoc_files: []
|
|
@@ -73,6 +87,7 @@ files:
|
|
|
73
87
|
- lib/generators/workhorse/templates/bin/workhorse.rb
|
|
74
88
|
- lib/generators/workhorse/templates/config/initializers/workhorse.rb
|
|
75
89
|
- lib/generators/workhorse/templates/create_table_jobs.rb
|
|
90
|
+
- lib/generators/workhorse/templates/create_table_workhorse_schedules.rb
|
|
76
91
|
- lib/workhorse.rb
|
|
77
92
|
- lib/workhorse/active_job_extension.rb
|
|
78
93
|
- lib/workhorse/daemon.rb
|
|
@@ -80,12 +95,19 @@ files:
|
|
|
80
95
|
- lib/workhorse/db_job.rb
|
|
81
96
|
- lib/workhorse/enqueuer.rb
|
|
82
97
|
- lib/workhorse/jobs/cleanup_succeeded_jobs.rb
|
|
98
|
+
- lib/workhorse/jobs/detect_late_schedules_job.rb
|
|
83
99
|
- lib/workhorse/jobs/detect_stale_jobs_job.rb
|
|
84
100
|
- lib/workhorse/jobs/run_active_job.rb
|
|
85
101
|
- lib/workhorse/jobs/run_rails_op.rb
|
|
102
|
+
- lib/workhorse/notifiers/base.rb
|
|
103
|
+
- lib/workhorse/notifiers/file_system.rb
|
|
104
|
+
- lib/workhorse/notifiers/none.rb
|
|
105
|
+
- lib/workhorse/notifiers/redis.rb
|
|
86
106
|
- lib/workhorse/performer.rb
|
|
87
107
|
- lib/workhorse/poller.rb
|
|
88
108
|
- lib/workhorse/pool.rb
|
|
109
|
+
- lib/workhorse/schedule.rb
|
|
110
|
+
- lib/workhorse/schedules.rb
|
|
89
111
|
- lib/workhorse/scoped_env.rb
|
|
90
112
|
- lib/workhorse/worker.rb
|
|
91
113
|
- test/active_job/queue_adapters/workhorse_adapter_test.rb
|
|
@@ -95,9 +117,11 @@ files:
|
|
|
95
117
|
- test/workhorse/daemon_test.rb
|
|
96
118
|
- test/workhorse/db_job_test.rb
|
|
97
119
|
- test/workhorse/enqueuer_test.rb
|
|
120
|
+
- test/workhorse/notifier_test.rb
|
|
98
121
|
- test/workhorse/performer_test.rb
|
|
99
122
|
- test/workhorse/poller_test.rb
|
|
100
123
|
- test/workhorse/pool_test.rb
|
|
124
|
+
- test/workhorse/schedule_test.rb
|
|
101
125
|
- test/workhorse/worker_test.rb
|
|
102
126
|
- test/workhorse/yjit_test.rb
|
|
103
127
|
- workhorse.gemspec
|
|
@@ -119,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
119
143
|
- !ruby/object:Gem::Version
|
|
120
144
|
version: '0'
|
|
121
145
|
requirements: []
|
|
122
|
-
rubygems_version: 4.0.
|
|
146
|
+
rubygems_version: 4.0.15
|
|
123
147
|
specification_version: 4
|
|
124
148
|
summary: Multi-threaded job backend with database queuing for ruby.
|
|
125
149
|
test_files:
|
|
@@ -130,8 +154,10 @@ test_files:
|
|
|
130
154
|
- test/workhorse/daemon_test.rb
|
|
131
155
|
- test/workhorse/db_job_test.rb
|
|
132
156
|
- test/workhorse/enqueuer_test.rb
|
|
157
|
+
- test/workhorse/notifier_test.rb
|
|
133
158
|
- test/workhorse/performer_test.rb
|
|
134
159
|
- test/workhorse/poller_test.rb
|
|
135
160
|
- test/workhorse/pool_test.rb
|
|
161
|
+
- test/workhorse/schedule_test.rb
|
|
136
162
|
- test/workhorse/worker_test.rb
|
|
137
163
|
- test/workhorse/yjit_test.rb
|