bud 0.1.0.pre1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/bud.rb CHANGED
@@ -11,9 +11,7 @@ require 'bud/monkeypatch'
11
11
  require 'bud/aggs'
12
12
  require 'bud/bud_meta'
13
13
  require 'bud/collections'
14
- require 'bud/joins'
15
14
  require 'bud/metrics'
16
- #require 'bud/meta_algebra'
17
15
  require 'bud/rtrace'
18
16
  require 'bud/server'
19
17
  require 'bud/state'
@@ -62,12 +60,11 @@ $bud_instances = {} # Map from instance id => Bud instance
62
60
  #
63
61
  # :main: Bud
64
62
  module Bud
65
- attr_reader :strata, :budtime, :inbound, :options, :meta_parser, :viz, :rtracer
66
- attr_reader :dsock
67
- attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :sources, :sinks, :app_tables
63
+ attr_reader :budtime, :inbound, :options, :meta_parser, :viz, :rtracer, :dsock
64
+ attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :app_tables
68
65
  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
66
+ attr_reader :this_stratum, :this_rule, :rule_orig_src, :done_bootstrap
67
+ attr_accessor :stratified_rules
71
68
  attr_accessor :metrics, :periodics
72
69
  attr_accessor :this_rule_context, :qualified_name
73
70
  attr_reader :running_async
@@ -82,7 +79,7 @@ module Bud
82
79
  # * operating system interaction
83
80
  # * <tt>:stdin</tt> if non-nil, reading from the +stdio+ collection results in reading from this +IO+ handle
84
81
  # * <tt>:stdout</tt> writing to the +stdio+ collection results in writing to this +IO+ handle; defaults to <tt>$stdout</tt>
85
- # * <tt>:signal_handling</tt> how to handle +SIGINT+ and +SIGTERM+. If :none, these signals are ignored. If :bloom, they're passed into the built-in scratch called +signals+. Else shutdown all bud instances.
82
+ # * <tt>:signal_handling</tt> how to handle +SIGINT+ and +SIGTERM+. If +:none+, these signals are ignored. Else shutdown all bud instances.
86
83
  # * tracing and output
87
84
  # * <tt>:quiet</tt> if true, suppress certain messages
88
85
  # * <tt>:trace</tt> if true, generate +budvis+ outputs
@@ -106,19 +103,18 @@ module Bud
106
103
  # * <tt>:dbm_truncate</tt> if true, DBM-backed collections are opened with +OTRUNC+
107
104
  def initialize(options={})
108
105
  # capture the binding for a subsequent 'eval'. This ensures that local
109
- # variable names introduced later in this method don't interfere with
106
+ # variable names introduced later in this method don't interfere with
110
107
  # table names used in the eval block.
111
108
  options[:dump_rewrite] ||= ENV["BUD_DUMP_REWRITE"].to_i > 0
112
109
  options[:dump_ast] ||= ENV["BUD_DUMP_AST"].to_i > 0
113
110
  options[:print_wiring] ||= ENV["BUD_PRINT_WIRING"].to_i > 0
114
111
  @qualified_name = ""
115
112
  @tables = {}
116
- @table_meta = []
117
- @stratified_rules = []
118
113
  @channels = {}
119
- @push_elems = {}
120
114
  @dbm_tables = {}
121
115
  @zk_tables = {}
116
+ @stratified_rules = []
117
+ @push_elems = {}
122
118
  @callbacks = {}
123
119
  @callback_id = 0
124
120
  @shutdown_callbacks = {}
@@ -133,15 +129,10 @@ module Bud
133
129
  @done_bootstrap = false
134
130
  @done_wiring = false
135
131
  @instance_id = ILLEGAL_INSTANCE_ID # Assigned when we start running
136
- @sources = {}
137
- @sinks = {}
138
132
  @metrics = {}
139
133
  @endtime = nil
140
134
  @this_stratum = 0
141
135
  @push_sorted_elems = nil
142
-
143
- # XXX This variable is unused in the Push executor
144
- @stratum_first_iter = false
145
136
  @running_async = false
146
137
  @bud_started = false
147
138
 
