bud 0.9.4 → 0.9.5

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/lib/bud/server.rb CHANGED
@@ -55,11 +55,22 @@ class Bud::BudServer < EM::Connection #:nodoc: all
55
55
  end
56
56
 
57
57
  def message_received(obj)
58
- unless (obj.class <= Array and obj.length == 2 and
59
- @bud.tables.include?(obj[0].to_sym) and obj[1].class <= Array)
58
+ unless (obj.class <= Array and obj.length == 3 and
59
+ @bud.tables.include?(obj[0].to_sym) and
60
+ obj[1].class <= Array and obj[2].class <= Array)
60
61
  raise Bud::Error, "bad inbound message of class #{obj.class}: #{obj.inspect}"
61
62
  end
62
63
 
64
+ # Deserialize any nested marshalled values
65
+ tbl_name, tuple, marshall_indexes = obj
66
+ marshall_indexes.each do |i|
67
+ if i < 0 || i >= tuple.length
68
+ raise Bud::Error, "bad inbound message: marshalled value at index #{i}, #{obj.inspect}"
69
+ end
70
+ tuple[i] = Marshal.load(tuple[i])
71
+ end
72
+
73
+ obj = [tbl_name, tuple]
63
74
  @bud.rtracer.recv(obj) if @bud.options[:rtrace]
64
75
  @filter_buf[obj[0].to_sym] ||= []
65
76
  @filter_buf[obj[0].to_sym] << obj[1]
data/lib/bud/source.rb CHANGED
@@ -1,6 +1,4 @@
1
1
  require 'rubygems'
2
- require 'ruby_parser'
3
- require 'bud/errors'
4
2
 
5
3
  module Source
6
4
  $cached_file_info = Struct.new(:curr_file, :lines, :last_state_bloom_line).new
@@ -8,35 +6,53 @@ module Source
8
6
  # Reads the block corresponding to the location (string of the form
9
7
  # "file:line_num"). Returns an ast for the block.
10
8
  def Source.read_block(location)
11
- raise Bud::IllegalSourceError, "source must be present in a file; cannot read interactive shell or eval block" if location.start_with? '('
9
+ if location.start_with? '('
10
+ raise Bud::IllegalSourceError, "source must be present in a file; cannot read interactive shell or eval block"
11
+ end
12
12
  location =~ /^(.*):(\d+)/
13
13
  filename, num = $1, $2.to_i
14
- raise Bud::IllegalSourceError, "couldn't determine filename from backtrace" if filename.nil?
14
+ if filename.nil?
15
+ raise Bud::IllegalSourceError, "couldn't determine filename from backtrace"
16
+ end
15
17
  lines = cache(filename, num)
16
18
  # Note: num is 1-based.
17
19
 
18
- src_asts = [] # array of SrcAsts to be returned
19
- ruby_parser = RubyParser.new
20
-
21
- stmt = "" # collection of lines that form one complete ruby statement
22
- endok = true #
20
+ parser = make_parser
21
+ stmt = "" # collection of lines that form one complete Ruby statement
23
22
  ast = nil
24
23
  lines[num .. -1].each do |l|
25
24
  next if l =~ /^\s*#/
26
- break if endok and l =~ /^\s*([}]|end)/
27
- stmt += l + "\n"
28
- begin
29
- ast = ruby_parser.parse stmt
30
- endok = true
31
- rescue => ex
32
- # puts "Syntax Error on #{l}: #{ex}"
33
- endok = false
34
- ast = nil
25
+ if l =~ /^\s*([}]|end)/
26
+ # We found some syntax that looks like it might terminate the Ruby
27
+ # statement. Hence, try to parse it; if we don't find a syntax error,
28
+ # we're done.
29
+ begin
30
+ ast = parser.parse stmt
31
+ break
32
+ rescue
33
+ ast = nil
34
+ end
35
35
  end
36
+ stmt += l + "\n"
36
37
  end
37
38
  ast
38
39
  end
39
40
 
