bud 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
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]