rocketjob 6.0.0.rc3 → 6.0.3

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -0
  3. data/lib/rocket_job/batch/categories.rb +26 -24
  4. data/lib/rocket_job/batch/io.rb +128 -128
  5. data/lib/rocket_job/batch/worker.rb +14 -12
  6. data/lib/rocket_job/category/base.rb +10 -7
  7. data/lib/rocket_job/category/input.rb +61 -1
  8. data/lib/rocket_job/category/output.rb +9 -0
  9. data/lib/rocket_job/dirmon_entry.rb +1 -1
  10. data/lib/rocket_job/job_exception.rb +1 -1
  11. data/lib/rocket_job/jobs/conversion_job.rb +21 -17
  12. data/lib/rocket_job/jobs/dirmon_job.rb +24 -35
  13. data/lib/rocket_job/jobs/housekeeping_job.rb +4 -5
  14. data/lib/rocket_job/jobs/on_demand_batch_job.rb +11 -5
  15. data/lib/rocket_job/jobs/on_demand_job.rb +6 -2
  16. data/lib/rocket_job/jobs/upload_file_job.rb +4 -0
  17. data/lib/rocket_job/plugins/cron.rb +60 -20
  18. data/lib/rocket_job/plugins/job/persistence.rb +36 -0
  19. data/lib/rocket_job/plugins/restart.rb +3 -110
  20. data/lib/rocket_job/plugins/state_machine.rb +2 -2
  21. data/lib/rocket_job/plugins/throttle_dependent_jobs.rb +10 -5
  22. data/lib/rocket_job/sliced/bzip2_output_slice.rb +18 -19
  23. data/lib/rocket_job/sliced/compressed_slice.rb +3 -6
  24. data/lib/rocket_job/sliced/encrypted_bzip2_output_slice.rb +49 -0
  25. data/lib/rocket_job/sliced/encrypted_slice.rb +4 -6
  26. data/lib/rocket_job/sliced/input.rb +42 -54
  27. data/lib/rocket_job/sliced/slice.rb +7 -3
  28. data/lib/rocket_job/sliced/slices.rb +12 -9
  29. data/lib/rocket_job/sliced/writer/input.rb +46 -18
  30. data/lib/rocket_job/sliced.rb +1 -19
  31. data/lib/rocket_job/subscribers/secret_config.rb +17 -0
  32. data/lib/rocket_job/supervisor.rb +1 -0
  33. data/lib/rocket_job/version.rb +1 -1
  34. data/lib/rocketjob.rb +4 -3
  35. metadata +11 -12
  36. data/lib/rocket_job/batch/tabular/input.rb +0 -133
  37. data/lib/rocket_job/batch/tabular/output.rb +0 -67
  38. data/lib/rocket_job/batch/tabular.rb +0 -58
@@ -13,6 +13,8 @@ module RocketJob
13
13
  # false: do not save nil values to the output categories.
14
14
  field :nils, type: ::Mongoid::Boolean, default: false
15
15
 
16
+ validates_inclusion_of :serializer, in: %i[none compress encrypt bz2 encrypted_bz2 bzip2]
17
+
16
18
  # Renders [String] the header line.
17
19
  # Returns [nil] if no header is needed.
18
20
  def render_header
@@ -20,6 +22,13 @@ module RocketJob
20
22
 
21
23
  tabular.render_header
22
24
  end
25
+
26
+ def data_store(job)
27
+ RocketJob::Sliced::Output.new(
28
+ collection_name: build_collection_name(:output, job),
29
+ slice_class: serializer_class
30
+ )
31
+ end
23
32
  end
24
33
  end
25
34
  end
@@ -173,7 +173,7 @@ module RocketJob
173
173
  counts
174
174
  end
175
175
 
176
- # Passes each filename [Pathname] found that matches the pattern into the supplied block
176
+ # Yields [IOStreams::Path] for each file found that matches the current pattern.
177
177
  def each
178
178
  SemanticLogger.named_tagged(dirmon_entry: id.to_s) do
