workhorse 1.2.16 → 1.2.17.rc1

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: 7c50e38fd05785d30e0dfa783d732f46df2be9b980ac0a54dca1a2d91264ff9d
4
- data.tar.gz: a4b282a25d39429da15c5380a41a1786b54b24b2b8afb4961786881380eecacf
3
+ metadata.gz: 2bcfdbfa710579ee64f436236ed5c4214404a82211ae7471513ba141e7e03eaf
4
+ data.tar.gz: 680b91c6bbdbc8473fdc454175bdf6a2f4b14a0831162ab02e92208914f69f7a
5
5
  SHA512:
6
- metadata.gz: f8fefeb28bb6651e93163e5d9b8744994f681064bf55168b73d8ca5e9c72aee9e3f7642d03a86eb496ebd81bf15e724757cb4f0a6790dcb87e3ae5f3ef09344d
7
- data.tar.gz: '090f112b83a2a69015e0244345d30616af8701bf99f2a6814238006efa2cca49850dfe024b26d1359b2863d01bf2c84d0bbf84a7122419f540969ba7f8d66dea'
6
+ metadata.gz: 29fe50d7eb989a0d51cdcbd23adb7d42ce56fccf553448c7baf112bae2dd678a81613d593637fe6f168ff3620d9b31048abcdd0f8674b4db2244a59a82865779
7
+ data.tar.gz: '094cb3bb85f2be4bd2575f6441e2abc6ef17d522796c586dca69f864031fde2b0095bc8ce2c3d8bee6507ab799297fd8e79c86fbbadd28467495a39cc7031694'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Workhorse Changelog
2
2
 
