rocketjob 6.3.2 → 7.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 +4 -4
- data/README.md +38 -316
- data/bin/rocketjob_batch_perf +1 -1
- data/lib/rocket_job/batch/categories.rb +39 -26
- data/lib/rocket_job/batch/io.rb +53 -22
- data/lib/rocket_job/batch/logger.rb +2 -2
- data/lib/rocket_job/batch/model.rb +2 -2
- data/lib/rocket_job/batch/performance.rb +1 -1
- data/lib/rocket_job/batch/statistics.rb +9 -2
- data/lib/rocket_job/batch/throttle.rb +7 -2
- data/lib/rocket_job/batch/throttle_running_workers.rb +5 -1
- data/lib/rocket_job/batch/throttle_windows.rb +4 -2
- data/lib/rocket_job/batch/worker.rb +51 -11
- data/lib/rocket_job/category/base.rb +119 -1
- data/lib/rocket_job/category/input.rb +166 -40
- data/lib/rocket_job/category/output.rb +7 -0
- data/lib/rocket_job/config.rb +6 -1
- data/lib/rocket_job/deprecation.rb +51 -0
- data/lib/rocket_job/dirmon_entry.rb +6 -4
- data/lib/rocket_job/event.rb +84 -4
- data/lib/rocket_job/extensions/mongo/logging.rb +1 -0
- data/lib/rocket_job/extensions/mongoid/clients/options.rb +3 -3
- data/lib/rocket_job/extensions/mongoid/contextual/mongo.rb +3 -4
- data/lib/rocket_job/extensions/mongoid/factory.rb +2 -2
- data/lib/rocket_job/extensions/psych/yaml_tree.rb +8 -4
- data/lib/rocket_job/extensions/rocket_job_adapter.rb +1 -1
- data/lib/rocket_job/jobs/active_job.rb +1 -1
- data/lib/rocket_job/jobs/copy_file_job.rb +11 -1
- data/lib/rocket_job/jobs/dirmon_job.rb +3 -1
- data/lib/rocket_job/jobs/on_demand_batch_job.rb +1 -1
- data/lib/rocket_job/jobs/upload_file_job.rb +8 -5
- data/lib/rocket_job/lookup_collection.rb +2 -2
- data/lib/rocket_job/performance.rb +1 -1
- data/lib/rocket_job/plugins/cron.rb +6 -3
- data/lib/rocket_job/plugins/document.rb +0 -2
- data/lib/rocket_job/plugins/job/logger.rb +2 -2
- data/lib/rocket_job/plugins/job/model.rb +1 -1
- data/lib/rocket_job/plugins/job/persistence.rb +2 -2
- data/lib/rocket_job/plugins/job/throttle.rb +14 -2
- data/lib/rocket_job/plugins/job/throttle_running_jobs.rb +4 -1
- data/lib/rocket_job/plugins/job/worker.rb +5 -2
- data/lib/rocket_job/plugins/retry.rb +36 -35
- data/lib/rocket_job/plugins/throttle_dependent_jobs.rb +7 -2
- data/lib/rocket_job/plugins/transaction.rb +2 -2
- data/lib/rocket_job/sliced/input.rb +17 -12
- data/lib/rocket_job/sliced/slice.rb +3 -3
- data/lib/rocket_job/sliced/slices.rb +3 -6
- data/lib/rocket_job/sliced/writer/input.rb +4 -4
- data/lib/rocket_job/supervisor.rb +2 -2
- data/lib/rocket_job/thread_worker.rb +3 -3
- data/lib/rocket_job/throttle_definition.rb +19 -2
- data/lib/rocket_job/throttle_definitions.rb +14 -9
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocket_job/worker.rb +77 -28
- data/lib/rocketjob.rb +2 -2
- metadata +34 -27
- data/lib/rocket_job/ractor_worker.rb +0 -42
|
@@ -94,11 +94,12 @@ module RocketJob
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
VALID_INSTANCE_METHODS = %i[upload upload_file_name= full_file_name=].freeze
|
|
97
|
+
private_constant :VALID_INSTANCE_METHODS
|
|
97
98
|
|
|
98
99
|
# Validates job_class is a Rocket Job
|
|
99
100
|
def job_implements_upload
|
|
100
101
|
klass = job_class
|
|
101
|
-
return if klass.nil? || klass.instance_methods.
|
|
102
|
+
return if klass.nil? || klass.instance_methods.intersect?(VALID_INSTANCE_METHODS)
|
|
102
103
|
|
|
103
104
|
errors.add(:job_class_name,
|
|
104
105
|
"#{job_class} must implement any one of: :#{VALID_INSTANCE_METHODS.join(' :')} instance methods")
|
|
@@ -120,14 +121,16 @@ module RocketJob
|
|
|
120
121
|
klass = job_class
|
|
121
122
|
return unless klass
|
|
122
123
|
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
# Mongoid 9 returns Hash field keys as Strings, earlier versions as Symbols.
|
|
125
|
+
properties.each_pair do |raw_key, value|
|
|
126
|
+
k = raw_key.to_sym
|
|
127
|
+
next if klass.public_method_defined?(:"#{k}=")
|
|
125
128
|
|
|
126
129
|
if %i[output_categories input_categories].include?(k)
|
|
127
130
|
category_class = k == :input_categories ? RocketJob::Category::Input : RocketJob::Category::Output
|
|
128
|
-
|
|
131
|
+
value.each do |category|
|
|
129
132
|
category.each_pair do |key, _value|
|
|
130
|
-
next if category_class.public_method_defined?("#{key}="
|
|
133
|
+
next if category_class.public_method_defined?(:"#{key}=")
|
|
131
134
|
|
|
132
135
|
errors.add(
|
|
133
136
|
:properties,
|
|
@@ -12,8 +12,8 @@ module RocketJob
|
|
|
12
12
|
# end
|
|
13
13
|
#
|
|
14
14
|
# input_category(:my_lookup).find(id: 123).first
|
|
15
|
-
def upload(batch_size: 10_000, &
|
|
16
|
-
BatchUploader.upload(batch_size: batch_size, &
|
|
15
|
+
def upload(batch_size: 10_000, &)
|
|
16
|
+
BatchUploader.upload(batch_size: batch_size, &)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
# Looks up the value at the specified id.
|
|
@@ -18,7 +18,7 @@ module RocketJob
|
|
|
18
18
|
# Loads the queue with jobs to be processed once the queue is loaded.
|
|
19
19
|
# Retain the first and last job for timings, all others are destroyed on completion.
|
|
20
20
|
def run_test_case(count = self.count)
|
|
21
|
-
if RocketJob::Server.where(:state.in => %w[running paused]).
|
|
21
|
+
if RocketJob::Server.where(:state.in => %w[running paused]).none?
|
|
22
22
|
raise "Please start servers before starting the performance test"
|
|
23
23
|
end
|
|
24
24
|
|
|
@@ -18,7 +18,8 @@ module RocketJob
|
|
|
18
18
|
|
|
19
19
|
# Whether to prevent another instance of this job from running with the exact _same_ cron schedule.
|
|
20
20
|
# Another job instance with a different `cron_schedule` string is permitted.
|
|
21
|
-
field :cron_singleton, type: Mongoid::Boolean, default: true, class_attribute: true, user_editable: true,
|
|
21
|
+
field :cron_singleton, type: Mongoid::Boolean, default: true, class_attribute: true, user_editable: true,
|
|
22
|
+
copy_on_restart: true
|
|
22
23
|
|
|
23
24
|
# Whether to re-schedule the next job occurrence when this job starts, or when it is complete.
|
|
24
25
|
#
|
|
@@ -38,7 +39,8 @@ module RocketJob
|
|
|
38
39
|
# at any time until after the job has failed, or is aborted.
|
|
39
40
|
# - To prevent this job creating any new duplicate instances during subsequent processing,
|
|
40
41
|
# its `cron_schedule` is set to `nil` after it fails or is aborted.
|
|
41
|
-
field :cron_after_start, type: Mongoid::Boolean, default: true, class_attribute: true, user_editable: true,
|
|
42
|
+
field :cron_after_start, type: Mongoid::Boolean, default: true, class_attribute: true, user_editable: true,
|
|
43
|
+
copy_on_restart: true
|
|
42
44
|
|
|
43
45
|
validates_each :cron_schedule do |record, attr, value|
|
|
44
46
|
record.errors.add(attr, "Invalid cron_schedule: #{value.inspect}") if value && !Fugit::Cron.new(value)
|
|
@@ -88,7 +90,8 @@ module RocketJob
|
|
|
88
90
|
def rocket_job_cron_singleton_check
|
|
89
91
|
return if cron_schedule.nil? || completed? || aborted? || !rocket_job_cron_duplicate?
|
|
90
92
|
|
|
91
|
-
errors.add(:state,
|
|
93
|
+
errors.add(:state,
|
|
94
|
+
"Another instance of #{self.class.name} is already queued, running, failed, or paused with the same cron schedule: #{cron_schedule}")
|
|
92
95
|
end
|
|
93
96
|
end
|
|
94
97
|
end
|
|
@@ -11,7 +11,6 @@ module RocketJob
|
|
|
11
11
|
store_in client: "rocketjob"
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
# rubocop:disable Style/RedundantSort
|
|
15
14
|
module ClassMethods
|
|
16
15
|
# Mongoid does not apply ordering, add sort
|
|
17
16
|
def first
|
|
@@ -23,7 +22,6 @@ module RocketJob
|
|
|
23
22
|
all.sort("_id" => -1).first
|
|
24
23
|
end
|
|
25
24
|
end
|
|
26
|
-
# rubocop:enable Style/RedundantSort
|
|
27
25
|
|
|
28
26
|
private
|
|
29
27
|
|
|
@@ -17,7 +17,7 @@ module RocketJob
|
|
|
17
17
|
# - log_exception logs entire exception if raised
|
|
18
18
|
# - on_exception_level changes log level from info to error on exception
|
|
19
19
|
# - silence noisy jobs by raising log level
|
|
20
|
-
def rocket_job_around_logger(&
|
|
20
|
+
def rocket_job_around_logger(&)
|
|
21
21
|
logger.info("Start #perform")
|
|
22
22
|
logger.measure_info(
|
|
23
23
|
"Completed #perform",
|
|
@@ -25,7 +25,7 @@ module RocketJob
|
|
|
25
25
|
log_exception: :full,
|
|
26
26
|
on_exception_level: :error,
|
|
27
27
|
silence: log_level,
|
|
28
|
-
&
|
|
28
|
+
&
|
|
29
29
|
)
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -79,8 +79,8 @@ module RocketJob
|
|
|
79
79
|
return
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
-
job_attrs = self.class.rocket_job_restart_attributes.
|
|
83
|
-
|
|
82
|
+
job_attrs = self.class.rocket_job_restart_attributes.to_h do |attr|
|
|
83
|
+
[attr, send(attr)]
|
|
84
84
|
end
|
|
85
85
|
job_attrs.merge!(overrides)
|
|
86
86
|
|
|
@@ -30,6 +30,13 @@ module RocketJob
|
|
|
30
30
|
|
|
31
31
|
included do
|
|
32
32
|
class_attribute :rocket_job_throttles
|
|
33
|
+
|
|
34
|
+
# Human readable reason why this job is currently being throttled.
|
|
35
|
+
# Set when a throttle prevents the job from running, cleared when it starts.
|
|
36
|
+
# Surfaced in Rocket Job Mission Control.
|
|
37
|
+
field :throttled_by, type: String
|
|
38
|
+
# Time at which this job was last throttled.
|
|
39
|
+
field :throttled_at, type: Time
|
|
33
40
|
end
|
|
34
41
|
|
|
35
42
|
module ClassMethods
|
|
@@ -45,11 +52,16 @@ module RocketJob
|
|
|
45
52
|
# Or, a block that will return the filter.
|
|
46
53
|
# Default: :throttle_filter_class (Throttle all jobs of this class)
|
|
47
54
|
#
|
|
55
|
+
# description: [String|Proc]
|
|
56
|
+
# Human readable reason why the job is throttled, persisted to `throttled_by`
|
|
57
|
+
# and shown in Mission Control. A Proc is called with the job and must
|
|
58
|
+
# return a String. Default: a humanized version of the method name.
|
|
59
|
+
#
|
|
48
60
|
# Note: Throttles are executed in the order they are defined.
|
|
49
|
-
def define_throttle(method_name, filter: :throttle_filter_class)
|
|
61
|
+
def define_throttle(method_name, filter: :throttle_filter_class, description: nil)
|
|
50
62
|
# Duplicate to prevent modifying parent class throttles
|
|
51
63
|
definitions = rocket_job_throttles ? rocket_job_throttles.deep_dup : ThrottleDefinitions.new
|
|
52
|
-
definitions.add(method_name, filter)
|
|
64
|
+
definitions.add(method_name, filter, description)
|
|
53
65
|
self.rocket_job_throttles = definitions
|
|
54
66
|
end
|
|
55
67
|
|
|
@@ -32,7 +32,10 @@ module RocketJob
|
|
|
32
32
|
# Allow jobs to be throttled by group name instance of the job class name.
|
|
33
33
|
field :throttle_group, type: String, class_attribute: true, user_editable: true, copy_on_restart: true
|
|
34
34
|
|
|
35
|
-
define_throttle :throttle_running_jobs_exceeded
|
|
35
|
+
define_throttle :throttle_running_jobs_exceeded?,
|
|
36
|
+
description: lambda { |job, *|
|
|
37
|
+
"Throttled: maximum of #{job.throttle_running_jobs} running jobs reached"
|
|
38
|
+
}
|
|
36
39
|
end
|
|
37
40
|
|
|
38
41
|
private
|
|
@@ -66,10 +66,13 @@ module RocketJob
|
|
|
66
66
|
# re_raise_exceptions: [true|false]
|
|
67
67
|
# Re-raise the exception after updating the job
|
|
68
68
|
# Default: false
|
|
69
|
-
def fail_on_exception!(re_raise_exceptions = false, &
|
|
70
|
-
SemanticLogger.named_tagged(job: id.to_s, &
|
|
69
|
+
def fail_on_exception!(re_raise_exceptions = false, &)
|
|
70
|
+
SemanticLogger.named_tagged(job: id.to_s, &)
|
|
71
71
|
rescue Exception => e
|
|
72
72
|
SemanticLogger.named_tagged(job: id.to_s) do
|
|
73
|
+
# Not a guard clause: the `fail` event clears `worker_name`, so the
|
|
74
|
+
# branches are not equivalent and must not be collapsed.
|
|
75
|
+
# rubocop:disable-next Style/GuardClause
|
|
73
76
|
if failed? || !may_fail?
|
|
74
77
|
self.exception = JobException.from_exception(e)
|
|
75
78
|
exception.worker_name = worker_name
|
|
@@ -27,41 +27,42 @@ module RocketJob
|
|
|
27
27
|
# MyCronJob.create!(retry_limit: 0)
|
|
28
28
|
#
|
|
29
29
|
# Below is a table of the delay for each retry attempt, as well as the _total_ duration spent retrying a job for
|
|
30
|
-
# the specified number of retries, excluding actual processing time.
|
|
30
|
+
# the specified number of retries, excluding actual processing time. The delay before retry `n` is
|
|
31
|
+
# `(n - 1)**4 + 5` seconds.
|
|
31
32
|
#
|
|
32
33
|
# |---------|------------|----------------|
|
|
33
34
|
# | Attempt | Delay | Total Duration |
|
|
34
35
|
# |---------|------------|----------------|
|
|
35
|
-
# | 01 |
|
|
36
|
-
# | 02 |
|
|
37
|
-
# | 03 |
|
|
38
|
-
# | 04 |
|
|
39
|
-
# | 05 |
|
|
40
|
-
# | 06 |
|
|
41
|
-
# | 07 |
|
|
42
|
-
# | 08 |
|
|
43
|
-
# | 09 |
|
|
44
|
-
# | 10 |
|
|
45
|
-
# | 11 |
|
|
46
|
-
# | 12 |
|
|
47
|
-
# | 13 |
|
|
48
|
-
# | 14 |
|
|
49
|
-
# | 15 |
|
|
50
|
-
# | 16 |
|
|
51
|
-
# | 17 | 18h 12m |
|
|
52
|
-
# | 18 |
|
|
53
|
-
# | 19 |
|
|
54
|
-
# | 20 | 1d
|
|
55
|
-
# | 21 |
|
|
56
|
-
# | 22 |
|
|
57
|
-
# | 23 |
|
|
58
|
-
# | 24 | 3d
|
|
59
|
-
# | 25 |
|
|
60
|
-
# | 26 |
|
|
61
|
-
# | 27 |
|
|
62
|
-
# | 28 |
|
|
63
|
-
# | 29 |
|
|
64
|
-
# | 30 |
|
|
36
|
+
# | 01 | 5.000s | 5.000s |
|
|
37
|
+
# | 02 | 6.000s | 11.000s |
|
|
38
|
+
# | 03 | 21.000s | 32.000s |
|
|
39
|
+
# | 04 | 1m 26s | 1m 58s |
|
|
40
|
+
# | 05 | 4m 21s | 6m 19s |
|
|
41
|
+
# | 06 | 10m 30s | 16m 49s |
|
|
42
|
+
# | 07 | 21m 41s | 38m 30s |
|
|
43
|
+
# | 08 | 40m 6s | 1h 18m |
|
|
44
|
+
# | 09 | 1h 8m | 2h 26m |
|
|
45
|
+
# | 10 | 1h 49m | 4h 16m |
|
|
46
|
+
# | 11 | 2h 46m | 7h 3m |
|
|
47
|
+
# | 12 | 4h 4m | 11h 7m |
|
|
48
|
+
# | 13 | 5h 45m | 16h 52m |
|
|
49
|
+
# | 14 | 7h 56m | 1d 0h 49m |
|
|
50
|
+
# | 15 | 10h 40m | 1d 11h 29m |
|
|
51
|
+
# | 16 | 14h 3m | 2d 1h 33m |
|
|
52
|
+
# | 17 | 18h 12m | 2d 19h 45m |
|
|
53
|
+
# | 18 | 23h 12m | 3d 18h 57m |
|
|
54
|
+
# | 19 | 1d 5h 9m | 5d 0h 7m |
|
|
55
|
+
# | 20 | 1d 12h 12m | 6d 12h 19m |
|
|
56
|
+
# | 21 | 1d 20h 26m | 8d 8h 46m |
|
|
57
|
+
# | 22 | 2d 6h 1m | 10d 14h 47m |
|
|
58
|
+
# | 23 | 2d 17h 4m | 13d 7h 51m |
|
|
59
|
+
# | 24 | 3d 5h 44m | 16d 13h 36m |
|
|
60
|
+
# | 25 | 3d 20h 9m | 20d 9h 45m |
|
|
61
|
+
# | 26 | 4d 12h 30m | 24d 22h 16m |
|
|
62
|
+
# | 27 | 5d 6h 56m | 30d 5h 12m |
|
|
63
|
+
# | 28 | 6d 3h 37m | 36d 8h 50m |
|
|
64
|
+
# | 29 | 7d 2h 44m | 43d 11h 34m |
|
|
65
|
+
# | 30 | 8d 4h 28m | 51d 16h 2m |
|
|
65
66
|
# |---------|------------|----------------|
|
|
66
67
|
module Retry
|
|
67
68
|
extend ActiveSupport::Concern
|
|
@@ -70,7 +71,7 @@ module RocketJob
|
|
|
70
71
|
after_fail :rocket_job_retry
|
|
71
72
|
|
|
72
73
|
# Maximum number of times to retry this job.
|
|
73
|
-
# The default of 25
|
|
74
|
+
# The default of 25 retries spans about 20 days, see the table above.
|
|
74
75
|
field :retry_limit, type: Integer, default: 25, class_attribute: true, user_editable: true, copy_on_restart: true
|
|
75
76
|
|
|
76
77
|
# List of times when this job failed
|
|
@@ -115,7 +116,7 @@ module RocketJob
|
|
|
115
116
|
#
|
|
116
117
|
# For example, to see the durations for the first 25 retries:
|
|
117
118
|
# count = 25
|
|
118
|
-
# intervals = (
|
|
119
|
+
# intervals = (0...count).map { |failures| (failures**4) + 5 }
|
|
119
120
|
#
|
|
120
121
|
# Display the above intervals as human readable durations:
|
|
121
122
|
# intervals.map { |seconds| RocketJob.seconds_as_duration(seconds) }
|
|
@@ -124,9 +125,9 @@ module RocketJob
|
|
|
124
125
|
# RocketJob.seconds_as_duration(intervals.sum)
|
|
125
126
|
#
|
|
126
127
|
# Or, to see the total durations based on the number of retries:
|
|
127
|
-
# (0
|
|
128
|
+
# (0...count).map { |i| "#{i + 1} ==> #{RocketJob.seconds_as_duration(intervals[0..i].sum)}" }
|
|
128
129
|
def rocket_job_retry_seconds_to_delay
|
|
129
|
-
rocket_job_failure_count**4 + 5
|
|
130
|
+
(rocket_job_failure_count**4) + 5
|
|
130
131
|
end
|
|
131
132
|
end
|
|
132
133
|
end
|
|
@@ -13,8 +13,13 @@ module RocketJob
|
|
|
13
13
|
included do
|
|
14
14
|
field :dependent_jobs, type: Array, class_attribute: true, user_editable: true, copy_on_restart: true
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
dependent_jobs_description = lambda { |job, *|
|
|
17
|
+
"Throttled: dependent jobs are running (#{Array(job.dependent_jobs).join(', ')})"
|
|
18
|
+
}
|
|
19
|
+
define_throttle :dependent_jobs_running?, description: dependent_jobs_description
|
|
20
|
+
if respond_to?(:define_batch_throttle)
|
|
21
|
+
define_batch_throttle :dependent_jobs_running?, description: dependent_jobs_description
|
|
22
|
+
end
|
|
18
23
|
end
|
|
19
24
|
|
|
20
25
|
class_methods do
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
module RocketJob
|
|
2
2
|
module Sliced
|
|
3
3
|
class Input < Slices
|
|
4
|
-
def upload(**args, &
|
|
4
|
+
def upload(**args, &)
|
|
5
5
|
# Create indexes before uploading
|
|
6
6
|
create_indexes
|
|
7
|
-
Writer::Input.collect(self, **args, &
|
|
7
|
+
Writer::Input.collect(self, **args, &)
|
|
8
8
|
rescue Exception => e
|
|
9
9
|
drop
|
|
10
10
|
raise(e)
|
|
@@ -117,19 +117,24 @@ module RocketJob
|
|
|
117
117
|
)
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
-
#
|
|
121
|
-
# Returns nil if there are currently no queued slices
|
|
120
|
+
# Claims and returns the next queued slice for this worker.
|
|
121
|
+
# Returns nil if there are currently no queued slices.
|
|
122
122
|
#
|
|
123
|
-
#
|
|
123
|
+
# No explicit sort is applied. Forcing the global minimum `_id` (a
|
|
124
|
+
# `sort("_id" => 1)`) makes every worker target the same document, so under
|
|
125
|
+
# concurrency they collide on the atomic claim: one wins and the rest hit a
|
|
126
|
+
# WriteConflict ("Document no longer matches the predicate") and retry,
|
|
127
|
+
# which throttles a large batch job as workers are added. Without the sort,
|
|
128
|
+
# the `{state: 1, _id: 1}` index still yields queued slices in roughly `_id`
|
|
129
|
+
# (upload) order, but concurrent workers land on different documents instead
|
|
130
|
+
# of contending for one. Output ordering is unaffected: each output slice
|
|
131
|
+
# inherits its input slice's `_id` and downloads read in `_id` order.
|
|
124
132
|
def next_slice(worker_name)
|
|
125
|
-
# TODO: Will it perform faster without the id sort?
|
|
126
|
-
# I.e. Just process on a FIFO basis?
|
|
127
133
|
document = all.queued.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
134
|
+
find_one_and_update(
|
|
135
|
+
{"$set" => {worker_name: worker_name, state: "running", started_at: Time.now}},
|
|
136
|
+
return_document: :after
|
|
137
|
+
)
|
|
133
138
|
document.collection_name = collection_name if document
|
|
134
139
|
document
|
|
135
140
|
end
|
|
@@ -154,8 +154,8 @@ module RocketJob
|
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
# Fail this slice if an exception occurs during processing.
|
|
157
|
-
def fail_on_exception!(re_raise_exceptions = false, &
|
|
158
|
-
SemanticLogger.named_tagged(slice: id.to_s, &
|
|
157
|
+
def fail_on_exception!(re_raise_exceptions = false, &)
|
|
158
|
+
SemanticLogger.named_tagged(slice: id.to_s, &)
|
|
159
159
|
rescue Exception => e
|
|
160
160
|
SemanticLogger.named_tagged(slice: id.to_s) do
|
|
161
161
|
if failed? || !may_fail?
|
|
@@ -175,7 +175,7 @@ module RocketJob
|
|
|
175
175
|
|
|
176
176
|
# Always add records to any updates.
|
|
177
177
|
def atomic_updates(*args)
|
|
178
|
-
r
|
|
178
|
+
r = super
|
|
179
179
|
(r["$set"] ||= {})["records"] = serialize_records if @records
|
|
180
180
|
r
|
|
181
181
|
end
|
|
@@ -44,8 +44,8 @@ module RocketJob
|
|
|
44
44
|
|
|
45
45
|
# Returns output slices in the order of their id
|
|
46
46
|
# which is usually the order in which they were written.
|
|
47
|
-
def each(&
|
|
48
|
-
all.sort(id: 1).each(&
|
|
47
|
+
def each(&)
|
|
48
|
+
all.sort(id: 1).each(&)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# Insert a new slice into the collection
|
|
@@ -120,7 +120,7 @@ module RocketJob
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
# Forward additional methods.
|
|
123
|
-
def_instance_delegators :@all, :collection, :count, :delete_all, :
|
|
123
|
+
def_instance_delegators :@all, :collection, :count, :delete_all, :find, :nor, :not, :or, :to_a, :where
|
|
124
124
|
|
|
125
125
|
# Drop this collection when it is no longer needed
|
|
126
126
|
def drop
|
|
@@ -145,7 +145,6 @@ module RocketJob
|
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
# Mongoid does not apply ordering, add sort
|
|
148
|
-
# rubocop:disable Style/RedundantSort
|
|
149
148
|
def first
|
|
150
149
|
all.sort("_id" => 1).first
|
|
151
150
|
end
|
|
@@ -154,8 +153,6 @@ module RocketJob
|
|
|
154
153
|
all.sort("_id" => -1).first
|
|
155
154
|
end
|
|
156
155
|
|
|
157
|
-
# rubocop:enable Style/RedundantSort
|
|
158
|
-
|
|
159
156
|
# Returns [Array<Struct>] grouped exceptions by class name,
|
|
160
157
|
# and unique exception messages by exception class.
|
|
161
158
|
#
|
|
@@ -9,8 +9,9 @@ module RocketJob
|
|
|
9
9
|
#
|
|
10
10
|
# Parameters
|
|
11
11
|
# on_first: [Proc]
|
|
12
|
-
#
|
|
13
|
-
# Useful for extracting the header row
|
|
12
|
+
# Called with each line before the first that is stored, instead of storing it, until it returns true.
|
|
13
|
+
# Useful for extracting the header row, after any blank lines before it, see
|
|
14
|
+
# Category::Input#extract_header_callback.
|
|
14
15
|
# Default: nil
|
|
15
16
|
#
|
|
16
17
|
# slice_size: [Integer]
|
|
@@ -42,8 +43,7 @@ module RocketJob
|
|
|
42
43
|
|
|
43
44
|
def <<(line)
|
|
44
45
|
if @on_first
|
|
45
|
-
@on_first.call(line)
|
|
46
|
-
@on_first = nil
|
|
46
|
+
@on_first = nil if @on_first.call(line)
|
|
47
47
|
return self
|
|
48
48
|
end
|
|
49
49
|
@slice << line
|
|
@@ -8,7 +8,7 @@ module RocketJob
|
|
|
8
8
|
attr_reader :thread
|
|
9
9
|
|
|
10
10
|
def initialize(id:, server_name:)
|
|
11
|
-
super
|
|
11
|
+
super
|
|
12
12
|
@shutdown = Concurrent::Event.new
|
|
13
13
|
@thread = Thread.new { run }
|
|
14
14
|
end
|
|
@@ -21,8 +21,8 @@ module RocketJob
|
|
|
21
21
|
@thread.backtrace
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def join(*
|
|
25
|
-
@thread.join(*
|
|
24
|
+
def join(*)
|
|
25
|
+
@thread.join(*)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# Send each active worker the RocketJob::ShutdownException so that stops processing immediately.
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
module RocketJob
|
|
2
2
|
class ThrottleDefinition
|
|
3
|
-
attr_reader :method_name, :filter
|
|
3
|
+
attr_reader :method_name, :filter, :description
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# Parameters:
|
|
6
|
+
# description: [String|Proc|nil]
|
|
7
|
+
# Human readable reason why the job is throttled, persisted to the job as
|
|
8
|
+
# `throttled_by` and surfaced in Mission Control.
|
|
9
|
+
# When a Proc, it is called with the same arguments as the throttle and must
|
|
10
|
+
# return a String, allowing the reason to include runtime detail.
|
|
11
|
+
# When nil, a humanized version of the method name is used.
|
|
12
|
+
def initialize(method_name, filter, description = nil)
|
|
6
13
|
@method_name = method_name.to_sym
|
|
7
14
|
@filter = filter
|
|
15
|
+
@description = description
|
|
8
16
|
end
|
|
9
17
|
|
|
10
18
|
# Returns [true|false] whether the throttle was triggered.
|
|
@@ -33,5 +41,14 @@ module RocketJob
|
|
|
33
41
|
job.send(filter)
|
|
34
42
|
end
|
|
35
43
|
end
|
|
44
|
+
|
|
45
|
+
# Returns [String] the human readable reason why the job is throttled.
|
|
46
|
+
def extract_description(job, *)
|
|
47
|
+
return description.call(job, *) if description.is_a?(Proc)
|
|
48
|
+
return description if description
|
|
49
|
+
|
|
50
|
+
# Default: humanize the method name, dropping a trailing `?` or `_exceeded`.
|
|
51
|
+
method_name.to_s.sub(/_exceeded\?\z/, "").sub(/\?\z/, "").tr("_", " ").capitalize
|
|
52
|
+
end
|
|
36
53
|
end
|
|
37
54
|
end
|
|
@@ -6,13 +6,13 @@ module RocketJob
|
|
|
6
6
|
@throttles = []
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def add(method_name, filter)
|
|
9
|
+
def add(method_name, filter, description = nil)
|
|
10
10
|
unless filter.is_a?(Symbol) || filter.is_a?(Proc)
|
|
11
11
|
raise(ArgumentError, "Filter for #{method_name} must be a Symbol or Proc")
|
|
12
12
|
end
|
|
13
13
|
raise(ArgumentError, "Cannot define #{method_name} twice, undefine previous throttle first") if exist?(method_name)
|
|
14
14
|
|
|
15
|
-
@throttles += [ThrottleDefinition.new(method_name, filter)]
|
|
15
|
+
@throttles += [ThrottleDefinition.new(method_name, filter, description)]
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Undefine a previously defined throttle
|
|
@@ -25,15 +25,20 @@ module RocketJob
|
|
|
25
25
|
throttles.any? { |throttle| throttle.method_name == method_name }
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
# Returns the
|
|
28
|
+
# Returns [ThrottleDefinition] the first throttle that was triggered,
|
|
29
29
|
# or nil if no throttles were triggered.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
#
|
|
31
|
+
# The returned definition exposes both the filter (`extract_filter`) and the
|
|
32
|
+
# human readable reason (`extract_description`) so callers can apply the filter
|
|
33
|
+
# and persist why the job was throttled.
|
|
34
|
+
def matching_throttle(job, *args)
|
|
35
|
+
throttles.find { |throttle| throttle.throttled?(job, *args) }
|
|
36
|
+
end
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
# Returns the matching filter,
|
|
39
|
+
# or nil if no throttles were triggered.
|
|
40
|
+
def matching_filter(job, *)
|
|
41
|
+
matching_throttle(job, *)&.extract_filter(job, *)
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
def deep_dup
|
data/lib/rocket_job/version.rb
CHANGED