rocketjob 3.3.0 → 3.3.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
  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