rocketjob 3.0.5 → 3.1.0

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: 61ffcbdf9443ca63dc699d732c9cc5c228b3505a
4
- data.tar.gz: cbecb1f0d5df94328fa4190847aa8422211294a4
3
+ metadata.gz: 90895c69510bfbacc92d44ee5145106c5ab07dac
4
+ data.tar.gz: 68297e81bd9d77e56c224443558c4db24c0ba701
5
5
  SHA512:
6
- metadata.gz: 5e6de043e3a10b052855dfc5db392501bfad718242eecd1ef78110400e94d9fd571a88928877d613da0d520dbb984ed59cf61c66df18ce041fc85cd4028f135a
7
- data.tar.gz: b1c2c436df6ceb73176a7cb65a343ab0a3c4b682099043270a8cc9409b79e272e029d2bc1b27b8e987629976030b929c3b3863bdf8a751e488c826d3ff5ec1c5
6
+ metadata.gz: 2bcd1137b8bdfa9fd2627c1a1d7f94bd051b9cf1868b159c5c2024932d4f12bb33d9e5151fcdf07599fc992b547717fc1b20bb1fc3a5a89b57b8d8c7b349b0b6
7
+ data.tar.gz: 07ebf671c8d45cf9e255cb079a1b31e2c26810fc4a94bcde1758d4b51b9e41ff8760e94e0f2a473c94d471a3723fb0e3a19ec65045ced24aa4c09c476a767263
@@ -15,9 +15,7 @@ module RocketJob
15
15
  end
16
16
  end
17
17
 
18
- # Useful for Testing, not recommended elsewhere.
19
- # When enabled all calls to `perform_later` will be redirected to `perform_now`.
20
- # Also, exceptions will be raised instead of failing the job.
18
+ # DEPRECATED
21
19
  cattr_accessor(:inline_mode) { false }
22
20
 
23
21
  store_in collection: 'rocket_job.configs'
@@ -11,5 +11,6 @@ module RocketJob
11
11
  include Plugins::Job::StateMachine
12
12
  include Plugins::Job::Worker
13
13
  include Plugins::Job::Throttle
14
+ include Plugins::Job::ThrottleRunningJobs
14
15
  end
15
16
  end
@@ -3,71 +3,96 @@ require 'active_support/concern'
3
3
  module RocketJob
4
4
  module Plugins
5
5
  module Job
6
- # Throttle number of jobs of a specific class that are processed at the same time.
6
+ # Rocket Job Throttling Framework.
7
7
  #
8
8
  # Example:
9
+ # # Do not run this job when the MySQL slave delay exceeds 5 minutes.
9
10
  # class MyJob < RocketJob
10
- # # Maximum number of workers to process instances of this job at the same time.
11
- # self.throttle_running_jobs = 25
11
+ # # Define a custom mysql throttle
12
+ # # Prevents all jobs of this class from running on the current server.
13
+ # define_throttle :mysql_throttle_exceeded?
12
14
  #
13
15
  # def perform
14
16
  # # ....
15
17
  # end
16
- # end
17
- #
18
- # Notes:
19
- # - The actual number will be around this value, it con go over slightly and
20
- # can drop depending on check interval can drop slightly below this value.
21
- # - By avoid hard locks and counters performance can be maintained while still
22
- # supporting good enough throttling.
23
- # - If throughput is not as important as preventing brief spikes when many
24
- # workers are running, add a double check into the perform:
25
- # class MyJob < RocketJob
26
- # self.throttle_running_jobs = 25
27
18
  #
28
- # def perform
29
- # # (Optional) Prevent a brief spike from exceeding the wax worker throttle
30
- # self.class.throttle_double_check
19
+ # private
31
20
  #
32
- # # ....
33
- # end
21
+ # # Returns true if the MySQL slave delay exceeds 5 minutes
22
+ # def mysql_throttle_exceeded?
23
+ # status = ActiveRecord::Base.connection.select_one('show slave status')
24
+ # seconds_delay = Hash(status)['Seconds_Behind_Master'].to_i
25
+ # seconds_delay >= 300
34
26
  # end
