bud 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/LICENSE +9 -0
  2. data/README +30 -0
  3. data/bin/budplot +134 -0
  4. data/bin/budvis +201 -0
  5. data/bin/rebl +4 -0
  6. data/docs/README.md +13 -0
  7. data/docs/bfs.md +379 -0
  8. data/docs/bfs.raw +251 -0
  9. data/docs/bfs_arch.png +0 -0
  10. data/docs/bloom-loop.png +0 -0
  11. data/docs/bust.md +83 -0
  12. data/docs/cheat.md +291 -0
  13. data/docs/deploy.md +96 -0
  14. data/docs/diffs +181 -0
  15. data/docs/getstarted.md +296 -0
  16. data/docs/intro.md +36 -0
  17. data/docs/modules.md +112 -0
  18. data/docs/operational.md +96 -0
  19. data/docs/rebl.md +99 -0
  20. data/docs/ruby_hooks.md +19 -0
  21. data/docs/visualizations.md +75 -0
  22. data/examples/README +1 -0
  23. data/examples/basics/hello.rb +12 -0
  24. data/examples/basics/out +1103 -0
  25. data/examples/basics/out.new +856 -0
  26. data/examples/basics/paths.rb +51 -0
  27. data/examples/bust/README.md +9 -0
  28. data/examples/bust/bustclient-example.rb +23 -0
  29. data/examples/bust/bustinspector.html +135 -0
  30. data/examples/bust/bustserver-example.rb +18 -0
  31. data/examples/chat/README.md +9 -0
  32. data/examples/chat/chat.rb +45 -0
  33. data/examples/chat/chat_protocol.rb +8 -0
  34. data/examples/chat/chat_server.rb +29 -0
  35. data/examples/deploy/tokenring-ec2.rb +26 -0
  36. data/examples/deploy/tokenring-local.rb +17 -0
  37. data/examples/deploy/tokenring.rb +39 -0
  38. data/lib/bud/aggs.rb +126 -0
  39. data/lib/bud/bud_meta.rb +185 -0
  40. data/lib/bud/bust/bust.rb +126 -0
  41. data/lib/bud/bust/client/idempotence.rb +10 -0
  42. data/lib/bud/bust/client/restclient.rb +49 -0
  43. data/lib/bud/collections.rb +937 -0
  44. data/lib/bud/depanalysis.rb +44 -0
  45. data/lib/bud/deploy/countatomicdelivery.rb +50 -0
  46. data/lib/bud/deploy/deployer.rb +67 -0
  47. data/lib/bud/deploy/ec2deploy.rb +200 -0
  48. data/lib/bud/deploy/localdeploy.rb +41 -0
  49. data/lib/bud/errors.rb +15 -0
  50. data/lib/bud/graphs.rb +405 -0
  51. data/lib/bud/joins.rb +300 -0
  52. data/lib/bud/rebl.rb +314 -0
  53. data/lib/bud/rewrite.rb +523 -0
  54. data/lib/bud/rtrace.rb +27 -0
  55. data/lib/bud/server.rb +43 -0
  56. data/lib/bud/state.rb +108 -0
  57. data/lib/bud/storage/tokyocabinet.rb +170 -0
  58. data/lib/bud/storage/zookeeper.rb +178 -0
  59. data/lib/bud/stratify.rb +83 -0
  60. data/lib/bud/viz.rb +65 -0
  61. data/lib/bud.rb +797 -0
  62. metadata +330 -0
