stn-dcell 0.16.0

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +7 -0
  4. data/.rspec +4 -0
  5. data/.travis.yml +30 -0
  6. data/CHANGES.md +53 -0
  7. data/Gemfile +18 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +168 -0
  10. data/Rakefile +4 -0
  11. data/benchmarks/messaging.rb +73 -0
  12. data/benchmarks/receiver.rb +37 -0
  13. data/dcell.gemspec +29 -0
  14. data/examples/itchy.rb +26 -0
  15. data/examples/scratchy.rb +12 -0
  16. data/explorer/css/bootstrap-responsive.css +686 -0
  17. data/explorer/css/bootstrap-responsive.min.css +12 -0
  18. data/explorer/css/bootstrap.css +3990 -0
  19. data/explorer/css/bootstrap.min.css +689 -0
  20. data/explorer/css/explorer.css +28 -0
  21. data/explorer/ico/favicon.ico +0 -0
  22. data/explorer/img/glyphicons-halflings-white.png +0 -0
  23. data/explorer/img/glyphicons-halflings.png +0 -0
  24. data/explorer/img/logo.png +0 -0
  25. data/explorer/index.html.erb +94 -0
  26. data/explorer/js/bootstrap.js +1726 -0
  27. data/explorer/js/bootstrap.min.js +6 -0
  28. data/lib/dcell.rb +127 -0
  29. data/lib/dcell/actor_proxy.rb +30 -0
  30. data/lib/dcell/celluloid_ext.rb +120 -0
  31. data/lib/dcell/directory.rb +31 -0
  32. data/lib/dcell/explorer.rb +74 -0
  33. data/lib/dcell/future_proxy.rb +32 -0
  34. data/lib/dcell/global.rb +23 -0
  35. data/lib/dcell/info_service.rb +122 -0
  36. data/lib/dcell/mailbox_proxy.rb +53 -0
  37. data/lib/dcell/messages.rb +65 -0
  38. data/lib/dcell/node.rb +138 -0
  39. data/lib/dcell/node_manager.rb +79 -0
  40. data/lib/dcell/registries/cassandra_adapter.rb +126 -0
  41. data/lib/dcell/registries/mongodb_adapter.rb +85 -0
  42. data/lib/dcell/registries/redis_adapter.rb +95 -0
  43. data/lib/dcell/registries/zk_adapter.rb +123 -0
  44. data/lib/dcell/responses.rb +16 -0
  45. data/lib/dcell/router.rb +46 -0
  46. data/lib/dcell/rpc.rb +95 -0
  47. data/lib/dcell/rspec.rb +1 -0
  48. data/lib/dcell/server.rb +73 -0
  49. data/lib/dcell/version.rb +3 -0
  50. data/logo.png +0 -0
  51. data/spec/dcell/actor_proxy_spec.rb +72 -0
  52. data/spec/dcell/celluloid_ext_spec.rb +32 -0
  53. data/spec/dcell/directory_spec.rb +22 -0
  54. data/spec/dcell/explorer_spec.rb +17 -0
  55. data/spec/dcell/global_spec.rb +25 -0
  56. data/spec/dcell/node_spec.rb +23 -0
  57. data/spec/dcell/registries/mongodb_adapter_spec.rb +7 -0
  58. data/spec/dcell/registries/redis_adapter_spec.rb +6 -0
  59. data/spec/dcell/registries/zk_adapter_spec.rb +28 -0
  60. data/spec/options/01-options.rb +10 -0
  61. data/spec/options/02-registry.rb +13 -0
  62. data/spec/spec_helper.rb +28 -0
  63. data/spec/support/helpers.rb +46 -0
  64. data/spec/support/registry_examples.rb +35 -0
  65. data/spec/test_node.rb +38 -0
  66. data/tasks/cassandra.task +84 -0
  67. data/tasks/rspec.task +7 -0
  68. data/tasks/zookeeper.task +58 -0
  69. metadata +239 -0
