workhorse 1.4.0 → 1.4.1

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: dd78ce0b033690529240d1111c8545ab78dfd753fbe7fee551313b2a7d33fc26
4
- data.tar.gz: 9e7e22992ed727f60882f448f0003318cd40142402590cf199812d461db58378
3
+ metadata.gz: df386a01e22eb4d5e05be449ef11a62341d28c3443fdef76b07ac7e8bca3e3d4
4
+ data.tar.gz: 0a4f325c0bf2cb08357a195a2297d0302df275abf3cd6d83329942d68419f7c8
5
5
  SHA512:
6
- metadata.gz: e5d6560a8a6ef01c555222568eb0af6ad44c09484cfeee05093c3ad966e19ebe80f0d6754b85dccab869bb98ef279aede2dbc39da5f7a38b2b1d3eef054136f4
7
- data.tar.gz: 2422ef7003b9dc3e3e3409a794afc57a4ae33226366292dadc89e4afe823289e2aa27c45f5c7b0586262344115d8ca1839b7e84d15980c5a69099c26f3c3c599
6
+ metadata.gz: 12b8ec75c276bf6d888e3f60b523a32cc387c6c4a935b20004fce4f63acbc4e7046979315eb7c6651953177d7289a3bf27fabc535da117a3d6a9c7be36f17ccd
7
+ data.tar.gz: 1d15f6cf25a9fe2878e9edf45a7b2ce69d0bf0904119efd2d6234e8c15dc7142ce81c427912f22f2459fa27967b58932497424646d5ea9af431d625a1e154a8a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Workhorse Changelog
2
2
 
3
+ ## 1.4.1 - 2026-02-18
4
+
5
+ * Close inherited lockfile fd in forked worker processes. Previously the
6
+ lockfile's file descriptor was inherited by children via `fork`, which could
7
+ prevent the POSIX `flock` from being released if the daemon process exited
8
+ abnormally.
9
+
10
+ * Fix `watch` and `kill` commands to actually abort when the lock is
11
+ unavailable. Previously the `flock` return value with `LOCK_NB` was not
12
+ checked, so the commands would silently proceed without the lock.
13
+
14
+ * Add error handling to the `HUP` signal handler for log reopening. Exceptions
15
+ from `logger.reopen` are now caught and reported via `on_exception`.
16
+
17
+ Sitrox reference: #120574.
18
+
3
19
  ## 1.4.0 - 2026-02-12
4
20
 
5
21
  * Stable release based on previous RC release.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.0
1
+ 1.4.1
@@ -1,5 +1,7 @@
1
1
  module Workhorse
2
2
  class Daemon::ShellHandler
3
+ class LockNotAvailableError < StandardError; end
4
+
3
5
  def self.run(**options, &block)
4
6
  unless ARGV.one?
5
7
  usage
@@ -15,27 +17,43 @@ module Workhorse
15
17
  case ARGV.first
16
18
  when 'start'
17
19
  lockfile = acquire_lock(lockfile_path, File::LOCK_EX)
20
+ daemon.lockfile = lockfile
18
21
  status = daemon.start
19
22
  when 'stop'
20
23
  lockfile = acquire_lock(lockfile_path, File::LOCK_EX)
24
+ daemon.lockfile = lockfile
21
25
  status = daemon.stop
22
26
  when 'kill'
23
- lockfile = acquire_lock(lockfile_path, File::LOCK_EX | File::LOCK_NB)
24
- status = daemon.stop(true)
27
+ begin
28
+ lockfile = acquire_lock(lockfile_path, File::LOCK_EX | File::LOCK_NB)
29
+ daemon.lockfile = lockfile
30
+ status = daemon.stop(true)
31
+ rescue LockNotAvailableError
32
+ status = 1
33
+ end
25
34
  when 'status'
26
35
  lockfile = acquire_lock(lockfile_path, File::LOCK_EX)
36
+ daemon.lockfile = lockfile
27
37
  status = daemon.status
28
38
  when 'watch'
29
- lockfile = acquire_lock(lockfile_path, File::LOCK_EX | File::LOCK_NB)
30
- status = daemon.watch
39
+ begin
40
+ lockfile = acquire_lock(lockfile_path, File::LOCK_EX | File::LOCK_NB)
41
+ daemon.lockfile = lockfile
42
+ status = daemon.watch
43
+ rescue LockNotAvailableError
44
+ status = 1
45
+ end
31
46
  when 'restart'
32
47
  lockfile = acquire_lock(lockfile_path, File::LOCK_EX)
48
+ daemon.lockfile = lockfile
33
49
  status = daemon.restart
34
50
  when 'restart-logging'
35
51
  lockfile = acquire_lock(lockfile_path, File::LOCK_EX)
52
+ daemon.lockfile = lockfile
36
53
  status = daemon.restart_logging
37
54
  when 'soft-restart'
38
55
  lockfile = acquire_lock(lockfile_path, File::LOCK_EX)
56
+ daemon.lockfile = lockfile
39
57
  status = daemon.soft_restart
40
58
  when 'usage'
41
59
  usage
@@ -105,7 +123,12 @@ module Workhorse
105
123
  def self.acquire_lock(lockfile_path, flags)
106
124
  if Workhorse.lock_shell_commands
107
125
  lockfile = File.open(lockfile_path, 'a')
108
- lockfile.flock(flags)
126
+ result = lockfile.flock(flags)
127
+
128
+ if result == false
129
+ lockfile.close
130
+ fail LockNotAvailableError, 'Could not acquire lock. Is another workhorse command already running?'
131
+ end
109
132
 
110
133
  return lockfile
111
134
  end
@@ -34,6 +34,10 @@ module Workhorse
34
34
  # @private
35
35
  attr_reader :workers
36
36
 
37
+ # @return [File, nil] Lockfile handle to close in forked children
38
+ # @private
39
+ attr_accessor :lockfile
40
+
37
41
  # Creates a new daemon instance.
38
42
  #
39
43
  # @param pidfile [String, nil] Path template for PID files (use %i placeholder for worker ID)
@@ -261,6 +265,8 @@ module Workhorse
261
265
 
262
266
  pid = fork do
263
267
  $0 = process_name(worker)
268
+ # Close inherited lockfile fd to prevent holding the flock after parent exits
269
+ @lockfile&.close
264
270
  # Reopen pipes to prevent #107576
265
271
  $stdin.reopen File.open(File::NULL, 'r')
266
272
  null_out = File.open File::NULL, 'w'
@@ -296,11 +296,14 @@ module Workhorse
296
296
  def trap_log_reopen
297
297
  Signal.trap(LOG_REOPEN_SIGNAL) do
298
298
  Thread.new do
299
- logger.reopen
299
+ logger&.reopen
300
300
 
301
301
  if defined?(ActiveRecord::Base) && ActiveRecord::Base.logger && ActiveRecord::Base.logger != logger
302
302
  ActiveRecord::Base.logger.reopen
303
303
  end
304
+ rescue Exception => e
305
+ log %(Log reopen signal handler error: #{e.message}\n#{e.backtrace.join("\n")}), :error
306
+ Workhorse.on_exception.call(e)
304
307
  end.join
305
308
  end
306
309
  end
data/workhorse.gemspec CHANGED
@@ -1,14 +1,14 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: workhorse 1.4.0 ruby lib
2
+ # stub: workhorse 1.4.1 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "workhorse".freeze
6
- s.version = "1.4.0"
6
+ s.version = "1.4.1"
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-12"
11
+ s.date = "2026-02-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/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.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sitrox
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-12 00:00:00.000000000 Z
10
+ date: 2026-02-18 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport