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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +182 -0
  4. data/lib/oflow/actor.rb +76 -0
  5. data/lib/oflow/actors/errorhandler.rb +32 -0
  6. data/lib/oflow/actors/ignore.rb +22 -0
  7. data/lib/oflow/actors/log.rb +175 -0
  8. data/lib/oflow/actors/relay.rb +23 -0
  9. data/lib/oflow/actors/timer.rb +126 -0
  10. data/lib/oflow/actors.rb +11 -0
  11. data/lib/oflow/box.rb +195 -0
  12. data/lib/oflow/env.rb +52 -0
  13. data/lib/oflow/errors.rb +74 -0
  14. data/lib/oflow/flow.rb +75 -0
  15. data/lib/oflow/haserrorhandler.rb +48 -0
  16. data/lib/oflow/haslinks.rb +64 -0
  17. data/lib/oflow/haslog.rb +72 -0
  18. data/lib/oflow/hasname.rb +31 -0
  19. data/lib/oflow/hastasks.rb +209 -0
  20. data/lib/oflow/inspector.rb +501 -0
  21. data/lib/oflow/link.rb +43 -0
  22. data/lib/oflow/pattern.rb +8 -0
  23. data/lib/oflow/stamp.rb +39 -0
  24. data/lib/oflow/task.rb +415 -0
  25. data/lib/oflow/test/action.rb +21 -0
  26. data/lib/oflow/test/actorwrap.rb +62 -0
  27. data/lib/oflow/test.rb +8 -0
  28. data/lib/oflow/tracker.rb +109 -0
  29. data/lib/oflow/version.rb +5 -0
  30. data/lib/oflow.rb +23 -0
  31. data/test/actors/log_test.rb +57 -0
  32. data/test/actors/timer_test.rb +56 -0
  33. data/test/actorwrap_test.rb +48 -0
  34. data/test/all_tests.rb +27 -0
  35. data/test/box_test.rb +127 -0
  36. data/test/collector.rb +23 -0
  37. data/test/flow_basic_test.rb +93 -0
  38. data/test/flow_cfg_error_test.rb +94 -0
  39. data/test/flow_log_test.rb +87 -0
  40. data/test/flow_nest_test.rb +215 -0
  41. data/test/flow_rescue_test.rb +133 -0
  42. data/test/flow_tracker_test.rb +82 -0
  43. data/test/stutter.rb +21 -0
  44. data/test/task_test.rb +98 -0
  45. data/test/tracker_test.rb +59 -0
  46. 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.
@@ -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
@@ -0,0 +1,11 @@
1
+
2
+ module OFlow
3
+ module Actors
4
+ end
5
+ end
6
+
7
+ require 'oflow/actors/ignore'
8
+ require 'oflow/actors/relay'
9
+ require 'oflow/actors/log'
10
+ require 'oflow/actors/errorhandler'
11
+ require 'oflow/actors/timer'