dynflow 1.3.0 → 1.4.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.
Files changed (225) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.travis.yml +3 -4
  4. data/Dockerfile +9 -0
  5. data/Gemfile +6 -0
  6. data/Rakefile +1 -0
  7. data/doc/pages/Gemfile +1 -0
  8. data/doc/pages/Rakefile +1 -0
  9. data/doc/pages/plugins/alert_block.rb +1 -0
  10. data/doc/pages/plugins/div_tag.rb +1 -0
  11. data/doc/pages/plugins/graphviz.rb +1 -0
  12. data/doc/pages/plugins/plantuml.rb +1 -0
  13. data/doc/pages/plugins/play.rb +1 -0
  14. data/doc/pages/plugins/tags.rb +1 -0
  15. data/doc/pages/plugins/toc.rb +1 -0
  16. data/docker-compose.yml +41 -0
  17. data/dynflow.gemspec +1 -0
  18. data/examples/clock_benchmark.rb +1 -0
  19. data/examples/example_helper.rb +19 -2
  20. data/examples/future_execution.rb +2 -1
  21. data/examples/memory_limit_watcher.rb +1 -0
  22. data/examples/orchestrate.rb +4 -5
  23. data/examples/orchestrate_evented.rb +3 -2
  24. data/examples/remote_executor.rb +68 -0
  25. data/examples/singletons.rb +4 -3
  26. data/examples/sub_plan_concurrency_control.rb +2 -1
  27. data/examples/sub_plans.rb +3 -2
  28. data/examples/termination.rb +1 -0
  29. data/lib/dynflow.rb +20 -0
  30. data/lib/dynflow/action.rb +28 -3
  31. data/lib/dynflow/action/cancellable.rb +1 -0
  32. data/lib/dynflow/action/format.rb +1 -0
  33. data/lib/dynflow/action/missing.rb +1 -0
  34. data/lib/dynflow/action/polling.rb +3 -1
  35. data/lib/dynflow/action/progress.rb +1 -0
  36. data/lib/dynflow/action/rescue.rb +1 -0
  37. data/lib/dynflow/action/singleton.rb +1 -0
  38. data/lib/dynflow/action/suspended.rb +9 -2
  39. data/lib/dynflow/action/timeouts.rb +2 -1
  40. data/lib/dynflow/action/with_bulk_sub_plans.rb +2 -1
  41. data/lib/dynflow/action/with_polling_sub_plans.rb +7 -5
  42. data/lib/dynflow/action/with_sub_plans.rb +1 -0
  43. data/lib/dynflow/active_job/queue_adapter.rb +1 -0
  44. data/lib/dynflow/actor.rb +13 -5
  45. data/lib/dynflow/actors.rb +1 -0
  46. data/lib/dynflow/actors/execution_plan_cleaner.rb +1 -0
  47. data/lib/dynflow/clock.rb +27 -47
  48. data/lib/dynflow/config.rb +11 -2
  49. data/lib/dynflow/connectors.rb +1 -0
  50. data/lib/dynflow/connectors/abstract.rb +1 -0
  51. data/lib/dynflow/connectors/database.rb +1 -0
  52. data/lib/dynflow/connectors/direct.rb +1 -0
  53. data/lib/dynflow/coordinator.rb +1 -0
  54. data/lib/dynflow/coordinator_adapters.rb +1 -0
  55. data/lib/dynflow/coordinator_adapters/abstract.rb +1 -0
  56. data/lib/dynflow/coordinator_adapters/sequel.rb +1 -0
  57. data/lib/dynflow/dead_letter_silencer.rb +2 -0
  58. data/lib/dynflow/debug/telemetry/persistence.rb +1 -0
  59. data/lib/dynflow/delayed_executors.rb +1 -0
  60. data/lib/dynflow/delayed_executors/abstract.rb +1 -0
  61. data/lib/dynflow/delayed_executors/abstract_core.rb +1 -0
  62. data/lib/dynflow/delayed_executors/polling.rb +1 -0
  63. data/lib/dynflow/delayed_plan.rb +1 -0
  64. data/lib/dynflow/director.rb +80 -15
  65. data/lib/dynflow/director/execution_plan_manager.rb +17 -3
  66. data/lib/dynflow/director/flow_manager.rb +1 -0
  67. data/lib/dynflow/director/{work_queue.rb → queue_hash.rb} +9 -8
  68. data/lib/dynflow/director/running_steps_manager.rb +55 -18
  69. data/lib/dynflow/director/sequence_cursor.rb +1 -0
  70. data/lib/dynflow/director/sequential_manager.rb +12 -2
  71. data/lib/dynflow/dispatcher.rb +4 -2
  72. data/lib/dynflow/dispatcher/abstract.rb +1 -0
  73. data/lib/dynflow/dispatcher/client_dispatcher.rb +6 -4
  74. data/lib/dynflow/dispatcher/executor_dispatcher.rb +13 -1
  75. data/lib/dynflow/errors.rb +1 -0
  76. data/lib/dynflow/execution_history.rb +1 -0
  77. data/lib/dynflow/execution_plan.rb +3 -2
  78. data/lib/dynflow/execution_plan/dependency_graph.rb +1 -0
  79. data/lib/dynflow/execution_plan/hooks.rb +1 -0
  80. data/lib/dynflow/execution_plan/output_reference.rb +2 -1
  81. data/lib/dynflow/execution_plan/steps.rb +1 -0
  82. data/lib/dynflow/execution_plan/steps/abstract.rb +10 -5
  83. data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +2 -0
  84. data/lib/dynflow/execution_plan/steps/error.rb +1 -0
  85. data/lib/dynflow/execution_plan/steps/finalize_step.rb +1 -0
  86. data/lib/dynflow/execution_plan/steps/plan_step.rb +1 -0
  87. data/lib/dynflow/execution_plan/steps/run_step.rb +1 -0
  88. data/lib/dynflow/executors.rb +1 -1
  89. data/lib/dynflow/executors/abstract/core.rb +132 -0
  90. data/lib/dynflow/executors/parallel.rb +24 -11
  91. data/lib/dynflow/executors/parallel/core.rb +10 -91
  92. data/lib/dynflow/executors/parallel/pool.rb +4 -2
  93. data/lib/dynflow/executors/parallel/worker.rb +2 -1
  94. data/lib/dynflow/executors/sidekiq/core.rb +121 -0
  95. data/lib/dynflow/executors/sidekiq/internal_job_base.rb +24 -0
  96. data/lib/dynflow/executors/sidekiq/orchestrator_jobs.rb +60 -0
  97. data/lib/dynflow/executors/sidekiq/redis_locking.rb +69 -0
  98. data/lib/dynflow/executors/sidekiq/serialization.rb +33 -0
  99. data/lib/dynflow/executors/sidekiq/worker_jobs.rb +42 -0
  100. data/lib/dynflow/flows.rb +1 -0
  101. data/lib/dynflow/flows/abstract.rb +1 -0
  102. data/lib/dynflow/flows/abstract_composed.rb +1 -0
  103. data/lib/dynflow/flows/atom.rb +1 -0
  104. data/lib/dynflow/flows/concurrence.rb +1 -0
  105. data/lib/dynflow/flows/sequence.rb +1 -0
  106. data/lib/dynflow/logger_adapters.rb +1 -0
  107. data/lib/dynflow/logger_adapters/abstract.rb +1 -0
  108. data/lib/dynflow/logger_adapters/delegator.rb +1 -0
  109. data/lib/dynflow/logger_adapters/formatters.rb +1 -0
  110. data/lib/dynflow/logger_adapters/formatters/abstract.rb +1 -0
  111. data/lib/dynflow/logger_adapters/formatters/exception.rb +1 -0
  112. data/lib/dynflow/logger_adapters/simple.rb +1 -0
  113. data/lib/dynflow/middleware.rb +1 -0
  114. data/lib/dynflow/middleware/common/singleton.rb +1 -0
  115. data/lib/dynflow/middleware/common/transaction.rb +1 -0
  116. data/lib/dynflow/middleware/register.rb +1 -0
  117. data/lib/dynflow/middleware/resolver.rb +1 -0
  118. data/lib/dynflow/middleware/stack.rb +1 -0
  119. data/lib/dynflow/middleware/world.rb +1 -0
  120. data/lib/dynflow/persistence.rb +3 -2
  121. data/lib/dynflow/persistence_adapters.rb +1 -0
  122. data/lib/dynflow/persistence_adapters/abstract.rb +1 -0
  123. data/lib/dynflow/persistence_adapters/sequel.rb +10 -7
  124. data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +1 -0
  125. data/lib/dynflow/persistence_adapters/sequel_migrations/002_incremental_progress.rb +1 -0
  126. data/lib/dynflow/persistence_adapters/sequel_migrations/003_parent_action.rb +1 -0
  127. data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +1 -0
  128. data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +1 -0
  129. data/lib/dynflow/persistence_adapters/sequel_migrations/006_fix_data_length.rb +1 -0
  130. data/lib/dynflow/persistence_adapters/sequel_migrations/007_future_execution.rb +1 -0
  131. data/lib/dynflow/persistence_adapters/sequel_migrations/008_rename_scheduled_plans_to_delayed_plans.rb +1 -0
  132. data/lib/dynflow/persistence_adapters/sequel_migrations/009_fix_mysql_data_length.rb +1 -0
  133. data/lib/dynflow/persistence_adapters/sequel_migrations/010_add_execution_plans_label.rb +1 -0
  134. data/lib/dynflow/persistence_adapters/sequel_migrations/011_placeholder.rb +1 -0
  135. data/lib/dynflow/persistence_adapters/sequel_migrations/012_add_delayed_plans_serialized_args.rb +1 -0
  136. data/lib/dynflow/persistence_adapters/sequel_migrations/013_add_action_columns.rb +1 -0
  137. data/lib/dynflow/persistence_adapters/sequel_migrations/014_add_step_columns.rb +1 -0
  138. data/lib/dynflow/persistence_adapters/sequel_migrations/015_add_execution_plan_columns.rb +1 -0
  139. data/lib/dynflow/persistence_adapters/sequel_migrations/016_add_step_queue.rb +1 -0
  140. data/lib/dynflow/persistence_adapters/sequel_migrations/017_add_delayed_plan_frozen.rb +1 -0
  141. data/lib/dynflow/persistence_adapters/sequel_migrations/018_add_uuid_column.rb +1 -0
  142. data/lib/dynflow/persistence_adapters/sequel_migrations/019_update_mysql_time_precision.rb +48 -0
  143. data/lib/dynflow/rails.rb +1 -0
  144. data/lib/dynflow/rails/configuration.rb +6 -3
  145. data/lib/dynflow/rails/daemon.rb +1 -0
  146. data/lib/dynflow/round_robin.rb +1 -0
  147. data/lib/dynflow/semaphores.rb +1 -0
  148. data/lib/dynflow/semaphores/abstract.rb +1 -0
  149. data/lib/dynflow/semaphores/aggregating.rb +1 -0
  150. data/lib/dynflow/semaphores/dummy.rb +1 -0
  151. data/lib/dynflow/semaphores/stateful.rb +1 -0
  152. data/lib/dynflow/serializable.rb +13 -4
  153. data/lib/dynflow/serializer.rb +24 -0
  154. data/lib/dynflow/serializers.rb +1 -0
  155. data/lib/dynflow/serializers/abstract.rb +1 -0
  156. data/lib/dynflow/serializers/noop.rb +1 -0
  157. data/lib/dynflow/stateful.rb +1 -0
  158. data/lib/dynflow/telemetry.rb +1 -0
  159. data/lib/dynflow/telemetry_adapters/abstract.rb +1 -0
  160. data/lib/dynflow/telemetry_adapters/dummy.rb +1 -0
  161. data/lib/dynflow/telemetry_adapters/statsd.rb +1 -0
  162. data/lib/dynflow/testing.rb +1 -0
  163. data/lib/dynflow/testing/assertions.rb +6 -5
  164. data/lib/dynflow/testing/dummy_execution_plan.rb +1 -0
  165. data/lib/dynflow/testing/dummy_executor.rb +19 -2
  166. data/lib/dynflow/testing/dummy_planned_action.rb +1 -0
  167. data/lib/dynflow/testing/dummy_step.rb +3 -1
  168. data/lib/dynflow/testing/dummy_world.rb +9 -0
  169. data/lib/dynflow/testing/factories.rb +6 -1
  170. data/lib/dynflow/testing/in_thread_executor.rb +22 -3
  171. data/lib/dynflow/testing/in_thread_world.rb +9 -0
  172. data/lib/dynflow/testing/managed_clock.rb +1 -0
  173. data/lib/dynflow/testing/mimic.rb +1 -0
  174. data/lib/dynflow/throttle_limiter.rb +1 -0
  175. data/lib/dynflow/transaction_adapters.rb +1 -0
  176. data/lib/dynflow/transaction_adapters/abstract.rb +1 -0
  177. data/lib/dynflow/transaction_adapters/active_record.rb +1 -0
  178. data/lib/dynflow/transaction_adapters/none.rb +1 -0
  179. data/lib/dynflow/utils.rb +1 -0
  180. data/lib/dynflow/utils/indifferent_hash.rb +1 -0
  181. data/lib/dynflow/utils/priority_queue.rb +1 -0
  182. data/lib/dynflow/version.rb +2 -1
  183. data/lib/dynflow/watchers/memory_consumption_watcher.rb +1 -0
  184. data/lib/dynflow/web.rb +1 -0
  185. data/lib/dynflow/web/console.rb +1 -0
  186. data/lib/dynflow/web/console_helpers.rb +1 -0
  187. data/lib/dynflow/web/filtering_helpers.rb +1 -0
  188. data/lib/dynflow/web/world_helpers.rb +1 -0
  189. data/lib/dynflow/web_console.rb +1 -0
  190. data/lib/dynflow/world.rb +11 -1
  191. data/lib/dynflow/world/invalidation.rb +7 -1
  192. data/test/abnormal_states_recovery_test.rb +41 -40
  193. data/test/action_test.rb +160 -110
  194. data/test/activejob_adapter_test.rb +1 -0
  195. data/test/batch_sub_tasks_test.rb +12 -11
  196. data/test/clock_test.rb +2 -1
  197. data/test/concurrency_control_test.rb +20 -19
  198. data/test/coordinator_test.rb +20 -21
  199. data/test/daemon_test.rb +2 -1
  200. data/test/dead_letter_silencer_test.rb +9 -7
  201. data/test/dispatcher_test.rb +2 -1
  202. data/test/execution_plan_cleaner_test.rb +13 -12
  203. data/test/execution_plan_hooks_test.rb +3 -2
  204. data/test/execution_plan_test.rb +33 -32
  205. data/test/executor_test.rb +533 -489
  206. data/test/future_execution_test.rb +45 -44
  207. data/test/memory_cosumption_watcher_test.rb +5 -4
  208. data/test/middleware_test.rb +55 -54
  209. data/test/persistence_test.rb +56 -53
  210. data/test/rescue_test.rb +36 -35
  211. data/test/round_robin_test.rb +13 -12
  212. data/test/semaphores_test.rb +31 -30
  213. data/test/support/code_workflow_example.rb +1 -0
  214. data/test/support/dummy_example.rb +14 -1
  215. data/test/support/middleware_example.rb +2 -1
  216. data/test/support/rails/config/environment.rb +1 -0
  217. data/test/support/rescue_example.rb +1 -0
  218. data/test/support/test_execution_log.rb +1 -0
  219. data/test/test_helper.rb +18 -17
  220. data/test/testing_test.rb +45 -44
  221. data/test/utils_test.rb +18 -17
  222. data/test/web_console_test.rb +1 -0
  223. data/test/world_test.rb +7 -6
  224. metadata +13 -4
  225. data/lib/dynflow/executors/abstract.rb +0 -40
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module Connectors
3
4
  require 'dynflow/connectors/abstract'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module Connectors
3
4
  class Abstract
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module Connectors
3
4
  class Database < Abstract
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module Connectors
3
4
  class Direct < Abstract
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'dynflow/coordinator_adapters'
2
3
 
3
4
  module Dynflow
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module CoordinatorAdapters
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module CoordinatorAdapters
3
4
  class Abstract
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module CoordinatorAdapters
3
4
  class Sequel < Abstract
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  class DeadLetterSilencer < Concurrent::Actor::DefaultDeadLetterHandler
3
4
  def initialize(matchers)
@@ -24,6 +25,7 @@ module Dynflow
24
25
  end
25
26
 
26
27
  def match?(dead_letter)
28
+ return unless dead_letter.sender.respond_to?(:actor_class)
27
29
  evaluate(dead_letter.sender.actor_class, @from) &&
28
30
  evaluate(dead_letter.message, @message) &&
29
31
  evaluate(dead_letter.address.actor_class, @to)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module Debug
3
4
  module Telemetry
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module DelayedExecutors
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module DelayedExecutors
3
4
  class Abstract
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module DelayedExecutors
3
4
  class AbstractCore < Actor
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  module DelayedExecutors
3
4
  class Polling < Abstract
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  class DelayedPlan < Serializable
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  # Director is responsible for telling what to do next when:
3
4
  # * new execution starts