@@ -155,10 +146,7 @@ module Bud
155
146
  # number won't be known until we start EM
156
147
 
157
148
  builtin_state
158
-
159
149
  resolve_imports
160
-
161
- # Invoke all the user-defined state blocks and initialize builtin state.
162
150
  call_state_methods
163
151
 
164
152
  @declarations = self.class.instance_methods.select {|m| m =~ /^__bloom__.+$/}.map {|m| m.to_s}
@@ -173,7 +161,7 @@ module Bud
173
161
  @scanners = num_strata.times.map{{}}
174
162
  @push_sources = num_strata.times.map{{}}
175
163
  @push_joins = num_strata.times.map{[]}
176
- @merge_targets = num_strata.times.map{{}}
164
+ @merge_targets = num_strata.times.map{Set.new}
177
165
  end
178
166
  end
179
167
 
@@ -182,7 +170,7 @@ module Bud
182
170
  begin
183
171
  klass = Module.const_get(class_name.to_sym)
184
172
  unless klass.is_a? Class
185
- raise Bud::Error, "Internal error: #{class_name} is in use"
173
+ raise Bud::Error, "internal error: #{class_name} is in use"
186
174
  end
187
175
  rescue NameError # exception if class class_name doesn't exist
188
176
  end
@@ -207,6 +195,11 @@ module Bud
207
195
  instance_variable_get(name) if instance_variable_defined? name
208
196
  end
209
197
 
198
+ def budtime
199
+ toplevel? ? @budtime : toplevel.budtime
200
+ end
201
+
202
+ private
210
203
  def import_defs
211
204
  @imported_defs = {} # mod name -> Module map
212
205
  self.class.ancestors.each do |anc|
@@ -215,7 +208,7 @@ module Bud
215
208
  anc_imp_tbl.each do |nm, mod|
216
209
  # Ensure that two modules have not been imported under one alias.
217
210
  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}'"
211
+ raise Bud::CompileError, "conflicting imports: modules #{@imported_defs[nm]} and #{anc_imp_tbl[nm]} both are imported as '#{nm}'"
219
212
  end
220
213
  @imported_defs[nm] = mod
221
214
  end
@@ -224,10 +217,6 @@ module Bud
224
217
  @imported_defs ||= self.class.ancestors.inject({}) {|tbl, e| tbl.merge(e.bud_import_table)}
225
218
  end
226
219
 
227
- def budtime
228
- toplevel? ? @budtime : toplevel.budtime
229
- end
230
-
231
220
  # absorb rules and dependencies from imported modules. The corresponding module instantiations
232
221
  # would themselves have resolved their own imports.
233
222
  def resolve_imports
@@ -236,47 +225,51 @@ module Bud
236
225
  import_tbl.each_pair do |local_name, mod_name|
237
226
  # corresponding to "import <mod_name> => :<local_name>"
238
227
  mod_inst = send(local_name)
239
- qlocal_name = toplevel? ? local_name.to_s : self.qualified_name + "." + local_name.to_s
240
228
  if mod_inst.nil?
241
229
  # create wrapper instances
242
230
  #puts "=== resolving #{self}.#{mod_name} => #{local_name}"
243
231
  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
232
+ qlocal_name = toplevel? ? local_name.to_s : "#{self.qualified_name}.#{local_name}"
233
+ # this instantiation will resolve the imported module's own imports
234
+ mod_inst = klass.new(:toplevel => toplevel, :qualified_name => qlocal_name)
245
235
  instance_variable_set("@#{local_name}", mod_inst)
246
236
  end
237
+ # Absorb the module wrapper's user-defined state.
247
238
  mod_inst.tables.each_pair do |name, t|
248
- # Absorb the module wrapper's user-defined state.
239
+ # Don't try to import module definitions for builtin Bud state. Note
240
+ # that @tables only includes the builtin tables, because resolve_imports
241
+ # is called before user-defined state blocks are run.
249
242
  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
243
+ qname = "#{local_name}.#{name}"
244
+ tables[qname.to_sym] = t
252
245
  end
253
246
  end
254
247
  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