27
+ # end
35
28
  module Throttle
36
29
  extend ActiveSupport::Concern
37
30
 
38
31
  included do
39
- class_attribute :throttle_running_jobs
40
- self.throttle_running_jobs = nil
32
+ class_attribute :rocket_job_throttles
33
+ self.rocket_job_throttles = []
41
34
  end
42
35
 
43
- # Throttle to add when the throttle is exceeded
44
- def throttle_filter
45
- {:_type.nin => [self.class.name]}
36
+ module ClassMethods
37
+ # Add a new throttle.
38
+ #
39
+ # Parameters:
40
+ # method_name: [Symbol]
41
+ # Name of method to call to evaluate whether a throttle has been exceeded.
42
+ # Note: Must return true or false.
43
+ # filter: [Symbol|Proc]
44
+ # Name of method to call to return the filter when the throttle has been exceeded.
45
+ # Or, a block that will return the filter.
46
+ # Default: :throttle_filter_class (Throttle all jobs of this class)
47
+ #
48
+ # Note: Throttles are executed in the order they are defined.
49
+ def define_throttle(method_name, filter: :throttle_filter_class)
50
+ raise(ArgumentError, "Filter for #{method_name} must be a Symbol or Proc") unless filter.is_a?(Symbol) || filter.is_a?(Proc)
51
+ raise(ArgumentError, "Cannot define #{method_name} twice, undefine previous throttle first") if has_throttle?(method_name)
52
+
53
+ self.rocket_job_throttles += [ThrottleDefinition.new(method_name, filter)]
54
+ end
55
+
56
+ # Undefine a previously defined throttle
57
+ def undefine_throttle(method_name)
58
+ rocket_job_throttles.delete_if { |throttle| throttle.method_name }
59
+ end
60
+
61
+ # Has a throttle been defined?
62
+ def has_throttle?(method_name)
63
+ rocket_job_throttles.find { |throttle| throttle.method_name == method_name }
64
+ end
46
65
  end
47
66
 
48
- # Returns [Boolean] whether the throttle for this job has been exceeded
49
- def throttle_exceeded?
50
- throttle_running_jobs && (throttle_running_jobs != 0) ? (self.class.running.where(:id.ne => id).count >= throttle_running_jobs) : false
67
+ # Default throttle to use when the throttle is exceeded.
68
+ # When the throttle has been exceeded all jobs of this class will be ignored until the
69
+ # next refresh. `RocketJob::Config::re_check_seconds` which by default is 60 seconds.
70
+ def throttle_filter_class
71
+ {:_type.nin => [self.class.name]}
51
72
  end
52
73
 
53
- # Prevent a brief spike from exceeding the wax worker throttle
54
- def throttle_double_check(check_seconds = 1)
55
- while !throttle_exceeded?
56
- sleep check_seconds
57
- end
74
+ # Filter out only this instance of the job.
75
+ # When the throttle has been exceeded this job will be ignored by this server until the next refresh.
76
+ # `RocketJob::Config::re_check_seconds` which by default is 60 seconds.
77
+ def throttle_filter_id
78
+ {:id.nin => [id]}
58
79
  end
59
80
 
60
- # Merge filter(s)
61
- def throttle_merge_filter(target, source)
62
- source.each_pair do |k, v|
63
- target[k] =
64
- if previous = target[k]
65
- v.is_a?(Array) ? previous + v : v
66
- else
67
- v
68
- end
81
+ private
82
+
83
+ ThrottleDefinition = Struct.new(:method_name, :filter)
84
+
85
+ # Returns the matching filter, or nil if no throttles were triggered.
86
+ def rocket_job_evaluate_throttles
87
+ rocket_job_throttles.each do |throttle|
88
+ # Throttle exceeded?
89
+ if send(throttle.method_name)
90
+ logger.debug { "Throttle: #{throttle.method_name} has been exceeded. #{self.class.name}:#{id}" }
91
+ filter = throttle.filter
92
+ return filter.is_a?(Proc) ? filter.call(self) : send(filter)
93
+ end
69
94
  end