@@ -10,7 +11,8 @@ module Dynflow
10
11
  include Algebrick::TypeCheck
11
12
 
12
13
  Event = Algebrick.type do
13
- fields! execution_plan_id: String,
14
+ fields! request_id: String,
15
+ execution_plan_id: String,
14
16
  step_id: Integer,
15
17
  event: Object,
16
18
  result: Concurrent::Promises::ResolvableFuture
@@ -18,57 +20,119 @@ module Dynflow
18
20
 
19
21
  UnprocessableEvent = Class.new(Dynflow::Error)
20
22
 
21
- class WorkItem
22
- attr_reader :execution_plan_id, :queue
23
+ class WorkItem < Serializable
24
+ attr_reader :execution_plan_id, :queue, :sender_orchestrator_id
23
25
 
24
- def initialize(execution_plan_id, queue)
26
+ def initialize(execution_plan_id, queue, sender_orchestrator_id)
25
27
  @execution_plan_id = execution_plan_id
26
28
  @queue = queue
29
+ @sender_orchestrator_id = sender_orchestrator_id
30
+ end
31
+
32
+ def world
33
+ raise "World expected but not set for the work item #{self}" unless @world
34
+ @world
35
+ end
36
+
37
+ # the world to be used for execution purposes of the step. Setting it separately and explicitly
38
+ # as the world can't be serialized
39
+ def world=(world)
40
+ @world = world
27
41
  end