41
+ # ruby_parser 3.x can produce either 1.8- or 1.9-compatible ASTs. Since we
42
+ # want to eventually turn the ASTs back into code we can eval() using the
43
+ # current Ruby runtime, we want to generate an appropriately compatible AST.
44
+ def Source.make_parser
45
+ maj, min, patch = RUBY_VERSION.split(".")
46
+ case [maj.to_i, min.to_i]
47
+ when [1, 9] then
48
+ Ruby19Parser.new
49
+ when [1, 8] then
50
+ Ruby18Parser.new
51
+ else
52
+ raise Bud::Error, "unrecognized RUBY_VERSION: #{RUBY_VERSION}"
53
+ end
54
+ end
55
+
40
56
  def Source.cache(filename, num) # returns array of lines
41
57
  if $cached_file_info.curr_file == filename
42
58
  retval = $cached_file_info.lines
data/lib/bud/state.rb CHANGED
@@ -2,7 +2,7 @@ module Bud
2
2
  ######## methods for registering collection types
3
3
  private
4
4
  def check_collection_name(name)
5
- if @tables.has_key? name
5
+ if @tables.has_key? name or @lattices.has_key? name
6
6
  raise Bud::CompileError, "collection already exists: #{name}"
7
7
  end
8
8
 
@@ -26,6 +26,18 @@ module Bud
26
26
  end
27
27
  end
28
28
 
29
+ def define_lattice(name)
30
+ check_collection_name(name)
31
+
32
+ self.singleton_class.send(:define_method, name) do |*args, &blk|
33
+ if blk.nil?
34
+ return @lattices[name]
35
+ else
36
+ return @lattices[name].pro(&blk)
37
+ end
38
+ end
39
+ end
40
+
29
41
  public
30
42
  def input # :nodoc: all
31
43
  true
@@ -140,4 +152,81 @@ module Bud
140
152
  @tables[name] = Bud::BudTerminal.new(name, [:line], self)
141
153
  @channels[name] = @tables[name]
142
154
  end
155
+
156
+ # an alternative approach to declaring interfaces
157
+ def interfaces(direction, collections)
158
+ mode = case direction
159
+ when :input then true
160
+ when :output then false
161
+ else
162
+ raise Bud::CompileError, "unrecognized interface type #{direction}"
163
+ end
164
+ collections.each do |tab|
165
+ t_provides << [tab.to_s, mode]
166
+ end
167
+ end
168
+
169
+ # Define methods to implement the state declarations for every registered kind
170
+ # of lattice.
171
+ def load_lattice_defs
172
+ Bud::Lattice.global_mfuncs.each do |m|
173
+ next if RuleRewriter::MONOTONE_WHITELIST.include? m
174
+ if Bud::BudCollection.instance_methods.include? m.to_s
175
+ puts "monotone method #{m} conflicts with non-monotonic method in BudCollection"
176
+ end
177
+ end
178
+
179
+ Bud::Lattice.global_morphs.each do |m|
180
+ next if RuleRewriter::MONOTONE_WHITELIST.include? m
181
+ if Bud::BudCollection.instance_methods.include? m.to_s
182
+ puts "morphism #{m} conflicts with non-monotonic method in BudCollection"
183
+ end
184
+ end
185
+
186
+ # Sanity-check lattice definitions
187
+ # XXX: We should do this only once per lattice
188
+ Bud::Lattice.lattice_kinds.each do |wrap_name, klass|
189
+ unless klass.method_defined? :merge
190
+ raise Bud::CompileError, "lattice #{wrap_name} does not define a merge function"
191
+ end
192
+
193
+ # If a method is marked as monotone in any lattice, every lattice that
194
+ # declares a method of that name must also mark it as monotone.
195
+ meth_list = klass.instance_methods(false).to_set
196
+ Bud::Lattice.global_mfuncs.each do |m|
197
+ next unless meth_list.include? m.to_s
198
+ unless klass.mfuncs.include? m
199
+ raise Bud::CompileError, "method #{m} in #{wrap_name} must be monotone"
200
+ end
201
+ end
202
+
203
+ # Apply a similar check for morphs
204
+ Bud::Lattice.global_morphs.each do |m|
205
+ next unless meth_list.include? m.to_s
206
+ unless klass.morphs.include? m
207
+ raise Bud::CompileError, "method #{m} in #{wrap_name} must be a morph"
208
+ end
209
+ end
210
+
211
+ # Similarly, check for non-monotone lattice methods that are found in the
212
+ # builtin list of monotone operators. The "merge" method is implicitly
213
+ # monotone (XXX: should it be declared as a morph or monotone function?)
214
+ meth_list.each do |m_str|
215
+ m = m_str.to_sym
216
+ next unless RuleRewriter::MONOTONE_WHITELIST.include? m
217
+ # XXX: ugly hack. We want to allow lattice class implementations to
218
+ # define their own equality semantics.
219
+ next if m == :==
220
+ unless klass.mfuncs.include?(m) || klass.morphs.include?(m) || m == :merge
221
+ raise Bud::CompileError, "method #{m} in #{wrap_name} must be monotone"
222
+ end
223
+ end
224
+
225
+ # XXX: replace "self" with toplevel?
226
+ self.singleton_class.send(:define_method, wrap_name) do |lat_name|
227
+ define_lattice(lat_name)
228
+ @lattices[lat_name] = Bud::LatticeWrapper.new(lat_name, klass, self)
229
+ end
230
+ end
231
+ end
143
232
  end
