rocketjob 5.1.1 → 5.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rocketjob +2 -2
  3. data/bin/rocketjob_batch_perf +1 -1
  4. data/bin/rocketjob_perf +1 -1
  5. data/lib/rocket_job/active_worker.rb +1 -0
  6. data/lib/rocket_job/batch.rb +16 -17
  7. data/lib/rocket_job/batch/callbacks.rb +1 -2
  8. data/lib/rocket_job/batch/io.rb +10 -6
  9. data/lib/rocket_job/batch/logger.rb +2 -2
  10. data/lib/rocket_job/batch/lower_priority.rb +2 -2
  11. data/lib/rocket_job/batch/model.rb +23 -23
  12. data/lib/rocket_job/batch/performance.rb +19 -21
  13. data/lib/rocket_job/batch/result.rb +1 -1
  14. data/lib/rocket_job/batch/results.rb +1 -1
  15. data/lib/rocket_job/batch/state_machine.rb +5 -6
  16. data/lib/rocket_job/batch/statistics.rb +10 -8
  17. data/lib/rocket_job/batch/tabular.rb +2 -2
  18. data/lib/rocket_job/batch/tabular/input.rb +11 -7
  19. data/lib/rocket_job/batch/tabular/output.rb +1 -1
  20. data/lib/rocket_job/batch/throttle.rb +11 -30
  21. data/lib/rocket_job/batch/{throttle_running_slices.rb → throttle_running_workers.rb} +13 -10
  22. data/lib/rocket_job/batch/worker.rb +102 -85
  23. data/lib/rocket_job/cli.rb +57 -54
  24. data/lib/rocket_job/config.rb +8 -10
  25. data/lib/rocket_job/dirmon_entry.rb +13 -10
  26. data/lib/rocket_job/event.rb +16 -16
  27. data/lib/rocket_job/extensions/mongo/logging.rb +2 -2
  28. data/lib/rocket_job/extensions/mongoid/clients/options.rb +2 -2
  29. data/lib/rocket_job/extensions/mongoid/contextual/mongo.rb +4 -2
  30. data/lib/rocket_job/extensions/mongoid/factory.rb +13 -5
  31. data/lib/rocket_job/extensions/rocket_job_adapter.rb +2 -1
  32. data/lib/rocket_job/job_exception.rb +0 -3
  33. data/lib/rocket_job/jobs/dirmon_job.rb +4 -4
  34. data/lib/rocket_job/jobs/housekeeping_job.rb +7 -7
  35. data/lib/rocket_job/jobs/on_demand_batch_job.rb +14 -4
  36. data/lib/rocket_job/jobs/on_demand_job.rb +3 -3
  37. data/lib/rocket_job/jobs/performance_job.rb +1 -1
  38. data/lib/rocket_job/jobs/re_encrypt/relational_job.rb +11 -10
  39. data/lib/rocket_job/jobs/upload_file_job.rb +9 -5
  40. data/lib/rocket_job/performance.rb +24 -22
  41. data/lib/rocket_job/plugins/cron.rb +7 -3
  42. data/lib/rocket_job/plugins/document.rb +7 -5
  43. data/lib/rocket_job/plugins/job/callbacks.rb +1 -1
  44. data/lib/rocket_job/plugins/job/logger.rb +3 -3
  45. data/lib/rocket_job/plugins/job/model.rb +34 -27
  46. data/lib/rocket_job/plugins/job/persistence.rb +7 -34
  47. data/lib/rocket_job/plugins/job/state_machine.rb +5 -4
  48. data/lib/rocket_job/plugins/job/throttle.rb +12 -28
  49. data/lib/rocket_job/plugins/job/throttle_running_jobs.rb +2 -2
  50. data/lib/rocket_job/plugins/job/worker.rb +22 -70
  51. data/lib/rocket_job/plugins/processing_window.rb +5 -4
  52. data/lib/rocket_job/plugins/restart.rb +3 -3
  53. data/lib/rocket_job/plugins/retry.rb +2 -2
  54. data/lib/rocket_job/plugins/singleton.rb +1 -2
  55. data/lib/rocket_job/plugins/state_machine.rb +4 -4
  56. data/lib/rocket_job/plugins/transaction.rb +1 -1
  57. data/lib/rocket_job/rocket_job.rb +5 -4
  58. data/lib/rocket_job/server.rb +2 -2
  59. data/lib/rocket_job/server/model.rb +14 -13
  60. data/lib/rocket_job/server/state_machine.rb +1 -2
  61. data/lib/rocket_job/sliced/compressed_slice.rb +4 -4
  62. data/lib/rocket_job/sliced/encrypted_slice.rb +4 -4
  63. data/lib/rocket_job/sliced/input.rb +16 -16
  64. data/lib/rocket_job/sliced/output.rb +2 -2
  65. data/lib/rocket_job/sliced/slice.rb +43 -20
  66. data/lib/rocket_job/sliced/slices.rb +14 -11
  67. data/lib/rocket_job/subscriber.rb +6 -6
  68. data/lib/rocket_job/subscribers/logger.rb +3 -3
  69. data/lib/rocket_job/supervisor.rb +12 -12
  70. data/lib/rocket_job/supervisor/shutdown.rb +7 -7
  71. data/lib/rocket_job/throttle_definition.rb +37 -0
  72. data/lib/rocket_job/throttle_definitions.rb +39 -0
  73. data/lib/rocket_job/version.rb +1 -1
  74. data/lib/rocket_job/worker.rb +116 -34
  75. data/lib/rocket_job/worker_pool.rb +6 -6
  76. data/lib/rocketjob.rb +72 -76
  77. metadata +16 -18
  78. data/lib/rocket_job/extensions/mongoid_5/clients/options.rb +0 -38
  79. data/lib/rocket_job/extensions/mongoid_5/contextual/mongo.rb +0 -64
  80. data/lib/rocket_job/extensions/mongoid_5/factory.rb +0 -13
