workhorse 1.3.0.rc2 → 1.3.0.rc4
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 +1 -1
- data/CHANGELOG.md +13 -2
- data/FAQ.md +10 -10
- data/Gemfile +5 -5
- data/LICENSE +1 -1
- data/README.md +23 -23
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/active_job/queue_adapters/workhorse_adapter.rb +22 -6
- data/lib/workhorse/active_job_extension.rb +9 -0
- data/lib/workhorse/daemon.rb +99 -0
- data/lib/workhorse/db_job.rb +53 -0
- data/lib/workhorse/enqueuer.rb +24 -3
- data/lib/workhorse/jobs/cleanup_succeeded_jobs.rb +18 -0
- data/lib/workhorse/jobs/detect_stale_jobs_job.rb +22 -8
- data/lib/workhorse/jobs/run_active_job.rb +17 -0
- data/lib/workhorse/jobs/run_rails_op.rb +19 -0
- data/lib/workhorse/performer.rb +42 -0
- data/lib/workhorse/poller.rb +68 -7
- data/lib/workhorse/pool.rb +32 -4
- data/lib/workhorse/scoped_env.rb +20 -0
- data/lib/workhorse/worker.rb +93 -4
- data/lib/workhorse.rb +51 -18
- data/workhorse.gemspec +5 -5
- 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: dca93061dd92dfbd43f1a44dcb2958181257d16079f408114af13a80197e48c6
|
4
|
+
data.tar.gz: 8a4c202c76b7380912288331bc8ef24a4dc8839c4455a4c455b48dddeb631588
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8b5829f8d84bd7ed135bfcc467f5b9afd313acd9f36d94b90bf77e106b0cf6f627189bf89e6f0ba31aecf791875fd5217091b3b5034d109473623da5d1b4cf3
|
7
|
+
data.tar.gz: b50b7514b14ce6170b192835a9d61b03d84577de0466ef471bd60f5561a873930151d61aa9c1c3638dce889c0bdf6432a6ae7289f9e061ff4e8220313dc3937a
|
data/.github/workflows/ruby.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# Workhorse Changelog
|
2
2
|
|
3
|
+
## 1.3.0.rc4 - 2025-08-27
|
4
|
+
|
5
|
+
* Fix race-condition in polling mechanism which could result in workers
|
6
|
+
trying to run a job that is not yet locked.
|
7
|
+
|
8
|
+
Sitrox reference: #128333.
|
9
|
+
|
10
|
+
## 1.3.0.rc3 - 2025-06-10
|
11
|
+
|
12
|
+
* Require Rails 7.0.0 or later
|
13
|
+
|
3
14
|
## 1.3.0.rc2 - 2025-06-10
|
4
15
|
|
5
16
|
* Update development dependencies
|
@@ -46,7 +57,7 @@
|
|
46
57
|
* Comply with RuboCop
|
47
58
|
|
48
59
|
* Skip `at_exit` handlers when exiting in ShellHandler. This ensures
|
49
|
-
compatibility with the `debug` gem, which
|
60
|
+
compatibility with the `debug` gem, which would otherwise hang when using the
|
50
61
|
Workhorse shell handler.
|
51
62
|
|
52
63
|
Sitrox reference: #128333.
|
@@ -296,7 +307,7 @@ battle before it can be considered stable.
|
|
296
307
|
* Simplify locking during polling. Other than locking individual jobs, pollers
|
297
308
|
now acquire a global lock. While this can lead to many pollers waiting for
|
298
309
|
each others locks, performing a poll is usually done very quickly and the
|
299
|
-
performance drawback is to be considered
|
310
|
+
performance drawback is to be considered negligible. This change should work
|
300
311
|
around some deadlock issues as well as an issue where a job was obtained by
|
301
312
|
more than one poller.
|
302
313
|
|
data/FAQ.md
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
## Why should I use workhorse over *X*?
|
4
4
|
|
5
|
-
There
|
5
|
+
There are a variety of job backends for Ruby,
|
6
6
|
[delayed_job](https://github.com/collectiveidea/delayed_job) probably being the
|
7
7
|
closest one to workhorse.
|
8
8
|
|
9
9
|
Some key advantages we feel workhorse has over other Gems:
|
10
10
|
|
11
|
-
- Workhorse is less than 500 lines of code at its core. The code is
|
11
|
+
- Workhorse is less than 500 lines of code at its core. The code is designed to
|
12
12
|
be easily readable, understandable, and modifiable.
|
13
13
|
|
14
14
|
- Workhorse allows you to run multiple jobs simultaneously *in the same
|
@@ -20,7 +20,7 @@ figure out which one best suits your needs.
|
|
20
20
|
|
21
21
|
## My code is not thread safe. How can I use workhorse safely?
|
22
22
|
|
23
|
-
Job code that is not thread safe
|
23
|
+
Job code that is not thread safe cannot be executed safely in multiple threads
|
24
24
|
of the same process. In these cases, set the `pool_size` to `1` and, if you
|
25
25
|
still want to execute multiple jobs simultaneously, the daemon `count` to a
|
26
26
|
number greater than `1`:
|
@@ -33,10 +33,10 @@ end
|
|
33
33
|
|
34
34
|
## I'm using jRuby. How can I use the daemon handler?
|
35
35
|
|
36
|
-
As
|
36
|
+
As Java processes in general cannot be forked safely, the daemon handler
|
37
37
|
provided with this Gem does not support jRuby platforms.
|
38
38
|
|
39
|
-
If your jRuby application consists of
|
39
|
+
If your jRuby application consists of a single application process, it is
|
40
40
|
recommended to just start the job backend in the same process:
|
41
41
|
|
42
42
|
```ruby
|
@@ -48,7 +48,7 @@ This code is non-blocking, which means that it will run as long as the process
|
|
48
48
|
is up. Make sure to trap `INT` and `TERM` and call `worker.shutdown` when the
|
49
49
|
process stops.
|
50
50
|
|
51
|
-
If you have multiple application processes
|
51
|
+
If you have multiple application processes, however, you may want to start the
|
52
52
|
worker in a separate process. For this purpose, adapt the startup script
|
53
53
|
`bin/workhorse.rb` so that it is blocking:
|
54
54
|
|
@@ -56,9 +56,9 @@ worker in a separate process. For this purpose, adapt the startup script
|
|
56
56
|
Workhorse::Worker.start_and_wait(pool_size: 5)
|
57
57
|
```
|
58
58
|
|
59
|
-
This can then be started and "daemonized" using standard
|
59
|
+
This can then be started and "daemonized" using standard Linux tools.
|
60
60
|
|
61
|
-
## I'm getting random autoloading
|
61
|
+
## I'm getting random autoloading exceptions
|
62
62
|
|
63
63
|
Make sure to always start the worker in *production mode*, i.e.:
|
64
64
|
|
@@ -69,7 +69,7 @@ RAILS_ENV=production bin/workhorse.rb start
|
|
69
69
|
## I'm getting "No live threads left. Deadlock?" exceptions
|
70
70
|
|
71
71
|
Make sure the Worker is logging somewhere and check the logs. Typically there is
|
72
|
-
an underlying error that leads to the exception, e.g
|
72
|
+
an underlying error that leads to the exception, e.g., a missing migration in
|
73
73
|
production mode.
|
74
74
|
|
75
75
|
## Why does workhorse not support timeouts?
|
@@ -77,5 +77,5 @@ production mode.
|
|
77
77
|
Generic timeout implementations are [a dangerous
|
78
78
|
thing](http://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/)
|
79
79
|
in Ruby. This is why we decided against providing this feature in Workhorse and
|
80
|
-
recommend to implement
|
80
|
+
recommend to implement timeouts inside of your jobs - i.e. via network
|
81
81
|
timeouts.
|
data/Gemfile
CHANGED
@@ -3,12 +3,12 @@ source 'https://rubygems.org'
|
|
3
3
|
# Specify gem dependencies in the .gemspec file
|
4
4
|
gemspec
|
5
5
|
|
6
|
+
gem 'activejob', '~> 7.1.3'
|
7
|
+
gem 'activerecord', '~> 7.1.3'
|
8
|
+
gem 'benchmark-ips'
|
6
9
|
gem 'bundler'
|
7
|
-
gem 'rake'
|
8
|
-
gem 'rubocop', '~> 1.28.0' # Latest version supported with Ruby 2.5
|
9
10
|
gem 'minitest'
|
10
11
|
gem 'mysql2'
|
11
|
-
gem 'benchmark-ips'
|
12
|
-
gem 'activejob', '~> 7.1.3'
|
13
|
-
gem 'activerecord', '~> 7.1.3'
|
14
12
|
gem 'pry'
|
13
|
+
gem 'rake'
|
14
|
+
gem 'rubocop', '~> 1.28.0' # Latest version supported with Ruby 2.5
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
# Workhorse
|
5
5
|
|
6
|
-
Multi-threaded job backend with database queuing for
|
6
|
+
Multi-threaded job backend with database queuing for Ruby. Battle-tested and ready for production-use.
|
7
7
|
|
8
8
|
## Introduction
|
9
9
|
|
@@ -12,7 +12,7 @@ How it works:
|
|
12
12
|
* Jobs are instances of classes that support the `perform` method.
|
13
13
|
* Jobs are persisted in the database using ActiveRecord.
|
14
14
|
* Each job has a priority, the default being 0. Jobs with higher priorities
|
15
|
-
(lower
|
15
|
+
(lower numbers have higher priority, with 0 being the highest) get processed first.
|
16
16
|
* Each job can be set to execute after a certain date / time.
|
17
17
|
* You can start one or more worker processes.
|
18
18
|
* Each worker is configurable as to which queue(s) it processes. Jobs in the
|
@@ -34,12 +34,12 @@ What it does not do:
|
|
34
34
|
### Requirements
|
35
35
|
|
36
36
|
* Ruby `>= 3.0` (may work with earlier versions but is untested)
|
37
|
-
* Rails `>=
|
37
|
+
* Rails `>= 7.0`
|
38
38
|
* A database and table handler that properly supports row-level locking (such as
|
39
39
|
MySQL with InnoDB, PostgreSQL, or Oracle).
|
40
40
|
* If you are planning on using the daemons handler:
|
41
41
|
* An operating system and file system that supports file locking.
|
42
|
-
* MRI
|
42
|
+
* MRI Ruby (aka "CRuby") as jRuby does not support `fork`. See the
|
43
43
|
[FAQ](FAQ.md#im-using-jruby-how-can-i-use-the-daemon-handler) for possible workarounds.
|
44
44
|
|
45
45
|
### Installing under Rails
|
@@ -81,8 +81,8 @@ GRANT execute ON DBMS_LOCK TO <schema-name>;
|
|
81
81
|
### Basic jobs
|
82
82
|
|
83
83
|
Workhorse can handle any jobs that support the `perform` method and are
|
84
|
-
serializable. To queue a basic job, use
|
85
|
-
You can optionally pass a queue name, a priority and a description
|
84
|
+
serializable. To queue a basic job, use `Workhorse.enqueue`.
|
85
|
+
You can optionally pass a queue name, a priority, and a description.
|
86
86
|
|
87
87
|
```ruby
|
88
88
|
class MyJob
|
@@ -108,15 +108,15 @@ method `Workhorse.enqueue_op`:
|
|
108
108
|
Workhorse.enqueue_op Operations::Jobs::CleanUpDatabase, { queue: :maintenance, priority: 2 }, quiet: true
|
109
109
|
```
|
110
110
|
|
111
|
-
The first argument of the method is the Operation you want to run.
|
112
|
-
using the second argument will be used by Workhorse and
|
111
|
+
The first argument of the method is the Operation you want to run. Parameters passed in
|
112
|
+
using the second argument will be used by Workhorse and parameters passed using the
|
113
113
|
third argument will be used for operation instantiation at job execution, i.e.:
|
114
114
|
|
115
115
|
```ruby
|
116
116
|
Workhorse.enqueue_op <Operation Class Name>, { <Workhorse Options> }, { <RailsOps Options> }
|
117
117
|
```
|
118
118
|
|
119
|
-
If you do not want to pass any
|
119
|
+
If you do not want to pass any parameters to the operation, just omit the third hash:
|
120
120
|
|
121
121
|
```ruby
|
122
122
|
Workhorse.enqueue_op Operations::Jobs::CleanUpDatabase, queue: :maintenance, priority: 2
|
@@ -138,9 +138,9 @@ achieving regular execution:
|
|
138
138
|
by scheduling the job before knowing whether the current run will succeed.
|
139
139
|
Proceed down this path at your own peril!)
|
140
140
|
|
141
|
-
*Example:* A job that takes 5 seconds to run and
|
142
|
-
|
143
|
-
|
141
|
+
*Example:* A job that takes 5 seconds to run and reschedules itself every
|
142
|
+
10 minutes. If started at 12:00 sharp, after one hour it will execute at
|
143
|
+
13:00:30 at the earliest due to cumulative execution time.
|
144
144
|
|
145
145
|
In its most basic form, the `perform` method of a job would look as follows:
|
146
146
|
|
@@ -198,7 +198,7 @@ achieving regular execution:
|
|
198
198
|
of which is that only one 'worker' should be started by the ShellHandler.
|
199
199
|
Otherwise there would be multiple jobs scheduled at the same time.
|
200
200
|
|
201
|
-
Please refer to the documentation
|
201
|
+
Please refer to the documentation for
|
202
202
|
[rufus-scheduler](https://github.com/jmettraux/rufus-scheduler) (or the
|
203
203
|
scheduler of your choice) for further options concerning the timing of the
|
204
204
|
jobs.
|
@@ -227,8 +227,7 @@ Workhorse::Worker.start_and_wait(
|
|
227
227
|
|
228
228
|
See [code documentation](http://www.rubydoc.info/github/sitrox/workhorse/Workhorse%2FWorker:initialize)
|
229
229
|
for more information on the arguments. All arguments passed to `start_and_wait`
|
230
|
-
are passed to the
|
231
|
-
in turn passed to the initializer of `Workhorse::Worker`.
|
230
|
+
are passed to the initializer of `Workhorse::Worker`.
|
232
231
|
|
233
232
|
### Start workers using a daemon script
|
234
233
|
|
@@ -264,7 +263,7 @@ end
|
|
264
263
|
|
265
264
|
### Instant repolling
|
266
265
|
|
267
|
-
|
266
|
+
By default, each worker only polls in the given interval. This means that if
|
268
267
|
you schedule, for example, 50 jobs at once and have a polling interval of 1
|
269
268
|
minute with a queue size of 1, the poller would tackle the first job and then
|
270
269
|
wait for a whole minute until the next poll. This would mean that these 50 jobs
|
@@ -287,8 +286,8 @@ transaction is created.
|
|
287
286
|
|
288
287
|
### Transaction callback
|
289
288
|
|
290
|
-
By default, transactions are created using `ActiveRecord::Base.transaction
|
291
|
-
|
289
|
+
By default, transactions are created using `ActiveRecord::Base.transaction`.
|
290
|
+
You can customize this using the setting `config.tx_callback` in your
|
292
291
|
`config/initializers/workhorse.rb` (see commented out section in the generated
|
293
292
|
configuration file).
|
294
293
|
|
@@ -342,7 +341,7 @@ You can turn off transaction wrapping in the following ways:
|
|
342
341
|
end
|
343
342
|
```
|
344
343
|
|
345
|
-
For plain
|
344
|
+
For plain Workhorse job classes:
|
346
345
|
|
347
346
|
1. Add the following static method to your job class:
|
348
347
|
|
@@ -352,13 +351,14 @@ You can turn off transaction wrapping in the following ways:
|
|
352
351
|
true
|
353
352
|
end
|
354
353
|
end
|
354
|
+
```
|
355
355
|
|
356
356
|
## Exception handling
|
357
357
|
|
358
|
-
|
358
|
+
By default, exceptions occurring in a worker thread will only be visible in the
|
359
359
|
respective log file, usually `production.log`. If you'd like to perform specific
|
360
360
|
actions when an exception arises, set the global option `on_exception` to a
|
361
|
-
callback of your
|
361
|
+
callback of your liking, e.g.:
|
362
362
|
|
363
363
|
```ruby
|
364
364
|
# config/initializers/workhorse.rb
|
@@ -467,7 +467,7 @@ succeeded jobs. You can run this using your scheduler in a specific interval.
|
|
467
467
|
## Memory handling
|
468
468
|
|
469
469
|
When a worker exceeds the memory limit specified by
|
470
|
-
`config.max_worker_memory_mb` (assuming it is configured
|
470
|
+
`config.max_worker_memory_mb` (assuming it is configured to a value greater than 0), it initiates
|
471
471
|
a graceful shutdown process by creating a shutdown file named
|
472
472
|
`tmp/pids/workhorse.<pid>.shutdown`.
|
473
473
|
|
@@ -564,4 +564,4 @@ Please consult the [FAQ](FAQ.md).
|
|
564
564
|
|
565
565
|
## Copyright
|
566
566
|
|
567
|
-
Copyright © 2017 -
|
567
|
+
Copyright © 2017 - 2026 Sitrox. See `LICENSE` for further details.
|
data/Rakefile
CHANGED
@@ -11,8 +11,8 @@ task :gemspec do
|
|
11
11
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
12
12
|
spec.require_paths = ['lib']
|
13
13
|
|
14
|
-
spec.add_dependency 'activesupport'
|
15
|
-
spec.add_dependency 'activerecord'
|
14
|
+
spec.add_dependency 'activesupport', '>= 7.0.0'
|
15
|
+
spec.add_dependency 'activerecord', '>= 7.0.0'
|
16
16
|
spec.add_dependency 'concurrent-ruby'
|
17
17
|
end
|
18
18
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.3.0.
|
1
|
+
1.3.0.rc4
|
@@ -1,28 +1,44 @@
|
|
1
1
|
module ActiveJob
|
2
2
|
module QueueAdapters
|
3
|
-
#
|
3
|
+
# Workhorse adapter for ActiveJob.
|
4
4
|
#
|
5
|
-
# Workhorse is a multi-threaded job backend with database queuing for
|
6
|
-
# Jobs are persisted in the database using
|
7
|
-
# Read more about Workhorse {here}[https://github.com/sitrox/
|
5
|
+
# Workhorse is a multi-threaded job backend with database queuing for Ruby.
|
6
|
+
# Jobs are persisted in the database using ActiveRecord.
|
7
|
+
# Read more about Workhorse {here}[https://github.com/sitrox/workhorse].
|
8
8
|
#
|
9
9
|
# To use Workhorse, set the queue_adapter config to +:workhorse+.
|
10
10
|
#
|
11
11
|
# Rails.application.config.active_job.queue_adapter = :workhorse
|
12
|
+
#
|
13
|
+
# @example Configuration
|
14
|
+
# Rails.application.config.active_job.queue_adapter = :workhorse
|
12
15
|
class WorkhorseAdapter
|
13
16
|
# Defines whether enqueuing should happen implicitly to after commit when called
|
14
17
|
# from inside a transaction. Most adapters should return true, but some adapters
|
15
18
|
# that use the same database as Active Record and are transaction aware can return
|
16
19
|
# false to continue enqueuing jobs as part of the transaction.
|
20
|
+
#
|
21
|
+
# @return [Boolean] False because Workhorse is transaction-aware
|
17
22
|
def enqueue_after_transaction_commit?
|
18
23
|
false
|
19
24
|
end
|
20
25
|
|
21
|
-
|
26
|
+
# Enqueues a job for immediate execution.
|
27
|
+
#
|
28
|
+
# @param job [ActiveJob::Base] The job to enqueue
|
29
|
+
# @return [Workhorse::DbJob] The created database job record
|
30
|
+
# @api private
|
31
|
+
def enqueue(job)
|
22
32
|
Workhorse.enqueue_active_job(job)
|
23
33
|
end
|
24
34
|
|
25
|
-
|
35
|
+
# Enqueues a job for execution at a specific time.
|
36
|
+
#
|
37
|
+
# @param job [ActiveJob::Base] The job to enqueue
|
38
|
+
# @param timestamp [Time] When to execute the job
|
39
|
+
# @return [Workhorse::DbJob] The created database job record
|
40
|
+
# @api private
|
41
|
+
def enqueue_at(job, timestamp = Time.now)
|
26
42
|
Workhorse.enqueue_active_job(job, perform_at: timestamp)
|
27
43
|
end
|
28
44
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module Workhorse
|
2
|
+
# Extension module for ActiveJob integration.
|
2
3
|
module ActiveJobExtension
|
3
4
|
extend ActiveSupport::Concern
|
4
5
|
|
@@ -8,10 +9,18 @@ module Workhorse
|
|
8
9
|
end
|
9
10
|
|
10
11
|
module ClassMethods
|
12
|
+
# Marks this job class to skip database transactions during execution.
|
13
|
+
# Use this for jobs that manage their own transactions or have long-running
|
14
|
+
# operations that should not be wrapped in a transaction.
|
15
|
+
#
|
16
|
+
# @return [void]
|
11
17
|
def skip_tx
|
12
18
|
self._skip_tx = true
|
13
19
|
end
|
14
20
|
|
21
|
+
# Checks if this job class should skip database transactions.
|
22
|
+
#
|
23
|
+
# @return [Boolean] True if transactions should be skipped
|
15
24
|
def skip_tx?
|
16
25
|
_skip_tx
|
17
26
|
end
|
data/lib/workhorse/daemon.rb
CHANGED
@@ -1,11 +1,28 @@
|
|
1
1
|
module Workhorse
|
2
|
+
# Daemon class for managing multiple worker processes.
|
3
|
+
# Provides functionality to start, stop, restart, and monitor worker processes
|
4
|
+
# through a simple Ruby DSL.
|
2
5
|
class Daemon
|
6
|
+
# Internal representation of a worker process.
|
7
|
+
# Stores worker metadata and the block to execute.
|
3
8
|
class Worker
|
9
|
+
# @return [Integer] The worker's unique ID
|
4
10
|
attr_reader :id
|
11
|
+
|
12
|
+
# @return [String] The worker's display name
|
5
13
|
attr_reader :name
|
14
|
+
|
15
|
+
# @return [Proc] The block containing the worker's logic
|
6
16
|
attr_reader :block
|
17
|
+
|
18
|
+
# @return [Integer, nil] The worker's process ID when running
|
7
19
|
attr_accessor :pid
|
8
20
|
|
21
|
+
# Creates a new worker definition.
|
22
|
+
#
|
23
|
+
# @param id [Integer] Unique identifier for this worker
|
24
|
+
# @param name [String] Display name for this worker
|
25
|
+
# @param block [Proc] Code block to execute in the worker process
|
9
26
|
def initialize(id, name, &block)
|
10
27
|
@id = id
|
11
28
|
@name = name
|
@@ -13,9 +30,16 @@ module Workhorse
|
|
13
30
|
end
|
14
31
|
end
|
15
32
|
|
33
|
+
# @return [Array<Worker>] Array of defined workers
|
16
34
|
# @private
|
17
35
|
attr_reader :workers
|
18
36
|
|
37
|
+
# Creates a new daemon instance.
|
38
|
+
#
|
39
|
+
# @param pidfile [String, nil] Path template for PID files (use %i placeholder for worker ID)
|
40
|
+
# @param quiet [Boolean] Whether to suppress output during operations
|
41
|
+
# @yield [ScopedEnv] Configuration block for defining workers
|
42
|
+
# @raise [RuntimeError] If no workers are defined or pidfile format is invalid
|
19
43
|
def initialize(pidfile: nil, quiet: false, &_block)
|
20
44
|
@pidfile = pidfile
|
21
45
|
@quiet = quiet
|
@@ -38,10 +62,19 @@ module Workhorse
|
|
38
62
|
end
|
39
63
|
end
|
40
64
|
|
65
|
+
# Defines a worker process.
|
66
|
+
#
|
67
|
+
# @param name [String] Display name for the worker
|
68
|
+
# @yield Block containing the worker's execution logic
|
69
|
+
# @return [void]
|
41
70
|
def worker(name = 'Job Worker', &block)
|
42
71
|
@workers << Worker.new(@workers.size + 1, name, &block)
|
43
72
|
end
|
44
73
|
|
74
|
+
# Starts all defined workers.
|
75
|
+
#
|
76
|
+
# @param quiet [Boolean] Whether to suppress status output
|
77
|
+
# @return [Integer] Exit code (0 = success, 2 = some workers already running)
|
45
78
|
def start(quiet: false)
|
46
79
|
code = 0
|
47
80
|
|
@@ -81,6 +114,11 @@ module Workhorse
|
|
81
114
|
return code
|
82
115
|
end
|
83
116
|
|
117
|
+
# Stops all running workers.
|
118
|
+
#
|
119
|
+
# @param kill [Boolean] Whether to use KILL signal instead of TERM/INT
|
120
|
+
# @param quiet [Boolean] Whether to suppress status output
|
121
|
+
# @return [Integer] Exit code (0 = success, 2 = some workers already stopped)
|
84
122
|
def stop(kill = false, quiet: false)
|
85
123
|
code = 0
|
86
124
|
|
@@ -102,6 +140,10 @@ module Workhorse
|
|
102
140
|
return code
|
103
141
|
end
|
104
142
|
|
143
|
+
# Checks the status of all workers.
|
144
|
+
#
|
145
|
+
# @param quiet [Boolean] Whether to suppress status output
|
146
|
+
# @return [Integer] Exit code (0 = all running, 2 = some not running)
|
105
147
|
def status(quiet: false)
|
106
148
|
code = 0
|
107
149
|
|
@@ -122,6 +164,10 @@ module Workhorse
|
|
122
164
|
return code
|
123
165
|
end
|
124
166
|
|
167
|
+
# Watches workers and starts them if they're not running.
|
168
|
+
# In Rails environments, respects the tmp/stop.txt file.
|
169
|
+
#
|
170
|
+
# @return [Integer] Exit code from start operation or 0 if no action needed
|
125
171
|
def watch
|
126
172
|
if defined?(Rails)
|
127
173
|
should_be_running = !File.exist?(Rails.root.join('tmp/stop.txt'))
|
@@ -136,11 +182,18 @@ module Workhorse
|
|
136
182
|
end
|
137
183
|
end
|
138
184
|
|
185
|
+
# Restarts all workers by stopping and then starting them.
|
186
|
+
#
|
187
|
+
# @return [Integer] Exit code from start operation
|
139
188
|
def restart
|
140
189
|
stop
|
141
190
|
return start
|
142
191
|
end
|
143
192
|
|
193
|
+
# Sends HUP signal to all workers to restart their logging.
|
194
|
+
# Useful for log rotation without full process restart.
|
195
|
+
#
|
196
|
+
# @return [Integer] Exit code (0 = success, 2 = some signals failed)
|
144
197
|
def restart_logging
|
145
198
|
code = 0
|
146
199
|
|
@@ -163,10 +216,20 @@ module Workhorse
|
|
163
216
|
|
164
217
|
private
|
165
218
|
|
219
|
+
# Executes the given block for each defined worker.
|
220
|
+
#
|
221
|
+
# @yield [Worker] Each worker instance
|
222
|
+
# @return [void]
|
223
|
+
# @private
|
166
224
|
def for_each_worker(&block)
|
167
225
|
@workers.each(&block)
|
168
226
|
end
|
169
227
|
|
228
|
+
# Starts a single worker process.
|
229
|
+
#
|
230
|
+
# @param worker [Worker] The worker to start
|
231
|
+
# @return [void]
|
232
|
+
# @private
|
170
233
|
def start_worker(worker)
|
171
234
|
check_rails_env if defined?(Rails)
|
172
235
|
|
@@ -185,6 +248,13 @@ module Workhorse
|
|
185
248
|
Process.detach(pid)
|
186
249
|
end
|
187
250
|
|
251
|
+
# Stops a single worker process.
|
252
|
+
#
|
253
|
+
# @param pid_file [String] Path to the worker's PID file
|
254
|
+
# @param pid [Integer] The worker's process ID
|
255
|
+
# @param kill [Boolean] Whether to use KILL signal
|
256
|
+
# @return [void]
|
257
|
+
# @private
|
188
258
|
def stop_worker(pid_file, pid, kill: false)
|
189
259
|
signals = kill ? %w[KILL] : %w[TERM INT]
|
190
260
|
|
@@ -201,10 +271,20 @@ module Workhorse
|
|
201
271
|
File.delete(pid_file)
|
202
272
|
end
|
203
273
|
|
274
|
+
# Sends HUP signal to a worker process.
|
275
|
+
#
|
276
|
+
# @param pid [Integer] The worker's process ID
|
277
|
+
# @return [void]
|
278
|
+
# @private
|
204
279
|
def hup_worker(pid)
|
205
280
|
Process.kill('HUP', pid)
|
206
281
|
end
|
207
282
|
|
283
|
+
# Generates a process name for a worker.
|
284
|
+
#
|
285
|
+
# @param worker [Worker] The worker instance
|
286
|
+
# @return [String] Process name for ps output
|
287
|
+
# @private
|
208
288
|
def process_name(worker)
|
209
289
|
if defined?(Rails)
|
210
290
|
path = Rails.root
|
@@ -215,6 +295,11 @@ module Workhorse
|
|
215
295
|
return "Workhorse #{worker.name} ##{worker.id}: #{path}"
|
216
296
|
end
|
217
297
|
|
298
|
+
# Checks if a process with the given PID is running.
|
299
|
+
#
|
300
|
+
# @param pid [Integer] Process ID to check
|
301
|
+
# @return [Boolean] True if process is running
|
302
|
+
# @private
|
218
303
|
def process?(pid)
|
219
304
|
return begin
|
220
305
|
Process.kill(0, pid)
|
@@ -224,10 +309,20 @@ module Workhorse
|
|
224
309
|
end
|
225
310
|
end
|
226
311
|
|
312
|
+
# Returns the PID file path for a worker.
|
313
|
+
#
|
314
|
+
# @param worker [Worker] The worker instance
|
315
|
+
# @return [String] Path to the PID file
|
316
|
+
# @private
|
227
317
|
def pid_file_for(worker)
|
228
318
|
@pidfile % worker.id
|
229
319
|
end
|
230
320
|
|
321
|
+
# Reads PID information for a worker.
|
322
|
+
#
|
323
|
+
# @param worker [Worker] The worker instance
|
324
|
+
# @return [Array<String, Integer, Boolean>] PID file path, PID, and active status
|
325
|
+
# @private
|
231
326
|
def read_pid(worker)
|
232
327
|
file = pid_file_for(worker)
|
233
328
|
pid = nil
|
@@ -247,6 +342,10 @@ module Workhorse
|
|
247
342
|
return file, pid, active
|
248
343
|
end
|
249
344
|
|
345
|
+
# Warns if not running in production environment.
|
346
|
+
#
|
347
|
+
# @return [void]
|
348
|
+
# @private
|
250
349
|
def check_rails_env
|
251
350
|
unless Rails.env.production?
|
252
351
|
warn 'WARNING: Always run workhorse workers in production environment. Other environments can lead to unexpected behavior.'
|