bud 0.0.2

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.
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