rocketjob 3.3.0 → 3.3.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
  SHA1:
3
- metadata.gz: 1974e77ea4b6aa126dfa57155d15bd49a659ae1e
4
- data.tar.gz: 900333f68eb5ee6e378fc19d494b7408944fa83b
3
+ metadata.gz: 73879d59ee0d46f407f77acfc5b443b693e16727
4
+ data.tar.gz: 27d8a5a990b39d19c00ff086d57b3df2263eb727
5
5
  SHA512:
6
- metadata.gz: 741923205b5e7caa759db125afe234fe4c237e80bfdadbb58db512b2ef2269f42fd9fc58207c4e9eb97a0447f164b9179472ccc0e1ba3ec64aaa155753cf0257
7
- data.tar.gz: b44d77f843c6e6342a2be309ec50fbf25eae7f9db26e074eb96143d18283a321981050d70287189cc5c855bc3f9600da5d11328e388ac9b2f5a3aea3420170cf
6
+ metadata.gz: 3fb4043fb6aca63ecb3968393aada59cd81101d69453592cc5032722d1751ce3716c095dee54baa0be77136df05f36ce49687d1a4f1a66a37c68feeae22870b9
7
+ data.tar.gz: ff2c14ea7b456f59f86b85f34174a2ab0d90d72799c7e839b90def2a288b44969f8a558a915129df0dce726d9b0d28252c91d1f9d5aefaf331dcf43d9500cd51
@@ -187,15 +187,7 @@ module RocketJob
187
187
  # # => {}
188
188
  def self.counts_by_state
189
189
  counts = {}
190
- collection.aggregate([
191
- {
192
- '$group' => {
193
- _id: '$state',
194
- count: {'$sum' => 1}
195
- }
196
- }
197
- ]
198
- ).each do |result|
190
+ collection.aggregate([{'$group' => {_id: '$state', count: {'$sum' => 1}}}]).each do |result|
199
191
  counts[result['_id'].to_sym] = result['count']
200
192
  end
201
193
  counts
@@ -229,14 +221,20 @@ module RocketJob
229
221
  # Case insensitive filename matching
230
222
  Pathname.glob(pattern, File::FNM_CASEFOLD).each do |pathname|
231
223
  next if pathname.directory?
232
- pathname = pathname.realpath
224
+ pathname = begin
225
+ pathname.realpath
226
+ rescue Errno::ENOENT
227
+ logger.warn("Unable to expand the realpath for #{pathname.inspect}. Skipping file.")
228
+ next
229
+ end
230
+
233
231
  file_name = pathname.to_s
234
232
 
235
233
  # Skip archive directories
236
234
  next if file_name.include?(self.class.default_archive_directory)
237
235
 
238
236
  # Security check?
239
- if (whitelist_paths.size > 0) && whitelist_paths.none? { |whitepath| file_name.to_s.start_with?(whitepath) }
237
+ if (whitelist_paths.size > 0) && whitelist_paths.none? {|whitepath| file_name.to_s.start_with?(whitepath)}
240
238
  logger.error "Skipping file: #{file_name} since it is not in any of the whitelisted paths: #{whitelist_paths.join(', ')}"
241
239
  next
242
240
  end
@@ -39,7 +39,7 @@ module RocketJob
39
39
  field :priority, type: Integer, default: 50, class_attribute: true, user_editable: true, copy_on_restart: true
40
40
 
41
41
  # When the job completes destroy it from both the database and the UI
42
- field :destroy_on_complete, type: Boolean, default: true, class_attribute: true, user_editable: true, copy_on_restart: true
42
+ field :destroy_on_complete, type: Boolean, default: true, class_attribute: true, copy_on_restart: true
43
43
 
44
44
  # Whether to store the results from this job
45
45
  field :collect_output, type: Boolean, default: false, class_attribute: true
@@ -58,12 +58,14 @@ module RocketJob
58
58
  end
59
59
 
60
60
  event :pause do
61
- transitions from: :running, to: :paused
61
+ transitions from: :running, to: :paused, if: :pausable?
62
+ # All jobs are pausable prior to starting the job.
62
63
  transitions from: :queued, to: :paused
63
64
  end
64
65
 
65
66
  event :resume do
66
- transitions from: :paused, to: :running, if: -> { started_at }
67
+ transitions from: :paused, to: :running, if: -> { pausable? && started_at }
68
+ # All jobs paused before processing started are pausable.
67
69
  transitions from: :paused, to: :queued, unless: -> { started_at }
68
70
  end
69
71
 
@@ -82,6 +84,10 @@ module RocketJob
82
84
  end
