workhorse 1.4.2 → 1.4.4
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/.releaser_config +1 -1
- data/CHANGELOG.md +11 -1
- data/VERSION +1 -1
- data/lib/workhorse/daemon/shell_handler.rb +2 -0
- data/lib/workhorse/daemon.rb +22 -1
- data/lib/workhorse/poller.rb +8 -0
- data/lib/workhorse/worker.rb +6 -3
- data/workhorse.gemspec +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 02c3e3163a1b21b8a983c756e9be5e916b2cc7017401ab3484905c04fe56293f
|
|
4
|
+
data.tar.gz: 52ffb9742bf24ea226e3aa3f7edf2f0a5deecdefa57310c3176a40bec00f227f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce5cd0d30660a0bb9829c84957e76589d5e65e9c47795c7ff414101df2b57f8661fbf01cd4b73bb4a69b7901f4fc43796a3a43e6f400c31b5c1a5b6b1eff8e56
|
|
7
|
+
data.tar.gz: 3594464e0fd00d036c9fe163daeea5a17d78d2fcd79095be05dac95753fbce43530812881529052af28917240920f70f706b708f7902d38636caa4e5c6995d3a
|
data/.releaser_config
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Workhorse Changelog
|
|
2
2
|
|
|
3
|
+
## 1.4.4 - 2026-04-28
|
|
4
|
+
|
|
5
|
+
* Make debug logging (enabled if `config.debug_log_path` is set) more verbose.
|
|
6
|
+
|
|
7
|
+
Sitrox reference: #120574.
|
|
8
|
+
|
|
9
|
+
## 1.4.3 - 2026-04-28
|
|
10
|
+
|
|
11
|
+
* Yanked from RubyGems. Do not use this release.
|
|
12
|
+
|
|
3
13
|
## 1.4.2 - 2026-02-20
|
|
4
14
|
|
|
5
15
|
* Detach forked worker processes into their own session using `Process.setsid`.
|
|
@@ -62,7 +72,7 @@
|
|
|
62
72
|
|
|
63
73
|
## 1.3.0.rc4 - 2025-08-27
|
|
64
74
|
|
|
65
|
-
* Fix race-condition in polling mechanism which could result in workers
|
|
75
|
+
* Fix race-condition in polling mechanism which could result in workers
|
|
66
76
|
trying to run a job that is not yet locked.
|
|
67
77
|
|
|
68
78
|
Sitrox reference: #128333.
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.4.
|
|
1
|
+
1.4.4
|
|
@@ -81,6 +81,7 @@ module Workhorse
|
|
|
81
81
|
status = 99
|
|
82
82
|
end
|
|
83
83
|
rescue StandardError => e
|
|
84
|
+
Workhorse.debug_log("ShellHandler: #{ARGV.first} failed with #{e.class}: #{e.message}")
|
|
84
85
|
warn "#{e.message}\n#{e.backtrace.join("\n")}"
|
|
85
86
|
status = 99
|
|
86
87
|
ensure
|
|
@@ -88,6 +89,7 @@ module Workhorse
|
|
|
88
89
|
Workhorse.debug_log("ShellHandler: releasing lock for #{ARGV.first}")
|
|
89
90
|
lockfile.flock(File::LOCK_UN)
|
|
90
91
|
end
|
|
92
|
+
Workhorse.debug_log("ShellHandler: exiting with status #{status}")
|
|
91
93
|
exit! status
|
|
92
94
|
end
|
|
93
95
|
end
|
data/lib/workhorse/daemon.rb
CHANGED
|
@@ -82,6 +82,8 @@ module Workhorse
|
|
|
82
82
|
def start(quiet: false)
|
|
83
83
|
code = 0
|
|
84
84
|
|
|
85
|
+
Workhorse.debug_log("Daemon: starting #{@workers.count} worker(s)")
|
|
86
|
+
|
|
85
87
|
# Holds messages in format [[<message>, <severity>]]
|
|
86
88
|
messages = []
|
|
87
89
|
|
|
@@ -89,9 +91,11 @@ module Workhorse
|
|
|
89
91
|
pid_file, pid, active = read_pid(worker)
|
|
90
92
|
|
|
91
93
|
if pid_file && pid && active
|
|
94
|
+
Workhorse.debug_log("Daemon start: worker ##{worker.id} (#{worker.name}) already running (PID #{pid})")
|
|
92
95
|
messages << ["Worker ##{worker.id} (#{worker.name}): Already started (PID #{pid})", 2] unless quiet
|
|
93
96
|
code = 2
|
|
94
97
|
elsif pid_file
|
|
98
|
+
Workhorse.debug_log("Daemon start: worker ##{worker.id} (#{worker.name}) has stale pid file (PID #{pid.inspect}), starting")
|
|
95
99
|
File.delete pid_file
|
|
96
100
|
|
|
97
101
|
shutdown_file = pid ? Workhorse::Worker.shutdown_file_for(pid) : nil
|
|
@@ -101,6 +105,7 @@ module Workhorse
|
|
|
101
105
|
start_worker worker
|
|
102
106
|
FileUtils.rm(shutdown_file) if shutdown_file
|
|
103
107
|
else
|
|
108
|
+
Workhorse.debug_log("Daemon start: worker ##{worker.id} (#{worker.name}) not running, starting")
|
|
104
109
|
messages << ["Worker ##{worker.id} (#{worker.name}): Starting", 1] unless quiet
|
|
105
110
|
start_worker worker
|
|
106
111
|
end
|
|
@@ -115,6 +120,7 @@ module Workhorse
|
|
|
115
120
|
end
|
|
116
121
|
end
|
|
117
122
|
|
|
123
|
+
Workhorse.debug_log("Daemon: start complete, exit code=#{code}")
|
|
118
124
|
return code
|
|
119
125
|
end
|
|
120
126
|
|
|
@@ -126,21 +132,27 @@ module Workhorse
|
|
|
126
132
|
def stop(kill = false, quiet: false)
|
|
127
133
|
code = 0
|
|
128
134
|
|
|
135
|
+
Workhorse.debug_log("Daemon: stopping #{@workers.count} worker(s) (kill=#{kill})")
|
|
136
|
+
|
|
129
137
|
for_each_worker do |worker|
|
|
130
138
|
pid_file, pid, active = read_pid(worker)
|
|
131
139
|
|
|
132
140
|
if pid_file && pid && active
|
|
141
|
+
Workhorse.debug_log("Daemon stop: worker ##{worker.id} (#{worker.name}) running (PID #{pid}), stopping")
|
|
133
142
|
puts "Worker (#{worker.name}) ##{worker.id}: Stopping" unless quiet
|
|
134
143
|
stop_worker pid_file, pid, kill: kill
|
|
135
144
|
elsif pid_file
|
|
145
|
+
Workhorse.debug_log("Daemon stop: worker ##{worker.id} (#{worker.name}) stale pid file (PID #{pid.inspect})")
|
|
136
146
|
File.delete pid_file
|
|
137
147
|
puts "Worker (#{worker.name}) ##{worker.id}: Already stopped (stale PID file)" unless quiet
|
|
138
148
|
else
|
|
149
|
+
Workhorse.debug_log("Daemon stop: worker ##{worker.id} (#{worker.name}) already stopped")
|
|
139
150
|
warn "Worker (#{worker.name}) ##{worker.id}: Already stopped" unless quiet
|
|
140
151
|
code = 2
|
|
141
152
|
end
|
|
142
153
|
end
|
|
143
154
|
|
|
155
|
+
Workhorse.debug_log("Daemon: stop complete, exit code=#{code}")
|
|
144
156
|
return code
|
|
145
157
|
end
|
|
146
158
|
|
|
@@ -155,16 +167,20 @@ module Workhorse
|
|
|
155
167
|
pid_file, pid, active = read_pid(worker)
|
|
156
168
|
|
|
157
169
|
if pid_file && pid && active
|
|
170
|
+
Workhorse.debug_log("Daemon status: worker ##{worker.id} (#{worker.name}) running (PID #{pid})")
|
|
158
171
|
puts "Worker ##{worker.id} (#{worker.name}): Running" unless quiet
|
|
159
172
|
elsif pid_file
|
|
173
|
+
Workhorse.debug_log("Daemon status: worker ##{worker.id} (#{worker.name}) not running (stale PID file, PID #{pid.inspect})")
|
|
160
174
|
warn "Worker ##{worker.id} (#{worker.name}): Not running (stale PID file)" unless quiet
|
|
161
175
|
code = 2
|
|
162
176
|
else
|
|
177
|
+
Workhorse.debug_log("Daemon status: worker ##{worker.id} (#{worker.name}) not running (no pid file)")
|
|
163
178
|
warn "Worker ##{worker.id} (#{worker.name}): Not running" unless quiet
|
|
164
179
|
code = 2
|
|
165
180
|
end
|
|
166
181
|
end
|
|
167
182
|
|
|
183
|
+
Workhorse.debug_log("Daemon: status complete, exit code=#{code}")
|
|
168
184
|
return code
|
|
169
185
|
end
|
|
170
186
|
|
|
@@ -179,9 +195,14 @@ module Workhorse
|
|
|
179
195
|
should_be_running = true
|
|
180
196
|
end
|
|
181
197
|
|
|
182
|
-
|
|
198
|
+
status_code = status(quiet: true)
|
|
199
|
+
Workhorse.debug_log("Daemon watch: should_be_running=#{should_be_running}, status_code=#{status_code}")
|
|
200
|
+
|
|
201
|
+
if should_be_running && status_code != 0
|
|
202
|
+
Workhorse.debug_log('Daemon watch: starting workers')
|
|
183
203
|
return start(quiet: Workhorse.silence_watcher)
|
|
184
204
|
else
|
|
205
|
+
Workhorse.debug_log('Daemon watch: no action needed')
|
|
185
206
|
return 0
|
|
186
207
|
end
|
|
187
208
|
end
|
data/lib/workhorse/poller.rb
CHANGED
|
@@ -49,14 +49,18 @@ module Workhorse
|
|
|
49
49
|
fail 'Poller is already running.' if running?
|
|
50
50
|
@running = true
|
|
51
51
|
|
|
52
|
+
Workhorse.debug_log("[Job worker #{worker.id}] Poller starting")
|
|
53
|
+
|
|
52
54
|
clean_stuck_jobs! if Workhorse.clean_stuck_jobs
|
|
53
55
|
|
|
54
56
|
@thread = Thread.new do
|
|
57
|
+
Workhorse.debug_log("[Job worker #{worker.id}] Poller thread started")
|
|
55
58
|
loop do
|
|
56
59
|
break unless running?
|
|
57
60
|
|
|
58
61
|
begin
|
|
59
62
|
unless @before_poll.call
|
|
63
|
+
Workhorse.debug_log("[Job worker #{worker.id}] before_poll returned false, triggering worker shutdown")
|
|
60
64
|
Thread.new { worker.shutdown }
|
|
61
65
|
sleep
|
|
62
66
|
next
|
|
@@ -65,6 +69,7 @@ module Workhorse
|
|
|
65
69
|
poll
|
|
66
70
|
sleep
|
|
67
71
|
rescue Exception => e
|
|
72
|
+
Workhorse.debug_log("[Job worker #{worker.id}] Poller exception, shutting down: #{e.class}: #{e.message}")
|
|
68
73
|
worker.log %(Poll encountered exception:\n#{e.message}\n#{e.backtrace.join("\n")})
|
|
69
74
|
worker.log 'Worker shutting down...'
|
|
70
75
|
Workhorse.on_exception.call(e) unless Workhorse.silence_poller_exceptions
|
|
@@ -73,6 +78,7 @@ module Workhorse
|
|
|
73
78
|
break
|
|
74
79
|
end
|
|
75
80
|
end
|
|
81
|
+
Workhorse.debug_log("[Job worker #{worker.id}] Poller thread exiting")
|
|
76
82
|
end
|
|
77
83
|
end
|
|
78
84
|
|
|
@@ -82,8 +88,10 @@ module Workhorse
|
|
|
82
88
|
# @raise [RuntimeError] If poller is not running
|
|
83
89
|
def shutdown
|
|
84
90
|
fail 'Poller is not running.' unless running?
|
|
91
|
+
Workhorse.debug_log("[Job worker #{worker.id}] Poller shutting down")
|
|
85
92
|
@running = false
|
|
86
93
|
wait
|
|
94
|
+
Workhorse.debug_log("[Job worker #{worker.id}] Poller shut down")
|
|
87
95
|
end
|
|
88
96
|
|
|
89
97
|
# Waits for the poller thread to complete.
|
data/lib/workhorse/worker.rb
CHANGED
|
@@ -302,18 +302,19 @@ module Workhorse
|
|
|
302
302
|
def trap_log_reopen
|
|
303
303
|
Signal.trap(LOG_REOPEN_SIGNAL) do
|
|
304
304
|
Thread.new do
|
|
305
|
-
Workhorse.debug_log("[Job worker #{id}]
|
|
305
|
+
Workhorse.debug_log("[Job worker #{id}] #{LOG_REOPEN_SIGNAL} received, logger state before reopen: #{describe_logger(logger)}")
|
|
306
306
|
|
|
307
307
|
logger&.reopen
|
|
308
308
|
Workhorse.debug_log("[Job worker #{id}] Logger state after reopen: #{describe_logger(logger)}")
|
|
309
309
|
|
|
310
|
-
Workhorse.debug_log("[Job worker #{id}]
|
|
310
|
+
Workhorse.debug_log("[Job worker #{id}] #{LOG_REOPEN_SIGNAL} handling complete")
|
|
311
311
|
rescue Exception => e
|
|
312
|
-
Workhorse.debug_log("[Job worker #{id}] Logger reopen failed: #{e.class}: #{e.message}")
|
|
312
|
+
Workhorse.debug_log("[Job worker #{id}] Logger reopen failed: #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
313
313
|
log %(Log reopen signal handler error: #{e.message}\n#{e.backtrace.join("\n")}), :error
|
|
314
314
|
Workhorse.on_exception.call(e)
|
|
315
315
|
end.join
|
|
316
316
|
end
|
|
317
|
+
Workhorse.debug_log("[Job worker #{id}] Signal handler installed: #{LOG_REOPEN_SIGNAL}")
|
|
317
318
|
end
|
|
318
319
|
|
|
319
320
|
# Sets up signal handlers for graceful termination (TERM/INT signals).
|
|
@@ -334,6 +335,7 @@ module Workhorse
|
|
|
334
335
|
end.join
|
|
335
336
|
end
|
|
336
337
|
end
|
|
338
|
+
Workhorse.debug_log("[Job worker #{id}] Signal handlers installed: #{SHUTDOWN_SIGNALS.join(', ')}")
|
|
337
339
|
end
|
|
338
340
|
|
|
339
341
|
# Initiates a soft restart of the worker.
|
|
@@ -385,6 +387,7 @@ module Workhorse
|
|
|
385
387
|
# NOTE: Unlike trap_termination, we don't join here because soft_restart
|
|
386
388
|
# is designed to be fire-and-forget (it spawns its own monitoring thread).
|
|
387
389
|
end
|
|
390
|
+
Workhorse.debug_log("[Job worker #{id}] Signal handler installed: #{SOFT_RESTART_SIGNAL}")
|
|
388
391
|
end
|
|
389
392
|
|
|
390
393
|
# Returns a human-readable description of a logger's internal state.
|
data/workhorse.gemspec
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: workhorse 1.4.
|
|
2
|
+
# stub: workhorse 1.4.4 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "workhorse".freeze
|
|
6
|
-
s.version = "1.4.
|
|
6
|
+
s.version = "1.4.4"
|
|
7
7
|
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
9
9
|
s.require_paths = ["lib".freeze]
|
|
10
10
|
s.authors = ["Sitrox".freeze]
|
|
11
|
-
s.date = "2026-
|
|
11
|
+
s.date = "2026-04-28"
|
|
12
12
|
s.files = [".github/workflows/ruby.yml".freeze, ".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, "CHANGELOG.md".freeze, "FAQ.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "RUBY_VERSION".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/rubocop".freeze, "lib/active_job/queue_adapters/workhorse_adapter.rb".freeze, "lib/generators/workhorse/install_generator.rb".freeze, "lib/generators/workhorse/templates/bin/workhorse.rb".freeze, "lib/generators/workhorse/templates/config/initializers/workhorse.rb".freeze, "lib/generators/workhorse/templates/create_table_jobs.rb".freeze, "lib/workhorse.rb".freeze, "lib/workhorse/active_job_extension.rb".freeze, "lib/workhorse/daemon.rb".freeze, "lib/workhorse/daemon/shell_handler.rb".freeze, "lib/workhorse/db_job.rb".freeze, "lib/workhorse/enqueuer.rb".freeze, "lib/workhorse/jobs/cleanup_succeeded_jobs.rb".freeze, "lib/workhorse/jobs/detect_stale_jobs_job.rb".freeze, "lib/workhorse/jobs/run_active_job.rb".freeze, "lib/workhorse/jobs/run_rails_op.rb".freeze, "lib/workhorse/performer.rb".freeze, "lib/workhorse/poller.rb".freeze, "lib/workhorse/pool.rb".freeze, "lib/workhorse/scoped_env.rb".freeze, "lib/workhorse/worker.rb".freeze, "test/active_job/queue_adapters/workhorse_adapter_test.rb".freeze, "test/lib/db_schema.rb".freeze, "test/lib/jobs.rb".freeze, "test/lib/test_helper.rb".freeze, "test/workhorse/daemon_test.rb".freeze, "test/workhorse/db_job_test.rb".freeze, "test/workhorse/enqueuer_test.rb".freeze, "test/workhorse/performer_test.rb".freeze, "test/workhorse/poller_test.rb".freeze, "test/workhorse/pool_test.rb".freeze, "test/workhorse/worker_test.rb".freeze, "workhorse.gemspec".freeze]
|
|
13
13
|
s.homepage = "https://github.com/sitrox/workhorse".freeze
|
|
14
14
|
s.licenses = ["MIT".freeze]
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: workhorse
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sitrox
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-04-28 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|