3
+ ## 1.2.17.rc1 - 2024-02-05
4
+
5
+ * Revamp memory handling:
6
+
7
+ * Change memory handling for workers to automatically shut down themselves
8
+ upon exceeding `config.max_worker_memory_mb` (if configured and > 0),
9
+ triggering the creation of a shutdown file
10
+ (`tmp/pids/workhorse.<pid>.shutdown`).
11
+ * Have the `watch` command, if scheduled, silently restart the shutdown worker
12
+ and remove the shutdown file.
13
+ * The presence of the shutdown file informs the watcher to produce output or
14
+ remain silent.
15
+ * Implement this adjustment to limit `watch` command output to error cases,
16
+ facilitating seamless cron integration for notification purposes.
17
+
18
+ Sitrox reference: #121312.
19
+
20
+ ## 1.2.17.rc0 - 2024-02-05
21
+
22
+ * Add option `config.max_worker_memory_mb` for automatic restart of workers
23
+ exceeding the specified memory threshold using the `watch` command. Default is
24
+ `0`, deactivating this feature. See [memory
25
+ handling](README.md#memory-handling) for more information.
26
+
27
+ Sitrox reference: #121312.
28
+
3
29
  ## 1.2.16 - 2023-09-18
4
30
 
5
31
  * Add support for `--skip-initializer` flag to install generator.
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 - 2023 Sitrox
3
+ Copyright (c) 2017 - 2024 Sitrox
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -464,6 +464,30 @@ jobs database on a regular interval. Workhorse provides the job
464
464
  `Workhorse::Jobs::CleanupSucceededJobs` for this purpose that cleans up all
465
465
  succeeded jobs. You can run this using your scheduler in a specific interval.
466
466
 
467
+ ## Memory handling
468
+
469
+ When a worker exceeds the memory limit specified by
470
+ `config.max_worker_memory_mb` (assuming it is configured and > 0), it initiates
471
+ a graceful shutdown process by creating a shutdown file named
472
+ `tmp/pids/workhorse.<pid>.shutdown`.
473
+
474
+ Simultaneously, the `watch` command, if scheduled, monitors the presence of this
475
+ shutdown file. Upon detecting its existence, it silently triggers the restart of
476
+ the shutdown worker and removes the shutdown file to signify that the restart
477
+ process has begun.
478
+
479
+ This mechanism ensures that workers are automatically restarted without manual
480
+ intervention when memory limits are exceeded.
481
+
482
+ Example configuration:
483
+
484
+ ```ruby
485
+ # config/initializers/workhorse.rb
486
+ Workhorse.setup do |config|
487
+ config.max_worker_memory_mb = 512 # Set the memory threshold to 512 megabytes
488
+ end
489
+ ```
490
+
467
491
  ## Load hooks
468
492
 
469
493
  Using the load hook `:workhorse_db_job`, you can inject custom code into the
@@ -540,4 +564,4 @@ Please consult the [FAQ](FAQ.md).
540
564
 
541
565
  ## Copyright
542
566
 
543
- Copyright © 2017 - 2023 Sitrox. See `LICENSE` for further details.
567
+ Copyright © 2017 - 2024 Sitrox. See `LICENSE` for further details.
data/RUBY_VERSION CHANGED
@@ -1 +1 @@
1
- ruby-2.7.1-p83
1
+ ruby-3.2.1-p31
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.16
1
+ 1.2.17.rc1
@@ -46,15 +46,20 @@ module Workhorse
46
46
  code = 0
47
47
 
48
48
  for_each_worker do |worker|
49
- pid_file, pid = read_pid(worker)
49
+ pid_file, pid, active = read_pid(worker)
50
50
 
51
- if pid_file && pid
51
+ if pid_file && pid && active
52
52
  warn "Worker ##{worker.id} (#{worker.name}): Already started (PID #{pid})" unless quiet
53
53
  code = 2
54
54
  elsif pid_file
55
55
  File.delete pid_file
56
- puts "Worker ##{worker.id} (#{worker.name}): Starting (stale pid file)" unless quiet
56
+
57
+ shutdown_file = pid ? Workhorse::Worker.shutdown_file_for(pid) : nil
58
+ shutdown_file = nil if shutdown_file && !File.exist?(shutdown_file)
59
+
60
+ puts "Worker ##{worker.id} (#{worker.name}): Starting (stale pid file)" unless quiet || shutdown_file
57
61
  start_worker worker
62
+ FileUtils.rm(shutdown_file) if shutdown_file
58
63
  else
59
64
  warn "Worker ##{worker.id} (#{worker.name}): Starting" unless quiet
60
65
  start_worker worker
@@ -68,9 +73,9 @@ module Workhorse
68
73
  code = 0
69
74
 
70
75
  for_each_worker do |worker|
71
- pid_file, pid = read_pid(worker)
76
+ pid_file, pid, active = read_pid(worker)
72
77
 
73
- if pid_file && pid
78
+ if pid_file && pid && active
74
79
  puts "Worker (#{worker.name}) ##{worker.id}: Stopping"
75
80
  stop_worker pid_file, pid, kill: kill
76
81
  elsif pid_file
@@ -89,9 +94,9 @@ module Workhorse
89
94
  code = 0
90
95
 
91
96
  for_each_worker do |worker|
92
- pid_file, pid = read_pid(worker)
97
+ pid_file, pid, active = read_pid(worker)
93
98
 
94
- if pid_file && pid
99
+ if pid_file && pid && active
95
100
  puts "Worker ##{worker.id} (#{worker.name}): Running" unless quiet
96
101
  elsif pid_file
97
102
  warn "Worker ##{worker.id} (#{worker.name}): Not running (stale PID file)" unless quiet
@@ -128,7 +133,9 @@ module Workhorse
128
133
  code = 0
129
134
 
130
135
  for_each_worker do |worker|
131
- _pid_file, pid = read_pid(worker)
136
+ _pid_file, pid, active = read_pid(worker)
137
+
138
+ next unless pid && active
132
139
 
133
140
  begin
134
141
  Process.kill 'HUP', pid
@@ -209,16 +216,21 @@ module Workhorse
209
216
 
210
217
  def read_pid(worker)
211
218
  file = pid_file_for(worker)
219
+ pid = nil
220
+ active = false
212
221
 
213
222
  if File.exist?(file)
214
223
  raw_pid = File.read(file)
215
- return nil, nil if raw_pid.blank?
216
224
 
217
- pid = Integer(raw_pid)
218
- return file, process?(pid) ? pid : nil
225
+ unless raw_pid.blank?
226
+ pid = Integer(raw_pid)
227
+ active = process?(pid)
228
+ end
219
229
  else
220
230
  return nil, nil
221
231
  end
232
+
233
+ return file, pid, active
222
234
  end
223
235
  end
224
236
  end
@@ -22,7 +22,7 @@ module Workhorse
22
22
  Thread.current[:workhorse_current_performer] = self
23
23
 
24
24
  ActiveRecord::Base.connection_pool.with_connection do
25
- if defined?(Rails) && Rails.application && Rails.application.respond_to?(:executor)
25
+ if defined?(Rails) && Rails.respond_to?(:application) && Rails.application && Rails.application.respond_to?(:executor)
26
26
  Rails.application.executor.wrap do
27
27
  perform_wrapped
28
28
  end
@@ -63,7 +63,6 @@ module Workhorse
63
63
 
64
64
  inner_job_class = deserialized_job.try(:job_class) || deserialized_job.class
65
65
  skip_tx = inner_job_class.try(:skip_tx?)
66
- log "SKIP TX: #{skip_tx.inspect}".red, :error
67
66
 
68
67
  if Workhorse.perform_jobs_in_tx && !skip_tx
69
68
  Workhorse.tx_callback.call do
@@ -9,7 +9,7 @@ module Workhorse
9
9
  attr_reader :worker
10
10
  attr_reader :table
11
11
 
12
- def initialize(worker)
12
+ def initialize(worker, before_poll = proc { true })
13
13
  @worker = worker
14
14
  @running = false
15
15
  @table = Workhorse::DbJob.arel_table
@@ -17,6 +17,7 @@ module Workhorse
17
17
  @instant_repoll = Concurrent::AtomicBoolean.new(false)
18
18
  @global_lock_fails = 0
19
19
  @max_global_lock_fails_reached = false
20
+ @before_poll = before_poll
20
21
  end
21
22
 
22
23
  def running?
@@ -34,6 +35,12 @@ module Workhorse
34
35
  break unless running?
35
36
 
36
37
  begin
38
+ unless @before_poll.call
39
+ Thread.new { worker.shutdown }
40
+ sleep
41
+ next
42
+ end
43
+
37
44
  poll
38
45
  sleep
39
46
  rescue Exception => e
@@ -2,6 +2,7 @@ module Workhorse
2
2
  # Abstraction layer of a simple thread pool implementation used by the worker.
3
3
  class Pool
4
4
  attr_reader :mutex
5
+ attr_reader :active_threads
5
6
 
6
7
  def initialize(size)
7
8
  @size = size
@@ -20,6 +20,12 @@ module Workhorse
20
20
  worker.wait
21
21
  end
22
22
 
23
+ # @private
24
+ def self.shutdown_file_for(pid)
25
+ return nil unless defined?(Rails)
26
+ Rails.root.join('tmp', 'pids', "workhorse.#{pid}.shutdown")
27
+ end
28
+
23
29
  # Instantiates a new worker. The worker is not automatically started.
24
30
  #
25
31
  # @param queues [Array] The queues you want this worker to process. If an
@@ -51,7 +57,7 @@ module Workhorse
51
57
 
52
58
  @mutex = Mutex.new
53
59
  @pool = Pool.new(@pool_size)
54
- @poller = Workhorse::Poller.new(self)
60
+ @poller = Workhorse::Poller.new(self, proc { check_memory })
55
61
  @logger = logger
56
62
 
57
63
  unless (@polling_interval / 0.1).round(2).modulo(1).zero?
@@ -155,6 +161,37 @@ module Workhorse
155
161
 
156
162
  private
157
163
 
164
+ def check_memory
165
+ mem = current_memory_consumption
166
+
167
+ unless mem
168
+ log "Could not determine memory consumption of worker with pid #{pid}"
169
+ return false
170
+ end
171
+
172
+ max = Workhorse.max_worker_memory_mb
173
+ exceeded = max > 0 && current_memory_consumption > max
174
+
175
+ return true unless exceeded
176
+
177
+ if defined?(Rails)
178
+ FileUtils.touch self.class.shutdown_file_for(pid)
179
+ end
180
+
181
+ log "Worker process #{id.inspect} memory consumption (RSS) of #{mem}MB exceeds "\
182
+ "configured per-worker limit of #{max}MB and is now being shut down. Make sure "\
183
+ 'that your worker processes are watched (e.g. using the "watch"-command) for ' \
184
+ 'this worker to be restarted automatically.'
185
+
186
+ return false
187
+ end
188
+
189
+ def current_memory_consumption
190
+ mem = `ps -p #{pid} -o rss=`&.strip
191
+ return nil if mem.blank?
192
+ return mem.to_i / 1024
193
+ end
194
+
158
195
  def check_rails_env
159
196
  unless Rails.env.production?
160
197
  warn 'WARNING: Always run workhorse workers in production environment. Other environments can lead to unexpected behavior.'
data/lib/workhorse.rb CHANGED
@@ -75,6 +75,12 @@ module Workhorse
75
75
  mattr_accessor :stale_detection_run_time_threshold
76
76
  self.stale_detection_run_time_threshold = 12 * 60
77
77
 
78
+ # Maximum memory for a worker in MB. If this memory limit (RSS / resident
79
+ # size) is reached for a worker process, the 'watch' command will restart said
80
+ # worker. Set this to 0 disable this feature.
81
+ mattr_accessor :max_worker_memory_mb
82
+ self.max_worker_memory_mb = 0
83
+
78
84
  def self.setup
79
85
  yield self
80
86
  end
data/test/lib/jobs.rb CHANGED
@@ -36,6 +36,15 @@ class SyntaxErrorJob
36
36
  end
37
37
  end
38
38
 
39
+ class MemHungryJob
40
+ class_attribute :data
41
+
42
+ # Should consume roughly 1GB of memory.
43
+ def perform
44
+ self.class.data = 'x' * 250.megabytes
45
+ end
46
+ end
47
+
39
48
  class DummyRailsOpsOp
40
49
  class_attribute :results
41
50
  self.results = Concurrent::Array.new
@@ -5,8 +5,33 @@ require 'pry'
5
5
  require 'colorize'
6
6
  require 'mysql2'
7
7
  require 'benchmark'
8
+ require 'concurrent'
8
9
  require 'jobs'
9
10
 
11
+ class MockRailsEnv < String
12
+ def production?
13
+ self == 'production'
14
+ end
15
+
16
+ def test?
17
+ self == 'test'
18
+ end
19
+
20
+ def development?
21
+ self == 'development'
22
+ end
23
+ end
24
+
25
+ class Rails
26
+ def self.root
27
+ Pathname.new(File.expand_path(File.join(File.dirname(__FILE__), '../../')))
28
+ end
29
+
30
+ def self.env
31
+ MockRailsEnv.new('production')
32
+ end
33
+ end
34
+
10
35
  class WorkhorseTest < ActiveSupport::TestCase
11
36
  def setup
12
37
  Workhorse::DbJob.delete_all
@@ -31,6 +56,14 @@ class WorkhorseTest < ActiveSupport::TestCase
31
56
  end
32
57
  end
33
58
 
59
+ def work_until(max: 50, interval: 0.1, **options, &block)
60
+ w = Workhorse::Worker.new(**options)
61
+ w.start
62
+ return with_retries(max, interval: interval, &block)
63
+ ensure
64
+ w.shutdown
65
+ end
66
+
34
67
  def with_worker(options = {})
35
68
  w = Workhorse::Worker.new(**options)
36
69
  w.start
@@ -40,6 +73,18 @@ class WorkhorseTest < ActiveSupport::TestCase
40
73
  w.shutdown
41
74
  end
42
75
  end
76
+
77
+ def with_retries(max = 50, interval: 0.1, &_block)
78
+ runs = 0
79
+
80
+ loop do
81
+ return yield
82
+ rescue Minitest::Assertion => e
83
+ fail if runs > max
84
+ sleep interval
85
+ runs += 1
86
+ end
87
+ end
43
88
  end
44
89
 
45
90
  ActiveRecord::Base.establish_connection(
@@ -180,8 +180,86 @@ class Workhorse::WorkerTest < WorkhorseTest
180
180
  assert_equal 'waiting', jobs[1].state
181
181
  end
182
182
 
183
+ def test_controlled_shutdown
184
+ remove_pids!
185
+
186
+ Workhorse.max_worker_memory_mb = 50
187
+
188
+ daemon = start_daemon
189
+
190
+ pid = with_retries do
191
+ pid = daemon.workers.first.pid
192
+ assert_process(pid)
193
+ pid
194
+ end
195
+
196
+ 10.times do
197
+ Workhorse.enqueue BasicJob.new(sleep_time: 0.1)
198
+
199
+ with_retries do
200
+ assert_equal 'succeeded', Workhorse::DbJob.first.state
201
+ Workhorse::DbJob.delete_all
202
+ end
203
+ end
204
+
205
+ Workhorse.enqueue MemHungryJob.new
206
+
207
+ with_retries do
208
+ assert_equal 'succeeded', Workhorse::DbJob.first.state
209
+
210
+ assert File.exist?("tmp/pids/workhorse.#{pid}.shutdown")
211
+ assert_not_process pid
212
+ end
213
+
214
+ daemon.watch
215
+
216
+ with_retries do
217
+ assert_not File.exist?("tmp/pids/workhorse.#{pid}.shutdown")
218
+ end
219
+ ensure
220
+ daemon.stop
221
+ Workhorse.max_worker_memory_mb = 0
222
+ end
223
+
183
224
  private
184
225
 
226
+ def remove_pids!
227
+ Dir[Rails.root.join('tmp', 'pids', '*')].each do |file|
228
+ FileUtils.rm file
229
+ end
230
+ end
231
+
232
+ def start_daemon
233
+ daemon = Workhorse::Daemon.new(pidfile: 'tmp/pids/test%s.pid') do |d|
234
+ d.worker 'Test Worker' do
235
+ begin
236
+ Workhorse::Worker.start_and_wait(
237
+ pool_size: 1,
238
+ polling_interval: 0.1,
239
+ logger: ActiveSupport::Logger.new('tmp/log.log')
240
+ )
241
+ end
242
+ end
243
+ end
244
+ daemon.start
245
+ return daemon
246
+ end
247
+
248
+ def assert_process(pid)
249
+ assert process?(pid), "Process #{pid} expected to be running"
250
+ end
251
+
252
+ def assert_not_process(pid)
253
+ assert_not process?(pid), "Process #{pid} expected to be stopped"
254
+ end
255
+
256
+ def process?(pid)
257
+ Process.kill(0, pid)
258
+ true
259
+ rescue Errno::EPERM, Errno::ESRCH
260
+ false
261
+ end
262
+
185
263
  def enqueue_in_multiple_queues
186
264
  Workhorse.enqueue BasicJob.new(some_param: nil)
187
265
  Workhorse.enqueue BasicJob.new(some_param: :q1), queue: :q1
data/workhorse.gemspec CHANGED
@@ -1,48 +1,31 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: workhorse 1.2.16 ruby lib
2
+ # stub: workhorse 1.2.17.rc1 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "workhorse".freeze
6
- s.version = "1.2.16"
6
+ s.version = "1.2.17.rc1"
7
7
 
8
- s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
8
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["Sitrox".freeze]
11
- s.date = "2023-09-18"
11
+ s.date = "2024-02-08"
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/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
- s.rubygems_version = "3.1.2".freeze
13
+ s.rubygems_version = "3.4.6".freeze
14
14
  s.summary = "Multi-threaded job backend with database queuing for ruby.".freeze
15
15
  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/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]
16
16
 
17
- if s.respond_to? :specification_version then
18
- s.specification_version = 4
19
- end
17
+ s.specification_version = 4
20
18
 
21
- if s.respond_to? :add_runtime_dependency then
22
- s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
23
- s.add_development_dependency(%q<rake>.freeze, [">= 0"])
24
- s.add_development_dependency(%q<rubocop>.freeze, ["~> 1.28.0"])
25
- s.add_development_dependency(%q<minitest>.freeze, [">= 0"])
26
- s.add_development_dependency(%q<mysql2>.freeze, [">= 0"])
27
- s.add_development_dependency(%q<colorize>.freeze, [">= 0"])
28
- s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
29
- s.add_development_dependency(%q<activejob>.freeze, [">= 0"])
30
- s.add_development_dependency(%q<pry>.freeze, [">= 0"])
31
- s.add_runtime_dependency(%q<activesupport>.freeze, [">= 0"])
32
- s.add_runtime_dependency(%q<activerecord>.freeze, [">= 0"])
33
- s.add_runtime_dependency(%q<concurrent-ruby>.freeze, [">= 0"])
34
- else
35
- s.add_dependency(%q<bundler>.freeze, [">= 0"])
36
- s.add_dependency(%q<rake>.freeze, [">= 0"])
37
- s.add_dependency(%q<rubocop>.freeze, ["~> 1.28.0"])
38
- s.add_dependency(%q<minitest>.freeze, [">= 0"])
39
- s.add_dependency(%q<mysql2>.freeze, [">= 0"])
40
- s.add_dependency(%q<colorize>.freeze, [">= 0"])
41
- s.add_dependency(%q<benchmark-ips>.freeze, [">= 0"])
42
- s.add_dependency(%q<activejob>.freeze, [">= 0"])
43
- s.add_dependency(%q<pry>.freeze, [">= 0"])
44
- s.add_dependency(%q<activesupport>.freeze, [">= 0"])
45
- s.add_dependency(%q<activerecord>.freeze, [">= 0"])
46
- s.add_dependency(%q<concurrent-ruby>.freeze, [">= 0"])
47
- end
19
+ s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
20
+ s.add_development_dependency(%q<rake>.freeze, [">= 0"])
21
+ s.add_development_dependency(%q<rubocop>.freeze, ["~> 1.28.0"])
22
+ s.add_development_dependency(%q<minitest>.freeze, [">= 0"])
23
+ s.add_development_dependency(%q<mysql2>.freeze, [">= 0"])
24
+ s.add_development_dependency(%q<colorize>.freeze, [">= 0"])
25
+ s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
26
+ s.add_development_dependency(%q<activejob>.freeze, [">= 0"])
27
+ s.add_development_dependency(%q<pry>.freeze, [">= 0"])
28
+ s.add_runtime_dependency(%q<activesupport>.freeze, [">= 0"])
29
+ s.add_runtime_dependency(%q<activerecord>.freeze, [">= 0"])
30
+ s.add_runtime_dependency(%q<concurrent-ruby>.freeze, [">= 0"])
48
31
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workhorse
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.16
4
+ version: 1.2.17.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sitrox
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-18 00:00:00.000000000 Z
11
+ date: 2024-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -242,11 +242,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
242
242
  version: '0'
243
243
  required_rubygems_version: !ruby/object:Gem::Requirement
244
244
  requirements:
245
- - - ">="
245
+ - - ">"
246
246
  - !ruby/object:Gem::Version
247
- version: '0'
247
+ version: 1.3.1
248
248
  requirements: []
249
- rubygems_version: 3.4.6
249
+ rubygems_version: 3.4.10
250
250
  signing_key:
251
251
  specification_version: 4
252
252
  summary: Multi-threaded job backend with database queuing for ruby.