stn-dcell 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +7 -0
- data/.rspec +4 -0
- data/.travis.yml +30 -0
- data/CHANGES.md +53 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +20 -0
- data/README.md +168 -0
- data/Rakefile +4 -0
- data/benchmarks/messaging.rb +73 -0
- data/benchmarks/receiver.rb +37 -0
- data/dcell.gemspec +29 -0
- data/examples/itchy.rb +26 -0
- data/examples/scratchy.rb +12 -0
- data/explorer/css/bootstrap-responsive.css +686 -0
- data/explorer/css/bootstrap-responsive.min.css +12 -0
- data/explorer/css/bootstrap.css +3990 -0
- data/explorer/css/bootstrap.min.css +689 -0
- data/explorer/css/explorer.css +28 -0
- data/explorer/ico/favicon.ico +0 -0
- data/explorer/img/glyphicons-halflings-white.png +0 -0
- data/explorer/img/glyphicons-halflings.png +0 -0
- data/explorer/img/logo.png +0 -0
- data/explorer/index.html.erb +94 -0
- data/explorer/js/bootstrap.js +1726 -0
- data/explorer/js/bootstrap.min.js +6 -0
- data/lib/dcell.rb +127 -0
- data/lib/dcell/actor_proxy.rb +30 -0
- data/lib/dcell/celluloid_ext.rb +120 -0
- data/lib/dcell/directory.rb +31 -0
- data/lib/dcell/explorer.rb +74 -0
- data/lib/dcell/future_proxy.rb +32 -0
- data/lib/dcell/global.rb +23 -0
- data/lib/dcell/info_service.rb +122 -0
- data/lib/dcell/mailbox_proxy.rb +53 -0
- data/lib/dcell/messages.rb +65 -0
- data/lib/dcell/node.rb +138 -0
- data/lib/dcell/node_manager.rb +79 -0
- data/lib/dcell/registries/cassandra_adapter.rb +126 -0
- data/lib/dcell/registries/mongodb_adapter.rb +85 -0
- data/lib/dcell/registries/redis_adapter.rb +95 -0
- data/lib/dcell/registries/zk_adapter.rb +123 -0
- data/lib/dcell/responses.rb +16 -0
- data/lib/dcell/router.rb +46 -0
- data/lib/dcell/rpc.rb +95 -0
- data/lib/dcell/rspec.rb +1 -0
- data/lib/dcell/server.rb +73 -0
- data/lib/dcell/version.rb +3 -0
- data/logo.png +0 -0
- data/spec/dcell/actor_proxy_spec.rb +72 -0
- data/spec/dcell/celluloid_ext_spec.rb +32 -0
- data/spec/dcell/directory_spec.rb +22 -0
- data/spec/dcell/explorer_spec.rb +17 -0
- data/spec/dcell/global_spec.rb +25 -0
- data/spec/dcell/node_spec.rb +23 -0
- data/spec/dcell/registries/mongodb_adapter_spec.rb +7 -0
- data/spec/dcell/registries/redis_adapter_spec.rb +6 -0
- data/spec/dcell/registries/zk_adapter_spec.rb +28 -0
- data/spec/options/01-options.rb +10 -0
- data/spec/options/02-registry.rb +13 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/helpers.rb +46 -0
- data/spec/support/registry_examples.rb +35 -0
- data/spec/test_node.rb +38 -0
- data/tasks/cassandra.task +84 -0
- data/tasks/rspec.task +7 -0
- data/tasks/zookeeper.task +58 -0
- metadata +239 -0
data/lib/dcell/router.rb
ADDED
@@ -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
|
data/lib/dcell/rspec.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path('../../../spec/support/registry_examples', __FILE__)
|
data/lib/dcell/server.rb
ADDED
@@ -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
|
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
|