acidic_job 1.0.0.rc2 → 1.0.0.rc4
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/.rubocop-https---www-goodcop-style-base-yml +1051 -0
- data/.rubocop.yml +4 -76
- data/Gemfile.lock +89 -81
- data/README.md +6 -6
- data/acidic_job.gemspec +0 -2
- data/app/models/acidic_job/entry.rb +9 -7
- data/app/models/acidic_job/execution.rb +45 -11
- data/app/models/acidic_job/value.rb +2 -0
- data/bin/console +3 -3
- data/lib/acidic_job/builder.rb +22 -6
- data/lib/acidic_job/context.rb +36 -20
- data/lib/acidic_job/errors.rb +22 -1
- data/lib/acidic_job/log_subscriber.rb +11 -13
- data/lib/acidic_job/plugin_context.rb +74 -0
- data/lib/acidic_job/plugins/transactional_step.rb +38 -0
- data/lib/acidic_job/serializer.rb +28 -0
- data/lib/acidic_job/serializers/job_serializer.rb +1 -1
- data/lib/acidic_job/serializers/range_serializer.rb +1 -3
- data/lib/acidic_job/testing.rb +10 -12
- data/lib/acidic_job/version.rb +1 -1
- data/lib/acidic_job/workflow.rb +104 -52
- data/lib/acidic_job.rb +7 -2
- data/lib/generators/acidic_job/install_generator.rb +5 -5
- data/lib/generators/acidic_job/templates/create_acidic_job_tables_migration.rb.erb +12 -11
- metadata +6 -30
data/lib/acidic_job/context.rb
CHANGED
@@ -8,14 +8,31 @@ module AcidicJob
|
|
8
8
|
|
9
9
|
def set(hash)
|
10
10
|
AcidicJob.instrument(:set_context, **hash) do
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
records = hash.map do |key, value|
|
12
|
+
{
|
13
|
+
execution_id: @execution.id,
|
14
|
+
key: key,
|
15
|
+
value: value,
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
case AcidicJob::Value.connection.adapter_name.downcase.to_sym
|
20
|
+
when :postgresql, :sqlite
|
21
|
+
AcidicJob::Value.upsert_all(records, unique_by: [:execution_id, :key])
|
22
|
+
when :mysql2, :mysql, :trilogy
|
23
|
+
AcidicJob::Value.upsert_all(records)
|
24
|
+
else
|
25
|
+
# Fallback for other adapters - try with unique_by first, fall back without
|
26
|
+
begin
|
27
|
+
AcidicJob::Value.upsert_all(records, unique_by: [:execution_id, :key])
|
28
|
+
rescue ArgumentError => e
|
29
|
+
if e.message.include?('does not support :unique_by')
|
30
|
+
AcidicJob::Value.upsert_all(records)
|
31
|
+
else
|
32
|
+
raise
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
19
36
|
end
|
20
37
|
end
|
21
38
|
|
@@ -25,22 +42,21 @@ module AcidicJob
|
|
25
42
|
end
|
26
43
|
end
|
27
44
|
|
28
|
-
|
45
|
+
def fetch(key, default = nil)
|
46
|
+
result = get(key).first
|
47
|
+
return result if result
|
48
|
+
|
49
|
+
fallback = default || yield(key)
|
50
|
+
set(key => fallback)
|
51
|
+
fallback
|
52
|
+
end
|
53
|
+
|
29
54
|
def []=(key, value)
|
30
|
-
|
31
|
-
AcidicJob::Value.upsert(
|
32
|
-
{ execution_id: @execution.id,
|
33
|
-
key: key,
|
34
|
-
value: value },
|
35
|
-
unique_by: %i[execution_id key]
|
36
|
-
)
|
37
|
-
end
|
55
|
+
set(key => value)
|
38
56
|
end
|
39
57
|
|
40
58
|
def [](key)
|
41
|
-
|
42
|
-
@execution.values.select(:value).find_by(key: key)&.value
|
43
|
-
end
|
59
|
+
get(key).first
|
44
60
|
end
|
45
61
|
end
|
46
62
|
end
|
data/lib/acidic_job/errors.rb
CHANGED
@@ -28,7 +28,6 @@ module AcidicJob
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
# rubocop:disable Lint/MissingSuper
|
32
31
|
class ArgumentMismatchError < Error
|
33
32
|
def initialize(expected, existing)
|
34
33
|
@expected = expected
|
@@ -98,5 +97,27 @@ module AcidicJob
|
|
98
97
|
"step method cannot expect arguments: #{@step.inspect}"
|
99
98
|
end
|
100
99
|
end
|
100
|
+
|
101
|
+
class DoublePluginCallError < Error
|
102
|
+
def initialize(plugin, step)
|
103
|
+
@plugin_name = (Module === plugin) ? plugin.name : plugin.class.name
|
104
|
+
@step = step
|
105
|
+
end
|
106
|
+
|
107
|
+
def message
|
108
|
+
"plugin `#{@plugin_name}` attempted to call step multiple times: #{@step.inspect}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class MissingPluginCallError < Error
|
113
|
+
def initialize(plugin, step)
|
114
|
+
@plugin_name = (Module === plugin) ? plugin.name : plugin.class.name
|
115
|
+
@step = step
|
116
|
+
end
|
117
|
+
|
118
|
+
def message
|
119
|
+
"plugin `#{@plugin_name}` failed to call step: #{@step.inspect}"
|
120
|
+
end
|
121
|
+
end
|
101
122
|
# rubocop:enable Lint/MissingSuper
|
102
123
|
end
|
@@ -5,45 +5,43 @@ require "active_support/log_subscriber"
|
|
5
5
|
module AcidicJob
|
6
6
|
class LogSubscriber < ActiveSupport::LogSubscriber
|
7
7
|
def define_workflow(event)
|
8
|
-
debug formatted_event(event,
|
8
|
+
debug formatted_event(event, title: "Define workflow", **event.payload.slice(:job_class, :job_id))
|
9
9
|
end
|
10
10
|
|
11
11
|
def initialize_workflow(event)
|
12
|
-
debug formatted_event(event,
|
12
|
+
debug formatted_event(event, title: "Initialize workflow", **event.payload.slice(:steps))
|
13
13
|
end
|
14
14
|
|
15
15
|
def process_workflow(event)
|
16
|
-
debug formatted_event(event,
|
16
|
+
debug formatted_event(event, title: "Process workflow", **event.payload[:execution].slice(:id, :recover_to))
|
17
17
|
end
|
18
18
|
|
19
19
|
def process_step(event)
|
20
|
-
debug formatted_event(event,
|
20
|
+
debug formatted_event(event, title: "Process step", **event.payload)
|
21
21
|
end
|
22
22
|
|
23
23
|
def perform_step(event)
|
24
|
-
debug formatted_event(event,
|
24
|
+
debug formatted_event(event, title: "Perform step", **event.payload)
|
25
25
|
end
|
26
26
|
|
27
27
|
def record_entry(event)
|
28
|
-
debug formatted_event(event,
|
28
|
+
debug formatted_event(event, title: "Record entry", **event.payload.slice(:step, :action, :timestamp))
|
29
29
|
end
|
30
30
|
|
31
|
-
private
|
32
|
-
|
33
|
-
def formatted_event(event, action:, **attributes)
|
34
|
-
"AcidicJob-#{AcidicJob::VERSION} #{action} (#{event.duration.round(1)}ms) #{formatted_attributes(**attributes)}"
|
31
|
+
private def formatted_event(event, title:, **attributes)
|
32
|
+
"AcidicJob-#{AcidicJob::VERSION} #{title} (#{event.duration.round(1)}ms) #{formatted_attributes(**attributes)}"
|
35
33
|
end
|
36
34
|
|
37
|
-
def formatted_attributes(**attributes)
|
35
|
+
private def formatted_attributes(**attributes)
|
38
36
|
attributes.map { |attr, value| "#{attr}: #{value.inspect}" }.join(", ")
|
39
37
|
end
|
40
38
|
|
41
|
-
def formatted_error(error)
|
39
|
+
private def formatted_error(error)
|
42
40
|
[error.class, error.message].compact.join(" ")
|
43
41
|
end
|
44
42
|
|
45
43
|
# Use the logger configured for AcidicJob
|
46
|
-
def logger
|
44
|
+
private def logger
|
47
45
|
AcidicJob.logger
|
48
46
|
end
|
49
47
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AcidicJob
|
4
|
+
class PluginContext
|
5
|
+
PLUGIN_INACTIVE = :__ACIDIC_JOB_PLUGIN_INACTIVE__
|
6
|
+
|
7
|
+
def initialize(plugin, job, execution, context, step_definition)
|
8
|
+
@plugin = plugin
|
9
|
+
@job = job
|
10
|
+
@execution = execution
|
11
|
+
@context = context
|
12
|
+
@step_definition = step_definition
|
13
|
+
end
|
14
|
+
|
15
|
+
def set(hash)
|
16
|
+
@context.set(hash)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(*keys)
|
20
|
+
@context.get(*keys)
|
21
|
+
end
|
22
|
+
|
23
|
+
def definition
|
24
|
+
@step_definition.fetch(@plugin.keyword.to_s, PLUGIN_INACTIVE)
|
25
|
+
end
|
26
|
+
|
27
|
+
def current_step
|
28
|
+
@step_definition["does"]
|
29
|
+
end
|
30
|
+
|
31
|
+
def inactive?
|
32
|
+
definition == PLUGIN_INACTIVE
|
33
|
+
end
|
34
|
+
|
35
|
+
def entries_for_action(action)
|
36
|
+
@execution.entries.for_action(plugin_action(action))
|
37
|
+
end
|
38
|
+
|
39
|
+
def record!(step:, action:, timestamp:, **kwargs)
|
40
|
+
@execution.record!(
|
41
|
+
step: step,
|
42
|
+
action: plugin_action(action),
|
43
|
+
timestamp: timestamp,
|
44
|
+
**kwargs
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def enqueue_job(...)
|
49
|
+
@job.enqueue(...)
|
50
|
+
end
|
51
|
+
|
52
|
+
def halt_workflow!
|
53
|
+
@job.halt_workflow!
|
54
|
+
end
|
55
|
+
|
56
|
+
def repeat_step!
|
57
|
+
@job.repeat_step!
|
58
|
+
end
|
59
|
+
|
60
|
+
def resolve_method(method_name)
|
61
|
+
begin
|
62
|
+
method_obj = @job.method(method_name)
|
63
|
+
rescue NameError
|
64
|
+
raise UndefinedMethodError.new(method_name)
|
65
|
+
end
|
66
|
+
|
67
|
+
method_obj
|
68
|
+
end
|
69
|
+
|
70
|
+
def plugin_action(action)
|
71
|
+
"#{@plugin.keyword}/#{action}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AcidicJob
|
4
|
+
module Plugins
|
5
|
+
module TransactionalStep
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def keyword
|
9
|
+
:transactional
|
10
|
+
end
|
11
|
+
|
12
|
+
# transactional: true
|
13
|
+
# transactional: false
|
14
|
+
# transactional: { on: Model }
|
15
|
+
def validate(input)
|
16
|
+
return input if input in true | false
|
17
|
+
|
18
|
+
raise ArgumentError.new("argument must be boolean or hash") unless input in Hash
|
19
|
+
raise ArgumentError.new("argument hash must have `on` key") unless input in Hash[on:]
|
20
|
+
raise ArgumentError.new("`on` key must have module value") unless input in Hash[on: Module]
|
21
|
+
|
22
|
+
input
|
23
|
+
end
|
24
|
+
|
25
|
+
def around_step(context, &block)
|
26
|
+
return yield if context.definition == false
|
27
|
+
|
28
|
+
model = if context.definition == true
|
29
|
+
AcidicJob::Execution
|
30
|
+
else
|
31
|
+
context.definition["on"].constantize
|
32
|
+
end
|
33
|
+
|
34
|
+
model.transaction(&block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require_relative "arguments"
|
5
|
+
|
6
|
+
module AcidicJob
|
7
|
+
# Used for `serialize` method in ActiveRecord
|
8
|
+
module Serializer
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def load(json)
|
12
|
+
return if json.nil? || json.empty?
|
13
|
+
|
14
|
+
data = JSON.parse json
|
15
|
+
|
16
|
+
Arguments.__send__ :deserialize_argument, data
|
17
|
+
end
|
18
|
+
|
19
|
+
def dump(obj)
|
20
|
+
data = Arguments.__send__ :serialize_argument, obj
|
21
|
+
|
22
|
+
JSON.generate data, strict: true
|
23
|
+
rescue ActiveJob::SerializationError => e
|
24
|
+
e.message << " (`#{obj.inspect}`)"
|
25
|
+
raise e
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/acidic_job/testing.rb
CHANGED
@@ -22,25 +22,23 @@ module AcidicJob
|
|
22
22
|
::DatabaseCleaner.cleaners = @original_cleaners
|
23
23
|
end
|
24
24
|
|
25
|
-
private
|
26
|
-
|
27
25
|
# Ensure that the system's original DatabaseCleaner configuration is maintained, options included,
|
28
26
|
# except that any `transaction` strategies for any ORMs are replaced with a `deletion` strategy.
|
29
|
-
def transaction_free_cleaners_for(original_cleaners)
|
27
|
+
private def transaction_free_cleaners_for(original_cleaners)
|
30
28
|
non_transaction_cleaners = original_cleaners.dup.to_h do |(orm, opts), cleaner|
|
31
29
|
[[orm, opts], ensure_no_transaction_strategies_for(cleaner)]
|
32
30
|
end
|
33
31
|
::DatabaseCleaner::Cleaners.new(non_transaction_cleaners)
|
34
32
|
end
|
35
33
|
|
36
|
-
def ensure_no_transaction_strategies_for(cleaner)
|
34
|
+
private def ensure_no_transaction_strategies_for(cleaner)
|
37
35
|
return cleaner unless strategy_name_for(cleaner) == "transaction"
|
38
36
|
|
39
37
|
cleaner.strategy = deletion_strategy_for(cleaner)
|
40
38
|
cleaner
|
41
39
|
end
|
42
40
|
|
43
|
-
def strategy_name_for(cleaner)
|
41
|
+
private def strategy_name_for(cleaner)
|
44
42
|
cleaner # <DatabaseCleaner::Cleaner>
|
45
43
|
.strategy # <DatabaseCleaner::ActiveRecord::Truncation>
|
46
44
|
.class # DatabaseCleaner::ActiveRecord::Truncation
|
@@ -50,19 +48,19 @@ module AcidicJob
|
|
50
48
|
.downcase # "truncation"
|
51
49
|
end
|
52
50
|
|
53
|
-
def deletion_strategy_for(cleaner)
|
51
|
+
private def deletion_strategy_for(cleaner)
|
54
52
|
strategy = cleaner.strategy
|
55
|
-
strategy_namespace = strategy
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
53
|
+
strategy_namespace = strategy # <DatabaseCleaner::ActiveRecord::Truncation>
|
54
|
+
.class # DatabaseCleaner::ActiveRecord::Truncation
|
55
|
+
.name # "DatabaseCleaner::ActiveRecord::Truncation"
|
56
|
+
.rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
|
57
|
+
.first # "DatabaseCleaner::ActiveRecord"
|
60
58
|
deletion_strategy_class_name = [strategy_namespace, "::", "Deletion"].join
|
61
59
|
deletion_strategy_class = deletion_strategy_class_name.constantize
|
62
60
|
instance_variable_hash = strategy.instance_variables.to_h do |var|
|
63
61
|
[
|
64
62
|
var.to_s.remove("@"),
|
65
|
-
strategy.instance_variable_get(var)
|
63
|
+
strategy.instance_variable_get(var),
|
66
64
|
]
|
67
65
|
end
|
68
66
|
options = instance_variable_hash.except("db", "connection_class")
|
data/lib/acidic_job/version.rb
CHANGED
data/lib/acidic_job/workflow.rb
CHANGED
@@ -4,43 +4,41 @@ require "active_job"
|
|
4
4
|
|
5
5
|
module AcidicJob
|
6
6
|
module Workflow
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
private_constant :NO_OP_WRAPPER, :REPEAT_STEP, :HALT_STEP
|
7
|
+
REPEAT_STEP = :__ACIDIC_JOB_REPEAT_STEP_SIGNAL__
|
8
|
+
HALT_STEP = :__ACIDIC_JOB_HALT_STEP_SIGNAL__
|
9
|
+
private_constant :REPEAT_STEP, :HALT_STEP
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
def execute_workflow(unique_by:, &block)
|
11
|
+
def execute_workflow(unique_by:, with: AcidicJob.plugins, &block)
|
12
|
+
@__acidic_job_plugins__ = with
|
15
13
|
serialized_job = serialize
|
16
14
|
|
17
15
|
workflow_definition = AcidicJob.instrument(:define_workflow, **serialized_job) do
|
18
|
-
raise RedefiningWorkflowError if defined? @
|
16
|
+
raise RedefiningWorkflowError if defined? @__acidic_job_builder__
|
19
17
|
|
20
|
-
@
|
18
|
+
@__acidic_job_builder__ = Builder.new(@__acidic_job_plugins__)
|
21
19
|
|
22
20
|
raise UndefinedWorkflowBlockError unless block_given?
|
23
21
|
raise InvalidWorkflowBlockError if block.arity != 1
|
24
22
|
|
25
|
-
block.call @
|
23
|
+
block.call @__acidic_job_builder__
|
26
24
|
|
27
|
-
raise MissingStepsError if @
|
25
|
+
raise MissingStepsError if @__acidic_job_builder__.steps.empty?
|
28
26
|
|
29
27
|
# convert the array of steps into a hash of recovery_points and next steps
|
30
|
-
@
|
28
|
+
@__acidic_job_builder__.define_workflow
|
31
29
|
end
|
32
30
|
|
33
|
-
AcidicJob.instrument(:initialize_workflow,
|
31
|
+
AcidicJob.instrument(:initialize_workflow, definition: workflow_definition) do
|
34
32
|
transaction_args = case ::ActiveRecord::Base.connection.adapter_name.downcase.to_sym
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
idempotency_key = Digest::SHA256.hexdigest(JSON.
|
42
|
-
|
43
|
-
@
|
33
|
+
# SQLite doesn't support `serializable` transactions
|
34
|
+
when :sqlite
|
35
|
+
{}
|
36
|
+
else
|
37
|
+
{ isolation: :serializable }
|
38
|
+
end
|
39
|
+
idempotency_key = Digest::SHA256.hexdigest(JSON.generate([self.class.name, unique_by], strict: true))
|
40
|
+
|
41
|
+
@__acidic_job_execution__ = ::ActiveRecord::Base.transaction(**transaction_args) do
|
44
42
|
record = Execution.find_by(idempotency_key: idempotency_key)
|
45
43
|
|
46
44
|
if record.present?
|
@@ -62,41 +60,51 @@ module AcidicJob
|
|
62
60
|
last_run_at: Time.current
|
63
61
|
)
|
64
62
|
else
|
63
|
+
starting_point = if workflow_definition.key?("steps")
|
64
|
+
workflow_definition["steps"].keys.first
|
65
|
+
else
|
66
|
+
# TODO: add deprecation warning
|
67
|
+
workflow_definition.keys.first
|
68
|
+
end
|
69
|
+
|
65
70
|
record = Execution.create!(
|
66
71
|
idempotency_key: idempotency_key,
|
67
72
|
serialized_job: serialized_job,
|
68
73
|
definition: workflow_definition,
|
69
|
-
recover_to:
|
74
|
+
recover_to: starting_point
|
70
75
|
)
|
71
76
|
end
|
72
77
|
|
73
78
|
record
|
74
79
|
end
|
75
80
|
end
|
76
|
-
@
|
81
|
+
@__acidic_job_context__ ||= Context.new(@__acidic_job_execution__)
|
77
82
|
|
78
|
-
AcidicJob.instrument(:process_workflow, execution: @
|
83
|
+
AcidicJob.instrument(:process_workflow, execution: @__acidic_job_execution__.attributes) do
|
79
84
|
# if the workflow record is already marked as finished, immediately return its result
|
80
|
-
return true if @
|
85
|
+
return true if @__acidic_job_execution__.finished?
|
81
86
|
|
82
87
|
loop do
|
83
|
-
break if @
|
88
|
+
break if @__acidic_job_execution__.finished?
|
84
89
|
|
85
|
-
current_step = @
|
90
|
+
current_step = @__acidic_job_execution__.recover_to
|
86
91
|
|
87
|
-
if not @
|
92
|
+
if not @__acidic_job_execution__.defined?(current_step)
|
88
93
|
raise UndefinedStepError.new(current_step)
|
89
94
|
end
|
90
95
|
|
91
|
-
step_definition = @
|
96
|
+
step_definition = @__acidic_job_execution__.definition_for(current_step)
|
92
97
|
AcidicJob.instrument(:process_step, **step_definition) do
|
93
98
|
recover_to = catch(:halt) { take_step(step_definition) }
|
94
99
|
case recover_to
|
95
100
|
when HALT_STEP
|
96
|
-
@
|
101
|
+
@__acidic_job_execution__.record!(
|
102
|
+
step: step_definition.fetch("does"),
|
103
|
+
action: :halted,
|
104
|
+
)
|
97
105
|
return true
|
98
106
|
else
|
99
|
-
@
|
107
|
+
@__acidic_job_execution__.update_column(:recover_to, recover_to)
|
100
108
|
end
|
101
109
|
end
|
102
110
|
end
|
@@ -107,31 +115,42 @@ module AcidicJob
|
|
107
115
|
throw :repeat, REPEAT_STEP
|
108
116
|
end
|
109
117
|
|
110
|
-
def
|
118
|
+
def halt_workflow!
|
111
119
|
throw :halt, HALT_STEP
|
112
120
|
end
|
113
121
|
|
122
|
+
def halt_step!
|
123
|
+
# TODO add deprecation warning
|
124
|
+
halt_workflow!
|
125
|
+
end
|
126
|
+
|
114
127
|
def step_retrying?
|
115
128
|
step_name = caller_locations.first.label
|
116
129
|
|
117
|
-
if not @
|
130
|
+
if not @__acidic_job_execution__.defined?(step_name)
|
118
131
|
raise UndefinedStepError.new(step_name)
|
119
132
|
end
|
120
133
|
|
121
|
-
@
|
134
|
+
@__acidic_job_execution__.entries.where(step: step_name, action: "started").count > 1
|
122
135
|
end
|
123
136
|
|
124
|
-
|
137
|
+
def execution
|
138
|
+
@__acidic_job_execution__
|
139
|
+
end
|
140
|
+
|
141
|
+
def ctx
|
142
|
+
@__acidic_job_context__
|
143
|
+
end
|
125
144
|
|
126
|
-
def take_step(step_definition)
|
145
|
+
private def take_step(step_definition)
|
127
146
|
curr_step = step_definition.fetch("does")
|
128
147
|
next_step = step_definition.fetch("then")
|
129
148
|
|
130
|
-
return next_step if @
|
149
|
+
return next_step if @__acidic_job_execution__.entries.exists?(step: curr_step, action: :succeeded)
|
131
150
|
|
132
151
|
rescued_error = nil
|
133
152
|
begin
|
134
|
-
@
|
153
|
+
@__acidic_job_execution__.record!(step: curr_step, action: :started)
|
135
154
|
result = AcidicJob.instrument(:perform_step, **step_definition) do
|
136
155
|
perform_step_for(step_definition)
|
137
156
|
end
|
@@ -139,44 +158,77 @@ module AcidicJob
|
|
139
158
|
when REPEAT_STEP
|
140
159
|
curr_step
|
141
160
|
else
|
142
|
-
@
|
161
|
+
@__acidic_job_execution__.record!(
|
162
|
+
step: curr_step,
|
163
|
+
action: :succeeded,
|
164
|
+
ignored: {
|
165
|
+
result: result,
|
166
|
+
}
|
167
|
+
)
|
143
168
|
next_step
|
144
169
|
end
|
145
|
-
rescue
|
170
|
+
rescue => e
|
146
171
|
rescued_error = e
|
147
172
|
raise e
|
148
173
|
ensure
|
149
174
|
if rescued_error
|
150
175
|
begin
|
151
|
-
@
|
176
|
+
@__acidic_job_execution__.record!(
|
152
177
|
step: curr_step,
|
153
178
|
action: :errored,
|
154
|
-
timestamp: Time.now,
|
155
179
|
exception_class: rescued_error.class.name,
|
156
180
|
message: rescued_error.message
|
157
181
|
)
|
158
|
-
rescue
|
182
|
+
rescue => e
|
159
183
|
# We're already inside an error condition, so swallow any additional
|
160
184
|
# errors from here and just send them to logs.
|
161
185
|
logger.error(
|
162
|
-
"Failed to store exception at step #{curr_step} for execution ##{@
|
186
|
+
"Failed to store exception at step #{curr_step} for execution ##{@__acidic_job_execution__.id}: #{e}."
|
163
187
|
)
|
164
188
|
end
|
165
189
|
end
|
166
190
|
end
|
167
191
|
end
|
168
192
|
|
169
|
-
def perform_step_for(step_definition)
|
193
|
+
private def perform_step_for(step_definition)
|
170
194
|
step_name = step_definition.fetch("does")
|
171
|
-
|
195
|
+
begin
|
196
|
+
step_method = method(step_name)
|
197
|
+
rescue NameError
|
198
|
+
raise UndefinedMethodError.new(step_name)
|
199
|
+
end
|
200
|
+
|
201
|
+
# raise InvalidMethodError.new(step_name) unless step_method.arity.zero?
|
202
|
+
|
203
|
+
plugin_pipeline_callable = @__acidic_job_plugins__.reverse.reduce(step_method) do |callable, plugin|
|
204
|
+
context = PluginContext.new(plugin, self, @__acidic_job_execution__, @__acidic_job_context__, step_definition)
|
205
|
+
|
206
|
+
if context.inactive?
|
207
|
+
callable
|
208
|
+
else
|
209
|
+
proc do
|
210
|
+
called = false
|
211
|
+
|
212
|
+
result = plugin.around_step(context) do |*args, **kwargs|
|
213
|
+
raise DoublePluginCallError.new(plugin, step_name) if called
|
172
214
|
|
173
|
-
|
215
|
+
called = true
|
174
216
|
|
175
|
-
|
217
|
+
if callable.arity.zero?
|
218
|
+
callable.call
|
219
|
+
else
|
220
|
+
callable.call(*args, **kwargs)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# raise MissingPluginCallError.new(plugin, step_name) unless called
|
225
|
+
|
226
|
+
result
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
176
230
|
|
177
|
-
catch(:repeat) {
|
178
|
-
rescue NameError
|
179
|
-
raise UndefinedMethodError.new(step_name)
|
231
|
+
catch(:repeat) { plugin_pipeline_callable.call }
|
180
232
|
end
|
181
233
|
end
|
182
234
|
end
|