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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0facaea70a5e826980ea41034914a0609a22fb3ed93c9fbe4a7607eb5a3854c9
4
- data.tar.gz: 35cc28b0a31206fa5f38df60858c5c2054c28fd7c47a4bf0b4983c9cbbcc02d0
3
+ metadata.gz: 02c3e3163a1b21b8a983c756e9be5e916b2cc7017401ab3484905c04fe56293f
4
+ data.tar.gz: 52ffb9742bf24ea226e3aa3f7edf2f0a5deecdefa57310c3176a40bec00f227f
5
5
  SHA512:
6
- metadata.gz: 551453702ecb4b89060a7e5fdba4639eccbb60c09c85ebd2fd561eec91787c1b8166be81e81b2bd2da46f250ba70c8426198bca404d1cf1e5fbf1c6c9b5096ee
7
- data.tar.gz: e1ee8b9ae1267271f8be2befd5455d90614e02c8708b2413bc90f957b8d5fb852b2f805ea4d42b6362844c2e7ed774e2a0a727e83687d570d0c430174e264227
6
+ metadata.gz: ce5cd0d30660a0bb9829c84957e76589d5e65e9c47795c7ff414101df2b57f8661fbf01cd4b73bb4a69b7901f4fc43796a3a43e6f400c31b5c1a5b6b1eff8e56
7
+ data.tar.gz: 3594464e0fd00d036c9fe163daeea5a17d78d2fcd79095be05dac95753fbce43530812881529052af28917240920f70f706b708f7902d38636caa4e5c6995d3a
data/.releaser_config CHANGED
@@ -1,3 +1,3 @@
1
1
  version_file: VERSION
2
- always_from_master: true
2
+ always_from_master: false
3
3
  gem_style: github
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.2
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
@@ -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
- if should_be_running && status(quiet: true) != 0
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
@@ -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.
@@ -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}] HUP received, logger state before reopen: #{describe_logger(logger)}")
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}] HUP handling complete")
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 ruby lib
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.2"
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-02-20"
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.2
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-02-20 00:00:00.000000000 Z
10
+ date: 2026-04-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport