rocketjob 5.4.1 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|