70
- target
95
+ nil
71
96
  end
72
97
 
73
98
  end
@@ -0,0 +1,45 @@
1
+ require 'active_support/concern'
2
+
3
+ module RocketJob
4
+ module Plugins
5
+ module Job
6
+ # Throttle the number of jobs of a specific class that are processed at the same time.
7
+ #
8
+ # Example:
9
+ # class MyJob < RocketJob
10
+ # # Maximum number of jobs of this class to process at the same time.
11
+ # self.throttle_running_jobs = 25
12
+ #
13
+ # def perform
14
+ # # ....
15
+ # end
16
+ # end
17
+ #
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.
23
+ module ThrottleRunningJobs
24
+ extend ActiveSupport::Concern
25
+
26
+ included do
27
+ # Limit number of jobs running of this class.
28
+ class_attribute :throttle_running_jobs
29
+ self.throttle_running_jobs = nil
30
+
31
+ define_throttle :throttle_running_jobs_exceeded?
32
+ end
33
+
34
+ private
35
+
36
+ # Returns [Boolean] whether the throttle for this job has been exceeded
37
+ def throttle_running_jobs_exceeded?
38
+ throttle_running_jobs &&
39
+ (throttle_running_jobs != 0) &&
40
+ (self.class.running.where(:id.ne => id).count >= throttle_running_jobs)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -8,25 +8,14 @@ module RocketJob
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  module ClassMethods
11
- # Run this job later
12
- #
13
- # Saves it to the database for processing later by workers
14
- def perform_later(args, &block)
15
- if RocketJob::Config.inline_mode
16
- perform_now(args, &block)
17
- else
18
- job = new(args)
19
- block.call(job) if block
20
- job.save!
21
- job
22
- end
23
- end
24
-
25
11
  # Run this job now.
26
12
  #
27
13
  # The job is not saved to the database since it is processed entriely in memory
28
14
  # As a result before_save and before_destroy callbacks will not be called.
29
15
  # Validations are still called however prior to calling #perform
16
+ #
17
+ # Note:
18
+ # - Only batch throttles are checked when perform_now is called.
30
19
  def perform_now(args, &block)
31
20
  job = new(args)
32
21
  block.call(job) if block
@@ -56,10 +45,8 @@ module RocketJob
56
45
  when job.expired?
57
46
  job.rocket_job_fail_on_exception!(worker_name) { job.destroy }
58
47
  logger.info "Destroyed expired job #{job.class.name}, id:#{job.id}"
59
- when job.throttle_exceeded?
60
- logger.debug { "Throttle exceeded with job #{job.class.name}, id:#{job.id}" }
61
- # Add jobs filter to the current filter
62
- job.throttle_merge_filter(filter, job.throttle_filter)
48
+ when new_filter = job.send(:rocket_job_evaluate_throttles)
49
+ rocket_job_merge_filter(filter, new_filter)
63
50
  # Restore retrieved job so that other workers can process it later
64
51
  job.set(worker_name: nil, state: :queued)
65
52
  else
@@ -79,6 +66,32 @@ module RocketJob
79
66
  job.requeue!(server_name) if job.may_requeue?(server_name)
80
67
  end
81
68
  end
69
+
70
+ # DEPRECATED
71
+ def perform_later(args, &block)
72
+ if RocketJob::Config.inline_mode
73
+ perform_now(args, &block)
74
+ else
75
+ job = new(args)
76
+ block.call(job) if block
77
+ job.save!
78
+ job
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ def rocket_job_merge_filter(target, source)
85
+ source.each_pair do |k, v|
86
+ target[k] =
87
+ if previous = target[k]
88
+ v.is_a?(Array) ? previous + v : v
89
+ else
90
+ v
91
+ end
92
+ end
93
+ target
94
+ end
82
95
  end
