workhorse 1.2.14 → 1.2.16
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 +6 -1
- data/.rubocop.yml +115 -16
- data/CHANGELOG.md +19 -0
- data/README.md +32 -2
- 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/jobs/run_rails_op.rb +4 -0
- data/lib/workhorse/poller.rb +65 -9
- 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 +7 -7
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,24 @@
|
|
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
|
+
|
18
|
+
## 1.2.15 - 2023-08-28
|
19
|
+
|
20
|
+
* Add capability to skip transactions for enqueued RailsOps operations.
|
21
|
+
|
3
22
|
## 1.2.14 - 2023-08-23
|
4
23
|
|
5
24
|
* Add documentation for transaction handling.
|
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.
|
@@ -303,8 +304,8 @@ You can turn off transaction wrapping in the following ways:
|
|
303
304
|
up their own transaction(s) or jobs that explicitly do not need a transaction
|
304
305
|
for whatever reason.
|
305
306
|
|
306
|
-
Usage of this feature depends on whether you are dealing with an ActiveJob
|
307
|
-
or a plain Workhorse job class.
|
307
|
+
Usage of this feature depends on whether you are dealing with an ActiveJob
|
308
|
+
job, an enqueued RailsOps operation or a plain Workhorse job class.
|
308
309
|
|
309
310
|
For ActiveJob:
|
310
311
|
|
@@ -329,6 +330,29 @@ You can turn off transaction wrapping in the following ways:
|
|
329
330
|
end
|
330
331
|
```
|
331
332
|
|
333
|
+
For enqueuable RailsOps operations:
|
334
|
+
|
335
|
+
1. Add the following static method to your operation class:
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
class MyOp < RailsOps::Operation
|
339
|
+
def self.skip_tx?
|
340
|
+
true
|
341
|
+
end
|
342
|
+
end
|
343
|
+
```
|
344
|
+
|
345
|
+
For plain Workhrose job clases:
|
346
|
+
|
347
|
+
1. Add the following static method to your job class:
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
class MyJob
|
351
|
+
def self.skip_tx?
|
352
|
+
true
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
332
356
|
## Exception handling
|
333
357
|
|
334
358
|
Per default, exceptions occurring in a worker thread will only be visible in the
|
@@ -504,6 +528,12 @@ exception is thrown (which may cause a notification if you configured
|
|
504
528
|
`on_exception` accordingly). See the job's API documentation for more
|
505
529
|
information.
|
506
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
|
+
|
507
537
|
## Frequently asked questions
|
508
538
|
|
509
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,18 +158,18 @@ 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
|
-
|
115
|
-
|
171
|
+
'and may only be re-triggered if the error happens again *after* the lock has ' \
|
172
|
+
'been solved in the meantime.'
|
116
173
|
|
117
174
|
worker.log message
|
118
175
|
exception = StandardError.new(message)
|
@@ -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
|
@@ -246,7 +246,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
246
246
|
- !ruby/object:Gem::Version
|
247
247
|
version: '0'
|
248
248
|
requirements: []
|
249
|
-
rubygems_version: 3.4.
|
249
|
+
rubygems_version: 3.4.6
|
250
250
|
signing_key:
|
251
251
|
specification_version: 4
|
252
252
|
summary: Multi-threaded job backend with database queuing for ruby.
|