179
179
  # Case insensitive filename matching
@@ -23,7 +23,7 @@ module RocketJob
23
23
  new(
24
24
  args.merge(
25
25
  class_name: exc.class.name,
26
- message: exc.message,
26
+ message: exc.message.to_s.encode("UTF-8", replace: ""),
27
27
  backtrace: exc.backtrace || []
28
28
  )
29
29
  )
@@ -1,39 +1,43 @@
1
1
  # Convert to and from CSV, JSON, xlsx, and PSV files.
2
2
  #
3
3
  # Example, Convert CSV file to JSON.
4
- # job = RocketJob::ConversionJob.new
5
- # job.upload("data.csv")
4
+ # job = RocketJob::Jobs::ConversionJob.new
5
+ # job.input_category.file_name = "data.csv"
6
6
  # job.output_category.file_name = "data.json"
7
7
  # job.save!
8
8
  #
9
9
  # Example, Convert JSON file to PSV and compress it with GZip.
10
- # job = RocketJob::ConversionJob.new
11
- # job.upload("data.json")
10
+ # job = RocketJob::Jobs::ConversionJob.new
11
+ # job.input_category.file_name = "data.json"
12
12
  # job.output_category.file_name = "data.psv.gz"
13
13
  # job.save!
14
14
  #
15
15
  # Example, Read a CSV file that has been zipped from a remote website and the convert it to a GZipped json file.
16
- # job = RocketJob::ConversionJob.new
17
- # job.upload("https://example.org/file.zip")
16
+ # job = RocketJob::Jobs::ConversionJob.new
17
+ # job.input_category.file_name = "https://example.org/file.zip"
18
18
  # job.output_category.file_name = "data.json.gz"
19
19
  # job.save!
20
20
  #
21
21
  module RocketJob
22
- class ConversionJob < RocketJob::Job
23
- include RocketJob::Batch
22
+ module Jobs
23
+ class ConversionJob < RocketJob::Job
24
+ include RocketJob::Batch
24
25
 
25
- self.destroy_on_complete = false
26
+ self.destroy_on_complete = false
26
27
 
27
- # Detects file extension for its type
28
- input_category format: :auto
29
- output_category format: :auto
28
+ # Detects file extension for its type
29
+ input_category format: :auto
30
+ output_category format: :auto
30
31
 
31
- # When the job completes it will write the result to the output_category.file_name
32
- after_batch :download
32
+ # Upload the file specified in `input_category.file_name` unless already uploaded.
33
+ before_batch :upload, unless: :record_count
33
34
 
34
- def perform(hash)
35
- # For this job return the input hash record as-is. Could be transformed here as needed.
36
- hash
35
+ # When the job completes it will write the result to `output_category.file_name`.
36
+ after_batch :cleanup!, :download
37
+
38
+ def perform(hash)
39
+ hash
40
+ end
37
41
  end
38
42
  end
39
43
  end
@@ -30,59 +30,48 @@ module RocketJob
30
30
  #
31
31
  # If another DirmonJob instance is already queued or running, then the create
32
32
  # above will fail with:
33
- # MongoMapper::DocumentNotValid: Validation failed: State Another instance of this job is already queued or running
33
+ # Validation failed: State Another instance of this job is already queued or running
34
34
  #
35
35
  # Or to start DirmonJob and ignore errors if already running
36
36
  # RocketJob::Jobs::DirmonJob.create
37
37
  class DirmonJob < RocketJob::Job
38
- # Only allow one DirmonJob instance to be running at a time
39
- include RocketJob::Plugins::Singleton
40
- # Start a new job when this one completes, fails, or aborts
41
- include RocketJob::Plugins::Restart
38
+ include RocketJob::Plugins::Cron
42
39
 
43
- self.priority = 30
44
-
45
- # Number of seconds between directory scans. Default 5 mins
46
- field :check_seconds, type: Float, default: 300.0, copy_on_restart: true
40
+ # Runs every 5 minutes by default
41
+ self.cron_schedule = "*/5 * * * * UTC"
42
+ self.description = "Directory Monitor"
43
+ self.priority = 30
47
44
 
