dynflow 1.4.3 → 1.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dynflow/connectors/abstract.rb +4 -0
- data/lib/dynflow/connectors/database.rb +4 -0
- data/lib/dynflow/connectors/direct.rb +5 -0
- data/lib/dynflow/executors.rb +32 -10
- data/lib/dynflow/persistence.rb +8 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +16 -0
- data/lib/dynflow/persistence_adapters/sequel.rb +23 -7
- data/lib/dynflow/persistence_adapters/sequel_migrations/020_drop_duplicate_indices.rb +30 -0
- data/lib/dynflow/rails/configuration.rb +11 -4
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/world/invalidation.rb +4 -0
- data/test/persistence_test.rb +36 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 541dbb9bd3cd2b7e0deb00b940ac1d387518d2a884263646e6bcc7666ad9a934
|
4
|
+
data.tar.gz: 7741c155b2538ab52c47fd21a2a56bd019bdfd15f5c69e48b4e37044d941bc5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1965d10b547c1fb091c7dbabeaef50f7825d3b472dc911b5f17d6ee8366c401577234bc6836c85e932c58548bfc6cac82bbce4719ea637793b9815a4feebcb5f
|
7
|
+
data.tar.gz: 66ee5081a627072548a30dedebce8cac285bd6bf0630aa3ddf93305852f6d3daded8d05260c5a896db45345913cb1a0a7a2ef4fa6dbde45aa29b3159ff61a595
|
@@ -25,6 +25,10 @@ module Dynflow
|
|
25
25
|
raise NotImplementedError
|
26
26
|
end
|
27
27
|
|
28
|
+
def prune_undeliverable_envelopes(world)
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
28
32
|
# we need to pass the world, as the connector can be shared
|
29
33
|
# between words: we need to know the one to send the message to
|
30
34
|
def receive(world, envelope)
|
@@ -172,6 +172,10 @@ module Dynflow
|
|
172
172
|
Telemetry.with_instance { |t| t.increment_counter(:dynflow_connector_envelopes, 1, :world => envelope.sender_id, :direction => 'outgoing') }
|
173
173
|
@core.ask([:handle_envelope, envelope])
|
174
174
|
end
|
175
|
+
|
176
|
+
def prune_undeliverable_envelopes(world)
|
177
|
+
world.persistence.prune_undeliverable_envelopes
|
178
|
+
end
|
175
179
|
end
|
176
180
|
end
|
177
181
|
end
|
@@ -68,6 +68,11 @@ module Dynflow
|
|
68
68
|
Telemetry.with_instance { |t| t.increment_counter(:dynflow_connector_envelopes, 1, :world => envelope.sender_id) }
|
69
69
|
@core.ask([:handle_envelope, envelope])
|
70
70
|
end
|
71
|
+
|
72
|
+
def prune_undeliverable_envelopes(_world)
|
73
|
+
# This is a noop
|
74
|
+
0
|
75
|
+
end
|
71
76
|
end
|
72
77
|
end
|
73
78
|
end
|
data/lib/dynflow/executors.rb
CHANGED
@@ -4,16 +4,38 @@ module Dynflow
|
|
4
4
|
|
5
5
|
require 'dynflow/executors/parallel'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
class << self
|
8
|
+
# Every time we run a code that can be defined outside of Dynflow,
|
9
|
+
# we should wrap it with this method, and we can ensure here to do
|
10
|
+
# necessary cleanup, such as cleaning ActiveRecord connections
|
11
|
+
def run_user_code
|
12
|
+
# Here we cover a case where the connection was already checked out from
|
13
|
+
# the pool and had opened transactions. In that case, we should leave the
|
14
|
+
# cleanup to the other runtime unit which opened the transaction. If the
|
15
|
+
# connection was checked out or there are no opened transactions, we can
|
16
|
+
# safely perform the cleanup.
|
17
|
+
no_previously_opened_transactions = active_record_open_transactions.zero?
|
18
|
+
yield
|
19
|
+
ensure
|
20
|
+
::ActiveRecord::Base.clear_active_connections! if no_previously_opened_transactions && active_record_connected?
|
21
|
+
::Logging.mdc.clear if defined? ::Logging
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def active_record_open_transactions
|
27
|
+
active_record_active_connection&.open_transactions || 0
|
28
|
+
end
|
17
29
|
|
30
|
+
def active_record_active_connection
|
31
|
+
return unless defined?(::ActiveRecord) && ::ActiveRecord::Base.connected?
|
32
|
+
# #active_connection? returns the connection if already established or nil
|
33
|
+
::ActiveRecord::Base.connection_pool.active_connection?
|
34
|
+
end
|
35
|
+
|
36
|
+
def active_record_connected?
|
37
|
+
!!active_record_active_connection
|
38
|
+
end
|
39
|
+
end
|
18
40
|
end
|
19
41
|
end
|
data/lib/dynflow/persistence.rb
CHANGED
@@ -134,5 +134,13 @@ module Dynflow
|
|
134
134
|
envelope
|
135
135
|
end
|
136
136
|
end
|
137
|
+
|
138
|
+
def prune_envelopes(receiver_ids)
|
139
|
+
adapter.prune_envelopes(receiver_ids)
|
140
|
+
end
|
141
|
+
|
142
|
+
def prune_undeliverable_envelopes
|
143
|
+
adapter.prune_undeliverable_envelopes
|
144
|
+
end
|
137
145
|
end
|
138
146
|
end
|
@@ -116,6 +116,22 @@ module Dynflow
|
|
116
116
|
def push_envelope(envelope)
|
117
117
|
raise NotImplementedError
|
118
118
|
end
|
119
|
+
|
120
|
+
def prune_envelopes(receiver_ids)
|
121
|
+
raise NotImplementedError
|
122
|
+
end
|
123
|
+
|
124
|
+
def prune_undeliverable_envelopes
|
125
|
+
raise NotImplementedError
|
126
|
+
end
|
127
|
+
|
128
|
+
def migrate_db
|
129
|
+
raise NotImplementedError
|
130
|
+
end
|
131
|
+
|
132
|
+
def abort_if_pending_migrations!
|
133
|
+
raise NotImplementedError
|
134
|
+
end
|
119
135
|
end
|
120
136
|
end
|
121
137
|
end
|
@@ -45,13 +45,15 @@ module Dynflow
|
|
45
45
|
step: %w(error children) }
|
46
46
|
|
47
47
|
def initialize(config)
|
48
|
+
migrate = true
|
48
49
|
config = config.dup
|
49
50
|
@additional_responsibilities = { coordinator: true, connector: true }
|
50
|
-
if config.is_a?(Hash)
|
51
|
-
@additional_responsibilities.merge!(config.delete(:additional_responsibilities))
|
51
|
+
if config.is_a?(Hash)
|
52
|
+
@additional_responsibilities.merge!(config.delete(:additional_responsibilities)) if config.key?(:additional_responsibilities)
|
53
|
+
migrate = config.fetch(:migrate, true)
|
52
54
|
end
|
53
55
|
@db = initialize_db config
|
54
|
-
migrate_db
|
56
|
+
migrate_db if migrate
|
55
57
|
end
|
56
58
|
|
57
59
|
def transaction(&block)
|
@@ -198,6 +200,16 @@ module Dynflow
|
|
198
200
|
table(:envelope).insert(prepare_record(:envelope, envelope))
|
199
201
|
end
|
200
202
|
|
203
|
+
def prune_envelopes(receiver_ids)
|
204
|
+
connector_feature!
|
205
|
+
table(:envelope).where(receiver_id: receiver_ids).delete
|
206
|
+
end
|
207
|
+
|
208
|
+
def prune_undeliverable_envelopes
|
209
|
+
connector_feature!
|
210
|
+
table(:envelope).where(receiver_id: table(:coordinator_record).select(:id)).invert.delete
|
211
|
+
end
|
212
|
+
|
201
213
|
def coordinator_feature!
|
202
214
|
unless @additional_responsibilities[:coordinator]
|
203
215
|
raise "The sequel persistence adapter coordinator feature used but not enabled in additional_features"
|
@@ -238,6 +250,14 @@ module Dynflow
|
|
238
250
|
envelopes: table(:envelope).all.to_a }
|
239
251
|
end
|
240
252
|
|
253
|
+
def migrate_db
|
254
|
+
::Sequel::Migrator.run(db, self.class.migrations_path, table: 'dynflow_schema_info')
|
255
|
+
end
|
256
|
+
|
257
|
+
def abort_if_pending_migrations!
|
258
|
+
::Sequel::Migrator.check_current(db, self.class.migrations_path, table: 'dynflow_schema_info')
|
259
|
+
end
|
260
|
+
|
241
261
|
private
|
242
262
|
|
243
263
|
TABLES = { execution_plan: :dynflow_execution_plans,
|
@@ -259,10 +279,6 @@ module Dynflow
|
|
259
279
|
File.expand_path('../sequel_migrations', __FILE__)
|
260
280
|
end
|
261
281
|
|
262
|
-
def migrate_db
|
263
|
-
::Sequel::Migrator.run(db, self.class.migrations_path, table: 'dynflow_schema_info')
|
264
|
-
end
|
265
|
-
|
266
282
|
def prepare_record(table_name, value, base = {}, with_data = true)
|
267
283
|
record = base.dup
|
268
284
|
if with_data && table(table_name).columns.include?(:data)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
Sequel.migration do
|
3
|
+
up do
|
4
|
+
alter_table(:dynflow_actions) do
|
5
|
+
drop_index [:execution_plan_uuid, :id]
|
6
|
+
end
|
7
|
+
|
8
|
+
alter_table(:dynflow_execution_plans) do
|
9
|
+
drop_index :uuid
|
10
|
+
end
|
11
|
+
|
12
|
+
alter_table(:dynflow_steps) do
|
13
|
+
drop_index [:execution_plan_uuid, :id]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
down do
|
18
|
+
alter_table(:dynflow_actions) do
|
19
|
+
add_index [:execution_plan_uuid, :id], :unique => true
|
20
|
+
end
|
21
|
+
|
22
|
+
alter_table(:dynflow_execution_plans) do
|
23
|
+
add_index :uuid, :unique => true
|
24
|
+
end
|
25
|
+
|
26
|
+
alter_table(:dynflow_steps) do
|
27
|
+
add_index [:execution_plan_uuid, :id], :unique => true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -100,8 +100,15 @@ module Dynflow
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def calculate_db_pool_size(world)
|
103
|
-
self.db_pool_size
|
104
|
-
|
103
|
+
return self.db_pool_size if self.db_pool_size
|
104
|
+
|
105
|
+
base_value = 5
|
106
|
+
if defined?(::Sidekiq)
|
107
|
+
Sidekiq.options[:concurrency] + base_value
|
108
|
+
else
|
109
|
+
world.config.queues.values.inject(base_value) do |pool_size, pool_options|
|
110
|
+
pool_size += pool_options[:pool_size]
|
111
|
+
end
|
105
112
|
end
|
106
113
|
end
|
107
114
|
|
@@ -185,8 +192,8 @@ module Dynflow
|
|
185
192
|
end
|
186
193
|
|
187
194
|
# Sequel adapter based on Rails app database.yml configuration
|
188
|
-
def initialize_persistence(world)
|
189
|
-
persistence_class.new(default_sequel_adapter_options(world))
|
195
|
+
def initialize_persistence(world, options = {})
|
196
|
+
persistence_class.new(default_sequel_adapter_options(world).merge(options))
|
190
197
|
end
|
191
198
|
end
|
192
199
|
end
|
data/lib/dynflow/version.rb
CHANGED
@@ -28,6 +28,8 @@ module Dynflow
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
pruned = persistence.prune_envelopes(world.id)
|
32
|
+
logger.error("Pruned #{pruned} envelopes for invalidated world #{world.id}") unless pruned.zero?
|
31
33
|
coordinator.delete_world(world)
|
32
34
|
end
|
33
35
|
end
|
@@ -115,6 +117,8 @@ module Dynflow
|
|
115
117
|
def perform_validity_checks
|
116
118
|
world_invalidation_result = worlds_validity_check
|
117
119
|
locks_validity_check
|
120
|
+
pruned = connector.prune_undeliverable_envelopes(self)
|
121
|
+
logger.error("Pruned #{pruned} undeliverable envelopes") unless pruned.zero?
|
118
122
|
world_invalidation_result.values.select { |result| result == :invalidated }.size
|
119
123
|
end
|
120
124
|
|
data/test/persistence_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require_relative 'test_helper'
|
3
3
|
require 'tmpdir'
|
4
|
+
require 'ostruct'
|
4
5
|
|
5
6
|
module Dynflow
|
6
7
|
module PersistenceTest
|
@@ -371,6 +372,41 @@ module Dynflow
|
|
371
372
|
assert_equal [], adapter.pull_envelopes(executor_world_id)
|
372
373
|
end
|
373
374
|
|
375
|
+
it 'supports pruning of envelopes of invalidated worlds' do
|
376
|
+
client_world_id = '5678'
|
377
|
+
executor_world_id = '1234'
|
378
|
+
envelope_hash = ->(envelope) { Dynflow::Utils.indifferent_hash(Dynflow.serializer.dump(envelope)) }
|
379
|
+
executor_envelope = envelope_hash.call(Dispatcher::Envelope['123', client_world_id, executor_world_id, Dispatcher::Execution['111']])
|
380
|
+
client_envelope = envelope_hash.call(Dispatcher::Envelope['123', executor_world_id, client_world_id, Dispatcher::Accepted])
|
381
|
+
envelopes = [client_envelope, executor_envelope]
|
382
|
+
|
383
|
+
envelopes.each { |e| adapter.push_envelope(e) }
|
384
|
+
|
385
|
+
assert_equal 1, adapter.prune_envelopes([executor_world_id])
|
386
|
+
assert_equal 0, adapter.prune_envelopes([executor_world_id])
|
387
|
+
assert_equal [], adapter.pull_envelopes(executor_world_id)
|
388
|
+
assert_equal [client_envelope], adapter.pull_envelopes(client_world_id)
|
389
|
+
end
|
390
|
+
|
391
|
+
it 'supports pruning of orphaned envelopes' do
|
392
|
+
client_world_id = '5678'
|
393
|
+
executor_world_id = '1234'
|
394
|
+
envelope_hash = ->(envelope) { Dynflow::Utils.indifferent_hash(Dynflow.serializer.dump(envelope)) }
|
395
|
+
executor_envelope = envelope_hash.call(Dispatcher::Envelope['123', client_world_id, executor_world_id, Dispatcher::Execution['111']])
|
396
|
+
client_envelope = envelope_hash.call(Dispatcher::Envelope['123', executor_world_id, client_world_id, Dispatcher::Accepted])
|
397
|
+
envelopes = [client_envelope, executor_envelope]
|
398
|
+
|
399
|
+
envelopes.each { |e| adapter.push_envelope(e) }
|
400
|
+
adapter.insert_coordinator_record({"class"=>"Dynflow::Coordinator::ExecutorWorld",
|
401
|
+
"id" => executor_world_id, "meta" => {}, "active" => true })
|
402
|
+
|
403
|
+
assert_equal 1, adapter.prune_undeliverable_envelopes
|
404
|
+
assert_equal 0, adapter.prune_undeliverable_envelopes
|
405
|
+
assert_equal [], adapter.pull_envelopes(client_world_id)
|
406
|
+
assert_equal [executor_envelope], adapter.pull_envelopes(executor_world_id)
|
407
|
+
assert_equal [], adapter.pull_envelopes(executor_world_id)
|
408
|
+
end
|
409
|
+
|
374
410
|
it 'supports reading data saved prior to normalization' do
|
375
411
|
db = adapter.send(:db)
|
376
412
|
# Prepare records for saving
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.4.
|
4
|
+
version: 1.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Necas
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-05-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -512,6 +512,7 @@ files:
|
|
512
512
|
- lib/dynflow/persistence_adapters/sequel_migrations/017_add_delayed_plan_frozen.rb
|
513
513
|
- lib/dynflow/persistence_adapters/sequel_migrations/018_add_uuid_column.rb
|
514
514
|
- lib/dynflow/persistence_adapters/sequel_migrations/019_update_mysql_time_precision.rb
|
515
|
+
- lib/dynflow/persistence_adapters/sequel_migrations/020_drop_duplicate_indices.rb
|
515
516
|
- lib/dynflow/rails.rb
|
516
517
|
- lib/dynflow/rails/configuration.rb
|
517
518
|
- lib/dynflow/rails/daemon.rb
|