248
+ qname = "#{local_name}.#{imp_rule.lhs}"
256
249
  self.t_rules << [imp_rule.bud_obj, imp_rule.rule_id, qname, imp_rule.op,
257
- imp_rule.src, imp_rule.orig_src]
250
+ imp_rule.src, imp_rule.orig_src, imp_rule.nm_funcs_called]
258
251
  end
259
252
  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]
253
+ qlname = "#{local_name}.#{imp_dep.lhs}"
254
+ qrname = "#{local_name}.#{imp_dep.body}"
255
+ self.t_depends << [imp_dep.bud_obj, imp_dep.rule_id, qlname,
256
+ imp_dep.op, qrname, imp_dep.nm]
263
257
  end
264
258
  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
259
+ qintname = "#{local_name}.#{imp_pro.interface}"
266
260
  self.t_provides << [qintname, imp_pro.input]
267
261
  end
268
262
  mod_inst.channels.each do |name, ch|
269
- qname = (local_name.to_s + "." + name.to_s)
263
+ qname = "#{local_name}.#{name}"
270
264
  @channels[qname.to_sym] = ch
271
265
  end
272
266
  mod_inst.dbm_tables.each do |name, t|
273
- qname = (local_name.to_s + "." + name.to_s)
267
+ qname = "#{local_name}.#{name}"
274
268
  @dbm_tables[qname.to_sym] = t
275
269
  end
276
270
  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]
271
+ qname = "#{local_name}.#{p.pername}"
272
+ @periodics << [qname.to_sym, p.period]
280
273
  end
281
274
  end
282
275
 
@@ -306,47 +299,30 @@ module Bud
306
299
  end
307
300
  end
308
301
 
309
- # Evaluate all bootstrap blocks and tick deltas
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
318
- self.class.ancestors.reverse.each do |anc|
319
- anc.instance_methods(false).each do |m|
320
- if /^__bootstrap__/.match m
321
- self.method(m.to_sym).call
322
- end
323
- end
324
- end
325
- bootstrap
326
-
327
- @tables.each_value {|t| t.bootstrap} if toplevel == self
328
- @done_bootstrap = true
329
- end
330
-
331
302
  def do_wiring
303
+ @num_strata = @stratified_rules.length
304
+
332
305
  @stratified_rules.each_with_index { |rules, stratum| eval_rules(rules, stratum) }
333
306
 
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}
307
+ # Prepare list of tables that will be actively used at run time. First, all
308
+ # the user-defined ones. We start @app_tables off as a set, then convert to
309
+ # an array later.
310
+ @app_tables = (@tables.keys - @builtin_tables.keys).map {|t| @tables[t]}.to_set
311
+
337
312
  # Check scan and merge_targets to see if any builtin_tables need to be added as well.
338
313
  @scanners.each do |scs|
339
- scs.each_value {|s| @app_tables << s.collection}
314
+ @app_tables += scs.values.map {|s| s.collection}
315
+ end
316
+ @merge_targets.each do |mts| #mts == merge_targets at stratum
317
+ @app_tables += mts
340
318
  end
341
- @merge_targets.each{|mts| #mts == merge_targets at stratum
342
- mts.each_key {|t| @app_tables << t}
343
- }
344
319
  @app_tables = @app_tables.nil? ? [] : @app_tables.to_a
345
320
 
346
321
  # for each stratum create a sorted list of push elements in topological order
347
322
  @push_sorted_elems = []
348
323
  @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
324
+ # start with scanners and transitively add all reachable elements in a
325
+ # breadth-first order
350
326
  working = scs.values
351
327
  seen = Set.new(working)
352
328
  sorted_elems = [] # sorted elements in this stratum
@@ -366,9 +342,9 @@ module Bud
366
342
  @push_sorted_elems << sorted_elems
367
343
  end
368
344
 
369
- @merge_targets.each_with_index do |stratum_tables, stratum|
345
+ @merge_targets.each_with_index do |stratum_targets, stratum|
370
346
  @scanners[stratum].each_value do |s|
371
- stratum_tables[s.collection] = true
347
+ stratum_targets << s.collection
372
348
  end
