bud 0.0.8 → 0.1.0.pre1

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.
data/lib/bud.rb CHANGED
@@ -2,37 +2,35 @@ require 'rubygems'
2
2
  require 'eventmachine'
3
3
  require 'msgpack'
4
4
  require 'socket'
5
- require 'superators'
5
+ require 'superators19'
6
6
  require 'thread'
7
-
8
- # Ruby2Ruby 1.3.1 is buggy (see issue #250)
9
- gem 'ruby2ruby', '< 1.3.1'
10
- require 'ruby2ruby'
7
+ require 'bud/errors'
11
8
 
12
9
  require 'bud/monkeypatch'
13
10
 
14
11
  require 'bud/aggs'
15
12
  require 'bud/bud_meta'
16
13
  require 'bud/collections'
17
- require 'bud/depanalysis'
18
- require 'bud/deploy/forkdeploy'
19
- require 'bud/deploy/threaddeploy'
20
- require 'bud/errors'
21
14
  require 'bud/joins'
22
15
  require 'bud/metrics'
23
- require 'bud/meta_algebra'
16
+ #require 'bud/meta_algebra'
24
17
  require 'bud/rtrace'
25
18
  require 'bud/server'
26
19
  require 'bud/state'
27
20
  require 'bud/storage/dbm'
28
- require 'bud/storage/tokyocabinet'
29
21
  require 'bud/storage/zookeeper'
30
- require 'bud/stratify'
31
22
  require 'bud/viz'
32
23
 
24
+ require 'bud/executor/elements.rb'
25
+ require 'bud/executor/group.rb'
26
+ require 'bud/executor/join.rb'
27
+
33
28
  ILLEGAL_INSTANCE_ID = -1
34
29
  SIGNAL_CHECK_PERIOD = 0.2
35
30
 
31
+ $BUD_DEBUG = ENV["BUD_DEBUG"].to_i > 0
32
+ $BUD_SAFE = ENV["BUD_SAFE"].to_i > 0
33
+
36
34
  $signal_lock = Mutex.new
37
35
  $got_shutdown_signal = false
38
36
  $signal_handler_setup = false
@@ -66,13 +64,13 @@ $bud_instances = {} # Map from instance id => Bud instance
66
64
  module Bud
67
65
  attr_reader :strata, :budtime, :inbound, :options, :meta_parser, :viz, :rtracer
68
66
  attr_reader :dsock
69
- attr_reader :builtin_tables, :tables
70
- attr_reader :channels, :tc_tables, :zk_tables, :dbm_tables, :sources, :sinks
71
- attr_reader :stratum_first_iter, :joinstate
72
- attr_reader :this_stratum, :this_rule, :rule_orig_src
67
+ attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :sources, :sinks, :app_tables
68
+ attr_reader :push_sources, :push_elems, :push_joins, :scanners, :merge_targets, :done_wiring
69
+ attr_reader :this_stratum, :this_rule, :rule_orig_src, :done_bootstrap, :done_wiring
70
+ attr_accessor :stratum_collection_map, :stratified_rules
71
+ attr_accessor :metrics, :periodics
72
+ attr_accessor :this_rule_context, :qualified_name
73
73
  attr_reader :running_async
74
- attr_accessor :stratum_collection_map, :rewritten_strata, :no_attr_rewrite_strata
75
- attr_accessor :metrics
76
74
 
77
75
  # options to the Bud runtime are passed in a hash, with the following keys
78
76
  # * network configuration
@@ -90,6 +88,7 @@ module Bud
90
88
  # * <tt>:trace</tt> if true, generate +budvis+ outputs
91
89
  # * <tt>:rtrace</tt> if true, generate +budplot+ outputs
92
90
  # * <tt>:dump_rewrite</tt> if true, dump results of internal rewriting of Bloom code to a file
91
+ # * <tt>:print_wiring</tt> if true, print the wiring diagram of the program to stdout
93
92
  # * <tt>:metrics</tt> if true, dumps a hash of internal performance metrics
94
93
  # * controlling execution
95
94
  # * <tt>:tag</tt> a name for this instance, suitable for display during tracing and visualization
@@ -105,17 +104,19 @@ module Bud
105
104
  # * storage configuration
106
105
  # * <tt>:dbm_dir</tt> filesystem directory to hold DBM-backed collections
107
106
  # * <tt>:dbm_truncate</tt> if true, DBM-backed collections are opened with +OTRUNC+
108
- # * <tt>:tc_dir</tt> filesystem directory to hold TokyoCabinet-backed collections
109
- # * <tt>:tc_truncate</tt> if true, TokyoCabinet-backed collections are opened with +OTRUNC+
110
- # * deployment
111
- # * <tt>:deploy</tt> enable deployment
112
- # * <tt>:deploy_child_opts</tt> option hash to pass to deployed instances
113
107
  def initialize(options={})