@@ -0,0 +1,46 @@
1
+ require 'weakref'
2
+
3
+ module DCell
4
+ # Route incoming messages to their recipient actors
5
+ class Router
6
+ @mutex = Mutex.new
7
+ @mailboxes = {}
8
+
9
+ class << self
10
+ # Enter a mailbox into the registry
11
+ def register(mailbox)
12
+ @mutex.synchronize do
13
+ address = mailbox.address
14
+ ref = @mailboxes[address]
15
+ @mailboxes[address] = WeakRef.new(mailbox) unless ref && ref.weakref_alive?
16
+
17
+ address
18
+ end
19
+ end
20
+
21
+ # Find a mailbox by its address
22
+ def find(mailbox_address)
23
+ @mutex.synchronize do
24
+ begin
25
+ ref = @mailboxes[mailbox_address]
26
+ return unless ref
27
+ ref.__getobj__
28
+ rescue WeakRef::RefError
29
+ # The referenced actor is dead, so prune the registry
30
+ @mailboxes.delete mailbox_address
31
+ nil
32
+ end
33
+ end
34
+ end
35
+
36
+ # Prune all entries that point to dead objects
37
+ def gc
38
+ @mutex.synchronize do
39
+ @mailboxes.each do |id, ref|
40
+ @mailboxes.delete id unless ref.weakref_alive?
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
data/lib/dcell/rpc.rb ADDED
@@ -0,0 +1,95 @@
1
+ require 'weakref'
2
+
3
+ module DCell
4
+ class RPB < Celluloid::BlockProxy
5
+ def initialize(id, mailbox, execution, arguments)
6
+ @id, @mailbox, @execution, @arguments = id, mailbox, execution, arguments
7
+ end
8
+
9
+ # Custom marshaller for compatibility with Celluloid::Mailbox marshalling
10
+ def _dump(level)
11
+ payload = Marshal.dump [@mailbox, @execution, @arguments]
12
+ "#{@id}:rpb:#{payload}"
13
+ end
14
+ end
15
+
16
+ class RPBC < Celluloid::BlockCall
17
+ def initialize(id, block_proxy, sender, arguments)
18
+ @id, @block_proxy, @sender, @arguments = id, block_proxy, sender, arguments
19
+ end
20
+
21
+ # Custom marshaller for compatibility with Celluloid::Mailbox marshalling
22
+ def _dump(level)
23
+ payload = Marshal.dump [@block_proxy, @sender, @arguments]
24
+ "#{@id}:rpbc:#{payload}"
25
+ end
26
+ end
27
+
28
+ class RPC < Celluloid::SyncCall
29
+ def initialize(id, sender, method, arguments, block)
30
+ @id, @sender, @method, @arguments, @block = id, sender, method, arguments, block
31
+ end
32
+
33
+ # Custom marshaller for compatibility with Celluloid::Mailbox marshalling
34
+ def _dump(level)
35
+ payload = Marshal.dump [@sender, @method, @arguments, @block]
36
+ "#{@id}:rpc:#{payload}"
37
+ end
38
+
39
+ # Loader for custom marshal format
40
+ def self._load(string)
41
+ id = string.slice!(0, string.index(":") + 1)
42
+ match = id.match(/^([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})@(.+?):$/)
43
+ raise ArgumentError, "couldn't parse call ID" unless match
44
+
45
+ uuid, node_id = match[1], match[2]
46
+
47
+ if DCell.id == node_id
48
+ Manager.claim uuid
49
+ else
50
+ type = string.slice!(0, string.index(":") + 1)
51
+ types = {
52
+ "rpc" => RPC,
53
+ "rpb" => RPB,
54
+ "rpbc" => RPBC,
55
+ }
56
+ types.fetch(type[0..-2]).new("#{uuid}@#{node_id}", *Marshal.load(string))
57
+ end
58
+ end
59
+
60
+ # Tracks calls-in-flight
61
+ class Manager
62
+ @mutex = Mutex.new
63
+ @ids = {}
64
+ @calls = {}
65
+
66
+ def self.register(call)
67
+ @mutex.lock
68
+ begin
69
+ call_id = @ids[call.object_id]
70
+ unless call_id
71
+ call_id = Celluloid.uuid
72
+ @ids[call.object_id] = call_id
73
+ end
74
+
75
+ @calls[call_id] = WeakRef.new(call)
76
+ call_id
77
+ ensure
78
+ @mutex.unlock rescue nil
79
+ end
80
+ end
81
+
82
+ def self.claim(call_id)
83
+ @mutex.lock
84
+ begin
85
+ ref = @calls.delete(call_id)
86
+ ref.__getobj__ if ref
87
+ rescue WeakRef::RefError
88
+ # Nothing to see here, folks
89
+ ensure
90
+ @mutex.unlock rescue nil
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path('../../../spec/support/registry_examples', __FILE__)
@@ -0,0 +1,73 @@
1
+ module DCell
2
+ # Servers handle incoming 0MQ traffic
3
+ class Server
4
+ include Celluloid::ZMQ
5
+
6
+ finalizer :close
7
+
8
+ # Bind to the given 0MQ address (in URL form ala tcp://host:port)
9
+ def initialize
10
+ # The gossip protocol is dependent on the node manager
11
+ link Celluloid::Actor[:node_manager]
12
+
13
+ @socket = PullSocket.new
14
+
15
+ begin
16
+ @socket.bind(DCell.addr)
17
+ real_addr = @socket.get(::ZMQ::LAST_ENDPOINT).strip
18
+ DCell::Directory.set DCell.id, real_addr
19
+ DCell.addr = real_addr
20
+ rescue IOError
21
+ @socket.close
22
+ raise
23
+ end
24
+
25
+ async.run
26
+ end
27
+
28
+ # Wait for incoming 0MQ messages
29
+ def run
30
+ while true; async.handle_message @socket.read; end
31
+ end
32
+
33
+ def close
34
+ @socket.close if @socket
35
+ end
36
+
37
+ # Handle incoming messages
38
+ def handle_message(message)
39
+ begin
40
+ message = decode_message message
41
+ rescue InvalidMessageError => ex
42
+ Logger.crash("couldn't decode message", ex)
43
+ return
44
+ end
45
+
46
+ begin
47
+ message.dispatch
48
+ rescue => ex
49
+ Logger.crash("DCell::Server: message dispatch failed", ex)
50
+ end
51
+ end
52
+
53
+ class InvalidMessageError < StandardError; end # undecodable message
54
+
55
+ # Decode incoming messages
56
+ def decode_message(message)
57
+ if message[0..1].unpack("CC") == [Marshal::MAJOR_VERSION, Marshal::MINOR_VERSION]
58
+ begin
59
+ Marshal.load message
60
+ rescue => ex
61
+ raise InvalidMessageError, "invalid message: #{ex}"
62
+ end
63
+ else raise InvalidMessageError, "couldn't determine message format: #{message}"
64
+ end
65
+ end
66
+
67
+ # Terminate this server
68
+ def terminate
69
+ @socket.close
70
+ super
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,3 @@
1
+ module DCell
2
+ VERSION = "0.16.0"
3
+ end
data/logo.png ADDED
Binary file
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe DCell::CellProxy do
4
+ before :all do
5
+ @node = DCell::Node[TEST_NODE[:id]]
6
+ @node.id.should == TEST_NODE[:id]
7
+ @remote_actor = @node[:test_actor]
8
+
9
+ class LocalActor
10
+ include Celluloid
11
+
12
+ attr_reader :crash_reason
13
+ trap_exit :exit_handler
14
+
15
+ def initialize
16
+ @crash_reason = nil
17
+ end
18
+
19
+ def exit_handler(actor, reason)
20
+ @crash_reason = reason
21
+ end
22
+ end
23
+ end
24
+
25
+ it "makes synchronous calls to remote actors" do
26
+ @remote_actor.value.should == 42
27
+ end
28
+
29
+ it "handles blocks" do
30
+ result = nil
31
+ @remote_actor.win do |value|
32
+ result = value
33
+ end
34
+ result.should == 10000
35
+ end
36
+
37
+ it "makes future calls to remote actors" do
38
+ @remote_actor.future(:value).value.should == 42
39
+ end
40
+
41
+ context :linking do
42
+ before :each do
43
+ @local_actor = LocalActor.new
44
+ end
45
+
46
+ it "links to remote actors" do
47
+ @local_actor.link @remote_actor
48
+ @local_actor.linked_to?(@remote_actor).should be_true
49
+ @remote_actor.linked_to?(@local_actor).should be_true
50
+ end
51
+
52
+ it "unlinks from remote actors" do
53
+ @local_actor.link @remote_actor
54
+ @local_actor.unlink @remote_actor
55
+
56
+ @local_actor.linked_to?(@remote_actor).should be_false
57
+ @remote_actor.linked_to?(@local_actor).should be_false
58
+ end
59
+
60
+ it "traps exit messages from other actors" do
61
+ @local_actor.link @remote_actor
62
+
63
+ expect do
64
+ @remote_actor.crash
65
+ end.to raise_exception(RuntimeError)
66
+
67
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
68
+ @local_actor.crash_reason.should be_a(RuntimeError)
69
+ @local_actor.crash_reason.message.should == "the spec purposely crashed me :("
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid, "extensions" do
4
+ before do
5
+ class WillKane
6
+ include Celluloid
7
+
8
+ def speak
9
+ "Don't shove me Harv."
10
+ end
11
+ end
12
+ @marshal = WillKane.new
13
+ end
14
+
15
+ it "marshals Celluloid::CellProxy objects" do
16
+ string = Marshal.dump(@marshal)
17
+ Marshal.load(string).should be_alive
18
+ end
19
+
20
+ it "marshals Celluloid::Mailbox objects" do
21
+ @marshal.mailbox.should be_a(Celluloid::Mailbox)
22
+ string = Marshal.dump(@marshal.mailbox)
23
+ Marshal.load(string).should be_alive
24
+ end
25
+
26
+ it "marshals Celluloid::Future objects" do
27
+ future = @marshal.future(:speak)
28
+ future.should be_a(Celluloid::Future)
29
+ string = Marshal.dump(future)
30
+ Marshal.load(string).value.should == "Don't shove me Harv."
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe DCell::Directory do
4
+ it "stores node addresses" do
5
+ DCell::Directory["foobar"] = "tcp://localhost:1870"
6
+ DCell::Directory["foobar"].should == "tcp://localhost:1870"
7
+ end
8
+ it "presents all stored addresses" do
9
+ DCell::Directory["foo"] = "tcp://fooaddress"
10
+ DCell::Directory["bar"] = "tcp://baraddress"
11
+ DCell::Directory.all.should include("foo")
12
+ DCell::Directory.all.should include("bar")
13
+ end
14
+ it "clears node addresses" do
15
+ DCell::Directory["foo"] = "tcp://fooaddress"
16
+ DCell::Directory["foobar"].should == "tcp://localhost:1870"
17
+ ["foo", "foobar"].each do |node|
18
+ DCell::Directory.remove node
19
+ end
20
+ DCell::Directory["foobar"].should_not == "tcp://localhost:1870"
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'dcell/explorer'
3
+
4
+ describe DCell::Explorer do
5
+ EXPLORER_HOST = 'localhost'
6
+ EXPLORER_PORT = 7778
7
+ EXPLORER_BASE = "http://#{EXPLORER_HOST}:#{EXPLORER_PORT}"
8
+
9
+ before do
10
+ @explorer = DCell::Explorer.new(EXPLORER_HOST, EXPLORER_PORT)
11
+ end
12
+
13
+ it "reports the current node's status" do
14
+ response = Net::HTTP.get URI(EXPLORER_BASE)
15
+ response[%r{<a href="/nodes/(.*?)">}, 1].should == DCell.id
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe DCell::Global do
4
+ it "can handle unexisting keys" do
5
+ expect { DCell::Global[:unexisting] }.to_not raise_exception
6
+ end
7
+
8
+ it "stores values" do
9
+ DCell::Global[:the_answer] = 42
10
+ DCell::Global[:the_answer].should == 42
11
+
12
+ # Double check the global value is available on all nodes
13
+ node = DCell::Node[TEST_NODE[:id]]
14
+ node[:test_actor].the_answer.should == 42
15
+ end
16
+
17
+ it "stores the keys of all globals" do
18
+ DCell::Global[:foo] = 1
19
+ DCell::Global[:bar] = 2
20
+ DCell::Global[:baz] = 3
21
+
22
+ keys = DCell::Global.keys
23
+ [:foo, :bar, :baz].each { |key| keys.should include key }
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe DCell::Node do
4
+ before do
5
+ @node = DCell::Node[TEST_NODE[:id]]
6
+ @node.id.should == TEST_NODE[:id]
7
+ end
8
+
9
+ it "finds all available nodes" do
10
+ nodes = DCell::Node.all
11
+ nodes.should include(DCell.me)
12
+ end
13
+
14
+ it "finds remote actors" do
15
+ actor = @node[:test_actor]
16
+ actor.value.should == 42
17
+ end
18
+
19
+ it "lists remote actors" do
20
+ @node.actors.should include :test_actor
21
+ @node.all.should include :test_actor
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+ require 'dcell/registries/mongodb_adapter'
3
+
4
+ describe DCell::Registry::MongodbAdapter, :pending => TEST_ADEPTER != 'mongodb' && "no mongodb" do
5
+ subject { DCell::Registry::MongodbAdapter.new TEST_DB[:mongodb] }
6
+ it_behaves_like "a DCell registry"
7
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ describe DCell::Registry::RedisAdapter, :pending => TEST_ADEPTER != 'redis' && "no redis" do
4
+ subject { DCell::Registry::RedisAdapter.new TEST_DB[:redis] }
5
+ it_behaves_like "a DCell registry"
6
+ end