373
349
  end
374
350
 
@@ -379,12 +355,27 @@ module Bud
379
355
  end
380
356
  end
381
357
 
358
+ # create sets of elements and collections to invalidate or rescan at the beginning of each tick.
382
359
  prepare_invalidation_scheme
383
360
 
361
+ # For all tables that are accessed (scanned) in a stratum higher than the one they are updated in, set
362
+ # a flag to track deltas accumulated in that tick (see: collection.tick_delta)
363
+ stratum_accessed = {}
364
+ (@num_strata-1).downto(0) do |stratum|
365
+ @scanners[stratum].each_value do |s|
366
+ stratum_accessed[s.collection] ||= stratum
367
+ end
368
+ end
369
+ @merge_targets.each_with_index do |stratum_targets, stratum|
370
+ stratum_targets.each {|tab|
371
+ tab.accumulate_tick_deltas = true if stratum_accessed[tab] and stratum_accessed[tab] > stratum
372
+ }
373
+ end
374
+
384
375
  @done_wiring = true
385
376
  if @options[:print_wiring]
386
- @push_sources.each do |strat|
387
- strat.each_value{|src| src.print_wiring}
377
+ @push_sources.each do |strat|
378
+ strat.each_value {|src| src.print_wiring}
388
379
  end
389
380
  end
390
381
  end
@@ -394,9 +385,9 @@ module Bud
394
385
  # permits us to preserve data from one tick to the next, and to keep things in incremental mode unless there's a
395
386
  # negation.
396
387
  # 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 \
388
+ # 1. A full scan of an element's contents results in downstream elements getting full scans themselves (i.e no \
398
389
  # deltas). This effect is transitive.
399
- # 2. Invalidation of an element's cache results in rebuilding of the cache and a consequent fullscan
390
+ # 2. Invalidation of an element's cache results in rebuilding of the cache and a consequent fullscan. See next.
400
391
  # 3. Invalidation of an element requires upstream elements to rescan their contents, or to transitively pass the
401
392
  # request on further upstream. Any element that has a cache can rescan without passing on the request to higher
402
393
  # levels.
@@ -407,16 +398,13 @@ module Bud
407
398
  # scanner[stratum].invalidate = Set of elements to additionally invalidate if the scanner's table is invalidated at
408
399
  # run-time
409
400
  # scanner[stratum].rescan = Similar to above.
410
-
411
-
412
401
  def prepare_invalidation_scheme
413
- num_strata = @push_sorted_elems.size
402
+ num_strata = @push_sorted_elems.size
414
403
  if $BUD_SAFE
415
404
  @app_tables = @tables.values # No tables excluded
416
405
 
417
- invalidate = Set.new
418
406
  rescan = Set.new
419
- @app_tables.each {|t| invalidate << t if (t.class <= BudScratch)}
407
+ invalidate = @app_tables.select {|t| t.class <= BudScratch}.to_set
420
408
  num_strata.times do |stratum|
421
409
  @push_sorted_elems[stratum].each do |elem|
422
410
  invalidate << elem
@@ -430,35 +418,53 @@ module Bud
430
418
  return
431
419
  end
432
420
 
421
+ # By default, all tables are considered sources unless they appear on the
422
+ # lhs. We only consider non-temporal rules because invalidation is only
423
+ # about this tick. Also, we track (in nm_targets) those tables that are the
424
+ # targets of user-defined code blocks that call non-monotonic functions
425
+ # (such as budtime). Elements that feed these tables are forced to rescan
426
+ # their contents, and thus forced to re-execute these code blocks.
427
+ nm_targets = Set.new
428
+ t_rules.each do |rule|
429
+ lhs = rule.lhs.to_sym
430
+ @tables[lhs].is_source = false if rule.op == "<="
431
+ nm_targets << lhs if rule.nm_funcs_called
432
+ end
433
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
434
+ # Compute a set of tables and elements that should be explicitly told to
435
+ # invalidate or rescan. Start with a set of tables that always invalidate
436
+ # and elements that always rescan.
437
+ invalidate = @app_tables.select {|t| t.invalidate_at_tick}.to_set
439
438
  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}