114
- @builtin_tables = {}
108
+ # capture the binding for a subsequent 'eval'. This ensures that local
109
+ # variable names introduced later in this method don't interfere with
110
+ # table names used in the eval block.
111
+ options[:dump_rewrite] ||= ENV["BUD_DUMP_REWRITE"].to_i > 0
112
+ options[:dump_ast] ||= ENV["BUD_DUMP_AST"].to_i > 0
113
+ options[:print_wiring] ||= ENV["BUD_PRINT_WIRING"].to_i > 0
114
+ @qualified_name = ""
115
115
  @tables = {}
116
- @rewritten_strata = []
116
+ @table_meta = []
117
+ @stratified_rules = []
117
118
  @channels = {}
118
- @tc_tables = {}
119
+ @push_elems = {}
119
120
  @dbm_tables = {}
120
121
  @zk_tables = {}
121
122
  @callbacks = {}
@@ -124,17 +125,23 @@ module Bud
124
125
  @shutdown_callback_id = 0
125
126
  @post_shutdown_callbacks = []
126
127
  @timers = []
128
+ @app_tables = []
127
129
  @inside_tick = false
128
130
  @tick_clock_time = nil
129
131
  @budtime = 0
130
132
  @inbound = {}
131
133
  @done_bootstrap = false
132
- @joinstate = {} # joins are stateful, their state needs to be kept inside the Bud instance
134
+ @done_wiring = false
133
135
  @instance_id = ILLEGAL_INSTANCE_ID # Assigned when we start running
134
136
  @sources = {}
135
137
  @sinks = {}
136
138
  @metrics = {}
137
139
  @endtime = nil
140
+ @this_stratum = 0
141
+ @push_sorted_elems = nil
142
+
143
+ # XXX This variable is unused in the Push executor
144
+ @stratum_first_iter = false
138
145
  @running_async = false
139
146
  @bud_started = false
140
147
 
@@ -147,104 +154,133 @@ module Bud
147
154
  # NB: If using an ephemeral port (specified by port = 0), the actual port
148
155
  # number won't be known until we start EM
149
156
 
150
- relatives = self.class.modules + [self.class]
151
- relatives.each do |r|
152
- Bud.rewrite_local_methods(r)
153
- end
157
+ builtin_state
154
158
 
155
- @declarations = ModuleRewriter.get_rule_defs(self.class)
159
+ resolve_imports
156
160
 
157
- init_state
161
+ # Invoke all the user-defined state blocks and initialize builtin state.
162
+ call_state_methods
163
+
164
+ @declarations = self.class.instance_methods.select {|m| m =~ /^__bloom__.+$/}.map {|m| m.to_s}
158
165
 
159
166
  @viz = VizOnline.new(self) if @options[:trace]
160
167
  @rtracer = RTrace.new(self) if @options[:rtrace]
161
168
 
162
- # Get dependency info and determine stratification order.
163
- unless self.class <= Stratification or self.class <= DepAnalysis
164
- do_rewrite
165
- end
166
-
167
- # Load the rules as a closure. Each element of @strata is an array of
168
- # lambdas, one for each rewritten rule in that strata. Note that legacy Bud
169
- # code (with user-specified stratification) assumes that @strata is a simple
170
- # array, so we need to convert it before loading the rewritten strata.
171
- @strata = []
172
- @rule_src = []
173
- @rule_orig_src = []
174
- declaration
175
- @strata.each_with_index do |s,i|
176
- raise Bud::Error if s.class <= Array
177
- @strata[i] = [s]
178
- # Don't try to record source text for old-style rule blocks
179
- @rule_src[i] = [""]
169
+ do_rewrite
170
+ if toplevel == self
171
+ # initialize per-stratum state
172
+ num_strata = @stratified_rules.length
173
+ @scanners = num_strata.times.map{{}}
174
+ @push_sources = num_strata.times.map{{}}
175
+ @push_joins = num_strata.times.map{[]}
176
+ @merge_targets = num_strata.times.map{{}}
180
177
  end
178
+ end
181
179
 
182
- @rewritten_strata.each_with_index do |src_ary,i|
183
- @strata[i] ||= []
184
- @rule_src[i] ||= []
185
- @rule_orig_src[i] ||= []
186
- src_ary.each_with_index do |src, j|
187
- @strata[i] << eval("lambda { #{src} }")
188
- @rule_src[i] << src
189
- @rule_orig_src[i] << @no_attr_rewrite_strata[i][j]
180
+ def module_wrapper_class(mod)
181
+ class_name = "#{mod}__wrap"
182
+ begin
183
+ klass = Module.const_get(class_name.to_sym)
184
+ unless klass.is_a? Class
185
+ raise Bud::Error, "Internal error: #{class_name} is in use"
190
186
  end
187
+ rescue NameError # exception if class class_name doesn't exist
191
188
  end