@@ -1,4 +1,4 @@
1
- require 'active_support/concern'
1
+ require "active_support/concern"
2
2
 
3
3
  module RocketJob
4
4
  module Plugins
@@ -49,8 +49,8 @@ module RocketJob
49
49
  validates_each :processing_schedule do |record, attr, value|
50
50
  begin
51
51
  RocketJob::Plugins::Rufus::CronLine.new(value)
52
- rescue ArgumentError => exc
53
- record.errors.add(attr, exc.message)
52
+ rescue ArgumentError => e
53
+ record.errors.add(attr, e.message)
54
54
  end
55
55
  end
56
56
  end
@@ -68,8 +68,9 @@ module RocketJob
68
68
  # Only process this job if it is still in its processing window
69
69
  def rocket_job_processing_window_check
70
70
  return if rocket_job_processing_window_active?
71
+
71
72
  logger.warn("Processing window closed before job was processed. Job is re-scheduled to run at: #{rocket_job_processing_schedule.next_time}")
72
- self.worker_name ||= 'inline'
73
+ self.worker_name ||= "inline"
73
74
  requeue!(worker_name)
74
75
  end
75
76
 
@@ -1,4 +1,4 @@
1
- require 'active_support/concern'
1
+ require "active_support/concern"
2
2
 
3
3
  module RocketJob
4
4
  module Plugins
@@ -88,7 +88,7 @@ module RocketJob
88
88
  # Run again in the future, even if this run fails with an exception
89
89
  def rocket_job_restart_new_instance
90
90
  if expired?
91
- logger.info('Job has expired. Not creating a new instance.')
91
+ logger.info("Job has expired. Not creating a new instance.")
92
92
  return
93
93
  end
94
94
  attributes = rocket_job_restart_attributes.each_with_object({}) { |attr, attrs| attrs[attr] = send(attr) }
@@ -109,7 +109,7 @@ module RocketJob
109
109
  logger.info("Created a new job instance: #{job.id}")
110
110
  return true
111
111
  else