439
+
443
440
  num_strata.times do |stratum|
444
441
  @push_sorted_elems[stratum].each do |elem|
445
- rescan << elem if elem.rescan_at_tick
442
+ if elem.rescan_at_tick
443
+ rescan << elem
444
+ end
445
+
446
+ if elem.outputs.any?{|tab| not(tab.class <= PushElement) and nm_targets.member? tab.qualified_tabname.to_sym }
447
+ rescan += elem.wired_by
448
+ end
446
449
  end
447
450
  rescan_invalidate_tc(stratum, rescan, invalidate)
448
451
  end
452
+
449
453
  prune_rescan_invalidate(rescan, invalidate)
450
454
  # transitive closure
451
455
  @default_rescan = rescan.to_a
452
456
  @default_invalidate = invalidate.to_a
453
457
 
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.
458
+ # Now compute for each table that is to be scanned, the set of dependent
459
+ # tables and elements that will be invalidated if that table were to be
460
+ # invalidated at run time.
456
461
  dflt_rescan = rescan
457
462
  dflt_invalidate = invalidate
458
463
  to_reset = rescan + invalidate
459
464
  num_strata.times do |stratum|
460
465
  @scanners[stratum].each_value do |scanner|
461
- # If it is going to be always invalidated, it doesn't need further examination.
466
+ # If it is going to be always invalidated, it doesn't need further
467
+ # examination
462
468
  next if dflt_rescan.member? scanner
463
469
 
464
470
  rescan = dflt_rescan + [scanner] # add scanner to scan set
@@ -466,7 +472,8 @@ module Bud
466
472
  rescan_invalidate_tc(stratum, rescan, invalidate)
467
473
  prune_rescan_invalidate(rescan, invalidate)
468
474
  to_reset += rescan + invalidate
469
- # Give the diffs (from default) to scanner; these are elements that are dependent on this scanner
475
+ # Give the diffs (from default) to scanner; these are elements that are
476
+ # dependent on this scanner
470
477
  diffscan = (rescan - dflt_rescan).find_all {|elem| elem.class <= PushElement}
471
478
  scanner.invalidate_at_tick(diffscan, (invalidate - dflt_invalidate).to_a)
472
479
  end
@@ -474,13 +481,13 @@ module Bud
474
481
  @reset_list = to_reset.to_a
475
482
  end
476
483
 
477
-
478
- #given rescan, invalidate sets, compute transitive closure
484
+ # given rescan, invalidate sets, compute transitive closure
479
485
  def rescan_invalidate_tc(stratum, rescan, invalidate)
480
486
  rescan_len = rescan.size
481
487
  invalidate_len = invalidate.size
482
488
  while true
483
- # Ask each element if it wants to add itself to either set, depending on who else is in those sets already.
489
+ # Ask each element if it wants to add itself to either set, depending on
490
+ # who else is in those sets already.
484
491
  @push_sorted_elems[stratum].each {|t| t.add_rescan_invalidate(rescan, invalidate)}
485
492
  break if rescan_len == rescan.size and invalidate_len == invalidate.size
486
493
  rescan_len = rescan.size
@@ -533,10 +540,10 @@ module Bud
533
540
  @rtracer.sleep if options[:rtrace]
534
541
  end
535
542
 
536
- # Startup a Bud instance. This starts EventMachine (if needed) and binds to a
537
- # UDP server socket. If do_tick is true, we also execute a single Bloom
538
- # timestep. Regardless, calling this method does NOT cause Bud to begin
539
- # executing timesteps asynchronously (see run_bg).
543
+ # Startup a Bud instance if it is not currently started. This starts
544
+ # EventMachine (if needed) and binds to a UDP server socket. If do_tick is
545
+ # true, we also execute a single Bloom timestep. Regardless, calling this
546
+ # method does NOT cause Bud to begin running asynchronously (see run_bg).
540
547
  def start(do_tick=false)
541
548
  start_reactor
542
549
  schedule_and_wait do
@@ -703,21 +710,26 @@ module Bud
703
710
  # Note that registering callbacks on persistent collections (e.g., tables,