189
+ klass ||= eval "class #{class_name}; include Bud; include #{mod}; end"
190
+ klass
192
191
  end
193
192
 
194
- private
193
+ def toplevel
194
+ @toplevel = (@options[:toplevel] || self)
195
+ end
195
196
 
196
- # Rewrite methods defined in the given klass to expand module references and
197
- # temp collections. Imported modules are rewritten during the import process;
198
- # we rewrite the main class associated with this Bud instance and any included
199
- # modules here. Note that we only rewrite each distinct Class once, and we
200
- # skip methods defined by the Bud (Ruby) module or the builtin Bloom programs
201
- # (since we can be sure those won't need rewriting).
202
- def self.rewrite_local_methods(klass)
203
- @done_rewrite ||= {}
204
- return if @done_rewrite.has_key? klass.name
205
- return if [self, DepAnalysis, Stratification].include? klass
197
+ def qualified_name
198
+ toplevel? ? "" : @options[:qualified_name]
199
+ end
206
200
 
207
- u = Unifier.new
208
- ref_expander = NestedRefRewriter.new(klass)
209
- tmp_expander = TempExpander.new
210
- with_expander = WithExpander.new
211
- r2r = Ruby2Ruby.new
201
+ def toplevel?
202
+ toplevel.object_id == self.object_id
203
+ end
212
204
 
213
- klass.instance_methods(false).each do |m|
214
- ast = ParseTree.translate(klass, m)
215
- ast = u.process(ast)
216
- ast = ref_expander.process(ast)
217
- ast = tmp_expander.process(ast)
218
- ast = with_expander.process(ast)
205
+ def import_instance(name)
206
+ name = "@" + name.to_s
207
+ instance_variable_get(name) if instance_variable_defined? name
208
+ end
219
209
 
220
- if ref_expander.did_work or tmp_expander.did_work or with_expander.did_work
221
- new_src = r2r.process(ast)
222
- klass.module_eval new_src # Replace previous method def
210
+ def import_defs
211
+ @imported_defs = {} # mod name -> Module map
212
+ self.class.ancestors.each do |anc|
213
+ if anc.respond_to? :bud_import_table
214
+ anc_imp_tbl = anc.bud_import_table
215
+ anc_imp_tbl.each do |nm, mod|
216
+ # Ensure that two modules have not been imported under one alias.
217
+ if @imported_defs.member? nm and not @imported_defs[nm] == anc_imp_tbl[nm]
218
+ raise Bud::CompileError, "Conflicting imports: modules #{@imported_defs[nm]} and #{anc_imp_tbl[nm]} both are imported as '#{nm}'"
219
+ end
220
+ @imported_defs[nm] = mod
221
+ end
223
222
  end
224
-
225
- ref_expander.did_work = false
226
- tmp_expander.did_work = false
227
- with_expander.did_work = false
228
223
  end
224
+ @imported_defs ||= self.class.ancestors.inject({}) {|tbl, e| tbl.merge(e.bud_import_table)}
225
+ end
229
226
 
230
- # If we found any temp statements in the klass's rule blocks, add a state
231
- # block with declarations for the corresponding temp collections.
232
- st = tmp_expander.get_state_meth(klass)
233
- if st
234
- state_src = r2r.process(st)
235
- klass.module_eval(state_src)
236
- end
237
-
238
- ModuleRewriter.ast_mangle_with(with_expander, klass)
239
-
240
- # Always rewrite anonymous classes
241
- @done_rewrite[klass.name] = true unless klass.name == ""
227
+ def budtime
228
+ toplevel? ? @budtime : toplevel.budtime
242
229
  end
243
230
 