48
45
  # Hash[file_name, size]
49
46
  field :previous_file_names, type: Hash, default: {}, copy_on_restart: true
50
47
 
51
- before_create :set_run_at
52
-
53
- # Iterate over each Dirmon entry looking for new files
54
- # If a new file is found, it is not processed immediately, instead
55
- # it is passed to the next run of this job along with the file size.
56
- # If the file size has not changed, the Job is kicked off.
48
+ # Checks the directories for new files, starting jobs if files have not changed since the last run.
57
49
  def perform
58
50
  check_directories
59
51
  end
60
52
 
61
53
  private
62
54
 
63
- # Set a run_at when a new instance of this job is created
64
- def set_run_at
65
- self.run_at = Time.now + check_seconds
66
- end
67
-
68
- # Checks the directories for new files, starting jobs if files have not changed
69
- # since the last run
55
+ # Iterate over each Dirmon Entry looking for new files
56
+ # If a new file is found, it is not processed immediately, instead
57
+ # it is passed to the next run of this job along with the file size.
58
+ # If the file size has not changed, the Job is kicked off.
70
59
  def check_directories
71
60
  new_file_names = {}
72
- DirmonEntry.enabled.each do |entry|
73
- entry.each do |iopath|
74
- # S3 files are only visible once completely uploaded.
75
- unless iopath.partial_files_visible?
76
- logger.info("File: #{iopath}. Starting: #{entry.job_class_name}")
77
- entry.later(iopath)
61
+ DirmonEntry.enabled.each do |dirmon_entry|
62
+ dirmon_entry.each do |path|
63
+ # Skip file size checking since S3 files are only visible once completely uploaded.
64
+ unless path.partial_files_visible?
65
+ logger.info("File: #{path}. Starting: #{dirmon_entry.job_class_name}")
66
+ dirmon_entry.later(path)
78
67
  next
79
68
  end
80
69
 
81
70
  # BSON Keys cannot contain periods
82
- key = iopath.to_s.tr(".", "_")
71
+ key = path.to_s.tr(".", "_")
83
72
  previous_size = previous_file_names[key]
84
73
  # Check every few minutes for a file size change before trying to process the file.
85
- size = check_file(entry, iopath, previous_size)
74
+ size = check_file(dirmon_entry, path, previous_size)
86
75
  new_file_names[key] = size if size
87
76
  end
88
77
  end
@@ -91,14 +80,14 @@ module RocketJob
91
80
 
92
81
  # Checks if a file should result in starting a job
93
82
  # Returns [Integer] file size, or nil if the file started a job
94
- def check_file(entry, iopath, previous_size)
95
- size = iopath.size
83
+ def check_file(dirmon_entry, path, previous_size)
84
+ size = path.size
96
85
  if previous_size && (previous_size == size)
97
- logger.info("File stabilized: #{iopath}. Starting: #{entry.job_class_name}")
98
- entry.later(iopath)
86
+ logger.info("File stabilized: #{path}. Starting: #{dirmon_entry.job_class_name}")
87
+ dirmon_entry.later(path)
99
88
  nil
100
89
  else
101
- logger.info("Found file: #{iopath}. File size: #{size}")
90
+ logger.info("Found file: #{path}. File size: #{size}")
102
91
  # Keep for the next run
103
92
  size
104
93
  end
@@ -27,12 +27,11 @@ module RocketJob
27
27
  # )
28
28
  class HousekeepingJob < RocketJob::Job
29
29
  include RocketJob::Plugins::Cron
30
- include RocketJob::Plugins::Singleton
31
30
 
32
- self.priority = 25
33
- self.description = "Cleans out historical jobs, and zombie servers."
34
- # Runs every 15 minutes
35
- self.cron_schedule = "*/15 * * * * UTC"
31
+ # Runs every 15 minutes on the 15 minute period
32
+ self.cron_schedule = "0,15,30,45 * * * * UTC"
33
+ self.description = "Cleans out historical jobs, and zombie servers."
34
+ self.priority = 25
36
35
 