@@ -8,40 +8,63 @@ module Bud
8
8
  # Persistent table implementation based on Zookeeper.
9
9
  class BudZkTable < BudPersistentCollection # :nodoc: all
10
10
  def initialize(name, zk_path, zk_addr, bud_instance)
11
- unless defined? HAVE_ZOOKEEPER
11
+ unless defined? Bud::HAVE_ZOOKEEPER
12
12
  raise Bud::Error, "zookeeper gem is not installed: zookeeper-backed stores cannot be used"
13
13
  end
14
14
 
15
- # schema = {[:key] => [:val]}
16
- super(name, bud_instance, nil)
15
+ super(name, bud_instance, [:key] => [:val, :opts])
17
16
 
18
- zk_path = zk_path.chomp("/") unless zk_path == "/"
19
17
  @zk = Zookeeper.new(zk_addr)
18
+ zk_path = zk_path.chomp("/") unless zk_path == "/"
20
19
  @zk_path = zk_path
21
20
  @base_path = @zk_path
22
21
  @base_path += "/" unless @zk_path.end_with? "/"
23
22
  @store_mutex = Mutex.new
23
+ @zk_mutex = Mutex.new
24
24
  @next_storage = {}
25
25
  @saw_delta = false
26
26
  @child_watch_id = nil
27
- @stat_watch_id = nil
28
27
  end
29
28
 
30
29
  # Since the watcher callbacks might invoke EventMachine, we wait until after
31
30
  # EM startup to start watching for Zk events.
32
31
  def start_watchers
33
- # NB: Watcher callbacks are invoked in a separate Ruby thread.
34
- @child_watcher = Zookeeper::WatcherCallback.new { get_and_watch }
35
- @stat_watcher = Zookeeper::WatcherCallback.new { stat_and_watch }
32
+ # Watcher callbacks are invoked in a separate Ruby thread. Note that there
33
+ # is a possible deadlock between invoking watcher callbacks and calling
34
+ # close(): if we get a watcher event and a close at around the same time,
35
+ # the close might fire first. Closing the Zk handle will block on
36
+ # dispatching outstanding watchers, but it does so holding the @zk_mutex,
37
+ # causing a deadlock. Hence, we just have the watcher callback spin on the
38
+ # @zk_mutex, aborting if the handle is ever closed.
39
+ @child_watcher = Zookeeper::Callbacks::WatcherCallback.new do
40
+ while true
41
+ break if @zk.closed?
42
+ if @zk_mutex.try_lock
43
+ get_and_watch unless @zk.closed?
44
+ @zk_mutex.unlock
45
+ break
46
+ end
47
+ end
48
+ end
49
+
50
+ @stat_watcher = Zookeeper::Callbacks::WatcherCallback.new do
51
+ while true
52
+ break if @zk.closed?
53
+ if @zk_mutex.try_lock
54
+ stat_and_watch unless @zk.closed?
55
+ @zk_mutex.unlock
56
+ break
57
+ end
58
+ end
59
+ end
60
+
36
61
  stat_and_watch