244
- # Invoke all the user-defined state blocks and initialize builtin state.
245
- def init_state
246
- builtin_state
247
- call_state_methods
231
+ # absorb rules and dependencies from imported modules. The corresponding module instantiations
232
+ # would themselves have resolved their own imports.
233
+ def resolve_imports
234
+ import_tbl = import_defs
235
+
236
+ import_tbl.each_pair do |local_name, mod_name|
237
+ # corresponding to "import <mod_name> => :<local_name>"
238
+ mod_inst = send(local_name)
239
+ qlocal_name = toplevel? ? local_name.to_s : self.qualified_name + "." + local_name.to_s
240
+ if mod_inst.nil?
241
+ # create wrapper instances
242
+ #puts "=== resolving #{self}.#{mod_name} => #{local_name}"
243
+ klass = module_wrapper_class(mod_name)
244
+ mod_inst = klass.new(:toplevel => toplevel, :qualified_name => qlocal_name) # this instantiation will resolve the imported module's own imports
245
+ instance_variable_set("@#{local_name}", mod_inst)
246
+ end
247
+ mod_inst.tables.each_pair do |name, t|
248
+ # Absorb the module wrapper's user-defined state.
249
+ unless @tables.has_key? t.tabname
250
+ qname = (local_name.to_s + "." + name.to_s).to_sym # access path to table.
251
+ tables[qname] = t
252
+ end
253
+ end
254
+ mod_inst.t_rules.each do |imp_rule|
255
+ qname = local_name.to_s + "." + imp_rule.lhs.to_s #qualify name by prepending with local_name
256
+ self.t_rules << [imp_rule.bud_obj, imp_rule.rule_id, qname, imp_rule.op,
257
+ imp_rule.src, imp_rule.orig_src]
258
+ end
259
+ mod_inst.t_depends.each do |imp_dep|
260
+ qlname = local_name.to_s + "." + imp_dep.lhs.to_s #qualify names by prepending with local_name
261
+ qrname = local_name.to_s + "." + imp_dep.body.to_s
262
+ self.t_depends << [imp_dep.bud_obj, imp_dep.rule_id, qlname, imp_dep.op, qrname, imp_dep.nm]
263
+ end
264
+ mod_inst.t_provides.each do |imp_pro|
265
+ qintname = local_name.to_s + "." + imp_pro.interface.to_s #qualify names by prepending with local_name
266
+ self.t_provides << [qintname, imp_pro.input]
267
+ end
268
+ mod_inst.channels.each do |name, ch|
269
+ qname = (local_name.to_s + "." + name.to_s)
270
+ @channels[qname.to_sym] = ch
271
+ end
272
+ mod_inst.dbm_tables.each do |name, t|
273
+ qname = (local_name.to_s + "." + name.to_s)
274
+ @dbm_tables[qname.to_sym] = t
275
+ end
276
+ mod_inst.periodics.each do |p|
277
+ qname = (local_name.to_s + "." + p.pername.to_s)
278
+ p.pername = qname.to_sym
279
+ @periodics << [p.pername, p.period]
280
+ end
281
+ end
282
+
283
+ nil
248
284
  end
249
285
 
250
286
  # If module Y is a parent module of X, X's state block might reference state
@@ -270,8 +306,15 @@ module Bud
270
306
  end
271
307
  end
272
308
 
273
- # Evaluate all bootstrap blocks
309
+ # Evaluate all bootstrap blocks and tick deltas
274
310
  def do_bootstrap
311
+ # evaluate bootstrap for imported modules
312
+ @this_rule_context = self
313
+ imported = import_defs.keys
314
+ imported.each do |mod_alias|
315
+ wrapper = import_instance mod_alias
316
+ wrapper.do_bootstrap
317
+ end
275
318
  self.class.ancestors.reverse.each do |anc|
276
319
  anc.instance_methods(false).each do |m|
277
320
  if /^__bootstrap__/.match m
@@ -281,19 +324,182 @@ module Bud
281
324
  end
282
325
  bootstrap
283
326
 
327
+ @tables.each_value {|t| t.bootstrap} if toplevel == self
284
328
  @done_bootstrap = true
285
329
  end
286
330
 
