workhorse 0.3.9 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4bcaee406798a7da933fd0a48c7e556447a589a6be42f73d259902ae3a69b339
4
- data.tar.gz: 2201639a37267a0980b873d0398222fbd0da0ac0a672244180a37b7e0b5265aa
3
+ metadata.gz: 713d9c117c06a2e448ec9b963b216df9e46741f81f7e5e80c62f4b3648b1d29b
4
+ data.tar.gz: f625a84d3173812546d200feac847cbd16dcd800ee6d2f188e44302c79078211
5
5
  SHA512:
6
- metadata.gz: 4f7813c2d5945df4a9a4757b2dd3ac2f270da19888b5d47cff50a3e5321bef65ded194d0775b30719754b20018c4050dc46ee0f602f445871661edf400a4afda
7
- data.tar.gz: db1da7961291a3eefd8c066fe9b36e8e0d4ce0fcb673968229b364b79e5fbe37e06574f506f1a1024487dbb1d73d667b92a14726d9adcce8791962a850505d8a
6
+ metadata.gz: 25818f59461494d50b08a00f863c81d48521998462643134454fac6b8ba7c6c7d2358213342373c01b4dbc5ef1dd4d02bdd561e1a1c9c5294d840095e1e5b931
7
+ data.tar.gz: 833e6132e223e85060253f678109dad5624802ad163c990caae78fc8a6de44820318505c36bb5709e2f7fd591f4ce7ed71aa2ed2487b6de43d3cec17eeb085da
data/CHANGELOG.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # Workhorse Change log
2
2
 