28
42
 
29
43
  def execute
30
44
  raise NotImplementedError
31
45
  end
46
+
47
+ def to_hash
48
+ { class: self.class.name,
49
+ execution_plan_id: execution_plan_id,
50
+ queue: queue,
51
+ sender_orchestrator_id: sender_orchestrator_id }
52
+ end
53
+
54
+ def self.new_from_hash(hash, *_args)
55
+ self.new(hash[:execution_plan_id], hash[:queue])
56
+ end
32
57
  end
33
58
 
34
59
  class StepWorkItem < WorkItem
35
60
  attr_reader :step
36
61
 
37
- def initialize(execution_plan_id, step, queue)
38
- super(execution_plan_id, queue)
62
+ def initialize(execution_plan_id, step, queue, sender_orchestrator_id)
63
+ super(execution_plan_id, queue, sender_orchestrator_id)
39
64
  @step = step
40
65
  end
41
66
 
42
67
  def execute
43
68
  @step.execute(nil)
44
69
  end
70
+
71
+ def to_hash
72
+ super.merge(step: step.to_hash)
73
+ end
74
+
75
+ def self.new_from_hash(hash, *_args)
76
+ self.new(hash[:execution_plan_id],
77
+ Serializable.from_hash(hash[:step], hash[:execution_plan_id], Dynflow.process_world),
78
+ hash[:queue],
79
+ hash[:sender_orchestrator_id])
80
+ end
45
81
  end