112
- logger.info('Job already active, retrying after a short sleep')
112
+ logger.info("Job already active, retrying after a short sleep")
113
113
  sleep(sleep_interval)
114
114
  end
115
115
  count += 1
@@ -1,4 +1,4 @@
1
- require 'active_support/concern'
1
+ require "active_support/concern"
2
2
 
3
3
  module RocketJob
4
4
  module Plugins
@@ -76,7 +76,7 @@ module RocketJob
76
76
  # Returns [Time] when to retry this job at
77
77
  # Same basic formula as Delayed Job
78
78
  def rocket_job_retry_seconds_to_delay
79
- (rocket_job_failure_count ** 4) + 15 + (rand(30) * (rocket_job_failure_count + 1))
79
+ (rocket_job_failure_count**4) + 15 + (rand(30) * (rocket_job_failure_count + 1))
80
80
  end
81
81
  end
82
82
  end
@@ -1,4 +1,4 @@
1
- require 'active_support/concern'
1
+ require "active_support/concern"
2
2
 
3
3
  module RocketJob
4
4
  module Plugins
@@ -25,7 +25,6 @@ module RocketJob
25
25
 
26
26
  errors.add(:state, "Another instance of #{self.class.name} is already running, queued, or paused")
27
27
  end
28
-
29
28
  end
30
29
  end
31
30
  end
@@ -1,5 +1,5 @@
1
- require 'active_support/concern'
2
- require 'aasm'
1
+ require "active_support/concern"
2
+ require "aasm"
3
3
 
4
4
  module RocketJob
5
5
  module Plugins
@@ -33,8 +33,8 @@ module RocketJob
33
33
  # Adds a :before or :after callback to an event
34
34
  # state_machine_add_event_callback(:start, :before, :my_method)
35
35
  def self.state_machine_add_event_callback(event_name, action, *methods, &block)
36
- raise(ArgumentError, 'Cannot supply both a method name and a block') if methods.size.positive? && block
37
- raise(ArgumentError, 'Must supply either a method name or a block') unless methods.size.positive? || block
36
+ raise(ArgumentError, "Cannot supply both a method name and a block") if methods.size.positive? && block
37
+ raise(ArgumentError, "Must supply either a method name or a block") unless methods.size.positive? || block
38
38
 
39
39
  # TODO: Somehow get AASM to support options such as :if and :unless to be consistent with other callbacks
40
40
  # For example:
@@ -1,4 +1,4 @@
1
- require 'active_support/concern'
1
+ require "active_support/concern"
2
2
 
3
3
  module RocketJob
4
4
  module Plugins
@@ -26,20 +26,21 @@ module RocketJob
26
26
  # Returns a human readable duration from the supplied [Float] number of seconds
27
27
  def self.seconds_as_duration(seconds)
28
28
  return nil unless seconds
29
+
29
30
  if seconds >= 86_400.0 # 1 day
30
31
  "#{(seconds / 86_400).to_i}d #{Time.at(seconds).strftime('%-Hh %-Mm')}"
31
32
  elsif seconds >= 3600.0 # 1 hour
32
- Time.at(seconds).strftime('%-Hh %-Mm')
33
+ Time.at(seconds).strftime("%-Hh %-Mm")
33
34
  elsif seconds >= 60.0 # 1 minute
34
- Time.at(seconds).strftime('%-Mm %-Ss')
35
+ Time.at(seconds).strftime("%-Mm %-Ss")
35
36
  elsif seconds >= 1.0 # 1 second
36
- format('%.3fs', seconds)
37
+ format("%.3fs", seconds)
37
38
  else
38
39
  duration = seconds * 1000
39
40
  if defined? JRuby
40
41
  "#{duration.to_i}ms"
41
42
  else
42
- duration < 10.0 ? format('%.3fms', duration) : format('%.1fms', duration)
43
+ duration < 10.0 ? format("%.3fms", duration) : format("%.1fms", duration)
43
44
  end
44
45
  end
45
46
  end
