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/History.txt +23 -0
- data/{README → README.md} +6 -2
- data/docs/cheat.md +1 -8
- data/docs/intro.md +1 -1
- data/lib/bud/aggs.rb +16 -16
- data/lib/bud/bud_meta.rb +8 -15
- data/lib/bud/collections.rb +85 -172
- data/lib/bud/errors.rb +5 -1
- data/lib/bud/executor/elements.rb +133 -118
- data/lib/bud/executor/group.rb +6 -6
- data/lib/bud/executor/join.rb +25 -22
- data/lib/bud/metrics.rb +1 -1
- data/lib/bud/monkeypatch.rb +18 -29
- data/lib/bud/rebl.rb +5 -4
- data/lib/bud/rewrite.rb +21 -160
- data/lib/bud/source.rb +5 -5
- data/lib/bud/state.rb +13 -12
- data/lib/bud/storage/dbm.rb +13 -23
- data/lib/bud/storage/zookeeper.rb +0 -4
- data/lib/bud.rb +184 -162
- metadata +144 -216
- data/docs/deploy.md +0 -96
- data/lib/bud/deploy/countatomicdelivery.rb +0 -38
- data/lib/bud/joins.rb +0 -526
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 :
|
66
|
-
attr_reader :
|
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
|
70
|
-
attr_accessor :
|
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
|
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, "
|
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, "
|
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
|
-
|
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
|
-
#
|
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 =
|
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
|
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
|
-
|
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
|
261
|
-
qrname = local_name
|
262
|
-
self.t_depends << [imp_dep.bud_obj, imp_dep.rule_id, qlname,
|
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
|
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 =
|
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 =
|
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 =
|
278
|
-
|
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
|
-
#
|
335
|
-
# We start @app_tables off as a set, then convert to
|
336
|
-
|
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.
|
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
|
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 |
|
345
|
+
@merge_targets.each_with_index do |stratum_targets, stratum|
|
370
346
|
@scanners[stratum].each_value do |s|
|
371
|
-
|
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
|
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 =
|
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.
|
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
|
-
#
|
435
|
-
#
|
436
|
-
|
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
|
-
|
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
|
-
|
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
|
455
|
-
# if that table were to be
|
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
|
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
|
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
|
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
|
537
|
-
# UDP server socket. If do_tick is
|
538
|
-
#
|
539
|
-
#
|
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, &
|
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
|
-
|
713
|
-
|
714
|
-
|
719
|
+
cb_id = do_register_callback(tbl_name, &blk)
|
720
|
+
end
|
721
|
+
return cb_id
|
722
|
+
end
|
715
723
|
|
716
|
-
|
717
|
-
|
718
|
-
|
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
|
-
|
751
|
-
|
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
|
-
|
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
|
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],
|
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
|
-
|
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
|
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 {
|
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
|
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].
|
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
|
1020
|
+
@push_sorted_elems[stratum].each do |p|
|
984
1021
|
p.stratum_end
|
985
|
-
|
986
|
-
merge_targets[stratum].
|
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
|
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
|
1076
|
-
|
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
|
-
$
|
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
|
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
|
-
|
1153
|
-
|
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
|
-
$
|
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 {
|