oflow 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/ohler55/oflow.png?branch=master)](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
|