37
62
  end
38
63
 
39
64
  def stat_and_watch
40
65
  r = @zk.stat(:path => @zk_path, :watcher => @stat_watcher)
41
- @stat_watch_id = r[:req_id]
42
66
 
43
67
  unless r[:stat].exists
44
- cancel_child_watch
45
68
  # The given @zk_path doesn't exist, so try to create it. Unclear
46
69
  # whether this is always the best behavior.
47
70
  r = @zk.create(:path => @zk_path)
@@ -54,27 +77,10 @@ module Bud
54
77
  get_and_watch unless @child_watch_id
55
78
  end
56
79
 
57
- def cancel_child_watch
58
- if @child_watch_id
59
- @zk.unregister_watcher(@child_watch_id)
60
- @child_watch_id = nil
61
- end
62
- end
63
-
64
- def cancel_stat_watch
65
- if @stat_watch_id
66
- @zk.unregister_watcher(@stat_watch_id)
67
- @stat_with_id = nil
68
- end
69
- end
70
-
71
80
  def get_and_watch
72
81
  r = @zk.get_children(:path => @zk_path, :watcher => @child_watcher)
82
+ return unless r[:stat].exists
73
83
  @child_watch_id = r[:req_id]
74
- unless r[:stat].exists
75
- cancel_child_watch
76
- return
77
- end
78
84
 
79
85
  # XXX: can we easily get snapshot isolation?
80
86
  new_children = {}
@@ -128,8 +134,8 @@ module Bud
128
134
  ephemeral = false
129
135
  sequence = false
130
136
 
131
- if t.length > 2
132
- opts = t.last.first
137
+ opts = t.opts
138
+ unless opts.nil?
133
139
  if opts[:ephemeral] == true
134
140
  ephemeral = true
135
141
  end
@@ -150,9 +156,8 @@ module Bud
150
156
  end
151
157
 
152
158
  def close
153
- cancel_child_watch
154
- cancel_stat_watch
155
- @zk.close
159
+ # See notes in start_watchers.
160
+ @zk_mutex.synchronize { @zk.close }
156
161
  end
157
162
 
158
163
  superator "<~" do |o|
data/lib/bud/viz.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'bud/state'
2
- require 'set'
3
2
 
4
3
  class VizOnline #:nodoc: all
5
4
  attr_reader :logtab
data/lib/bud.rb CHANGED
@@ -1,16 +1,27 @@
1
1
  require 'rubygems'
2
+ gem 'ruby2ruby', '>= 2.0.1'
3
+ gem 'ruby_parser', '>= 3.0.2'
4
+
2
5
  require 'eventmachine'
3
6
  require 'msgpack'
7
+ require 'ruby2ruby'
8
+ require 'ruby_parser'
9
+ require 'set'
4
10
  require 'socket'
5
11
  require 'superators19'
6
12
  require 'thread'
7
- require 'bud/errors'
8
13
 
14
+ require 'bud/errors'
9
15
  require 'bud/monkeypatch'
10
16
 
11
17
  require 'bud/aggs'
12
18
  require 'bud/bud_meta'
13
19
  require 'bud/collections'
20
+ require 'bud/executor/elements.rb'
21
+ require 'bud/executor/group.rb'
22
+ require 'bud/executor/join.rb'
23
+ require 'bud/lattice-core'
24
+ require 'bud/lattice-lib'
14
25
  require 'bud/metrics'
15
26
  require 'bud/rtrace'
16
27
  require 'bud/server'
@@ -19,10 +30,6 @@ require 'bud/storage/dbm'
19
30
  require 'bud/storage/zookeeper'
20
31
  require 'bud/viz'
21
32
 
