workhorse 1.4.2 → 1.4.5

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: ef9cd623d06cab42a5c5a849f9a5c334c0bb8f4dd517f2c84e5ba3c4df611b24
4
+ data.tar.gz: 9681900417bc660a347afeeaebf6cf57fa342268afc38c24b4e5ce11479ce1e3
5
5
  SHA512:
6
- metadata.gz: 551453702ecb4b89060a7e5fdba4639eccbb60c09c85ebd2fd561eec91787c1b8166be81e81b2bd2da46f250ba70c8426198bca404d1cf1e5fbf1c6c9b5096ee
7
- data.tar.gz: e1ee8b9ae1267271f8be2befd5455d90614e02c8708b2413bc90f957b8d5fb852b2f805ea4d42b6362844c2e7ed774e2a0a727e83687d570d0c430174e264227
6
+ metadata.gz: 86a5b9f9db82efbd890dc77fa763dcc1052027df45d922358428e721bfe3e900670e28b1e55af06e04972fef7c6408fe8a445239443f0707063281d4baf344fe
7
+ data.tar.gz: 74f47b979b3589b5530947638c7ec7a6acbb9f85599c88bd9eccaf25fcecf5a92addecb58fa9357bc07eb840bf16b31e5a7fe7fe2f9a6e11fa697b31d9aaf526
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Workhorse Changelog
2
2
 
3
+ ## 1.4.5 - 2026-05-09
4
+
5
+ * Close the lockfile after releasing the lock in the ShellHandler.
6
+
7
+ Sitrox reference: #120574.
8
+
9
+ * Prevent YJIT from being enabled when `RUBY_YJIT_ENABLE=0` is set. This allows
10
+ running workhorse commands in systemd services that set
11
+ `MemoryDenyWriteExecute=yes`.
12
+
13
+ Sitrox reference: #120574.
14
+
15
+ ## 1.4.4 - 2026-04-28
16
+
17
+ * Make debug logging (enabled if `config.debug_log_path` is set) more verbose.
18
+
19
+ Sitrox reference: #120574.
20
+
21
+ ## 1.4.3 - 2026-04-28
22
+
23
+ * Yanked from RubyGems. Do not use this release.
24
+
3
25
  ## 1.4.2 - 2026-02-20
4
26
 
5
27
  * Detach forked worker processes into their own session using `Process.setsid`.
@@ -62,7 +84,7 @@
62
84
 
63
85
  ## 1.3.0.rc4 - 2025-08-27
64
86
 
65
- * Fix race-condition in polling mechanism which could result in workers
87
+ * Fix race-condition in polling mechanism which could result in workers
66
88
  trying to run a job that is not yet locked.
67
89
 
68
90
  Sitrox reference: #128333.
data/README.md CHANGED
@@ -529,6 +529,17 @@ ActiveSupport.on_load :workhorse_db_job do
529
529
  end
