oflow 0.6.0 → 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.
- checksums.yaml +4 -4
- data/README.md +23 -41
- data/lib/oflow.rb +2 -3
- data/lib/oflow/actor.rb +3 -0
- data/lib/oflow/actors/httpserver.rb +3 -1
- data/lib/oflow/actors/log.rb +3 -2
- data/lib/oflow/actors/persister.rb +29 -6
- data/lib/oflow/actors/timer.rb +29 -12
- data/lib/oflow/box.rb +2 -2
- data/lib/oflow/env.rb +221 -15
- data/lib/oflow/flow.rb +217 -37
- data/lib/oflow/graffle.rb +293 -0
- data/lib/oflow/haserrorhandler.rb +3 -22
- data/lib/oflow/haslog.rb +21 -15
- data/lib/oflow/inspector.rb +18 -17
- data/lib/oflow/link.rb +11 -6
- data/lib/oflow/task.rb +134 -22
- data/lib/oflow/test/actorwrap.rb +1 -1
- data/lib/oflow/version.rb +1 -1
- data/test/actors/balancer_test.rb +17 -12
- data/test/actors/httpserver_test.rb +11 -10
- data/test/actors/log_test.rb +3 -6
- data/test/actors/merger_test.rb +23 -18
- data/test/actors/persister_test.rb +6 -8
- data/test/actors/timer_test.rb +63 -35
- data/test/actorwrap_test.rb +2 -6
- data/test/all_tests.rb +3 -7
- data/test/box_test.rb +4 -10
- data/test/flow_basic_test.rb +24 -22
- data/test/flow_cfg_error_test.rb +17 -13
- data/test/flow_linked_test.rb +146 -0
- data/test/flow_log_test.rb +43 -29
- data/test/flow_rescue_test.rb +41 -27
- data/test/flow_tracker_test.rb +26 -30
- data/test/helper.rb +15 -0
- data/test/task_test.rb +3 -7
- data/test/tracker_test.rb +3 -11
- metadata +5 -7
- data/lib/oflow/haslinks.rb +0 -68
- data/lib/oflow/hasname.rb +0 -31
- data/lib/oflow/hastasks.rb +0 -214
- data/test/flow_nest_test.rb +0 -215
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41a62977484dfde369899d13d67f34f41e88d643
|
4
|
+
data.tar.gz: 80f9dc4d7fb42859bfd4b292393b53857ac418b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a32921aa1cfae8f6053f571d5b9fad0bf473f2365f46b36f12ff5866ea913e8836d6f5463b46f7fe05cdc839dd93455f985aabaccc2ae1ecf85455f84353beb0
|
7
|
+
data.tar.gz: 04ad4e9db6a86bb6ea02ab1f31f405f8abcb34c7af516e3598c0549414a5fbb704224eb91935a635884daad0837e1e3a8eb99805c0f074a54946fc31b818052e
|
data/README.md
CHANGED
@@ -25,6 +25,18 @@ Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announceme
|
|
25
25
|
|
26
26
|
## Release Notes
|
27
27
|
|
28
|
+
### Next Release 0.8
|
29
|
+
|
30
|
+
- A somewhat non trivial example. GemChart collects statistics on my gems and
|
31
|
+
stores those statistics. It also provides a web interface to view the
|
32
|
+
statistics as a graph.
|
33
|
+
|
34
|
+
### Current Release 0.7
|
35
|
+
|
36
|
+
- Simplified the APIs and structure.
|
37
|
+
|
38
|
+
- Added OmniGraffle support. Diagrams can now be executed.
|
39
|
+
|
28
40
|
### Release 0.6
|
29
41
|
|
30
42
|
- Added HTTP Server Actor that acts as a simple HTTP server.
|
@@ -95,19 +107,23 @@ end
|
|
95
107
|
Next build the flow using Ruby code.
|
96
108
|
|
97
109
|
```ruby
|
110
|
+
env = ::OFlow::Env.new('')
|
111
|
+
|
98
112
|
def hello_flow(period)
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
}
|
103
|
-
f.task(:hello, HelloWorld)
|
113
|
+
env.flow('hello_world') { |f|
|
114
|
+
f.task(:repeater, ::OFlow::Actors::Timer, repeat: 3, period: period) { |t|
|
115
|
+
t.link(nil, :hello, nil)
|
104
116
|
}
|
117
|
+
f.task(:hello, HelloWorld)
|
118
|
+
}
|
119
|
+
env.prepare()
|
120
|
+
env.start()
|
105
121
|
end
|
106
122
|
|
107
123
|
hello_flow(1.0)
|
108
124
|
|
109
125
|
if $0 == __FILE__
|
110
|
-
|
126
|
+
env.flush()
|
111
127
|
end
|
112
128
|
```
|
113
129
|
|
@@ -123,10 +139,6 @@ Hello World!
|
|
123
139
|
|
124
140
|
## Future Features
|
125
141
|
|
126
|
-
- HTTP Server Actor
|
127
|
-
|
128
|
-
- OmniGraffle file input for configuration.
|
129
|
-
|
130
142
|
- .svg file input for configuration.
|
131
143
|
|
132
144
|
- Visio file input for configuration.
|
@@ -141,7 +153,7 @@ Hello World!
|
|
141
153
|
around 10M operations per second where an operation is one task execution per
|
142
154
|
thread.
|
143
155
|
|
144
|
-
- HTTP based inpector.
|
156
|
+
- HTTP/Websockets based inpector.
|
145
157
|
|
146
158
|
# Links
|
147
159
|
|
@@ -162,33 +174,3 @@ Hello World!
|
|
162
174
|
[Oj Object Mode Performance](http://www.ohler.com/dev/oj_misc/performance_object.html) compares Oj object mode parser performance to other marshallers.
|
163
175
|
|
164
176
|
[Oj Callback Performance](http://www.ohler.com/dev/oj_misc/performance_callback.html) compares Oj callback parser performance to other JSON parsers.
|
165
|
-
|
166
|
-
### License:
|
167
|
-
|
168
|
-
Copyright (c) 2014, Peter Ohler
|
169
|
-
All rights reserved.
|
170
|
-
|
171
|
-
Redistribution and use in source and binary forms, with or without
|
172
|
-
modification, are permitted provided that the following conditions are met:
|
173
|
-
|
174
|
-
- Redistributions of source code must retain the above copyright notice, this
|
175
|
-
list of conditions and the following disclaimer.
|
176
|
-
|
177
|
-
- Redistributions in binary form must reproduce the above copyright notice,
|
178
|
-
this list of conditions and the following disclaimer in the documentation
|
179
|
-
and/or other materials provided with the distribution.
|
180
|
-
|
181
|
-
- Neither the name of Peter Ohler nor the names of its contributors may be
|
182
|
-
used to endorse or promote products derived from this software without
|
183
|
-
specific prior written permission.
|
184
|
-
|
185
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
186
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
187
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
188
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
189
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
190
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
191
|
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
192
|
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
193
|
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
194
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/lib/oflow.rb
CHANGED
@@ -4,9 +4,6 @@ end
|
|
4
4
|
|
5
5
|
require 'oflow/errors'
|
6
6
|
require 'oflow/stamp'
|
7
|
-
require 'oflow/hasname'
|
8
|
-
require 'oflow/hastasks'
|
9
|
-
require 'oflow/haslinks'
|
10
7
|
require 'oflow/haserrorhandler'
|
11
8
|
require 'oflow/haslog'
|
12
9
|
require 'oflow/pattern'
|
@@ -19,5 +16,7 @@ require 'oflow/flow'
|
|
19
16
|
|
20
17
|
require 'oflow/actors'
|
21
18
|
|
19
|
+
require 'oflow/graffle'
|
20
|
+
|
22
21
|
require 'oflow/env'
|
23
22
|
require 'oflow/inspector'
|
data/lib/oflow/actor.rb
CHANGED
@@ -28,7 +28,8 @@ module OFlow
|
|
28
28
|
session = @server.accept_nonblock()
|
29
29
|
session.fcntl(Fcntl::F_SETFL, session.fcntl(Fcntl::F_GETFL, 0) | Fcntl::O_NONBLOCK)
|
30
30
|
@count += 1
|
31
|
-
|
31
|
+
# if nil is returned the request was empty
|
32
|
+
next if (req = read_req(session, @count)).nil?
|
32
33
|
@sessions[@count] = session
|
33
34
|
resp = {
|
34
35
|
status: 200,
|
@@ -106,6 +107,7 @@ module OFlow
|
|
106
107
|
id: id,
|
107
108
|
}
|
108
109
|
line = session.gets()
|
110
|
+
return if line.nil?
|
109
111
|
parts = line.split(' ')
|
110
112
|
req[:method] = parts[0]
|
111
113
|
req[:protocol] = parts[2]
|
data/lib/oflow/actors/log.rb
CHANGED
@@ -78,6 +78,7 @@ module OFlow
|
|
78
78
|
# @option options [String|Fixnum] :severity initial setting for severity
|
79
79
|
# @option options [Proc] :formatter initial setting for the formatter procedure
|
80
80
|
def set_options(options)
|
81
|
+
@formatter = options.fetch(:formatter, nil)
|
81
82
|
if !(filename = options[:filename]).nil?
|
82
83
|
max_file_size = options.fetch(:max_file_size, options.fetch(:shift_size, 1048576))
|
83
84
|
max_file_count = options.fetch(:max_file_count, options.fetch(:shift_age, 7))
|
@@ -86,9 +87,9 @@ module OFlow
|
|
86
87
|
@logger = Logger.new(stream)
|
87
88
|
else
|
88
89
|
@logger = Logger.new(STDOUT)
|
90
|
+
@formatter = proc { |s,t,p,m| "#{s[0]} #{p}> #{m}\n" } if @formatter.nil?
|
89
91
|
end
|
90
92
|
@logger.level = options.fetch(:severity, Env.log_level)
|
91
|
-
@formatter = options.fetch(:formatter, nil)
|
92
93
|
@logger.formatter = proc { |s,t,p,m| m }
|
93
94
|
@name = 'Logger' if @name.nil?
|
94
95
|
end
|
@@ -103,7 +104,7 @@ module OFlow
|
|
103
104
|
ss = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'][level]
|
104
105
|
ss = '' if ss.nil?
|
105
106
|
if @formatter.nil?
|
106
|
-
msg = "#{ss[0]}
|
107
|
+
msg = "#{ss[0]} #{now.strftime('%Y-%m-%dT%H:%M:%S.%6N')} #{tid}> #{message}\n"
|
107
108
|
else
|
108
109
|
msg = @formatter.call(ss, now, tid, message)
|
109
110
|
end
|
@@ -29,6 +29,8 @@ module OFlow
|
|
29
29
|
# - :key_data [String] path to record data (default: nil (all))
|
30
30
|
# - :key_path [String] path to key for the record (default: 'key')
|
31
31
|
# - :seq_path [String] path to sequence for the record (default: 'seq')
|
32
|
+
# - :results_path [String] path to where the results should be placed in
|
33
|
+
# the request (default: nil or ship only results)
|
32
34
|
# - :cache [Boolean] if true, cache records in memory
|
33
35
|
# - :historic [Boolean] if true, do not delete previous versions
|
34
36
|
def initialize(task, options)
|
@@ -37,21 +39,25 @@ module OFlow
|
|
37
39
|
if @dir.nil?
|
38
40
|
@dir = File.join('db', task.full_name.gsub(':', '/'))
|
39
41
|
end
|
40
|
-
@
|
41
|
-
|
42
|
+
@dir = File.expand_path(@dir.strip)
|
43
|
+
|
44
|
+
@key_path = options.fetch(:key_path, 'key').strip
|
45
|
+
@seq_path = options.fetch(:seq_path, 'seq').strip
|
42
46
|
@data_path = options.fetch(:data_path, nil) # nil means all contents
|
47
|
+
@data_path.strip! unless @data_path.nil?
|
48
|
+
@results_path = options[:results_path]
|
49
|
+
@results_path.strip! unless @results_path.nil?
|
43
50
|
if options.fetch(:cache, true)
|
44
51
|
# key is record key, value is [seq, rec]
|
45
52
|
@cache = {}
|
46
53
|
else
|
47
54
|
@cache = nil
|
48
55
|
end
|
49
|
-
@historic = options.fetch(:historic, false)
|
56
|
+
@historic = ('true' == options.fetch(:historic, 'false').to_s)
|
50
57
|
|
51
58
|
if Dir.exist?(@dir)
|
52
59
|
unless @cache.nil?
|
53
|
-
Dir.glob(File.join('**', '*.json')).each do |path|
|
54
|
-
path = File.join(@dir, path)
|
60
|
+
Dir.glob(File.join(@dir, '**', '*.json')).each do |path|
|
55
61
|
if File.symlink?(path)
|
56
62
|
rec = load(path)
|
57
63
|
unless @cache.nil?
|
@@ -76,6 +82,8 @@ module OFlow
|
|
76
82
|
result = read(box)
|
77
83
|
when :update
|
78
84
|
result = update(box)
|
85
|
+
when :insert_update
|
86
|
+
result = insert_update(box)
|
79
87
|
when :delete, :remove
|
80
88
|
result = delete(box)
|
81
89
|
when :query
|
@@ -85,7 +93,14 @@ module OFlow
|
|
85
93
|
else
|
86
94
|
raise OpError.new(task.full_name, op)
|
87
95
|
end
|
88
|
-
|
96
|
+
unless dest.nil?
|
97
|
+
if @results_path.nil?
|
98
|
+
box = Box.new(result, box.tracker)
|
99
|
+
else
|
100
|
+
box = box.set(@results_path, result)
|
101
|
+
end
|
102
|
+
task.ship(dest, box)
|
103
|
+
end
|
89
104
|
end
|
90
105
|
|
91
106
|
def insert(box)
|
@@ -151,6 +166,14 @@ module OFlow
|
|
151
166
|
rec
|
152
167
|
end
|
153
168
|
|
169
|
+
def insert_update(box)
|
170
|
+
begin
|
171
|
+
insert(box)
|
172
|
+
rescue ExistsError
|
173
|
+
update(box)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
154
177
|
def delete(box)
|
155
178
|
key = box.get(@key_path)
|
156
179
|
@cache.delete(key) unless @cache.nil?
|
data/lib/oflow/actors/timer.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
|
2
|
+
require 'date'
|
3
|
+
|
2
4
|
require 'logger'
|
3
5
|
|
4
6
|
module OFlow
|
@@ -70,7 +72,6 @@ module OFlow
|
|
70
72
|
end
|
71
73
|
while true
|
72
74
|
now = Time.now()
|
73
|
-
|
74
75
|
# If past stop time then it is done. A future change in options can
|
75
76
|
# restart the timer.
|
76
77
|
return if !@stop.nil? && @stop < now
|
@@ -123,7 +124,7 @@ module OFlow
|
|
123
124
|
|
124
125
|
def set_options(options)
|
125
126
|
set_start(options[:start]) # if nil let start get set to now
|
126
|
-
set_stop(
|
127
|
+
set_stop(options[:stop]) if options.has_key?(:stop)
|
127
128
|
set_period(options[:period]) if options.has_key?(:period)
|
128
129
|
set_repeat(options[:repeat]) if options.has_key?(:repeat)
|
129
130
|
set_with_tracker(options[:with_tracker])
|
@@ -131,9 +132,11 @@ module OFlow
|
|
131
132
|
end
|
132
133
|
|
133
134
|
def set_start(v)
|
134
|
-
|
135
|
-
|
136
|
-
v =
|
135
|
+
if v.is_a?(String)
|
136
|
+
v = DateTime.parse(v).to_time
|
137
|
+
v = v - v.gmtoff
|
138
|
+
elsif v.is_a?(Numeric)
|
139
|
+
v = Time.now() + v
|
137
140
|
elsif v.nil?
|
138
141
|
v = Time.now()
|
139
142
|
elsif !v.kind_of?(Time) && !v.kind_of?(Date)
|
@@ -143,9 +146,11 @@ module OFlow
|
|
143
146
|
end
|
144
147
|
|
145
148
|
def set_stop(v)
|
146
|
-
|
147
|
-
|
148
|
-
v =
|
149
|
+
if v.is_a?(String)
|
150
|
+
v = DateTime.parse(v).to_time
|
151
|
+
v = v - v.gmtoff
|
152
|
+
elsif v.is_a?(Numeric)
|
153
|
+
v = Time.now() + v
|
149
154
|
elsif !v.nil? && !v.kind_of?(Time) && !v.kind_of?(Date)
|
150
155
|
raise ConfigError.new("Expected stop to be a Time or Numeric, not a #{v.class}.")
|
151
156
|
end
|
@@ -153,17 +158,29 @@ module OFlow
|
|
153
158
|
end
|
154
159
|
|
155
160
|
def set_period(v)
|
156
|
-
|
161
|
+
p = 0.0
|
162
|
+
if v.kind_of?(Numeric)
|
163
|
+
p = v
|
164
|
+
elsif v.is_a?(String)
|
165
|
+
p = v.strip().to_f
|
166
|
+
else
|
157
167
|
raise ConfigError.new("Expected period to be a Numeric, not a #{v.class}.")
|
158
168
|
end
|
159
|
-
|
169
|
+
raise ConfigError.new("period must be greater than 0.0.") if 0.0 >= p
|
170
|
+
@period = p
|
160
171
|
end
|
161
172
|
|
162
173
|
def set_repeat(v)
|
163
|
-
|
174
|
+
r = nil
|
175
|
+
if v.kind_of?(Fixnum)
|
176
|
+
r = v
|
177
|
+
elsif v.is_a?(String)
|
178
|
+
r = v.strip().to_i
|
179
|
+
elsif !v.nil?
|
164
180
|
raise ConfigError.new("Expected repeat to be a Fixnum, not a #{v.class}.")
|
165
181
|
end
|
166
|
-
|
182
|
+
raise ConfigError.new("repeat must be greater than or equal 0.0 or nil") if !r.nil? && 0.0 >= r
|
183
|
+
@repeat = r
|
167
184
|
end
|
168
185
|
|
169
186
|
def set_label(v)
|
data/lib/oflow/box.rb
CHANGED
@@ -35,7 +35,7 @@ module OFlow
|
|
35
35
|
Box.new(@contents, @tracker.receive(location, op))
|
36
36
|
end
|
37
37
|
|
38
|
-
# Sets or adds a value
|
38
|
+
# Sets or adds a value inside the Box. The Box is changed with the new
|
39
39
|
# contents being thawed where necessary. A path is a set of element names in
|
40
40
|
# the case of a Hash or index numbers in the case of an Array joined with
|
41
41
|
# the ':' character as a separator.
|
@@ -46,7 +46,7 @@ module OFlow
|
|
46
46
|
aset(path.split(':'), value)
|
47
47
|
end
|
48
48
|
|
49
|
-
# Sets or adds a value
|
49
|
+
# Sets or adds a value inside the Box where the path is an array of
|
50
50
|
# element names or indices. Indices can be Fixnum or Strings.
|
51
51
|
# @param path [Array] location of element to change or add.
|
52
52
|
# @param value value for the addition or change
|
data/lib/oflow/env.rb
CHANGED
@@ -5,17 +5,11 @@ module OFlow
|
|
5
5
|
# OFlow system.
|
6
6
|
class Env
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
extend HasName
|
11
|
-
extend HasErrorHandler
|
8
|
+
include HasLog
|
9
|
+
include HasErrorHandler
|
12
10
|
|
13
|
-
# The default logging level.
|
14
11
|
@@log_level = Logger::WARN
|
15
12
|
|
16
|
-
init_name(nil, '')
|
17
|
-
init_tasks()
|
18
|
-
|
19
13
|
# Returns the default log level.
|
20
14
|
# @return [Fixnum] the default log level which is one of the Logger::Severity values.
|
21
15
|
def self.log_level()
|
@@ -26,23 +20,235 @@ module OFlow
|
|
26
20
|
# @param level [Fixnum] Logger::Severity to set the default log level to
|
27
21
|
def self.log_level=(level)
|
28
22
|
@@log_level = level unless level < Logger::Severity::DEBUG || Logger::Severity::FATAL < level
|
23
|
+
#@log.receive(:severity, Box.new(@log_level)) unless @log.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(name='')
|
27
|
+
# The default logging level.
|
28
|
+
@flows = {}
|
29
|
+
@prepared = false
|
30
|
+
@name = name
|
31
|
+
@log = nil
|
32
|
+
_clear()
|
33
|
+
end
|
34
|
+
|
35
|
+
def full_name()
|
36
|
+
@name
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a log Task if one is set on the instance.
|
40
|
+
# @return [Task] log Task.
|
41
|
+
def log()
|
42
|
+
@log
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a error_handler Task if one is set on the instance.
|
46
|
+
# @return [Task] error_handler Task.
|
47
|
+
def error_handler()
|
48
|
+
@error_handler
|
49
|
+
end
|
50
|
+
|
51
|
+
# Creates a Flow and yield to a block with the newly create Flow. Used to
|
52
|
+
# contruct Flows.
|
53
|
+
# @param name [Symbol|String] base name for the Flow
|
54
|
+
# @param options [Hash] optional parameters
|
55
|
+
# @param block [Proc] block to yield to with the new Flow instance
|
56
|
+
# @return [Flow] new Flow
|
57
|
+
def flow(name, &block)
|
58
|
+
f = Flow.new(self, name)
|
59
|
+
@flows[f.name] = f
|
60
|
+
yield(f) if block_given?
|
61
|
+
f
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepare()
|
65
|
+
@flows.each_value { |f|
|
66
|
+
f.resolve_all_links()
|
67
|
+
}
|
68
|
+
validate()
|
69
|
+
@prepared = true
|
70
|
+
end
|
71
|
+
|
72
|
+
# Validates the container by verifying all links on a task have been set to
|
73
|
+
# a valid destination and that destination has been resolved.
|
74
|
+
# @raise [ValidateError] if there is an error in validation
|
75
|
+
def validate()
|
76
|
+
# collects errors and raises all errors at once if there are any
|
77
|
+
errors = _validation_errors()
|
78
|
+
raise ValidateError.new(errors) unless errors.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns an Array of validation errors.
|
82
|
+
def _validation_errors()
|
83
|
+
errors = []
|
84
|
+
@flows.each_value { |f| errors += f._validation_errors() }
|
85
|
+
errors
|
86
|
+
end
|
87
|
+
|
88
|
+
# Resolves all the Links on all the Flows being managed.
|
89
|
+
def resolve_all_links()
|
90
|
+
@flows.each_value { |f|
|
91
|
+
f.resolve_all_links()
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Iterates over each Flow and yields to the provided block with each Flow.
|
96
|
+
# @param blk [Proc] Proc to call on each iteration
|
97
|
+
def each_flow(&blk)
|
98
|
+
@flows.each { |name,flow| blk.yield(flow) }
|
99
|
+
end
|
100
|
+
|
101
|
+
# Performs a recursive walk over all Flows and yields to the provided block
|
102
|
+
# for each.
|
103
|
+
# @param blk [Proc] Proc to call on each iteration
|
104
|
+
def walk_flows(&blk)
|
105
|
+
@flows.each_value do |f|
|
106
|
+
blk.yield(t)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Performs a recursive walk over all Tasks in all Flows and yields to the
|
111
|
+
# provided block for each.
|
112
|
+
# @param blk [Proc] Proc to call on each iteration
|
113
|
+
def walk_tasks(&blk)
|
114
|
+
@flows.each_value do |f|
|
115
|
+
f.walk_tasks(&blk)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Locates and return a Flow with the specified name.
|
120
|
+
# @param name [String] name of the Flow
|
121
|
+
# @return [Flow|nil] the Flow with the name specified or nil
|
122
|
+
def find_flow(name)
|
123
|
+
name = name.to_sym unless name.nil?
|
124
|
+
@flows[name]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Locates and return a Task with the specified full name.
|
128
|
+
# @param name [String] full name of the Task
|
129
|
+
# @return [Flow|Task|nil] the Flow or Task with the name specified or nil
|
130
|
+
def locate(name)
|
131
|
+
name = name[1..-1] if name.start_with?(':')
|
132
|
+
name = name[0..-2] if name.end_with?(':')
|
133
|
+
path = name.split(':')
|
134
|
+
_locate(path)
|
135
|
+
end
|
136
|
+
|
137
|
+
def _locate(path)
|
138
|
+
f = @flows[path[0].to_sym]
|
139
|
+
return f if f.nil? || 1 == path.size
|
140
|
+
f._locate(path[1..-1])
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns the number of active Tasks.
|
144
|
+
def flow_count()
|
145
|
+
@flows.size
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the sum of all the requests in all the Flow's Task's queues.
|
149
|
+
# @return [Fixnum] total number of items waiting to be processed
|
150
|
+
def queue_count()
|
151
|
+
cnt = 0
|
152
|
+
@flows.each_value { |f| cnt += f.queue_count() }
|
153
|
+
cnt
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns true of one or more Tasks is either processing a request or has a
|
157
|
+
# request waiting to be processed on it's input queue.
|
158
|
+
# @return [true|false] the busy state across all Tasks
|
159
|
+
def busy?
|
160
|
+
@flows.each_value { |f| return true if f.busy? }
|
161
|
+
return true if !@log.nil? && @log.busy?
|
162
|
+
return true if !@error_handler.nil? && @error_handler.busy?
|
163
|
+
false
|
164
|
+
end
|
165
|
+
|
166
|
+
# Calls the stop() method on all Tasks.
|
167
|
+
def stop()
|
168
|
+
@flows.each_value { |f| f.stop() }
|
169
|
+
end
|
170
|
+
|
171
|
+
# Calls the step() method one Task that is stopped and has an item in the
|
172
|
+
# queue. The Tasks with the highest backed_up() value is selected.
|
173
|
+
def step()
|
174
|
+
max = 0.0
|
175
|
+
best = nil
|
176
|
+
walk_tasks() do |t|
|
177
|
+
if Task::STOPPED == t.state
|
178
|
+
bu = t.backed_up()
|
179
|
+
if max < bu
|
180
|
+
best = t
|
181
|
+
max = bu
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
best.step() unless best.nil?
|
186
|
+
best
|
187
|
+
end
|
188
|
+
|
189
|
+
# Calls the start() method on all Tasks.
|
190
|
+
def start()
|
191
|
+
prepare() unless @prepared
|
192
|
+
@flows.each_value { |f| f.start() }
|
193
|
+
end
|
194
|
+
|
195
|
+
# Wakes up all the Tasks in the Flow.
|
196
|
+
def wakeup()
|
197
|
+
@flows.each_value { |f| f.wakeup() }
|
198
|
+
end
|
199
|
+
|
200
|
+
# Wakes up all the Tasks in the Flow and waits for the system to become idle
|
201
|
+
# before returning.
|
202
|
+
def flush()
|
203
|
+
wakeup()
|
204
|
+
@flows.each_value { |f| f.flush() }
|
205
|
+
while busy?
|
206
|
+
sleep(0.2)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Sets the state of all Tasks recursively. This should not be called
|
211
|
+
# directly.
|
212
|
+
def state=(s)
|
213
|
+
@flows.each_value do |f|
|
214
|
+
f.state = s
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Shuts down all Tasks.
|
219
|
+
# @param flush_first [true|false] flag indicating shutdown should occur after the system becomes idle
|
220
|
+
def shutdown(flush_first=false)
|
221
|
+
# block all tasks first so threads can empty queues
|
222
|
+
@flows.each_value do |f|
|
223
|
+
f.state = Task::BLOCKED
|
224
|
+
end
|
225
|
+
# shutdown and wait for queues to empty if necessary
|
226
|
+
@flows.each_value do |f|
|
227
|
+
f.shutdown(flush_first)
|
228
|
+
end
|
229
|
+
@flows = {}
|
230
|
+
end
|
231
|
+
|
232
|
+
# Clears out all Tasks and Flows and resets the object back to a empty state.
|
233
|
+
def clear()
|
234
|
+
shutdown()
|
235
|
+
@flows = {}
|
236
|
+
_clear()
|
29
237
|
end
|
30
238
|
|
31
239
|
# Resets the error handler and log. Usually called on init and by the
|
32
240
|
# clear() method.
|
33
|
-
def
|
241
|
+
def _clear()
|
34
242
|
@error_handler = Task.new(self, :error, Actors::ErrorHandler)
|
35
243
|
@log = Task.new(self, :log, Actors::Log)
|
36
244
|
end
|
37
245
|
|
38
|
-
_clear()
|
39
|
-
|
40
246
|
# Describes all the Flows and Tasks in the system.
|
41
|
-
def
|
247
|
+
def describe(detail=0, indent=0)
|
42
248
|
i = ' ' * indent
|
43
|
-
lines = ["#{i}#{self} {"]
|
44
|
-
@
|
45
|
-
lines <<
|
249
|
+
lines = ["#{i}#{@name} (#{self.class.name}) {"]
|
250
|
+
@flows.each_value { |f|
|
251
|
+
lines << f.describe(detail, indent + 2)
|
46
252
|
}
|
47
253
|
lines << i + "}"
|
48
254
|
lines.join("\n")
|