workhorse 1.0.0.beta1 → 1.1.1

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: 4e60ee9b02f73f0ecffe49df98f1d83e59d23f612d84e7a15232e7c95da15873
4
- data.tar.gz: 3dcae37910f823e33af81b47ad4578a6e0294213b8c2eb5a01dca39762d8f9ca
3
+ metadata.gz: 5958b8fe7db7f321dd981b409b217b90cc53ec31115b64b12b1d5c70afcf6d86
4
+ data.tar.gz: aa4f0a87290e36b884d54b8a9bc56889249d32808e80aa7e671c6ae120ec4a6c
5
5
  SHA512:
6
- metadata.gz: 6fd243a410f19deb78d09331e7e67e27438851b90ac99a9fcd4495cf33be832014f63c9c691a118da003e1bc4305e793a13dc8a3399885d69c168b39bc5c9d6e
7
- data.tar.gz: 8b0407c7f00e05258a7f7a837c9246514afcb2a039dc3e68aeb7622fe929e9fc7f6b84a90de9026219d0acc87a7a8131effb49c43c791761d518410492e5c652
6
+ metadata.gz: 74be8af063fa45182bd30b9b94e6fbc13e1ba84a6da75ca10ba8356c345105ee7b8613c83c55b9d6df8236f5320c919ce101ce8342f7bf8ce2ab9052c54fdac9
7
+ data.tar.gz: 5b48f0bd62fca831890141ea0e4c7e8b206104873b1c95d0db0da8d737731e6732a416ffb31a54f7057244edc109e291355fb63d6619bbc6e5e2ca1e62c23372
@@ -1,7 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.3.0
4
3
  - 2.5.0
4
+ - 2.6.0
5
5
  services:
6
6
  - mysql
7
7
  before_install:
@@ -1,5 +1,38 @@
1
1
  # Workhorse Changelog
2
2
 
3
+ ## 1.1.1 - 2021-01-19
4
+
5
+ * Remove deprecation warnings with Ruby 2.7
6
+
7
+ ## 1.1.0 - 2020-12-24
8
+
9
+ * Add `description` column to `DbJob`.
10
+
11
+ If you're upgrading from a previous version, add the `description` column
12
+ to your `DbJob` table, e.g. with such a migration:
13
+
14
+ ```ruby
15
+ class AddDescriptionToWorkhorseDbJobs < ActiveRecord::Migration[6.0]
16
+ def change
17
+ add_column :jobs, :description, :string, after: :perform_at, null: true
18
+ end
19
+ end
20
+ ```
21
+
22
+ ## 1.0.1 - 2020-12-15
23
+
24
+ * Fix handling of empty pid files
25
+
26
+ ## 1.0.0 - 2020-09-21
27
+
28
+ * Stable release, identical to 1.0.0.beta2 but now extensively battle-tested
29
+
30
+ ## 1.0.0.beta2 - 2020-08-27
31
+
32
+ * Add option `config.silence_poller_exceptions` (default `false`)
33
+
34
+ * Add option `config.silence_watcher` (default `false`)
35
+
3
36
  ## 1.0.0.beta1 - 2020-08-20
4
37
 
5
38
  This is a stability release that is still experimental and has to be tested in
data/README.md CHANGED
@@ -81,7 +81,7 @@ GRANT execute ON DBMS_LOCK TO <schema-name>;
81
81
 
82
82
  Workhorse can handle any jobs that support the `perform` method and are
83
83
  serializable. To queue a basic job, use the static method `Workhorse.enqueue`.
84
- You can optionally pass a queue name and a priority.
84
+ You can optionally pass a queue name, a priority and a description (as a string).
85
85
 
86
86
  ```ruby
87
87
  class MyJob
@@ -94,7 +94,7 @@ class MyJob
94
94
  end
95
95
  end
96
96
 
97
- Workhorse.enqueue MyJob.new('John'), queue: :test, priority: 2
97
+ Workhorse.enqueue MyJob.new('John'), queue: :test, priority: 2, description: 'Basic Job'
98
98
  ```
99
99
 
100
100
  ### RailsOps operations
@@ -290,6 +290,10 @@ Workhorse.setup do |config|
290
290
  end
