oflow 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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'