46
82
 
47
83
  class EventWorkItem < StepWorkItem
48
- attr_reader :event
84
+ attr_reader :event, :request_id
49
85
 
50
- def initialize(execution_plan_id, step, event, queue)
51
- super(execution_plan_id, step, queue)
86
+ def initialize(request_id, execution_plan_id, step, event, queue, sender_orchestrator_id)
87
+ super(execution_plan_id, step, queue, sender_orchestrator_id)
52
88
  @event = event
89
+ @request_id = request_id
53
90
  end
54
91
 
55
92
  def execute
56
- @step.execute(@event.event)
93
+ @step.execute(@event)
94
+ end
95
+
96
+ def to_hash
97
+ super.merge(request_id: @request_id, event: Dynflow.serializer.dump(@event))
98
+ end
99
+
100
+ def self.new_from_hash(hash, *_args)
101
+ self.new(hash[:request_id],
102
+ hash[:execution_plan_id],
103
+ Serializable.from_hash(hash[:step], hash[:execution_plan_id], Dynflow.process_world),
104
+ Dynflow.serializer.load(hash[:event]),
105
+ hash[:queue],
106
+ hash[:sender_orchestrator_id])
57
107
  end
58
108
  end
59
109
 
60
110
  class FinalizeWorkItem < WorkItem