22
- require 'bud/executor/elements.rb'
23
- require 'bud/executor/group.rb'
24
- require 'bud/executor/join.rb'
25
-
26
33
  ILLEGAL_INSTANCE_ID = -1
27
34
  SIGNAL_CHECK_PERIOD = 0.2
28
35
 
@@ -61,7 +68,7 @@ $bud_instances = {} # Map from instance id => Bud instance
61
68
  # :main: Bud
62
69
  module Bud
63
70
  attr_reader :budtime, :inbound, :options, :meta_parser, :viz, :rtracer, :dsock
64
- attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :app_tables
71
+ attr_reader :tables, :builtin_tables, :channels, :zk_tables, :dbm_tables, :app_tables, :lattices
65
72
  attr_reader :push_sources, :push_elems, :push_joins, :scanners, :merge_targets
66
73
  attr_reader :this_stratum, :this_rule, :rule_orig_src, :done_bootstrap
67
74
  attr_accessor :stratified_rules
@@ -109,6 +116,7 @@ module Bud
109
116
  options[:print_wiring] ||= ENV["BUD_PRINT_WIRING"].to_i > 0
110
117
  @qualified_name = ""
111
118
  @tables = {}
119
+ @lattices = {}
112
120
  @channels = {}
113
121
  @dbm_tables = {}
114
122
  @zk_tables = {}
@@ -144,6 +152,7 @@ module Bud
144
152
  # NB: If using an ephemeral port (specified by port = 0), the actual port
145
153
  # number won't be known until we start EM
146
154
 
155
+ load_lattice_defs
147
156
  builtin_state
148
157
  resolve_imports
149
158
  call_state_methods
@@ -248,6 +257,11 @@ module Bud
248
257
  tables[qname.to_sym] = t
249
258
  end
250
259
  end
260
+ mod_inst.lattices.each_pair do |name, t|
261
+ qname = "#{local_name}.#{name}".to_sym
262
+ raise Bud::Error if lattices.has_key? qname
263
+ lattices[qname] = t
264
+ end
251
265
  mod_inst.t_rules.each do |imp_rule|
252
266
  qname = "#{local_name}.#{imp_rule.lhs}"
253
267
  self.t_rules << [imp_rule.bud_obj, imp_rule.rule_id, qname, imp_rule.op,
@@ -257,7 +271,7 @@ module Bud
257
271
  qlname = "#{local_name}.#{imp_dep.lhs}"
258
272
  qrname = "#{local_name}.#{imp_dep.body}"
259
273
  self.t_depends << [imp_dep.bud_obj, imp_dep.rule_id, qlname,
260
- imp_dep.op, qrname, imp_dep.nm]
274
+ imp_dep.op, qrname, imp_dep.nm, imp_dep.in_body]
261
275
  end
262
276
  mod_inst.t_provides.each do |imp_pro|
263
277
  qintname = "#{local_name}.#{imp_pro.interface}"
@@ -309,9 +323,10 @@ module Bud
309
323
  @stratified_rules.each_with_index { |rules, stratum| eval_rules(rules, stratum) }
310
324
 
311
325
  # Prepare list of tables that will be actively used at run time. First, all
312
- # the user-defined ones. We start @app_tables off as a set, then convert to
313
- # an array later.
326
+ # the user-defined tables and lattices. We start @app_tables off as a set,
327
+ # then convert to an array later.
314
328
  @app_tables = (@tables.keys - @builtin_tables.keys).map {|t| @tables[t]}.to_set
329
+ @app_tables += @lattices.values
315
330
 
316
331
  # Check scan and merge_targets to see if any builtin_tables need to be added as well.
317
332
  @scanners.each do |scs|
@@ -335,7 +350,7 @@ module Bud
335
350
  wired_to = []
336
351
  working.each do |e|
337
352
  e.wirings.each do |out|
338
- if (out.class <= PushElement and not seen.member?(out))
353
+ if ((out.class <= PushElement || out.class <= LatticePushElement) and not seen.member?(out))
339
354
  seen << out
340
355
  wired_to << out
341
356
  end