331
+ def do_wiring
332
+ @stratified_rules.each_with_index { |rules, stratum| eval_rules(rules, stratum) }
333
+
334
+ # prepare list of tables that will be actively used at run time. First, all the user defined ones.
335
+ # We start @app_tables off as a set, then convert to an array later.
336
+ @app_tables = (@tables.keys - @builtin_tables.keys).reduce(Set.new) {|tabset, nm| tabset << @tables[nm]; tabset}
337
+ # Check scan and merge_targets to see if any builtin_tables need to be added as well.
338
+ @scanners.each do |scs|
339
+ scs.each_value {|s| @app_tables << s.collection}
340
+ end
341
+ @merge_targets.each{|mts| #mts == merge_targets at stratum
342
+ mts.each_key {|t| @app_tables << t}
343
+ }
344
+ @app_tables = @app_tables.nil? ? [] : @app_tables.to_a
345
+
346
+ # for each stratum create a sorted list of push elements in topological order
347
+ @push_sorted_elems = []
348
+ @scanners.each do |scs| # scs's values constitute scanners at a stratum
349
+ # start with scanners and transitively add all reachable elements in a breadth-first order
350
+ working = scs.values
351
+ seen = Set.new(working)
352
+ sorted_elems = [] # sorted elements in this stratum
353
+ while not working.empty?
354
+ sorted_elems += working
355
+ wired_to = []
356
+ working.each do |e|
357
+ e.wirings.each do |out|
358
+ if (out.class <= PushElement and not seen.member?(out))
359
+ seen << out
360
+ wired_to << out
361
+ end
362
+ end
363
+ end
364
+ working = wired_to
365
+ end
366
+ @push_sorted_elems << sorted_elems
367
+ end
368
+
369
+ @merge_targets.each_with_index do |stratum_tables, stratum|
370
+ @scanners[stratum].each_value do |s|
371
+ stratum_tables[s.collection] = true
372
+ end
373
+ end
374
+
375
+ # sanity check
376
+ @push_sorted_elems.each do |stratum_elems|
377
+ stratum_elems.each do |se|
378
+ se.check_wiring
379
+ end
380
+ end
381
+
382
+ prepare_invalidation_scheme
383
+
384
+ @done_wiring = true
385
+ if @options[:print_wiring]
386
+ @push_sources.each do |strat|
387
+ strat.each_value{|src| src.print_wiring}
388
+ end
389
+ end
390
+ end
391
+
392
+ # All collections (elements included) are semantically required to erase any cached information at the start of a tick
393
+ # and start from a clean slate. prepare_invalidation_scheme prepares a just-in-time invalidation scheme that
394
+ # permits us to preserve data from one tick to the next, and to keep things in incremental mode unless there's a
395
+ # negation.
396
+ # This scheme solves the following constraints.
397
+ # 1. A full scan of an elements contents results in downstream elements getting full scans themselves (i.e no \
398
+ # deltas). This effect is transitive.
399
+ # 2. Invalidation of an element's cache results in rebuilding of the cache and a consequent fullscan
400
+ # 3. Invalidation of an element requires upstream elements to rescan their contents, or to transitively pass the
401
+ # request on further upstream. Any element that has a cache can rescan without passing on the request to higher
402
+ # levels.
403
+ #
404
+ # This set of constraints is solved once during wiring, resulting in four data structures
405
+ # @default_invalidate = set of elements and tables to always invalidate at every tick. Organized by stratum
406
+ # @default_rescan = set of elements and tables to always scan fully in the first iteration of every tick.
407
+ # scanner[stratum].invalidate = Set of elements to additionally invalidate if the scanner's table is invalidated at
408
+ # run-time
409
+ # scanner[stratum].rescan = Similar to above.
410
+
411
+
412
+ def prepare_invalidation_scheme
413
+ num_strata = @push_sorted_elems.size
414
+ if $BUD_SAFE
415
+ @app_tables = @tables.values # No tables excluded
416
+
417
+ invalidate = Set.new
418
+ rescan = Set.new
419
+ @app_tables.each {|t| invalidate << t if (t.class <= BudScratch)}
420
+ num_strata.times do |stratum|
421
+ @push_sorted_elems[stratum].each do |elem|
422
+ invalidate << elem
423
+ rescan << elem
424
+ end
425
+ end
426
+ #prune_rescan_invalidate(rescan, invalidate)
427
+ @default_rescan = rescan.to_a
428
+ @default_invalidate = invalidate.to_a
429
+ @reset_list = [] # Nothing to reset at end of tick. It'll be overwritten anyway
430
+ return
431
+ end
432
+
433
+
434
+ # Any table that occurs on the lhs of rule is not considered a source (by default it is).
435
+ # In addition, we only consider non-temporal rules because invalidation is only about this tick.
436
+ t_rules.each {|rule| @tables[rule.lhs.to_sym].is_source = false if rule.op == "<="}
437
+
438
+ invalidate = Set.new
439
+ rescan = Set.new
440
+ # Compute a set of tables and elements that should be explicitly told to invalidate or rescan.
441
+ # Start with a set of tables that always invalidate, and elements that always rescan
442
+ @app_tables.each {|t| invalidate << t if t.invalidate_at_tick}
443
+ num_strata.times do |stratum|
444
+ @push_sorted_elems[stratum].each do |elem|
445
+ rescan << elem if elem.rescan_at_tick
446
+ end
447
+ rescan_invalidate_tc(stratum, rescan, invalidate)
448
+ end
449
+ prune_rescan_invalidate(rescan, invalidate)
450
+ # transitive closure
451
+ @default_rescan = rescan.to_a
452
+ @default_invalidate = invalidate.to_a
453
+
454
+ # Now compute for each table that is to be scanned, the set of dependent tables and elements that will be invalidated
455
+ # if that table were to be invalidated at run time.
456
+ dflt_rescan = rescan
457
+ dflt_invalidate = invalidate
458
+ to_reset = rescan + invalidate
459
+ num_strata.times do |stratum|
460
+ @scanners[stratum].each_value do |scanner|
461
+ # If it is going to be always invalidated, it doesn't need further examination.
462
+ next if dflt_rescan.member? scanner
463
+
464
+ rescan = dflt_rescan + [scanner] # add scanner to scan set
465
+ invalidate = dflt_invalidate.clone
466
+ rescan_invalidate_tc(stratum, rescan, invalidate)
467
+ prune_rescan_invalidate(rescan, invalidate)
468
+ to_reset += rescan + invalidate
469
+ # Give the diffs (from default) to scanner; these are elements that are dependent on this scanner
470
+ diffscan = (rescan - dflt_rescan).find_all {|elem| elem.class <= PushElement}
471
+ scanner.invalidate_at_tick(diffscan, (invalidate - dflt_invalidate).to_a)
472
+ end
473
+ end
474
+ @reset_list = to_reset.to_a
475
+ end
476
+
477
+
478
+ #given rescan, invalidate sets, compute transitive closure
479
+ def rescan_invalidate_tc(stratum, rescan, invalidate)
480
+ rescan_len = rescan.size
481
+ invalidate_len = invalidate.size
482
+ while true
483
+ # Ask each element if it wants to add itself to either set, depending on who else is in those sets already.
484
+ @push_sorted_elems[stratum].each {|t| t.add_rescan_invalidate(rescan, invalidate)}
485
+ break if rescan_len == rescan.size and invalidate_len == invalidate.size
486
+ rescan_len = rescan.size
487
+ invalidate_len = invalidate.size
488
+ end
489
+ end
490
+
491
+ def prune_rescan_invalidate(rescan, invalidate)
492
+ rescan.delete_if {|e| e.rescan_at_tick}
493
+ end
494
+
287
495
  def do_rewrite