291
291
  ```
292
292
 
293
+ Using the settings `config.silence_poller_exceptions` and
294
+ `config.silence_watcher`, you can silence certain exceptions / error outputs
295
+ (both are disabled by default).
296
+
293
297
  ## Handling database jobs
294
298
 
295
299
  Jobs stored in the database can be accessed via the ActiveRecord model
@@ -377,7 +381,7 @@ configuration or else using `self.queue_adapter` in a job class inheriting from
377
381
  Per default, jobs remain in the database, no matter in which state. This can
378
382
  eventually lead to a very large jobs database. You are advised to clean your
379
383
  jobs database on a regular interval. Workhorse provides the job
380
- `Workhose::Jobs::CleanupSucceededJobs` for this purpose that cleans up all
384
+ `Workhorse::Jobs::CleanupSucceededJobs` for this purpose that cleans up all
381
385
  succeeded jobs. You can run this using your scheduler in a specific interval.
382
386
 
383
387
  ## Caveats
@@ -417,4 +421,4 @@ Please consult the [FAQ](FAQ.md).
417
421
 
418
422
  ## Copyright
419
423
 
420
- Copyright © 2020 Sitrox. See `LICENSE` for further details.
424
+ Copyright © 2021 Sitrox. See `LICENSE` for further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0.beta1
1
+ 1.1.1
@@ -17,6 +17,8 @@ class CreateTableJobs < ActiveRecord::Migration
17
17
  t.integer :priority, null: false
18
18
  t.datetime :perform_at, null: true
19
19
 
20
+ t.string :description, null: true
21
+
20
22
  t.timestamps null: false
21
23
  end
22
24
 
@@ -1,7 +1,8 @@
1
- require 'socket'
2
- require 'active_support/all'
3
1
  require 'active_record'
2
+ require 'active_support/all'
4
3
  require 'concurrent'
4
+ require 'socket'
5
+ require 'uri'
5
6
 
6
7
  require 'workhorse/enqueuer'
7
8
  require 'workhorse/scoped_env'
@@ -30,6 +31,17 @@ module Workhorse
30
31
  # ExceptionNotifier.notify_exception(exception)
31
32
  end
32
33
 
34
+ # If set to `true`, the defined `on_exception` will not be called when the
35
+ # poller encounters an exception and the worker has to be shut down. The
36
+ # exception will still be logged.
37
+ mattr_accessor :silence_poller_exceptions
38
+ self.silence_poller_exceptions = false
39
+
40
+ # If set to `true`, the `watch` command won't produce any output. This does
41
+ # not include warnings such as the "development mode" warning.
42
+ mattr_accessor :silence_watcher
43
+ self.silence_watcher = false
44
+
33
45
  mattr_accessor :perform_jobs_in_tx
34
46
  self.perform_jobs_in_tx = true
35
47
 
@@ -38,21 +38,21 @@ module Workhorse
38
38
  @workers << Worker.new(@workers.size + 1, name, &block)
39
39
  end
40
40
 
41
- def start
41
+ def start(quiet: false)
42
42
  code = 0
43
43
 
44
44
  for_each_worker do |worker|
45
45
  pid_file, pid = read_pid(worker)
46
46
 
47
47
  if pid_file && pid
48
- warn "Worker ##{worker.id} (#{worker.name}): Already started (PID #{pid})"
48
+ warn "Worker ##{worker.id} (#{worker.name}): Already started (PID #{pid})" unless quiet
49
49
  code = 1
50
50
  elsif pid_file
51
51
  File.delete pid_file
52
- puts "Worker ##{worker.id} (#{worker.name}): Starting (stale pid file)"
52
+ puts "Worker ##{worker.id} (#{worker.name}): Starting (stale pid file)" unless quiet
53
53
  start_worker worker
54
54
  else
55
- warn "Worker ##{worker.id} (#{worker.name}): Starting"
55
+ warn "Worker ##{worker.id} (#{worker.name}): Starting" unless quiet
56
56
  start_worker worker
57
57
  end
58
58
  end
@@ -109,7 +109,7 @@ module Workhorse
109
109
  end
110
110
 
111
111
  if should_be_running && status(quiet: true) != 0
112
- return start
112
+ return start(quiet: Workhorse.silence_watcher)
113
113
  else
114
114
  return 0
115
115
  end
@@ -177,7 +177,9 @@ module Workhorse
177
177
  file = pid_file_for(worker)
178
178
 
179
179
  if File.exist?(file)
180
- pid = IO.read(file).to_i
180
+ raw_pid = IO.read(file)
181
+ return nil, nil if raw_pid.blank?
182
+ pid = Integer(raw_pid)
181
183
  return file, process?(pid) ? pid : nil
182
184
  else
183
185
  return nil, nil
@@ -1,6 +1,6 @@
1
1
  module Workhorse
2
2
  class Daemon::ShellHandler
3
- def self.run(options = {}, &block)
3
+ def self.run(**options, &block)
4
4
  unless ARGV.count == 1
5
5
  usage
6
6
  exit 99
@@ -10,7 +10,7 @@ module Workhorse
10
10
  lockfile = File.open(lockfile_path, 'a')
11
11
  lockfile.flock(File::LOCK_EX || File::LOCK_NB)
12
12
 
13
- daemon = Workhorse::Daemon.new(options, &block)
13
+ daemon = Workhorse::Daemon.new(**options, &block)
14
14
 
15
15
  begin
16
16
  case ARGV.first
@@ -7,7 +7,7 @@ module Workhorse
7
7
  STATE_FAILED = :failed
8
8
 
9
9
  if respond_to?(:attr_accessible)
10
- attr_accessible :queue, :priority, :perform_at, :handler
10
+ attr_accessible :queue, :priority, :perform_at, :handler, :description
11
11
  end
12
12
 
13
13
  self.table_name = 'jobs'
@@ -1,11 +1,12 @@
1
1
  module Workhorse
2
2
  module Enqueuer
3
3
  # Enqueue any object that is serializable and has a `perform` method
4
- def enqueue(job, queue: nil, priority: 0, perform_at: Time.now)
4
+ def enqueue(job, queue: nil, priority: 0, perform_at: Time.now, description: nil)
5
5
  return DbJob.create!(
6
6
  queue: queue,
7
7
  priority: priority,
8
8
  perform_at: perform_at,
9
+ description: description,
9
10
  handler: Marshal.dump(job)
10
11
  )
11
12
  end
@@ -22,7 +22,7 @@ module Workhorse::Jobs
22
22
  rel = rel.where('locked_at < ?', @locked_to_started_threshold.seconds.ago)
23
23
  ids = rel.pluck(:id)
24
24
 
25
- if ids.size > 0
25
+ unless ids.empty?
26
26
  messages << "Detected #{ids.size} jobs that were locked more than "\
27
27
  "#{@locked_to_started_threshold}s ago and might be stale: #{ids.inspect}."
28
28
  end
@@ -34,7 +34,7 @@ module Workhorse::Jobs
34
34
  rel = rel.where('started_at < ?', @run_time_threshold.seconds.ago)
35
35
  ids = rel.pluck(:id)
36
36
 
37
- if ids.size > 0
37
+ unless ids.empty?
38
38
  messages << "Detected #{ids.size} jobs that are running for longer than "\
39
39
  "#{@run_time_threshold}s ago and might be stale: #{ids.inspect}."
40
40
  end
@@ -3,8 +3,8 @@ module Workhorse
3
3
  MIN_LOCK_TIMEOUT = 0.1 # In seconds
4
4
  MAX_LOCK_TIMEOUT = 1.0 # In seconds
5
5
 
6
- ORACLE_LOCK_MODE = 6 # X_MODE (exclusive)
7
- ORACLE_LOCK_HANDLE = 478564848 # Randomly chosen number
6
+ ORACLE_LOCK_MODE = 6 # X_MODE (exclusive)
7
+ ORACLE_LOCK_HANDLE = 478_564_848 # Randomly chosen number
8
8
 
9
9
  attr_reader :worker
10
10
  attr_reader :table
@@ -35,7 +35,7 @@ module Workhorse
35
35
  rescue Exception => e
36
36
  worker.log %(Poll encountered exception:\n#{e.message}\n#{e.backtrace.join("\n")})
37
37
  worker.log 'Worker shutting down...'
38
- Workhorse.on_exception.call(e)
38
+ Workhorse.on_exception.call(e) unless Workhorse.silence_poller_exceptions
39
39
  @running = false
40
40
  worker.instance_variable_get(:@pool).shutdown
41
41
  break
@@ -72,7 +72,7 @@ module Workhorse
72
72
  end
73
73
  end
74
74
 
75
- def with_global_lock(name: :workhorse, timeout: 2, &block)
75
+ def with_global_lock(name: :workhorse, timeout: 2, &_block)
76
76
  if @is_oracle
77
77
  result = Workhorse::DbJob.connection.select_all(
78
78
  "SELECT DBMS_LOCK.REQUEST(#{ORACLE_LOCK_HANDLE}, #{ORACLE_LOCK_MODE}, #{timeout}) FROM DUAL"
@@ -13,8 +13,8 @@ module Workhorse
13
13
 
14
14
  # Instantiates and starts a new worker with the given arguments and then
15
15
  # waits for its completion (i.e. an interrupt).
16
- def self.start_and_wait(*args)
17
- worker = new(*args)
16
+ def self.start_and_wait(**args)
17
+ worker = new(**args)
18
18
  worker.start
19
19
  worker.wait
20
20
  end
@@ -18,6 +18,8 @@ ActiveRecord::Schema.define do
18
18
  t.integer :priority, null: false
19
19
  t.datetime :perform_at, null: true
20
20
 
21
+ t.string :description, null: true
22
+
21
23
  t.timestamps null: false
22
24
  end
23
25
 
@@ -33,6 +33,15 @@ class Workhorse::EnqueuerTest < WorkhorseTest
33
33
  assert_equal 1, Workhorse::DbJob.first.priority
34
34
  end
35
35
 
36
+ def test_with_description
37
+ assert_equal 0, Workhorse::DbJob.all.count
38
+ Workhorse.enqueue BasicJob.new, description: 'Lorem ipsum'
39
+ assert_equal 1, Workhorse::DbJob.all.count
40
+
41
+ db_job = Workhorse::DbJob.first
42
+ assert_equal 'Lorem ipsum', db_job.description
43
+ end
44
+
36
45
  def test_op
37
46
  Workhorse.enqueue_op DummyRailsOpsOp, { queue: :q1 }, foo: :bar
38
47
 
@@ -48,7 +48,7 @@ class Workhorse::PollerTest < WorkhorseTest
48
48
  assert_equal %w[q1 q2], w.poller.send(:valid_queues)
49
49
  end
50
50
 
51
- def test_valid_queues
51
+ def test_valid_queues_2
52
52
  w = Workhorse::Worker.new(polling_interval: 60)
53
53
 
54
54
  assert_equal [], w.poller.send(:valid_queues)
@@ -148,6 +148,7 @@ class Workhorse::PollerTest < WorkhorseTest
148
148
  assert_equal 25, used_workers
149
149
  end
150
150
 
151
+ # rubocop: disable Style/GlobalVars
151
152
  def test_connection_loss
152
153
  $thread_conn = nil
153
154
 
@@ -174,6 +175,7 @@ class Workhorse::PollerTest < WorkhorseTest
174
175
 
175
176
  assert_equal 1, Workhorse::DbJob.succeeded.count
176
177
  end
178
+ # rubocop: enable Style/GlobalVars
177
179
 
178
180
  private
179
181
 
@@ -1,14 +1,14 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: workhorse 1.0.0.beta1 ruby lib
2
+ # stub: workhorse 1.1.1 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "workhorse".freeze
6
- s.version = "1.0.0.beta1"
6
+ s.version = "1.1.1"
7
7
 
8
- s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version=
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 = "2020-08-20"
11
+ s.date = "2021-01-19"
12
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/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/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
13
  s.rubygems_version = "3.0.3".freeze
14
14
  s.summary = "Multi-threaded job backend with database queuing for ruby.".freeze
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.0.0.beta1
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sitrox
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-20 00:00:00.000000000 Z
11
+ date: 2021-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -192,8 +192,8 @@ dependencies:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
- description:
196
- email:
195
+ description:
196
+ email:
197
197
  executables: []
198
198
  extensions: []
199
199
  extra_rdoc_files: []
@@ -241,10 +241,10 @@ files:
241
241
  - test/workhorse/pool_test.rb
242
242
  - test/workhorse/worker_test.rb
243
243
  - workhorse.gemspec
244
- homepage:
244
+ homepage:
245
245
  licenses: []
246
246
  metadata: {}
247
- post_install_message:
247
+ post_install_message:
248
248
  rdoc_options: []
249
249
  require_paths:
250
250
  - lib
@@ -255,12 +255,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
255
255
  version: '0'
256
256
  required_rubygems_version: !ruby/object:Gem::Requirement
257
257
  requirements:
258
- - - ">"
258
+ - - ">="
259
259
  - !ruby/object:Gem::Version
260
- version: 1.3.1
260
+ version: '0'
261
261
  requirements: []
262
- rubygems_version: 3.1.2
263
- signing_key:
262
+ rubygems_version: 3.1.4
263
+ signing_key:
264
264
  specification_version: 4
265
265
  summary: Multi-threaded job backend with database queuing for ruby.
266
266
  test_files: