bud 0.0.8 → 0.1.0.pre1

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