288
496
  @meta_parser = BudMeta.new(self, @declarations)
289
- @rewritten_strata, @no_attr_rewrite_strata = @meta_parser.meta_rewrite
497
+ @stratified_rules = @meta_parser.meta_rewrite
290
498
  end
291
499
 
292
500
  public
293
501
 
294
502
  ########### give empty defaults for these
295
- def declaration # :nodoc: all
296
- end
297
503
  def bootstrap # :nodoc: all
298
504
  end
299
505
 
@@ -357,7 +563,7 @@ module Bud
357
563
  # earlier because we need to wait for EventMachine startup.
358
564
  @stdio.start_stdin_reader if @options[:stdin]
359
565
  @zk_tables.each_value {|t| t.start_watchers}
360
-
566
+
361
567
  @halt_cb = register_callback(:halt) do |t|
362
568
  stop
363
569
  if t.first.key == :kill
@@ -529,6 +735,7 @@ module Bud
529
735
  # inserted into the output collection: these are returned to the caller.
530
736
  def sync_callback(in_tbl, tupleset, out_tbl)
531
737
  q = Queue.new
738
+ # XXXX Why two separate entrances into the event loop? Why is the callback not registered in the sync_do below?
532
739
  cb = register_callback(out_tbl) do |c|
533
740
  q.push c.to_a
534
741
  end
@@ -565,6 +772,11 @@ module Bud
565
772
  sync_callback(nil, nil, out_tbl)
566
773
  end
567
774
 
775
+ # XXX make tag specific
776
+ def inspect
777
+ "#{self.class}:#{self.object_id.to_s(16)}"
778
+ end
779
+
568
780
  private
569
781
 
570
782
  def invoke_callbacks
@@ -635,7 +847,6 @@ module Bud
635
847
  # Silently ignore duplicate shutdown requests or attempts to shutdown an
636
848
  # instance that hasn't been started yet.
637
849
  return if @instance_id == ILLEGAL_INSTANCE_ID