61
- def initialize(execution_plan_id, sequential_manager, queue)
62
- super(execution_plan_id, queue)
63
- @sequential_manager = sequential_manager
111
+ attr_reader :finalize_steps_data
112
+
113
+ # @param finalize_steps_data - used to pass the result steps from the worker back to orchestrator
114
+ def initialize(execution_plan_id, queue, sender_orchestrator_id, finalize_steps_data = nil)
115
+ super(execution_plan_id, queue, sender_orchestrator_id)
116
+ @finalize_steps_data = finalize_steps_data
64
117
  end
65
118
 
66
119
  def execute
67
- @sequential_manager.finalize
120
+ execution_plan = world.persistence.load_execution_plan(execution_plan_id)
121
+ manager = Director::SequentialManager.new(world, execution_plan)
122
+ manager.finalize
123
+ @finalize_steps_data = manager.finalize_steps.map(&:to_hash)
124
+ end
125
+
126
+ def to_hash
127
+ super.merge(finalize_steps_data: @finalize_steps_data)
128
+ end
129
+
130
+ def self.new_from_hash(hash, *_args)
131
+ self.new(*hash.values_at(:execution_plan_id, :queue, :sender_orchestrator_id, :finalize_steps_data))
68
132
  end
69
133
  end
70
134
 
71
- require 'dynflow/director/work_queue'
135
+ require 'dynflow/director/queue_hash'
72
136
  require 'dynflow/director/sequence_cursor'
73
137
  require 'dynflow/director/flow_manager'
74
138
  require 'dynflow/director/execution_plan_manager'
@@ -109,6 +173,7 @@ module Dynflow
109
173
 
110
174
  def work_finished(work)
111
175
  manager = @execution_plan_managers[work.execution_plan_id]
176
+ return [] unless manager # skip case when getting event from execution plan that is not running anymore
112
177
  unless_done(manager, manager.what_is_next(work))
113
178
  end
114
179
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  class Director
3
4
  class ExecutionPlanManager
@@ -30,7 +31,7 @@ module Dynflow
30
31
  end
31
32
 
32
33
  def prepare_next_step(step)
33
- StepWorkItem.new(execution_plan.id, step, step.queue).tap do |work|
34
+ StepWorkItem.new(execution_plan.id, step, step.queue, @world.id).tap do |work|
34
35
  @running_steps_manager.add(step, work)
35
36
  end
36
37
  end
@@ -42,13 +43,22 @@ module Dynflow
42
43
  case work
43
44
  when StepWorkItem
44
45
  step = work.step
