dynflow 0.7.9 → 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.
- data/.gitignore +2 -0
- data/.travis.yml +16 -1
- data/Gemfile +13 -1
- data/doc/pages/source/_drafts/2015-03-01-new-documentation.markdown +10 -0
- data/doc/pages/source/_includes/menu.html +1 -0
- data/doc/pages/source/_includes/menu_right.html +1 -1
- data/doc/pages/source/_sass/_bootstrap-variables.sass +1 -0
- data/doc/pages/source/_sass/_style.scss +4 -0
- data/doc/pages/source/blog/index.html +12 -0
- data/doc/pages/source/documentation/index.md +330 -5
- data/dynflow.gemspec +3 -1
- data/examples/example_helper.rb +18 -11
- data/examples/orchestrate_evented.rb +2 -1
- data/examples/remote_executor.rb +53 -20
- data/lib/dynflow.rb +16 -6
- data/lib/dynflow/action/suspended.rb +1 -1
- data/lib/dynflow/action/with_sub_plans.rb +3 -6
- data/lib/dynflow/actor.rb +56 -0
- data/lib/dynflow/clock.rb +43 -38
- data/lib/dynflow/config.rb +107 -0
- data/lib/dynflow/connectors.rb +7 -0
- data/lib/dynflow/connectors/abstract.rb +41 -0
- data/lib/dynflow/connectors/database.rb +175 -0
- data/lib/dynflow/connectors/direct.rb +71 -0
- data/lib/dynflow/coordinator.rb +280 -0
- data/lib/dynflow/coordinator_adapters.rb +8 -0
- data/lib/dynflow/coordinator_adapters/abstract.rb +28 -0
- data/lib/dynflow/coordinator_adapters/sequel.rb +29 -0
- data/lib/dynflow/dispatcher.rb +58 -0
- data/lib/dynflow/dispatcher/abstract.rb +14 -0
- data/lib/dynflow/dispatcher/client_dispatcher.rb +139 -0
- data/lib/dynflow/dispatcher/executor_dispatcher.rb +86 -0
- data/lib/dynflow/errors.rb +7 -1
- data/lib/dynflow/execution_history.rb +46 -0
- data/lib/dynflow/execution_plan.rb +19 -15
- data/lib/dynflow/executors.rb +0 -1
- data/lib/dynflow/executors/abstract.rb +5 -10
- data/lib/dynflow/executors/parallel.rb +16 -13
- data/lib/dynflow/executors/parallel/core.rb +76 -78
- data/lib/dynflow/executors/parallel/execution_plan_manager.rb +4 -5
- data/lib/dynflow/executors/parallel/pool.rb +22 -52
- data/lib/dynflow/executors/parallel/running_steps_manager.rb +9 -2
- data/lib/dynflow/executors/parallel/worker.rb +5 -10
- data/lib/dynflow/persistence.rb +14 -0
- data/lib/dynflow/persistence_adapters/abstract.rb +14 -3
- data/lib/dynflow/persistence_adapters/sequel.rb +142 -38
- data/lib/dynflow/persistence_adapters/sequel_migrations/004_coordinator_records.rb +14 -0
- data/lib/dynflow/persistence_adapters/sequel_migrations/005_envelopes.rb +14 -0
- data/lib/dynflow/round_robin.rb +37 -0
- data/lib/dynflow/serializable.rb +1 -2
- data/lib/dynflow/serializer.rb +46 -0
- data/lib/dynflow/testing/dummy_executor.rb +2 -2
- data/lib/dynflow/testing/dummy_world.rb +1 -1
- data/lib/dynflow/transaction_adapters/abstract.rb +0 -5
- data/lib/dynflow/transaction_adapters/active_record.rb +0 -10
- data/lib/dynflow/version.rb +1 -1
- data/lib/dynflow/web.rb +26 -0
- data/lib/dynflow/web/console.rb +108 -0
- data/lib/dynflow/web/console_helpers.rb +158 -0
- data/lib/dynflow/web/filtering_helpers.rb +85 -0
- data/lib/dynflow/web/world_helpers.rb +9 -0
- data/lib/dynflow/web_console.rb +3 -310
- data/lib/dynflow/world.rb +188 -119
- data/test/abnormal_states_recovery_test.rb +152 -0
- data/test/action_test.rb +2 -3
- data/test/clock_test.rb +1 -5
- data/test/coordinator_test.rb +152 -0
- data/test/dispatcher_test.rb +146 -0
- data/test/execution_plan_test.rb +2 -1
- data/test/executor_test.rb +534 -612
- data/test/middleware_test.rb +4 -4
- data/test/persistence_test.rb +17 -0
- data/test/prepare_travis_env.sh +35 -0
- data/test/rescue_test.rb +5 -3
- data/test/round_robin_test.rb +28 -0
- data/test/support/code_workflow_example.rb +0 -73
- data/test/support/dummy_example.rb +130 -0
- data/test/support/test_execution_log.rb +41 -0
- data/test/test_helper.rb +222 -116
- data/test/testing_test.rb +10 -10
- data/test/web_console_test.rb +3 -3
- data/test/world_test.rb +23 -0
- data/web/assets/images/logo-square.png +0 -0
- data/web/assets/stylesheets/application.css +9 -0
- data/web/assets/vendor/bootstrap/config.json +429 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-theme.css +479 -0
- data/web/assets/vendor/bootstrap/css/bootstrap-theme.min.css +10 -0
- data/web/assets/vendor/bootstrap/css/bootstrap.css +5377 -4980
- data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -8
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.svg +288 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- data/web/assets/vendor/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/web/assets/vendor/bootstrap/js/bootstrap.js +1674 -1645
- data/web/assets/vendor/bootstrap/js/bootstrap.min.js +11 -5
- data/web/views/execution_history.erb +17 -0
- data/web/views/index.erb +4 -6
- data/web/views/layout.erb +44 -8
- data/web/views/show.erb +4 -5
- data/web/views/worlds.erb +26 -0
- metadata +116 -23
- checksums.yaml +0 -15
- data/lib/dynflow/daemon.rb +0 -30
- data/lib/dynflow/executors/remote_via_socket.rb +0 -43
- data/lib/dynflow/executors/remote_via_socket/core.rb +0 -184
- data/lib/dynflow/future.rb +0 -173
- data/lib/dynflow/listeners.rb +0 -7
- data/lib/dynflow/listeners/abstract.rb +0 -17
- data/lib/dynflow/listeners/serialization.rb +0 -77
- data/lib/dynflow/listeners/socket.rb +0 -117
- data/lib/dynflow/micro_actor.rb +0 -102
- data/lib/dynflow/simple_world.rb +0 -19
- data/test/remote_via_socket_test.rb +0 -170
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +0 -1109
- data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +0 -9
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
- data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module CoordinatorAdapters
|
|
3
|
+
class Abstract
|
|
4
|
+
include Algebrick::TypeCheck
|
|
5
|
+
|
|
6
|
+
def initialize(world)
|
|
7
|
+
Type! world, World
|
|
8
|
+
@world = world
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def create_record(record)
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def update_record(record)
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def delete_record(record)
|
|
20
|
+
raise NotImplementedError
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def find_records(record)
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module CoordinatorAdapters
|
|
3
|
+
class Sequel < Abstract
|
|
4
|
+
def initialize(world)
|
|
5
|
+
super
|
|
6
|
+
@sequel_adapter = world.persistence.adapter
|
|
7
|
+
Type! @sequel_adapter, PersistenceAdapters::Sequel
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create_record(record)
|
|
11
|
+
@sequel_adapter.insert_coordinator_record(record.to_hash)
|
|
12
|
+
rescue ::Sequel::UniqueConstraintViolation
|
|
13
|
+
raise Coordinator::DuplicateRecordError.new(record)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def update_record(record)
|
|
17
|
+
@sequel_adapter.update_coordinator_record(record.class.name, record.id, record.to_hash)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def delete_record(record)
|
|
21
|
+
@sequel_adapter.delete_coordinator_record(record.class.name, record.id)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def find_records(filter_options)
|
|
25
|
+
@sequel_adapter.find_coordinator_records(filters: filter_options)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Dispatcher
|
|
3
|
+
Request = Algebrick.type do
|
|
4
|
+
Event = type do
|
|
5
|
+
fields! execution_plan_id: String,
|
|
6
|
+
step_id: Fixnum,
|
|
7
|
+
event: Object
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
Execution = type do
|
|
11
|
+
fields! execution_plan_id: String
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Ping = type do
|
|
15
|
+
fields! receiver_id: String
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
variants Event, Execution, Ping
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Response = Algebrick.type do
|
|
22
|
+
variants Accepted = atom,
|
|
23
|
+
Failed = type { fields! error: String },
|
|
24
|
+
Done = atom,
|
|
25
|
+
Pong = atom
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
Envelope = Algebrick.type do
|
|
29
|
+
fields! request_id: Integer,
|
|
30
|
+
sender_id: String,
|
|
31
|
+
receiver_id: type { variants String, AnyExecutor = atom, UnknownWorld = atom },
|
|
32
|
+
message: type { variants Request, Response }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module Envelope
|
|
36
|
+
def build_response_envelope(response_message, sender)
|
|
37
|
+
Envelope[self.request_id,
|
|
38
|
+
sender.id,
|
|
39
|
+
self.sender_id,
|
|
40
|
+
response_message]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
module Event
|
|
45
|
+
def to_hash
|
|
46
|
+
super.update event: Base64.strict_encode64(Marshal.dump(event))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.product_from_hash(hash)
|
|
50
|
+
super(hash.merge 'event' => Marshal.load(Base64.strict_decode64(hash.fetch('event'))))
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
require 'dynflow/dispatcher/abstract'
|
|
57
|
+
require 'dynflow/dispatcher/client_dispatcher'
|
|
58
|
+
require 'dynflow/dispatcher/executor_dispatcher'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Dispatcher
|
|
3
|
+
class Abstract < Actor
|
|
4
|
+
def connector
|
|
5
|
+
@world.connector
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def respond(request_envelope, response)
|
|
9
|
+
response_envelope = request_envelope.build_response_envelope(response, @world)
|
|
10
|
+
connector.send(response_envelope)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Dispatcher
|
|
3
|
+
class ClientDispatcher < Abstract
|
|
4
|
+
|
|
5
|
+
TrackedRequest = Algebrick.type do
|
|
6
|
+
fields! id: Integer, request: Request,
|
|
7
|
+
accepted: Concurrent::Edge::Future, finished: Concurrent::Edge::Future
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module TrackedRequest
|
|
11
|
+
def accept!
|
|
12
|
+
accepted.success true unless accepted.completed?
|
|
13
|
+
self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def fail!(error)
|
|
17
|
+
accepted.fail error unless accepted.completed?
|
|
18
|
+
finished.fail error
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def success!(resolve_to)
|
|
23
|
+
accepted.success true unless accepted.completed?
|
|
24
|
+
finished.success(resolve_to)
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize(world)
|
|
30
|
+
@world = Type! world, World
|
|
31
|
+
@last_id = 0
|
|
32
|
+
@tracked_requests = {}
|
|
33
|
+
@terminated = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def publish_request(future, request, timeout)
|
|
37
|
+
track_request(future, request, timeout) do |tracked_request|
|
|
38
|
+
dispatch_request(request, @world.id, tracked_request.id)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def timeout(request_id)
|
|
43
|
+
resolve_tracked_request(request_id, Dynflow::Error.new("Request timeout"))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def start_termination(*args)
|
|
47
|
+
super
|
|
48
|
+
@tracked_requests.values.each { |tracked_request| tracked_request.fail!(Dynflow::Error.new('Dispatcher terminated')) }
|
|
49
|
+
@tracked_requests.clear
|
|
50
|
+
finish_termination
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def dispatch_request(request, client_world_id, request_id)
|
|
54
|
+
executor_id = match request,
|
|
55
|
+
(on ~Execution do |execution|
|
|
56
|
+
AnyExecutor
|
|
57
|
+
end),
|
|
58
|
+
(on ~Event do |event|
|
|
59
|
+
find_executor(event.execution_plan_id)
|
|
60
|
+
end),
|
|
61
|
+
(on Ping.(~any) do |receiver_id|
|
|
62
|
+
receiver_id
|
|
63
|
+
end)
|
|
64
|
+
request = Envelope[request_id, client_world_id, executor_id, request]
|
|
65
|
+
if Dispatcher::UnknownWorld === request.receiver_id
|
|
66
|
+
raise Dynflow::Error, "Could not find an executor for #{request}"
|
|
67
|
+
end
|
|
68
|
+
connector.send(request).value!
|
|
69
|
+
rescue => e
|
|
70
|
+
respond(request, Failed[e.message])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def dispatch_response(envelope)
|
|
74
|
+
return unless @tracked_requests.key?(envelope.request_id)
|
|
75
|
+
match envelope.message,
|
|
76
|
+
(on ~Accepted do
|
|
77
|
+
@tracked_requests[envelope.request_id].accept!
|
|
78
|
+
end),
|
|
79
|
+
(on ~Failed do |msg|
|
|
80
|
+
resolve_tracked_request(envelope.request_id, Dynflow::Error.new(msg.error))
|
|
81
|
+
end),
|
|
82
|
+
(on Done | Pong do
|
|
83
|
+
resolve_tracked_request(envelope.request_id)
|
|
84
|
+
end)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def find_executor(execution_plan_id)
|
|
90
|
+
execution_lock = @world.coordinator.find_locks(class: Coordinator::ExecutionLock.name,
|
|
91
|
+
id: "execution-plan:#{execution_plan_id}").first
|
|
92
|
+
if execution_lock
|
|
93
|
+
execution_lock.world_id
|
|
94
|
+
else
|
|
95
|
+
Dispatcher::UnknownWorld
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def track_request(finished, request, timeout)
|
|
100
|
+
id = @last_id += 1
|
|
101
|
+
tracked_request = TrackedRequest[id, request, Concurrent.future, finished]
|
|
102
|
+
@tracked_requests[id] = tracked_request
|
|
103
|
+
@world.clock.ping(self, timeout, [:timeout, id]) if timeout
|
|
104
|
+
yield tracked_request
|
|
105
|
+
rescue Dynflow::Error => e
|
|
106
|
+
resolve_tracked_request(tracked_request.id, e)
|
|
107
|
+
log(Logger::ERROR, e)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def reset_tracked_request(tracked_request)
|
|
111
|
+
if tracked_request.finished.completed?
|
|
112
|
+
raise Dynflow::Error.new('Can not reset resolved tracked request')
|
|
113
|
+
end
|
|
114
|
+
unless tracked_request.accepted.completed?
|
|
115
|
+
tracked_request.accept! # otherwise nobody would set the accept future
|
|
116
|
+
end
|
|
117
|
+
@tracked_requests[tracked_request.id] = TrackedRequest[tracked_request.id, tracked_request.request, Concurrent.future, tracked_request.finished]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def resolve_tracked_request(id, error = nil)
|
|
121
|
+
return unless @tracked_requests.key?(id)
|
|
122
|
+
if error
|
|
123
|
+
@tracked_requests.delete(id).fail! error
|
|
124
|
+
else
|
|
125
|
+
tracked_request = @tracked_requests[id]
|
|
126
|
+
resolve_to = match tracked_request.request,
|
|
127
|
+
(on Execution.(execution_plan_id: ~any) do |uuid|
|
|
128
|
+
@world.persistence.load_execution_plan(uuid)
|
|
129
|
+
end),
|
|
130
|
+
(on Event | Ping do
|
|
131
|
+
true
|
|
132
|
+
end)
|
|
133
|
+
@tracked_requests.delete(id).success! resolve_to
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
module Dispatcher
|
|
3
|
+
class ExecutorDispatcher < Abstract
|
|
4
|
+
def initialize(world)
|
|
5
|
+
@world = Type! world, World
|
|
6
|
+
@current_futures = Set.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def handle_request(envelope)
|
|
10
|
+
match(envelope.message,
|
|
11
|
+
on(Execution) { perform_execution(envelope, envelope.message) },
|
|
12
|
+
on(Event) { perform_event(envelope, envelope.message) })
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
def perform_execution(envelope, execution)
|
|
18
|
+
allocate_executor(execution.execution_plan_id, envelope.sender_id, envelope.request_id)
|
|
19
|
+
execution_lock = Coordinator::ExecutionLock.new(@world, execution.execution_plan_id, envelope.sender_id, envelope.request_id)
|
|
20
|
+
future = on_finish do |f|
|
|
21
|
+
f.then do |plan|
|
|
22
|
+
if plan.state == :running
|
|
23
|
+
@world.invalidate_execution_lock(execution_lock)
|
|
24
|
+
else
|
|
25
|
+
@world.coordinator.release(execution_lock)
|
|
26
|
+
respond(envelope, Done)
|
|
27
|
+
end
|
|
28
|
+
end.rescue do |reason|
|
|
29
|
+
@world.coordinator.release(execution_lock)
|
|
30
|
+
respond(envelope, Failed[reason.to_s])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
@world.executor.execute(execution.execution_plan_id, future)
|
|
34
|
+
respond(envelope, Accepted)
|
|
35
|
+
rescue Dynflow::Error => e
|
|
36
|
+
future.fail(e) if future && !future.completed?
|
|
37
|
+
respond(envelope, Failed[e.message])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def perform_event(envelope, event_request)
|
|
41
|
+
future = on_finish do |f|
|
|
42
|
+
f.then do
|
|
43
|
+
respond(envelope, Done)
|
|
44
|
+
end.rescue do |reason|
|
|
45
|
+
respond(envelope, Failed[reason.to_s])
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
@world.executor.event(event_request.execution_plan_id, event_request.step_id, event_request.event, future)
|
|
49
|
+
rescue Dynflow::Error => e
|
|
50
|
+
future.fail(e) if future && !future.completed?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def start_termination(*args)
|
|
54
|
+
super
|
|
55
|
+
if @current_futures.empty?
|
|
56
|
+
reference.tell(:finish_termination)
|
|
57
|
+
else
|
|
58
|
+
Concurrent.zip(*@current_futures).then { reference.tell(:finish_termination) }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def allocate_executor(execution_plan_id, client_world_id, request_id)
|
|
65
|
+
execution_lock = Coordinator::ExecutionLock.new(@world, execution_plan_id, client_world_id, request_id)
|
|
66
|
+
@world.coordinator.acquire(execution_lock)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def on_finish
|
|
70
|
+
raise "Dispatcher terminating: no new work can be started" if terminating?
|
|
71
|
+
future = Concurrent.future
|
|
72
|
+
callbacks_future = (yield future).rescue { |reason| @world.logger.error("Unexpected fail on future #{reason}") }
|
|
73
|
+
# we track currently running futures to make sure to not
|
|
74
|
+
# terminate until the execution is finished (including
|
|
75
|
+
# cleaning of locks etc)
|
|
76
|
+
@current_futures << callbacks_future
|
|
77
|
+
callbacks_future.on_completion! { reference.tell([:finish_execution, callbacks_future]) }
|
|
78
|
+
return future
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def finish_execution(future)
|
|
82
|
+
@current_futures.delete(future)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/dynflow/errors.rb
CHANGED
|
@@ -25,7 +25,13 @@ module Dynflow
|
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
class
|
|
28
|
+
class InactiveWorldError < Dynflow::Error
|
|
29
|
+
def initialize(world)
|
|
30
|
+
super("The world #{world.id} is not active (terminating or terminated)")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class PersistenceError < Dynflow::Error
|
|
29
35
|
def self.delegate(original_exception)
|
|
30
36
|
self.new("caused by #{original_exception.class}: #{original_exception.message}").tap do |e|
|
|
31
37
|
e.set_backtrace original_exception.backtrace
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
class ExecutionHistory
|
|
3
|
+
include Algebrick::TypeCheck
|
|
4
|
+
include Enumerable
|
|
5
|
+
|
|
6
|
+
Event = Algebrick.type do
|
|
7
|
+
fields! time: Integer,
|
|
8
|
+
name: String,
|
|
9
|
+
world_id: type { variants String, NilClass }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module Event
|
|
13
|
+
def inspect
|
|
14
|
+
"#{Time.at(time).utc}: #{name}".tap { |s| s << " @ #{world_id}" if world_id }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :events
|
|
19
|
+
|
|
20
|
+
def initialize(events = [])
|
|
21
|
+
@events = (events || []).each { |e| Type! e, Event }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def each(&block)
|
|
25
|
+
@events.each(&block)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add(name, world_id = nil)
|
|
29
|
+
@events << Event[Time.now.to_i, name, world_id]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_hash
|
|
33
|
+
@events.map(&:to_hash)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def inspect
|
|
37
|
+
"ExecutionHistory: #{ @events.inspect }"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.new_from_hash(value)
|
|
41
|
+
value ||= [] # for compatibility with tasks before the
|
|
42
|
+
# introduction of execution history
|
|
43
|
+
self.new(value.map { |hash| Event[hash] })
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -13,7 +13,7 @@ module Dynflow
|
|
|
13
13
|
require 'dynflow/execution_plan/dependency_graph'
|
|
14
14
|
|
|
15
15
|
attr_reader :id, :world, :root_plan_step, :steps, :run_flow, :finalize_flow,
|
|
16
|
-
:started_at, :ended_at, :execution_time, :real_time
|
|
16
|
+
:started_at, :ended_at, :execution_time, :real_time, :execution_history
|
|
17
17
|
|
|
18
18
|
def self.states
|
|
19
19
|
@states ||= [:pending, :planning, :planned, :running, :paused, :stopped]
|
|
@@ -39,18 +39,20 @@ module Dynflow
|
|
|
39
39
|
started_at = nil,
|
|
40
40
|
ended_at = nil,
|
|
41
41
|
execution_time = nil,
|
|
42
|
-
real_time = 0.0
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@
|
|
49
|
-
@
|
|
50
|
-
@
|
|
51
|
-
@
|
|
52
|
-
@
|
|
53
|
-
@
|
|
42
|
+
real_time = 0.0,
|
|
43
|
+
execution_history = ExecutionHistory.new)
|
|
44
|
+
|
|
45
|
+
@id = Type! id, String
|
|
46
|
+
@world = Type! world, World
|
|
47
|
+
self.state = state
|
|
48
|
+
@run_flow = Type! run_flow, Flows::Abstract
|
|
49
|
+
@finalize_flow = Type! finalize_flow, Flows::Abstract
|
|
50
|
+
@root_plan_step = root_plan_step
|
|
51
|
+
@started_at = Type! started_at, Time, NilClass
|
|
52
|
+
@ended_at = Type! ended_at, Time, NilClass
|
|
53
|
+
@execution_time = Type! execution_time, Numeric, NilClass
|
|
54
|
+
@real_time = Type! real_time, Numeric
|
|
55
|
+
@execution_history = Type! execution_history, ExecutionHistory
|
|
54
56
|
|
|
55
57
|
steps.all? do |k, v|
|
|
56
58
|
Type! k, Integer
|
|
@@ -274,7 +276,8 @@ module Dynflow
|
|
|
274
276
|
started_at: time_to_str(started_at),
|
|
275
277
|
ended_at: time_to_str(ended_at),
|
|
276
278
|
execution_time: execution_time,
|
|
277
|
-
real_time: real_time
|
|
279
|
+
real_time: real_time,
|
|
280
|
+
execution_history: execution_history.to_hash
|
|
278
281
|
end
|
|
279
282
|
|
|
280
283
|
def save
|
|
@@ -295,7 +298,8 @@ module Dynflow
|
|
|
295
298
|
string_to_time(hash[:started_at]),
|
|
296
299
|
string_to_time(hash[:ended_at]),
|
|
297
300
|
hash[:execution_time].to_f,
|
|
298
|
-
hash[:real_time].to_f
|
|
301
|
+
hash[:real_time].to_f,
|
|
302
|
+
ExecutionHistory.new_from_hash(hash[:execution_history]))
|
|
299
303
|
end
|
|
300
304
|
|
|
301
305
|
def compute_execution_time
|