workhorse 1.2.24 → 1.3.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/.github/workflows/ruby.yml +0 -3
- data/CHANGELOG.md +17 -0
- data/README.md +1 -1
- data/Rakefile +0 -1
- data/VERSION +1 -1
- data/lib/workhorse/daemon/shell_handler.rb +11 -11
- data/lib/workhorse/poller.rb +12 -5
- data/lib/workhorse/worker.rb +3 -0
- data/test/lib/test_helper.rb +21 -1
- data/test/workhorse/db_job_test.rb +3 -7
- data/test/workhorse/performer_test.rb +1 -1
- data/test/workhorse/poller_test.rb +15 -14
- data/workhorse.gemspec +4 -5
- metadata +5 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85aa56534b17816d2256e85b1929397d7cab79c05bd25aa3efeef6ae538bb7ae
|
4
|
+
data.tar.gz: 9e7b16ff371b8113e2de2b475c71512d2fca292873bb7a66b9905107e86a5f4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dbc294e830bc9a594844055de6c392401bf7e18871d078da60580b5b5d7c3c9cc72ca9cc5abd846aaa3a6f5b18c85b80b097b0a94ff68759fcb166f9ed33eff6
|
7
|
+
data.tar.gz: 7f1454dd8a7dd0ad2d491050b6b5e1fdbee429cfe99fcfdf5518590bcb1e9e372500c5a3f0c8b2c4faeaa6b277d0304c2547f0e995af9d53ce82cdbdfe24871e
|
data/.github/workflows/ruby.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Workhorse Changelog
|
2
2
|
|
3
|
+
## 1.3.0.rc0 - 2025-06-10
|
4
|
+
|
5
|
+
* Drop official support for Ruby < 3.0
|
6
|
+
|
7
|
+
* Skip `at_exit` handlers again (as introduced in 1.2.22) when exiting in
|
8
|
+
ShellHandler but still release lock file to fix issue originally fixed in
|
9
|
+
1.2.23. This ensures compatibility with the `debug` gem, which would
|
10
|
+
otherwise hang when using the Workhorse shell handler.
|
11
|
+
|
12
|
+
Sitrox reference: #128333.
|
13
|
+
|
14
|
+
* Improve reliability of worker shutdown
|
15
|
+
|
16
|
+
* Improve reliability and efficiency of polling mechanism
|
17
|
+
|
18
|
+
* Improve reliability of automated tests
|
19
|
+
|
3
20
|
## 1.2.24 - 2024-10-21
|
4
21
|
|
5
22
|
* Fix compatibility with ActiveJob 7.2.x
|
data/README.md
CHANGED
@@ -33,7 +33,7 @@ What it does not do:
|
|
33
33
|
|
34
34
|
### Requirements
|
35
35
|
|
36
|
-
* Ruby `>=
|
36
|
+
* Ruby `>= 3.0` (may work with earlier versions but is untested)
|
37
37
|
* Rails `>= 3.2`
|
38
38
|
* A database and table handler that properly supports row-level locking (such as
|
39
39
|
MySQL with InnoDB, PostgreSQL, or Oracle).
|
data/Rakefile
CHANGED
@@ -16,7 +16,6 @@ task :gemspec do
|
|
16
16
|
spec.add_development_dependency 'rubocop', '~> 1.28.0' # Latest version supported with Ruby 2.5
|
17
17
|
spec.add_development_dependency 'minitest'
|
18
18
|
spec.add_development_dependency 'mysql2'
|
19
|
-
spec.add_development_dependency 'colorize'
|
20
19
|
spec.add_development_dependency 'benchmark-ips'
|
21
20
|
spec.add_development_dependency 'activejob'
|
22
21
|
spec.add_development_dependency 'pry'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.3.0.rc0
|
@@ -19,32 +19,32 @@ module Workhorse
|
|
19
19
|
begin
|
20
20
|
case ARGV.first
|
21
21
|
when 'start'
|
22
|
-
|
22
|
+
status = daemon.start
|
23
23
|
when 'stop'
|
24
|
-
|
24
|
+
status = daemon.stop
|
25
25
|
when 'kill'
|
26
|
-
|
26
|
+
status = daemon.stop(true)
|
27
27
|
when 'status'
|
28
|
-
|
28
|
+
status = daemon.status
|
29
29
|
when 'watch'
|
30
|
-
|
30
|
+
status = daemon.watch
|
31
31
|
when 'restart'
|
32
|
-
|
32
|
+
status = daemon.restart
|
33
33
|
when 'restart-logging'
|
34
|
-
|
34
|
+
status = daemon.restart_logging
|
35
35
|
when 'usage'
|
36
36
|
usage
|
37
|
-
|
37
|
+
status = 0
|
38
38
|
else
|
39
39
|
usage
|
40
|
+
status = 99
|
40
41
|
end
|
41
|
-
|
42
|
-
exit 0
|
43
42
|
rescue StandardError => e
|
44
43
|
warn "#{e.message}\n#{e.backtrace.join("\n")}"
|
45
|
-
|
44
|
+
status = 99
|
46
45
|
ensure
|
47
46
|
lockfile&.flock(File::LOCK_UN)
|
47
|
+
exit! status
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
data/lib/workhorse/poller.rb
CHANGED
@@ -206,9 +206,9 @@ module Workhorse
|
|
206
206
|
|
207
207
|
timeout = [MIN_LOCK_TIMEOUT, [MAX_LOCK_TIMEOUT, worker.polling_interval].min].max
|
208
208
|
with_global_lock timeout: timeout do
|
209
|
-
job_ids = []
|
210
|
-
|
211
209
|
Workhorse.tx_callback.call do
|
210
|
+
job_ids = []
|
211
|
+
|
212
212
|
# As we are the only thread posting into the worker pool, it is safe to
|
213
213
|
# get the number of idle threads without mutex synchronization. The
|
214
214
|
# actual number of idle workers at time of posting can only be larger
|
@@ -225,9 +225,14 @@ module Workhorse
|
|
225
225
|
job_ids << job.id
|
226
226
|
end
|
227
227
|
end
|
228
|
-
end
|
229
228
|
|
230
|
-
|
229
|
+
unless running?
|
230
|
+
worker.log 'Rolling back transaction to unlock jobs, as worker has been shut down in the meantime'
|
231
|
+
fail ActiveRecord::Rollback
|
232
|
+
end
|
233
|
+
|
234
|
+
job_ids.each { |job_id| worker.perform(job_id) }
|
235
|
+
end
|
231
236
|
end
|
232
237
|
end
|
233
238
|
|
@@ -358,8 +363,10 @@ module Workhorse
|
|
358
363
|
# Get the names of all valid queues. The extra project here allows
|
359
364
|
# selecting the last value in each row of the resulting array and getting
|
360
365
|
# the queue name.
|
366
|
+
select.projections = []
|
361
367
|
queues = select.project(:queue)
|
362
|
-
|
368
|
+
|
369
|
+
return Workhorse::DbJob.connection.execute(queues.distinct.to_sql).to_a.flatten
|
363
370
|
end
|
364
371
|
end
|
365
372
|
end
|
data/lib/workhorse/worker.rb
CHANGED
@@ -116,6 +116,9 @@ module Workhorse
|
|
116
116
|
# final state this worker can be in.
|
117
117
|
return if @state == :shutdown
|
118
118
|
|
119
|
+
# TODO: There is a race-condition with this shutdown:
|
120
|
+
# - If the poller is currently locking a job, it may call
|
121
|
+
# "worker.perform", which in turn tries to synchronize the same mutex.
|
119
122
|
mutex.synchronize do
|
120
123
|
assert_state! :running
|
121
124
|
|
data/test/lib/test_helper.rb
CHANGED
@@ -2,7 +2,6 @@ require 'minitest/autorun'
|
|
2
2
|
require 'active_record'
|
3
3
|
require 'active_job'
|
4
4
|
require 'pry'
|
5
|
-
require 'colorize'
|
6
5
|
require 'mysql2'
|
7
6
|
require 'benchmark'
|
8
7
|
require 'concurrent'
|
@@ -35,6 +34,7 @@ end
|
|
35
34
|
class WorkhorseTest < ActiveSupport::TestCase
|
36
35
|
def setup
|
37
36
|
remove_pids!
|
37
|
+
clear_locks_and_db_threads!
|
38
38
|
Workhorse.silence_watcher = true
|
39
39
|
Workhorse::DbJob.delete_all
|
40
40
|
end
|
@@ -43,6 +43,22 @@ class WorkhorseTest < ActiveSupport::TestCase
|
|
43
43
|
|
44
44
|
attr_reader :daemon
|
45
45
|
|
46
|
+
def clear_locks_and_db_threads!
|
47
|
+
Workhorse::DbJob.connection.execute('SELECT RELEASE_ALL_LOCKS()')
|
48
|
+
|
49
|
+
pids = Workhorse::DbJob.connection.execute(<<~SQL.squish).to_a.flatten
|
50
|
+
SELECT ID FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID != CONNECTION_ID()
|
51
|
+
SQL
|
52
|
+
|
53
|
+
begin
|
54
|
+
pids.each { |pid| Workhorse::DbJob.connection.execute("KILL QUERY #{pid}") }
|
55
|
+
rescue ActiveRecord::StatementInvalid
|
56
|
+
# Ignore
|
57
|
+
end
|
58
|
+
|
59
|
+
Workhorse::DbJob.connection.execute('SELECT RELEASE_ALL_LOCKS()')
|
60
|
+
end
|
61
|
+
|
46
62
|
def remove_pids!
|
47
63
|
Dir[Rails.root.join('tmp', 'pids', '*')].each do |file|
|
48
64
|
FileUtils.rm file
|
@@ -74,6 +90,7 @@ class WorkhorseTest < ActiveSupport::TestCase
|
|
74
90
|
def work(time = 2, options = {})
|
75
91
|
options[:pool_size] ||= 5
|
76
92
|
options[:polling_interval] ||= 1
|
93
|
+
options[:auto_terminate] = options.fetch(:auto_terminate, false)
|
77
94
|
|
78
95
|
with_worker(options) do
|
79
96
|
sleep time
|
@@ -81,6 +98,8 @@ class WorkhorseTest < ActiveSupport::TestCase
|
|
81
98
|
end
|
82
99
|
|
83
100
|
def work_until(max: 50, interval: 0.1, **options, &block)
|
101
|
+
options[:auto_terminate] = options.fetch(:auto_terminate, false)
|
102
|
+
|
84
103
|
w = Workhorse::Worker.new(**options)
|
85
104
|
w.start
|
86
105
|
return with_retries(max, interval: interval, &block)
|
@@ -143,6 +162,7 @@ ActiveRecord::Base.establish_connection(
|
|
143
162
|
username: ENV.fetch('DB_USERNAME', nil) || 'root',
|
144
163
|
password: ENV.fetch('DB_PASSWORD', nil) || '',
|
145
164
|
host: ENV.fetch('DB_HOST', nil) || '127.0.0.1',
|
165
|
+
port: ENV.fetch('DB_PORT', nil) || 3306,
|
146
166
|
pool: 10
|
147
167
|
)
|
148
168
|
|
@@ -3,17 +3,13 @@ require 'test_helper'
|
|
3
3
|
class Workhorse::DbJobTest < WorkhorseTest
|
4
4
|
def test_reset_succeeded
|
5
5
|
job = Workhorse.enqueue(BasicJob.new(sleep_time: 0))
|
6
|
-
|
7
|
-
job.reload
|
8
|
-
assert_equal 'succeeded', job.state
|
9
|
-
|
6
|
+
work_until { assert_equal 'succeeded', job.reload.state }
|
10
7
|
job.reset!
|
11
|
-
|
12
|
-
assert_clean job
|
8
|
+
assert_clean job.reload
|
13
9
|
end
|
14
10
|
|
15
11
|
def test_reset_failed
|
16
|
-
job = Workhorse.enqueue FailingTestJob
|
12
|
+
job = Workhorse.enqueue FailingTestJob.new
|
17
13
|
work 0.5
|
18
14
|
job.reload
|
19
15
|
assert_equal 'failed', job.state
|
@@ -219,26 +219,27 @@ class Workhorse::PollerTest < WorkhorseTest
|
|
219
219
|
[true, false].each do |clean|
|
220
220
|
Workhorse::DbJob.delete_all
|
221
221
|
|
222
|
-
Workhorse.clean_stuck_jobs =
|
222
|
+
Workhorse.clean_stuck_jobs = clean
|
223
223
|
with_daemon do
|
224
224
|
Workhorse.enqueue BasicJob.new(sleep_time: 5)
|
225
|
-
sleep 0.2
|
226
|
-
kill_deamon_workers
|
227
225
|
|
228
|
-
|
229
|
-
|
226
|
+
with_retries do
|
227
|
+
assert_equal 'started', Workhorse::DbJob.first.state
|
228
|
+
end
|
230
229
|
|
231
|
-
|
230
|
+
kill_deamon_workers
|
232
231
|
|
233
|
-
assert_equal
|
232
|
+
assert_equal 'started', Workhorse::DbJob.first.state
|
234
233
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
234
|
+
work_until do
|
235
|
+
Workhorse::DbJob.first.tap do |job|
|
236
|
+
if clean
|
237
|
+
assert_equal 'failed', job.state
|
238
|
+
assert_match(/started by PID #{daemon.workers.first.pid}/, job.last_error)
|
239
|
+
assert_match(/on host #{Socket.gethostname}/, job.last_error)
|
240
|
+
else
|
241
|
+
assert_equal 'started', job.state
|
242
|
+
end
|
242
243
|
end
|
243
244
|
end
|
244
245
|
end
|
data/workhorse.gemspec
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: workhorse 1.
|
2
|
+
# stub: workhorse 1.3.0.rc0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "workhorse".freeze
|
6
|
-
s.version = "1.
|
6
|
+
s.version = "1.3.0.rc0"
|
7
7
|
|
8
|
-
s.required_rubygems_version = Gem::Requirement.new("
|
8
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib".freeze]
|
10
10
|
s.authors = ["Sitrox".freeze]
|
11
|
-
s.date = "
|
11
|
+
s.date = "2025-06-10"
|
12
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, "workhorse.gemspec".freeze]
|
13
13
|
s.rubygems_version = "3.4.6".freeze
|
14
14
|
s.summary = "Multi-threaded job backend with database queuing for ruby.".freeze
|
@@ -21,7 +21,6 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.add_development_dependency(%q<rubocop>.freeze, ["~> 1.28.0"])
|
22
22
|
s.add_development_dependency(%q<minitest>.freeze, [">= 0"])
|
23
23
|
s.add_development_dependency(%q<mysql2>.freeze, [">= 0"])
|
24
|
-
s.add_development_dependency(%q<colorize>.freeze, [">= 0"])
|
25
24
|
s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
|
26
25
|
s.add_development_dependency(%q<activejob>.freeze, [">= 0"])
|
27
26
|
s.add_development_dependency(%q<pry>.freeze, [">= 0"])
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: workhorse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0.rc0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sitrox
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-06-10 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: bundler
|
@@ -80,20 +79,6 @@ dependencies:
|
|
80
79
|
- - ">="
|
81
80
|
- !ruby/object:Gem::Version
|
82
81
|
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: colorize
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
82
|
- !ruby/object:Gem::Dependency
|
98
83
|
name: benchmark-ips
|
99
84
|
requirement: !ruby/object:Gem::Requirement
|
@@ -178,8 +163,6 @@ dependencies:
|
|
178
163
|
- - ">="
|
179
164
|
- !ruby/object:Gem::Version
|
180
165
|
version: '0'
|
181
|
-
description:
|
182
|
-
email:
|
183
166
|
executables: []
|
184
167
|
extensions: []
|
185
168
|
extra_rdoc_files: []
|
@@ -229,10 +212,8 @@ files:
|
|
229
212
|
- test/workhorse/pool_test.rb
|
230
213
|
- test/workhorse/worker_test.rb
|
231
214
|
- workhorse.gemspec
|
232
|
-
homepage:
|
233
215
|
licenses: []
|
234
216
|
metadata: {}
|
235
|
-
post_install_message:
|
236
217
|
rdoc_options: []
|
237
218
|
require_paths:
|
238
219
|
- lib
|
@@ -243,12 +224,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
243
224
|
version: '0'
|
244
225
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
245
226
|
requirements:
|
246
|
-
- - "
|
227
|
+
- - ">"
|
247
228
|
- !ruby/object:Gem::Version
|
248
|
-
version:
|
229
|
+
version: 1.3.1
|
249
230
|
requirements: []
|
250
|
-
rubygems_version: 3.
|
251
|
-
signing_key:
|
231
|
+
rubygems_version: 3.6.8
|
252
232
|
specification_version: 4
|
253
233
|
summary: Multi-threaded job backend with database queuing for ruby.
|
254
234
|
test_files:
|