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
data/dynflow.gemspec
CHANGED
|
@@ -21,7 +21,9 @@ Gem::Specification.new do |s|
|
|
|
21
21
|
s.add_dependency "activesupport"
|
|
22
22
|
s.add_dependency "multi_json"
|
|
23
23
|
s.add_dependency "apipie-params"
|
|
24
|
-
s.add_dependency "algebrick", '~> 0.
|
|
24
|
+
s.add_dependency "algebrick", '~> 0.7.0'
|
|
25
|
+
s.add_dependency "concurrent-ruby", '~> 0.9.0.pre3'
|
|
26
|
+
s.add_dependency "concurrent-ruby-edge"
|
|
25
27
|
|
|
26
28
|
s.add_development_dependency "rack-test"
|
|
27
29
|
s.add_development_dependency "minitest"
|
data/examples/example_helper.rb
CHANGED
|
@@ -8,9 +8,15 @@ class ExampleHelper
|
|
|
8
8
|
@world ||= create_world
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def create_world
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
def create_world
|
|
12
|
+
config = Dynflow::Config.new
|
|
13
|
+
config.persistence_adapter = persistence_adapter
|
|
14
|
+
config.logger_adapter = logger_adapter
|
|
15
|
+
config.auto_rescue = false
|
|
16
|
+
yield config if block_given?
|
|
17
|
+
Dynflow::World.new(config).tap do |world|
|
|
18
|
+
puts "World #{world.id} started..."
|
|
19
|
+
end
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
def persistence_conn_string
|
|
@@ -21,22 +27,17 @@ class ExampleHelper
|
|
|
21
27
|
Dynflow::PersistenceAdapters::Sequel.new persistence_conn_string
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
def default_world_options
|
|
25
|
-
{ logger_adapter: logger_adapter,
|
|
26
|
-
persistence_adapter: persistence_adapter }
|
|
27
|
-
end
|
|
28
|
-
|
|
29
30
|
def logger_adapter
|
|
30
31
|
Dynflow::LoggerAdapters::Simple.new $stderr, 4
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
def run_web_console(world = ExampleHelper.world)
|
|
35
|
-
require 'dynflow/
|
|
36
|
-
dynflow_console = Dynflow::
|
|
36
|
+
require 'dynflow/web'
|
|
37
|
+
dynflow_console = Dynflow::Web.setup do
|
|
37
38
|
set :world, world
|
|
38
39
|
end
|
|
39
|
-
dynflow_console.
|
|
40
|
+
Rack::Server.new(:app => dynflow_console, :Port => 4567).start
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
# for simulation of the execution failing for the first time
|
|
@@ -52,5 +53,11 @@ class ExampleHelper
|
|
|
52
53
|
def nothing_should_fail!
|
|
53
54
|
@should_fail = false
|
|
54
55
|
end
|
|
56
|
+
|
|
57
|
+
def terminate
|
|
58
|
+
@world.terminate.wait if @world
|
|
59
|
+
end
|
|
55
60
|
end
|
|
56
61
|
end
|
|
62
|
+
|
|
63
|
+
at_exit { ExampleHelper.terminate }
|
|
@@ -153,7 +153,8 @@ module OrchestrateEvented
|
|
|
153
153
|
puts <<-MSG.gsub(/^.*\|/, '')
|
|
154
154
|
|
|
155
155
|
| Execution plan #{execution_plan_id} got stuck
|
|
156
|
-
| You can cancel the stucked step at
|
|
156
|
+
| You can cancel the stucked step at
|
|
157
|
+
| http://localhost:4567/#{execution_plan_id}
|
|
157
158
|
|
|
158
159
|
MSG
|
|
159
160
|
# we suspend the action but don't plan the wakeup event,
|
data/examples/remote_executor.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
require_relative 'example_helper'
|
|
5
|
+
require_relative 'orchestrate_evented'
|
|
5
6
|
require 'tmpdir'
|
|
6
7
|
|
|
7
8
|
class SampleAction < Dynflow::Action
|
|
@@ -19,37 +20,64 @@ end
|
|
|
19
20
|
class RemoteExecutorExample
|
|
20
21
|
class << self
|
|
21
22
|
|
|
22
|
-
def
|
|
23
|
-
world = ExampleHelper.create_world
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
File.delete(db_path)
|
|
23
|
+
def run_observer
|
|
24
|
+
world = ExampleHelper.create_world do |config|
|
|
25
|
+
config.persistence_adapter = persistence_adapter
|
|
26
|
+
config.connector = connector
|
|
27
|
+
config.executor = false
|
|
28
|
+
end
|
|
29
|
+
run(world)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
def run_server
|
|
33
|
+
world = ExampleHelper.create_world do |config|
|
|
34
|
+
config.persistence_adapter = persistence_adapter
|
|
35
|
+
config.connector = connector
|
|
36
|
+
end
|
|
37
|
+
run(world)
|
|
38
|
+
end
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
def run(world)
|
|
41
|
+
begin
|
|
42
|
+
ExampleHelper.run_web_console(world)
|
|
43
|
+
rescue Errno::EADDRINUSE
|
|
44
|
+
require 'io/console'
|
|
45
|
+
puts "Running without a web console. Press q<enter> to quit."
|
|
46
|
+
until STDIN.gets.chomp == 'q'
|
|
47
|
+
end
|
|
40
48
|
end
|
|
41
49
|
end
|
|
42
50
|
|
|
43
|
-
def
|
|
44
|
-
File.
|
|
51
|
+
def db_path
|
|
52
|
+
File.expand_path("../remote_executor_db.sqlite", __FILE__)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def persistence_conn_string
|
|
56
|
+
ENV['DB_CONN_STRING'] || "sqlite://#{db_path}"
|
|
45
57
|
end
|
|
46
58
|
|
|
47
59
|
def persistence_adapter
|
|
48
|
-
Dynflow::PersistenceAdapters::Sequel.new
|
|
60
|
+
Dynflow::PersistenceAdapters::Sequel.new persistence_conn_string
|
|
49
61
|
end
|
|
50
62
|
|
|
51
|
-
def
|
|
52
|
-
|
|
63
|
+
def connector
|
|
64
|
+
Proc.new { |world| Dynflow::Connectors::Database.new(world) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def run_client
|
|
68
|
+
world = ExampleHelper.create_world do |config|
|
|
69
|
+
config.persistence_adapter = persistence_adapter
|
|
70
|
+
config.executor = false
|
|
71
|
+
config.connector = connector
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
world.trigger(OrchestrateEvented::CreateInfrastructure)
|
|
75
|
+
world.trigger(OrchestrateEvented::CreateInfrastructure, true)
|
|
76
|
+
|
|
77
|
+
loop do
|
|
78
|
+
world.trigger(SampleAction).finished.wait
|
|
79
|
+
sleep 0.5
|
|
80
|
+
end
|
|
53
81
|
end
|
|
54
82
|
|
|
55
83
|
end
|
|
@@ -59,6 +87,11 @@ command = ARGV.first || 'server'
|
|
|
59
87
|
|
|
60
88
|
if $0 == __FILE__
|
|
61
89
|
case command
|
|
90
|
+
when 'observer'
|
|
91
|
+
puts <<MSG
|
|
92
|
+
The observer starting…. You can see what's going on there
|
|
93
|
+
MSG
|
|
94
|
+
RemoteExecutorExample.run_observer
|
|
62
95
|
when 'server'
|
|
63
96
|
puts <<MSG
|
|
64
97
|
The server is starting…. You can send the work to it by running:
|
data/lib/dynflow.rb
CHANGED
|
@@ -4,6 +4,14 @@ require 'thread'
|
|
|
4
4
|
require 'set'
|
|
5
5
|
require 'active_support/core_ext/hash/indifferent_access'
|
|
6
6
|
require 'base64'
|
|
7
|
+
require 'concurrent'
|
|
8
|
+
require 'concurrent-edge'
|
|
9
|
+
|
|
10
|
+
logger = Logger.new($stderr)
|
|
11
|
+
logger.level = Logger::INFO
|
|
12
|
+
Concurrent.configuration.logger = lambda do |level, progname, message = nil, &block|
|
|
13
|
+
logger.add level, message, progname, &block
|
|
14
|
+
end
|
|
7
15
|
|
|
8
16
|
# TODO validate in/output, also validate unknown keys
|
|
9
17
|
# TODO performance testing, how many actions will it handle?
|
|
@@ -14,23 +22,25 @@ module Dynflow
|
|
|
14
22
|
class Error < StandardError
|
|
15
23
|
end
|
|
16
24
|
|
|
25
|
+
require 'dynflow/round_robin'
|
|
26
|
+
require 'dynflow/actor'
|
|
17
27
|
require 'dynflow/errors'
|
|
18
|
-
require 'dynflow/
|
|
19
|
-
require 'dynflow/micro_actor'
|
|
28
|
+
require 'dynflow/serializer'
|
|
20
29
|
require 'dynflow/serializable'
|
|
21
30
|
require 'dynflow/clock'
|
|
22
31
|
require 'dynflow/stateful'
|
|
23
32
|
require 'dynflow/transaction_adapters'
|
|
33
|
+
require 'dynflow/coordinator'
|
|
24
34
|
require 'dynflow/persistence'
|
|
25
35
|
require 'dynflow/middleware'
|
|
26
36
|
require 'dynflow/flows'
|
|
37
|
+
require 'dynflow/execution_history'
|
|
27
38
|
require 'dynflow/execution_plan'
|
|
28
39
|
require 'dynflow/action'
|
|
29
|
-
require 'dynflow/listeners'
|
|
30
40
|
require 'dynflow/executors'
|
|
31
41
|
require 'dynflow/logger_adapters'
|
|
32
42
|
require 'dynflow/world'
|
|
33
|
-
require 'dynflow/
|
|
34
|
-
require 'dynflow/
|
|
35
|
-
|
|
43
|
+
require 'dynflow/connectors'
|
|
44
|
+
require 'dynflow/dispatcher'
|
|
45
|
+
require 'dynflow/config'
|
|
36
46
|
end
|
|
@@ -51,9 +51,7 @@ module Dynflow
|
|
|
51
51
|
|
|
52
52
|
# Helper for creating sub plans
|
|
53
53
|
def trigger(*args)
|
|
54
|
-
world.trigger
|
|
55
|
-
world.plan_with_caller(self, *args)
|
|
56
|
-
end
|
|
54
|
+
world.trigger { world.plan_with_caller(self, *args) }
|
|
57
55
|
end
|
|
58
56
|
|
|
59
57
|
def wait_for_sub_plans(sub_plans)
|
|
@@ -102,9 +100,8 @@ module Dynflow
|
|
|
102
100
|
def notify_on_finish(plans)
|
|
103
101
|
suspend do |suspended_action|
|
|
104
102
|
plans.each do |plan|
|
|
105
|
-
plan.finished.
|
|
106
|
-
suspended_action << SubPlanFinished[plan.
|
|
107
|
-
value.result == :success]
|
|
103
|
+
plan.finished.on_completion! do |success, value|
|
|
104
|
+
suspended_action << SubPlanFinished[plan.id, success && (value.result == :success)]
|
|
108
105
|
end
|
|
109
106
|
end
|
|
110
107
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Dynflow
|
|
2
|
+
|
|
3
|
+
module MethodicActor
|
|
4
|
+
def on_message(message)
|
|
5
|
+
method, *args = message
|
|
6
|
+
self.send(method, *args)
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Common parent for all the Dynflow actors defining some defaults
|
|
11
|
+
# that we preffer here.
|
|
12
|
+
class Actor < Concurrent::Actor::Context
|
|
13
|
+
|
|
14
|
+
include MethodicActor
|
|
15
|
+
|
|
16
|
+
# Behaviour that watches for polite asking for termination
|
|
17
|
+
# and calls corresponding method on the context to do so
|
|
18
|
+
class PoliteTermination < Concurrent::Actor::Behaviour::Abstract
|
|
19
|
+
def on_envelope(envelope)
|
|
20
|
+
message, terminated_future = envelope
|
|
21
|
+
if :start_termination == message
|
|
22
|
+
context.start_termination(terminated_future)
|
|
23
|
+
envelope.future.success true if !envelope.future.nil?
|
|
24
|
+
Concurrent::Actor::Behaviour::MESSAGE_PROCESSED
|
|
25
|
+
else
|
|
26
|
+
pass envelope
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
include Algebrick::Matching
|
|
32
|
+
|
|
33
|
+
def start_termination(future)
|
|
34
|
+
@terminated = future
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def finish_termination
|
|
38
|
+
@terminated.success(true)
|
|
39
|
+
reference.tell(:terminate!)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def terminating?
|
|
43
|
+
!!@terminated
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def behaviour_definition
|
|
47
|
+
[*Concurrent::Actor::Behaviour.base(:just_log),
|
|
48
|
+
Concurrent::Actor::Behaviour::Buffer,
|
|
49
|
+
[Concurrent::Actor::Behaviour::SetResults, :just_log],
|
|
50
|
+
Concurrent::Actor::Behaviour::Awaits,
|
|
51
|
+
PoliteTermination,
|
|
52
|
+
Concurrent::Actor::Behaviour::ExecutesContext,
|
|
53
|
+
Concurrent::Actor::Behaviour::ErrorsOnUnknownMessage]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/dynflow/clock.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Dynflow
|
|
2
2
|
require 'set'
|
|
3
3
|
|
|
4
|
-
class Clock <
|
|
4
|
+
class Clock < Actor
|
|
5
5
|
|
|
6
6
|
include Algebrick::Types
|
|
7
7
|
|
|
@@ -40,23 +40,7 @@ module Dynflow
|
|
|
40
40
|
Pill = type { fields Float }
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
def
|
|
44
|
-
Type! time, Time, Numeric
|
|
45
|
-
time = Time.now + time if time.is_a? Numeric
|
|
46
|
-
timer = Timer[who, time, with_what.nil? ? Algebrick::Types::None : Some[Object][with_what], where]
|
|
47
|
-
if terminated?
|
|
48
|
-
Thread.new do
|
|
49
|
-
sleep [timer.when - Time.now, 0].max
|
|
50
|
-
timer.apply
|
|
51
|
-
end
|
|
52
|
-
else
|
|
53
|
-
self << timer
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
def delayed_initialize
|
|
43
|
+
def initialize
|
|
60
44
|
@timers = SortedSet.new
|
|
61
45
|
@sleeping_pill = None
|
|
62
46
|
@sleep_barrier = Mutex.new
|
|
@@ -64,27 +48,32 @@ module Dynflow
|
|
|
64
48
|
Thread.pass until @sleep_barrier.locked? || @sleeper.status == 'sleep'
|
|
65
49
|
end
|
|
66
50
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
super
|
|
51
|
+
def default_reference_class
|
|
52
|
+
ClockReference
|
|
70
53
|
end
|
|
71
54
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
sleep_to first_timer
|
|
77
|
-
end,
|
|
78
|
-
~Timer >-> timer do
|
|
79
|
-
@timers.add timer
|
|
80
|
-
if @timers.size == 1
|
|
81
|
-
sleep_to timer
|
|
82
|
-
else
|
|
83
|
-
wakeup if timer == first_timer
|
|
84
|
-
end
|
|
85
|
-
end
|
|
55
|
+
def on_event(event)
|
|
56
|
+
if event == :terminated
|
|
57
|
+
@sleeper.kill
|
|
58
|
+
end
|
|
86
59
|
end
|
|
87
60
|
|
|
61
|
+
def tick
|
|
62
|
+
run_ready_timers
|
|
63
|
+
sleep_to first_timer
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def add_timer(timer)
|
|
67
|
+
@timers.add timer
|
|
68
|
+
if @timers.size == 1
|
|
69
|
+
sleep_to timer
|
|
70
|
+
else
|
|
71
|
+
wakeup if timer == first_timer
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
88
77
|
def run_ready_timers
|
|
89
78
|
while first_timer && first_timer.when <= Time.now
|
|
90
79
|
first_timer.apply
|
|
@@ -122,12 +111,28 @@ module Dynflow
|
|
|
122
111
|
pill = @sleeping_pill
|
|
123
112
|
@sleeping_pill = Took
|
|
124
113
|
@sleep_barrier.sleep pill.value
|
|
125
|
-
|
|
114
|
+
reference.tell(:tick)
|
|
126
115
|
end
|
|
127
116
|
end
|
|
128
117
|
end
|
|
129
|
-
|
|
130
118
|
end
|
|
131
|
-
end
|
|
132
119
|
|
|
120
|
+
class ClockReference < Concurrent::Actor::Reference
|
|
121
|
+
include Algebrick::Types
|
|
122
|
+
|
|
123
|
+
def ping(who, time, with_what = nil, where = :<<)
|
|
124
|
+
Type! time, Time, Numeric
|
|
125
|
+
time = Time.now + time if time.is_a? Numeric
|
|
126
|
+
timer = Clock::Timer[who, time, with_what.nil? ? Algebrick::Types::None : Some[Object][with_what], where]
|
|
127
|
+
# if self.ask!(:terminated?) # FIXME not thread safe
|
|
128
|
+
# Thread.new do
|
|
129
|
+
# sleep [timer.when - Time.now, 0].max
|
|
130
|
+
# timer.apply
|
|
131
|
+
# end
|
|
132
|
+
# else
|
|
133
|
+
self.tell([:add_timer, timer])
|
|
134
|
+
# end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
133
137
|
|
|
138
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
|
|
3
|
+
module Dynflow
|
|
4
|
+
class Config
|
|
5
|
+
include Algebrick::TypeCheck
|
|
6
|
+
|
|
7
|
+
def self.config_attr(name, *types, &default)
|
|
8
|
+
self.send(:define_method, "validate_#{ name }!") do |value|
|
|
9
|
+
Type! value, *types unless types.empty?
|
|
10
|
+
end
|
|
11
|
+
self.send(:define_method, name) do
|
|
12
|
+
var_name = "@#{ name }"
|
|
13
|
+
if instance_variable_defined?(var_name)
|
|
14
|
+
return instance_variable_get(var_name)
|
|
15
|
+
else
|
|
16
|
+
return default
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
self.send(:attr_writer, name)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class ForWorld
|
|
23
|
+
attr_reader :world, :config
|
|
24
|
+
|
|
25
|
+
def initialize(config, world)
|
|
26
|
+
@config = config
|
|
27
|
+
@world = world
|
|
28
|
+
@cache = {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def validate
|
|
32
|
+
@config.validate(self)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def method_missing(name)
|
|
36
|
+
return @cache[name] if @cache.key?(name)
|
|
37
|
+
value = @config.send(name)
|
|
38
|
+
value = value.call(@world, self) if value.is_a? Proc
|
|
39
|
+
@config.send("validate_#{ name }!", value)
|
|
40
|
+
@cache[name] = value
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
config_attr :logger_adapter, LoggerAdapters::Abstract do
|
|
45
|
+
LoggerAdapters::Simple.new
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
config_attr :transaction_adapter, TransactionAdapters::Abstract do
|
|
49
|
+
TransactionAdapters::None.new
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
config_attr :persistence_adapter, PersistenceAdapters::Abstract do
|
|
53
|
+
PersistenceAdapters::Sequel.new('sqlite:/')
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
config_attr :coordinator_adapter, CoordinatorAdapters::Abstract do |world|
|
|
57
|
+
CoordinatorAdapters::Sequel.new(world)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
config_attr :pool_size, Fixnum do
|
|
61
|
+
5
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
config_attr :executor, Executors::Abstract, FalseClass do |world, config|
|
|
65
|
+
Executors::Parallel.new(world, config.pool_size)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
config_attr :connector, Connectors::Abstract do |world|
|
|
69
|
+
Connectors::Direct.new(world)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
config_attr :auto_rescue, Algebrick::Types::Boolean do
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
config_attr :exit_on_terminate, Algebrick::Types::Boolean do
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
config_attr :auto_terminate, Algebrick::Types::Boolean do
|
|
81
|
+
true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
config_attr :auto_execute, Algebrick::Types::Boolean do
|
|
85
|
+
true
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
config_attr :action_classes do
|
|
89
|
+
Action.all_children
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
config_attr :meta do
|
|
93
|
+
{ 'hostname' => Socket.gethostname, 'pid' => Process.pid }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def validate(config_for_world)
|
|
97
|
+
if defined? ::ActiveRecord::Base
|
|
98
|
+
ar_pool_size = ::ActiveRecord::Base.connection_pool.instance_variable_get(:@size)
|
|
99
|
+
if (config_for_world.pool_size / 2.0) > ar_pool_size
|
|
100
|
+
config_for_world.world.logger.warn 'Consider increasing ActiveRecord::Base.connection_pool size, ' +
|
|
101
|
+
"it's #{ar_pool_size} but there is #{config_for_world.pool_size} " +
|
|
102
|
+
'threads in Dynflow pool.'
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|