@@ -1,5 +1,5 @@
1
- require 'rocket_job/server/model'
2
- require 'rocket_job/server/state_machine'
1
+ require "rocket_job/server/model"
2
+ require "rocket_job/server/state_machine"
3
3
 
4
4
  module RocketJob
5
5
  # Server
@@ -1,5 +1,5 @@
1
- require 'yaml'
2
- require 'active_support/concern'
1
+ require "yaml"
2
+ require "active_support/concern"
3
3
 
4
4
  module RocketJob
5
5
  class Server
@@ -8,7 +8,7 @@ module RocketJob
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  included do
11
- store_in collection: 'rocket_job.servers'
11
+ store_in collection: "rocket_job.servers"
12
12
 
13
13
  # Unique Name of this server instance
14
14
  # Default: `host name:PID`
@@ -24,13 +24,13 @@ module RocketJob
24
24
  field :started_at, type: Time
25
25
 
26
26
  # The heartbeat information for this server
27
- embeds_one :heartbeat, class_name: 'RocketJob::Heartbeat'
27
+ embeds_one :heartbeat, class_name: "RocketJob::Heartbeat"
28
28
 
29
29
  # Current state
30
30
  # Internal use only. Do not set this field directly
31
31
  field :state, type: Symbol, default: :starting
32
32
 
33
- index({name: 1}, background: true, unique: true, drop_dups: true)
33
+ index({name: 1}, background: true, unique: true)
34
34
 
35
35
  validates_presence_of :state, :name, :max_workers
36
36
 
@@ -58,8 +58,8 @@ module RocketJob
58
58
  # # => {}
59
59
  def self.counts_by_state
60
60
  counts = {}
61
- collection.aggregate([{'$group' => {_id: '$state', count: {'$sum' => 1}}}]).each do |result|
62
- counts[result['_id'].to_sym] = result['count']
61
+ collection.aggregate([{"$group" => {_id: "$state", count: {"$sum" => 1}}}]).each do |result|
62
+ counts[result["_id"].to_sym] = result["count"]
63
63
  end
64
64
  counts
65
65
  end
@@ -70,6 +70,7 @@ module RocketJob
70
70
  count = 0
71
71
  each do |server|
72
72
  next unless server.zombie?
73
+
73
74
  logger.warn "Destroying zombie server #{server.name}, and requeueing its jobs"
74
75
  server.destroy
75
76
  count += 1
@@ -83,9 +84,9 @@ module RocketJob
83
84
  last_heartbeat_time = Time.now - dead_seconds