37
36
  # Whether to destroy zombie servers automatically
38
37
  field :destroy_zombies, type: Mongoid::Boolean, default: true, user_editable: true, copy_on_restart: true
@@ -65,27 +65,29 @@ module RocketJob
65
65
  module Jobs
66
66
  class OnDemandBatchJob < RocketJob::Job
67
67
  include RocketJob::Plugins::Cron
68
+ include RocketJob::Plugins::Retry
68
69
  include RocketJob::Batch
69
70
  include RocketJob::Batch::Statistics
70
71
 
71
72
  self.priority = 90
72
- self.description = "Batch Job"
73
+ self.description = "On Demand Batch Job"
73
74
  self.destroy_on_complete = false
75
+ self.retry_limit = 0
74
76
 
75
77
  # Code that is performed against every row / record.
76
- field :code, type: String
78
+ field :code, type: String, user_editable: true, copy_on_restart: true
77
79
 
78
80
  # Optional code to execute before the batch is run.
79
81
  # Usually to upload data into the job.
80
- field :before_code, type: String
82
+ field :before_code, type: String, user_editable: true, copy_on_restart: true
81
83
 
82
84
  # Optional code to execute after the batch is run.
83
85
  # Usually to upload data into the job.
84
- field :after_code, type: String
86
+ field :after_code, type: String, user_editable: true, copy_on_restart: true
85
87
 
86
88
  # Data that is made available to the job during the perform.
87
89
  # Be sure to store key names only as Strings, not Symbols.
88
- field :data, type: Hash, default: {}
90
+ field :data, type: Hash, default: {}, user_editable: true, copy_on_restart: true
89
91
 
90
92
  validates :code, presence: true
91
93
  validate :validate_code
@@ -143,6 +145,10 @@ module RocketJob
143
145
  rescue Exception => e
144
146
  errors.add(field, "Failed to load :#{field}, #{e.inspect}")
145
147
  end
148
+
149
+ # Allow multiple instances of this job to run with the same cron schedule
150
+ def rocket_job_cron_singleton_check
151
+ end
146
152
  end
147
153
  end
148
154
  end
@@ -78,8 +78,8 @@ module RocketJob
78
78
  self.retry_limit = 0
79
79
 
80
80
  # Be sure to store key names only as Strings, not Symbols
81
- field :data, type: Hash, default: {}, copy_on_restart: true
82
- field :code, type: String, copy_on_restart: true
81
+ field :data, type: Hash, default: {}, user_editable: true, copy_on_restart: true
82
+ field :code, type: String, user_editable: true, copy_on_restart: true
83
83
 
84
84
  validates :code, presence: true
85
85
  validate :validate_code
@@ -97,6 +97,10 @@ module RocketJob
97
97
  rescue Exception => e
98
98
  errors.add(:code, "Failed to parse :code, #{e.inspect}")
99
99
  end
100
+
101
+ # Allow multiple instances of this job to run with the same cron schedule
102
+ def rocket_job_cron_singleton_check
103
+ end
100
104
  end
101
105
  end
102
106
  end
@@ -57,6 +57,10 @@ module RocketJob
57
57
 
58
58
  def upload_file(job)
59
59
  if job.respond_to?(:upload)
60
+ # Return the database connection for this thread back to the connection pool
61
+ # in case the upload takes a long time and the database connection expires.
62
+ ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
63
+
60
64
  if original_file_name
61
65
  job.upload(upload_file_name, file_name: original_file_name)
62
66
  else
@@ -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
- private
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
- # Prevent auto restart if this job does not have a cron schedule.
29
- # Overrides: RocketJob::Plugins::Restart#rocket_job_restart_new_instance
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
- super
34
- end
59
+ self.run_at = Fugit::Cron.new(cron_schedule).next_time.to_utc_time
60
+ end
35
61
 