704
711
  # syncs and stores) is probably not wise: as long as any tuples are stored in
705
712
  # the collection, the callback will be invoked at the end of every tick.
706
- def register_callback(tbl_name, &block)
713
+ def register_callback(tbl_name, &blk)
707
714
  # We allow callbacks to be added before or after EM has been started. To
708
715
  # simplify matters, we start EM if it hasn't been started yet.
709
716
  start_reactor
710
717
  cb_id = nil
711
718
  schedule_and_wait do
712
- unless @tables.has_key? tbl_name
713
- raise Bud::Error, "no such collection: #{tbl_name}"
714
- end
719
+ cb_id = do_register_callback(tbl_name, &blk)
720
+ end
721
+ return cb_id
722
+ end
715
723
 
716
- raise Bud::Error if @callbacks.has_key? @callback_id
717
- @callbacks[@callback_id] = [tbl_name, block]
718
- cb_id = @callback_id
719
- @callback_id += 1
724
+ def do_register_callback(tbl_name, &blk)
725
+ unless @tables.has_key? tbl_name
726
+ raise Bud::Error, "no such collection: #{tbl_name}"
720
727
  end
728
+
729
+ raise Bud::Error if @callbacks.has_key? @callback_id
730
+ @callbacks[@callback_id] = [tbl_name, blk]
731
+ cb_id = @callback_id
732
+ @callback_id += 1
721
733
  return cb_id
722
734
  end
723
735
 
@@ -735,10 +747,6 @@ module Bud
735
747
  # inserted into the output collection: these are returned to the caller.
736
748
  def sync_callback(in_tbl, tupleset, out_tbl)
737
749
  q = Queue.new
738
- # XXXX Why two separate entrances into the event loop? Why is the callback not registered in the sync_do below?
739
- cb = register_callback(out_tbl) do |c|
740
- q.push c.to_a
741
- end
742
750
 
743
751
  # If the runtime shuts down before we see anything in the output collection,
744
752
  # make sure we hear about it so we can raise an error
@@ -747,16 +755,21 @@ module Bud
747
755
  q.push :callback
748
756
  end
749
757
 
750
- unless in_tbl.nil?
751
- sync_do {
758
+ cb = nil
759
+ sync_do {
760
+ if in_tbl
752
761
  t = @tables[in_tbl]
753
762
  if t.class <= Bud::BudChannel or t.class <= Bud::BudZkTable
754
763
  t <~ tupleset
755
764
  else
756
765
  t <+ tupleset
757
766
  end
758
- }
759
- end
767
+ end
768
+
769
+ cb = do_register_callback(out_tbl) do |c|
770
+ q.push c.to_a
771
+ end
772
+ }
760
773
  result = q.pop
761
774
  if result == :callback
762
775
  # Don't try to unregister the callbacks first: runtime is already shutdown
@@ -764,7 +777,7 @@ module Bud
764
777
  end
765
778
  unregister_callback(cb)
766
779
  cancel_shutdown_cb(shutdown_cb)
767
- return (result == :callback) ? nil : result
780
+ return result
768
781
  end
769
782
 
770
783
  # A common special case for sync_callback: block on a delta to a table.
@@ -848,7 +861,7 @@ module Bud
848
861
  # instance that hasn't been started yet.
849
862
  return if @instance_id == ILLEGAL_INSTANCE_ID
850
863
  $signal_lock.synchronize {
851
- raise unless $bud_instances.has_key? @instance_id
864
+ raise Bud::Error unless $bud_instances.has_key? @instance_id
852
865
  $bud_instances.delete @instance_id
853
866
  @instance_id = ILLEGAL_INSTANCE_ID
854
867
  }
@@ -911,6 +924,28 @@ module Bud
911
924
  start(true)
912
925
  end
913
926
 
