rocketjob 3.0.5 → 3.1.0

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: 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