@@ -0,0 +1,523 @@
1
+ require 'rubygems'
2
+ require 'ruby2ruby'
3
+
4
+ class RuleRewriter < Ruby2Ruby #:nodoc: all
5
+ attr_accessor :rule_indx, :rules, :depends
6
+
7
+ def initialize(seed, bud_instance)
8
+ @bud_instance = bud_instance
9
+ @ops = {:<< => 1, :< => 1, :<= => 1}
10
+ @monotonic_whitelist = {
11
+ :== => 1, :+ => 1, :<= => 1, :- => 1, :< => 1, :> => 1,
12
+ :* => 1, :pairs => 1, :matches => 1, :flatten => 1,
13
+ :lefts => 1, :rights => 1, :map => 1, :pro => 1, :schema => 1
14
+ }
15
+ @temp_ops = {:-@ => 1, :~ => 1, :+@ => 1}
16
+ @tables = {}
17
+ @nm = false
18
+ @rule_indx = seed
19
+ @collect = false
20
+ @rules = []
21
+ @depends = []
22
+ super()
23
+ end
24
+
25
+ def process_call(exp)
26
+ recv, op, args = exp
27
+ if recv.nil? and args == s(:arglist) and @collect
28
+ do_table(exp)
29
+ elsif @ops[op] and @context[1] == :block and @context.length == 4
30
+ # NB: context.length is 4 when see a method call at the top-level of a
31
+ # :defn block -- this is where we expect Bloom statements to appear
32
+ do_rule(exp)
33
+ else
34
+ if recv and recv.class == Sexp
35
+ # ignore accessors of iterator variables
36
+ unless recv.first == :lvar
37
+ @nm = true if op == :-@
38
+ @nm = true unless (@monotonic_whitelist[op] or @bud_instance.tables.has_key? op)
39
+ end
40
+ end
41
+ if @temp_ops[op]
42
+ @temp_op = op.to_s.gsub("@", "")
43
+ end
44
+ super
45
+ end
46
+ end
47
+
48
+ def collect_rhs(exp)
49
+ @collect = true
50
+ rhs = process exp
51
+ @collect = false
52
+ return rhs
53
+ end
54
+
55
+ def record_rule(lhs, op, rhs)
56
+ rule_txt = "#{lhs} #{op} (#{rhs})"
57
+ if op == :<
58
+ op = "<#{@temp_op}"
59
+ else
60
+ op = op.to_s
61
+ end
62
+
63
+ @rules << [@rule_indx, lhs, op, rule_txt]
64
+ @tables.each_pair do |t, non_monotonic|
65
+ @depends << [@rule_indx, lhs, op, t, non_monotonic]
66
+ end
67
+
68
+ @tables = {}
69
+ @nm = false
70
+ @temp_op = nil
71
+ @rule_indx += 1
72
+ end
73
+
74
+ def do_table(exp)
75
+ t = exp[1].to_s
76
+ # If we're called on a "table-like" part of the AST that doesn't correspond
77
+ # to an extant table, ignore it.
78
+ @tables[t] = @nm if @bud_instance.tables.has_key? t.to_sym
79
+ drain(exp)
80
+ return t
81
+ end
82
+
83
+ def do_rule(exp)
84
+ lhs = process exp[0]
85
+ op = exp[1]
86
+ rhs = collect_rhs(map2pro(exp[2]))
87
+ record_rule(lhs, op, rhs)
88
+ drain(exp)
89
+ end
90
+
91
+ # Look for top-level map on a base-table on rhs, and rewrite to pro
92
+ def map2pro(exp)
93
+ if exp[1] and exp[1][0] and exp[1][0] == :iter \
94
+ and exp[1][1] and exp[1][1][1] == :call \
95
+ and exp[1][1][2] == :map
96
+ exp[1][1][2] = :pro
97
+ end
98
+ exp
99
+ end
100
+
101
+ def drain(exp)
102
+ exp.shift until exp.empty?
103
+ return ""
104
+ end
105
+ end
106
+
107
+ # Given a table of renames from x => y, replace all calls to "x" with calls to
108
+ # "y" instead. We don't try to handle shadowing due to block variables: if a
109
+ # block references a block variable that shadows an identifier in the rename
110
+ # tbl, it should appear as an :lvar node rather than a :call, so we should be
111
+ # okay.
112
+ class CallRewriter < SexpProcessor # :nodoc: all
113
+ def initialize(rename_tbl)
114
+ super()
115
+ self.require_empty = false
116
+ self.expected = Sexp
117
+ @rename_tbl = rename_tbl
118
+ end
119
+
120
+ def process_call(exp)
121
+ tag, recv, meth_name, args = exp
122
+
123
+ if @rename_tbl.has_key? meth_name
124
+ meth_name = @rename_tbl[meth_name] # No need to deep-copy Symbol
125
+ end
126
+
127
+ recv = process(recv)
128
+ args = process(args)
129
+
130
+ s(tag, recv, meth_name, args)
131
+ end
132
+ end
133
+
134
+ # Rewrite qualified references to collections defined by an imported module. In
135
+ # the AST, this looks like a tree of :call nodes. For example, a.b.c looks like:
136
+ #
137
+ # (:call, (:call, (:call, nil, :a, args), :b, args), :c, args)
138
+ #
139
+ # If the import table contains [a][b], we want to rewrite this into a single
140
+ # call to a__b__c, which matches how the corresponding Bloom collection will
141
+ # be name-mangled. Note that we don't currently check that a__b__c (or a.b.c)
142
+ # corresponds to an extant Bloom collection.
143
+ class NestedRefRewriter < SexpProcessor # :nodoc: all
144
+ attr_accessor :did_work
145
+
146
+ def initialize(import_tbl)
147
+ super()
148
+ self.require_empty = false
149
+ self.expected = Sexp
150
+ @import_tbl = import_tbl
151
+ @did_work = false
152
+ end
153
+
154
+ def process_call(exp)
155
+ return exp if @import_tbl.empty?
156
+ tag, recv, meth_name, args = exp
157
+
158
+ catch :skip do
159
+ recv_stack = make_recv_stack(recv)
160
+ throw :skip unless recv_stack.length > 0
161
+
162
+ lookup_tbl = @import_tbl
163
+ new_meth_name = ""
164
+ until recv_stack.empty?
165
+ m = recv_stack.pop
166
+ throw :skip unless lookup_tbl.has_key? m
167
+
168
+ new_meth_name += "#{m}__"
169
+ lookup_tbl = lookup_tbl[m]
170
+ end
171
+
172
+ # Okay, apply the rewrite
173
+ @did_work = true
174
+ new_meth_name += meth_name.to_s
175
+ recv = nil
176
+ meth_name = new_meth_name.to_sym
177
+ end
178
+
179
+ recv = process(recv)
180
+ args = process(args)
181
+
182
+ s(tag, recv, meth_name, args)
183
+ end
184
+
185
+ private
186
+ def make_recv_stack(r)
187
+ rv = []
188
+
189
+ while true
190
+ break if r.nil?
191
+ # We can exit early if we see something unexpected
192
+ throw :skip unless r.sexp_type == :call
193
+
194
+ recv, meth_name, args = r.sexp_body
195
+ unless args.sexp_type == :arglist and args.sexp_body.length == 0
196
+ throw :skip
197
+ end
198
+
199
+ rv << meth_name
200
+ r = recv
201
+ end
202
+
203
+ return rv
204
+ end
205
+ end
206
+
207
+ # Look for temp declarations and remove the "temp" keyword, yielding code that
208
+ # we can safely eval. We also record the set of "temp" collections we've seen,
209
+ # and provide a helper method that returns the AST of a state block that
210
+ # contains declarations for all those temp tables.
211
+ class TempExpander < SexpProcessor # :nodoc: all
212
+ attr_reader :tmp_tables
213
+ attr_accessor :did_work
214
+
215
+ def initialize
216
+ super()
217
+ self.require_empty = false
218
+ self.expected = Sexp
219
+
220
+ @tmp_tables = []
221
+ @did_work = false
222
+ end
223
+
224
+ def process_defn(exp)
225
+ tag, name, args, scope = exp
226
+
227
+ if name.to_s =~ /^__bloom__.+/
228
+ block = scope[1]
229
+
230
+ block.each_with_index do |n,i|
231
+ if i == 0
232
+ raise Bud::CompileError if n != :block
233
+ next
234
+ end
235
+
236
+ # temp declarations are misparsed if the RHS contains certain constructs
237
+ # (e.g., old-style join syntax, group, "do |f| ... end" rather than
238
+ # "{|f| ... }"). Rewrite to correct the misparsing.
239
+ if n.sexp_type == :iter
240
+ iter_body = n.sexp_body
241
+
242
+ if iter_body.first.sexp_type == :call
243
+ call_node = iter_body.first
244
+
245
+ _, recv, meth, meth_args = call_node
246
+ if meth == :temp and recv.nil?
247
+ _, lhs, op, rhs = meth_args.sexp_body.first
248
+
249
+ old_rhs_body = rhs.sexp_body
250
+ rhs[1] = s(:iter)
251
+ rhs[1] += old_rhs_body
252
+ rhs[1] += iter_body[1..-1]
253
+ block[i] = n = call_node
254
+ @did_work = true
255
+ end
256
+ end
257
+ end
258
+
259
+ _, recv, meth, meth_args = n
260
+ if meth == :temp and recv.nil?
261
+ block[i] = rewrite_temp(n)
262
+ @did_work = true
263
+ end
264
+ end
265
+ end
266
+
267
+ s(tag, name, args, scope)
268
+ end
269
+
270
+ def get_state_meth(klass)
271
+ return if @tmp_tables.empty?
272
+ block = s(:block)
273
+
274
+ @tmp_tables.each do |t|
275
+ args = s(:arglist, s(:lit, t.to_sym))
276
+ block << s(:call, nil, :temp, args)
277
+ end
278
+
279
+ meth_name = Module.make_state_meth_name(klass).to_s + "__tmp"
280
+ return s(:defn, meth_name.to_sym, s(:args), s(:scope, block))
281
+ end
282
+
283
+ private
284
+ def rewrite_temp(exp)
285
+ _, recv, meth, args = exp
286
+
287
+ raise Bud::CompileError unless recv == nil
288
+ nest_call = args.sexp_body.first
289
+ raise Bud::CompileError unless nest_call.sexp_type == :call
290
+
291
+ nest_recv, nest_op, nest_args = nest_call.sexp_body
292
+ raise Bud::CompileError unless nest_recv.sexp_type == :lit
293
+
294
+ tmp_name = nest_recv.sexp_body.first
295
+ @tmp_tables << tmp_name
296
+ new_recv = s(:call, nil, tmp_name, s(:arglist))
297
+ return s(:call, new_recv, nest_op, nest_args)
298
+ end
299
+ end
300
+
301
+ class DefnRenamer < SexpProcessor # :nodoc: all
302
+ def initialize(old_mod_name, new_mod_name, local_name)
303
+ super()
304
+ self.require_empty = false
305
+ self.expected = Sexp
306
+ @old_mod_name = old_mod_name
307
+ @new_mod_name = new_mod_name
308
+ @local_name = local_name
309
+ end
310
+
311
+ def process_defn(exp)
312
+ tag, name, args, scope = exp
313
+ name_s = name.to_s
314
+
315
+ if name_s =~ /^__bootstrap__.+$/
316
+ name = name_s.sub(/^(__bootstrap__)(.+)$/, "\\1#{@local_name}__\\2").to_sym
317
+ elsif name_s =~ /^__state\d+__/
318
+ name = name_s.sub(/^(__state\d+__)(.*)$/, "\\1#{@local_name}__\\2").to_sym
319
+ elsif name_s =~ /^__bloom__.+$/
320
+ name = name_s.sub(/^(__bloom__)(.+)$/, "\\1#{@local_name}__\\2").to_sym
321
+ else
322
+ name = "#{@local_name}__#{name_s}".to_sym
323
+ end
324
+
325
+ # Note that we don't bother to recurse further into the AST: we're only
326
+ # interested in top-level :defn nodes.
327
+ s(tag, name, args, scope)
328
+ end
329
+ end
330
+
331
+ module ModuleRewriter # :nodoc: all
332
+ # Do the heavy-lifting to import the Bloom module "mod" into the class/module
333
+ # "import_site", bound to "local_name" at the import site. We implement this
334
+ # by converting the imported module into an AST and rewriting the AST like so:
335
+ #
336
+ # (a) the module name is mangled to include the local bind name and the
337
+ # importer
338
+ # (b) instance method names are mangled to include the local bind name
339
+ # (c) state defined by the module is mangled to include the local bind name
340
+ # (d) statements in the module are rewritten to reference the mangled names
341
+ # (e) statements in the module that reference sub-modules are rewritten to
342
+ # reference the mangled name of the submodule.
343
+ #
344
+ # We then convert the rewritten AST back into Ruby source code using Ruby2Ruby
345
+ # and eval() it to define a new module. We return the name of that newly
346
+ # defined module; the caller can then use "include" to load the module into
347
+ # the import site. Note that additional rewrites are needed to ensure that
348
+ # code in the import site that accesses module contents does the right thing;
349
+ # see Bud#rewrite_local_methods.
350
+ def self.do_import(import_site, mod, local_name)
351
+ ast = get_module_ast(mod)
352
+ ast = ast_flatten_nested_refs(ast, mod.bud_import_table)
353
+ ast = ast_process_temps(ast, mod)
354
+ ast, new_mod_name = ast_rename_module(ast, import_site, mod, local_name)
355
+ rename_tbl = ast_rename_state(ast, local_name)
356
+ ast = ast_update_refs(ast, rename_tbl)
357
+
358
+ str = Ruby2Ruby.new.process(ast)
359
+ rv = import_site.module_eval str
360
+ raise Bud::BudError unless rv.nil?
361
+
362
+ return new_mod_name
363
+ end
364
+
365
+ def self.get_module_ast(mod)
366
+ raw_ast = get_raw_parse_tree(mod)
367
+ unless raw_ast.first == :module
368
+ raise Bud::BudError, "import must be used with a Module"
369
+ end
370
+
371
+ return Unifier.new.process(raw_ast)
372
+ end
373
+
374
+ # Returns the AST for the given module (as a tree of Sexps). ParseTree
375
+ # provides native support for doing this, but we choose to do it ourselves. In
376
+ # ParseTree <= 3.0.7, the support is buggy; in later versions of ParseTree,
377
+ # the AST is returned in a different format than we expect. In particular, we
378
+ # expect that the methods from any modules included in the target module will
379
+ # be "inlined" into the dumped AST; ParseTree > 3.0.7 adds an "include"
380
+ # statement to the AST instead. In the long run we should adapt the module
381
+ # rewrite system to work with ParseTree > 3.0.7 and get rid of this code, but
382
+ # that will require further changes.
383
+ def self.get_raw_parse_tree(klass)
384
+ pt = RawParseTree.new(false)
385
+ klassname = klass.name
386
+ klassname = klassname.to_sym
387
+
388
+ code = if Class === klass then
389
+ sc = klass.superclass
390
+ sc_name = ((sc.nil? or sc.name.empty?) ? "nil" : sc.name).intern
391
+ [:class, klassname, [:const, sc_name]]
392
+ else
393
+ [:module, klassname]
394
+ end
395
+
396
+ method_names = klass.private_instance_methods false
397
+ # protected methods are included in instance_methods, go figure!
398
+
399
+ # Get the set of classes/modules that define instance methods we want to
400
+ # include in the result
401
+ relatives = klass.modules + [klass]
402
+ relatives.each do |r|
403
+ method_names += r.instance_methods false
404
+ end
405
+
406
+ # For each distinct method name, use the implementation that appears the
407
+ # furthest down in the inheritance hierarchy.
408
+ relatives.reverse!
409
+ method_names.uniq.sort.each do |m|
410
+ relatives.each do |r|
411
+ t = pt.parse_tree_for_method(r, m.to_sym)
412
+ if t != [nil]
413
+ code << t
414
+ break
415
+ end
416
+ end
417
+ end
418
+
419
+ klass.singleton_methods(false).sort.each do |m|
420
+ code << pt.parse_tree_for_method(klass, m.to_sym, true)
421
+ end
422
+
423
+ return code
424
+ end
425
+
426
+ # If this module imports a submodule and binds it to :x, references to x.t1
427
+ # need to be flattened to the mangled name of x.t1.
428
+ def self.ast_flatten_nested_refs(ast, import_tbl)
429
+ NestedRefRewriter.new(import_tbl).process(ast)
430
+ end
431
+
432
+ # Handle temp collections defined in the module's Bloom blocks.
433
+ def self.ast_process_temps(ast, mod)
434
+ t = TempExpander.new
435
+ ast = t.process(ast)
436
+
437
+ new_meth = t.get_state_meth(mod)
438
+ if new_meth
439
+ # Insert the new extra state method into the module's AST
440
+ ast << new_meth
441
+ end
442
+
443
+ return ast
444
+ end
445
+
446
+ # Rename the given module's name to be a mangle of import site, imported
447
+ # module, and local bind name. We also need to rename special "state" and
448
+ # "bootstrap" methods. We also rename "bloom" methods, but we can just mangle
449
+ # with the local bind name for those.
450
+ def self.ast_rename_module(ast, importer, importee, local_name)
451
+ mod_name = ast.sexp_body.first
452
+ raise Bud::BudError if mod_name.to_s != importee.to_s
453
+
454
+ # If the importer or importee modules are nested inside an outer module,
455
+ # strip off the outer module name before using for name mangling purposes
456
+ importer_name = Module.get_class_name(importer)
457
+ importee_name = Module.get_class_name(importee)
458
+ new_name = "#{importer_name}__#{importee_name}__#{local_name}"
459
+ ast[1] = new_name.to_sym
460
+
461
+ dr = DefnRenamer.new(mod_name, new_name, local_name)
462
+ new_ast = dr.process(ast)
463
+
464
+ # XXX: it would be nice to return a Module, rather than a string containing
465
+ # the Module's name. Unfortunately, I can't see how to do that.
466
+ return [new_ast, new_name]
467
+ end
468
+
469
+ # Mangle the names of all the collections defined in state blocks found in the
470
+ # given module's AST. Returns a table mapping old => new names.
471
+ def self.ast_rename_state(ast, local_name)
472
+ # Find all the state blocks in the AST
473
+ raise Bud::BudError unless ast.sexp_type == :module
474
+
475
+ rename_tbl = {}
476
+ ast.sexp_body.each do |b|
477
+ next unless b.class <= Sexp
478
+ next if b.sexp_type != :defn
479
+
480
+ def_name, args, scope = b.sexp_body
481
+ next unless /^__state\d+__/.match def_name.to_s
482
+
483
+ raise Bud::BudError unless scope.sexp_type == :scope
484
+ state_block = scope.sexp_body.first
485
+ raise Bud::BudError unless state_block.sexp_type == :block
486
+ next unless state_block.sexp_body
487
+
488
+ # Look for collection definition statements inside the block
489
+ state_block.sexp_body.each do |e|
490
+ raise Bud::BudError unless e.sexp_type == :call
491
+
492
+ recv, meth_name, args = e.sexp_body
493
+ raise Bud::BudError unless args.sexp_type == :arglist
494
+
495
+ if meth_name == :interface
496
+ tbl_name_node = args.sexp_body[1]
497
+ else
498
+ tbl_name_node = args.sexp_body[0]
499
+ end
500
+
501
+ raise Bud::BudError unless tbl_name_node.sexp_type == :lit
502
+ tbl_name = tbl_name_node.sexp_body.first
503
+
504
+ new_tbl_name = "#{local_name}__#{tbl_name}".to_sym
505
+ rename_tbl[tbl_name] = new_tbl_name
506
+
507
+ tbl_name_node[1] = new_tbl_name
508
+ end
509
+ end
510
+
511
+ return rename_tbl
512
+ end
513
+
514
+ def self.ast_update_refs(ast, rename_tbl)
515
+ CallRewriter.new(rename_tbl).process(ast)
516
+ end
517
+
518
+ # Return a list of symbols containing the names of def blocks containing Bloom
519
+ # rules in the given module and all of its ancestors.
520
+ def self.get_rule_defs(mod)
521
+ mod.instance_methods.select {|m| m =~ /^__bloom__.+$/}
522
+ end
523
+ end
data/lib/bud/rtrace.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'bud/state'
3
+
4
+ class RTrace #:nodoc: all
5
+ attr_reader :table_recv, :table_send, :table_sleep
6
+
7
+ def initialize(bud_instance)
8
+ @bud_instance = bud_instance
9
+ return if bud_instance.class == Stratification or
10
+ @bud_instance.class == DepAnalysis
11
+ @table_recv = Bud::BudTable.new(:t_recv_time, @bud_instance, [:pred, :tuple, :time])
12
+ @table_send = Bud::BudTable.new(:t_send_time, @bud_instance, [:pred, :tuple, :time])
13
+ @table_sleep = Bud::BudTable.new(:t_sleep_time, @bud_instance, [:time])
14
+ end
15
+
16
+ def send(pred, datum)
17
+ @table_send << [pred.to_s, datum, Time.now.to_f]
18
+ end
19
+
20
+ def recv(datum)
21
+ @table_recv << [datum[0].to_s, datum[1], Time.now.to_f]
22
+ end
23
+
24
+ def sleep
25
+ @table_sleep << [Time.now.to_f]
26
+ end
27
+ end
data/lib/bud/server.rb ADDED
@@ -0,0 +1,43 @@
1
+ require 'socket'
2
+
3
+ module Bud
4
+ class BudServer < EM::Connection #:nodoc: all
5
+ def initialize(bud)
6
+ @bud = bud
7
+ @pac = MessagePack::Unpacker.new
8
+ super
9
+ end
10
+
11
+ def receive_data(data)
12
+ # Feed the received data to the deserializer
13
+ @pac.feed data
14
+
15
+ # streaming deserialize
16
+ @pac.each do |obj|
17
+ message_received(obj)
18
+ end
19
+
20
+ @bud.rtracer.sleep if @bud.options[:rtrace]
21
+ end
22
+
23
+ def message_received(obj)
24
+ # puts "#{@bud.ip_port} <= #{obj.inspect}"
25
+ unless (obj.class <= Array and obj.length == 2 and not
26
+ @bud.tables[obj[0].to_sym].nil? and obj[1].class <= Array)
27
+ raise BudError, "Bad inbound message of class #{obj.class}: #{obj.inspect}"
28
+ end
29
+
30
+ @bud.rtracer.recv(obj) if @bud.options[:rtrace]
31
+
32
+ @bud.inbound << obj
33
+ begin
34
+ @bud.tick unless @bud.lazy
35
+ rescue Exception
36
+ # If we raise an exception here, EM dies, which causes problems (e.g.,
37
+ # other Bud instances in the same process will crash). Ignoring the
38
+ # error isn't best though -- we should do better (#74).
39
+ puts "Exception handling network message (channel '#{obj[0]}'): #{$!}"
40
+ end
41
+ end
42
+ end
43
+ end
data/lib/bud/state.rb ADDED
@@ -0,0 +1,108 @@
1
+ module Bud
2
+ ######## methods for registering collection types
3
+ private
4
+ def define_collection(name, &block)
5
+ # Don't allow duplicate collection definitions
6
+ if @tables.has_key? name
7
+ raise Bud::CompileError, "collection already exists: #{name}"
8
+ end
9
+
10
+ # Rule out collection names that use reserved words, including
11
+ # previously-defined method names.
12
+ reserved = eval "defined?(#{name})"
13
+ unless reserved.nil?
14
+ raise Bud::CompileError, "symbol :#{name} reserved, cannot be used as table name"
15
+ end
16
+ self.singleton_class.send(:define_method, name) do |*args, &blk|
17
+ unless blk.nil? then
18
+ return @tables[name].pro(&blk)
19
+ else
20
+ return @tables[name]
21
+ end
22
+ end
23
+ end
24
+
25
+ public
26
+
27
+ def input # :nodoc: all
28
+ true
29
+ end
30
+
31
+ def output # :nodoc: all
32
+ false
33
+ end
34
+
35
+ # declare a scratch collection to be an input or output interface
36
+ def interface(mode, name, schema=nil)
37
+ t_provides << [name.to_s, mode]
38
+ scratch(name, schema)
39
+ end
40
+
41
+ # declare a persistent collection. default schema <tt>[:key] => [:val]</tt>
42
+ def table(name, schema=nil)
43
+ define_collection(name)
44
+ @tables[name] = Bud::BudTable.new(name, self, schema)
45
+ end
46
+
47
+ # declare a transient (1-timestep) collection. default schema <tt>[:key] => [:val]</tt>
48
+ def scratch(name, schema=nil)
49
+ define_collection(name)
50
+ @tables[name] = Bud::BudScratch.new(name, self, schema)
51
+ end
52
+
53
+ # declare a scratch in a bloom statement lhs. schema inferred from rhs.
54
+ def temp(name)
55
+ define_collection(name)
56
+ # defer schema definition until merge
57
+ @tables[name] = Bud::BudTemp.new(name, self, nil, true)
58
+ end
59
+
60
+ # declare a transient network collection. default schema <tt>[:address, :val] => []</tt>
61
+ def channel(name, schema=nil)
62
+ define_collection(name)
63
+ @tables[name] = Bud::BudChannel.new(name, self, schema)
64
+ @channels[name] = @tables[name].locspec_idx
65
+ end
66
+
67
+ # declare a collection to be read from +filename+. rhs of statements only
68
+ def file_reader(name, filename, delimiter='\n')
69
+ define_collection(name)
70
+ @tables[name] = Bud::BudFileReader.new(name, filename, delimiter, self)
71
+ end
72
+
73
+ # declare a collection to be auto-populated every +period+ seconds. schema <tt>[:key] => [:val]</tt>.
74
+ # rhs of statements only.
75
+ def periodic(name, period=1)
76
+ define_collection(name)
77
+ # stick with default schema -- [:key] => [:val]
78
+ @tables[name] = Bud::BudPeriodic.new(name, self)
79
+ raise BudError if @periodics.has_key? [name]
80
+ t = [name, gen_id, period]
81
+ @periodics << t
82
+ end
83
+
84
+ def terminal(name) # :nodoc: all
85
+ if defined?(@terminal) && @terminal != name
86
+ raise Bud::BudError, "can't register IO collection #{name} in addition to #{@terminal}"
87
+ else
88
+ @terminal = name
89
+ end
90
+ define_collection(name)
91
+ @channels[name] = nil
92
+ @tables[name] = Bud::BudTerminal.new(name, [:line], self)
93
+ end
94
+
95
+ # declare a TokyoCabinet table
96
+ def tctable(name, schema=nil)
97
+ define_collection(name)
98
+ @tables[name] = Bud::BudTcTable.new(name, self, schema)
99
+ @tc_tables[name] = @tables[name]
100
+ end
101
+
102
+ # declare an Apache ZooKeeper table
103
+ def zktable(name, path, addr="localhost:2181")
104
+ define_collection(name)
105
+ @tables[name] = Bud::BudZkTable.new(name, path, addr, self)
106
+ @zk_tables[name] = @tables[name]
107
+ end
108
+ end