530
530
  ```
531
531
 
532
+ ## Running in systemd services
533
+
534
+ When running workhorse commands inside a systemd service that sets
535
+ `MemoryDenyWriteExecute=yes`, Ruby's YJIT cannot allocate executable memory and
536
+ will crash. To prevent this, set `RUBY_YJIT_ENABLE=0` before invoking the
537
+ workhorse command:
538
+
539
+ ```bash
540
+ RUBY_YJIT_ENABLE=0 ./bin/workhorse restart-logging
541
+ ```
542
+
532
543
  ## Debug logging
533
544
 
534
545
  Workhorse includes an optional debug log for diagnosing issues with signal
data/RUBY_VERSION CHANGED
@@ -1 +1 @@
1
- ruby-3.2.1-p31
1
+ ruby-3.3.5
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.2
1
+ 1.4.5
@@ -81,13 +81,16 @@ 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
87
88
  if lockfile
88
89
  Workhorse.debug_log("ShellHandler: releasing lock for #{ARGV.first}")
89
90
  lockfile.flock(File::LOCK_UN)
91
+ lockfile.close
90
92
  end
93
+ Workhorse.debug_log("ShellHandler: exiting with status #{status}")
91
94
  exit! status
92
95
  end
93
96
  end
@@ -143,7 +146,7 @@ module Workhorse
143
146
 
144
147
  def self.acquire_lock(lockfile_path, flags)
145
148
  if Workhorse.lock_shell_commands
146
- lockfile = File.open(lockfile_path, 'a')
149
+ lockfile = File.open(lockfile_path, 'a') # rubocop:disable Style/FileOpen
147
150
  result = lockfile.flock(flags)
148
151
 
149
152
  if result == false
@@ -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
@@ -289,7 +310,7 @@ module Workhorse
289
310
  @lockfile&.close
290
311
  # Reopen pipes to prevent #107576
291
312
  $stdin.reopen File.open(File::NULL, 'r')
292
- null_out = File.open File::NULL, 'w'
313
+ null_out = File.open(File::NULL, 'w') # rubocop:disable Style/FileOpen
293
314
  $stdout.reopen null_out
294
315
  $stderr.reopen null_out
295
316
 
@@ -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/lib/workhorse.rb CHANGED
@@ -8,6 +8,16 @@ require 'workhorse/enqueuer'
8
8
  require 'workhorse/scoped_env'
9
9
  require 'workhorse/active_job_extension'
10
10
 
11
+ # Prevent YJIT from being enabled when RUBY_YJIT_ENABLE is explicitly set to 0.
12
+ # systemd's logrotate.service typically sets MemoryDenyWriteExecute=yes, which
13
+ # prevents mprotect(PROT_EXEC). Rails unconditionally calls
14
+ # RubyVM::YJIT.enable on boot, which triggers a fatal Ruby [BUG] in that
15
+ # environment. Set RUBY_YJIT_ENABLE=0 in the logrotate postrotate script
16
+ # to prevent this.
17
+ if ENV['RUBY_YJIT_ENABLE'] == '0' && defined?(RubyVM::YJIT) && !RubyVM::YJIT.enabled?
18
+ RubyVM::YJIT.define_singleton_method(:enable) { |**| nil }
19
+ end
20
+
11
21
  # Main Gem module.
12
22
  module Workhorse
13
23
  # Check if the available Arel version is greater or equal than 7.0.0
@@ -0,0 +1,37 @@
1
+ require 'English'
2
+ require 'test_helper'
3
+ require 'bundler'
4
+
5
+ class Workhorse::YjitTest < ActiveSupport::TestCase
6
+ YJIT_CHECK_SCRIPT = <<~RUBY.freeze
7
+ require 'bundler/setup'
8
+ require 'workhorse'
9
+ RubyVM::YJIT.enable
10
+ print RubyVM::YJIT.enabled?
11
+ RUBY
12
+
13
+ def test_yjit_not_enabled_when_ruby_yjit_enable_is_zero
14
+ skip 'RubyVM::YJIT.enable not available' unless defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enable)
15
+
16
+ # Sanity check: YJIT can actually be enabled in this environment
17
+ without_env = run_ruby_script(YJIT_CHECK_SCRIPT, 'RUBY_YJIT_ENABLE' => nil)
18
+ skip 'YJIT cannot be enabled in this environment' unless without_env == 'true'
19
+
20
+ # With RUBY_YJIT_ENABLE=0, RubyVM::YJIT.enable should be a no-op
21
+ with_env = run_ruby_script(YJIT_CHECK_SCRIPT, 'RUBY_YJIT_ENABLE' => '0')
22
+ assert_equal 'false', with_env, 'YJIT should not be enabled when RUBY_YJIT_ENABLE=0'
23
+ end
24
+
25
+ private
26
+
27
+ def run_ruby_script(script, env = {})
28
+ output = nil
29
+ Bundler.with_unbundled_env do
30
+ IO.popen(env, %w[bundle exec ruby -e] + [script], chdir: Rails.root.to_s, err: %i[child out]) do |io|
31
+ output = io.read.strip
32
+ end
33
+ end
34
+ assert $CHILD_STATUS.success?, "Ruby subprocess failed (exit #{$CHILD_STATUS.exitstatus}): #{output}"
35
+ output
36
+ end
37
+ end
data/workhorse.gemspec CHANGED
@@ -1,24 +1,24 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: workhorse 1.4.2 ruby lib
2
+ # stub: workhorse 1.4.5 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.5".freeze
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"
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]
11
+ s.date = "2026-05-09"
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, "test/workhorse/yjit_test.rb".freeze, "workhorse.gemspec".freeze]
13
13
  s.homepage = "https://github.com/sitrox/workhorse".freeze
14
14
  s.licenses = ["MIT".freeze]
15
- s.rubygems_version = "3.4.6".freeze
15
+ s.rubygems_version = "3.5.18".freeze
16
16
  s.summary = "Multi-threaded job backend with database queuing for ruby.".freeze
17
- 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/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]
17
+ 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/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, "test/workhorse/yjit_test.rb".freeze]
18
18
 
19
19
  s.specification_version = 4
20
20
 
21
- s.add_runtime_dependency(%q<activesupport>.freeze, [">= 7.0.0"])
22
- s.add_runtime_dependency(%q<activerecord>.freeze, [">= 7.0.0"])
23
- s.add_runtime_dependency(%q<concurrent-ruby>.freeze, [">= 0"])
21
+ s.add_runtime_dependency(%q<activesupport>.freeze, [">= 7.0.0".freeze])
22
+ s.add_runtime_dependency(%q<activerecord>.freeze, [">= 7.0.0".freeze])
23
+ s.add_runtime_dependency(%q<concurrent-ruby>.freeze, [">= 0".freeze])
24
24
  end
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.5
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-05-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -99,6 +99,7 @@ files:
99
99
  - test/workhorse/poller_test.rb
100
100
  - test/workhorse/pool_test.rb
101
101
  - test/workhorse/worker_test.rb
102
+ - test/workhorse/yjit_test.rb
102
103
  - workhorse.gemspec
103
104
  homepage: https://github.com/sitrox/workhorse
104
105
  licenses:
@@ -133,3 +134,4 @@ test_files:
133
134
  - test/workhorse/poller_test.rb
134
135
  - test/workhorse/pool_test.rb
135
136
  - test/workhorse/worker_test.rb
137
+ - test/workhorse/yjit_test.rb