927
+ # Evaluate all bootstrap blocks and tick deltas
928
+ def do_bootstrap
929
+ # Evaluate bootstrap for imported modules
930
+ @this_rule_context = self
931
+ imported = import_defs.keys
932
+ imported.each do |mod_alias|
933
+ wrapper = import_instance mod_alias
934
+ wrapper.do_bootstrap
935
+ end
936
+ self.class.ancestors.reverse.each do |anc|
937
+ anc.instance_methods(false).each do |m|
938
+ if /^__bootstrap__/.match m
939
+ self.method(m.to_sym).call
940
+ end
941
+ end
942
+ end
943
+ bootstrap
944
+
945
+ @tables.each_value {|t| t.bootstrap} if toplevel == self
946
+ @done_bootstrap = true
947
+ end
948
+
914
949
  # One timestep of Bloom execution. This MUST be invoked from the EventMachine
915
950
  # thread; it is not intended to be called directly by client code.
916
951
  def tick_internal
@@ -919,10 +954,11 @@ module Bud
919
954
  starttime = Time.now if options[:metrics]
920
955
  if options[:metrics] and not @endtime.nil?
921
956
  @metrics[:betweentickstats] ||= initialize_stats
922
- @metrics[:betweentickstats] = running_stats(@metrics[:betweentickstats], starttime - @endtime)
957
+ @metrics[:betweentickstats] = running_stats(@metrics[:betweentickstats],
958
+ starttime - @endtime)
923
959
  end
924
960
  @inside_tick = true
925
-
961
+
926
962
  unless @done_bootstrap
927
963
  do_bootstrap
928
964
  do_wiring
@@ -932,27 +968,28 @@ module Bud
932
968
  @default_rescan.each {|elem| elem.rescan = true}
933
969
  @default_invalidate.each {|elem|
934
970
  elem.invalidated = true
935
- elem.invalidate_cache unless elem.class <= PushElement # call tick on tables here itself. The rest below.
971
+ # Call tick on tables here itself. The rest below
972
+ elem.invalidate_cache unless elem.class <= PushElement
936
973
  }
937
974
 
938
975
  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.
976
+ # The following loop invalidates additional (non-default) elements and
977
+ # tables that depend on the run-time invalidation state of a table.
941
978
  # Loop once to set the flags
942
979
  num_strata.times do |stratum|
943
980
  @scanners[stratum].each_value do |scanner|
944
981
  if scanner.rescan
945
982
  scanner.rescan_set.each {|e| e.rescan = true}
946
983
  scanner.invalidate_set.each {|e|
947
- e.invalidated = true;
984
+ e.invalidated = true
948
985
  e.invalidate_cache unless e.class <= PushElement
949
- }
986
+ }
950
987
  end
951
988
  end
952
989
  end
953
- #Loop a second time to actually call invalidate_cache
990
+ # Loop a second time to actually call invalidate_cache
954
991
  num_strata.times do |stratum|
955
- @push_sorted_elems[stratum].each { |elem| elem.invalidate_cache if elem.invalidated}
992
+ @push_sorted_elems[stratum].each {|e| e.invalidate_cache if e.invalidated}
956
993
  end
957
994
  end
958
995
 
@@ -970,20 +1007,20 @@ module Bud
970
1007
  # tick deltas on any merge targets and look for more deltas
971
1008
  # check to see if any joins saw a delta
972
1009
  push_joins[stratum].each do |p|
973
- if p.found_delta==true
974
- fixpoint = false
1010
+ if p.found_delta
1011
+ fixpoint = false
975
1012
  p.tick_deltas
976
1013
  end
977
1014
  end
978
- merge_targets[stratum].each_key do |t|
1015
+ merge_targets[stratum].each do |t|
979
1016
  fixpoint = false if t.tick_deltas
980
1017
  end
981
1018
  end
982
1019
  # push end-of-fixpoint
983
- @push_sorted_elems[stratum].each {|p|
1020
+ @push_sorted_elems[stratum].each do |p|
984
1021
  p.stratum_end
985
- }
986
- merge_targets[stratum].each_key do |t|
1022
+ end
1023
+ merge_targets[stratum].each do |t|
987
1024
  t.flush_deltas
988
1025
  end
989
1026
  end
@@ -1019,7 +1056,7 @@ module Bud
1019
1056
 
1020
1057
  private
1021
1058
 
