geneva_drive 0.5.0 → 0.7.0
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/CHANGELOG.md +14 -1
- data/MANUAL.md +449 -11
- data/README.md +5 -1
- data/lib/generators/geneva_drive/install/install_generator.rb +20 -0
- data/lib/generators/geneva_drive/install/templates/add_metadata_to_workflows.rb +17 -0
- data/lib/generators/geneva_drive/install/templates/add_resumable_step_support.rb +29 -0
- data/lib/generators/geneva_drive/install/templates/add_signals_support.rb +94 -0
- data/lib/generators/geneva_drive/install/templates/add_started_at_index_to_step_executions.rb +48 -0
- data/lib/generators/geneva_drive/install/templates/create_workflows_migration.rb +11 -0
- data/lib/generators/geneva_drive/install/templates/initializer.rb.tt +8 -0
- data/lib/geneva_drive/combined_exception_policy.rb +1 -1
- data/lib/geneva_drive/exception_policy.rb +36 -10
- data/lib/geneva_drive/executor.rb +352 -26
- data/lib/geneva_drive/flow_control.rb +87 -7
- data/lib/geneva_drive/iterable_step.rb +199 -0
- data/lib/geneva_drive/job_options.rb +70 -0
- data/lib/geneva_drive/jobs/housekeeping_job.rb +120 -7
- data/lib/geneva_drive/resumable_step_definition.rb +97 -0
- data/lib/geneva_drive/signal.rb +243 -0
- data/lib/geneva_drive/signal_matcher.rb +60 -0
- data/lib/geneva_drive/step_definition.rb +96 -2
- data/lib/geneva_drive/step_execution.rb +141 -2
- data/lib/geneva_drive/test_helpers.rb +212 -6
- data/lib/geneva_drive/version.rb +1 -1
- data/lib/geneva_drive/workflow/metadata_accessor.rb +85 -0
- data/lib/geneva_drive/workflow.rb +559 -43
- data/lib/geneva_drive.rb +37 -0
- data/test/dsl/signal_step_definition_test.rb +132 -0
- data/test/dsl/step_definition_test.rb +70 -0
- data/test/jobs/housekeeping_job_test.rb +119 -0
- data/test/jobs/housekeeping_signals_test.rb +198 -0
- data/test/jobs/perform_step_job_test.rb +25 -2
- data/test/migration_helpers_test.rb +1 -1
- data/test/test_helper.rb +4 -0
- data/test/test_helper_test.rb +281 -0
- data/test/workflow/cursor_size_limit_test.rb +75 -0
- data/test/workflow/instrumentation_test.rb +28 -0
- data/test/workflow/resumable_step_integration_test.rb +341 -0
- data/test/workflow/resumable_step_test.rb +615 -0
- data/test/workflow/resumable_without_migration_test.rb +103 -0
- data/test/workflow/signal_consumption_test.rb +397 -0
- data/test/workflow/signal_delivery_test.rb +255 -0
- data/test/workflow/signal_durability_test.rb +164 -0
- data/test/workflow/signal_rendezvous_test.rb +581 -0
- data/test/workflow/signals_without_migration_test.rb +97 -0
- data/test/workflow/workflow_test.rb +72 -1
- metadata +25 -15
- data/test/dummy/config/initializers/geneva_drive.rb +0 -44
- data/test/dummy/db/migrate/20260219212321_create_geneva_drive_workflows.rb +0 -74
- data/test/dummy/db/migrate/20260219212322_create_geneva_drive_step_executions.rb +0 -108
- data/test/dummy/db/migrate/20260219212323_add_finished_at_to_geneva_drive_step_executions.rb +0 -25
- data/test/dummy/db/migrate/20260219212324_add_error_class_name_to_geneva_drive_step_executions.rb +0 -7
- data/test/dummy/db/migrate/20260316100007_add_metadata_to_geneva_drive_step_executions.rb +0 -17
- data/test/dummy/db/migrate/20260327000000_allow_null_hero_on_geneva_drive_workflows.rb +0 -66
- data/test/dummy/db/schema.rb +0 -73
- data/test/dummy/log/development.log +0 -202
- data/test/dummy/log/test.log +0 -79471
- data/test/dummy/log/test.log.0 +0 -4
- data/test/dummy/tmp/local_secret.txt +0 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddMetadataToGenevaDriveWorkflows < ActiveRecord::Migration[7.2]
|
|
4
|
+
def change
|
|
5
|
+
return if column_exists?(:geneva_drive_workflows, :metadata)
|
|
6
|
+
|
|
7
|
+
adapter = connection.adapter_name.downcase
|
|
8
|
+
|
|
9
|
+
if adapter.include?("postgresql")
|
|
10
|
+
add_column :geneva_drive_workflows, :metadata, :jsonb
|
|
11
|
+
elsif adapter.include?("mysql")
|
|
12
|
+
add_column :geneva_drive_workflows, :metadata, :text, limit: 4_294_967_295
|
|
13
|
+
else
|
|
14
|
+
add_column :geneva_drive_workflows, :metadata, :text
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddResumableStepSupportToGenevaDriveStepExecutions < ActiveRecord::Migration[7.2]
|
|
4
|
+
include GenevaDrive::MigrationHelpers
|
|
5
|
+
|
|
6
|
+
def change
|
|
7
|
+
unless column_exists?(:geneva_drive_step_executions, :cursor)
|
|
8
|
+
# Cursor for resumable steps. Use database-native JSON type:
|
|
9
|
+
# - PostgreSQL: jsonb (indexed, efficient, supports containment queries)
|
|
10
|
+
# - MySQL 5.7+: json (native validation and storage)
|
|
11
|
+
# - SQLite: json (Rails handles as TEXT with serialization)
|
|
12
|
+
if connection.adapter_name.downcase.include?("postgresql")
|
|
13
|
+
add_column :geneva_drive_step_executions, :cursor, :jsonb
|
|
14
|
+
else
|
|
15
|
+
add_column :geneva_drive_step_executions, :cursor, :json
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
unless column_exists?(:geneva_drive_step_executions, :continues_from_id)
|
|
20
|
+
# Link successor executions to their predecessor, chaining the execution
|
|
21
|
+
# records of a resumable step. Match the primary key type (bigint or uuid)
|
|
22
|
+
# of the step_executions table.
|
|
23
|
+
# No foreign key constraint - SQLite rewrites the table on add_foreign_key,
|
|
24
|
+
# which can destroy data.
|
|
25
|
+
add_column :geneva_drive_step_executions, :continues_from_id, geneva_drive_key_type
|
|
26
|
+
add_index :geneva_drive_step_executions, :continues_from_id
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Signals: external events delivered to a workflow, and the two columns on
|
|
4
|
+
# step executions that let one park until a matching event arrives. Both halves
|
|
5
|
+
# ship together - a signals table nobody can wait on is useless, and a waiting
|
|
6
|
+
# step with nowhere to read its event from is worse - so they are one migration.
|
|
7
|
+
class AddSignalsSupportToGenevaDrive < ActiveRecord::Migration[7.2]
|
|
8
|
+
include GenevaDrive::MigrationHelpers
|
|
9
|
+
|
|
10
|
+
def change
|
|
11
|
+
key_type = geneva_drive_key_type
|
|
12
|
+
adapter = connection.adapter_name.downcase
|
|
13
|
+
|
|
14
|
+
# Build reference options - we add the foreign key separately to avoid
|
|
15
|
+
# MySQL type mismatch (see below).
|
|
16
|
+
reference_options = {
|
|
17
|
+
null: false,
|
|
18
|
+
index: true
|
|
19
|
+
}
|
|
20
|
+
reference_options[:type] = key_type if key_type == :uuid
|
|
21
|
+
|
|
22
|
+
create_table :geneva_drive_signals, **geneva_drive_table_options do |t|
|
|
23
|
+
# Link to workflow (cascade delete when the workflow is deleted)
|
|
24
|
+
t.references :workflow, **reference_options
|
|
25
|
+
|
|
26
|
+
# The event name, as passed to Workflow#signal!
|
|
27
|
+
t.string :name, null: false
|
|
28
|
+
|
|
29
|
+
# Optional caller-supplied deduplication key. NULLs are distinct in
|
|
30
|
+
# unique indexes on PostgreSQL, MySQL and SQLite alike, so signals
|
|
31
|
+
# without an idempotency key always insert.
|
|
32
|
+
t.string :idempotency_key
|
|
33
|
+
|
|
34
|
+
# Lifecycle: pending -> claimed -> consumed
|
|
35
|
+
t.string :state, null: false, default: "pending"
|
|
36
|
+
|
|
37
|
+
# Serialized payload. Use the database-native JSON type, same flavor
|
|
38
|
+
# logic as the resumable step cursor:
|
|
39
|
+
# - PostgreSQL: jsonb
|
|
40
|
+
# - MySQL 5.7+: json
|
|
41
|
+
# - SQLite: json (Rails handles as TEXT with serialization)
|
|
42
|
+
if adapter.include?("postgresql")
|
|
43
|
+
t.jsonb :payload
|
|
44
|
+
else
|
|
45
|
+
t.json :payload
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
t.datetime :claimed_at
|
|
49
|
+
t.datetime :consumed_at
|
|
50
|
+
|
|
51
|
+
# How many executions have attached to this signal, and how many of
|
|
52
|
+
# those attached chains have resolved cleanly. For a linear workflow
|
|
53
|
+
# both end at 1 (more claims if the step was retried); when one signal
|
|
54
|
+
# is dispatched onto several concurrent executions the pair is the
|
|
55
|
+
# progress readout of that fan-out. Counters, not the lifecycle state -
|
|
56
|
+
# the `state` column above is what says claimed or consumed.
|
|
57
|
+
t.bigint :claimed, null: false, default: 0
|
|
58
|
+
t.bigint :consumed, null: false, default: 0
|
|
59
|
+
|
|
60
|
+
t.timestamps
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Deduplication: one signal per (workflow, name, idempotency_key)
|
|
64
|
+
add_index :geneva_drive_signals, [:workflow_id, :name, :idempotency_key],
|
|
65
|
+
unique: true, name: "index_geneva_drive_signals_dedup"
|
|
66
|
+
|
|
67
|
+
# Scanning a workflow's unconsumed signals
|
|
68
|
+
add_index :geneva_drive_signals, [:workflow_id, :state]
|
|
69
|
+
|
|
70
|
+
# Add foreign key separately to avoid MySQL type mismatch (UNSIGNED vs SIGNED bigint)
|
|
71
|
+
# MySQL creates primary keys as UNSIGNED but references as SIGNED, causing FK constraint failure
|
|
72
|
+
unless adapter.include?("mysql")
|
|
73
|
+
add_foreign_key :geneva_drive_signals, :geneva_drive_workflows,
|
|
74
|
+
column: :workflow_id, on_delete: :cascade
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
unless column_exists?(:geneva_drive_step_executions, :signal_id)
|
|
78
|
+
# The attachment pin: which signal this execution is processing. One
|
|
79
|
+
# signal may be pinned by many executions, so this lives on the
|
|
80
|
+
# execution side. Match the primary key type (bigint or uuid) of the
|
|
81
|
+
# signals table.
|
|
82
|
+
# No foreign key constraint - SQLite rewrites the table on
|
|
83
|
+
# add_foreign_key, which can destroy data.
|
|
84
|
+
add_column :geneva_drive_step_executions, :signal_id, key_type
|
|
85
|
+
add_index :geneva_drive_step_executions, :signal_id
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
unless column_exists?(:geneva_drive_step_executions, :waiting_since)
|
|
89
|
+
# When the execution parked waiting for a signal, so "waiting for N
|
|
90
|
+
# days" is queryable without abusing updated_at.
|
|
91
|
+
add_column :geneva_drive_step_executions, :waiting_since, :datetime
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddStartedAtIndexToGenevaDriveStepExecutions < ActiveRecord::Migration[7.2]
|
|
4
|
+
# Partial index scoped to state = 'in_progress'. HousekeepingJob's
|
|
5
|
+
# recover_stuck_in_progress! filters `state = 'in_progress' AND
|
|
6
|
+
# started_at < cutoff LIMIT N`; without this the planner walks the whole
|
|
7
|
+
# in-progress set looking for old rows and trips the caller's
|
|
8
|
+
# statement_timeout on large / mostly-fresh workloads.
|
|
9
|
+
INDEX_NAME = :index_geneva_drive_step_executions_in_progress_started_at
|
|
10
|
+
|
|
11
|
+
disable_ddl_transaction!
|
|
12
|
+
|
|
13
|
+
def up
|
|
14
|
+
return if index_name_exists?(:geneva_drive_step_executions, INDEX_NAME)
|
|
15
|
+
|
|
16
|
+
adapter = connection.adapter_name.downcase
|
|
17
|
+
|
|
18
|
+
if adapter.include?("postgresql")
|
|
19
|
+
add_index :geneva_drive_step_executions, :started_at,
|
|
20
|
+
name: INDEX_NAME,
|
|
21
|
+
where: "state = 'in_progress'",
|
|
22
|
+
algorithm: :concurrently
|
|
23
|
+
elsif adapter.include?("sqlite")
|
|
24
|
+
# SQLite supports partial indexes but has no CONCURRENTLY.
|
|
25
|
+
add_index :geneva_drive_step_executions, :started_at,
|
|
26
|
+
name: INDEX_NAME,
|
|
27
|
+
where: "state = 'in_progress'"
|
|
28
|
+
else
|
|
29
|
+
# MySQL does not support partial indexes; fall back to a plain index.
|
|
30
|
+
# Composite (state, started_at) so the planner can seek by state first.
|
|
31
|
+
add_index :geneva_drive_step_executions, [:state, :started_at],
|
|
32
|
+
name: INDEX_NAME
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def down
|
|
37
|
+
return unless index_name_exists?(:geneva_drive_step_executions, INDEX_NAME)
|
|
38
|
+
|
|
39
|
+
adapter = connection.adapter_name.downcase
|
|
40
|
+
if adapter.include?("postgresql")
|
|
41
|
+
remove_index :geneva_drive_step_executions,
|
|
42
|
+
name: INDEX_NAME,
|
|
43
|
+
algorithm: :concurrently
|
|
44
|
+
else
|
|
45
|
+
remove_index :geneva_drive_step_executions, name: INDEX_NAME
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -4,6 +4,8 @@ class CreateGenevaDriveWorkflows < ActiveRecord::Migration[7.2]
|
|
|
4
4
|
include GenevaDrive::MigrationHelpers
|
|
5
5
|
|
|
6
6
|
def change
|
|
7
|
+
adapter = connection.adapter_name.downcase
|
|
8
|
+
|
|
7
9
|
create_table :geneva_drive_workflows, **geneva_drive_table_options do |t|
|
|
8
10
|
# Core identification (STI)
|
|
9
11
|
t.string :type, null: false, index: true
|
|
@@ -22,6 +24,15 @@ class CreateGenevaDriveWorkflows < ActiveRecord::Migration[7.2]
|
|
|
22
24
|
# Multiple workflows of same type for same hero
|
|
23
25
|
t.boolean :allow_multiple, default: false, null: false
|
|
24
26
|
|
|
27
|
+
# Freeform JSON metadata
|
|
28
|
+
if adapter.include?("postgresql")
|
|
29
|
+
t.jsonb :metadata
|
|
30
|
+
elsif adapter.include?("mysql")
|
|
31
|
+
t.text :metadata, limit: 4_294_967_295
|
|
32
|
+
else
|
|
33
|
+
t.text :metadata
|
|
34
|
+
end
|
|
35
|
+
|
|
25
36
|
# Timestamps
|
|
26
37
|
t.datetime :started_at
|
|
27
38
|
t.datetime :transitioned_at
|
|
@@ -31,6 +31,14 @@
|
|
|
31
31
|
#
|
|
32
32
|
# GenevaDrive.housekeeping_batch_size = 1000
|
|
33
33
|
|
|
34
|
+
# Maximum size in bytes of a resumable step cursor once serialized to JSON.
|
|
35
|
+
# The cursor is a position marker (an id, page number, or pagination token),
|
|
36
|
+
# not a place to store the data being processed - it is rewritten on every
|
|
37
|
+
# checkpoint and copied to every successor execution. Exceeding the limit
|
|
38
|
+
# raises GenevaDrive::CursorTooLargeError. Set to nil to disable the check.
|
|
39
|
+
#
|
|
40
|
+
# GenevaDrive.max_cursor_size = 128.kilobytes
|
|
41
|
+
|
|
34
42
|
# Whether to defer job enqueueing until after the database transaction commits.
|
|
35
43
|
# In production this ensures step execution records are visible to job workers
|
|
36
44
|
# before the job runs. In test environments (the default when Rails.env.test?)
|
|
@@ -88,7 +88,7 @@ class GenevaDrive::CombinedExceptionPolicy
|
|
|
88
88
|
cap = max_reattempts
|
|
89
89
|
if cap && result[:action] == :reattempt && reattempt_count >= cap
|
|
90
90
|
terminal = child.terminal_action.to_s.chomp("!").to_sym
|
|
91
|
-
return {action: terminal, error: error}
|
|
91
|
+
return {action: terminal, error: error, report: result[:report], terminal: true}
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
result
|
|
@@ -85,6 +85,9 @@ class GenevaDrive::ExceptionPolicy
|
|
|
85
85
|
# Each entry can be an Exception subclass or any object responding to #===.
|
|
86
86
|
attr_reader :exception_matchers
|
|
87
87
|
|
|
88
|
+
# @return [Symbol] when to report the exception via Rails.error.report (:always, :never, or :terminal_only)
|
|
89
|
+
attr_reader :report
|
|
90
|
+
|
|
88
91
|
# @return [Proc, nil] the handler block (imperative mode)
|
|
89
92
|
attr_reader :handler
|
|
90
93
|
|
|
@@ -93,7 +96,10 @@ class GenevaDrive::ExceptionPolicy
|
|
|
93
96
|
# Valid terminal_action values
|
|
94
97
|
VALID_TERMINAL_ACTIONS = %i[pause! cancel! skip!].freeze
|
|
95
98
|
|
|
96
|
-
#
|
|
99
|
+
# Valid report values
|
|
100
|
+
VALID_REPORT_OPTIONS = %i[always never terminal_only].freeze
|
|
101
|
+
|
|
102
|
+
# @overload initialize(action, matching: nil, wait: nil, max_reattempts: nil, terminal_action: :pause!, report: :always)
|
|
97
103
|
# Declarative mode — specify action and options.
|
|
98
104
|
# @param action [Symbol] the flow control action (:pause!, :cancel!, :reattempt!, :skip!)
|
|
99
105
|
# @param matching [Class, String, #===, Array<Class, String, #===>, nil] exception classes
|
|
@@ -104,12 +110,19 @@ class GenevaDrive::ExceptionPolicy
|
|
|
104
110
|
# @param wait [ActiveSupport::Duration, nil] wait time before reattempt
|
|
105
111
|
# @param max_reattempts [Integer, nil] max consecutive reattempts (nil = unlimited)
|
|
106
112
|
# @param terminal_action [Symbol] what to do when max_reattempts is exceeded (:pause!, :cancel!, or :skip!)
|
|
113
|
+
# @param report [Symbol] when to report the exception to +Rails.error.report+.
|
|
114
|
+
# - +:always+ (default) — report every exception regardless of what action is taken
|
|
115
|
+
# - +:never+ — never report; the exception is expected and handled by the policy
|
|
116
|
+
# - +:terminal_only+ — suppress reports while the step is being reattempted, but
|
|
117
|
+
# report when reattempts are exhausted and the +terminal_action+ fires
|
|
107
118
|
#
|
|
108
|
-
# @overload initialize(matching: nil, &block)
|
|
119
|
+
# @overload initialize(matching: nil, report: :always, &block)
|
|
109
120
|
# Imperative mode — block receives exception, runs in workflow context.
|
|
110
121
|
# Must call a flow control method (reattempt!, cancel!, pause!, skip!).
|
|
111
|
-
# Can be combined with +matching:+ to target specific exception classes
|
|
122
|
+
# Can be combined with +matching:+ and +report:+ to target specific exception classes
|
|
123
|
+
# and control error reporting.
|
|
112
124
|
# @param matching [Class, String, #===, Array<Class, String, #===>, nil] exception classes
|
|
125
|
+
# @param report [Symbol] when to report the exception (+:always+, +:never+, or +:terminal_only+)
|
|
113
126
|
# @yield [error] the exception that was raised
|
|
114
127
|
#
|
|
115
128
|
# @example Blanket reattempt policy
|
|
@@ -126,7 +139,18 @@ class GenevaDrive::ExceptionPolicy
|
|
|
126
139
|
#
|
|
127
140
|
# @example Imperative policy with exception matching
|
|
128
141
|
# ExceptionPolicy.new(matching: Timeout::Error) { |error| reattempt!(wait: error.retry_after) }
|
|
129
|
-
|
|
142
|
+
#
|
|
143
|
+
# @example Suppress error reporting for expected exceptions
|
|
144
|
+
# ExceptionPolicy.new(:reattempt!, matching: RateLimitError, report: :never)
|
|
145
|
+
#
|
|
146
|
+
# @example Only report when reattempts are exhausted
|
|
147
|
+
# ExceptionPolicy.new(:reattempt!, matching: Net::OpenTimeout, max_reattempts: 5, report: :terminal_only)
|
|
148
|
+
def initialize(action = nil, wait: nil, max_reattempts: nil, terminal_action: :pause!, matching: nil, report: :always, &block)
|
|
149
|
+
@report = report
|
|
150
|
+
unless VALID_REPORT_OPTIONS.include?(@report)
|
|
151
|
+
raise ArgumentError,
|
|
152
|
+
"report: must be one of #{VALID_REPORT_OPTIONS.join(", ")}, got #{@report.inspect}"
|
|
153
|
+
end
|
|
130
154
|
if block
|
|
131
155
|
if action || wait || max_reattempts || terminal_action != :pause!
|
|
132
156
|
raise ArgumentError,
|
|
@@ -191,8 +215,10 @@ class GenevaDrive::ExceptionPolicy
|
|
|
191
215
|
# in the workflow context and translates the resulting flow control signal
|
|
192
216
|
# into a result.
|
|
193
217
|
#
|
|
194
|
-
# The returned Hash always contains +:action+ (Symbol without +!+)
|
|
195
|
-
# +:error+ (the original exception)
|
|
218
|
+
# The returned Hash always contains +:action+ (Symbol without +!+),
|
|
219
|
+
# +:error+ (the original exception), and +:report+ (the reporting mode).
|
|
220
|
+
# Reattempt results also include +:wait+. When the terminal action fires
|
|
221
|
+
# (reattempt limit exceeded), +:terminal+ is set to +true+.
|
|
196
222
|
#
|
|
197
223
|
# @param error [Exception] the exception that was raised
|
|
198
224
|
# @param reattempt_count [Integer] consecutive reattempts so far for this step
|
|
@@ -241,9 +267,9 @@ class GenevaDrive::ExceptionPolicy
|
|
|
241
267
|
end
|
|
242
268
|
|
|
243
269
|
if signal.is_a?(GenevaDrive::FlowControlSignal)
|
|
244
|
-
{action: signal.action, wait: signal.options[:wait], error: error}
|
|
270
|
+
{action: signal.action, wait: signal.options[:wait], error: error, report: @report}
|
|
245
271
|
else
|
|
246
|
-
{action: :pause, error: error}
|
|
272
|
+
{action: :pause, error: error, report: @report}
|
|
247
273
|
end
|
|
248
274
|
end
|
|
249
275
|
|
|
@@ -256,9 +282,9 @@ class GenevaDrive::ExceptionPolicy
|
|
|
256
282
|
def apply_declarative(error, reattempt_count)
|
|
257
283
|
if action == :reattempt! && max_reattempts && reattempt_count >= max_reattempts
|
|
258
284
|
terminal = terminal_action.to_s.chomp("!").to_sym
|
|
259
|
-
{action: terminal, error: error}
|
|
285
|
+
{action: terminal, error: error, report: @report, terminal: true}
|
|
260
286
|
else
|
|
261
|
-
{action: action.to_s.chomp("!").to_sym, wait: wait, error: error}
|
|
287
|
+
{action: action.to_s.chomp("!").to_sym, wait: wait, error: error, report: @report}
|
|
262
288
|
end
|
|
263
289
|
end
|
|
264
290
|
|