active_durable 0.6.0 → 0.8.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 +99 -1
- data/README.md +238 -12
- data/app/controllers/active_durable/executions_controller.rb +2 -1
- data/app/helpers/active_durable/dashboard_helper.rb +12 -4
- data/app/views/active_durable/executions/index.html.erb +1 -1
- data/app/views/active_durable/executions/show.html.erb +9 -6
- data/app/views/layouts/active_durable/application.html.erb +3 -3
- data/lib/active_durable/configuration.rb +30 -0
- data/lib/active_durable/errors.rb +53 -8
- data/lib/active_durable/execution.rb +22 -2
- data/lib/active_durable/flow.rb +147 -31
- data/lib/active_durable/flow_parallel.rb +38 -12
- data/lib/active_durable/notebook.rb +17 -4
- data/lib/active_durable/operations.rb +55 -17
- data/lib/active_durable/pruner.rb +19 -4
- data/lib/active_durable/record.rb +9 -1
- data/lib/active_durable/runner.rb +45 -10
- data/lib/active_durable/serializer.rb +42 -5
- data/lib/active_durable/signal_record.rb +18 -2
- data/lib/active_durable/step.rb +20 -0
- data/lib/active_durable/sweeper.rb +1 -1
- data/lib/active_durable/testing.rb +18 -13
- data/lib/active_durable/version.rb +1 -1
- data/lib/active_durable.rb +79 -11
- data/lib/generators/active_durable/install/templates/create_active_durable_tables.rb.tt +15 -6
- data/lib/generators/active_durable/upgrade/templates/add_active_durable_context.rb.tt +19 -0
- data/lib/generators/active_durable/upgrade/templates/add_active_durable_continued_as.rb.tt +21 -0
- data/lib/generators/active_durable/upgrade/templates/add_active_durable_signal_name.rb.tt +22 -0
- data/lib/generators/active_durable/upgrade/templates/make_active_durable_ids_case_sensitive.rb.tt +72 -0
- data/lib/generators/active_durable/upgrade/upgrade_generator.rb +4 -1
- metadata +5 -1
|
@@ -4,33 +4,37 @@ class CreateActiveDurableTables < ActiveRecord::Migration<%= migration_version %
|
|
|
4
4
|
def change
|
|
5
5
|
# One row per saga run. It is also the buzón: create it in the same transaction as your record.
|
|
6
6
|
create_table :durable_executions, id: false do |t|
|
|
7
|
-
t.string :id, null: false, primary_key: true
|
|
7
|
+
t.string :id, null: false, primary_key: true, **exact
|
|
8
8
|
t.string :recipe, null: false
|
|
9
9
|
t.integer :recipe_version, null: false, default: 1
|
|
10
10
|
t.string :status, null: false
|
|
11
11
|
t.boolean :compensating, null: false, default: false
|
|
12
12
|
t.column :input, json_type
|
|
13
|
+
t.column :context, json_type
|
|
13
14
|
t.column :output, json_type
|
|
14
15
|
t.column :error, json_type
|
|
15
16
|
t.datetime :wake_at, precision: 6
|
|
16
17
|
t.datetime :locked_until, precision: 6
|
|
17
18
|
t.string :lease_token
|
|
18
|
-
t.string :forked_from
|
|
19
|
+
t.string :forked_from, **exact
|
|
20
|
+
t.string :continued_as, **exact
|
|
19
21
|
t.timestamps precision: 6
|
|
20
22
|
end
|
|
21
23
|
add_index :durable_executions, %i[status wake_at]
|
|
22
24
|
add_index :durable_executions, %i[status updated_at]
|
|
23
25
|
add_index :durable_executions, %i[recipe status]
|
|
24
26
|
add_index :durable_executions, :forked_from
|
|
27
|
+
add_index :durable_executions, :continued_as
|
|
25
28
|
|
|
26
29
|
# The notebook: one row per step (and per undo) of an execution.
|
|
27
30
|
create_table :durable_steps do |t|
|
|
28
|
-
t.string :execution_id, null: false
|
|
29
|
-
t.string :name, null: false
|
|
31
|
+
t.string :execution_id, null: false, **exact
|
|
32
|
+
t.string :name, null: false, **exact
|
|
30
33
|
t.string :kind, null: false
|
|
31
34
|
t.integer :position
|
|
32
35
|
t.string :status, null: false
|
|
33
36
|
t.integer :attempts, null: false, default: 0
|
|
37
|
+
t.string :signal_name, **exact
|
|
34
38
|
t.column :result, json_type
|
|
35
39
|
t.column :error, json_type
|
|
36
40
|
t.datetime :wake_at, precision: 6
|
|
@@ -42,8 +46,8 @@ class CreateActiveDurableTables < ActiveRecord::Migration<%= migration_version %
|
|
|
42
46
|
|
|
43
47
|
# Signals for flow.wait_for. They can arrive before the saga starts waiting.
|
|
44
48
|
create_table :durable_signals do |t|
|
|
45
|
-
t.string :execution_id, null: false
|
|
46
|
-
t.string :name, null: false
|
|
49
|
+
t.string :execution_id, null: false, **exact
|
|
50
|
+
t.string :name, null: false, **exact
|
|
47
51
|
t.column :payload, json_type
|
|
48
52
|
t.datetime :consumed_at, precision: 6
|
|
49
53
|
t.datetime :created_at, null: false, precision: 6
|
|
@@ -58,4 +62,9 @@ class CreateActiveDurableTables < ActiveRecord::Migration<%= migration_version %
|
|
|
58
62
|
def json_type
|
|
59
63
|
connection.adapter_name.downcase.include?("postg") ? :jsonb : :json
|
|
60
64
|
end
|
|
65
|
+
|
|
66
|
+
# MySQL compares strings ignoring case and accents by default; ids and step names must match exactly.
|
|
67
|
+
def exact
|
|
68
|
+
connection.adapter_name.match?(/mysql|trilogy/i) ? { collation: "utf8mb4_bin" } : {}
|
|
69
|
+
end
|
|
61
70
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveDurable 0.8: the context an execution started with (config.context), given back to it on every run.
|
|
4
|
+
class AddActiveDurableContext < ActiveRecord::Migration<%= migration_version %>
|
|
5
|
+
def up
|
|
6
|
+
add_column :durable_executions, :context, json_type unless column_exists?(:durable_executions, :context)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def down
|
|
10
|
+
remove_column :durable_executions, :context if column_exists?(:durable_executions, :context)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
# jsonb on PostgreSQL, json elsewhere, like the other JSON columns.
|
|
16
|
+
def json_type
|
|
17
|
+
connection.adapter_name.downcase.include?("postg") ? :jsonb : :json
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveDurable 0.8: flow.continue_as_new closes an execution and points it to the one that carries on.
|
|
4
|
+
class AddActiveDurableContinuedAs < ActiveRecord::Migration<%= migration_version %>
|
|
5
|
+
def up
|
|
6
|
+
add_column :durable_executions, :continued_as, :string, **exact unless column_exists?(:durable_executions, :continued_as)
|
|
7
|
+
add_index :durable_executions, :continued_as unless index_exists?(:durable_executions, :continued_as)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def down
|
|
11
|
+
remove_index :durable_executions, :continued_as if index_exists?(:durable_executions, :continued_as)
|
|
12
|
+
remove_column :durable_executions, :continued_as if column_exists?(:durable_executions, :continued_as)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# MySQL compares strings ignoring case and accents by default; ids must match exactly.
|
|
18
|
+
def exact
|
|
19
|
+
connection.adapter_name.match?(/mysql|trilogy/i) ? { collation: "utf8mb4_bin" } : {}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveDurable 0.8: flow.wait_for(name, signal: ...) writes down which signal a step waits for, when it is not the
|
|
4
|
+
# step's own name.
|
|
5
|
+
class AddActiveDurableSignalName < ActiveRecord::Migration<%= migration_version %>
|
|
6
|
+
def up
|
|
7
|
+
return if column_exists?(:durable_steps, :signal_name)
|
|
8
|
+
|
|
9
|
+
add_column :durable_steps, :signal_name, :string, **exact
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def down
|
|
13
|
+
remove_column :durable_steps, :signal_name if column_exists?(:durable_steps, :signal_name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# MySQL compares strings ignoring case and accents by default; signal names must match exactly.
|
|
19
|
+
def exact
|
|
20
|
+
connection.adapter_name.match?(/mysql|trilogy/i) ? { collation: "utf8mb4_bin" } : {}
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/generators/active_durable/upgrade/templates/make_active_durable_ids_case_sensitive.rb.tt
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# ActiveDurable 0.7: MySQL compared ids and step names ignoring case and accents, so "order-abc" and "order-ÁBC"
|
|
4
|
+
# shared one saga. These columns now compare byte by byte, as on PostgreSQL and SQLite. Elsewhere it does nothing.
|
|
5
|
+
#
|
|
6
|
+
# On MySQL it rebuilds the three tables and blocks writes to them while it runs: run it at a quiet time, or prune
|
|
7
|
+
# first. MySQL cannot roll DDL back, so if it stops halfway, run it again: it finishes the columns and puts the
|
|
8
|
+
# foreign keys back.
|
|
9
|
+
class MakeActiveDurableIdsCaseSensitive < ActiveRecord::Migration<%= migration_version %>
|
|
10
|
+
COLUMNS = {
|
|
11
|
+
durable_executions: %w[id forked_from],
|
|
12
|
+
durable_steps: %w[execution_id name],
|
|
13
|
+
durable_signals: %w[execution_id name]
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def up
|
|
17
|
+
change_collation { |_table| "utf8mb4_bin" }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def down
|
|
21
|
+
change_collation { |table| table_collation(table) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def change_collation
|
|
27
|
+
return unless connection.adapter_name.match?(/mysql|trilogy/i)
|
|
28
|
+
|
|
29
|
+
changes = COLUMNS.flat_map do |table, names|
|
|
30
|
+
wanted = yield(table)
|
|
31
|
+
connection.columns(table).select { |column| names.include?(column.name) && column.collation != wanted }
|
|
32
|
+
.map { |column| [table, column, wanted] }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Both ends of a foreign key must share a collation, so the keys come off while the columns change.
|
|
36
|
+
on_delete = remove_foreign_keys if changes.any?
|
|
37
|
+
changes.each do |table, column, collation|
|
|
38
|
+
change_column table, column.name, :string, limit: column.limit, null: column.null, collation: collation
|
|
39
|
+
end
|
|
40
|
+
restore_foreign_keys(on_delete || {})
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
FOREIGN_KEY_TABLES = %i[durable_steps durable_signals].freeze
|
|
44
|
+
|
|
45
|
+
def foreign_key(table)
|
|
46
|
+
connection.foreign_keys(table).find { |key| key.column == "execution_id" }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def remove_foreign_keys
|
|
50
|
+
FOREIGN_KEY_TABLES.each_with_object({}) do |table, on_delete|
|
|
51
|
+
key = foreign_key(table)
|
|
52
|
+
next unless key
|
|
53
|
+
|
|
54
|
+
on_delete[table] = key.options[:on_delete]
|
|
55
|
+
remove_foreign_key table, column: :execution_id
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Also puts back keys that an earlier run, stopped halfway, left off.
|
|
60
|
+
def restore_foreign_keys(on_delete)
|
|
61
|
+
FOREIGN_KEY_TABLES.each do |table|
|
|
62
|
+
next if foreign_key(table)
|
|
63
|
+
|
|
64
|
+
add_foreign_key table, :durable_executions, column: :execution_id, on_delete: on_delete.fetch(table, :cascade)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def table_collation(table)
|
|
69
|
+
select_value("SELECT TABLE_COLLATION FROM information_schema.TABLES " \
|
|
70
|
+
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = #{connection.quote(table.to_s)}")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -16,7 +16,10 @@ module ActiveDurable
|
|
|
16
16
|
desc "Adds the migrations that newer ActiveDurable versions need. Run it after updating the gem."
|
|
17
17
|
|
|
18
18
|
# In the order they were released. Never edit one that shipped: add a new one.
|
|
19
|
-
MIGRATIONS = %w[
|
|
19
|
+
MIGRATIONS = %w[
|
|
20
|
+
add_active_durable_prune_index make_active_durable_ids_case_sensitive add_active_durable_context
|
|
21
|
+
add_active_durable_signal_name add_active_durable_continued_as
|
|
22
|
+
].freeze
|
|
20
23
|
|
|
21
24
|
# @api private
|
|
22
25
|
def create_migration_files
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_durable
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- William Romero
|
|
@@ -120,7 +120,11 @@ files:
|
|
|
120
120
|
- lib/active_durable/version.rb
|
|
121
121
|
- lib/generators/active_durable/install/install_generator.rb
|
|
122
122
|
- lib/generators/active_durable/install/templates/create_active_durable_tables.rb.tt
|
|
123
|
+
- lib/generators/active_durable/upgrade/templates/add_active_durable_context.rb.tt
|
|
124
|
+
- lib/generators/active_durable/upgrade/templates/add_active_durable_continued_as.rb.tt
|
|
123
125
|
- lib/generators/active_durable/upgrade/templates/add_active_durable_prune_index.rb.tt
|
|
126
|
+
- lib/generators/active_durable/upgrade/templates/add_active_durable_signal_name.rb.tt
|
|
127
|
+
- lib/generators/active_durable/upgrade/templates/make_active_durable_ids_case_sensitive.rb.tt
|
|
124
128
|
- lib/generators/active_durable/upgrade/upgrade_generator.rb
|
|
125
129
|
- lib/tasks/active_durable.rake
|
|
126
130
|
- sig/active_durable.rbs
|