dynflow 0.7.9 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,41 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Connectors
|
3
|
+
class Abstract
|
4
|
+
include Algebrick::TypeCheck
|
5
|
+
include Algebrick::Matching
|
6
|
+
|
7
|
+
def start_listening(world)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def stop_listening(world)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def terminate
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
|
19
|
+
def send(envelope)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
# we need to pass the world, as the connector can be shared
|
24
|
+
# between words: we need to know the one to send the message to
|
25
|
+
def receive(world, envelope)
|
26
|
+
Type! envelope, Dispatcher::Envelope
|
27
|
+
match(envelope.message,
|
28
|
+
(on Dispatcher::Ping do
|
29
|
+
response_envelope = envelope.build_response_envelope(Dispatcher::Pong, world)
|
30
|
+
send(response_envelope)
|
31
|
+
end),
|
32
|
+
(on Dispatcher::Request do
|
33
|
+
world.executor_dispatcher.tell([:handle_request, envelope])
|
34
|
+
end),
|
35
|
+
(on Dispatcher::Response do
|
36
|
+
world.client_dispatcher.tell([:dispatch_response, envelope])
|
37
|
+
end))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Connectors
|
3
|
+
class Database < Abstract
|
4
|
+
|
5
|
+
class PostgresListerner
|
6
|
+
def initialize(core, world_id, db)
|
7
|
+
@core = core
|
8
|
+
@db = db
|
9
|
+
@world_id = world_id
|
10
|
+
@started = Concurrent::AtomicReference.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.notify_supported?(db)
|
14
|
+
db.class.name == "Sequel::Postgres::Database"
|
15
|
+
end
|
16
|
+
|
17
|
+
def started?
|
18
|
+
@started.get
|
19
|
+
end
|
20
|
+
|
21
|
+
def start
|
22
|
+
@started.set true
|
23
|
+
@thread = Thread.new do
|
24
|
+
@db.listen("world:#{ @world_id }", :loop => true) do
|
25
|
+
if started?
|
26
|
+
@core << :check_inbox
|
27
|
+
else
|
28
|
+
break # the listener is stopped: don't continue listening
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def notify(world_id)
|
35
|
+
@db.notify("world:#{world_id}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
@started.set false
|
40
|
+
notify(@world_id)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Core < Actor
|
45
|
+
attr_reader :polling_interval
|
46
|
+
|
47
|
+
def initialize(connector, polling_interval)
|
48
|
+
@connector = connector
|
49
|
+
@world = nil
|
50
|
+
@executor_round_robin = RoundRobin.new
|
51
|
+
@stopped = false
|
52
|
+
@polling_interval = polling_interval
|
53
|
+
end
|
54
|
+
|
55
|
+
def stopped?
|
56
|
+
!!@stopped
|
57
|
+
end
|
58
|
+
|
59
|
+
def start_listening(world)
|
60
|
+
@world = world
|
61
|
+
@stopped = false
|
62
|
+
postgres_listen_start
|
63
|
+
self << :periodic_check_inbox
|
64
|
+
end
|
65
|
+
|
66
|
+
def stop_receiving_new_work
|
67
|
+
@world.coordinator.deactivate_world(@world.registered_world)
|
68
|
+
end
|
69
|
+
|
70
|
+
def stop_listening
|
71
|
+
@stopped = true
|
72
|
+
postgres_listen_stop
|
73
|
+
end
|
74
|
+
|
75
|
+
def periodic_check_inbox
|
76
|
+
self << :check_inbox
|
77
|
+
@world.clock.ping(self, polling_interval, :periodic_check_inbox) unless @stopped
|
78
|
+
end
|
79
|
+
|
80
|
+
def check_inbox
|
81
|
+
return unless @world
|
82
|
+
receive_envelopes
|
83
|
+
end
|
84
|
+
|
85
|
+
def handle_envelope(envelope)
|
86
|
+
world_id = find_receiver(envelope)
|
87
|
+
if world_id == @world.id
|
88
|
+
if @stopped
|
89
|
+
log(Logger::ERROR, "Envelope #{envelope} received for stopped world")
|
90
|
+
else
|
91
|
+
@connector.receive(@world, envelope)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
send_envelope(update_receiver_id(envelope, world_id))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def postgres_listen_start
|
101
|
+
if PostgresListerner.notify_supported?(@world.persistence.adapter.db)
|
102
|
+
@postgres_listener ||= PostgresListerner.new(self, @world.id, @world.persistence.adapter.db)
|
103
|
+
@postgres_listener.start unless @postgres_listener.started?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def postgres_listen_stop
|
108
|
+
@postgres_listener.stop if @postgres_listener
|
109
|
+
end
|
110
|
+
|
111
|
+
def receive_envelopes
|
112
|
+
@world.persistence.pull_envelopes(@world.id).each do |envelope|
|
113
|
+
self.tell([:handle_envelope, envelope])
|
114
|
+
end
|
115
|
+
rescue => e
|
116
|
+
log(Logger::ERROR, "Receiving envelopes failed on #{e}")
|
117
|
+
end
|
118
|
+
|
119
|
+
def send_envelope(envelope)
|
120
|
+
@world.persistence.push_envelope(envelope)
|
121
|
+
if @postgres_listener
|
122
|
+
@postgres_listener.notify(envelope.receiver_id)
|
123
|
+
end
|
124
|
+
rescue => e
|
125
|
+
log(Logger::ERROR, "Sending envelope failed on #{e}")
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_receiver_id(envelope, new_receiver_id)
|
129
|
+
Dispatcher::Envelope[envelope.request_id, envelope.sender_id, new_receiver_id, envelope.message]
|
130
|
+
end
|
131
|
+
|
132
|
+
def find_receiver(envelope)
|
133
|
+
if Dispatcher::AnyExecutor === envelope.receiver_id
|
134
|
+
any_executor.id
|
135
|
+
else
|
136
|
+
envelope.receiver_id
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def any_executor
|
141
|
+
@executor_round_robin.data = @world.coordinator.find_worlds(true)
|
142
|
+
@executor_round_robin.next or raise Dynflow::Error, "No executor available"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def initialize(world = nil, polling_interval = nil)
|
147
|
+
polling_interval ||= begin
|
148
|
+
if world && PostgresListerner.notify_supported?(world.persistence.adapter.db)
|
149
|
+
30 # when the notify is supported, we don't need that much polling
|
150
|
+
else
|
151
|
+
1
|
152
|
+
end
|
153
|
+
end
|
154
|
+
@core = Core.spawn('connector-database-core', self, polling_interval)
|
155
|
+
start_listening(world) if world
|
156
|
+
end
|
157
|
+
|
158
|
+
def start_listening(world)
|
159
|
+
@core.ask([:start_listening, world])
|
160
|
+
end
|
161
|
+
|
162
|
+
def stop_receiving_new_work(_)
|
163
|
+
@core.ask(:stop_receiving_new_work).wait
|
164
|
+
end
|
165
|
+
|
166
|
+
def stop_listening(_)
|
167
|
+
@core.ask(:stop_listening).then { @core.ask(:terminate!) }.wait
|
168
|
+
end
|
169
|
+
|
170
|
+
def send(envelope)
|
171
|
+
@core.ask([:handle_envelope, envelope])
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Dynflow
|
2
|
+
module Connectors
|
3
|
+
class Direct < Abstract
|
4
|
+
|
5
|
+
class Core < Actor
|
6
|
+
|
7
|
+
def initialize(connector)
|
8
|
+
@connector = connector
|
9
|
+
@worlds = {}
|
10
|
+
@executor_round_robin = RoundRobin.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def start_listening(world)
|
14
|
+
@worlds[world.id] = world
|
15
|
+
@executor_round_robin.add(world) if world.executor
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop_receiving_new_work(world)
|
19
|
+
@executor_round_robin.delete(world)
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop_listening(world)
|
23
|
+
@worlds.delete(world.id)
|
24
|
+
@executor_round_robin.delete(world) if world.executor
|
25
|
+
reference.tell(:terminate!) if @worlds.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def handle_envelope(envelope)
|
29
|
+
if world = find_receiver(envelope)
|
30
|
+
@connector.receive(world, envelope)
|
31
|
+
else
|
32
|
+
log(Logger::ERROR, "Receiver for envelope #{ envelope } not found")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def find_receiver(envelope)
|
39
|
+
receiver = if Dispatcher::AnyExecutor === envelope.receiver_id
|
40
|
+
@executor_round_robin.next
|
41
|
+
else
|
42
|
+
@worlds[envelope.receiver_id]
|
43
|
+
end
|
44
|
+
raise Dynflow::Error, "No executor available" unless receiver
|
45
|
+
return receiver
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(world = nil)
|
50
|
+
@core = Core.spawn('connector-direct-core', self)
|
51
|
+
start_listening(world) if world
|
52
|
+
end
|
53
|
+
|
54
|
+
def start_listening(world)
|
55
|
+
@core.ask([:start_listening, world])
|
56
|
+
end
|
57
|
+
|
58
|
+
def stop_receiving_new_work(world)
|
59
|
+
@core.ask([:stop_receiving_new_work, world]).wait
|
60
|
+
end
|
61
|
+
|
62
|
+
def stop_listening(world)
|
63
|
+
@core.ask([:stop_listening, world]).wait
|
64
|
+
end
|
65
|
+
|
66
|
+
def send(envelope)
|
67
|
+
@core.ask([:handle_envelope, envelope])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
require 'dynflow/coordinator_adapters'
|
2
|
+
|
3
|
+
module Dynflow
|
4
|
+
class Coordinator
|
5
|
+
|
6
|
+
include Algebrick::TypeCheck
|
7
|
+
|
8
|
+
class DuplicateRecordError < Dynflow::Error
|
9
|
+
attr_reader :record
|
10
|
+
|
11
|
+
def initialize(record)
|
12
|
+
@record = record
|
13
|
+
super("record #{record} already exists")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class LockError < Dynflow::Error
|
18
|
+
attr_reader :lock
|
19
|
+
|
20
|
+
def initialize(lock)
|
21
|
+
@lock = lock
|
22
|
+
super("Unable to acquire lock #{lock}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Record < Serializable
|
27
|
+
attr_reader :data
|
28
|
+
|
29
|
+
include Algebrick::TypeCheck
|
30
|
+
|
31
|
+
def self.new_from_hash(hash)
|
32
|
+
self.allocate.tap { |record| record.from_hash(hash) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.constantize(name)
|
36
|
+
Serializable.constantize(name)
|
37
|
+
rescue NameError
|
38
|
+
# If we don't find the lock name, return the most generic version
|
39
|
+
Record
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(*args)
|
43
|
+
@data ||= {}
|
44
|
+
@data = @data.merge(class: self.class.name).with_indifferent_access
|
45
|
+
end
|
46
|
+
|
47
|
+
def from_hash(hash)
|
48
|
+
@data = hash
|
49
|
+
@from_hash = true
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_hash
|
53
|
+
@data
|
54
|
+
end
|
55
|
+
|
56
|
+
def id
|
57
|
+
@data[:id]
|
58
|
+
end
|
59
|
+
|
60
|
+
# @api override
|
61
|
+
# check to be performed before we try to acquire the lock
|
62
|
+
def validate!
|
63
|
+
Type! id, String
|
64
|
+
Type! @data, Hash
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
"#{self.class.name}: #{id}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def ==(other_object)
|
72
|
+
self.class == other_object.class && self.id == other_object.id
|
73
|
+
end
|
74
|
+
|
75
|
+
def hash
|
76
|
+
[self.class, self.id].hash
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class WorldRecord < Record
|
81
|
+
def initialize(world)
|
82
|
+
super
|
83
|
+
@data[:id] = world.id
|
84
|
+
@data[:meta] = world.meta
|
85
|
+
end
|
86
|
+
|
87
|
+
def meta
|
88
|
+
@data[:meta]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class ExecutorWorld < WorldRecord
|
93
|
+
def initialize(world)
|
94
|
+
super
|
95
|
+
self.active = !world.terminating?
|
96
|
+
end
|
97
|
+
|
98
|
+
def active?
|
99
|
+
@data[:active]
|
100
|
+
end
|
101
|
+
|
102
|
+
def active=(value)
|
103
|
+
Type! value, Algebrick::Types::Boolean
|
104
|
+
@data[:active] = value
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class ClientWorld < WorldRecord
|
109
|
+
end
|
110
|
+
|
111
|
+
class Lock < Record
|
112
|
+
def self.constantize(name)
|
113
|
+
Serializable.constantize(name)
|
114
|
+
rescue NameError
|
115
|
+
# If we don't find the lock name, return the most generic version
|
116
|
+
Lock
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_s
|
120
|
+
"#{self.class.name}: #{id} by #{owner_id}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def owner_id
|
124
|
+
@data[:owner_id]
|
125
|
+
end
|
126
|
+
|
127
|
+
# @api override
|
128
|
+
# check to be performed before we try to acquire the lock
|
129
|
+
def validate!
|
130
|
+
super
|
131
|
+
raise "Can't acquire the lock after deserialization" if @from_hash
|
132
|
+
Type! owner_id, String
|
133
|
+
end
|
134
|
+
|
135
|
+
def to_s
|
136
|
+
"#{self.class.name}: #{id} by #{owner_id}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class LockByWorld < Lock
|
141
|
+
def initialize(world)
|
142
|
+
super
|
143
|
+
@world = world
|
144
|
+
@data.merge!(owner_id: "world:#{world.id}", world_id: world.id)
|
145
|
+
end
|
146
|
+
|
147
|
+
def validate!
|
148
|
+
super
|
149
|
+
raise Errors::InactiveWorldError.new(@world) if @world.terminating?
|
150
|
+
end
|
151
|
+
|
152
|
+
def world_id
|
153
|
+
@data[:world_id]
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
class WorldInvalidationLock < LockByWorld
|
159
|
+
def initialize(world, invalidated_world)
|
160
|
+
super(world)
|
161
|
+
@data[:id] = "world-invalidation:#{invalidated_world.id}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class AutoExecuteLock < LockByWorld
|
166
|
+
def initialize(*args)
|
167
|
+
super
|
168
|
+
@data[:id] = "auto-execute"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class ExecutionLock < LockByWorld
|
173
|
+
def initialize(world, execution_plan_id, client_world_id, request_id)
|
174
|
+
super(world)
|
175
|
+
@data.merge!(id: "execution-plan:#{execution_plan_id}",
|
176
|
+
execution_plan_id: execution_plan_id,
|
177
|
+
client_world_id: client_world_id,
|
178
|
+
request_id: request_id)
|
179
|
+
end
|
180
|
+
|
181
|
+
# we need to store the following data in case of
|
182
|
+
# invalidation of the lock from outside (after
|
183
|
+
# the owner world terminated unexpectedly)
|
184
|
+
def execution_plan_id
|
185
|
+
@data[:execution_plan_id]
|
186
|
+
end
|
187
|
+
|
188
|
+
def client_world_id
|
189
|
+
@data[:client_world_id]
|
190
|
+
end
|
191
|
+
|
192
|
+
def request_id
|
193
|
+
@data[:request_id]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
attr_reader :adapter
|
198
|
+
|
199
|
+
def initialize(coordinator_adapter)
|
200
|
+
@adapter = coordinator_adapter
|
201
|
+
end
|
202
|
+
|
203
|
+
def acquire(lock, &block)
|
204
|
+
Type! lock, Lock
|
205
|
+
lock.validate!
|
206
|
+
adapter.create_record(lock)
|
207
|
+
if block
|
208
|
+
begin
|
209
|
+
block.call
|
210
|
+
ensure
|
211
|
+
release(lock)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
rescue DuplicateRecordError => e
|
215
|
+
raise LockError.new(e.record)
|
216
|
+
end
|
217
|
+
|
218
|
+
def release(lock)
|
219
|
+
Type! lock, Lock
|
220
|
+
adapter.delete_record(lock)
|
221
|
+
end
|
222
|
+
|
223
|
+
def release_by_owner(owner_id)
|
224
|
+
find_locks(owner_id: owner_id).map { |lock| release(lock) }
|
225
|
+
end
|
226
|
+
|
227
|
+
def find_locks(filter_options)
|
228
|
+
adapter.find_records(filter_options).map do |lock_data|
|
229
|
+
Lock.from_hash(lock_data)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def create_record(record)
|
234
|
+
Type! record, Record
|
235
|
+
adapter.create_record(record)
|
236
|
+
end
|
237
|
+
|
238
|
+
def update_record(record)
|
239
|
+
Type! record, Record
|
240
|
+
adapter.update_record(record)
|
241
|
+
end
|
242
|
+
|
243
|
+
def delete_record(record)
|
244
|
+
Type! record, Record
|
245
|
+
adapter.delete_record(record)
|
246
|
+
end
|
247
|
+
|
248
|
+
def find_records(filter)
|
249
|
+
adapter.find_records(filter).map do |record_data|
|
250
|
+
Record.from_hash(record_data)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def find_worlds(active_executor_only = false, filters = {})
|
255
|
+
ret = find_records(filters.merge(class: Coordinator::ExecutorWorld.name))
|
256
|
+
if active_executor_only
|
257
|
+
ret = ret.select(&:active?)
|
258
|
+
else
|
259
|
+
ret.concat(find_records(filters.merge(class: Coordinator::ClientWorld.name)))
|
260
|
+
end
|
261
|
+
ret
|
262
|
+
end
|
263
|
+
|
264
|
+
def register_world(world)
|
265
|
+
Type! world, Coordinator::ClientWorld, Coordinator::ExecutorWorld
|
266
|
+
create_record(world)
|
267
|
+
end
|
268
|
+
|
269
|
+
def delete_world(world)
|
270
|
+
Type! world, Coordinator::ClientWorld, Coordinator::ExecutorWorld
|
271
|
+
delete_record(world)
|
272
|
+
end
|
273
|
+
|
274
|
+
def deactivate_world(world)
|
275
|
+
Type! world, Coordinator::ExecutorWorld
|
276
|
+
world.active = false
|
277
|
+
update_record(world)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|