1022
- # Builtin BUD state (predefined collections). We could define this using the
1059
+ # Builtin Bud state (predefined collections). We could define this using the
1023
1060
  # standard state block syntax, but we want to ensure that builtin state is
1024
1061
  # initialized before user-defined state.
1025
1062
  def builtin_state
@@ -1028,12 +1065,11 @@ module Bud
1028
1065
 
1029
1066
  loopback :localtick, [:col1]
1030
1067
  @stdio = terminal :stdio
1031
- signal :signals, [:name]
1032
1068
  scratch :halt, [:key]
1033
1069
  @periodics = table :periodics_tbl, [:pername] => [:period]
1034
1070
 
1035
1071
  # for BUD reflection
1036
- table :t_rules, [:bud_obj, :rule_id] => [:lhs, :op, :src, :orig_src]
1072
+ table :t_rules, [:bud_obj, :rule_id] => [:lhs, :op, :src, :orig_src, :nm_funcs_called]
1037
1073
  table :t_depends, [:bud_obj, :rule_id, :lhs, :op, :body] => [:nm]
1038
1074
  table :t_provides, [:interface] => [:input]
1039
1075
  table :t_underspecified, t_provides.schema
@@ -1072,10 +1108,10 @@ module Bud
1072
1108
  end
1073
1109
 
1074
1110
  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
1111
+ # This routine evals the rules in a given stratum, which results in a wiring
1112
+ # of PushElements
1113
+ @this_stratum = strat_num
1077
1114
  rules.each_with_index do |rule, i|
1078
- @this_rule = i
1079
1115
  @this_rule_context = rule.bud_obj # user-supplied code blocks will be evaluated in this context at run-time
1080
1116
  begin
1081
1117
  eval_rule(rule.bud_obj, rule.src)
@@ -1123,7 +1159,7 @@ module Bud
1123
1159
  Bud.shutdown_all_instances(false)
1124
1160
 
1125
1161
  $got_shutdown_signal = false
1126
- $setup_signal_handler = false
1162
+ $signal_handler_setup = false
1127
1163
 
1128
1164
  yield
1129
1165
  end
@@ -1146,15 +1182,11 @@ module Bud
1146
1182
  $signal_lock.synchronize {
1147
1183
  # If we setup signal handlers and then fork a new process, we want to
1148
1184
  # reinitialize the signal handler in the child process.
1149
- unless b.options[:signal_handling] == :none or $signal_handler_setup
1185
+ unless b.options[:signal_handling] == :none || $signal_handler_setup
1150
1186
  EventMachine::PeriodicTimer.new(SIGNAL_CHECK_PERIOD) do
1151
1187
  if $got_shutdown_signal
1152
- if b.options[:signal_handling] == :bloom
1153
- Bud.tick_all_instances
1154
- else
1155
- Bud.shutdown_all_instances
1156
- Bud.stop_em_loop
1157
- end
1188
+ Bud.shutdown_all_instances
1189
+ Bud.stop_em_loop
1158
1190
  $got_shutdown_signal = false
1159
1191
  end
1160
1192
  end
@@ -1162,10 +1194,9 @@ module Bud
1162
1194
  ["INT", "TERM"].each do |signal|
1163
1195
  Signal.trap(signal) {
1164
1196
  $got_shutdown_signal = true
1165
- b.sync_do{b.signals.pending_merge([[signal]])}
1166
1197
  }
1167
1198
  end
1168
- $setup_signal_handler_pid = true
1199
+ $signal_handler_setup = true
1169
1200
  end
1170
1201
 
1171
1202
  $instance_id += 1
@@ -1174,15 +1205,6 @@ module Bud
1174
1205
  }
1175
1206
  end
1176
1207
 
1177
- def self.tick_all_instances
1178
- instances = nil
1179
- $signal_lock.synchronize {
1180
- instances = $bud_instances.clone
1181
- }
1182
-
1183
- instances.each_value {|b| b.sync_do }
1184
- end
1185
-
1186
1208
  def self.shutdown_all_instances(do_shutdown_cb=true)
1187
1209
  instances = nil
1188
1210
  $signal_lock.synchronize {