rocketjob 5.4.0.beta2 → 6.0.0.rc3
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 +149 -5
- data/bin/rocketjob_batch_perf +1 -1
- data/bin/rocketjob_perf +1 -1
- data/lib/rocket_job/batch.rb +3 -1
- data/lib/rocket_job/batch/categories.rb +341 -0
- data/lib/rocket_job/batch/io.rb +128 -60
- 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/tabular.rb +2 -0
- data/lib/rocket_job/batch/tabular/input.rb +8 -6
- data/lib/rocket_job/batch/tabular/output.rb +4 -2
- data/lib/rocket_job/batch/throttle_running_workers.rb +8 -17
- data/lib/rocket_job/batch/worker.rb +27 -24
- data/lib/rocket_job/category/base.rb +78 -0
- data/lib/rocket_job/category/input.rb +110 -0
- data/lib/rocket_job/category/output.rb +25 -0
- data/lib/rocket_job/cli.rb +25 -17
- data/lib/rocket_job/dirmon_entry.rb +22 -12
- 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 +39 -0
- data/lib/rocket_job/jobs/dirmon_job.rb +2 -2
- data/lib/rocket_job/jobs/housekeeping_job.rb +7 -7
- data/lib/rocket_job/jobs/on_demand_batch_job.rb +17 -6
- data/lib/rocket_job/jobs/on_demand_job.rb +1 -2
- 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 +44 -8
- data/lib/rocket_job/lookup_collection.rb +69 -0
- data/lib/rocket_job/plugins/job/model.rb +25 -50
- data/lib/rocket_job/plugins/job/throttle.rb +2 -2
- data/lib/rocket_job/plugins/job/throttle_running_jobs.rb +12 -4
- data/lib/rocket_job/plugins/job/worker.rb +2 -7
- data/lib/rocket_job/plugins/restart.rb +12 -5
- data/lib/rocket_job/plugins/state_machine.rb +2 -1
- data/lib/rocket_job/plugins/throttle_dependent_jobs.rb +38 -0
- data/lib/rocket_job/ractor_worker.rb +42 -0
- data/lib/rocket_job/server/model.rb +1 -1
- data/lib/rocket_job/sliced.rb +15 -70
- data/lib/rocket_job/sliced/bzip2_output_slice.rb +1 -1
- data/lib/rocket_job/sliced/input.rb +1 -1
- data/lib/rocket_job/sliced/slice.rb +5 -13
- data/lib/rocket_job/sliced/slices.rb +14 -2
- data/lib/rocket_job/sliced/writer/output.rb +33 -45
- 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 -26
- data/lib/rocket_job/extensions/mongoid/remove_warnings.rb +0 -12
- data/lib/rocket_job/jobs/on_demand_batch_tabular_job.rb +0 -28
|
@@ -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
|
|
@@ -29,20 +29,28 @@ module RocketJob
|
|
|
29
29
|
class_attribute :throttle_running_jobs
|
|
30
30
|
self.throttle_running_jobs = nil
|
|
31
31
|
|
|
32
|
+
# Allow jobs to be throttled by group name instance of the job class name.
|
|
33
|
+
field :throttle_group, type: String, class_attribute: true, user_editable: true, copy_on_restart: true
|
|
34
|
+
|
|
32
35
|
define_throttle :throttle_running_jobs_exceeded?
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
private
|
|
36
39
|
|
|
37
|
-
# Returns [
|
|
40
|
+
# Returns [true|false] whether the throttle for this job has been exceeded
|
|
38
41
|
def throttle_running_jobs_exceeded?
|
|
39
|
-
return unless throttle_running_jobs&.positive?
|
|
42
|
+
return false unless throttle_running_jobs&.positive?
|
|
40
43
|
|
|
41
|
-
# Cannot use this class since it will include instances of parent job classes.
|
|
42
44
|
RocketJob::Job.with(read: {mode: :primary}) do |conn|
|
|
43
|
-
|
|
45
|
+
query = throttle_running_jobs_base_query
|
|
46
|
+
throttle_group ? query["throttle_group"] = throttle_group : query["_type"] = self.class.name
|
|
47
|
+
conn.running.where(query).count >= throttle_running_jobs
|
|
44
48
|
end
|
|
45
49
|
end
|
|
50
|
+
|
|
51
|
+
def throttle_running_jobs_base_query
|
|
52
|
+
{:id.ne => id}
|
|
53
|
+
end
|
|
46
54
|
end
|
|
47
55
|
end
|
|
48
56
|
end
|
|
@@ -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
|
|
@@ -91,8 +91,16 @@ module RocketJob
|
|
|
91
91
|
logger.info("Job has expired. Not creating a new instance.")
|
|
92
92
|
return
|
|
93
93
|
end
|
|
94
|
-
|
|
95
|
-
|
|
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)
|
|
96
104
|
end
|
|
97
105
|
|
|
98
106
|
def rocket_job_restart_abort
|
|
@@ -101,11 +109,10 @@ module RocketJob
|
|
|
101
109
|
|
|
102
110
|
# Allow Singleton to prevent the creation of a new job if one is already running
|
|
103
111
|
# Retry since the delete may not have persisted to disk yet.
|
|
104
|
-
def
|
|
112
|
+
def rocket_job_restart_save(job, retry_limit = 10, sleep_interval = 0.5)
|
|
105
113
|
count = 0
|
|
106
114
|
while count < retry_limit
|
|
107
|
-
job
|
|
108
|
-
if job.persisted?
|
|
115
|
+
if job.save
|
|
109
116
|
logger.info("Created a new job instance: #{job.id}")
|
|
110
117
|
return true
|
|
111
118
|
else
|
|
@@ -51,7 +51,8 @@ module RocketJob
|
|
|
51
51
|
# Validate methods are any of Symbol String Proc
|
|
52
52
|
methods.each do |method|
|
|
53
53
|
unless method.is_a?(Symbol) || method.is_a?(String)
|
|
54
|
-
raise(ArgumentError,
|
|
54
|
+
raise(ArgumentError,
|
|
55
|
+
"#{action}_#{event_name} currently does not support any options. Only Symbol and String method names can be supplied.")
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
58
|
methods
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require "active_support/concern"
|
|
2
|
+
module RocketJob
|
|
3
|
+
module Plugins
|
|
4
|
+
# Prevent this job from starting, or a batch slice from starting if the dependent jobs are running.
|
|
5
|
+
#
|
|
6
|
+
# Features:
|
|
7
|
+
# - Ensures dependent jobs won't run
|
|
8
|
+
# When the throttle has been exceeded all jobs of this class will be ignored until the
|
|
9
|
+
# next refresh. `RocketJob::Config::re_check_seconds` which by default is 60 seconds.
|
|
10
|
+
module ThrottleDependentJobs
|
|
11
|
+
extend ActiveSupport::Concern
|
|
12
|
+
|
|
13
|
+
included do
|
|
14
|
+
class_attribute :dependent_jobs
|
|
15
|
+
self.dependent_jobs = nil
|
|
16
|
+
|
|
17
|
+
define_throttle :dependent_job_exists?
|
|
18
|
+
define_batch_throttle :dependent_job_exists? if respond_to?(:define_batch_throttle)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# Checks if there are any dependent jobs are running
|
|
24
|
+
def dependent_job_exists?
|
|
25
|
+
return false if dependent_jobs.blank?
|
|
26
|
+
|
|
27
|
+
jobs_count = RocketJob::Job.running.where(:_type.in => dependent_jobs).count
|
|
28
|
+
return false if jobs_count.zero?
|
|
29
|
+
|
|
30
|
+
logger.info(
|
|
31
|
+
message: "#{jobs_count} Dependent Jobs are running from #{dependent_jobs.join(', ')}",
|
|
32
|
+
metric: "#{self.class.name}/dependent_jobs_throttle"
|
|
33
|
+
)
|
|
34
|
+
true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module RocketJob
|
|
2
|
+
# Run each worker in its own "Ractor".
|
|
3
|
+
class RactorWorker < Worker
|
|
4
|
+
attr_reader :thread
|
|
5
|
+
|
|
6
|
+
def initialize(id:, server_name:)
|
|
7
|
+
super(id: id, server_name: server_name)
|
|
8
|
+
@shutdown = Concurrent::Event.new
|
|
9
|
+
@thread = Ractor.new(name: "rocketjob-#{id}") { run }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def alive?
|
|
13
|
+
@thread.alive?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def backtrace
|
|
17
|
+
@thread.backtrace
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def join(*args)
|
|
21
|
+
@thread.join(*args)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Send each active worker the RocketJob::ShutdownException so that stops processing immediately.
|
|
25
|
+
def kill
|
|
26
|
+
@thread.raise(Shutdown, "Shutdown due to kill request for worker: #{name}") if @thread.alive?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def shutdown?
|
|
30
|
+
@shutdown.set?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def shutdown!
|
|
34
|
+
@shutdown.set
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns [true|false] whether the shutdown indicator was set
|
|
38
|
+
def wait_for_shutdown?(timeout = nil)
|
|
39
|
+
@shutdown.wait(timeout)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -28,7 +28,7 @@ module RocketJob
|
|
|
28
28
|
|
|
29
29
|
# Current state
|
|
30
30
|
# Internal use only. Do not set this field directly
|
|
31
|
-
field :state, type:
|
|
31
|
+
field :state, type: Mongoid::StringifiedSymbol, default: :starting
|
|
32
32
|
|
|
33
33
|
index({name: 1}, background: true, unique: true)
|
|
34
34
|
|
data/lib/rocket_job/sliced.rb
CHANGED
|
@@ -14,78 +14,23 @@ module RocketJob
|
|
|
14
14
|
autoload :Output, "rocket_job/sliced/writer/output"
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
# Returns [RocketJob::Sliced::Slices] for the relevant
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
# self.compress = {output: true}
|
|
32
|
-
# end
|
|
33
|
-
#
|
|
34
|
-
# To use the specialized BZip output compressor, and the regular compressor for the input collections:
|
|
35
|
-
# class MyJob < RocketJob::Job
|
|
36
|
-
# include RocketJob::Batch
|
|
37
|
-
# self.compress = {output: :bzip2, input: true}
|
|
38
|
-
# end
|
|
39
|
-
def self.factory(type, category, job)
|
|
40
|
-
raise(ArgumentError, "Unknown type: #{type.inspect}") unless %i[input output].include?(type)
|
|
41
|
-
|
|
42
|
-
collection_name = "rocket_job.#{type}s.#{job.id}"
|
|
43
|
-
collection_name << ".#{category}" unless category == :main
|
|
44
|
-
|
|
45
|
-
args = {collection_name: collection_name, slice_size: job.slice_size}
|
|
46
|
-
klass = slice_class(type, job)
|
|
47
|
-
args[:slice_class] = klass if klass
|
|
48
|
-
|
|
49
|
-
if type == :input
|
|
50
|
-
RocketJob::Sliced::Input.new(args)
|
|
17
|
+
# Returns [RocketJob::Sliced::Slices] for the relevant direction and category.
|
|
18
|
+
def self.factory(direction, category, job)
|
|
19
|
+
collection_name = "rocket_job.#{direction}s.#{job.id}"
|
|
20
|
+
collection_name << ".#{category.name}" unless category.name == :main
|
|
21
|
+
|
|
22
|
+
case direction
|
|
23
|
+
when :input
|
|
24
|
+
RocketJob::Sliced::Input.new(
|
|
25
|
+
collection_name: collection_name,
|
|
26
|
+
slice_class: category.serializer_class,
|
|
27
|
+
slice_size: category.slice_size
|
|
28
|
+
)
|
|
29
|
+
when :output
|
|
30
|
+
RocketJob::Sliced::Output.new(collection_name: collection_name, slice_class: category.serializer_class)
|
|
51
31
|
else
|
|
52
|
-
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
# Parses the encrypt and compress options to determine which slice serializer to use.
|
|
59
|
-
# `encrypt` takes priority over any `compress` option.
|
|
60
|
-
def self.slice_class(type, job)
|
|
61
|
-
encrypt = extract_value(type, job.encrypt)
|
|
62
|
-
compress = extract_value(type, job.compress)
|
|
63
|
-
|
|
64
|
-
if encrypt
|
|
65
|
-
case encrypt
|
|
66
|
-
when true
|
|
67
|
-
EncryptedSlice
|
|
68
|
-
else
|
|
69
|
-
raise(ArgumentError, "Unknown job `encrypt` value: #{compress}") unless compress.is_a?(Slices)
|
|
70
|
-
# Returns the supplied class to use for encryption.
|
|
71
|
-
encrypt
|
|
72
|
-
end
|
|
73
|
-
elsif compress
|
|
74
|
-
case compress
|
|
75
|
-
when true
|
|
76
|
-
CompressedSlice
|
|
77
|
-
when :bzip2
|
|
78
|
-
BZip2OutputSlice
|
|
79
|
-
else
|
|
80
|
-
raise(ArgumentError, "Unknown job `compress` value: #{compress}") unless compress.is_a?(Slices)
|
|
81
|
-
# Returns the supplied class to use for compression.
|
|
82
|
-
compress
|
|
83
|
-
end
|
|
32
|
+
raise(ArgumentError, "Unknown direction: #{direction.inspect}")
|
|
84
33
|
end
|
|
85
34
|
end
|
|
86
|
-
|
|
87
|
-
def self.extract_value(type, value)
|
|
88
|
-
value.is_a?(Hash) ? value[type] : value
|
|
89
|
-
end
|
|
90
35
|
end
|
|
91
36
|
end
|
|
@@ -139,7 +139,7 @@ module RocketJob
|
|
|
139
139
|
document = all.queued.
|
|
140
140
|
sort("_id" => 1).
|
|
141
141
|
find_one_and_update(
|
|
142
|
-
{"$set" => {worker_name: worker_name, state:
|
|
142
|
+
{"$set" => {worker_name: worker_name, state: "running", started_at: Time.now}},
|
|
143
143
|
return_document: :after
|
|
144
144
|
)
|
|
145
145
|
document.collection_name = collection_name if document
|
|
@@ -33,7 +33,7 @@ module RocketJob
|
|
|
33
33
|
#
|
|
34
34
|
|
|
35
35
|
# Current state, as set by AASM
|
|
36
|
-
field :state, type:
|
|
36
|
+
field :state, type: Mongoid::StringifiedSymbol, default: :queued
|
|
37
37
|
|
|
38
38
|
# When processing started on this slice
|
|
39
39
|
field :started_at, type: Time
|
|
@@ -139,18 +139,10 @@ module RocketJob
|
|
|
139
139
|
|
|
140
140
|
# Returns [Hash] the slice as a Hash for storage purposes
|
|
141
141
|
# Compresses / Encrypts the slice according to the job setting
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
attrs
|
|
147
|
-
end
|
|
148
|
-
else
|
|
149
|
-
def as_document
|
|
150
|
-
attrs = super
|
|
151
|
-
attrs["records"] = serialize_records if @records
|
|
152
|
-
attrs
|
|
153
|
-
end
|
|
142
|
+
def as_attributes
|
|
143
|
+
attrs = super
|
|
144
|
+
attrs["records"] = serialize_records if @records
|
|
145
|
+
attrs
|
|
154
146
|
end
|
|
155
147
|
|
|
156
148
|
def inspect
|
|
@@ -50,8 +50,8 @@ module RocketJob
|
|
|
50
50
|
|
|
51
51
|
# Returns output slices in the order of their id
|
|
52
52
|
# which is usually the order in which they were written.
|
|
53
|
-
def each
|
|
54
|
-
all.sort(id: 1).each
|
|
53
|
+
def each(&block)
|
|
54
|
+
all.sort(id: 1).each(&block)
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
# Insert a new slice into the collection
|
|
@@ -96,6 +96,17 @@ module RocketJob
|
|
|
96
96
|
slice
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
# Append to an existing slice if already present
|
|
100
|
+
def append(slice, input_slice)
|
|
101
|
+
existing_slice = all.where(id: input_slice.id).first
|
|
102
|
+
return insert(slice, input_slice) unless existing_slice
|
|
103
|
+
|
|
104
|
+
extra_records = slice.is_a?(Slice) ? slice.records : slice
|
|
105
|
+
existing_slice.records = existing_slice.records + extra_records
|
|
106
|
+
existing_slice.save!
|
|
107
|
+
existing_slice
|
|
108
|
+
end
|
|
109
|
+
|
|
99
110
|
alias << insert
|
|
100
111
|
|
|
101
112
|
# Index for find_and_modify only if it is not already present
|
|
@@ -139,6 +150,7 @@ module RocketJob
|
|
|
139
150
|
def last
|
|
140
151
|
all.sort("_id" => -1).first
|
|
141
152
|
end
|
|
153
|
+
|
|
142
154
|
# rubocop:enable Style/RedundantSort
|
|
143
155
|
|
|
144
156
|
# Returns [Array<Struct>] grouped exceptions by class name,
|
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
module RocketJob
|
|
2
2
|
module Sliced
|
|
3
3
|
module Writer
|
|
4
|
-
|
|
5
|
-
class Output
|
|
4
|
+
class Null
|
|
6
5
|
attr_reader :job, :categorized_records
|
|
7
|
-
attr_accessor :input_slice
|
|
8
|
-
|
|
9
|
-
# Collect output results and write to output collections
|
|
10
|
-
# iff job is collecting output
|
|
11
|
-
# Notes:
|
|
12
|
-
# Nothing is saved if an exception is raised inside the block
|
|
13
|
-
def self.collect(job, input_slice = nil)
|
|
14
|
-
if job.collect_output?
|
|
15
|
-
writer = new(job, input_slice)
|
|
16
|
-
yield(writer)
|
|
17
|
-
writer.close
|
|
18
|
-
else
|
|
19
|
-
writer = NullWriter.new(job, input_slice)
|
|
20
|
-
yield(writer)
|
|
21
|
-
end
|
|
22
|
-
end
|
|
6
|
+
attr_accessor :input_slice, :append
|
|
23
7
|
|
|
24
|
-
def initialize(job, input_slice
|
|
8
|
+
def initialize(job, input_slice: nil, append: false)
|
|
25
9
|
@job = job
|
|
26
10
|
@input_slice = input_slice
|
|
27
11
|
@categorized_records = {}
|
|
12
|
+
@append = append
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def <<(_)
|
|
16
|
+
# noop
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def close
|
|
20
|
+
# noop
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Internal class for writing categorized results into output slices
|
|
25
|
+
class Output < Null
|
|
26
|
+
# Collect output results and write to output collections
|
|
27
|
+
# iff job is collecting output
|
|
28
|
+
# Notes:
|
|
29
|
+
# Partial slices are saved when an exception is raised inside the block
|
|
30
|
+
def self.collect(job, **args)
|
|
31
|
+
writer = job.output_categories.present? ? new(job, **args) : Null.new(job, **args)
|
|
32
|
+
yield(writer)
|
|
33
|
+
ensure
|
|
34
|
+
writer&.close
|
|
28
35
|
end
|
|
29
36
|
|
|
30
37
|
# Writes the supplied result, RocketJob::Batch::Result or RocketJob::Batch::Results
|
|
@@ -40,7 +47,8 @@ module RocketJob
|
|
|
40
47
|
# Write categorized results to their relevant collections
|
|
41
48
|
def close
|
|
42
49
|
categorized_records.each_pair do |category, results|
|
|
43
|
-
job.output(category)
|
|
50
|
+
collection = job.output(category)
|
|
51
|
+
append ? collection.append(results, input_slice) : collection.insert(results, input_slice)
|
|
44
52
|
end
|
|
45
53
|
end
|
|
46
54
|
|
|
@@ -48,33 +56,13 @@ module RocketJob
|
|
|
48
56
|
|
|
49
57
|
# Stores the categorized result from one result
|
|
50
58
|
def extract_categorized_result(result)
|
|
51
|
-
|
|
52
|
-
value
|
|
59
|
+
named_category = :main
|
|
60
|
+
value = result
|
|
53
61
|
if result.is_a?(RocketJob::Batch::Result)
|
|
54
|
-
|
|
55
|
-
value
|
|
56
|
-
raise(ArgumentError, "Invalid RocketJob Output Category: #{category}") if job.output_categories.exclude?(category)
|
|
62
|
+
named_category = result.category
|
|
63
|
+
value = result.value
|
|
57
64
|
end
|
|
58
|
-
(categorized_records[
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
class NullWriter
|
|
63
|
-
attr_reader :job, :categorized_records
|
|
64
|
-
attr_accessor :input_slice
|
|
65
|
-
|
|
66
|
-
def initialize(job, input_slice = nil)
|
|
67
|
-
@job = job
|
|
68
|
-
@input_slice = input_slice
|
|
69
|
-
@categorized_records = {}
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def <<(_)
|
|
73
|
-
# noop
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def close
|
|
77
|
-
# noop
|
|
65
|
+
(categorized_records[named_category] ||= []) << value unless value.nil? && !job.output_category(named_category).nils
|
|
78
66
|
end
|
|
79
67
|
end
|
|
80
68
|
end
|