3
- ## Unreleased
3
+ ## 0.4.0 - 2019-05-15
4
+
5
+ * Added instruments for clearing DbJob data. (PR #17)
6
+
7
+ * Added instant repolling feature. (#PR18)
4
8
 
5
9
  ## 0.3.9 – 2019-03-09
6
10
 
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # Workhorse
5
5
 
6
- Multi-threaded job backend with database queuing for ruby.
6
+ Multi-threaded job backend with database queuing for ruby. Battle-tested and ready for production-use.
7
7
 
8
8
  ## Introduction
9
9
 
@@ -240,6 +240,23 @@ Workhorse::Daemon::ShellHandler.run count: 5 do
240
240
  end
241
241
  ```
242
242
 
243
+ ### Instant repolling
244
+
245
+ Per default, each worker only polls in the given interval. This means that if
246
+ you schedule, for example, 50 jobs at once and have a polling interval of 1
247
+ minute with a queue size of 1, the poller would tackle the first job and then
248
+ wait for a whole minute until the next poll. This would mean that these 50 jobs
249
+ would take at least 50 minutes to be executed, even if they only take a few
250
+ seconds each.
251
+
252
+ This is where *instant repolling* comes into play: Using the worker option
253
+ `instant_repolling`, you can force the poller to automatically re-poll the
254
+ database whenever a job has been performed. It then goes back to the usual
255
+ polling interval.
256
+
257
+ This setting is recommended for all setups and may eventually be enabled by
258
+ default.
259
+
243
260
  ## Exception handling
244
261
 
245
262
  Per default, exceptions occurring in a worker thread will only be visible in the
@@ -257,6 +274,77 @@ Workhorse.setup do |config|
257
274
  end
258
275
  ```
259
276
 
277
+ ## Handling database jobs
278
+
279
+ Jobs stored in the database can be accessed via the ActiveRecord model
280
+ {Workhorse::DbJob}. This is the model representing a specific job database entry
281
+ and is not to be confused with the actual job class you're enqueueing.
282
+
283
+ ### Obtaining database jobs
284
+
285
+ DbJobs are returned to you when enqueuing new jobs:
286
+
287
+ ```ruby
288
+ db_job = Workhorse.enqueue(MyJob.new)
289
+ ```
290
+
291
+ You can also obtain a job via its ID that you either get from a returned job
292
+ (see example above) or else by manually querying the database table:
293
+
294
+ ```ruby
295
+ db_job = Workhorse::DbJob.find(42)
296
+ ```
297
+
298
+ Note that database job objects reflect the job at the point in time when the
299
+ database job object has been instantiated. To make sure you're looking at the
300
+ latest job info, use the in-place `reload` method:
301
+
302
+ ```ruby
303
+ db_job.reload
304
+ ```
305
+
306
+ You can also retrieve a list of jobs in a specific state using one of the
307
+ following methods:
308
+
309
+ ```ruby
310
+ DbJob.waiting
311
+ DbJob.locked
312
+ DbJob.started
313
+ DbJob.succeeded
314
+ DbJob.failed
315
+ ```
316
+
317
+ ### Resetting jobs
318
+
319
+ Jobs in a state other than `waiting` are either being processed or else already
320
+ in a final state such as `succeeded` and won't be performed again. Workhorse
321
+ provides an API method for resetting jobs in the following cases:
322
+
323
+ * A job has succeeded or failed (states `succeeded` and `failed`) and needs to
324
+ re-run. In these cases, perform a non-forced reset:
325
+
326
+ ```ruby
327
+ db_job.reset!
328
+ ```
329
+
330
+ This is always safe to do, even with workers running.
331
+
332
+ * A job is stuck in state `locked` or `started` and the corresponding worker
333
+ (check the database field `locked_by`) is not running anymore, i.e. due to a
334
+ database connection loss or an unexpected worker crash. In these cases, the
335
+ job will never be processed, and, if the job is in a queue, the entire queue is
336
+ considered to be locked and no further jobs will be processed in this queue.
337
+
338
+ In these cases, make sure the worker is stopped and perform a forced reset:
339
+
340
+ ```ruby
341
+ db_job.reset!(true)
342
+ ```
343
+
344
+ Performing a reset will reset the job state to `waiting` and it will be
345
+ processed again. All meta fields will be reset as well. See inline documentation
346
+ of `Workhorse::DbJob#reset!` for more details.
347
+
260
348
  ## Frequently asked questions
261
349
 
262
350
  Please consult the [FAQ](FAQ.md).
data/Rakefile CHANGED
@@ -11,11 +11,12 @@ task :gemspec do
11
11
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
12
12
  spec.require_paths = ['lib']
13
13
 
14
- spec.add_development_dependency 'bundler', '~> 1.3'
14
+ spec.add_development_dependency 'bundler'
15
15
  spec.add_development_dependency 'rake'
16
16
  spec.add_development_dependency 'rubocop', '0.51.0'
17
17
  spec.add_development_dependency 'minitest'
18
18
  spec.add_development_dependency 'mysql2'
19
+ spec.add_development_dependency 'colorize'
19
20
  spec.add_development_dependency 'benchmark-ips'
20
21
  spec.add_dependency 'activesupport'
21
22
  spec.add_dependency 'activerecord'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.9
1
+ 0.4.0
@@ -12,6 +12,58 @@ module Workhorse
12
12
 
13
13
  self.table_name = 'jobs'
14
14
 
15
+ def self.waiting
16
+ where(state: STATE_WAITING)
17
+ end
18
+
19
+ def self.locked
20
+ where(state: STATE_LOCKED)
21
+ end
22
+
23
+ def self.started
24
+ where(state: STATE_STARTED)
25
+ end
26
+
27
+ def self.succeeded
28
+ where(state: STATE_SUCCEEDED)
29
+ end
30
+
31
+ def self.failed
32
+ where(state: STATE_FAILED)
33
+ end
34
+
35
+ # Resets job to state "waiting" and clears all meta fields
36
+ # set by workhorse in course of processing this job.
37
+ #
38
+ # This is only allowed if the job is in a final state ("succeeded" or
39
+ # "failed"), as only those jobs are safe to modify; workhorse will not touch
40
+ # these jobs. To reset a job without checking the state it is in, set
41
+ # "force" to true. Prior to doing so, ensure that the job is not still being
42
+ # processed by a worker. If possible, shut down all workers before
43
+ # performing a forced reset.
44
+ #
45
+ # After the job is reset, it will be performed again. If you reset a job
46
+ # that has already been performed ("succeeded") or partially performed
47
+ # ("failed"), make sure the actions performed in the job are repeatable or
48
+ # have been rolled back. E.g. if the job already wrote something to an
49
+ # external API, it may cause inconsistencies if the job is performed again.
50
+ def reset!(force = false)
51
+ unless force
52
+ assert_state! STATE_SUCCEEDED, STATE_FAILED
53
+ end
54
+
55
+ self.state = STATE_WAITING
56
+ self.locked_at = nil
57
+ self.locked_by = nil
58
+ self.started_at = nil
59
+ self.succeeded_at = nil
60
+ self.failed_at = nil
61
+ self.last_error = nil
62
+
63
+ save!
64
+ end
65
+
66
+ # @private Only to be used by workhorse
15
67
  def mark_locked!(worker_id)
16
68
  if changed?
17
69
  fail "Dirty jobs can't be locked."
@@ -27,6 +79,7 @@ module Workhorse
27
79
  save!
28
80
  end
29
81
 
82
+ # @private Only to be used by workhorse
30
83
  def mark_started!
31
84
  assert_state! STATE_LOCKED
32
85
 
@@ -35,6 +88,7 @@ module Workhorse
35
88
  save!
36
89
  end
37
90
 
91
+ # @private Only to be used by workhorse
38
92
  def mark_failed!(exception)
39
93
  assert_state! STATE_LOCKED, STATE_STARTED
40
94
 
@@ -44,6 +98,7 @@ module Workhorse
44
98
  save!
45
99
  end
46
100
 
101
+ # @private Only to be used by workhorse
47
102
  def mark_succeeded!
48
103
  assert_state! STATE_STARTED
49
104
 
@@ -57,15 +112,5 @@ module Workhorse
57
112
  fail "Job #{id} is not in state #{states.inspect} but in state #{state.inspect}."
58
113
  end
59
114
  end
60
-
61
- def assert_locked_by!(worker_id)
62
- assert_state! STATE_WAITING
63
-
64
- if locked_by.nil?
65
- fail "Job #{id} is not locked by any worker."
66
- elsif locked_by != worker_id
67
- fail "Job #{id} is locked by another worker (#{locked_by})."
68
- end
69
- end
70
115
  end
71
116
  end
@@ -8,6 +8,7 @@ module Workhorse
8
8
  @running = false
9
9
  @table = Workhorse::DbJob.arel_table
10
10
  @is_oracle = ActiveRecord::Base.connection.adapter_name == 'OracleEnhanced'
11
+ @instant_repoll = Concurrent::AtomicBoolean.new(false)
11
12
  end
12
13
 
13
14
  def running?
@@ -41,18 +42,27 @@ module Workhorse
41
42
  @thread.join
42
43
  end
43
44
 
45
+ # Call this to interrupt current sleep and perform the next poll as soon as
46
+ # possible, then resume in the normal polling interval.
47
+ def instant_repoll!
48
+ worker.log 'Aborting next sleep to perform instant repoll', :debug
49
+ @instant_repoll.make_true
50
+ end
51
+
44
52
  private
45
53
 
46
54
  def sleep
47
55
  remaining = worker.polling_interval
48
56
 
49
- while running? && remaining > 0
57
+ while running? && remaining > 0 && @instant_repoll.false?
50
58
  Kernel.sleep 0.1
51
59
  remaining -= 0.1
52
60
  end
53
61
  end
54
62
 
55
63
  def poll
64
+ @instant_repoll.make_false
65
+
56
66
  Workhorse.tx_callback.call do
57
67
  # As we are the only thread posting into the worker pool, it is safe to
58
68
  # get the number of idle threads without mutex synchronization. The
@@ -150,7 +160,7 @@ module Workhorse
150
160
 
151
161
  select = select.lock
152
162
 
153
- return Workhorse::DbJob.find_by_sql(select.to_sql)
163
+ return Workhorse::DbJob.find_by_sql(select.to_sql).to_a
154
164
  end
155
165
 
156
166
  # Returns a fresh Arel select manager containing the id of all waiting jobs,
@@ -14,6 +14,11 @@ module Workhorse
14
14
  )
15
15
  @mutex = Mutex.new
16
16
  @active_threads = Concurrent::AtomicFixnum.new(0)
17
+ @on_idle = nil
18
+ end
19
+
20
+ def on_idle(&block)
21
+ @on_idle = block
17
22
  end
18
23
 
19
24
  # Posts a new work unit to the pool.
@@ -32,6 +37,7 @@ module Workhorse
32
37
  yield
33
38
  ensure
34
39
  active_threads.decrement
40
+ @on_idle&.call
35
41
  end
36
42
  end
37
43
  end
@@ -35,10 +35,12 @@ module Workhorse
35
35
  # worker properly on INT and TERM signals.
36
36
  # @param quiet [Boolean] If this is set to `false`, the worker will also log
37
37
  # to STDOUT.
38
+ # @param instant_repolling [Boolean] If this is set to `true`, the worker
39
+ # immediately re-polls for new jobs when a job execution has finished.
38
40
  # @param logger [Logger] An optional logger the worker will append to. This
39
41
  # can be any instance of ruby's `Logger` but is commonly set to
40
42
  # `Rails.logger`.
41
- def initialize(queues: [], pool_size: nil, polling_interval: 300, auto_terminate: true, quiet: true, logger: nil)
43
+ def initialize(queues: [], pool_size: nil, polling_interval: 300, auto_terminate: true, quiet: true, instant_repolling: false, logger: nil)
42
44
  @queues = queues
43
45
  @pool_size = pool_size || queues.size + 1
44
46
  @polling_interval = polling_interval
@@ -55,6 +57,10 @@ module Workhorse
55
57
  fail 'Polling interval must be a multiple of 0.1.'
56
58
  end
57
59
 
60
+ if instant_repolling
61
+ @pool.on_idle { @poller.instant_repoll! }
62
+ end
63
+
58
64
  check_rails_env if defined?(Rails)
59
65
  end
60
66
 
@@ -11,6 +11,14 @@ class WorkhorseTest < ActiveSupport::TestCase
11
11
 
12
12
  protected
13
13
 
14
+ def capture_log(level: :debug)
15
+ io = StringIO.new
16
+ logger = Logger.new(io, level: level)
17
+ yield logger
18
+ io.close
19
+ return io.string
20
+ end
21
+
14
22
  def work(time = 2, options = {})
15
23
  options[:pool_size] ||= 5
16
24
  options[:polling_interval] ||= 1
@@ -0,0 +1,58 @@
1
+ require 'test_helper'
2
+
3
+ class Workhorse::DbJobTest < WorkhorseTest
4
+ def test_reset_succeeded
5
+ job = Workhorse.enqueue(BasicJob.new(sleep_time: 0))
6
+ work 0.5
7
+ job.reload
8
+ assert_equal 'succeeded', job.state
9
+
10
+ job.reset!
11
+
12
+ assert_clean job
13
+ end
14
+
15
+ def test_reset_failed
16
+ job = Workhorse.enqueue FailingTestJob
17
+ work 0.5
18
+ job.reload
19
+ assert_equal 'failed', job.state
20
+
21
+ job.reset!
22
+
23
+ assert_clean job
24
+ end
25
+
26
+ def test_reset_locked_unforced
27
+ job = Workhorse.enqueue(BasicJob.new(sleep_time: 0))
28
+ job.mark_locked!(42)
29
+
30
+ err = assert_raises do
31
+ job.reset!
32
+ end
33
+ assert_equal %(Job #{job.id} is not in state [:succeeded, :failed] but in state "locked".), err.message
34
+ end
35
+
36
+ def test_forced_reset
37
+ job = Workhorse.enqueue(BasicJob.new(sleep_time: 0))
38
+ job.mark_locked!(42)
39
+
40
+ assert_nothing_raised do
41
+ job.reset!(true)
42
+ end
43
+
44
+ assert_clean job
45
+ end
46
+
47
+ private
48
+
49
+ def assert_clean(job)
50
+ assert_equal 'waiting', job.state
51
+ assert_nil job.locked_by
52
+ assert_nil job.locked_at
53
+ assert_nil job.started_at
54
+ assert_nil job.failed_at
55
+ assert_nil job.succeeded_at
56
+ assert_nil job.last_error
57
+ end
58
+ end
@@ -67,4 +67,42 @@ class Workhorse::PollerTest < WorkhorseTest
67
67
 
68
68
  assert_equal [nil], w.poller.send(:valid_queues)
69
69
  end
70
+
71
+ def test_with_instant_repolling
72
+ 3.times do
73
+ Workhorse.enqueue BasicJob.new(sleep_time: 0)
74
+ end
75
+
76
+ assert_equal 3, Workhorse::DbJob.where(state: :waiting).count
77
+
78
+ log = capture_log do |logger|
79
+ work 2, instant_repolling: true, polling_interval: 5, pool_size: 1, logger: logger
80
+ end
81
+
82
+ assert_repolling_logged 3, log
83
+ assert_equal 3, Workhorse::DbJob.where(state: :succeeded).count
84
+ end
85
+
86
+ def test_without_instant_repolling
87
+ 3.times do
88
+ Workhorse.enqueue BasicJob.new(sleep_time: 0)
89
+ end
90
+
91
+ log = capture_log do |logger|
92
+ work 0.5, instant_repolling: false, polling_interval: 5, pool_size: 1, logger: logger
93
+ end
94
+
95
+ assert_repolling_logged 0, log
96
+ assert_equal 1, Workhorse::DbJob.where(state: :succeeded).count
97
+ end
98
+
99
+ private
100
+
101
+ def setup
102
+ Workhorse::DbJob.delete_all
103
+ end
104
+
105
+ def assert_repolling_logged(count, log)
106
+ assert_equal count, log.scan(/Aborting next sleep to perform instant repoll/m).size
107
+ end
70
108
  end
@@ -19,6 +19,28 @@ class Workhorse::PoolTest < WorkhorseTest
19
19
  end
20
20
  end
21
21
 
22
+ def test_on_idle
23
+ on_idle_calls = Concurrent::AtomicFixnum.new
24
+
25
+ with_pool 2 do |p|
26
+ p.on_idle { on_idle_calls.increment }
27
+
28
+ assert_equal 0, on_idle_calls.value
29
+
30
+ p.post { sleep 0.2 }
31
+ p.post { sleep 0.4 }
32
+
33
+ sleep 0.1
34
+ assert_equal 0, on_idle_calls.value
35
+
36
+ sleep 0.2
37
+ assert_equal 1, on_idle_calls.value
38
+
39
+ sleep 0.1
40
+ assert_equal 2, on_idle_calls.value
41
+ end
42
+ end
43
+
22
44
  def test_overflow
23
45
  with_pool 5 do |p|
24
46
  5.times { p.post { sleep 0.2 } }
@@ -5,10 +5,10 @@ class Workhorse::WorkerTest < WorkhorseTest
5
5
  with_worker(pool_size: 5, polling_interval: 0.2) do |w|
6
6
  assert_equal 5, w.idle
7
7
 
8
- sleep 0.1
8
+ sleep 0.05
9
9
  Workhorse.enqueue BasicJob.new(sleep_time: 0.2)
10
10
 
11
- sleep 0.2
11
+ sleep 0.25
12
12
  assert_equal 4, w.idle
13
13
 
14
14
  sleep 0.2
data/workhorse.gemspec CHANGED
@@ -1,39 +1,41 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: workhorse 0.3.9 ruby lib
2
+ # stub: workhorse 0.4.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "workhorse".freeze
6
- s.version = "0.3.9"
6
+ s.version = "0.4.0"
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 = "2019-04-09"
12
- s.files = [".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, ".travis.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/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/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/run_rails_op.rb".freeze, "lib/workhorse/performer.rb".freeze, "lib/workhorse/poller.rb".freeze, "lib/workhorse/pool.rb".freeze, "lib/workhorse/worker.rb".freeze, "test/lib/db_schema.rb".freeze, "test/lib/jobs.rb".freeze, "test/lib/test_helper.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 = "2019-05-15"
12
+ s.files = [".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, ".travis.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/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/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/run_rails_op.rb".freeze, "lib/workhorse/performer.rb".freeze, "lib/workhorse/poller.rb".freeze, "lib/workhorse/pool.rb".freeze, "lib/workhorse/worker.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
13
  s.rubygems_version = "3.0.3".freeze
14
14
  s.summary = "Multi-threaded job backend with database queuing for ruby.".freeze
15
- s.test_files = ["test/lib/db_schema.rb".freeze, "test/lib/jobs.rb".freeze, "test/lib/test_helper.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]
15
+ s.test_files = ["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
17
  if s.respond_to? :specification_version then
18
18
  s.specification_version = 4
19
19
 
20
20
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
21
- s.add_development_dependency(%q<bundler>.freeze, ["~> 1.3"])
21
+ s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
22
22
  s.add_development_dependency(%q<rake>.freeze, [">= 0"])
23
23
  s.add_development_dependency(%q<rubocop>.freeze, ["= 0.51.0"])
24
24
  s.add_development_dependency(%q<minitest>.freeze, [">= 0"])
25
25
  s.add_development_dependency(%q<mysql2>.freeze, [">= 0"])
26
+ s.add_development_dependency(%q<colorize>.freeze, [">= 0"])
26
27
  s.add_development_dependency(%q<benchmark-ips>.freeze, [">= 0"])
27
28
  s.add_runtime_dependency(%q<activesupport>.freeze, [">= 0"])
28
29
  s.add_runtime_dependency(%q<activerecord>.freeze, [">= 0"])
29
30
  s.add_runtime_dependency(%q<schemacop>.freeze, ["~> 2.0"])
30
31
  s.add_runtime_dependency(%q<concurrent-ruby>.freeze, [">= 0"])
31
32
  else
32
- s.add_dependency(%q<bundler>.freeze, ["~> 1.3"])
33
+ s.add_dependency(%q<bundler>.freeze, [">= 0"])
33
34
  s.add_dependency(%q<rake>.freeze, [">= 0"])
34
35
  s.add_dependency(%q<rubocop>.freeze, ["= 0.51.0"])
35
36
  s.add_dependency(%q<minitest>.freeze, [">= 0"])
36
37
  s.add_dependency(%q<mysql2>.freeze, [">= 0"])
38
+ s.add_dependency(%q<colorize>.freeze, [">= 0"])
37
39
  s.add_dependency(%q<benchmark-ips>.freeze, [">= 0"])
38
40
  s.add_dependency(%q<activesupport>.freeze, [">= 0"])
39
41
  s.add_dependency(%q<activerecord>.freeze, [">= 0"])
@@ -41,11 +43,12 @@ Gem::Specification.new do |s|
41
43
  s.add_dependency(%q<concurrent-ruby>.freeze, [">= 0"])
42
44
  end
43
45
  else
44
- s.add_dependency(%q<bundler>.freeze, ["~> 1.3"])
46
+ s.add_dependency(%q<bundler>.freeze, [">= 0"])
45
47
  s.add_dependency(%q<rake>.freeze, [">= 0"])
46
48
  s.add_dependency(%q<rubocop>.freeze, ["= 0.51.0"])
47
49
  s.add_dependency(%q<minitest>.freeze, [">= 0"])
48
50
  s.add_dependency(%q<mysql2>.freeze, [">= 0"])
51
+ s.add_dependency(%q<colorize>.freeze, [">= 0"])
49
52
  s.add_dependency(%q<benchmark-ips>.freeze, [">= 0"])
50
53
  s.add_dependency(%q<activesupport>.freeze, [">= 0"])
51
54
  s.add_dependency(%q<activerecord>.freeze, [">= 0"])
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: workhorse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.9
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sitrox
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-04-09 00:00:00.000000000 Z
11
+ date: 2019-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.3'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.3'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: colorize
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: benchmark-ips
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -187,6 +201,7 @@ files:
187
201
  - test/lib/db_schema.rb
188
202
  - test/lib/jobs.rb
189
203
  - test/lib/test_helper.rb
204
+ - test/workhorse/db_job_test.rb
190
205
  - test/workhorse/enqueuer_test.rb
191
206
  - test/workhorse/performer_test.rb
192
207
  - test/workhorse/poller_test.rb
@@ -219,6 +234,7 @@ test_files:
219
234
  - test/lib/db_schema.rb
220
235
  - test/lib/jobs.rb
221
236
  - test/lib/test_helper.rb
237
+ - test/workhorse/db_job_test.rb
222
238
  - test/workhorse/enqueuer_test.rb
223
239
  - test/workhorse/performer_test.rb
224
240
  - test/workhorse/poller_test.rb