36
- # On failure:
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
- rocket_job_restart_new_instance
44
- update_attribute(:cron_schedule, nil)
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
- def rocket_job_cron_set_run_at
49
- return if cron_schedule.nil? || !(cron_schedule_changed? && !run_at_changed?)
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.run_at = Fugit::Cron.new(cron_schedule).next_time.to_utc_time
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
@@ -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
@@ -2,128 +2,21 @@ require "active_support/concern"
2
2
 
3
3
  module RocketJob
4
4
  module Plugins
5
- # Automatically starts a new instance of this job anytime it fails, aborts, or completes.
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 :rocket_job_restart_new_instance
82
- after_complete :rocket_job_restart_new_instance
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
- job_attrs =
95
- rocket_job_restart_attributes.each_with_object({}) { |attr, attrs| attrs[attr] = send(attr) }
96
- job = self.class.new(job_attrs)
97
-
98
- # Copy across input and output categories to new scheduled job so that all of the
99
- # settings are remembered between instance. Example: slice_size
100
- job.input_categories = input_categories if respond_to?(:input_categories)
101
- job.output_categories = output_categories if respond_to?(:output_categories)
102
-
103
- rocket_job_restart_save(job)
104
- end
105
-
106
17
  def rocket_job_restart_abort
107
18
  new_record? ? abort : abort!
108
19
  end
109
-
110
- # Allow Singleton to prevent the creation of a new job if one is already running
111
- # Retry since the delete may not have persisted to disk yet.
112
- def rocket_job_restart_save(job, retry_limit = 10, sleep_interval = 0.5)
113
- count = 0
114
- while count < retry_limit
115
- if job.save
116
- logger.info("Created a new job instance: #{job.id}")
117
- return true
118
- else
119
- logger.info("Job already active, retrying after a short sleep")
120
- sleep(sleep_interval)
121
- end
122
- count += 1
123
- end
124
- logger.error("New job instance not started: #{job.errors.messages.inspect}")
125
- false
126
- end
127
20
  end
128
21
  end
129
22
  end
@@ -36,8 +36,8 @@ module RocketJob
36
36
  raise(ArgumentError, "Cannot supply both a method name and a block") if methods.size.positive? && block
37
37
  raise(ArgumentError, "Must supply either a method name or a block") unless methods.size.positive? || block
38
38
 
39
- # TODO: Somehow get AASM to support options such as :if and :unless to be consistent with other callbacks
40
- # For example:
39
+ # Limitation with AASM. It only supports guards on event transitions, not for callbacks.
40
+ # For example, AASM does not support callback options such as :if and :unless, yet Rails callbacks do.
41
41
  # before_start :my_callback, unless: :encrypted?
42
42
  # before_start :my_callback, if: :encrypted?
43
43
  event = aasm.state_machine.events[event_name]
@@ -11,17 +11,22 @@ module RocketJob
11
11
  extend ActiveSupport::Concern
12
12
 
13
13
  included do
14
- class_attribute :dependent_jobs
15
- self.dependent_jobs = nil
14
+ field :dependent_jobs, type: Array, class_attribute: true, user_editable: true, copy_on_restart: true
16
15
 
17
- define_throttle :dependent_job_exists?
18
- define_batch_throttle :dependent_job_exists? if respond_to?(:define_batch_throttle)
16
+ define_throttle :dependent_jobs_running?
17
+ define_batch_throttle :dependent_jobs_running? if respond_to?(:define_batch_throttle)
18
+ end
19
+
20
+ class_methods do
21
+ def depends_on_job(*jobs)
22
+ self.dependent_jobs = Array(jobs).collect(&:to_s)
23
+ end
19
24
  end
20
25
 
21
26
  private
22
27
 
23
28
  # Checks if there are any dependent jobs are running
24
- def dependent_job_exists?
29
+ def dependent_jobs_running?
25
30
  return false if dependent_jobs.blank?
26
31
 
27
32
  jobs_count = RocketJob::Job.running.where(:_type.in => dependent_jobs).count