@@ -419,7 +434,7 @@ module Bud
419
434
  def prepare_invalidation_scheme
420
435
  num_strata = @push_sorted_elems.size
421
436
  if $BUD_SAFE
422
- @app_tables = @tables.values # No tables excluded
437
+ @app_tables = @tables.values + @lattices.values # No collections excluded
423
438
 
424
439
  rescan = Set.new
425
440
  invalidate = @app_tables.select {|t| t.class <= BudScratch}.to_set
@@ -445,7 +460,10 @@ module Bud
445
460
  nm_targets = Set.new
446
461
  t_rules.each do |rule|
447
462
  lhs = rule.lhs.to_sym
448
- @tables[lhs].is_source = false if rule.op == "<="
463
+ if rule.op == "<="
464
+ # Note that lattices cannot be sources
465
+ @tables[lhs].is_source = false if @tables.has_key? lhs
466
+ end
449
467
  nm_targets << lhs if rule.nm_funcs_called
450
468
  end
451
469
 
@@ -459,7 +477,7 @@ module Bud
459
477
  @push_sorted_elems[stratum].each do |elem|
460
478
  rescan << elem if elem.rescan_at_tick
461
479
 
462
- if elem.outputs.any?{|tab| not(tab.class <= PushElement) and nm_targets.member? tab.qualified_tabname.to_sym }
480
+ if elem.outputs.any?{|tab| not(tab.class <= PushElement) and not(tab.class <= LatticePushElement) and nm_targets.member? tab.qualified_tabname.to_sym }
463
481
  rescan.merge(elem.wired_by)
464
482
  end
465
483
  end
@@ -499,6 +517,25 @@ module Bud
499
517
  end
500
518
  end
501
519
  @reset_list = to_reset.to_a
520
+
521
+ # For each lattice, find the set of tables that should be rescanned when
522
+ # there is a new delta for the lattice. That is, if we have a rule like:
523
+ # "t2 <= t1 {|t| [t.key, lat_foo]}", whenever there is a delta on lat_foo we
524
+ # should rescan t1 (to produce tuples with the updated lat_foo value).
525
+ # TODO:
526
+ # (1) support non-join ops to be rescanned (+ tests) + lambdas
527
+ # (2) if t1 is fed by rules r1 and r2 but only r1 references lattice x,
528
+ # don't trigger rescan of r2 on deltas for x (hard)
529
+ t_depends.each do |dep|
530
+ src, dst = dep.body.to_sym, dep.lhs.to_sym
531
+ if @lattices.has_key? src and @tables.has_key? dst and dep.in_body
532
+ src_lat = @lattices[src]
533
+ dst_tbl = @tables[dst]
534
+ dst_tbl.non_temporal_predecessors.each do |e|
535
+ src_lat.rescan_on_merge << e
536
+ end
537
+ end
538
+ end
502
539
  end
503
540
 
504
541
  # given rescan, invalidate sets, compute transitive closure
@@ -962,7 +999,10 @@ module Bud
962
999
  end
963
1000
  bootstrap
964
1001
 
965
- @tables.each_value {|t| t.bootstrap} if toplevel == self
1002
+ if toplevel == self
1003
+ @tables.each_value {|t| t.bootstrap}
1004
+ @lattices.each_value {|l| l.bootstrap}
1005
+ end
966
1006
  @done_bootstrap = true
967
1007
  end
968
1008
 
@@ -1100,7 +1140,7 @@ module Bud
1100
1140
 
1101
1141
  # for BUD reflection
1102
1142
  table :t_rules, [:bud_obj, :rule_id] => [:lhs, :op, :src, :orig_src, :nm_funcs_called]
1103
- table :t_depends, [:bud_obj, :rule_id, :lhs, :op, :body] => [:nm]
1143
+ table :t_depends, [:bud_obj, :rule_id, :lhs, :op, :body] => [:nm, :in_body]
1104
1144
  table :t_provides, [:interface] => [:input]
1105
1145
  table :t_underspecified, t_provides.schema
1106
1146
  table :t_stratum, [:predicate] => [:stratum]