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 +4 -4
- data/lib/rocket_job/dirmon_entry.rb +9 -11
- data/lib/rocket_job/plugins/job/model.rb +1 -1
- data/lib/rocket_job/plugins/job/state_machine.rb +8 -2
- data/lib/rocket_job/plugins/job/throttle_running_jobs.rb +7 -5
- data/lib/rocket_job/plugins/transaction.rb +10 -5
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocketjob.rb +1 -0
- data/test/config/database.yml +5 -0
- data/test/plugins/job/throttle_test.rb +5 -4
- data/test/plugins/restart_test.rb +21 -6
- data/test/plugins/transaction_test.rb +86 -0
- data/test/test_db.sqlite3 +0 -0
- metadata +12 -7
- data/Rakefile +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73879d59ee0d46f407f77acfc5b443b693e16727
|
4
|
+
data.tar.gz: 27d8a5a990b39d19c00ff086d57b3df2263eb727
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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? {
|
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,
|
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
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
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
|
|
data/lib/rocket_job/version.rb
CHANGED
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
|
@@ -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), -> {
|
89
|
-
assert_equal @job.id, job.id, -> {
|
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), -> {
|
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), -> {
|
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
|
-
|
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
|
93
|
-
@job =
|
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,
|
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 =
|
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.
|
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-
|
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: '
|
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: '
|
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
|