83
85
  # @formatter:on
84
86
 
87
+ # By default all jobs are not pausable / resumable
88
+ class_attribute(:pausable)
89
+ self.pausable = false
90
+
85
91
  # Define a before and after callback method for each event
86
92
  state_machine_define_event_callbacks(*aasm.state_machine.events.keys)
87
93
 
@@ -16,10 +16,11 @@ module RocketJob
16
16
  # end
17
17
  #
18
18
  # Notes:
19
- # - The actual number will be around this value, it can go over slightly and
20
- # can drop depending on check interval can drop slightly below this value.
21
- # - By avoiding hard locks and counters performance can be maintained while still
22
- # supporting good enough quantity throttling.
19
+ # - The number of running jobs will not exceed this value.
20
+ # - It may appear that a job is running briefly over this limit, but then is immediately back into queued state.
21
+ # This is expected behavior and is part of the check to ensure this value is not exceeded.
22
+ # The worker grabs the job and only then verifies the throttle, this is to prevent any other worker
23
+ # from attempting to grab the job, which would have exceeded the throttle.
23
24
  module ThrottleRunningJobs
24
25
  extend ActiveSupport::Concern
25
26
 
@@ -37,7 +38,8 @@ module RocketJob
37
38
  def throttle_running_jobs_exceeded?
38
39
  throttle_running_jobs &&
39
40
  (throttle_running_jobs != 0) &&
40
- (self.class.running.where(:id.ne => id).count >= throttle_running_jobs)
41
+ # Cannot use class since it will include instances of parent job classes.
42
+ (RocketJob::Job.running.where('_type' => self.class.name, :id.ne => id).count >= throttle_running_jobs)
41
43
  end
42
44
  end
43
45
  end
@@ -2,7 +2,7 @@ require 'active_support/concern'
2
2
 
3
3
  module RocketJob
4
4
  module Plugins
5
- # Perform under a single Active Record transaction / unit or work.
5
+ # Wraps every #perform call with an Active Record transaction / unit or work.
6
6
  #
7
7
  # If the perform raises an exception it will cause any database changes to be rolled back.
8
8
  #
@@ -23,14 +23,19 @@ module RocketJob
23
23
  # Audit.create!(table: 'user', description: 'Changed age to 21')
24
24
  # end
25
25
  # end
26
+ #
27
+ # Performance
28
+ # - On Ruby (MRI) an empty transaction block call takes about 1ms.
29
+ # - On JRuby an empty transaction block call takes about 55ms.
30
+ #
31
+ # Note:
32
+ # - This plugin will only be activated if ActiveRecord has been loaded first.
26
33
  module Transaction
27
34
  extend ActiveSupport::Concern
28
35
 
29
36
  included do
30
- if respond_to?(:around_slice)
31
- around_slice :rocket_job_transaction
32
- else
33
- around_perform :rocket_job_transaction
37
+ if defined?(ActiveRecord::Base)
38
+ respond_to?(:around_slice) ? around_slice(:rocket_job_transaction) : around_perform(:rocket_job_transaction)
34
39
  end
35
40
  end
36
41
 
@@ -1,3 +1,3 @@
1
1
  module RocketJob #:nodoc
2
- VERSION = '3.3.0'
2
+ VERSION = '3.3.1'
3
3
  end
data/lib/rocketjob.rb CHANGED
@@ -40,6 +40,7 @@ module RocketJob
40
40
  autoload :Restart, 'rocket_job/plugins/restart'
41
41
  autoload :Singleton, 'rocket_job/plugins/singleton'
42
42
  autoload :StateMachine, 'rocket_job/plugins/state_machine'
43
+ autoload :Transaction, 'rocket_job/plugins/transaction'
43
44
  end
44
45
 
45
46
  module Jobs
@@ -0,0 +1,5 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: test/test_db.sqlite3
4
+ pool: 5
5
+ timeout: 5000
@@ -8,6 +8,7 @@ module Plugins
8
8
  class ThrottleJob < RocketJob::Job
9
9
  # Only allow one to be processed at a time
10
10
  self.throttle_running_jobs = 1
11
+ self.pausable = true
11
12
 
12
13
  def perform
13
14
  21
@@ -85,15 +86,15 @@ module Plugins
85
86
  ThrottleJob.create!(state: :failed)
86
87
  ThrottleJob.create!(state: :complete)
87
88
  ThrottleJob.create!(state: :paused)
