dynflow 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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