84
85
  where(
85
86
  :state.in => %i[stopping running paused],
86
- '$or' => [
87
- {'heartbeat.updated_at' => {'$exists' => false}},
88
- {'heartbeat.updated_at' => {'$lte' => last_heartbeat_time}}
87
+ "$or" => [
88
+ {"heartbeat.updated_at" => {"$exists" => false}},
89
+ {"heartbeat.updated_at" => {"$lte" => last_heartbeat_time}}
89
90
  ]
90
91
  )
91
92
  end
@@ -100,6 +101,7 @@ module RocketJob
100
101
  def zombie?(missed = 4)
101
102
  return false unless running? || stopping? || paused?
102
103
  return true if heartbeat.nil? || heartbeat.updated_at.nil?
104
+
103
105
  dead_seconds = Config.heartbeat_seconds * missed
104
106
  (Time.now - heartbeat.updated_at) >= dead_seconds
105
107
  end
@@ -108,8 +110,8 @@ module RocketJob
108
110
  def refresh(worker_count)
109
111
  SemanticLogger.silence(:info) do
110
112
  find_and_update(
111
- 'heartbeat.updated_at' => Time.now,
112
- 'heartbeat.workers' => worker_count
113
+ "heartbeat.updated_at" => Time.now,
114
+ "heartbeat.workers" => worker_count
113
115
  )
114
116
  end
115
117
  end
@@ -120,7 +122,6 @@ module RocketJob
120
122
  def requeue_jobs
121
123
  RocketJob::Job.requeue_dead_server(name)
122
124
  end
123
-
124
125
  end
125
126
  end
126
127
  end
@@ -1,4 +1,4 @@
1
- require 'active_support/concern'
1
+ require "active_support/concern"
2
2
 
3
3
  module RocketJob
4
4
  class Server
@@ -54,7 +54,6 @@ module RocketJob
54
54
  paused.each(&:resume!)
55
55
  end
56
56
  end
57
-
58
57
  end
59
58
  end
60
59
  end
@@ -1,4 +1,4 @@
1
- require 'zlib'
1
+ require "zlib"
2
2
  module RocketJob
3
3
  module Sliced
4
4
  # Compress the records within a slice
@@ -6,20 +6,20 @@ module RocketJob
6
6
  private
7
7
 
8
8
  def parse_records
9
- records = attributes.delete('records')
9
+ records = attributes.delete("records")
10
10
 
11
11
  # Convert BSON::Binary to a string
12
12
  binary_str = records.data
13
13
 
14
14
  str = Zlib::Inflate.inflate(binary_str)
15
- @records = Hash.from_bson(BSON::ByteBuffer.new(str))['r']
15
+ @records = Hash.from_bson(BSON::ByteBuffer.new(str))["r"]
16
16
  end
17
17
 
18
18
  def serialize_records
19
19
  return [] if @records.nil? || @records.empty?
20
20
 
21
21
  # Convert slice of records into a single string
22
- str = {'r' => records.to_a}.to_bson.to_s
22
+ str = {"r" => records.to_a}.to_bson.to_s
23
23
 
24
24
  data = Zlib::Deflate.deflate(str)
25
25
  BSON::Binary.new(data)
@@ -1,4 +1,4 @@
1
- require 'symmetric-encryption'
1
+ require "symmetric-encryption"
2
2
  module RocketJob
3
3
  module Sliced
4
4
  # Compress the records within a slice
@@ -6,7 +6,7 @@ module RocketJob
6
6
  private
7
7
 
8
8
  def parse_records
9
- records = attributes.delete('records')
9
+ records = attributes.delete("records")
10
10
 
11
11
  # Convert BSON::Binary to a string
12
12
  binary_str = records.data
@@ -16,14 +16,14 @@ module RocketJob
16
16
  # Use the header that is present to decrypt the data, since its version could be different
17
17
  str = header.cipher.binary_decrypt(binary_str, header: header)
18
18
 
19
- @records = Hash.from_bson(BSON::ByteBuffer.new(str))['r']
19
+ @records = Hash.from_bson(BSON::ByteBuffer.new(str))["r"]
20
20
  end
21
21
 
22
22
  def serialize_records
23
23
  return [] if @records.nil? || @records.empty?
24
24
 
25
25
  # Convert slice of records into a single string
26
- str = {'r' => to_a}.to_bson.to_s
26
+ str = {"r" => to_a}.to_bson.to_s
27
27
 
28
28
  # Encrypt to binary without applying an encoding such as Base64
29
29
  # Use a random_iv with each encryption for better security
@@ -5,9 +5,9 @@ module RocketJob
5
5
  # Create indexes before uploading
6
6
  create_indexes
7
7
  Writer::Input.collect(self, on_first: on_first, &block)
8
- rescue StandardError => exc
8
+ rescue StandardError => e
9
9
  drop
10
- raise(exc)
10
+ raise(e)
11
11
  end
12
12
 
13
13
  def upload_mongo_query(criteria, *column_names, &block)
@@ -19,7 +19,7 @@ module RocketJob
19
19
  options[:projection] = options.delete(:fields) if options.key?(:fields)
20
20
  else
21
21
  column_names = column_names.collect(&:to_s)
22
- column_names << '_id' if column_names.size.zero?
22
+ column_names << "_id" if column_names.size.zero?
23
23
 
24
24
  fields = options.delete(:fields) || {}
25
25
  column_names.each { |col| fields[col] = 1 }
@@ -73,9 +73,9 @@ module RocketJob
73
73
  count += 1
74
74
  end
75
75
  count
76
- rescue StandardError => exc
76
+ rescue StandardError => e
77
77
  drop
78
- raise(exc)
78
+ raise(e)
79
79
  end
80
80
 
81
81
  def upload_integer_range_in_reverse_order(start_id, last_id)
@@ -91,9 +91,9 @@ module RocketJob
91
91
  count += 1
92
92
  end
93
93
  count
94
- rescue StandardError => exc
94
+ rescue StandardError => e
95
95
  drop
96
- raise(exc)
96
+ raise(e)
97
97
  end
98
98
 
99
99
  # Iterate over each failed record, if any
@@ -116,16 +116,16 @@ module RocketJob
116
116
  # Requeue all failed slices
117
117
  def requeue_failed
118
118
  failed.update_all(
119
- '$unset' => {worker_name: nil, started_at: nil},
120
- '$set' => {state: :queued}
119
+ "$unset" => {worker_name: nil, started_at: nil},
120
+ "$set" => {state: :queued}
121
121
  )
122
122
  end
123
123
 
124
124
  # Requeue all running slices for a server or worker that is no longer available
125
125
  def requeue_running(worker_name)
126
126
  running.where(worker_name: /\A#{worker_name}/).update_all(
127
- '$unset' => {worker_name: nil, started_at: nil},
128
- '$set' => {state: :queued}
127
+ "$unset" => {worker_name: nil, started_at: nil},
128
+ "$set" => {state: :queued}
129
129
  )
130
130
  end
131
131
 
@@ -137,11 +137,11 @@ module RocketJob
137
137
  # TODO: Will it perform faster without the id sort?
138
138
  # I.e. Just process on a FIFO basis?
139
139
  document = all.queued.
140
- sort('_id' => 1).
141
- find_one_and_update(
142
- {'$set' => {worker_name: worker_name, state: :running, started_at: Time.now}},
143
- return_document: :after
144
- )
140
+ sort("_id" => 1).
141
+ find_one_and_update(
142
+ {"$set" => {worker_name: worker_name, state: :running, started_at: Time.now}},
143
+ return_document: :after
144
+ )
145
145
  document.collection_name = collection_name if document
146
146
  document
147
147
  end
@@ -1,10 +1,10 @@
1
- require 'tempfile'
1
+ require "tempfile"
2
2
 
3
3
  module RocketJob
4
4
  module Sliced
5
5
  class Output < Slices
6
6
  def download(header_line: nil)
7
- raise(ArgumentError, 'Block is mandatory') unless block_given?
7
+ raise(ArgumentError, "Block is mandatory") unless block_given?
8
8
 
9
9
  # Write the header line
10
10
  yield(header_line) if header_line
@@ -1,4 +1,4 @@
1
- require 'forwardable'
1
+ require "forwardable"
2
2
  module RocketJob
3
3
  module Sliced
4
4
  # A slice is an Array of Records, along with meta-data that is used
@@ -21,12 +21,11 @@ module RocketJob
21
21
  include RocketJob::Plugins::StateMachine
22
22
  extend Forwardable
23
23
 
24
- store_in client: 'rocketjob_slices'
24
+ store_in client: "rocketjob_slices"
25
25
 
26
26
  # The record number of the first record in this slice.
27
- #
28
- # Optional: If present the record_number is set while the job
29
- # is being processed.
27
+ # Useful in knowing the line number of each record in this slice
28
+ # relative to the original file that was uploaded.
30
29
  field :first_record_number, type: Integer
31
30
 
32
31
  #
@@ -42,11 +41,14 @@ module RocketJob
42
41
  # Number of times that this job has failed to process
43
42
  field :failure_count, type: Integer
44
43
 
44
+ # Number of the record within this slice (not the entire file/job) currently being processed. (One based index)
45
+ field :processing_record_number, type: Integer
46
+
45
47
  # This name of the worker that this job is being processed by, or was processed by
46
48
  field :worker_name, type: String
47
49
 
48
50
  # The last exception for this slice if any
49
- embeds_one :exception, class_name: 'RocketJob::JobException'
51
+ embeds_one :exception, class_name: "RocketJob::JobException"
50
52
 
51
53
  after_find :parse_records
52
54
 
@@ -108,12 +110,16 @@ module RocketJob
108
110
  def_instance_delegators :records, :each, :<<, :size, :concat, :at
109
111
  def_instance_delegators :records, *(Enumerable.instance_methods - Module.methods)
110
112
 
111
- # Fail this slice, along with the exception that caused the failure
112
- def set_exception(exc = nil, record_number = nil)
113
+ # Returns [Integer] the record number of the record currently being processed relative to the entire file.
114
+ def current_record_number
115
+ first_record_number.to_i + processing_record_number.to_i
116
+ end
117
+
118
+ # Before Fail save the exception to this slice.
119
+ def set_exception(exc = nil)
113
120
  if exc
114
- self.exception = JobException.from_exception(exc)
115
- exception.worker_name = worker_name
116
- exception.record_number = record_number
121
+ self.exception = JobException.from_exception(exc)
122
+ exception.worker_name = worker_name
117
123
  end
118
124
  self.failure_count = failure_count.to_i + 1
119
125
  self.worker_name = nil
@@ -122,8 +128,8 @@ module RocketJob
122
128
  # Returns the failed record.
123
129
  # Returns [nil] if there is no failed record
124
130
  def failed_record
125
- if exception && (record_number = exception.record_number)
126
- at(record_number - 1)
131
+ if exception && processing_record_number
132
+ at(processing_record_number - 1)
127
133
  end
128
134
  end
129
135
 
@@ -132,13 +138,13 @@ module RocketJob
132
138
  if ::Mongoid::VERSION.to_i >= 6
133
139
  def as_attributes
134
140
  attrs = super
135
- attrs['records'] = serialize_records if @records
141
+ attrs["records"] = serialize_records if @records
136
142
  attrs
137
143
  end
138
144
  else
139
145
  def as_document
140
146
  attrs = super
141
- attrs['records'] = serialize_records if @records
147
+ attrs["records"] = serialize_records if @records
142
148
  attrs
143
149
  end
144
150
  end
@@ -147,25 +153,42 @@ module RocketJob
147
153
  "#{super[0...-1]}, records: #{@records.inspect}, collection_name: #{collection_name.inspect}>"
148
154
  end
149
155
 
156
+ # Fail this slice if an exception occurs during processing.
157
+ def fail_on_exception!(re_raise_exceptions = false, &block)
158
+ SemanticLogger.named_tagged(slice: id.to_s, &block)
159
+ rescue Exception => e
160
+ SemanticLogger.named_tagged(slice: id.to_s) do
161
+ if failed? || !may_fail?
162
+ exception = JobException.from_exception(e)
163
+ exception.worker_name = worker_name
164
+ save! unless new_record? || destroyed?
165
+ elsif new_record? || destroyed?
166
+ fail(e)
167
+ else
168
+ fail!(e)
169
+ end
170
+ raise e if re_raise_exceptions
171
+ end
172
+ end
173
+
150
174
  private
151
175
 
152
176
  # Always add records to any updates.
153
177
  def atomic_updates(*args)
154
- r = super(*args)
155
- if @records
156
- (r['$set'] ||= {})['records'] = serialize_records
157
- end
178
+ r = super(*args)
179
+ (r["$set"] ||= {})["records"] = serialize_records if @records
158
180
  r
159
181
  end
160
182
 
161
183
  def parse_records
162
- @records = attributes.delete('records')
184
+ @records = attributes.delete("records")
163
185
  end
164
186
 
165
187
  def serialize_records
166
188
  records.mongoize
167
189
  end
168
190
 
191
+ # Before Start
169
192
  def set_started_at
170
193
  self.started_at = Time.now
171
194
  end