83
96
 
84
97
  # Runs the job now in the current thread.
@@ -1,7 +1,6 @@
1
1
  require 'thread'
2
2
  require 'active_support/concern'
3
3
  require 'aasm'
4
- require 'rocket_job/extensions/aasm'
5
4
 
6
5
  module RocketJob
7
6
  module Plugins
@@ -28,14 +27,9 @@ module RocketJob
28
27
  # end
29
28
  module StateMachine
30
29
  extend ActiveSupport::Concern
31
- @@aasm_mutex = Mutex.new
32
30
 
33
31
  included do
34
32
  include AASM
35
- # Try to make aasm lookup thread safe
36
- @aasm = Concurrent::Map.new
37
- # Call aasm to create default instance upfront
38
- aasm
39
33
 
40
34
  # Adds a :before or :after callback to an event
41
35
  # state_machine_add_event_callback(:start, :before, :my_method)
@@ -43,30 +37,28 @@ module RocketJob
43
37
  raise(ArgumentError, 'Cannot supply both a method name and a block') if (methods.size > 0) && block
44
38
  raise(ArgumentError, 'Must supply either a method name or a block') unless (methods.size > 0) || block
45
39
 
46
- @@aasm_mutex.synchronize do
47
- # TODO Somehow get AASM to support options such as :if and :unless to be consistent with other callbacks
48
- # For example:
49
- # before_start :my_callback, unless: :encrypted?
50
- # before_start :my_callback, if: :encrypted?
51
- if event = aasm.state_machine.events[event_name]
52
- values = Array(event.options[action])
53
- code =
54
- if block
55
- block
56
- else
57
- # Validate methods are any of Symbol String Proc
58
- methods.each do |method|
59
- unless method.is_a?(Symbol) || method.is_a?(String)
60
- raise(ArgumentError, "#{action}_#{event_name} currently does not support any options. Only Symbol and String method names can be supplied.")
61
- end
40
+ # TODO Somehow get AASM to support options such as :if and :unless to be consistent with other callbacks
41
+ # For example:
42
+ # before_start :my_callback, unless: :encrypted?
43
+ # before_start :my_callback, if: :encrypted?
44
+ if event = aasm.state_machine.events[event_name]
45
+ values = Array(event.options[action])
46
+ code =
47
+ if block
48
+ block
49
+ else
50
+ # Validate methods are any of Symbol String Proc
51
+ methods.each do |method|
52
+ unless method.is_a?(Symbol) || method.is_a?(String)
53
+ raise(ArgumentError, "#{action}_#{event_name} currently does not support any options. Only Symbol and String method names can be supplied.")
62
54
  end
63
- methods
64
55
  end
65
- action == :before ? values.push(code) : values.unshift(code)
66
- event.options[action] = values.flatten.uniq
67
- else
68
- raise(ArgumentError, "Unknown event: #{event_name.inspect}")
69
- end
56
+ methods
57
+ end
58
+ action == :before ? values.push(code) : values.unshift(code)
59
+ event.options[action] = values.flatten.uniq
60
+ else
61
+ raise(ArgumentError, "Unknown event: #{event_name.inspect}")
70
62
  end
71
63
  end
72
64
 
@@ -84,21 +76,6 @@ module RocketJob
84
76
  end
85
77
  end
86
78
 
87
- # Patch AASM so that save! is called instead of save
88
- # So that validations are run before job.requeue! is completed
89
- # Otherwise it just fails silently
90
- def aasm_write_state(state, name=:default)
91
- attr_name = self.class.aasm(name).attribute_name
92
- old_value = read_attribute(attr_name)
93
- write_attribute(attr_name, state)
94
-
95
- begin
96
- save!
97
- rescue Exception => exc
98
- write_attribute(attr_name, old_value)
99
- raise(exc)
100
- end
101
- end
102
79
  end
103
80
 
104
81
  end
