workhorse 1.2.15 → 1.2.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +6 -1
- data/.rubocop.yml +115 -16
- data/CHANGELOG.md +15 -0
- data/README.md +7 -0
- data/RUBY_VERSION +1 -1
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/lib/active_job/queue_adapters/workhorse_adapter.rb +2 -2
- data/lib/generators/workhorse/install_generator.rb +5 -2
- data/lib/workhorse/daemon/shell_handler.rb +29 -29
- data/lib/workhorse/daemon.rb +13 -8
- data/lib/workhorse/db_job.rb +27 -5
- data/lib/workhorse/enqueuer.rb +4 -4
- data/lib/workhorse/jobs/cleanup_succeeded_jobs.rb +1 -1
- data/lib/workhorse/jobs/detect_stale_jobs_job.rb +2 -2
- data/lib/workhorse/poller.rb +63 -7
- data/lib/workhorse/pool.rb +8 -10
- data/lib/workhorse/worker.rb +15 -9
- data/lib/workhorse.rb +7 -2
- data/test/lib/test_helper.rb +4 -4
- data/test/workhorse/poller_test.rb +89 -3
- data/workhorse.gemspec +19 -32
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c50e38fd05785d30e0dfa783d732f46df2be9b980ac0a54dca1a2d91264ff9d
|
4
|
+
data.tar.gz: a4b282a25d39429da15c5380a41a1786b54b24b2b8afb4961786881380eecacf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8fefeb28bb6651e93163e5d9b8744994f681064bf55168b73d8ca5e9c72aee9e3f7642d03a86eb496ebd81bf15e724757cb4f0a6790dcb87e3ae5f3ef09344d
|
7
|
+
data.tar.gz: '090f112b83a2a69015e0244345d30616af8701bf99f2a6814238006efa2cca49850dfe024b26d1359b2863d01bf2c84d0bbf84a7122419f540969ba7f8d66dea'
|
data/.github/workflows/ruby.yml
CHANGED
@@ -31,6 +31,11 @@ jobs:
|
|
31
31
|
sudo /etc/init.d/mysql start
|
32
32
|
mysql -u${{ env.DB_USER }} -p${{ env.DB_PASSWORD }} -e 'CREATE DATABASE ${{ env.DB_DATABASE }};'
|
33
33
|
- name: Run rake tests
|
34
|
-
|
34
|
+
uses: nick-fields/retry@v2
|
35
|
+
with:
|
36
|
+
timeout_seconds: 120
|
37
|
+
retry_on: error
|
38
|
+
max_attempts: 3
|
39
|
+
command: bundle exec rake test TESTOPTS='--verbose'
|
35
40
|
- name: Run rubocop
|
36
41
|
run: bundle exec rubocop
|
data/.rubocop.yml
CHANGED
@@ -1,81 +1,158 @@
|
|
1
1
|
AllCops:
|
2
|
-
|
3
|
-
|
2
|
+
DisplayCopNames: true
|
3
|
+
NewCops: enable
|
4
|
+
SuggestExtensions: false
|
5
|
+
TargetRubyVersion: 2.5
|
4
6
|
Exclude:
|
7
|
+
- 'local/**/*'
|
5
8
|
- 'vendor/**/*'
|
6
9
|
- 'tmp/**/*'
|
10
|
+
- 'target/**/*'
|
7
11
|
- 'log/**/*'
|
12
|
+
- 'db/schema.rb'
|
13
|
+
- 'locale/translations.rb'
|
14
|
+
- 'config/initializers/assets.rb'
|
15
|
+
- 'config/puma.rb'
|
16
|
+
- 'config_scripts/release_notes'
|
17
|
+
- 'config/spring.rb'
|
18
|
+
- 'bin/yarn'
|
8
19
|
- '*.gemspec'
|
9
20
|
|
10
|
-
|
21
|
+
# Make sure accessors are on separate lines for diff readability.
|
22
|
+
Style/AccessorGrouping:
|
23
|
+
EnforcedStyle: separated
|
24
|
+
|
25
|
+
# Cop would break a lot of existing code.
|
26
|
+
Style/OptionalBooleanParameter:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
# Multiline hashes should be aligned cleanly as a table to improve readability.
|
30
|
+
Layout/HashAlignment:
|
31
|
+
EnforcedHashRocketStyle: table
|
32
|
+
EnforcedColonStyle: table
|
11
33
|
|
34
|
+
# Template style is easier on the eyes.
|
35
|
+
Style/FormatStringToken:
|
36
|
+
EnforcedStyle: template
|
37
|
+
|
38
|
+
# file. This will be addressed when approaching the first ruby 3 application.
|
12
39
|
Style/FrozenStringLiteralComment:
|
13
40
|
Enabled: false
|
14
41
|
|
42
|
+
# Double negation is very useful to make sure you have a boolean in hand. Use it
|
43
|
+
# wisely though and know what you're doing.
|
15
44
|
Style/DoubleNegation:
|
16
45
|
Enabled: false
|
17
46
|
|
47
|
+
# Depending on the case, [].include? can be a lot harder to read and less
|
48
|
+
# expressive than multiple comparisons.
|
49
|
+
Style/MultipleComparison:
|
50
|
+
Enabled: false
|
51
|
+
|
52
|
+
# Over time, the ruby guide changed from raise to fail back to raise. Both fail
|
53
|
+
# and raise are programatically exactly the same and our decision fell to "fail"
|
54
|
+
# for all kinds of exceptions.
|
18
55
|
Style/SignalException:
|
19
56
|
EnforcedStyle: only_fail
|
20
57
|
|
21
|
-
|
22
|
-
Enabled: False
|
23
|
-
|
24
|
-
Lint/RescueException:
|
25
|
-
Enabled: False
|
26
|
-
|
58
|
+
# Enforced styles can sometimes be hard to read.
|
27
59
|
Style/ConditionalAssignment:
|
28
60
|
Enabled: false
|
29
61
|
|
30
|
-
|
62
|
+
# Enforce consistent array indentation.
|
63
|
+
Layout/FirstArrayElementIndentation:
|
31
64
|
EnforcedStyle: consistent
|
32
65
|
|
66
|
+
# Disable layout cop because methods just consisting of a number of returns
|
67
|
+
# would look very odd with an extra empty line between each return.
|
68
|
+
Layout/EmptyLineAfterGuardClause:
|
69
|
+
Enabled: false
|
70
|
+
|
71
|
+
# While you should try to keep your code as expressive and short as possible,
|
72
|
+
# limitting lengths hardly is over the top.
|
33
73
|
Metrics/MethodLength:
|
34
74
|
Enabled: false
|
35
75
|
|
76
|
+
# While you should try to keep your code as expressive and short as possible,
|
77
|
+
# limitting lengths hardly is over the top.
|
36
78
|
Metrics/ClassLength:
|
37
79
|
Enabled: false
|
38
80
|
|
81
|
+
# While you should try to keep your code as expressive and short as possible,
|
82
|
+
# limitting lengths hardly is over the top.
|
39
83
|
Metrics/ModuleLength:
|
40
84
|
Enabled: false
|
41
85
|
|
86
|
+
# While you should try to keep your code as expressive and short as possible,
|
87
|
+
# limitting lengths hardly is over the top.
|
88
|
+
Metrics/BlockLength:
|
89
|
+
Enabled: false
|
90
|
+
|
91
|
+
# While not always desirable, it can be useful to have a lot of keyword
|
92
|
+
# arguments on certain methods. Try to avoid it though.
|
42
93
|
Metrics/ParameterLists:
|
43
94
|
Max: 5
|
44
95
|
CountKeywordArgs: false
|
45
96
|
|
97
|
+
# The results of this cop sometimes seemed arbitrary and can signifficantly
|
98
|
+
# restrict certain styles of coding.
|
46
99
|
Metrics/AbcSize:
|
47
100
|
Enabled: False
|
48
101
|
|
102
|
+
# The results of this cop sometimes seemed arbitrary and can signifficantly
|
103
|
+
# restrict certain styles of coding.
|
49
104
|
Metrics/CyclomaticComplexity:
|
50
105
|
Enabled: False
|
51
106
|
|
107
|
+
# The results of this cop sometimes seemed arbitrary and can signifficantly
|
108
|
+
# restrict certain styles of coding.
|
52
109
|
Metrics/PerceivedComplexity:
|
53
110
|
Enabled: False
|
54
111
|
|
55
|
-
|
56
|
-
|
57
|
-
|
112
|
+
# In certain cases, "excessive" block nesting might just be useful. Try to keep
|
113
|
+
# this down as much as possible though.
|
58
114
|
Metrics/BlockNesting:
|
59
115
|
Enabled: false
|
60
116
|
|
61
|
-
|
62
|
-
|
117
|
+
# A line length of 80 is not considered to be temporary anymore. That's why line
|
118
|
+
# length is doubled to 160. If absolutely necessary, create a temporary rubocop
|
119
|
+
# exclusion for the lines in question.
|
120
|
+
Layout/LineLength:
|
121
|
+
Max: 160
|
63
122
|
|
123
|
+
# Prefer variable_1 over variable1 for aesthetic reasons. Do not check symbols,
|
124
|
+
# as they often need to be another case for use in external palces (e.g. :md5).
|
125
|
+
Naming/VariableNumber:
|
126
|
+
EnforcedStyle: snake_case
|
127
|
+
CheckSymbols: false
|
128
|
+
|
129
|
+
# Depending on the surrounding code, even simple if/unless clauses may be more
|
130
|
+
# descriptive when on multiple lines.
|
64
131
|
Style/IfUnlessModifier:
|
65
132
|
Enabled: false
|
66
133
|
|
134
|
+
# In most cases, timing does not allow documenting each and every bit of source
|
135
|
+
# code. Do not hesitate to enable this cop otherwise.
|
67
136
|
Style/Documentation:
|
68
137
|
Enabled: false
|
69
138
|
|
139
|
+
# Return should be used whenever there is more than one statement or line in a
|
140
|
+
# method. This helps avoiding programming mistakes. This is not enforced yet as
|
141
|
+
# this would require a custom cop. However, to allow this style of programming,
|
142
|
+
# the RedundantReturn cop needs to be disabled.
|
70
143
|
Style/RedundantReturn:
|
71
144
|
Enabled: false
|
72
145
|
|
146
|
+
# Non-ascii comments can be useful sometimes.
|
73
147
|
Style/AsciiComments:
|
74
148
|
Enabled: false
|
75
149
|
|
150
|
+
# Depending on the case, if/unless can be more descriptive than guard clauses.
|
76
151
|
Style/GuardClause:
|
77
152
|
Enabled: false
|
78
153
|
|
154
|
+
# For technical reasons, nested and compact styles must be mixed in certain
|
155
|
+
# applications.
|
79
156
|
Style/ClassAndModuleChildren:
|
80
157
|
Enabled: false
|
81
158
|
EnforcedStyle: compact
|
@@ -83,8 +160,30 @@ Style/ClassAndModuleChildren:
|
|
83
160
|
- nested
|
84
161
|
- compact
|
85
162
|
|
163
|
+
# Depending on the case, it may be more descriptive to use i.e. == 0 instead of
|
164
|
+
# .zero?, especially when testing against multiple numbers.
|
86
165
|
Style/NumericPredicate:
|
87
166
|
Enabled: false
|
88
167
|
|
89
|
-
|
168
|
+
# Detection is not implemented in a reliable manner for all cases which can lead
|
169
|
+
# to false positives and negatives.
|
170
|
+
Style/FormatString:
|
171
|
+
Enabled: false
|
172
|
+
|
173
|
+
# Do not require MFA, as gems checked with sitrox_standards are only pushed to the
|
174
|
+
# internal repo
|
175
|
+
Gemspec/RequireMFA:
|
176
|
+
Enabled: false
|
177
|
+
|
178
|
+
# Use explicit style
|
179
|
+
Naming/BlockForwarding:
|
180
|
+
Enabled: true
|
181
|
+
EnforcedStyle: explicit
|
182
|
+
|
183
|
+
Style/HashSyntax:
|
184
|
+
# Use `either` style for `EnforcedShorthandyntax` (see #106550)
|
185
|
+
EnforcedShorthandSyntax: either
|
186
|
+
|
187
|
+
# Allow rescue 'Exception', necessary for Workhorse
|
188
|
+
Lint/RescueException:
|
90
189
|
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Workhorse Changelog
|
2
2
|
|
3
|
+
## 1.2.16 - 2023-09-18
|
4
|
+
|
5
|
+
* Add support for `--skip-initializer` flag to install generator.
|
6
|
+
|
7
|
+
Sitrox reference: #114673.
|
8
|
+
|
9
|
+
* Add option `config.clean_stuck_jobs` that enabled automatic cleaning of stuck
|
10
|
+
jobs whenever a worker starts up.
|
11
|
+
|
12
|
+
Sitrox reference: #113708
|
13
|
+
|
14
|
+
* Add `retry-step` to actions such that failed unit tests are executed again
|
15
|
+
|
16
|
+
Sitrox reference: #115888
|
17
|
+
|
3
18
|
## 1.2.15 - 2023-08-28
|
4
19
|
|
5
20
|
* Add capability to skip transactions for enqueued RailsOps operations.
|
data/README.md
CHANGED
@@ -62,6 +62,7 @@ What it does not do:
|
|
62
62
|
|
63
63
|
* A database migration for creating a table named `jobs`
|
64
64
|
* The initializer `config/initializers/workhorse.rb` for global configuration
|
65
|
+
* This can be skipped using the `--skip-initializer` flag
|
65
66
|
* The daemon worker script `bin/workhorse.rb`
|
66
67
|
|
67
68
|
Please customize the initializer and worker script to your liking.
|
@@ -527,6 +528,12 @@ exception is thrown (which may cause a notification if you configured
|
|
527
528
|
`on_exception` accordingly). See the job's API documentation for more
|
528
529
|
information.
|
529
530
|
|
531
|
+
Starting with Workhorse 1.2.16, there is also a feature that automatically
|
532
|
+
checks for stuck jobs (jobs in state `locked` or `started` running on the same
|
533
|
+
host where the corresponding PID does not have a process anymore) when starting
|
534
|
+
up the worker / poller. This feature can be turned on using the setting
|
535
|
+
`config.clean_stuck_jobs`. This is turned off by default.
|
536
|
+
|
530
537
|
## Frequently asked questions
|
531
538
|
|
532
539
|
Please consult the [FAQ](FAQ.md).
|
data/RUBY_VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
1
|
+
ruby-2.7.1-p83
|
data/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
task :gemspec do
|
2
2
|
gemspec = Gem::Specification.new do |spec|
|
3
3
|
spec.name = 'workhorse'
|
4
|
-
spec.version =
|
4
|
+
spec.version = File.read('VERSION').chomp
|
5
5
|
spec.authors = ['Sitrox']
|
6
6
|
spec.summary = %(
|
7
7
|
Multi-threaded job backend with database queuing for ruby.
|
@@ -13,7 +13,7 @@ task :gemspec do
|
|
13
13
|
|
14
14
|
spec.add_development_dependency 'bundler'
|
15
15
|
spec.add_development_dependency 'rake'
|
16
|
-
spec.add_development_dependency 'rubocop', '
|
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
19
|
spec.add_development_dependency 'colorize'
|
@@ -25,7 +25,7 @@ task :gemspec do
|
|
25
25
|
spec.add_dependency 'concurrent-ruby'
|
26
26
|
end
|
27
27
|
|
28
|
-
File.
|
28
|
+
File.write('workhorse.gemspec', gemspec.to_ruby.strip)
|
29
29
|
end
|
30
30
|
|
31
31
|
require 'rake/testtask'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.2.
|
1
|
+
1.2.16
|
@@ -10,11 +10,11 @@ module ActiveJob
|
|
10
10
|
#
|
11
11
|
# Rails.application.config.active_job.queue_adapter = :workhorse
|
12
12
|
class WorkhorseAdapter
|
13
|
-
def enqueue(job)
|
13
|
+
def enqueue(job) # :nodoc:
|
14
14
|
Workhorse.enqueue_active_job(job)
|
15
15
|
end
|
16
16
|
|
17
|
-
def enqueue_at(job, timestamp = Time.now)
|
17
|
+
def enqueue_at(job, timestamp = Time.now) # :nodoc:
|
18
18
|
Workhorse.enqueue_active_job(job, perform_at: timestamp)
|
19
19
|
end
|
20
20
|
end
|
@@ -2,7 +2,10 @@ module Workhorse
|
|
2
2
|
class InstallGenerator < Rails::Generators::Base
|
3
3
|
include Rails::Generators::Migration
|
4
4
|
|
5
|
-
|
5
|
+
class_option :skip_initializer, type: :boolean, default: false,
|
6
|
+
desc: 'Skip generating the initializer file'
|
7
|
+
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
6
9
|
|
7
10
|
def self.next_migration_number(_dir)
|
8
11
|
Time.now.utc.strftime('%Y%m%d%H%M%S')
|
@@ -18,7 +21,7 @@ module Workhorse
|
|
18
21
|
end
|
19
22
|
|
20
23
|
def install_initializer
|
21
|
-
template 'config/initializers/workhorse.rb'
|
24
|
+
template 'config/initializers/workhorse.rb' unless options[:skip_initializer]
|
22
25
|
end
|
23
26
|
end
|
24
27
|
end
|
@@ -40,7 +40,7 @@ module Workhorse
|
|
40
40
|
end
|
41
41
|
|
42
42
|
exit 0
|
43
|
-
rescue => e
|
43
|
+
rescue StandardError => e
|
44
44
|
warn "#{e.message}\n#{e.backtrace.join("\n")}"
|
45
45
|
exit 99
|
46
46
|
ensure
|
@@ -49,44 +49,44 @@ module Workhorse
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def self.usage
|
52
|
-
warn
|
53
|
-
Usage: #{$PROGRAM_NAME} start|stop|status|watch|restart|usage
|
52
|
+
warn <<~USAGE
|
53
|
+
Usage: #{$PROGRAM_NAME} start|stop|status|watch|restart|usage
|
54
54
|
|
55
|
-
Options:
|
55
|
+
Options:
|
56
56
|
|
57
|
-
|
58
|
-
|
57
|
+
start
|
58
|
+
Start the daemon
|
59
59
|
|
60
|
-
|
61
|
-
|
60
|
+
stop
|
61
|
+
Stop the daemon
|
62
62
|
|
63
|
-
|
64
|
-
|
63
|
+
kill
|
64
|
+
Kill the daemon
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
66
|
+
status
|
67
|
+
Query the status of the daemon. Exit with status 1 if any worker is
|
68
|
+
not running.
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
watch
|
71
|
+
Checks the status (running or stopped) and whether it is as
|
72
|
+
expected. Starts the daemon if it is expected to run but is not.
|
73
73
|
|
74
|
-
|
75
|
-
|
74
|
+
restart
|
75
|
+
Shortcut for consecutive 'stop' and 'start'.
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
restart-logging
|
78
|
+
Re-opens log files, useful e.g. after the log files have been moved or
|
79
|
+
removed by log rotation.
|
80
80
|
|
81
|
-
|
82
|
-
|
81
|
+
usage
|
82
|
+
Show this message
|
83
83
|
|
84
|
-
Exit status:
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
USAGE
|
84
|
+
Exit status:
|
85
|
+
0 if OK,
|
86
|
+
1 on fatal errors outside of workhorse,
|
87
|
+
2 if at least one worker has an unexpected status,
|
88
|
+
99 on all other errors.
|
89
|
+
USAGE
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
data/lib/workhorse/daemon.rb
CHANGED
@@ -4,6 +4,7 @@ module Workhorse
|
|
4
4
|
attr_reader :id
|
5
5
|
attr_reader :name
|
6
6
|
attr_reader :block
|
7
|
+
attr_accessor :pid
|
7
8
|
|
8
9
|
def initialize(id, name, &block)
|
9
10
|
@id = id
|
@@ -12,6 +13,9 @@ module Workhorse
|
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
16
|
+
# @private
|
17
|
+
attr_reader :workers
|
18
|
+
|
15
19
|
def initialize(pidfile: nil, quiet: false, &_block)
|
16
20
|
@pidfile = pidfile
|
17
21
|
@quiet = quiet
|
@@ -68,7 +72,7 @@ module Workhorse
|
|
68
72
|
|
69
73
|
if pid_file && pid
|
70
74
|
puts "Worker (#{worker.name}) ##{worker.id}: Stopping"
|
71
|
-
stop_worker pid_file, pid, kill
|
75
|
+
stop_worker pid_file, pid, kill: kill
|
72
76
|
elsif pid_file
|
73
77
|
File.delete pid_file
|
74
78
|
puts "Worker (#{worker.name}) ##{worker.id}: Already stopped (stale PID file)"
|
@@ -147,20 +151,20 @@ module Workhorse
|
|
147
151
|
def start_worker(worker)
|
148
152
|
pid = fork do
|
149
153
|
$0 = process_name(worker)
|
150
|
-
|
151
154
|
# Reopen pipes to prevent #107576
|
152
|
-
|
155
|
+
$stdin.reopen File.open('/dev/null', 'r')
|
153
156
|
null_out = File.open '/dev/null', 'w'
|
154
|
-
|
155
|
-
|
157
|
+
$stdout.reopen null_out
|
158
|
+
$stderr.reopen null_out
|
156
159
|
|
157
160
|
worker.block.call
|
158
161
|
end
|
159
|
-
|
162
|
+
worker.pid = pid
|
163
|
+
File.write(pid_file_for(worker), pid)
|
160
164
|
Process.detach(pid)
|
161
165
|
end
|
162
166
|
|
163
|
-
def stop_worker(pid_file, pid, kill
|
167
|
+
def stop_worker(pid_file, pid, kill: false)
|
164
168
|
signals = kill ? %w[KILL] : %w[TERM INT]
|
165
169
|
|
166
170
|
loop do
|
@@ -207,8 +211,9 @@ module Workhorse
|
|
207
211
|
file = pid_file_for(worker)
|
208
212
|
|
209
213
|
if File.exist?(file)
|
210
|
-
raw_pid =
|
214
|
+
raw_pid = File.read(file)
|
211
215
|
return nil, nil if raw_pid.blank?
|
216
|
+
|
212
217
|
pid = Integer(raw_pid)
|
213
218
|
return file, process?(pid) ? pid : nil
|
214
219
|
else
|
data/lib/workhorse/db_job.rb
CHANGED
@@ -6,6 +6,8 @@ module Workhorse
|
|
6
6
|
STATE_SUCCEEDED = :succeeded
|
7
7
|
STATE_FAILED = :failed
|
8
8
|
|
9
|
+
EXP_LOCKED_BY = /^(.*?)\.(\d+?)\.([^.]+)$/.freeze
|
10
|
+
|
9
11
|
if respond_to?(:attr_accessible)
|
10
12
|
attr_accessible :queue, :priority, :perform_at, :handler, :description
|
11
13
|
end
|
@@ -32,6 +34,31 @@ module Workhorse
|
|
32
34
|
where(state: STATE_FAILED)
|
33
35
|
end
|
34
36
|
|
37
|
+
# @private
|
38
|
+
def self.with_split_locked_by
|
39
|
+
select(<<~SQL)
|
40
|
+
#{table_name}.*,
|
41
|
+
|
42
|
+
-- random string
|
43
|
+
substring_index(locked_by, '.', -1) as locked_by_rnd,
|
44
|
+
|
45
|
+
-- pid
|
46
|
+
substring_index(
|
47
|
+
substring_index(locked_by, '.', -2),
|
48
|
+
'.',
|
49
|
+
1
|
50
|
+
) as locked_by_pid,
|
51
|
+
|
52
|
+
-- get host
|
53
|
+
substring(
|
54
|
+
locked_by,
|
55
|
+
1,
|
56
|
+
length(locked_by) -
|
57
|
+
length(substring_index(locked_by, '.', -2)) - 1
|
58
|
+
) as locked_by_host
|
59
|
+
SQL
|
60
|
+
end
|
61
|
+
|
35
62
|
# Resets job to state "waiting" and clears all meta fields
|
36
63
|
# set by workhorse in course of processing this job.
|
37
64
|
#
|
@@ -69,11 +96,6 @@ module Workhorse
|
|
69
96
|
fail "Dirty jobs can't be locked."
|
70
97
|
end
|
71
98
|
|
72
|
-
# TODO: Remove this debug output
|
73
|
-
# if Workhorse::DbJob.lock.find(id).locked_at
|
74
|
-
# puts "Already locked (with FOR UPDATE)"
|
75
|
-
# end
|
76
|
-
|
77
99
|
if locked_at
|
78
100
|
# TODO: Remove this debug output
|
79
101
|
# puts "Already locked. Job: #{self.id} Worker: #{worker_id}"
|
data/lib/workhorse/enqueuer.rb
CHANGED
@@ -3,11 +3,11 @@ module Workhorse
|
|
3
3
|
# Enqueue any object that is serializable and has a `perform` method
|
4
4
|
def enqueue(job, queue: nil, priority: 0, perform_at: Time.now, description: nil)
|
5
5
|
return DbJob.create!(
|
6
|
-
queue:
|
7
|
-
priority:
|
8
|
-
perform_at:
|
6
|
+
queue: queue,
|
7
|
+
priority: priority,
|
8
|
+
perform_at: perform_at,
|
9
9
|
description: description,
|
10
|
-
handler:
|
10
|
+
handler: Marshal.dump(job)
|
11
11
|
)
|
12
12
|
end
|
13
13
|
|
@@ -27,7 +27,7 @@ module Workhorse::Jobs
|
|
27
27
|
ids = rel.pluck(:id)
|
28
28
|
|
29
29
|
unless ids.empty?
|
30
|
-
messages << "Detected #{ids.size} jobs that were locked more than "\
|
30
|
+
messages << "Detected #{ids.size} jobs that were locked more than " \
|
31
31
|
"#{@locked_to_started_threshold}s ago and might be stale: #{ids.inspect}."
|
32
32
|
end
|
33
33
|
end
|
@@ -39,7 +39,7 @@ module Workhorse::Jobs
|
|
39
39
|
ids = rel.pluck(:id)
|
40
40
|
|
41
41
|
unless ids.empty?
|
42
|
-
messages << "Detected #{ids.size} jobs that are running for longer than "\
|
42
|
+
messages << "Detected #{ids.size} jobs that are running for longer than " \
|
43
43
|
"#{@run_time_threshold}s ago and might be stale: #{ids.inspect}."
|
44
44
|
end
|
45
45
|
end
|
data/lib/workhorse/poller.rb
CHANGED
@@ -27,6 +27,8 @@ module Workhorse
|
|
27
27
|
fail 'Poller is already running.' if running?
|
28
28
|
@running = true
|
29
29
|
|
30
|
+
clean_stuck_jobs! if Workhorse.clean_stuck_jobs
|
31
|
+
|
30
32
|
@thread = Thread.new do
|
31
33
|
loop do
|
32
34
|
break unless running?
|
@@ -65,6 +67,61 @@ module Workhorse
|
|
65
67
|
|
66
68
|
private
|
67
69
|
|
70
|
+
def clean_stuck_jobs!
|
71
|
+
with_global_lock timeout: MAX_LOCK_TIMEOUT do
|
72
|
+
Workhorse.tx_callback.call do
|
73
|
+
# Basic relation: Fetch jobs locked by current host in state 'locked' or
|
74
|
+
# 'started'
|
75
|
+
rel = Workhorse::DbJob.select('*').from(<<~SQL)
|
76
|
+
(#{Workhorse::DbJob.with_split_locked_by.to_sql}) #{Workhorse::DbJob.table_name}
|
77
|
+
SQL
|
78
|
+
rel.where!(
|
79
|
+
locked_by_host: worker.hostname,
|
80
|
+
state: [Workhorse::DbJob::STATE_LOCKED, Workhorse::DbJob::STATE_STARTED]
|
81
|
+
)
|
82
|
+
|
83
|
+
# Select all pids
|
84
|
+
job_pids = rel.distinct.pluck(:locked_by_pid).to_set(&:to_i)
|
85
|
+
|
86
|
+
# Get pids without active process
|
87
|
+
orphaned_pids = job_pids.select do |pid|
|
88
|
+
Process.getpgid(pid)
|
89
|
+
false
|
90
|
+
rescue Errno::ESRCH
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
# Reset jobs in state 'locked'
|
95
|
+
rel.where(locked_by_pid: orphaned_pids.to_a, state: Workhorse::DbJob::STATE_LOCKED).each do |job|
|
96
|
+
worker.log(
|
97
|
+
"Job ##{job.id} has been locked but not yet startet by PID #{job.locked_by_pid} on host " \
|
98
|
+
"#{job.locked_by_host}, but the process is not running anymore. This job has therefore been " \
|
99
|
+
"reset (set to 'waiting') by the Workhorse cleanup logic.",
|
100
|
+
:warn
|
101
|
+
)
|
102
|
+
job.reset!(true)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Mark jobs in state 'started' as failed
|
106
|
+
rel.where(locked_by_pid: orphaned_pids.to_a, state: Workhorse::DbJob::STATE_STARTED).each do |job|
|
107
|
+
worker.log(
|
108
|
+
"Job ##{job.id} has been started by PID #{job.locked_by_pid} on host #{job.locked_by_host} " \
|
109
|
+
'but the process is not running anymore. This job has therefore been marked as ' \
|
110
|
+
'failed by the Workhorse cleanup logic.',
|
111
|
+
:warn
|
112
|
+
)
|
113
|
+
exception = Exception.new(
|
114
|
+
"Job has been started by PID #{job.locked_by_pid} on host #{job.locked_by_host} " \
|
115
|
+
'but the process is not running anymore. This job has therefore been marked as ' \
|
116
|
+
'failed by the Workhorse cleanup logic.'
|
117
|
+
)
|
118
|
+
exception.set_backtrace []
|
119
|
+
job.mark_failed!(exception)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
68
125
|
def sleep
|
69
126
|
remaining = worker.polling_interval
|
70
127
|
|
@@ -101,15 +158,15 @@ module Workhorse
|
|
101
158
|
if @global_lock_fails > Workhorse.max_global_lock_fails && !@max_global_lock_fails_reached
|
102
159
|
@max_global_lock_fails_reached = true
|
103
160
|
|
104
|
-
worker.log 'Could not obtain global lock, retrying with next poll. '\
|
105
|
-
'This will be the last such message for this worker until '\
|
161
|
+
worker.log 'Could not obtain global lock, retrying with next poll. ' \
|
162
|
+
'This will be the last such message for this worker until ' \
|
106
163
|
'the issue is resolved.', :warn
|
107
164
|
|
108
165
|
message = "Worker reached maximum number of consecutive times (#{Workhorse.max_global_lock_fails}) " \
|
109
166
|
"where the global lock could no be acquired within the specified timeout (#{timeout}). " \
|
110
167
|
'A worker that obtained this lock may have crashed without ending the database ' \
|
111
168
|
'connection properly. On MySQL, use "show processlist;" to see which connection(s) ' \
|
112
|
-
'is / are holding the lock for a long period of time and consider killing them using '\
|
169
|
+
'is / are holding the lock for a long period of time and consider killing them using ' \
|
113
170
|
"MySQL's \"kill <Id>\" command. This message will be issued only once per worker " \
|
114
171
|
'and may only be re-triggered if the error happens again *after* the lock has ' \
|
115
172
|
'been solved in the meantime.'
|
@@ -137,7 +194,6 @@ module Workhorse
|
|
137
194
|
@instant_repoll.make_false
|
138
195
|
|
139
196
|
timeout = [MIN_LOCK_TIMEOUT, [MAX_LOCK_TIMEOUT, worker.polling_interval].min].max
|
140
|
-
|
141
197
|
with_global_lock timeout: timeout do
|
142
198
|
job_ids = []
|
143
199
|
|
@@ -199,9 +255,9 @@ module Workhorse
|
|
199
255
|
# uses the keyword 'AS' in SQL generated for Oracle, which is invalid for
|
200
256
|
# table aliases.
|
201
257
|
union_query_sql = '('
|
202
|
-
union_query_sql +=
|
258
|
+
union_query_sql += "SELECT * FROM (#{union_parts.shift.to_sql}) union_0"
|
203
259
|
union_parts.each_with_index do |part, idx|
|
204
|
-
union_query_sql +=
|
260
|
+
union_query_sql += " UNION SELECT * FROM (#{part.to_sql}) union_#{idx + 1}"
|
205
261
|
end
|
206
262
|
union_query_sql += ') subselect'
|
207
263
|
|
@@ -277,7 +333,7 @@ module Workhorse
|
|
277
333
|
unless worker.queues.empty?
|
278
334
|
if worker.queues.include?(nil)
|
279
335
|
where = table[:queue].eq(nil)
|
280
|
-
remaining_queues = worker.queues.
|
336
|
+
remaining_queues = worker.queues.compact
|
281
337
|
unless remaining_queues.empty?
|
282
338
|
where = where.or(table[:queue].in(remaining_queues))
|
283
339
|
end
|
data/lib/workhorse/pool.rb
CHANGED
@@ -6,11 +6,11 @@ module Workhorse
|
|
6
6
|
def initialize(size)
|
7
7
|
@size = size
|
8
8
|
@executor = Concurrent::ThreadPoolExecutor.new(
|
9
|
-
min_threads:
|
10
|
-
max_threads:
|
11
|
-
max_queue:
|
9
|
+
min_threads: 0,
|
10
|
+
max_threads: @size,
|
11
|
+
max_queue: 0,
|
12
12
|
fallback_policy: :abort,
|
13
|
-
auto_terminate:
|
13
|
+
auto_terminate: false
|
14
14
|
)
|
15
15
|
@mutex = Mutex.new
|
16
16
|
@active_threads = Concurrent::AtomicFixnum.new(0)
|
@@ -33,12 +33,10 @@ module Workhorse
|
|
33
33
|
active_threads.increment
|
34
34
|
|
35
35
|
@executor.post do
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@on_idle.try(:call)
|
41
|
-
end
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
active_threads.decrement
|
39
|
+
@on_idle.try(:call)
|
42
40
|
end
|
43
41
|
end
|
44
42
|
end
|
data/lib/workhorse/worker.rb
CHANGED
@@ -43,7 +43,7 @@ module Workhorse
|
|
43
43
|
# `Rails.logger`.
|
44
44
|
def initialize(queues: [], pool_size: nil, polling_interval: 300, auto_terminate: true, quiet: true, instant_repolling: false, logger: nil)
|
45
45
|
@queues = queues
|
46
|
-
@pool_size = pool_size || queues.size + 1
|
46
|
+
@pool_size = pool_size || (queues.size + 1)
|
47
47
|
@polling_interval = polling_interval
|
48
48
|
@auto_terminate = auto_terminate
|
49
49
|
@state = :initialized
|
@@ -54,7 +54,7 @@ module Workhorse
|
|
54
54
|
@poller = Workhorse::Poller.new(self)
|
55
55
|
@logger = logger
|
56
56
|
|
57
|
-
unless (@polling_interval / 0.1).round(2).modulo(1)
|
57
|
+
unless (@polling_interval / 0.1).round(2).modulo(1).zero?
|
58
58
|
fail 'Polling interval must be a multiple of 0.1.'
|
59
59
|
end
|
60
60
|
|
@@ -74,7 +74,15 @@ module Workhorse
|
|
74
74
|
end
|
75
75
|
|
76
76
|
def id
|
77
|
-
@id ||= "#{
|
77
|
+
@id ||= "#{hostname}.#{pid}.#{SecureRandom.hex(3)}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def pid
|
81
|
+
@pid ||= Process.pid
|
82
|
+
end
|
83
|
+
|
84
|
+
def hostname
|
85
|
+
@hostname ||= Socket.gethostname
|
78
86
|
end
|
79
87
|
|
80
88
|
# Starts the worker. This call is not blocking - call {wait} for this
|
@@ -135,12 +143,10 @@ module Workhorse
|
|
135
143
|
log "Posting job #{db_job_id} to thread pool"
|
136
144
|
|
137
145
|
@pool.post do
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
Workhorse.on_exception.call(e)
|
143
|
-
end
|
146
|
+
Workhorse::Performer.new(db_job_id, self).perform
|
147
|
+
rescue Exception => e
|
148
|
+
log %(#{e.message}\n#{e.backtrace.join("\n")}), :error
|
149
|
+
Workhorse.on_exception.call(e)
|
144
150
|
end
|
145
151
|
end
|
146
152
|
rescue Exception => e
|
data/lib/workhorse.rb
CHANGED
@@ -17,7 +17,7 @@ module Workhorse
|
|
17
17
|
# Returns the performer currently performing the active job. This can only be
|
18
18
|
# called from within a job and the same thread.
|
19
19
|
def self.performer
|
20
|
-
Thread.current[:workhorse_current_performer]\
|
20
|
+
Thread.current[:workhorse_current_performer] \
|
21
21
|
|| fail('No performer is associated with the current thread. This method must always be called inside of a job.')
|
22
22
|
end
|
23
23
|
|
@@ -58,6 +58,11 @@ module Workhorse
|
|
58
58
|
mattr_accessor :perform_jobs_in_tx
|
59
59
|
self.perform_jobs_in_tx = true
|
60
60
|
|
61
|
+
# If enabled, each poller will attempt to clean jobs that are stuck in state
|
62
|
+
# 'locked' or 'running' when it is starting up.
|
63
|
+
mattr_accessor :clean_stuck_jobs
|
64
|
+
self.clean_stuck_jobs = false
|
65
|
+
|
61
66
|
# This setting is for {Workhorse::Jobs::DetectStaleJobsJob} and specifies the
|
62
67
|
# maximum number of seconds a job is allowed to stay 'locked' before this job
|
63
68
|
# throws an exception. Set this to 0 to skip this check.
|
@@ -92,5 +97,5 @@ if RUBY_PLATFORM != 'java'
|
|
92
97
|
end
|
93
98
|
|
94
99
|
if defined?(ActiveJob)
|
95
|
-
require 'active_job/queue_adapters/workhorse_adapter
|
100
|
+
require 'active_job/queue_adapters/workhorse_adapter'
|
96
101
|
end
|
data/test/lib/test_helper.rb
CHANGED
@@ -44,10 +44,10 @@ end
|
|
44
44
|
|
45
45
|
ActiveRecord::Base.establish_connection(
|
46
46
|
adapter: 'mysql2',
|
47
|
-
database: ENV
|
48
|
-
username: ENV
|
49
|
-
password: ENV
|
50
|
-
host: ENV
|
47
|
+
database: ENV.fetch('DB_NAME', nil) || 'workhorse',
|
48
|
+
username: ENV.fetch('DB_USERNAME', nil) || 'root',
|
49
|
+
password: ENV.fetch('DB_PASSWORD', nil) || '',
|
50
|
+
host: ENV.fetch('DB_HOST', nil) || '127.0.0.1',
|
51
51
|
pool: 10
|
52
52
|
)
|
53
53
|
|
@@ -41,7 +41,7 @@ class Workhorse::PollerTest < WorkhorseTest
|
|
41
41
|
|
42
42
|
begin
|
43
43
|
fail 'Some exception'
|
44
|
-
rescue => e
|
44
|
+
rescue StandardError => e
|
45
45
|
last_job.mark_failed!(e)
|
46
46
|
end
|
47
47
|
|
@@ -148,9 +148,10 @@ class Workhorse::PollerTest < WorkhorseTest
|
|
148
148
|
assert_equal 25, used_workers
|
149
149
|
end
|
150
150
|
|
151
|
-
# rubocop: disable Style/GlobalVars
|
152
151
|
def test_connection_loss
|
152
|
+
# rubocop: disable Style/GlobalVars
|
153
153
|
$thread_conn = nil
|
154
|
+
# rubocop: enable Style/GlobalVars
|
154
155
|
|
155
156
|
Workhorse.enqueue BasicJob.new(sleep_time: 3)
|
156
157
|
|
@@ -175,10 +176,95 @@ class Workhorse::PollerTest < WorkhorseTest
|
|
175
176
|
|
176
177
|
assert_equal 1, Workhorse::DbJob.succeeded.count
|
177
178
|
end
|
178
|
-
|
179
|
+
|
180
|
+
def test_clean_stuck_jobs_locked
|
181
|
+
[true, false].each do |clean|
|
182
|
+
Workhorse::DbJob.delete_all
|
183
|
+
|
184
|
+
Workhorse.clean_stuck_jobs = clean
|
185
|
+
start_deamon
|
186
|
+
Workhorse.enqueue BasicJob.new(sleep_time: 5)
|
187
|
+
sleep 0.2
|
188
|
+
kill_deamon_workers
|
189
|
+
|
190
|
+
assert_equal 1, Workhorse::DbJob.count
|
191
|
+
|
192
|
+
Workhorse::DbJob.first.update(
|
193
|
+
state: 'locked',
|
194
|
+
started_at: nil
|
195
|
+
)
|
196
|
+
|
197
|
+
Workhorse::Worker.new.poller.send(:clean_stuck_jobs!) if clean
|
198
|
+
|
199
|
+
assert_equal 1, Workhorse::DbJob.count
|
200
|
+
|
201
|
+
Workhorse::DbJob.first.tap do |job|
|
202
|
+
if clean
|
203
|
+
assert_equal 'waiting', job.state
|
204
|
+
assert_nil job.locked_at
|
205
|
+
assert_nil job.locked_by
|
206
|
+
assert_nil job.started_at
|
207
|
+
assert_nil job.last_error
|
208
|
+
else
|
209
|
+
assert_equal 'locked', job.state
|
210
|
+
end
|
211
|
+
end
|
212
|
+
ensure
|
213
|
+
Workhorse.clean_stuck_jobs = false
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_clean_stuck_jobs_running
|
218
|
+
[true, false].each do |clean|
|
219
|
+
Workhorse::DbJob.delete_all
|
220
|
+
|
221
|
+
Workhorse.clean_stuck_jobs = true
|
222
|
+
start_deamon
|
223
|
+
Workhorse.enqueue BasicJob.new(sleep_time: 5)
|
224
|
+
sleep 0.2
|
225
|
+
kill_deamon_workers
|
226
|
+
|
227
|
+
assert_equal 1, Workhorse::DbJob.count
|
228
|
+
assert_equal 'started', Workhorse::DbJob.first.state
|
229
|
+
|
230
|
+
work 0.1 if clean
|
231
|
+
|
232
|
+
assert_equal 1, Workhorse::DbJob.count
|
233
|
+
|
234
|
+
Workhorse::DbJob.first.tap do |job|
|
235
|
+
if clean
|
236
|
+
assert_equal 'failed', job.state
|
237
|
+
assert_match(/started by PID #{@daemon.workers.first.pid}/, job.last_error)
|
238
|
+
assert_match(/on host #{Socket.gethostname}/, job.last_error)
|
239
|
+
else
|
240
|
+
assert_equal 'started', job.state
|
241
|
+
end
|
242
|
+
end
|
243
|
+
ensure
|
244
|
+
Workhorse.clean_stuck_jobs = false
|
245
|
+
end
|
246
|
+
end
|
179
247
|
|
180
248
|
private
|
181
249
|
|
250
|
+
def kill_deamon_workers
|
251
|
+
@daemon.workers.each do |worker|
|
252
|
+
Process.kill 'KILL', worker.pid
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def start_deamon
|
257
|
+
@daemon = Workhorse::Daemon.new(pidfile: 'tmp/pids/test%s.pid') do |d|
|
258
|
+
d.worker 'Test Worker' do
|
259
|
+
Workhorse::Worker.start_and_wait(
|
260
|
+
pool_size: 1,
|
261
|
+
polling_interval: 0.1
|
262
|
+
)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
@daemon.start
|
266
|
+
end
|
267
|
+
|
182
268
|
def setup
|
183
269
|
Workhorse::DbJob.delete_all
|
184
270
|
end
|
data/workhorse.gemspec
CHANGED
@@ -1,53 +1,40 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: workhorse 1.2.
|
2
|
+
# stub: workhorse 1.2.16 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "workhorse".freeze
|
6
|
-
s.version = "1.2.
|
6
|
+
s.version = "1.2.16"
|
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 = "2023-
|
11
|
+
s.date = "2023-09-18"
|
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/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
|
-
s.rubygems_version = "3.
|
13
|
+
s.rubygems_version = "3.1.2".freeze
|
14
14
|
s.summary = "Multi-threaded job backend with database queuing for ruby.".freeze
|
15
15
|
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/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]
|
16
16
|
|
17
17
|
if s.respond_to? :specification_version then
|
18
18
|
s.specification_version = 4
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
else
|
34
|
-
s.add_dependency(%q<bundler>.freeze, [">= 0"])
|
35
|
-
s.add_dependency(%q<rake>.freeze, [">= 0"])
|
36
|
-
s.add_dependency(%q<rubocop>.freeze, ["= 0.51.0"])
|
37
|
-
s.add_dependency(%q<minitest>.freeze, [">= 0"])
|
38
|
-
s.add_dependency(%q<mysql2>.freeze, [">= 0"])
|
39
|
-
s.add_dependency(%q<colorize>.freeze, [">= 0"])
|
40
|
-
s.add_dependency(%q<benchmark-ips>.freeze, [">= 0"])
|
41
|
-
s.add_dependency(%q<activejob>.freeze, [">= 0"])
|
42
|
-
s.add_dependency(%q<pry>.freeze, [">= 0"])
|
43
|
-
s.add_dependency(%q<activesupport>.freeze, [">= 0"])
|
44
|
-
s.add_dependency(%q<activerecord>.freeze, [">= 0"])
|
45
|
-
s.add_dependency(%q<concurrent-ruby>.freeze, [">= 0"])
|
46
|
-
end
|
21
|
+
if s.respond_to? :add_runtime_dependency then
|
22
|
+
s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
|
23
|
+
s.add_development_dependency(%q<rake>.freeze, [">= 0"])
|
24
|
+
s.add_development_dependency(%q<rubocop>.freeze, ["~> 1.28.0"])
|
25
|
+
s.add_development_dependency(%q<minitest>.freeze, [">= 0"])
|
26
|
+
s.add_development_dependency(%q<mysql2>.freeze, [">= 0"])
|
27
|
+
s.add_development_dependency(%q<colorize>.freeze, [">= 0"])
|
28
|
+
s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
|
29
|
+
s.add_development_dependency(%q<activejob>.freeze, [">= 0"])
|
30
|
+
s.add_development_dependency(%q<pry>.freeze, [">= 0"])
|
31
|
+
s.add_runtime_dependency(%q<activesupport>.freeze, [">= 0"])
|
32
|
+
s.add_runtime_dependency(%q<activerecord>.freeze, [">= 0"])
|
33
|
+
s.add_runtime_dependency(%q<concurrent-ruby>.freeze, [">= 0"])
|
47
34
|
else
|
48
35
|
s.add_dependency(%q<bundler>.freeze, [">= 0"])
|
49
36
|
s.add_dependency(%q<rake>.freeze, [">= 0"])
|
50
|
-
s.add_dependency(%q<rubocop>.freeze, ["
|
37
|
+
s.add_dependency(%q<rubocop>.freeze, ["~> 1.28.0"])
|
51
38
|
s.add_dependency(%q<minitest>.freeze, [">= 0"])
|
52
39
|
s.add_dependency(%q<mysql2>.freeze, [">= 0"])
|
53
40
|
s.add_dependency(%q<colorize>.freeze, [">= 0"])
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: workhorse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sitrox
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -42,16 +42,16 @@ dependencies:
|
|
42
42
|
name: rubocop
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 1.28.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 1.28.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: minitest
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|