bud 0.0.8 → 0.1.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/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
|