638
-
639
850
  $signal_lock.synchronize {
640
851
  raise unless $bud_instances.has_key? @instance_id
641
852
  $bud_instances.delete @instance_id
@@ -691,7 +902,7 @@ module Bud
691
902
 
692
903
  # Returns the internal IP and port. See ip_port.
693
904
  def int_ip_port
694
- raise Bud::Error, "ip_port called before port defined" if @port.nil? and @options[:port] == 0
905
+ raise Bud::Error, "int_ip_port called before port defined" if @port.nil? and @options[:port] == 0
695
906
  @port.nil? ? "#{@ip}:#{@options[:port]}" : "#{@ip}:#{@port}"
696
907
  end
697
908
 
@@ -703,6 +914,7 @@ module Bud
703
914
  # One timestep of Bloom execution. This MUST be invoked from the EventMachine
704
915
  # thread; it is not intended to be called directly by client code.
705
916
  def tick_internal
917
+ puts "#{object_id}/#{port} : =============================================" if $BUD_DEBUG
706
918
  begin
707
919
  starttime = Time.now if options[:metrics]
708
920
  if options[:metrics] and not @endtime.nil?
@@ -710,21 +922,80 @@ module Bud
710
922
  @metrics[:betweentickstats] = running_stats(@metrics[:betweentickstats], starttime - @endtime)
711
923
  end
712
924
  @inside_tick = true
713
- @tables.each_value do |t|
714
- t.tick
925
+
926
+ unless @done_bootstrap
927
+ do_bootstrap
928
+ do_wiring
929
+ else
930
+ # inform tables and elements about beginning of tick.
931
+ @app_tables.each {|t| t.tick}
932
+ @default_rescan.each {|elem| elem.rescan = true}
933
+ @default_invalidate.each {|elem|
934
+ elem.invalidated = true
935
+ elem.invalidate_cache unless elem.class <= PushElement # call tick on tables here itself. The rest below.
936
+ }
937
+
938
+ num_strata = @push_sorted_elems.size
939
+ # The following loop invalidates additional (non-default) elements and tables that depend on the run-time
940
+ # invalidation state of a table.
941
+ # Loop once to set the flags
942
+ num_strata.times do |stratum|
943
+ @scanners[stratum].each_value do |scanner|
944
+ if scanner.rescan
945
+ scanner.rescan_set.each {|e| e.rescan = true}
946
+ scanner.invalidate_set.each {|e|
947
+ e.invalidated = true;
948
+ e.invalidate_cache unless e.class <= PushElement
949
+ }
950
+ end
951
+ end
952
+ end
953
+ #Loop a second time to actually call invalidate_cache
954
+ num_strata.times do |stratum|
955
+ @push_sorted_elems[stratum].each { |elem| elem.invalidate_cache if elem.invalidated}
956
+ end
715
957
  end
716
958
 
717
- @joinstate = {}
718
-
719
- do_bootstrap unless @done_bootstrap
720
959
  receive_inbound
721
-
722
- @strata.each_with_index { |s,i| stratum_fixpoint(s, i) }
960
+ # compute fixpoint for each stratum in order
961
+ @stratified_rules.each_with_index do |rules,stratum|
962
+ fixpoint = false
963
+ first_iter = true
964
+ until fixpoint
965
+ fixpoint = true
966
+ @scanners[stratum].each_value {|s| s.scan(first_iter)}
967
+ first_iter = false
968
+ # flush any tuples in the pipes
969
+ @push_sorted_elems[stratum].each {|p| p.flush}
970
+ # tick deltas on any merge targets and look for more deltas
971
+ # check to see if any joins saw a delta
972
+ push_joins[stratum].each do |p|
973
+ if p.found_delta==true
974
+ fixpoint = false
975
+ p.tick_deltas
976
+ end
977
+ end
978
+ merge_targets[stratum].each_key do |t|
979
+ fixpoint = false if t.tick_deltas
980
+ end
981
+ end
982
+ # push end-of-fixpoint
983
+ @push_sorted_elems[stratum].each {|p|
984
+ p.stratum_end
985
+ }
986
+ merge_targets[stratum].each_key do |t|
987
+ t.flush_deltas
988
+ end
989
+ end
723
990
  @viz.do_cards if @options[:trace]
724
991
  do_flush
992
+
725
993
  invoke_callbacks
726
994
  @budtime += 1
727
995
  @inbound.clear
996
+
997
+ @reset_list.each {|e| e.invalidated = false; e.rescan = false}
998
+
728
999
  ensure
729
1000
  @inside_tick = false
730
1001
  @tick_clock_time = nil
@@ -757,14 +1028,13 @@ module Bud
757
1028
 
758
1029
  loopback :localtick, [:col1]
759
1030
  @stdio = terminal :stdio
760
- readonly :signals, [:key]
1031
+ signal :signals, [:name]
761
1032
  scratch :halt, [:key]
762
1033
  @periodics = table :periodics_tbl, [:pername] => [:period]
763
1034
 
764
- # For Bud reflection
765
- table :t_rules, [:rule_id] => [:lhs, :op, :src, :orig_src]
766
- table :t_depends, [:rule_id, :lhs, :op, :body] => [:nm]
767
- table :t_depends_tc, [:head, :body, :via, :neg, :temporal]
1035
+ # for BUD reflection
1036
+ table :t_rules, [:bud_obj, :rule_id] => [:lhs, :op, :src, :orig_src]
1037
+ table :t_depends, [:bud_obj, :rule_id, :lhs, :op, :body] => [:nm]
768
1038
  table :t_provides, [:interface] => [:input]
769
1039
  table :t_underspecified, t_provides.schema
770
1040
  table :t_stratum, [:predicate] => [:stratum]
@@ -773,7 +1043,7 @@ module Bud
773
1043
  table :t_table_schema, [:tab_name, :col_name, :ord, :loc]
774
1044
 
775
1045
  # Identify builtin tables as such
776
- @builtin_tables = @tables.clone
1046
+ @builtin_tables = @tables.clone if toplevel
777
1047
  end
778
1048
 
779
1049
  # Handle any inbound tuples off the wire. Received messages are placed
@@ -781,6 +1051,7 @@ module Bud
781
1051
  # queue is cleared at the end of the tick.
782
1052
  def receive_inbound
783
1053
  @inbound.each do |tbl_name, msg_buf|
1054
+ puts "channel #{tbl_name} rcv: #{msg_buf}" if $BUD_DEBUG
784
1055
  msg_buf.each do |b|
785
1056
  tables[tbl_name] << b
786
1057
  end
@@ -793,89 +1064,32 @@ module Bud
793
1064
  def do_flush
794
1065
  @channels.each_value { |c| c.flush }
795
1066
  @zk_tables.each_value { |t| t.flush }
796
- @tc_tables.each_value { |t| t.flush }
797
1067
  @dbm_tables.each_value { |t| t.flush }
798
1068
  end
799
1069
 
800
- def stratum_fixpoint(strat, strat_num)
801
- # This routine uses semi-naive evaluation to compute a fixpoint of the rules
802
- # in strat.
803
- #
804
- # As described in lib/collections.rb, each collection has three
805
- # sub-collections of note here:
806
- # @storage: the "main" storage of tuples
807
- # @delta: tuples that should be used to drive derivation of new facts
808
- # @new_delta: a place to store newly-derived facts
809
- #
810
- # The first time through this loop we mark @stratum_first_iter=true, which
811
- # tells the Join::each code to join up all its @storage subcollections to
812
- # start. In subsequent iterations the join code uses some table's @delta to
813
- # ensure that only new tuples are derived.
814
- #
815
- # Note that calling "each" on a non-Join collection will iterate through
816
- # both storage and delta.
817
- #
818
- # At the end of each iteration of this loop we transition:
819
- # - @delta tuples are merged into @storage
820
- # - @new_delta tuples are moved into @delta
821
- # - @new_delta is set to empty
822
- #
823
- # XXX as a performance optimization, it would be nice to bypass the delta
824
- # tables for any preds that don't participate in a rhs Join -- in that case
825
- # there's pointless extra tuple movement letting tuples "graduate" through
826
- # @new_delta and @delta.
827
-
828
- # In semi-naive, the first iteration should join up tables on their storage
829
- # fields; subsequent iterations do the delta-joins only. The
830
- # stratum_first_iter field here distinguishes these cases.
831
- @stratum_first_iter = true
832
- begin
833
- @this_stratum = strat_num
834
- strat.each_with_index do |r,i|
835
- @this_rule = i
836
- fixpoint = false
837
- rule_src = @rule_orig_src[strat_num][i] unless @rule_orig_src[strat_num].nil?
838
- begin
839
- if options[:metrics]
840
- metrics[:rules] ||= {}
841
- metrics[:rules][{:strat_num => strat_num, :rule_num => i, :rule_src => rule_src}] ||= 0
842
- metrics[:rules][{:strat_num => strat_num, :rule_num => i, :rule_src => rule_src}] += 1
843
- end
844
- r.call
845
- rescue Exception => e
846
- # Don't report source text for certain rules (old-style rule blocks)
847
- src_msg = ""
848
- unless rule_src == ""
849
- src_msg = "\nRule: #{rule_src}"
850
- end
1070
+ def eval_rule(__obj__, __src__)
1071
+ __obj__.instance_eval __src__ # ensure that the only local variables are __obj__ and __src__
1072
+ end
851
1073
 
852
- new_e = e
853
- unless new_e.class <= Bud::Error
854
- new_e = Bud::Error
855
- end
856
- raise new_e, "exception during Bud evaluation.\nException: #{e.inspect}\nLocation: #{e.backtrace.first}.#{src_msg}"
857
- end
858
- end
859
- @stratum_first_iter = false
860
- fixpoint = true
861
- # tick collections in this stratum; if we don't have info on that, tick all collections
862
- colls = @stratum_collection_map[strat_num] if @stratum_collection_map
863
- colls ||= @tables.keys
864
- colls.each do |name|
865
- coll = @tables[name]
866
- # ignore missing tables; rebl for example deletes them mid-stream
867
- next if coll.nil?
868
-
869
- unless coll.delta.empty? and coll.new_delta.empty?
870
- fixpoint = false unless coll.new_delta.empty?
871
- coll.tick_deltas
872
- end
1074
+ def eval_rules(rules, strat_num)
1075
+ # This routine evals the rules in a given stratum, which results in a wiring of PushElements
1076
+ @this_stratum = strat_num
1077
+ rules.each_with_index do |rule, i|
1078
+ @this_rule = i
1079
+ @this_rule_context = rule.bud_obj # user-supplied code blocks will be evaluated in this context at run-time
1080
+ begin
1081
+ eval_rule(rule.bud_obj, rule.src)
1082
+ rescue Exception => e
1083
+ err_msg = "** Exception while wiring rule: #{rule.src}\n ****** #{e}"
1084
+ # Create a new exception for accomodating err_msg, but reuse original backtrace
1085
+ new_e = (e.class <= Bud::Error) ? e.class.new(err_msg) : Bud::Error.new(err_msg)
1086
+ new_e.set_backtrace(e.backtrace)
1087
+ raise new_e
873
1088
  end
874
- end while not fixpoint
1089
+ end
875
1090
  end
876
1091
 
877
1092
  private
878
-
879
1093
  ######## ids and timers
880
1094
  def gen_id
881
1095
  Time.new.to_i.to_s << rand.to_s