45
- execution_plan.steps[step.id] = step
46
+ update_steps([step])
46
47
  suspended, work = @running_steps_manager.done(step)
47
48
  work = compute_next_from_step(step) unless suspended
48
49
  work
49
50
  when FinalizeWorkItem
51
+ if work.finalize_steps_data
52
+ steps = work.finalize_steps_data.map do |step_data|
53
+ Serializable.from_hash(step_data, execution_plan.id, @world)
54
+ end
55
+ update_steps(steps)
56
+ end
50
57
  raise "Finalize work item without @finalize_manager ready" unless @finalize_manager
58
+ @finalize_manager.done!
51
59
  finish
60
+ else
61
+ raise "Unexpected work #{work}"
52
62
  end
53
63
  end
54
64
 
@@ -70,6 +80,10 @@ module Dynflow
70
80
 
71
81
  private
72
82
 
83
+ def update_steps(steps)
84
+ steps.each { |step| execution_plan.steps[step.id] = step }
85
+ end
86
+
73
87
  def compute_next_from_step(step)
74
88
  raise "run manager not set" unless @run_manager
75
89
  raise "run manager already done" if @run_manager.done?
@@ -98,7 +112,7 @@ module Dynflow
98
112
  return if execution_plan.finalize_flow.empty?
99
113
  raise 'finalize phase already started' if @finalize_manager
100
114
  @finalize_manager = SequentialManager.new(@world, execution_plan)
101
- [FinalizeWorkItem.new(execution_plan.id, @finalize_manager, execution_plan.finalize_steps.first.queue)]
115
+ [FinalizeWorkItem.new(execution_plan.id, execution_plan.finalize_steps.first.queue, @world.id)]
102
116
  end
103
117
 
104
118
  def finish
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  class Director
3
4
  class FlowManager
@@ -1,23 +1,24 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  class Director
3
- class WorkQueue
4
+ class QueueHash
4
5
  include Algebrick::TypeCheck
5
6
 
6
- def initialize(key_type = Object, work_type = Object)
7
- @key_type = key_type
8
- @work_type = work_type
7
+ def initialize(key_type = Object, value_type = Object)
8
+ @key_type = key_type
9
+ @value_type = value_type
9
10
  @stash = Hash.new { |hash, key| hash[key] = [] }
10
11
  end
11
12
 
12
- def push(key, work)
13
+ def push(key, value)
13
14
  Type! key, @key_type
14
- Type! work, @work_type
15
- @stash[key].push work
15
+ Type! value, @value_type
16
+ @stash[key].push value
16
17
  end
17
18
 
18
19
  def shift(key)
19
20
  return nil unless present? key
20
- @stash[key].shift.tap { |work| @stash.delete(key) if @stash[key].empty? }
21
+ @stash[key].shift.tap { @stash.delete(key) if @stash[key].empty? }
21
22
  end
22
23
 
23
24
  def present?(key)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Dynflow
2
3
  class Director
3
4
  # Handles the events generated while running actions, makes sure
@@ -8,11 +9,16 @@ module Dynflow
8
9
  def initialize(world)
9
10
  @world = Type! world, World
10
11
  @running_steps = {}
11
- @events = WorkQueue.new(Integer, WorkItem)
12
+ # enqueued work items by step id
13
+ @work_items = QueueHash.new(Integer, WorkItem)
14
+ # enqueued events by step id - we delay creating work items from events until execution time
15
+ # to handle potential updates of the step object (that is part of the event)
16
+ @events = QueueHash.new(Integer, Director::Event)
17
+ @events_by_request_id = {}
12
18
  end
13
19
 
14
20
  def terminate
15
- pending_work = @events.clear.values.flatten(1)
21
+ pending_work = @work_items.clear.values.flatten(1)
16
22
  pending_work.each do |w|
17
23
  if EventWorkItem === w
18
24
  w.event.result.reject UnprocessableEvent.new("dropping due to termination")
@@ -24,27 +30,38 @@ module Dynflow
24
30
  Type! step, ExecutionPlan::Steps::RunStep
25
31
  @running_steps[step.id] = step
