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,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
|