@@ -1,3 +1,3 @@
1
1
  module RocketJob #:nodoc
2
- VERSION = '3.0.5'
2
+ VERSION = '3.1.0'
3
3
  end
@@ -18,14 +18,15 @@ module RocketJob
18
18
 
19
19
  module Plugins
20
20
  module Job
21
- autoload :Callbacks, 'rocket_job/plugins/job/callbacks'
22
- autoload :Defaults, 'rocket_job/plugins/job/defaults'
23
- autoload :StateMachine, 'rocket_job/plugins/job/state_machine'
24
- autoload :Logger, 'rocket_job/plugins/job/logger'
25
- autoload :Model, 'rocket_job/plugins/job/model'
26
- autoload :Persistence, 'rocket_job/plugins/job/persistence'
27
- autoload :Throttle, 'rocket_job/plugins/job/throttle'
28
- autoload :Worker, 'rocket_job/plugins/job/worker'
21
+ autoload :Callbacks, 'rocket_job/plugins/job/callbacks'
22
+ autoload :Defaults, 'rocket_job/plugins/job/defaults'
23
+ autoload :StateMachine, 'rocket_job/plugins/job/state_machine'
24
+ autoload :Logger, 'rocket_job/plugins/job/logger'
25
+ autoload :Model, 'rocket_job/plugins/job/model'
26
+ autoload :Persistence, 'rocket_job/plugins/job/persistence'
27
+ autoload :Throttle, 'rocket_job/plugins/job/throttle'
28
+ autoload :ThrottleRunningJobs, 'rocket_job/plugins/job/throttle_running_jobs'
29
+ autoload :Worker, 'rocket_job/plugins/job/worker'
29
30
  end
30
31
  module Rufus
31
32
  autoload :CronLine, 'rocket_job/plugins/rufus/cron_line'
@@ -19,18 +19,35 @@ module Plugins
19
19
  RocketJob::Job.delete_all
20
20
  end
21
21
 
22
- describe '#throttle_exceeded?' do
22
+ describe '.has_throttle?' do
23
+ it 'defines the running jobs throttle' do
24
+ assert ThrottleJob.has_throttle?(:throttle_running_jobs_exceeded?), ThrottleJob.rocket_job_throttles
25
+ refute ThrottleJob.has_throttle?(:blah?), ThrottleJob.rocket_job_throttles
26
+ end
27
+ end
28
+
29
+ describe '.undefine_throttle' do
30
+ it 'undefines the running jobs throttle' do
31
+ assert ThrottleJob.has_throttle?(:throttle_running_jobs_exceeded?), ThrottleJob.rocket_job_throttles
32
+ ThrottleJob.undefine_throttle(:throttle_running_jobs_exceeded?)
33
+ refute ThrottleJob.has_throttle?(:throttle_running_jobs_exceeded?), ThrottleJob.rocket_job_throttles
34
+ ThrottleJob.define_throttle(:throttle_running_jobs_exceeded?)
35
+ assert ThrottleJob.has_throttle?(:throttle_running_jobs_exceeded?), ThrottleJob.rocket_job_throttles
36
+ end
37
+ end
38
+
39
+ describe '#throttle_running_jobs_exceeded??' do
23
40
  it 'does not exceed throttle when no other jobs are running' do
24
41
  ThrottleJob.create!
25
42
  job = ThrottleJob.new
26
- refute job.throttle_exceeded?
43
+ refute job.send(:throttle_running_jobs_exceeded?)
27
44
  end
28
45
 
29
46
  it 'exceeds throttle when other jobs are running' do
30
47
  job1 = ThrottleJob.new
31
48
  job1.start!
32
49
  job2 = ThrottleJob.new
33
- assert job2.throttle_exceeded?
50
+ assert job2.send(:throttle_running_jobs_exceeded?)
34
51
  end
35
52
 
36
53
  it 'excludes paused jobs' do
@@ -38,7 +55,7 @@ module Plugins
38
55
  job1.start
