workhorse 1.5.2 → 2.0.0.rc0
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/CHANGELOG.md +102 -0
- data/README.md +303 -74
- 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 +11 -13
- data/lib/generators/workhorse/templates/create_table_workhorse_schedules.rb +29 -0
- data/lib/workhorse/daemon.rb +52 -6
- data/lib/workhorse/db_job.rb +58 -7
- 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 +28 -0
- data/lib/workhorse/poller.rb +289 -47
- 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 +77 -27
- data/lib/workhorse.rb +136 -0
- data/test/lib/db_schema.rb +21 -1
- data/test/lib/jobs.rb +29 -0
- data/test/lib/test_helper.rb +9 -14
- data/test/workhorse/daemon_test.rb +33 -0
- data/test/workhorse/db_job_test.rb +1 -1
- data/test/workhorse/notifier_test.rb +500 -0
- data/test/workhorse/poller_test.rb +8 -4
- data/test/workhorse/schedule_test.rb +967 -0
- data/test/workhorse/worker_test.rb +92 -0
- data/workhorse.gemspec +6 -5
- metadata +29 -3
|
@@ -1,6 +1,98 @@
|
|
|
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
|
data/workhorse.gemspec
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: workhorse
|
|
2
|
+
# stub: workhorse 2.0.0.rc0 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.rc0".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-29"
|
|
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.rc0
|
|
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-29 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
|