88
- assert job = RocketJob::Job.rocket_job_next_job(@worker_name), -> { ThrottleJob.all.to_a.ai }
89
- assert_equal @job.id, job.id, -> { ThrottleJob.all.to_a.ai }
89
+ assert job = RocketJob::Job.rocket_job_next_job(@worker_name), -> {ThrottleJob.all.to_a.ai}
90
+ assert_equal @job.id, job.id, -> {ThrottleJob.all.to_a.ai}
90
91
  end
91
92
 
92
93
  it 'return nil when other jobs are running' do
93
94
  ThrottleJob.create!
94
95
  @job = ThrottleJob.new
95
96
  @job.start!
96
- assert_nil RocketJob::Job.rocket_job_next_job(@worker_name), -> { ThrottleJob.all.to_a.ai }
97
+ assert_nil RocketJob::Job.rocket_job_next_job(@worker_name), -> {ThrottleJob.all.to_a.ai}
97
98
  end
98
99
 
99
100
  it 'add job to filter when other jobs are running' do
@@ -101,7 +102,7 @@ module Plugins
101
102
  @job = ThrottleJob.new
102
103
  @job.start!
103
104
  filter = {}
104
- assert_nil RocketJob::Job.rocket_job_next_job(@worker_name, filter), -> { ThrottleJob.all.to_a.ai }
105
+ assert_nil RocketJob::Job.rocket_job_next_job(@worker_name, filter), -> {ThrottleJob.all.to_a.ai}
105
106
  assert_equal 1, filter.size
106
107
  end
107
108
  end
@@ -16,11 +16,26 @@ module Plugins
16
16
  end
17
17
  end
18
18
 
19
+ class RestartablePausableJob < RocketJob::Job
20
+ include RocketJob::Plugins::Restart
21
+
22
+ field :start_at, type: Date
23
+ field :end_at, type: Date
24
+
25
+ # Job will reload itself during process to check if it was paused.
26
+ self.pausable = true
27
+
28
+ def perform
29
+ self.start_at = Date.today
30
+ self.end_at = Date.today
31
+ 'DONE'
32
+ end
33
+ end
34
+
19
35
  describe RocketJob::Plugins::Restart do
20
36
  before do
21
- # destroy_all could create new instances
22
37
  RestartableJob.delete_all
23
- assert_equal 0, RestartableJob.count
38
+ RestartablePausableJob.delete_all
24
39
  end
25
40
 
26
41
  after do
@@ -89,12 +104,12 @@ module Plugins
89
104
  end
90
105
 
91
106
  describe '#pause' do
92
- it 'does not enqueues a new job when paused' do
93
- @job = RestartableJob.new
107
+ it 'does not enqueue a new job when paused' do
108
+ @job = RestartablePausableJob.new
94
109
  @job.start
95
110
  @job.pause!
96
111
  assert @job.paused?
97
- assert_equal 1, RestartableJob.count
112
+ assert_equal 1, RestartablePausableJob.count
98
113
  end
99
114
  end
100
115
 
@@ -115,7 +130,7 @@ module Plugins
115
130
  end
116
131
 
117
132
  it 'aborts from paused' do
118
- @job = RestartableJob.new
133
+ @job = RestartablePausableJob.new
119
134
  @job.start
120
135
  @job.pause
121
136
  assert @job.paused?