26
32
  # we make sure not to run any event when the step is still being executed
27
- @events.push(step.id, work)
33
+ @work_items.push(step.id, work)
28
34
  self
29
35
  end
30
36
 
31
37
  # @returns [TrueClass|FalseClass, Array<WorkItem>]
32
38
  def done(step)
33
39
  Type! step, ExecutionPlan::Steps::RunStep
34
- @events.shift(step.id).tap do |work|
35
- work.event.result.fulfill true if EventWorkItem === work
40
+ # update the step based on the latest finished work
41
+ @running_steps[step.id] = step
42
+
43
+ @work_items.shift(step.id).tap do |work|
44
+ finish_event_result(work) { |f| f.fulfill true }
36
45
  end
37
46
 
38
47
  if step.state == :suspended
39
- return true, [@events.first(step.id)].compact
48
+ return true, [create_next_event_work_item(step)].compact
40
49
  else
50
+ while (work = @work_items.shift(step.id))
51
+ @world.logger.debug "step #{step.execution_plan_id}:#{step.id} dropping event #{work.request_id}/#{work.event}"
52
+ finish_event_result(work) do |f|
53
+ f.reject UnprocessableEvent.new("Message dropped").tap { |e| e.set_backtrace(caller) }
54
+ end
55
+ end
41
56
  while (event = @events.shift(step.id))
42
- message = "step #{step.execution_plan_id}:#{step.id} dropping event #{event.event}"
43
- @world.logger.warn message
44
- event.event.result.reject UnprocessableEvent.new(message).
45
- tap { |e| e.set_backtrace(caller) }
57
+ @world.logger.debug "step #{step.execution_plan_id}:#{step.id} dropping event #{event.request_id}/#{event}"
58
+ if event.result
59
+ event.result.reject UnprocessableEvent.new("Message dropped").tap { |e| e.set_backtrace(caller) }
60
+ end
61
+ end
62
+ unless @work_items.empty?(step.id) && @events.empty?(step.id)
63
+ raise "Unexpected item in @work_items (#{@work_items.inspect}) or @events (#{@events.inspect})"
46
64
  end
47
- raise 'assert' unless @events.empty?(step.id)
48
65
  @running_steps.delete(step.id)
49
66
  return false, []
50
67
  end
@@ -60,19 +77,39 @@ module Dynflow
60
77
  # @returns [Array<WorkItem>]
61
78
  def event(event)
62
79
  Type! event, Event
63
- next_work_items = []
64
80
 
65
81
  step = @running_steps[event.step_id]
66
82
  unless step
67
83
  event.result.reject UnprocessableEvent.new('step is not suspended, it cannot process events')
68
- return next_work_items
84
+ return []
69
85
  end
70
86
 
71
- can_run_event = @events.empty?(step.id)
72
- work = EventWorkItem.new(event.execution_plan_id, step, event, step.queue)
73
- @events.push(step.id, work)
74
- next_work_items << work if can_run_event
75
- next_work_items
87
+ can_run_event = @work_items.empty?(step.id)
88
+ @events_by_request_id[event.request_id] = event
89
+ @events.push(step.id, event)
90
+ if can_run_event
91
+ [create_next_event_work_item(step)]
92
+ else
93
+ []
94
+ end
95
+ end
96
+
97
+ # turns the first event from the queue to the next work item to work on
98
+ def create_next_event_work_item(step)
99
+ event = @events.shift(step.id)
100
+ return unless event
101
+ work = EventWorkItem.new(event.request_id, event.execution_plan_id, step, event.event, step.queue, @world.id)
102
+ @work_items.push(step.id, work)
103
+ work
104
+ end
105
+
106
+ # @yield [Concurrent.resolvable_future] in case the work item has an result future assigned
107
+ # and deletes the tracked event
108
+ def finish_event_result(work_item)
109
+ return unless EventWorkItem === work_item
110
+ if event = @events_by_request_id.delete(work_item.request_id)
111
+ yield event.result if event.result
112
+ end
76
113
  end
77
114
  end
78
115
  end