rocketjob 5.4.1 → 6.0.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 +175 -5
- data/bin/rocketjob_batch_perf +1 -1
- data/bin/rocketjob_perf +1 -1
- data/lib/rocket_job/batch/categories.rb +345 -0
- data/lib/rocket_job/batch/io.rb +174 -106
- data/lib/rocket_job/batch/model.rb +20 -68
- data/lib/rocket_job/batch/performance.rb +19 -7
- data/lib/rocket_job/batch/statistics.rb +34 -12
- data/lib/rocket_job/batch/throttle_running_workers.rb +2 -6
- data/lib/rocket_job/batch/worker.rb +31 -26
- data/lib/rocket_job/batch.rb +3 -1
- data/lib/rocket_job/category/base.rb +81 -0
- data/lib/rocket_job/category/input.rb +170 -0
- data/lib/rocket_job/category/output.rb +34 -0
- data/lib/rocket_job/cli.rb +25 -17
- data/lib/rocket_job/dirmon_entry.rb +23 -13
- data/lib/rocket_job/event.rb +1 -1
- data/lib/rocket_job/extensions/iostreams/path.rb +32 -0
- data/lib/rocket_job/extensions/mongoid/contextual/mongo.rb +2 -2
- data/lib/rocket_job/extensions/mongoid/factory.rb +4 -12
- data/lib/rocket_job/extensions/mongoid/stringified_symbol.rb +50 -0
- data/lib/rocket_job/extensions/psych/yaml_tree.rb +8 -0
- data/lib/rocket_job/extensions/rocket_job_adapter.rb +2 -2
- data/lib/rocket_job/jobs/conversion_job.rb +43 -0
- data/lib/rocket_job/jobs/dirmon_job.rb +25 -36
- data/lib/rocket_job/jobs/housekeeping_job.rb +11 -12
- data/lib/rocket_job/jobs/on_demand_batch_job.rb +24 -11
- data/lib/rocket_job/jobs/on_demand_job.rb +3 -4
- data/lib/rocket_job/jobs/performance_job.rb +3 -1
- data/lib/rocket_job/jobs/re_encrypt/relational_job.rb +103 -96
- data/lib/rocket_job/jobs/upload_file_job.rb +48 -8
- data/lib/rocket_job/lookup_collection.rb +69 -0
- data/lib/rocket_job/plugins/cron.rb +60 -20
- data/lib/rocket_job/plugins/job/model.rb +25 -50
- data/lib/rocket_job/plugins/job/persistence.rb +36 -0
- data/lib/rocket_job/plugins/job/throttle.rb +2 -2
- data/lib/rocket_job/plugins/job/throttle_running_jobs.rb +1 -1
- data/lib/rocket_job/plugins/job/worker.rb +2 -7
- data/lib/rocket_job/plugins/restart.rb +3 -103
- data/lib/rocket_job/plugins/state_machine.rb +4 -3
- data/lib/rocket_job/plugins/throttle_dependent_jobs.rb +37 -0
- data/lib/rocket_job/ractor_worker.rb +42 -0
- data/lib/rocket_job/server/model.rb +1 -1
- data/lib/rocket_job/sliced/bzip2_output_slice.rb +18 -19
- data/lib/rocket_job/sliced/compressed_slice.rb +3 -6
- data/lib/rocket_job/sliced/encrypted_bzip2_output_slice.rb +49 -0
- data/lib/rocket_job/sliced/encrypted_slice.rb +4 -6
- data/lib/rocket_job/sliced/input.rb +42 -54
- data/lib/rocket_job/sliced/slice.rb +12 -16
- data/lib/rocket_job/sliced/slices.rb +26 -11
- data/lib/rocket_job/sliced/writer/input.rb +46 -18
- data/lib/rocket_job/sliced/writer/output.rb +33 -45
- data/lib/rocket_job/sliced.rb +1 -74
- data/lib/rocket_job/subscribers/server.rb +1 -1
- data/lib/rocket_job/thread_worker.rb +46 -0
- data/lib/rocket_job/throttle_definitions.rb +7 -1
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocket_job/worker.rb +21 -55
- data/lib/rocket_job/worker_pool.rb +5 -7
- data/lib/rocketjob.rb +53 -43
- metadata +36 -28
- data/lib/rocket_job/batch/tabular/input.rb +0 -131
- data/lib/rocket_job/batch/tabular/output.rb +0 -65
- data/lib/rocket_job/batch/tabular.rb +0 -56
- data/lib/rocket_job/extensions/mongoid/remove_warnings.rb +0 -12
- data/lib/rocket_job/jobs/on_demand_batch_tabular_job.rb +0 -28
@@ -0,0 +1,69 @@
|
|
1
|
+
module RocketJob
|
2
|
+
class LookupCollection < Mongo::Collection
|
3
|
+
# Rapidly upload individual records in batches.
|
4
|
+
#
|
5
|
+
# Operates directly on a Mongo Collection to avoid the overhead of creating Mongoid objects
|
6
|
+
# for each and every row.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# lookup_collection(:my_lookup).upload do |io|
|
10
|
+
# io << {id: 123, data: "first record"}
|
11
|
+
# io << {id: 124, data: "second record"}
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# input_category(:my_lookup).find(id: 123).first
|
15
|
+
def upload(batch_size: 10_000, &block)
|
16
|
+
BatchUploader.upload(batch_size: batch_size, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Looks up the value at the specified id.
|
20
|
+
# Returns [nil] if no record was found with the supplied id.
|
21
|
+
def lookup(id)
|
22
|
+
find(id: id).first
|
23
|
+
end
|
24
|
+
|
25
|
+
# Internal class for uploading records in batches
|
26
|
+
class BatchUploader
|
27
|
+
attr_reader :record_count
|
28
|
+
|
29
|
+
def self.upload(collection, **args)
|
30
|
+
writer = new(collection, **args)
|
31
|
+
yield(writer)
|
32
|
+
writer.record_count
|
33
|
+
ensure
|
34
|
+
writer&.close
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(collection, batch_size:)
|
38
|
+
@batch_size = batch_size
|
39
|
+
@record_count = 0
|
40
|
+
@batch_count = 0
|
41
|
+
@documents = []
|
42
|
+
@collection = collection
|
43
|
+
end
|
44
|
+
|
45
|
+
def <<(record)
|
46
|
+
raise(ArgumentError, "Record must be a Hash") unless record.is_a?(Hash)
|
47
|
+
|
48
|
+
unless record.key?(:id) || record.key?("id") || record.key?("_id")
|
49
|
+
raise(ArgumentError, "Record must include an :id key")
|
50
|
+
end
|
51
|
+
|
52
|
+
@documents << record
|
53
|
+
@record_count += 1
|
54
|
+
@batch_count += 1
|
55
|
+
if @batch_count >= @batch_size
|
56
|
+
@collection.insert_many(@documents)
|
57
|
+
@documents.clear
|
58
|
+
@batch_count = 0
|
59
|
+
end
|
60
|
+
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def close
|
65
|
+
@collection.insert_many(@documents) unless @documents.empty?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -14,41 +14,81 @@ module RocketJob
|
|
14
14
|
extend ActiveSupport::Concern
|
15
15
|
|
16
16
|
included do
|
17
|
-
include Restart
|
18
|
-
|
19
17
|
field :cron_schedule, type: String, class_attribute: true, user_editable: true, copy_on_restart: true
|
20
18
|
|
19
|
+
# Whether to prevent another instance of this job from running with the exact _same_ cron schedule.
|
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, copy_on_restart: true
|
22
|
+
|
23
|
+
# Whether to re-schedule the next job occurrence when this job starts, or when it is complete.
|
24
|
+
#
|
25
|
+
# `true`: Create a new scheduled instance of this job after it has started. (Default)
|
26
|
+
# - Ensures that the next scheduled instance is not missed because the current instance is still running.
|
27
|
+
# - Any changes to fields marked with `copy_on_restart` of `true` will be saved to the new scheduled instance
|
28
|
+
# _only_ if they were changed during an `after_start` callback.
|
29
|
+
# Changes to these during other callbacks or during the `perform` will not be saved to the new scheduled
|
30
|
+
# instance.
|
31
|
+
# - To prevent this job creating any new duplicate instances during subsequent processing,
|
32
|
+
# its `cron_schedule` is set to `nil`.
|
33
|
+
#
|
34
|
+
# `false`: Create a new scheduled instance of this job on `fail`, or `abort`.
|
35
|
+
# - Prevents the next scheduled instance from running or being scheduled while the current instance is
|
36
|
+
# still running.
|
37
|
+
# - Any changes to fields marked with `copy_on_restart` of `true` will be saved to the new scheduled instance
|
38
|
+
# at any time until after the job has failed, or is aborted.
|
39
|
+
# - To prevent this job creating any new duplicate instances during subsequent processing,
|
40
|
+
# 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, copy_on_restart: true
|
42
|
+
|
21
43
|
validates_each :cron_schedule do |record, attr, value|
|
22
44
|
record.errors.add(attr, "Invalid cron_schedule: #{value.inspect}") if value && !Fugit::Cron.new(value)
|
23
45
|
end
|
46
|
+
validate :rocket_job_cron_singleton_check
|
47
|
+
|
24
48
|
before_save :rocket_job_cron_set_run_at
|
25
49
|
|
26
|
-
|
50
|
+
after_start :rocket_job_cron_on_start
|
51
|
+
after_abort :rocket_job_cron_end_state
|
52
|
+
after_complete :rocket_job_cron_end_state
|
53
|
+
after_fail :rocket_job_cron_end_state
|
54
|
+
end
|
27
55
|
|
28
|
-
|
29
|
-
|
30
|
-
def rocket_job_restart_new_instance
|
31
|
-
return unless cron_schedule
|
56
|
+
def rocket_job_cron_set_run_at
|
57
|
+
return if cron_schedule.nil? || !(cron_schedule_changed? && !run_at_changed?)
|
32
58
|
|
33
|
-
|
34
|
-
|
59
|
+
self.run_at = Fugit::Cron.new(cron_schedule).next_time.to_utc_time
|
60
|
+
end
|
35
61
|
|
36
|
-
|
37
|
-
# - create a new instance scheduled to run in the future.
|
38
|
-
# - clear out the `cron_schedule` so this instance will not schedule another instance to run on completion.
|
39
|
-
# Overrides: RocketJob::Plugins::Restart#rocket_job_restart_abort
|
40
|
-
def rocket_job_restart_abort
|
41
|
-
return unless cron_schedule
|
62
|
+
private
|
42
63
|
|
43
|
-
|
44
|
-
|
64
|
+
def rocket_job_cron_on_start
|
65
|
+
return unless cron_schedule && cron_after_start
|
66
|
+
|
67
|
+
current_cron_schedule = cron_schedule
|
68
|
+
update_attribute(:cron_schedule, nil)
|
69
|
+
create_restart!(cron_schedule: current_cron_schedule)
|
70
|
+
end
|
71
|
+
|
72
|
+
def rocket_job_cron_end_state
|
73
|
+
return unless cron_schedule && !cron_after_start
|
74
|
+
|
75
|
+
current_cron_schedule = cron_schedule
|
76
|
+
update_attribute(:cron_schedule, nil)
|
77
|
+
create_restart!(cron_schedule: current_cron_schedule)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns [true|false] whether another instance of this job with the same cron schedule is already active
|
81
|
+
def rocket_job_cron_duplicate?
|
82
|
+
self.class.with(read: {mode: :primary}) do |conn|
|
83
|
+
conn.where(:state.in => %i[queued running failed paused], :id.ne => id, cron_schedule: cron_schedule).exists?
|
45
84
|
end
|
46
85
|
end
|
47
86
|
|
48
|
-
|
49
|
-
|
87
|
+
# Prevent creation of a new job when another is running with the same cron schedule.
|
88
|
+
def rocket_job_cron_singleton_check
|
89
|
+
return if cron_schedule.nil? || completed? || aborted? || !rocket_job_cron_duplicate?
|
50
90
|
|
51
|
-
self.
|
91
|
+
errors.add(:state, "Another instance of #{self.class.name} is already queued, running, failed, or paused with the same cron schedule: #{cron_schedule}")
|
52
92
|
end
|
53
93
|
end
|
54
94
|
end
|
@@ -37,12 +37,10 @@ module RocketJob
|
|
37
37
|
# arrives, then the current job will complete the current slices and process
|
38
38
|
# the new higher priority job
|
39
39
|
field :priority, type: Integer, default: 50, class_attribute: true, user_editable: true, copy_on_restart: true
|
40
|
+
validates_inclusion_of :priority, in: 1..100
|
40
41
|
|
41
42
|
# 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, copy_on_restart: true
|
43
|
-
|
44
|
-
# Whether to store the results from this job
|
45
|
-
field :collect_output, type: Boolean, default: false, class_attribute: true
|
43
|
+
field :destroy_on_complete, type: Mongoid::Boolean, default: true, class_attribute: true, copy_on_restart: true
|
46
44
|
|
47
45
|
# Run this job no earlier than this time
|
48
46
|
field :run_at, type: Time, user_editable: true
|
@@ -54,14 +52,15 @@ module RocketJob
|
|
54
52
|
# Can be used to reduce log noise, especially during high volume calls
|
55
53
|
# For debugging a single job can be logged at a low level such as :trace
|
56
54
|
# Levels supported: :trace, :debug, :info, :warn, :error, :fatal
|
57
|
-
field :log_level, type:
|
55
|
+
field :log_level, type: Mongoid::StringifiedSymbol, class_attribute: true, user_editable: true, copy_on_restart: true
|
56
|
+
validates_inclusion_of :log_level, in: SemanticLogger::LEVELS + [nil]
|
58
57
|
|
59
58
|
#
|
60
59
|
# Read-only attributes
|
61
60
|
#
|
62
61
|
|
63
62
|
# Current state, as set by the state machine. Do not modify this value directly.
|
64
|
-
field :state, type:
|
63
|
+
field :state, type: Mongoid::StringifiedSymbol, default: :queued
|
65
64
|
|
66
65
|
# When the job was created
|
67
66
|
field :created_at, type: Time, default: -> { Time.now }
|
@@ -89,17 +88,12 @@ module RocketJob
|
|
89
88
|
# Store the last exception for this job
|
90
89
|
embeds_one :exception, class_name: "RocketJob::JobException"
|
91
90
|
|
92
|
-
#
|
93
|
-
# and the job returned actually returned a Hash, otherwise nil
|
94
|
-
# Not applicable to SlicedJob jobs, since its output is stored in a
|
95
|
-
# separate collection
|
96
|
-
field :result, type: Hash
|
97
|
-
|
91
|
+
# Used when workers fetch jobs to work on.
|
98
92
|
index({state: 1, priority: 1, _id: 1}, background: true)
|
93
|
+
# Used by Mission Control to display completed jobs sorted by completion.
|
94
|
+
index({completed_at: 1}, background: true)
|
99
95
|
|
100
96
|
validates_presence_of :state, :failure_count, :created_at
|
101
|
-
validates :priority, inclusion: 1..100
|
102
|
-
validates :log_level, inclusion: SemanticLogger::LEVELS + [nil]
|
103
97
|
end
|
104
98
|
|
105
99
|
module ClassMethods
|
@@ -155,14 +149,8 @@ module RocketJob
|
|
155
149
|
|
156
150
|
# Scope for queued jobs that can run now
|
157
151
|
# I.e. Queued jobs excluding scheduled jobs
|
158
|
-
|
159
|
-
|
160
|
-
queued.and(RocketJob::Job.where(run_at: nil).or(:run_at.lte => Time.now))
|
161
|
-
end
|
162
|
-
else
|
163
|
-
def queued_now
|
164
|
-
queued.or({run_at: nil}, :run_at.lte => Time.now)
|
165
|
-
end
|
152
|
+
def queued_now
|
153
|
+
queued.and(RocketJob::Job.where(run_at: nil).or(:run_at.lte => Time.now))
|
166
154
|
end
|
167
155
|
|
168
156
|
# Defines all the fields that are accessible on the Document
|
@@ -183,43 +171,30 @@ module RocketJob
|
|
183
171
|
#
|
184
172
|
# @return [ Field ] The generated field
|
185
173
|
def field(name, options)
|
186
|
-
if options.delete(:user_editable) == true
|
187
|
-
self.user_editable_fields += [name.to_sym]
|
174
|
+
if (options.delete(:user_editable) == true) && !user_editable_fields.include?(name.to_sym)
|
175
|
+
self.user_editable_fields += [name.to_sym]
|
188
176
|
end
|
177
|
+
|
189
178
|
if options.delete(:class_attribute) == true
|
190
179
|
class_attribute(name, instance_accessor: false)
|
191
180
|
public_send("#{name}=", options[:default]) if options.key?(:default)
|
192
181
|
options[:default] = -> { self.class.public_send(name) }
|
193
182
|
end
|
194
|
-
|
195
|
-
|
183
|
+
|
184
|
+
if (options.delete(:copy_on_restart) == true) && !rocket_job_restart_attributes.include?(name.to_sym)
|
185
|
+
self.rocket_job_restart_attributes += [name.to_sym]
|
196
186
|
end
|
197
|
-
super(name, options)
|
198
|
-
end
|
199
187
|
|
200
|
-
|
201
|
-
def rocket_job
|
202
|
-
warn "Replace calls to .rocket_job with calls to set class instance variables. For example: self.priority = 50"
|
203
|
-
yield(self)
|
188
|
+
super(name, options)
|
204
189
|
end
|
205
190
|
|
206
|
-
#
|
207
|
-
|
208
|
-
|
209
|
-
|
191
|
+
# Builds this job instance from the supplied properties hash.
|
192
|
+
# Overridden by batch to support child objects.
|
193
|
+
def from_properties(properties)
|
194
|
+
new(properties)
|
210
195
|
end
|
211
196
|
end
|
212
197
|
|
213
|
-
# Returns [true|false] whether to collect nil results from running this batch
|
214
|
-
def collect_nil_output?
|
215
|
-
collect_output? ? (collect_nil_output == true) : false
|
216
|
-
end
|
217
|
-
|
218
|
-
# Returns [true|false] whether to collect the results from running this batch
|
219
|
-
def collect_output?
|
220
|
-
collect_output == true
|
221
|
-
end
|
222
|
-
|
223
198
|
# Returns [Float] the number of seconds the job has taken
|
224
199
|
# - Elapsed seconds to process the job from when a worker first started working on it
|
225
200
|
# until now if still running, or until it was completed
|
@@ -282,7 +257,6 @@ module RocketJob
|
|
282
257
|
# Returns [Hash] status of this job
|
283
258
|
def as_json
|
284
259
|
attrs = serializable_hash(methods: %i[seconds duration])
|
285
|
-
attrs.delete("result") unless collect_output?
|
286
260
|
attrs.delete("failure_count") unless failure_count.positive?
|
287
261
|
if queued?
|
288
262
|
attrs.delete("started_at")
|
@@ -319,16 +293,17 @@ module RocketJob
|
|
319
293
|
h = as_json
|
320
294
|
h.delete("seconds")
|
321
295
|
h.dup.each_pair do |k, v|
|
322
|
-
|
296
|
+
case v
|
297
|
+
when Time
|
323
298
|
h[k] = v.in_time_zone(time_zone).to_s
|
324
|
-
|
299
|
+
when BSON::ObjectId
|
325
300
|
h[k] = v.to_s
|
326
301
|
end
|
327
302
|
end
|
328
303
|
h
|
329
304
|
end
|
330
305
|
|
331
|
-
# Returns [
|
306
|
+
# Returns [true|false] whether the worker runs on a particular server.
|
332
307
|
def worker_on_server?(server_name)
|
333
308
|
return false unless worker_name.present? && server_name.present?
|
334
309
|
|
@@ -70,6 +70,29 @@ module RocketJob
|
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
+
# Create a new instance of this job, copying across only the `copy_on_restart` attributes.
|
74
|
+
# Copy across input and output categories to new scheduled job so that all of the
|
75
|
+
# settings are remembered between instance. Example: slice_size
|
76
|
+
def create_restart!(**overrides)
|
77
|
+
if expired?
|
78
|
+
logger.info("Job has expired. Not creating a new instance.")
|
79
|
+
return
|
80
|
+
end
|
81
|
+
|
82
|
+
job_attrs = self.class.rocket_job_restart_attributes.each_with_object({}) do |attr, attrs|
|
83
|
+
attrs[attr] = send(attr)
|
84
|
+
end
|
85
|
+
job_attrs.merge!(overrides)
|
86
|
+
|
87
|
+
job = self.class.new(job_attrs)
|
88
|
+
job.input_categories = input_categories if respond_to?(:input_categories)
|
89
|
+
job.output_categories = output_categories if respond_to?(:output_categories)
|
90
|
+
|
91
|
+
job.save_with_retry!
|
92
|
+
|
93
|
+
logger.info("Created a new job instance: #{job.id}")
|
94
|
+
end
|
95
|
+
|
73
96
|
# Set in-memory job to complete if `destroy_on_complete` and the job has been destroyed
|
74
97
|
def reload
|
75
98
|
return super unless destroy_on_complete
|
@@ -85,6 +108,19 @@ module RocketJob
|
|
85
108
|
self
|
86
109
|
end
|
87
110
|
end
|
111
|
+
|
112
|
+
# Save with retry in case persistence takes a moment.
|
113
|
+
def save_with_retry!(retry_limit = 10, sleep_interval = 0.5)
|
114
|
+
count = 0
|
115
|
+
while count < retry_limit
|
116
|
+
return true if save
|
117
|
+
|
118
|
+
logger.info("Retrying to persist new scheduled instance: #{errors.messages.inspect}")
|
119
|
+
sleep(sleep_interval)
|
120
|
+
count += 1
|
121
|
+
end
|
122
|
+
save!
|
123
|
+
end
|
88
124
|
end
|
89
125
|
end
|
90
126
|
end
|
@@ -48,7 +48,7 @@ module RocketJob
|
|
48
48
|
# Note: Throttles are executed in the order they are defined.
|
49
49
|
def define_throttle(method_name, filter: :throttle_filter_class)
|
50
50
|
# Duplicate to prevent modifying parent class throttles
|
51
|
-
definitions = rocket_job_throttles ? rocket_job_throttles.
|
51
|
+
definitions = rocket_job_throttles ? rocket_job_throttles.deep_dup : ThrottleDefinitions.new
|
52
52
|
definitions.add(method_name, filter)
|
53
53
|
self.rocket_job_throttles = definitions
|
54
54
|
end
|
@@ -57,7 +57,7 @@ module RocketJob
|
|
57
57
|
def undefine_throttle(method_name)
|
58
58
|
return unless rocket_job_throttles
|
59
59
|
|
60
|
-
definitions = rocket_job_throttles.
|
60
|
+
definitions = rocket_job_throttles.deep_dup
|
61
61
|
definitions.remove(method_name)
|
62
62
|
self.rocket_job_throttles = definitions
|
63
63
|
end
|
@@ -37,7 +37,7 @@ module RocketJob
|
|
37
37
|
|
38
38
|
private
|
39
39
|
|
40
|
-
# Returns [
|
40
|
+
# Returns [true|false] whether the throttle for this job has been exceeded
|
41
41
|
def throttle_running_jobs_exceeded?
|
42
42
|
return false unless throttle_running_jobs&.positive?
|
43
43
|
|
@@ -48,11 +48,11 @@ module RocketJob
|
|
48
48
|
def perform_now
|
49
49
|
raise(::Mongoid::Errors::Validations, self) unless valid?
|
50
50
|
|
51
|
-
worker = RocketJob::Worker.new
|
51
|
+
worker = RocketJob::Worker.new
|
52
52
|
start if may_start?
|
53
53
|
# Re-Raise exceptions
|
54
54
|
rocket_job_work(worker, true) if running?
|
55
|
-
|
55
|
+
@rocket_job_output
|
56
56
|
end
|
57
57
|
|
58
58
|
def perform(*)
|
@@ -106,11 +106,6 @@ module RocketJob
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
109
|
-
if collect_output?
|
110
|
-
# Result must be a Hash, if not put it in a Hash
|
111
|
-
self.result = @rocket_job_output.is_a?(Hash) ? @rocket_job_output : {"result" => @rocket_job_output}
|
112
|
-
end
|
113
|
-
|
114
109
|
if new_record? || destroyed?
|
115
110
|
complete if may_complete?
|
116
111
|
else
|
@@ -2,121 +2,21 @@ require "active_support/concern"
|
|
2
2
|
|
3
3
|
module RocketJob
|
4
4
|
module Plugins
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# Notes:
|
8
|
-
# * Restartable jobs automatically abort if they fail. This prevents the failed job from being retried.
|
9
|
-
# - To disable this behavior, add the following empty method:
|
10
|
-
# def rocket_job_restart_abort
|
11
|
-
# end
|
12
|
-
# * On destroy this job is destroyed without starting a new instance.
|
13
|
-
# * On Abort a new instance is created.
|
14
|
-
# * Include `RocketJob::Plugins::Singleton` to prevent multiple copies of a job from running at
|
15
|
-
# the same time.
|
16
|
-
# * The job will not be restarted if:
|
17
|
-
# - A validation fails after creating the new instance of this job.
|
18
|
-
# - The job has expired.
|
19
|
-
# * Only the fields that have `copy_on_restart: true` will be passed onto the new instance of this job.
|
20
|
-
#
|
21
|
-
# Example:
|
22
|
-
#
|
23
|
-
# class RestartableJob < RocketJob::Job
|
24
|
-
# include RocketJob::Plugins::Restart
|
25
|
-
#
|
26
|
-
# # Retain the completed job under the completed tab in Rocket Job Web Interface.
|
27
|
-
# self.destroy_on_complete = false
|
28
|
-
#
|
29
|
-
# # Will be copied to the new job on restart.
|
30
|
-
# field :limit, type: Integer, copy_on_restart: true
|
31
|
-
#
|
32
|
-
# # Will _not_ be copied to the new job on restart.
|
33
|
-
# field :list, type: Array, default: [1,2,3]
|
34
|
-
#
|
35
|
-
# # Set run_at every time a new instance of the job is created.
|
36
|
-
# after_initialize set_run_at, if: :new_record?
|
37
|
-
#
|
38
|
-
# def perform
|
39
|
-
# puts "The limit is #{limit}"
|
40
|
-
# puts "The list is #{list}"
|
41
|
-
# 'DONE'
|
42
|
-
# end
|
43
|
-
#
|
44
|
-
# private
|
45
|
-
#
|
46
|
-
# # Run this job in 30 minutes.
|
47
|
-
# def set_run_at
|
48
|
-
# self.run_at = 30.minutes.from_now
|
49
|
-
# end
|
50
|
-
# end
|
51
|
-
#
|
52
|
-
# job = RestartableJob.create!(limit: 10, list: [4,5,6])
|
53
|
-
# job.reload.state
|
54
|
-
# # => :queued
|
55
|
-
#
|
56
|
-
# job.limit
|
57
|
-
# # => 10
|
58
|
-
#
|
59
|
-
# job.list
|
60
|
-
# # => [4,5,6]
|
61
|
-
#
|
62
|
-
# # Wait 30 minutes ...
|
63
|
-
#
|
64
|
-
# job.reload.state
|
65
|
-
# # => :completed
|
66
|
-
#
|
67
|
-
# # A new instance was automatically created.
|
68
|
-
# job2 = RestartableJob.last
|
69
|
-
# job2.state
|
70
|
-
# # => :queued
|
71
|
-
#
|
72
|
-
# job2.limit
|
73
|
-
# # => 10
|
74
|
-
#
|
75
|
-
# job2.list
|
76
|
-
# # => [1,2,3]
|
5
|
+
# @deprecated
|
77
6
|
module Restart
|
78
7
|
extend ActiveSupport::Concern
|
79
8
|
|
80
9
|
included do
|
81
|
-
after_abort :
|
82
|
-
after_complete :
|
10
|
+
after_abort :create_restart!
|
11
|
+
after_complete :create_restart!
|
83
12
|
after_fail :rocket_job_restart_abort
|
84
13
|
end
|
85
14
|
|
86
15
|
private
|
87
16
|
|
88
|
-
# Run again in the future, even if this run fails with an exception
|
89
|
-
def rocket_job_restart_new_instance
|
90
|
-
if expired?
|
91
|
-
logger.info("Job has expired. Not creating a new instance.")
|
92
|
-
return
|
93
|
-
end
|
94
|
-
attributes = rocket_job_restart_attributes.each_with_object({}) { |attr, attrs| attrs[attr] = send(attr) }
|
95
|
-
rocket_job_restart_create(attributes)
|
96
|
-
end
|
97
|
-
|
98
17
|
def rocket_job_restart_abort
|
99
18
|
new_record? ? abort : abort!
|
100
19
|
end
|
101
|
-
|
102
|
-
# Allow Singleton to prevent the creation of a new job if one is already running
|
103
|
-
# Retry since the delete may not have persisted to disk yet.
|
104
|
-
def rocket_job_restart_create(attrs, retry_limit = 3, sleep_interval = 0.1)
|
105
|
-
count = 0
|
106
|
-
while count < retry_limit
|
107
|
-
job = self.class.create(attrs)
|
108
|
-
if job.persisted?
|
109
|
-
logger.info("Created a new job instance: #{job.id}")
|
110
|
-
return true
|
111
|
-
else
|
112
|
-
logger.info("Job already active, retrying after a short sleep")
|
113
|
-
sleep(sleep_interval)
|
114
|
-
end
|
115
|
-
count += 1
|
116
|
-
end
|
117
|
-
logger.error("New job instance not started: #{job.errors.messages.inspect}")
|
118
|
-
false
|
119
|
-
end
|
120
20
|
end
|
121
21
|
end
|
122
22
|
end
|