oflow 0.3.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 +7 -0
- data/LICENSE +20 -0
- data/README.md +182 -0
- data/lib/oflow/actor.rb +76 -0
- data/lib/oflow/actors/errorhandler.rb +32 -0
- data/lib/oflow/actors/ignore.rb +22 -0
- data/lib/oflow/actors/log.rb +175 -0
- data/lib/oflow/actors/relay.rb +23 -0
- data/lib/oflow/actors/timer.rb +126 -0
- data/lib/oflow/actors.rb +11 -0
- data/lib/oflow/box.rb +195 -0
- data/lib/oflow/env.rb +52 -0
- data/lib/oflow/errors.rb +74 -0
- data/lib/oflow/flow.rb +75 -0
- data/lib/oflow/haserrorhandler.rb +48 -0
- data/lib/oflow/haslinks.rb +64 -0
- data/lib/oflow/haslog.rb +72 -0
- data/lib/oflow/hasname.rb +31 -0
- data/lib/oflow/hastasks.rb +209 -0
- data/lib/oflow/inspector.rb +501 -0
- data/lib/oflow/link.rb +43 -0
- data/lib/oflow/pattern.rb +8 -0
- data/lib/oflow/stamp.rb +39 -0
- data/lib/oflow/task.rb +415 -0
- data/lib/oflow/test/action.rb +21 -0
- data/lib/oflow/test/actorwrap.rb +62 -0
- data/lib/oflow/test.rb +8 -0
- data/lib/oflow/tracker.rb +109 -0
- data/lib/oflow/version.rb +5 -0
- data/lib/oflow.rb +23 -0
- data/test/actors/log_test.rb +57 -0
- data/test/actors/timer_test.rb +56 -0
- data/test/actorwrap_test.rb +48 -0
- data/test/all_tests.rb +27 -0
- data/test/box_test.rb +127 -0
- data/test/collector.rb +23 -0
- data/test/flow_basic_test.rb +93 -0
- data/test/flow_cfg_error_test.rb +94 -0
- data/test/flow_log_test.rb +87 -0
- data/test/flow_nest_test.rb +215 -0
- data/test/flow_rescue_test.rb +133 -0
- data/test/flow_tracker_test.rb +82 -0
- data/test/stutter.rb +21 -0
- data/test/task_test.rb +98 -0
- data/test/tracker_test.rb +59 -0
- metadata +93 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3bffd9fcf94e7e2e7bd1042d9eac1579debdbb15
|
4
|
+
data.tar.gz: 1606f17e500c5c533b7192dfb2308ffffb99a393
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c9c6fac1a663adba34988a7caeaff5603054aaa7cf581186bcd199ff439650bc168e3ff65f9edf5a0cc2bf71e246aef5e032f97a11b70f7b2b92050170467053
|
7
|
+
data.tar.gz: a158841d007f3be5f1bddc54fb22686931ff42bdd033c5b097d2bcef6ceb351347e9c0709107926866de9ea0504c429d7bbe28ffeb1d9a2b2974075bb790de80
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Peter Ohler
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
OFlow-ruby
|
2
|
+
==========
|
3
|
+
|
4
|
+
Operations Workflow in Ruby. This implements a workflow/process flow using
|
5
|
+
multiple task nodes that each have their own queues and execution thread.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
gem install oflow
|
9
|
+
|
10
|
+
## Documentation
|
11
|
+
|
12
|
+
*Documentation*: http://www.ohler.com/oflow
|
13
|
+
|
14
|
+
## Source
|
15
|
+
|
16
|
+
*GitHub* *repo*: https://github.com/ohler55/oflow
|
17
|
+
|
18
|
+
*RubyGems* *repo*: https://rubygems.org/gems/oflow
|
19
|
+
|
20
|
+
Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announcements and news about the OFlow gem.
|
21
|
+
|
22
|
+
## Build Status
|
23
|
+
|
24
|
+
[](http://travis-ci.org/ohler55/oflow)
|
25
|
+
|
26
|
+
### Current Release 0.3
|
27
|
+
|
28
|
+
- Initial release with minimal features.
|
29
|
+
|
30
|
+
## Description
|
31
|
+
|
32
|
+
Workflow or more accurately, process flow is associated with processing data
|
33
|
+
based on a diagram of what the processing steps are. OFlow-Ruby implements that
|
34
|
+
in Ruby. Each node in a flow diagram is processing unit with it's own
|
35
|
+
thread. This allows highly parallel processing of data or at least as much as
|
36
|
+
Ruby allows. The future C version will exploit parallel processing to a greater
|
37
|
+
extent with much higher performance.
|
38
|
+
|
39
|
+
One of the problem in any system that processes data in parallel is determing
|
40
|
+
what is going on in the system. OFlow provides a run time inspector that can
|
41
|
+
monitor and control the system and individual nodes in a flow.
|
42
|
+
|
43
|
+
## How to Use / Example
|
44
|
+
|
45
|
+
Flows are composed of individual building blocks referred to as Tasks. A Task
|
46
|
+
wraps an Actor that is the custom portion of a Task. Once the individual Actors
|
47
|
+
have been written they are assembled by linking Tasks together to define the
|
48
|
+
process flow. The process flow can be defined in Ruby or in the near future with
|
49
|
+
a drawing application such as OmniGraffle.
|
50
|
+
|
51
|
+
The approach of using diagrams to define what is effectively a program allows
|
52
|
+
less experience developers to assmeble pieces build by more experienced
|
53
|
+
developers. It also make is much easier to describe a process to non-developers.
|
54
|
+
|
55
|
+
OFlow uses a design pattern of queues and encapsulated processing units in the
|
56
|
+
form of Tasks. It also forces isolation by freezing data that is passed from one
|
57
|
+
Task to another to assure data that is sent to more than one Task is not
|
58
|
+
modified by another Task.
|
59
|
+
|
60
|
+
Putting it all together in a simple hello world flow with timmer triggers starts
|
61
|
+
with defining a hello world Actor in a file called helloworld.rb.
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
require 'oflow'
|
65
|
+
|
66
|
+
class HelloWorld < ::OFlow::Actor
|
67
|
+
def initialize(task, options)
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
def perform(op, box)
|
72
|
+
puts 'Hello World!'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
Next build the flow using Ruby code.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
def hello_flow(period)
|
81
|
+
::OFlow::Env.flow('hello_world') { |f|
|
82
|
+
f.task(:repeater, ::OFlow::Actors::Timer, repeat: 3, period: period) { |t|
|
83
|
+
t.link(nil, :hello, nil)
|
84
|
+
}
|
85
|
+
f.task(:hello, HelloWorld)
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
hello_flow(1.0)
|
90
|
+
|
91
|
+
if $0 == __FILE__
|
92
|
+
::OFlow::Env.flush()
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
Running the helloworld.rb results in this output.
|
97
|
+
|
98
|
+
```
|
99
|
+
> helloworld.rb
|
100
|
+
Hello World!
|
101
|
+
Hello World!
|
102
|
+
Hello World!
|
103
|
+
>
|
104
|
+
```
|
105
|
+
|
106
|
+
## Future Features
|
107
|
+
|
108
|
+
- Balancer Actor that distributes to a set of others Tasks based on how busy each is.
|
109
|
+
|
110
|
+
- Merger Actor that waits for a criteria to be met before continuing.
|
111
|
+
|
112
|
+
- HTTP Server Actor
|
113
|
+
|
114
|
+
- Persister Actor that writes to disk and reads on start)
|
115
|
+
|
116
|
+
- CallOut Actor that uses pipes and fork to use a non-Ruby actor.
|
117
|
+
|
118
|
+
- Cross linking Tasks and Flows.
|
119
|
+
|
120
|
+
- Dynamic links to Tasks and Flows.
|
121
|
+
|
122
|
+
- OmniGraffle file input for configuration.
|
123
|
+
|
124
|
+
- .svg file input for configuration.
|
125
|
+
|
126
|
+
- Visio file input for configuration.
|
127
|
+
|
128
|
+
- High performance C version. Proof of concept puts the performance range at
|
129
|
+
around 10M operations per second where an operation is one task execution per
|
130
|
+
thread.
|
131
|
+
|
132
|
+
- HTTP based inpector.
|
133
|
+
|
134
|
+
# Links
|
135
|
+
|
136
|
+
## Links of Interest
|
137
|
+
|
138
|
+
*Fast XML parser and marshaller on RubyGems*: https://rubygems.org/gems/ox
|
139
|
+
|
140
|
+
*Fast XML parser and marshaller on GitHub*: https://github.com/ohler55/ox
|
141
|
+
|
142
|
+
[Oj Object Encoding Format](http://www.ohler.com/dev/oj_misc/encoding_format.html) describes the OJ Object JSON encoding format.
|
143
|
+
|
144
|
+
[Need for Speed](http://www.ohler.com/dev/need_for_speed/need_for_speed.html) for an overview of how Oj::Doc was designed.
|
145
|
+
|
146
|
+
[Oj Strict Mode Performance](http://www.ohler.com/dev/oj_misc/performance_strict.html) compares Oj strict mode parser performance to other JSON parsers.
|
147
|
+
|
148
|
+
[Oj Compat Mode Performance](http://www.ohler.com/dev/oj_misc/performance_compat.html) compares Oj compat mode parser performance to other JSON parsers.
|
149
|
+
|
150
|
+
[Oj Object Mode Performance](http://www.ohler.com/dev/oj_misc/performance_object.html) compares Oj object mode parser performance to other marshallers.
|
151
|
+
|
152
|
+
[Oj Callback Performance](http://www.ohler.com/dev/oj_misc/performance_callback.html) compares Oj callback parser performance to other JSON parsers.
|
153
|
+
|
154
|
+
### License:
|
155
|
+
|
156
|
+
Copyright (c) 2014, Peter Ohler
|
157
|
+
All rights reserved.
|
158
|
+
|
159
|
+
Redistribution and use in source and binary forms, with or without
|
160
|
+
modification, are permitted provided that the following conditions are met:
|
161
|
+
|
162
|
+
- Redistributions of source code must retain the above copyright notice, this
|
163
|
+
list of conditions and the following disclaimer.
|
164
|
+
|
165
|
+
- Redistributions in binary form must reproduce the above copyright notice,
|
166
|
+
this list of conditions and the following disclaimer in the documentation
|
167
|
+
and/or other materials provided with the distribution.
|
168
|
+
|
169
|
+
- Neither the name of Peter Ohler nor the names of its contributors may be
|
170
|
+
used to endorse or promote products derived from this software without
|
171
|
+
specific prior written permission.
|
172
|
+
|
173
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
174
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
175
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
176
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
177
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
178
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
179
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
180
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
181
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
182
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/lib/oflow/actor.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
|
2
|
+
module OFlow
|
3
|
+
|
4
|
+
# Actors provide the custom functionality for Tasks. Each Task creates an
|
5
|
+
# instance of some Actor. Actors are not shared between Tasks and each can be
|
6
|
+
# assure that the data they operate on is their own.
|
7
|
+
class Actor
|
8
|
+
|
9
|
+
# The enclosing task.
|
10
|
+
attr_reader :task
|
11
|
+
|
12
|
+
# Creates a new instance.
|
13
|
+
# @param task [Task] enclosing Task
|
14
|
+
# @param options [Hash] additional options
|
15
|
+
def initialize(task, options)
|
16
|
+
@task = task
|
17
|
+
end
|
18
|
+
|
19
|
+
# Perform the primary functions for the Actor.
|
20
|
+
# @param op [Symbol] operation to perform
|
21
|
+
# @param box [Box] contents or data for the operation
|
22
|
+
def perform(op, box)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns whether the Actor should have it's own thread or not. In almost
|
26
|
+
# all cases the Actor should have it's own thread. The exception is when the
|
27
|
+
# action is trivial such as a relay.
|
28
|
+
# @return [Boolean] indicator of whether a new thread should be created.
|
29
|
+
def with_own_thread()
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return array of Specs.
|
34
|
+
# @return [Array] Array of Specs.
|
35
|
+
def inputs()
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return array of Specs.
|
40
|
+
# @return [Array] Array of Specs.
|
41
|
+
def outputs()
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return any options that should be displayed as part of a Task.describe().
|
46
|
+
# @return [Hash] Hash of options with String keys.
|
47
|
+
def options()
|
48
|
+
{}
|
49
|
+
end
|
50
|
+
|
51
|
+
class Spec
|
52
|
+
attr_reader :op
|
53
|
+
attr_reader :type
|
54
|
+
|
55
|
+
def initialize(op, type)
|
56
|
+
if op.nil? || op.is_a?(Symbol)
|
57
|
+
@op = op
|
58
|
+
else
|
59
|
+
@op = op.to_sym
|
60
|
+
end
|
61
|
+
@type = type
|
62
|
+
end
|
63
|
+
|
64
|
+
alias dest op
|
65
|
+
|
66
|
+
def to_s()
|
67
|
+
"Spec{op: #{@op}, type: #{@type}}"
|
68
|
+
end
|
69
|
+
alias inspect to_s
|
70
|
+
|
71
|
+
end # Spec
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end # OFlow
|
76
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
module OFlow
|
3
|
+
module Actors
|
4
|
+
|
5
|
+
# The default error handler.
|
6
|
+
class ErrorHandler < Actor
|
7
|
+
|
8
|
+
def initialize(task, options={})
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
# Open the box, form a reasonable message, then log that message.
|
13
|
+
# @param op [Symbol] ignores
|
14
|
+
# @param box [Box] data associated with the error
|
15
|
+
def perform(op, box)
|
16
|
+
contents = box.contents
|
17
|
+
return task.error(contents.to_s) unless contents.is_a?(Array)
|
18
|
+
e, where = contents
|
19
|
+
task.error(e.to_s) unless e.is_a?(Exception)
|
20
|
+
msg = ["#{e.class}: #{e.message}"]
|
21
|
+
e.backtrace.each { |line| msg << (' ' + line) }
|
22
|
+
task.log_msg(:error, msg.join("\n"), where)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Handle error immediately.
|
26
|
+
def with_own_thread()
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
end # ErrorHandler
|
31
|
+
end # Actors
|
32
|
+
end # OFlow
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module OFlow
|
3
|
+
module Actors
|
4
|
+
# Does nothing but can be used to terminate a Link to assure all output from
|
5
|
+
# a Task terminate somewhere.
|
6
|
+
class Ignore < Actor
|
7
|
+
|
8
|
+
def initialize(task, options)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform(op, box)
|
13
|
+
# ignore
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_own_thread()
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
end # Ignore
|
21
|
+
end # Actors
|
22
|
+
end # OFlow
|
@@ -0,0 +1,175 @@
|
|
1
|
+
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module OFlow
|
5
|
+
module Actors
|
6
|
+
|
7
|
+
# An asynchronous logger build on top of the Actor class. It is able to log
|
8
|
+
# messages as well as forward calls to a Task.
|
9
|
+
class Log < Actor
|
10
|
+
|
11
|
+
SEVERITY_MAP = {
|
12
|
+
:fatal => Logger::Severity::FATAL,
|
13
|
+
:error => Logger::Severity::ERROR,
|
14
|
+
:warn => Logger::Severity::WARN,
|
15
|
+
:info => Logger::Severity::INFO,
|
16
|
+
:debug => Logger::Severity::DEBUG,
|
17
|
+
}
|
18
|
+
def initialize(task, options={})
|
19
|
+
@logger = nil
|
20
|
+
@formatter = nil
|
21
|
+
@name = nil
|
22
|
+
super
|
23
|
+
set_options(options)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the current severity level.
|
27
|
+
# @return [Fixnum] Logger severity level
|
28
|
+
def severity()
|
29
|
+
@logger.level
|
30
|
+
end
|
31
|
+
alias :level :severity
|
32
|
+
|
33
|
+
# Returns the current formatter.
|
34
|
+
# @return [Logger::Formatter] current formatter
|
35
|
+
def formatter()
|
36
|
+
@formatter
|
37
|
+
end
|
38
|
+
|
39
|
+
# Writes a log entry. op is the severity. The box contents is expected to be
|
40
|
+
# an Array with the first element as the full task name of the logging task
|
41
|
+
# and the second argument being the message to log
|
42
|
+
def perform(op, box)
|
43
|
+
op = op.to_sym unless op.nil?
|
44
|
+
a = box.contents
|
45
|
+
case op
|
46
|
+
when :severity
|
47
|
+
self.severity = a
|
48
|
+
when :formatter
|
49
|
+
# TBD
|
50
|
+
when :file, :filename
|
51
|
+
# TBD
|
52
|
+
# self.set_filename(filename, shift_age=7, shift_size=1048576)
|
53
|
+
else
|
54
|
+
level = SEVERITY_MAP.fetch(op, Logger::Severity::UNKNOWN)
|
55
|
+
if a.is_a?(Array)
|
56
|
+
log(level, a[0], a[1])
|
57
|
+
else
|
58
|
+
log(level, a.to_s, '')
|
59
|
+
end
|
60
|
+
# Forward to the next if there is a generic (nil) link.
|
61
|
+
task.ship(nil, box) if task.find_link(nil)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Sets the logger, severity, and formatter if provided.
|
68
|
+
# @param [Hash] options options to be used for initialization
|
69
|
+
# @option options [String] :filename filename to write to
|
70
|
+
# @option options [Fixnum] :max_file_size maximum file size
|
71
|
+
# @option options [Fixnum] :max_file_count maximum number of log file
|
72
|
+
# @option options [IO] :stream IO stream
|
73
|
+
# @option options [String|Fixnum] :severity initial setting for severity
|
74
|
+
# @option options [Proc] :formatter initial setting for the formatter procedure
|
75
|
+
def set_options(options)
|
76
|
+
if !(filename = options[:filename]).nil?
|
77
|
+
max_file_size = options.fetch(:max_file_size, options.fetch(:shift_size, 1048576))
|
78
|
+
max_file_count = options.fetch(:max_file_count, options.fetch(:shift_age, 7))
|
79
|
+
@logger = Logger.new(filename, max_file_count, max_file_size)
|
80
|
+
elsif !(stream = options[:stream]).nil?
|
81
|
+
@logger = Logger.new(stream)
|
82
|
+
else
|
83
|
+
@logger = Logger.new(STDOUT)
|
84
|
+
end
|
85
|
+
@logger.level = options.fetch(:severity, Env.log_level)
|
86
|
+
@formatter = options.fetch(:formatter, nil)
|
87
|
+
@logger.formatter = proc { |s,t,p,m| m }
|
88
|
+
@name = 'Logger' if @name.nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Writes a message if the severity is high enough. This method is
|
92
|
+
# executed asynchronously.
|
93
|
+
# @param level [Fixnum] one of the Logger levels
|
94
|
+
# @param message [String] string to log
|
95
|
+
# @param tid [Fixnum|String] Task id of the Task generating the message
|
96
|
+
def log(level, message, tid)
|
97
|
+
now = Time.now
|
98
|
+
ss = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'][level]
|
99
|
+
ss = '' if ss.nil?
|
100
|
+
if @formatter.nil?
|
101
|
+
msg = "#{ss[0]}, [#{now.strftime('%Y-%m-%dT%H:%M:%S.%6N')} #{tid}] #{ss} -- #{message}\n"
|
102
|
+
else
|
103
|
+
msg = @formatter.call(ss, now, tid, message)
|
104
|
+
end
|
105
|
+
@logger.add(level, msg)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Sets the logger to use the stream specified. This method is executed
|
109
|
+
# asynchronously.
|
110
|
+
# @param [IO] stream stream to write log messages to
|
111
|
+
def stream=(stream)
|
112
|
+
logger = Logger.new(stream)
|
113
|
+
logger.level = @logger.level
|
114
|
+
logger.formatter = @logger.formatter
|
115
|
+
@logger = logger
|
116
|
+
end
|
117
|
+
|
118
|
+
# Creates a new Logger to write log messages to using the parameters
|
119
|
+
# specified. This method is executed asynchronously.
|
120
|
+
# @param filename [String] filename of active log file
|
121
|
+
# @param shift_age [Fixmun] maximum number of archive files to save
|
122
|
+
# @param shift_size [Fixmun] maximum file size
|
123
|
+
def set_filename(filename, shift_age=7, shift_size=1048576)
|
124
|
+
logger = Logger.new(filename, shift_age, shift_size)
|
125
|
+
logger.level = @logger.level
|
126
|
+
logger.formatter = @logger.formatter
|
127
|
+
@logger = logger
|
128
|
+
end
|
129
|
+
|
130
|
+
# Replace the logger with a new Logger Object. This method is executed
|
131
|
+
# asynchronously.
|
132
|
+
# @param logger [Logger] replacement logger
|
133
|
+
def logger=(logger)
|
134
|
+
@logger = logger
|
135
|
+
end
|
136
|
+
|
137
|
+
# Sets the severity level of the logger. This method is executed
|
138
|
+
# asynchronously.
|
139
|
+
# @param level [String|Fixnum] value to set the severity to
|
140
|
+
def severity=(level)
|
141
|
+
if level.is_a?(String)
|
142
|
+
sev = {
|
143
|
+
'FATAL' => Logger::Severity::FATAL,
|
144
|
+
'ERROR' => Logger::Severity::ERROR,
|
145
|
+
'WARN' => Logger::Severity::WARN,
|
146
|
+
'INFO' => Logger::Severity::INFO,
|
147
|
+
'DEBUG' => Logger::Severity::DEBUG,
|
148
|
+
'4' => Logger::Severity::FATAL,
|
149
|
+
'3' => Logger::Severity::ERROR,
|
150
|
+
'2' => Logger::Severity::WARN,
|
151
|
+
'1' => Logger::Severity::INFO,
|
152
|
+
'0' => Logger::Severity::DEBUG
|
153
|
+
}[level.upcase()]
|
154
|
+
raise "#{level} is not a severity" if sev.nil?
|
155
|
+
level = sev
|
156
|
+
elsif level.is_a?(Symbol)
|
157
|
+
sev = SEVERITY_MAP[level]
|
158
|
+
raise "#{level} is not a severity" if sev.nil?
|
159
|
+
level = sev
|
160
|
+
elsif !level.is_a?(Fixnum) || level < Logger::Severity::DEBUG || Logger::Severity::FATAL < level
|
161
|
+
raise "#{level} is not a severity"
|
162
|
+
end
|
163
|
+
@logger.level = level
|
164
|
+
end
|
165
|
+
|
166
|
+
# Sets the formatter procedure of the logger. This method is executed
|
167
|
+
# asynchronously.
|
168
|
+
# @param proc [Proc] value to set the formatter to
|
169
|
+
def formatter=(proc)
|
170
|
+
@formatter = proc
|
171
|
+
end
|
172
|
+
|
173
|
+
end # Log
|
174
|
+
end # Actors
|
175
|
+
end # OFlow
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module OFlow
|
3
|
+
module Actors
|
4
|
+
|
5
|
+
# Relays a shipment to another location. This is useful for creating aliases
|
6
|
+
# for a Task.
|
7
|
+
class Relay < Actor
|
8
|
+
|
9
|
+
def initialize(task, options)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def perform(op, box)
|
14
|
+
task.ship(op, box)
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_own_thread()
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
end # Relay
|
22
|
+
end # Actors
|
23
|
+
end # OFlow
|
@@ -0,0 +1,126 @@
|
|
1
|
+
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module OFlow
|
5
|
+
module Actors
|
6
|
+
|
7
|
+
class Timer < Actor
|
8
|
+
|
9
|
+
MAX_SLEEP = 1.0
|
10
|
+
|
11
|
+
# When to trigger the first event. nil means start now.
|
12
|
+
attr_reader :start
|
13
|
+
# The stop time. If nil then there is not stopping unless the repeat limit
|
14
|
+
# kicks in.
|
15
|
+
attr_reader :stop
|
16
|
+
# How long to wait between each trigger. nil indicates as fast as possible,
|
17
|
+
attr_reader :period
|
18
|
+
# How many time to repeat before stopping. nil mean go forever.
|
19
|
+
attr_reader :repeat
|
20
|
+
# Label for the Tracker is used and for trigger content.
|
21
|
+
attr_reader :label
|
22
|
+
# The number of time the timer has fired or shipped.
|
23
|
+
attr_reader :count
|
24
|
+
# Boolean flag indicating a tracker should be added to the trigger content
|
25
|
+
# if true.
|
26
|
+
attr_reader :with_tracker
|
27
|
+
# Time of next or pending trigger.
|
28
|
+
attr_reader :pending
|
29
|
+
|
30
|
+
def initialize(task, options={})
|
31
|
+
@count = 0
|
32
|
+
@pending = nil
|
33
|
+
set_options(options)
|
34
|
+
@start = Time.now() if @start.nil?
|
35
|
+
@pending = @start
|
36
|
+
super
|
37
|
+
task.receive(:init, nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
# The loop in the Task containing this Actor is the thread used for the
|
41
|
+
# timer. Mostly the perform() method sleeps but it will be woken when a
|
42
|
+
# new request is placed on the Task queue so it exits if there is a
|
43
|
+
# request on the queue even if it has not triggered a ship() know that it
|
44
|
+
# will be re-entered.
|
45
|
+
def perform(op, box)
|
46
|
+
op = op.to_sym unless op.nil?
|
47
|
+
case op
|
48
|
+
when :stop
|
49
|
+
# TBD if no arg (or earlier than now) then stop now else set to new stop time
|
50
|
+
when :start
|
51
|
+
# TBD if stopped then start if no arg, if arg then set start time
|
52
|
+
when :period
|
53
|
+
# TBD
|
54
|
+
when :repeat
|
55
|
+
# TBD
|
56
|
+
when :label
|
57
|
+
# TBD
|
58
|
+
when :with_tracker
|
59
|
+
# TBD
|
60
|
+
end
|
61
|
+
while true
|
62
|
+
now = Time.now()
|
63
|
+
|
64
|
+
# If past stop time then it is done. A future change in options can
|
65
|
+
# restart the timer.
|
66
|
+
return if !@stop.nil? && @stop < now
|
67
|
+
# Has repeat number been exceeded?
|
68
|
+
return if !@repeat.nil? && @repeat <= @count
|
69
|
+
# If there is nothing pending the timer has completed.
|
70
|
+
return if @pending.nil?
|
71
|
+
# If the Task is blocked or shutting down.
|
72
|
+
return if Task::CLOSING == task.state || Task::BLOCKED == task.state
|
73
|
+
|
74
|
+
if @pending <= now
|
75
|
+
# Skip if stopped but do not increment counter.
|
76
|
+
unless Task::STOPPED == task.state
|
77
|
+
@count += 1
|
78
|
+
now = Time.now()
|
79
|
+
tracker = @with_tracker ? Tracker.new(@label) : nil
|
80
|
+
box = Box.new([@label, @count, now.utc()], tracker)
|
81
|
+
task.links.each_key do |key|
|
82
|
+
begin
|
83
|
+
task.ship(key, box)
|
84
|
+
rescue BlockedError => e
|
85
|
+
task.warn("Failed to ship timer #{box.contents} to #{key}. Task blocked.")
|
86
|
+
rescue BusyError => e
|
87
|
+
task.warn("Failed to ship timer #{box.contents} to #{key}. Task busy.")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
if @period.nil? || @period == 0
|
92
|
+
@pending = now
|
93
|
+
else
|
94
|
+
@pending += period
|
95
|
+
end
|
96
|
+
end
|
97
|
+
# If there is a request waiting then return so it can be handled. It
|
98
|
+
# will come back here to allow more timer processing.
|
99
|
+
return if 0 < task.queue_count()
|
100
|
+
|
101
|
+
if Task::STOPPED == task.state
|
102
|
+
sleep(0.1)
|
103
|
+
else
|
104
|
+
now = Time.now()
|
105
|
+
if now < @pending
|
106
|
+
wait_time = @pending - now
|
107
|
+
wait_time = MAX_SLEEP if MAX_SLEEP < wait_time
|
108
|
+
sleep(wait_time)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def set_options(options)
|
115
|
+
@start = options[:start]
|
116
|
+
@stop = options[:stop]
|
117
|
+
@period = options[:period]
|
118
|
+
@repeat = options[:repeat]
|
119
|
+
@label = options[:label]
|
120
|
+
@with_tracker = options[:with_tracker]
|
121
|
+
# TBD check values for type and range
|
122
|
+
end
|
123
|
+
|
124
|
+
end # Timer
|
125
|
+
end # Actors
|
126
|
+
end # OFlow
|