@@ -0,0 +1,86 @@
1
+ require_relative '../test_helper'
2
+ require 'active_record'
3
+
4
+ ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read('test/config/database.yml')).result)
5
+ ActiveRecord::Base.establish_connection(:test)
6
+
7
+ ActiveRecord::Schema.define version: 0 do
8
+ create_table :users, force: true do |t|
9
+ t.string :login
10
+ end
11
+ end
12
+
13
+ class User < ActiveRecord::Base
14
+ end
15
+
16
+ module Plugins
17
+ module Job
18
+ class TransactionTest < Minitest::Test
19
+
20
+ class CommitTransactionJob < RocketJob::Job
21
+ # Wrap perform with a transaction, so that it is rolled back on exception.
22
+ include RocketJob::Plugins::Transaction
23
+
24
+ field :login, type: String
25
+
26
+ def perform
27
+ User.create!(login: login)
28
+ end
29
+ end
30
+
31
+ class RollbackTransactionJob < RocketJob::Job
32
+ # Wrap perform with a transaction, so that it is rolled back on exception.
33
+ include RocketJob::Plugins::Transaction
34
+
35
+ field :login, type: String
36
+
37
+ def perform
38
+ User.create!(login: login)
39
+ raise "This must fail and rollback the transaction"
40
+ end
41
+ end
42
+
43
+ describe RocketJob::Plugins::Job::Logger do
44
+ before do
45
+ User.delete_all
46
+ CommitTransactionJob.delete_all
47
+ RollbackTransactionJob.delete_all
48
+ end
49
+
50
+ after do
51
+ @job.destroy if @job && !@job.new_record?
52
+ end
53
+
54
+ describe '#rocket_job_transaction' do
55
+ it 'is registered' do
56
+ assert CommitTransactionJob.send(:get_callbacks, :perform).find {|c| c.filter == :rocket_job_transaction}
57
+ assert RollbackTransactionJob.send(:get_callbacks, :perform).find {|c| c.filter == :rocket_job_transaction}
58
+ refute RocketJob::Job.send(:get_callbacks, :perform).find {|c| c.filter == :rocket_job_transaction}
59
+ end
60
+ end
61
+
62
+ describe '#perform' do
63
+ it 'commits on success' do
64
+ assert_equal 0, User.count
65
+ job = CommitTransactionJob.new(login: 'Success')
66
+ job.perform_now
67
+ assert job.completed?
68
+ assert_equal 1, User.count
69
+ assert_equal 'Success', User.first.login
70
+ end
71
+
72
+ it 'rolls back on exception' do
73
+ assert_equal 0, User.count
74
+ job = RollbackTransactionJob.new(login: 'Bad')
75
+ assert_raises RuntimeError do
76
+ job.perform_now
77
+ end
78
+ assert job.failed?
79
+ assert_equal 0, User.count
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+ end
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rocketjob
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 3.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-19 00:00:00.000000000 Z
11
+ date: 2017-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: semantic_logger
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '3.1'
47
+ version: '4.1'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '3.1'
54
+ version: '4.1'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: aasm
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -77,7 +77,6 @@ extra_rdoc_files: []
77
77
  files:
78
78
  - LICENSE.txt
79
79
  - README.md
80
- - Rakefile
81
80
  - bin/rocketjob
82
81
  - bin/rocketjob_perf
83
82
  - lib/rocket_job/active_worker.rb
@@ -117,6 +116,7 @@ files:
117
116
  - lib/rocket_job/version.rb
118
117
  - lib/rocket_job/worker.rb
119
118
  - lib/rocketjob.rb
119
+ - test/config/database.yml
120
120
  - test/config/mongoid.yml
121
121
  - test/config_test.rb
122
122
  - test/dirmon_entry_test.rb
@@ -138,6 +138,8 @@ files:
138
138
  - test/plugins/singleton_test.rb
139
139
  - test/plugins/state_machine_event_callbacks_test.rb
140
140
  - test/plugins/state_machine_test.rb
141
+ - test/plugins/transaction_test.rb
142
+ - test/test_db.sqlite3
141
143
  - test/test_helper.rb
142
144
  homepage: http://rocketjob.io
143
145
  licenses:
@@ -164,6 +166,7 @@ signing_key:
164
166
  specification_version: 4
165
167
  summary: Ruby's missing batch system.
166
168
  test_files:
169
+ - test/config/database.yml
167
170
  - test/config/mongoid.yml
168
171
  - test/config_test.rb
169
172
  - test/dirmon_entry_test.rb
@@ -185,4 +188,6 @@ test_files:
185
188
  - test/plugins/singleton_test.rb
186
189
  - test/plugins/state_machine_event_callbacks_test.rb
187
190
  - test/plugins/state_machine_test.rb
191
+ - test/plugins/transaction_test.rb
192
+ - test/test_db.sqlite3
188
193
  - test/test_helper.rb
data/Rakefile DELETED
@@ -1,31 +0,0 @@
1
- # Setup bundler to avoid having to run bundle exec all the time.
2
- require 'rubygems'
3
- require 'bundler/setup'
4
-
5
- require 'rake/testtask'
6
- require_relative 'lib/rocket_job/version'
7
-
8
- task :gem do
9
- system 'gem build rocketjob.gemspec'
10
- end
11
-
12
- task publish: :gem do
13
- system "git tag -a v#{RocketJob::VERSION} -m 'Tagging #{RocketJob::VERSION}'"
14
- system 'git push --tags'
15
- system "gem push rocketjob-#{RocketJob::VERSION}.gem"
16
- system "rm rocketjob-#{RocketJob::VERSION}.gem"
17
- end
18
-
19
- Rake::TestTask.new(:test) do |t|
20
- t.pattern = 'test/**/*_test.rb'
21
- t.verbose = true
22
- t.warning = false
23
- end
24
-
25
- # By default run tests against all appraisals
26
- if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
27
- require 'appraisal'
28
- task default: :appraisal
29
- else
30
- task default: :test
31
- end