andromeda 0.1 → 0.1.2

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.
@@ -0,0 +1,132 @@
1
+ module Andromeda
2
+
3
+ # A spot is a reachable destination to which data may be sent for processing.
4
+ # It encapsulates addressing the processing logic of a Plan into a separate,
5
+ # immutable Object that may be passed around freely.
6
+ #
7
+ # It is somewhat similiar/a mixture of notions such as actor address,
8
+ # RPC endpoint, and stack frame in other frameworks
9
+ #
10
+ # You MUST not inherit from this class
11
+ #
12
+ class Spot < Impl::ConnectorBase
13
+ include Impl::To_S
14
+
15
+ # @return [Plan] Plan to which this spot will deliver data events
16
+ attr_reader :plan
17
+
18
+ # @return [Symbol] Name of spot attribute in plan that corresponds to this spot
19
+ attr_reader :name
20
+
21
+ # @return [Plan, nil] Plan of calling spot if any, nil otherwise
22
+ attr_reader :here
23
+
24
+ # @return [Symbol, nil] Spot's destination name, or nil for plan.dest, returned by >>
25
+ def dest_name ; @dest end
26
+
27
+ # @param [Plan] plan Plan to which this spot will deliver data events
28
+ # @param [Symbol] name Name of spot attribute in plan that corresponds to this spot
29
+ # @param [Plan, nil] here Plan of calling Spot if any, nil otherwise
30
+ # @param [Symbol, nil] dest destination name use to obtain return value for >>
31
+ def initialize(plan, name, here, dest = nil)
32
+ raise ArgumentError, "#{plan} is not a Plan" unless plan.is_a? Plan
33
+ raise ArgumentError, "#{name} is not a Symbol" unless name.is_a? Symbol
34
+ unless plan.meth_spot_name?(name)
35
+ raise ArgumentError, "#{name} is not a known method spot name of #{plan}"
36
+ end
37
+ unless dest.nil? || dest.is_a?(Symbol)
38
+ raise ArgumentError, "#{dest} is neither nil nor a Symbol"
39
+ end
40
+ if !here || here.is_a?(Plan)
41
+ @plan = plan
42
+ @name = name
43
+ @here = here
44
+ @dest = dest
45
+ else
46
+ raise ArgumentError, "#{here} is neither nil nor a Plan"
47
+ end
48
+ end
49
+
50
+ def cloneable? ; true end
51
+ def clone_to_copy? ; false end
52
+ def identical_copy ; self end
53
+
54
+ # Spots compare attribute-wise and do not accept subclasses
55
+ #
56
+ # @retrun [TrueClass, FalseClass] self == other
57
+ def ==(other)
58
+ return true if self.equal? other
59
+ return false unless other.class.equal? Spot
60
+ name.eq(other.name) && plan.eq(other.plan) && here.eq(other.here) && dest.eq(other.dest)
61
+ end
62
+
63
+ def hash ; plan.hash ^ name.hash ^ here.hash ^ dest.hash end
64
+
65
+ def to_short_s
66
+ dest_ = dest_name
67
+ dest_ = if dest_ then " dest=:#{dest_name}" else '' end
68
+ here_ = here
69
+ if here_
70
+ then " plan=#{plan} name=:#{name} here=#{here_}#{dest_}"
71
+ else " plan=#{plan} name=:#{name}#{dest_}" end
72
+ end
73
+ alias_method :inspect, :to_s
74
+
75
+ # Post data with the associated tags_in to this's spot's plan's method spot with name name
76
+ # and hint that the caller requested the spot activation to be executed on track tack
77
+ #
78
+ # @param [Track] track requested target track
79
+ # @param [Any] data any data event
80
+ # @param [Hash] tags to be passed along
81
+ #
82
+ # @return [self]
83
+ def post_to(track, data, tags_in = {})
84
+ tags_in = (here.tags.identical_copy.update(tags_in) rescue tags_in) if here
85
+ plan.post_data self, track, data, tags_in
86
+ self
87
+ end
88
+
89
+ # @return [Spot] a fresh copy of self for which dest_name == spot_name holds
90
+ def via(spot_name)
91
+ raise ArgumentError unless spot_name.nil? || spot_name.is_a?(Symbol)
92
+ Spot.new plan, name, here, spot_name
93
+ end
94
+
95
+ # Call the spot method associated with this spot with the provided key and val
96
+ #
97
+ # Precondition is that here == plan, i.e. the caller already executes in the scope
98
+ # of this spot's plan
99
+ #
100
+ # @return [Any] plan.call_local name, key, val
101
+ def call_local(key, val)
102
+ plan.call_local name, key, val
103
+ end
104
+
105
+ # @return [self] for compatibility with plan's API
106
+ def entry ; self end
107
+
108
+ # @return [Spot] plan.public_spot(dest_name) if that exists, plan.dest otherwise
109
+ def dest
110
+ if dest_name
111
+ then plan.public_spot(dest_name)
112
+ else plan.dest end
113
+ end
114
+
115
+ def >>(target)
116
+ return (plan >> target) unless dest_name
117
+ unless plan.attr_spot_name?(dest_name)
118
+ raise ArgumentError, "#{dest_name} is not an attr_spot_name"
119
+ end
120
+ plan.send :"#{dest_name}=", target.entry
121
+ plan.public_spot(dest_name)
122
+ end
123
+
124
+ # @return [Spot] this spot or a modified copy of it such that here == new_calling_plan holds
125
+ def intern(new_calling_plan)
126
+ if here.equal? new_calling_plan
127
+ then self
128
+ else Spot.new plan, name, new_calling_plan, dest_name end
129
+ end
130
+ end
131
+
132
+ end
@@ -0,0 +1,41 @@
1
+ module Andromeda
2
+
3
+ class Plan
4
+ def pool ; guide.pool_track.pool rescue nil end
5
+ end
6
+
7
+ module Guides
8
+
9
+ def self.default ; DefaultGuide.instance end
10
+ def self.single ; SinglePoolGuide.new end
11
+ def self.shared_pool ; SharedPoolGuide end
12
+ def self.pool ; SharedPoolGuide.new end
13
+ def self.local ; LocalGuide end
14
+ def self.shared_single ; SharedSinglePoolGuide end
15
+
16
+ class DefaultGuide < SimpleDelegator
17
+ include Singleton
18
+
19
+ def initialize
20
+ super LocalGuide.instance
21
+ end
22
+
23
+ def instance=(new_instance)
24
+ if new_instance.is_a?(Class) && new_instance.include?(Singleton)
25
+ new_instance = new_instance.instance
26
+ end
27
+ instance.__setobj__ new_instance
28
+ end
29
+ end
30
+
31
+ end
32
+
33
+ class DefaultLogger < SimpleDelegator
34
+ include Singleton
35
+
36
+ def initialize
37
+ super Logger.new(STDERR)
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,68 @@
1
+ module Andromeda
2
+
3
+ module Sync
4
+
5
+ # Comparable to a join in join calculus, called Sync here to reserve the name Join
6
+ # for map_reduce.rb
7
+ #
8
+ class Sync < Plan
9
+
10
+ def initialize(config = {})
11
+ super config
12
+ @mutex = Mutex.new
13
+ @cv = ConditionVariable.new
14
+ # box value to keep ref after clone
15
+ @state = [ state_init ]
16
+ end
17
+
18
+ protected
19
+
20
+ def state_init ; {} end
21
+
22
+ def state_ready?(state)
23
+ raise RuntimeException, 'Not implemented'
24
+ end
25
+
26
+ def state_empty?(state, k, chunk) ; state[k].nil? end
27
+ def state_updated(state, k, chunk) ; state[k] = chunk; state end
28
+
29
+ def state_chunk_key(name, state) ; chunk_key(name, state) end
30
+
31
+ def run_chunk(pool, scope, name, meth, k, chunk, &thunk)
32
+ @mutex.synchronize do
33
+ state = @state[0]
34
+ while true
35
+ if state_empty?(state, k, chunk)
36
+ @state[0] = (state = state_updated(state, k, chunk))
37
+ if state_ready?(state)
38
+ @state[0] = state_init
39
+ cv.signal
40
+ new_k = state_chunk_key(name, state)
41
+ return super pool, scope, name, meth, new_k, state, &thunk
42
+ else
43
+ cv.signal
44
+ return self
45
+ end
46
+ else
47
+ cv.wait @mutex
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # Passes all input and waits for the associated scope to return to the start value
55
+ # (will only work if there is no concurrent modification to the associated scope)
56
+ class ScopeWaiter < Plan
57
+
58
+ def on_enter(k, v)
59
+ scope_ = current_scope
60
+ value_ = scope_.value
61
+ super k, v
62
+ scope_.wait_until_eq value_
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -1,3 +1,3 @@
1
1
  module Andromeda
2
- VERSION = '0.1'
2
+ VERSION = '0.1.2'
3
3
  end
@@ -0,0 +1,28 @@
1
+ require 'yard'
2
+
3
+ class MethSpotHandler < YARD::Handlers::Ruby::AttributeHandler
4
+ handles method_call(:meth_spot)
5
+ namespace_only
6
+
7
+ def process
8
+ push_state(:scope => :class) { super }
9
+ end
10
+ end
11
+
12
+ class AttrSpotHandler < YARD::Handlers::Ruby::AttributeHandler
13
+ handles method_call(:attr_spot)
14
+ namespace_only
15
+
16
+ def process
17
+ push_state(:scope => :class) { super }
18
+ end
19
+ end
20
+
21
+ class SignalSpotHandler < YARD::Handlers::Ruby::AttributeHandler
22
+ handles method_call(:signal_spot)
23
+ namespace_only
24
+
25
+ def process
26
+ push_state(:scope => :class) { super }
27
+ end
28
+ end
metadata CHANGED
@@ -1,18 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: andromeda
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
5
4
  prerelease:
5
+ version: 0.1.2
6
6
  platform: ruby
7
7
  authors:
8
8
  - Stefan Plantikow
9
9
  autorequire:
10
10
  bindir: script
11
11
  cert_chain: []
12
- date: 2012-04-24 00:00:00.000000000 Z
12
+ date: 2012-05-01 00:00:00.000000000 Z
13
13
  dependencies: []
14
- description: Ultra light weight multicore stream processing framework based on a dataflow
15
- DSL
14
+ description: Andromeda is a light weight framework for complex event processing on
15
+ multicore architectures. Andromeda users construct networks of plans that are interconnected
16
+ via endpoint spots, describe how plans are scheduled onto threads, and process data
17
+ by feeding data events to the resulting structure.
16
18
  email: stefanp@moviepilot.com
17
19
  executables: []
18
20
  extensions: []
@@ -21,22 +23,38 @@ files:
21
23
  - .gitignore
22
24
  - .rvmrc
23
25
  - AUTHORS
26
+ - CHANGELOG.md
24
27
  - Gemfile
25
28
  - Gemfile.lock
26
29
  - LICENSE.txt
27
30
  - README.md
31
+ - ROADMAP.md
28
32
  - Rakefile
29
33
  - andromeda.gemspec
30
34
  - lib/andromeda.rb
31
- - lib/andromeda/andromeda.rb
32
- - lib/andromeda/commando.rb
33
- - lib/andromeda/helpers.rb
35
+ - lib/andromeda/atom.rb
36
+ - lib/andromeda/cmd.rb
37
+ - lib/andromeda/common.rb
38
+ - lib/andromeda/copy_clone.rb
39
+ - lib/andromeda/error.rb
40
+ - lib/andromeda/guide.rb
41
+ - lib/andromeda/guide_track.rb
34
42
  - lib/andromeda/id.rb
35
- - lib/andromeda/join.rb
36
- - lib/andromeda/pools.rb
37
- - lib/andromeda/scope.rb
43
+ - lib/andromeda/impl/atom.rb
44
+ - lib/andromeda/impl/class_attr.rb
45
+ - lib/andromeda/impl/proto_plan.rb
46
+ - lib/andromeda/impl/to_s.rb
47
+ - lib/andromeda/impl/xor_id.rb
48
+ - lib/andromeda/kit.rb
49
+ - lib/andromeda/map_reduce.rb
50
+ - lib/andromeda/plan.rb
51
+ - lib/andromeda/pool_guide.rb
52
+ - lib/andromeda/spot.rb
53
+ - lib/andromeda/sugar.rb
54
+ - lib/andromeda/sync.rb
38
55
  - lib/andromeda/version.rb
39
56
  - spec/spec_helper.rb
57
+ - yard_extensions/andromeda.rb
40
58
  homepage: https://github.com/moviepilot/andromeda
41
59
  licenses:
42
60
  - PUBLIC DOMAIN WITHOUT ANY WARRANTY
@@ -58,11 +76,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
58
76
  version: '0'
59
77
  requirements: []
60
78
  rubyforge_project: andromeda
61
- rubygems_version: 1.8.17
79
+ rubygems_version: 1.8.12
62
80
  signing_key:
63
81
  specification_version: 3
64
- summary: Ultra light weight multicore stream processing framework based on a dataflow
65
- DSL
82
+ summary: light weght framework for complex event processing based on a dataflow DSL
66
83
  test_files:
67
84
  - spec/spec_helper.rb
68
85
  has_rdoc:
@@ -1,225 +0,0 @@
1
- # TODO
2
- # - Turn into separate gem
3
- # - Write Tests
4
- # - Write fusor for synchronization (extra class that blocks until ready to submit)
5
- # - Write docs, add yard support for documenting bases, attrs, etc.
6
- # - Make nice slideshow and become very famous and rich. yay!
7
- module Andromeda
8
-
9
- module Internal
10
- class Transplanting
11
- attr_reader :orig
12
-
13
- def initialize(init_opts = nil)
14
- @opts = init_opts
15
- @orig = self
16
- end
17
-
18
- protected
19
-
20
- def transplant(new_opts = nil)
21
- return self if @opts == new_opts
22
- obj = self.clone
23
- obj.instance_variable_set '@opts', new_opts
24
- obj
25
- end
26
- end
27
- end
28
-
29
- class Dest < Internal::Transplanting
30
- attr_reader :base
31
- attr_reader :meth
32
-
33
- def initialize(base, meth, init_opts = nil)
34
- super init_opts
35
- raise ArgumentError, "'#{meth}' is not a symbol" unless meth.kind_of?(Symbol)
36
- raise ArgumentError, "'#{base}' is not a base" unless base.kind_of?(Base)
37
- raise NoMethodError, "'#{base}' does not respond to '#{meth}'" unless base.respond_to?(meth)
38
- @base = base
39
- @meth = meth
40
- end
41
-
42
- def <<(chunk, opts = {}) ; submit chunk, opts ; self end
43
- def submit(chunk, opts = {}) ; submit_to nil, chunk, opts end
44
- def submit_now(chunk, opts = {}) ; submit_to :local, chunk, opts end
45
-
46
- def submit_to(target_pool, chunk, opts = {})
47
- if @opts
48
- new_opts = @opts.clone
49
- opts.each { |k, v| new_opts[k] = v }
50
- else
51
- new_opts = opts
52
- end
53
- new_opts[:scope] ||= Scope.new
54
- new_opts[:mark] ||= Id.zero
55
- base.transplant(new_opts).process target_pool, new_opts[:scope], self.meth, chunk
56
- new_opts
57
- end
58
-
59
- def entry ; self end
60
- end
61
-
62
- class Base < Internal::Transplanting
63
- attr_reader :id
64
- attr_reader :opts
65
-
66
- attr_accessor :log
67
- attr_accessor :mark
68
- attr_accessor :emit
69
- attr_accessor :pool
70
-
71
- attr_reader :trace_enter
72
- attr_reader :trace_exit
73
-
74
- def initialize(config = {})
75
- super config[:init_opts]
76
- @id = Id.gen
77
- set_from_config init_from_config, config
78
- @trace_enter ||= init_trace_hash :enter
79
- @trace_exit ||= init_trace_hash :emit
80
- @pool ||= init_pool_config
81
- end
82
-
83
- def trace=(new_trace)
84
- raise ArgumentError, "'#{new_trace}' is not a Hash" unless new_trace.kind_of?(Hash)
85
- @trace = new_trace
86
- end
87
-
88
- def init_trace_hash(kind) ; {} end
89
- def init_pool_config ; nil end
90
- def init_from_config ; [:readers, :writers] end
91
-
92
- def log ; @log = Logger.new(STDERR) unless @log ; @log end
93
- def mark ; @mark = Id.zero unless @mark ; @mark end
94
-
95
- def on_enter(c)
96
- emit << c rescue nil
97
- end
98
-
99
- # @param [nil, :local, :spawn, :single, :fifo, :default, :global, #process, #process_base] pool_descr
100
- # If nil, uses self.pool as pool_descr and continues. If that is nil, too, defaults to :local.
101
- # If #process_base, uses target_pool.process_base(meth) as pool_descr and continues.
102
- # If pool_descr is :spawn, uses SpawnPool.default_pool
103
- # If pool_descr is :single, uses PoolSupport.new_single_pool
104
- # If pool_descr is :fifo, uses PoolSupport.new_fifo_pool
105
- # If pool_descr is :global, uses the globally shared PoolSupport.global_pool
106
- # If pool_descr is :default uses PoolSupport.new_default_pool
107
- # Finally, if #process, runs by calling #process. If :local, runs in current thread.
108
- # Otherwise, the behaviour is undefined.
109
- def process(pool_descr, scope, meth, chunk)
110
- this = self
111
- run target_pool(pool_descr, meth), scope, meth, chunk do
112
- begin
113
- enter_level = trace_enter[meth]
114
- exit_level = trace_exit[meth]
115
- trace :enter, enter_level, meth, chunk if enter_level
116
- send meth, chunk
117
- trace :exit, exit_level, meth, chunk if exit_level
118
- rescue Exception => e
119
- handle_exception meth, chunk, e
120
- ensure
121
- scope.leave if scope
122
- end
123
- end
124
- end
125
-
126
- def check_mark
127
- mark = @opts[:mark]
128
- raise RuntimeError, 'invalid mark' if mark && !mark.zero?
129
- end
130
-
131
- def intern(dest) ; dest.transplant(opts) end
132
-
133
- def dest(name)
134
- result = if self.respond_to?(name)
135
- then intern self.send(name)
136
- else Dest.new self, "on_#{name}".to_sym, opts end
137
- raise ArgumentError, "unknown or invalid dest: '#{name}'" unless result.kind_of?(Dest)
138
- result
139
- end
140
-
141
- def trace(kind, level, method, chunk)
142
- log_ = log
143
- log_.send level, "TRACE #{id.to_s} :#{kind} :#{method} chunk: '#{chunk}'" if log_
144
- end
145
-
146
- protected
147
-
148
- def set_from_config(what, config = {})
149
- init_readers = what.include? :readers
150
- init_writers = what.include? :writers
151
- config.each_pair do |k, v|
152
- k = k.to_sym rescue nil
153
- if init_writers
154
- writer = "#{k}=".to_sym rescue nil
155
- if writer && self.respond_to?(writer)
156
- then self.send writer, v
157
- else instance_variable_set "@#{k}", v if init_readers && self.respond_to?(k) end
158
- else
159
- instance_variable_set "@#{k}", v if init_readers && self.respond_to?(k)
160
- end
161
- end
162
- end
163
-
164
- def mark_opts
165
- if @mark && !@mark.zero?
166
- if @opts[:mark]
167
- then @opts[:mark] = @opts[:mark].xor(mark)
168
- else @opts[:mark] = mark end
169
- end
170
- end
171
-
172
- def run(pool, scope, meth, chunk, &thunk)
173
- scope.enter
174
- begin
175
- if pool && pool.respond_to?(:process)
176
- then pool.process(&thunk)
177
- else thunk.call end
178
- rescue Exception => e
179
- handle_exception meth, chunk, e
180
- scope.leave if scope
181
- end
182
- self
183
- end
184
-
185
- def target_pool(pool_descr, meth)
186
- pool_descr = pool unless pool_descr
187
- case pool_descr
188
- when nil then :local
189
- when :local then :local
190
- when :spawn then SpawnPool.default_pool
191
- when :global then PoolSupport.global_pool
192
- when :default then PoolSupport.new_default_pool
193
- when :single then PoolSupport.new_single_pool
194
- when :fifo then PoolSupport.new_fifo_pool
195
- else
196
- if pool_descr.respond_to(:process_base)
197
- then pool_descr.process_base(meth)
198
- else pool_descr end
199
- end
200
- end
201
-
202
- def handle_exception(meth, chunk, e)
203
- if log
204
- trace = ''
205
- e.backtrace.each { |s| trace << "\n #{s}" }
206
- log.error "Caught '#{e}' when processing chunk: '#{chunk}' via meth: '#{meth}' with backtrace: '#{trace}'"
207
- end
208
- end
209
-
210
- public
211
-
212
- def >>(dest) ; self.emit = dest.entry end
213
-
214
- def drop ; self.emit = nil end
215
-
216
- def entry ; dest(:enter) end
217
- alias_method :exit, :emit
218
-
219
- def <<(chunk, opts = {}) ; entry.<< chunk, opts end
220
- def submit(chunk, opts = {}) ; entry.submit chunk, opts end
221
- def submit_now(chunk, opts = {}) ; entry.submit_now chunk, opts end
222
- def submit_to(target_pool, chunk, opts = {}) ; entry.submit_to target_pool, chunk, opts end
223
- end
224
- end
225
-