bud 0.0.8 → 0.1.0.pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +4 -10
- data/bin/budplot +1 -2
- data/docs/cheat.md +2 -15
- data/examples/basics/paths.rb +7 -7
- data/lib/bud/aggs.rb +15 -19
- data/lib/bud/bud_meta.rb +165 -77
- data/lib/bud/bust/bust.rb +11 -4
- data/lib/bud/collections.rb +643 -280
- data/lib/bud/depanalysis.rb +50 -25
- data/lib/bud/executor/elements.rb +592 -0
- data/lib/bud/executor/group.rb +104 -0
- data/lib/bud/executor/join.rb +638 -0
- data/lib/bud/graphs.rb +12 -11
- data/lib/bud/joins.rb +2 -1
- data/lib/bud/meta_algebra.rb +5 -4
- data/lib/bud/metrics.rb +9 -3
- data/lib/bud/monkeypatch.rb +131 -23
- data/lib/bud/rebl.rb +41 -28
- data/lib/bud/rewrite.rb +112 -440
- data/lib/bud/server.rb +3 -2
- data/lib/bud/source.rb +109 -0
- data/lib/bud/state.rb +16 -9
- data/lib/bud/storage/dbm.rb +62 -16
- data/lib/bud/storage/zookeeper.rb +2 -2
- data/lib/bud/viz.rb +8 -4
- data/lib/bud/viz_util.rb +10 -9
- data/lib/bud.rb +413 -199
- metadata +40 -55
- data/examples/deploy/tokenring-ec2.rb +0 -26
- data/examples/deploy/tokenring-fork.rb +0 -15
- data/examples/deploy/tokenring-thread.rb +0 -15
- data/examples/deploy/tokenring.rb +0 -47
- data/lib/bud/deploy/deployer.rb +0 -67
- data/lib/bud/deploy/ec2deploy.rb +0 -199
- data/lib/bud/deploy/forkdeploy.rb +0 -90
- data/lib/bud/deploy/threaddeploy.rb +0 -38
- data/lib/bud/storage/tokyocabinet.rb +0 -190
- data/lib/bud/stratify.rb +0 -85
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 '
|
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, :
|
70
|
-
attr_reader :
|
71
|
-
attr_reader :
|
72
|
-
|
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
|
-
|
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
|
-
@
|
116
|
+
@table_meta = []
|
117
|
+
@stratified_rules = []
|
117
118
|
@channels = {}
|
118
|
-
@
|
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
|
-
@
|
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
|
-
|
151
|
-
relatives.each do |r|
|
152
|
-
Bud.rewrite_local_methods(r)
|
153
|
-
end
|
157
|
+
builtin_state
|
154
158
|
|
155
|
-
|
159
|
+
resolve_imports
|
156
160
|
|
157
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
193
|
+
def toplevel
|
194
|
+
@toplevel = (@options[:toplevel] || self)
|
195
|
+
end
|
195
196
|
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
208
|
-
|
209
|
-
|
210
|
-
with_expander = WithExpander.new
|
211
|
-
r2r = Ruby2Ruby.new
|
201
|
+
def toplevel?
|
202
|
+
toplevel.object_id == self.object_id
|
203
|
+
end
|
212
204
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
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
|
-
|
231
|
-
|
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
|
-
#
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
-
@
|
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, "
|
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
|
-
|
714
|
-
|
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
|
-
@
|
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
|
-
|
1031
|
+
signal :signals, [:name]
|
761
1032
|
scratch :halt, [:key]
|
762
1033
|
@periodics = table :periodics_tbl, [:pername] => [:period]
|
763
1034
|
|
764
|
-
#
|
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
|
801
|
-
#
|
802
|
-
|
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
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
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
|
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
|