39
56
  job1.pause!
40
57
  job2 = ThrottleJob.new
41
- refute job2.throttle_exceeded?
58
+ refute job2.send(:throttle_running_jobs_exceeded?)
42
59
  end
43
60
 
44
61
  it 'excludes failed jobs' do
@@ -46,7 +63,7 @@ module Plugins
46
63
  job1.start
47
64
  job1.fail!
48
65
  job2 = ThrottleJob.new
49
- refute job2.throttle_exceeded?
66
+ refute job2.send(:throttle_running_jobs_exceeded?)
50
67
  end
51
68
  end
52
69
 
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.0.5
4
+ version: 3.1.0
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-03-16 00:00:00.000000000 Z
11
+ date: 2017-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '4.3'
61
+ version: '4.12'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '4.3'
68
+ version: '4.12'
69
69
  description:
70
70
  email:
71
71
  - support@rocketjob.io
@@ -84,7 +84,6 @@ files:
84
84
  - lib/rocket_job/cli.rb
85
85
  - lib/rocket_job/config.rb
86
86
  - lib/rocket_job/dirmon_entry.rb
87
- - lib/rocket_job/extensions/aasm.rb
88
87
  - lib/rocket_job/extensions/mongo/logging.rb
89
88
  - lib/rocket_job/extensions/rocket_job_adapter.rb
90
89
  - lib/rocket_job/heartbeat.rb
@@ -102,6 +101,7 @@ files:
102
101
  - lib/rocket_job/plugins/job/persistence.rb
103
102
  - lib/rocket_job/plugins/job/state_machine.rb
104
103
  - lib/rocket_job/plugins/job/throttle.rb
104
+ - lib/rocket_job/plugins/job/throttle_running_jobs.rb
105
105
  - lib/rocket_job/plugins/job/worker.rb
106
106
  - lib/rocket_job/plugins/processing_window.rb
107
107
  - lib/rocket_job/plugins/restart.rb
@@ -1,72 +0,0 @@
1
- require 'aasm'
2
-
3
- # The following patches can be removed once the following PR has been merged into AASM:
4
- # https://github.com/aasm/aasm/pull/269
5
-
6
- AASM::Core::Event
7
- module AASM::Core
8
- class Event
9
- def initialize_copy(orig)
10
- super
11
- @transitions = @transitions.collect { |transition| transition.clone }
12
- @guards = @guards.dup
13
- @unless = @unless.dup
14
- @options = {}
15
- orig.options.each_pair { |name, setting| @options[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
16
- end
17
- end
18
- end
19
-
20
- AASM::Core::State
21
- module AASM::Core
22
- class State
23
- # called internally by Ruby 1.9 after clone()
24
- def initialize_copy(orig)
25
- super
26
- @options = {}
27
- orig.options.each_pair { |name, setting| @options[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
28
- end
29
- end
30
- end
31
-
32
- AASM::Core::Transition
33
- module AASM::Core
34
- class Transition
35
- def initialize_copy(orig)
36
- super
37
- @guards = @guards.dup
38
- @unless = @unless.dup
39
- @opts = {}
40
- orig.opts.each_pair { |name, setting| @opts[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
41
- end
42
- end
43
- end
44
-
45
- AASM::StateMachine
46
- module AASM
47
- class StateMachine
48
- def initialize_copy(orig)
49
- super
50
- @states = orig.states.collect { |state| state.clone }
51
- @events = {}
52
- orig.events.each_pair { |name, event| @events[name] = event.clone }
53
- @global_callbacks = @global_callbacks.dup
54
- end
55
- end
56
- end
57
-
58
- # Patch to try and make AASM threadsafe
59
- AASM::StateMachineStore
60
- module AASM
61
- class StateMachineStore
62
- @stores = Concurrent::Map.new
63
-
64
- def self.stores
65
- @stores
66
- end
67
-
68
- def initialize
69
- @machines = Concurrent::Map.new
70
- end
71
- end
72
- end