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