dcell 0.0.0 → 0.0.1
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.
- data/.gitignore +2 -0
- data/.rspec +4 -0
- data/CHANGES.md +8 -0
- data/Gemfile +2 -0
- data/README.md +209 -0
- data/Rakefile +3 -0
- data/benchmarks/messaging.rb +67 -0
- data/benchmarks/receiver.rb +37 -0
- data/dcell.gemspec +10 -3
- data/lib/celluloid/README +8 -0
- data/lib/celluloid/zmq.rb +28 -0
- data/lib/celluloid/zmq/mailbox.rb +13 -0
- data/lib/celluloid/zmq/reactor.rb +74 -0
- data/lib/dcell.rb +92 -2
- data/lib/dcell/actor_proxy.rb +4 -0
- data/lib/dcell/application.rb +6 -0
- data/lib/dcell/celluloid_ext.rb +57 -0
- data/lib/dcell/directory.rb +23 -0
- data/lib/dcell/global.rb +23 -0
- data/lib/dcell/mailbox_proxy.rb +61 -0
- data/lib/dcell/messages.rb +67 -0
- data/lib/dcell/node.rb +120 -0
- data/lib/dcell/registries/redis_adapter.rb +86 -0
- data/lib/dcell/registries/zk_adapter.rb +122 -0
- data/lib/dcell/responses.rb +16 -0
- data/lib/dcell/router.rb +71 -0
- data/lib/dcell/rspec.rb +1 -0
- data/lib/dcell/server.rb +80 -0
- data/lib/dcell/version.rb +1 -1
- data/spec/celluloid/zmq/mailbox_spec.rb +6 -0
- data/spec/dcell/actor_proxy_spec.rb +60 -0
- data/spec/dcell/celluloid_ext_spec.rb +21 -0
- data/spec/dcell/directory_spec.rb +8 -0
- data/spec/dcell/global_spec.rb +21 -0
- data/spec/dcell/node_spec.rb +23 -0
- data/spec/dcell/registries/redis_adapter_spec.rb +6 -0
- data/spec/dcell/registries/zk_adapter_spec.rb +11 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/helpers.rb +40 -0
- data/spec/support/registry_examples.rb +35 -0
- data/spec/test_node.rb +33 -0
- data/tasks/rspec.task +7 -0
- data/tasks/zookeeper.task +58 -0
- metadata +111 -7
data/lib/dcell.rb
CHANGED
@@ -1,5 +1,95 @@
|
|
1
|
-
require
|
1
|
+
require 'celluloid'
|
2
|
+
require 'celluloid/zmq'
|
3
|
+
require 'dcell/version'
|
2
4
|
|
5
|
+
require 'dcell/actor_proxy'
|
6
|
+
require 'dcell/directory'
|
7
|
+
require 'dcell/mailbox_proxy'
|
8
|
+
require 'dcell/messages'
|
9
|
+
require 'dcell/node'
|
10
|
+
require 'dcell/global'
|
11
|
+
require 'dcell/responses'
|
12
|
+
require 'dcell/router'
|
13
|
+
require 'dcell/server'
|
14
|
+
|
15
|
+
require 'dcell/registries/redis_adapter'
|
16
|
+
require 'dcell/application'
|
17
|
+
require 'dcell/celluloid_ext'
|
18
|
+
|
19
|
+
# Distributed Celluloid
|
3
20
|
module DCell
|
4
|
-
#
|
21
|
+
DEFAULT_PORT = 7777 # Default DCell port
|
22
|
+
ZMQ_POOL_SIZE = 1 # DCell uses a fixed-size 0MQ thread pool
|
23
|
+
@zmq_context = ::ZMQ::Context.new(ZMQ_POOL_SIZE)
|
24
|
+
@config_lock = Mutex.new
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_reader :me, :registry, :zmq_context
|
28
|
+
|
29
|
+
# Configure DCell with the following options:
|
30
|
+
#
|
31
|
+
# * id: to identify the local node, defaults to hostname
|
32
|
+
# * addr: 0MQ address of the local node (e.g. tcp://4.3.2.1:7777)
|
33
|
+
# *
|
34
|
+
def setup(options = {})
|
35
|
+
# Stringify keys :/
|
36
|
+
options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
37
|
+
|
38
|
+
@config_lock.synchronize do
|
39
|
+
@configuration = {
|
40
|
+
'id' => generate_node_id,
|
41
|
+
'addr' => "tcp://127.0.0.1:#{DEFAULT_PORT}",
|
42
|
+
'registry' => {'adapter' => 'redis', 'server' => 'localhost'}
|
43
|
+
}.merge(options)
|
44
|
+
|
45
|
+
@me = Node.new @configuration['id'], @configuration['addr']
|
46
|
+
|
47
|
+
registry_adapter = @configuration['registry'][:adapter] || @configuration['registry']['adapter']
|
48
|
+
raise ArgumentError, "no registry adapter given in config" unless registry_adapter
|
49
|
+
|
50
|
+
registry_class_name = registry_adapter.split("_").map(&:capitalize).join << "Adapter"
|
51
|
+
|
52
|
+
begin
|
53
|
+
registry_class = DCell::Registry.const_get registry_class_name
|
54
|
+
rescue NameError
|
55
|
+
raise ArgumentError, "invalid registry adapter: #{@configuration['registry']['adapter']}"
|
56
|
+
end
|
57
|
+
|
58
|
+
@registry = registry_class.new(@configuration['registry'])
|
59
|
+
|
60
|
+
addr = @configuration['public'] || @configuration['addr']
|
61
|
+
DCell::Directory.set @configuration['id'], addr
|
62
|
+
end
|
63
|
+
|
64
|
+
me
|
65
|
+
end
|
66
|
+
|
67
|
+
# Obtain the local node ID
|
68
|
+
def id; @configuration['id']; end
|
69
|
+
|
70
|
+
# Obtain the 0MQ address to the local mailbox
|
71
|
+
def addr; @configuration['addr']; end
|
72
|
+
alias_method :address, :addr
|
73
|
+
|
74
|
+
# Attempt to generate a unique node ID for this machine
|
75
|
+
def generate_node_id
|
76
|
+
`hostname`.strip # Super creative I know
|
77
|
+
end
|
78
|
+
|
79
|
+
# Run the DCell application
|
80
|
+
def run
|
81
|
+
DCell::Application.run
|
82
|
+
end
|
83
|
+
|
84
|
+
# Run the DCell application in the background
|
85
|
+
def run!
|
86
|
+
DCell::Application.run!
|
87
|
+
end
|
88
|
+
|
89
|
+
# Start combines setup and run! into a single step
|
90
|
+
def start(options = {})
|
91
|
+
setup options
|
92
|
+
run!
|
93
|
+
end
|
94
|
+
end
|
5
95
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Celluloid mailboxes are the universal message exchange points. You won't
|
2
|
+
# be able to marshal them though, unfortunately, because they contain
|
3
|
+
# mutexes.
|
4
|
+
#
|
5
|
+
# DCell provides a message routing layer between nodes that can direct
|
6
|
+
# messages back to local mailboxes. To accomplish this, DCell adds custom
|
7
|
+
# marshalling to mailboxes so that if they're unserialized on a remote
|
8
|
+
# node you instead get a proxy object that routes messages through the
|
9
|
+
# DCell overlay network back to the node where the actor actually exists
|
10
|
+
|
11
|
+
module Celluloid
|
12
|
+
class ActorProxy
|
13
|
+
# Marshal uses respond_to? to determine if this object supports _dump so
|
14
|
+
# unfortunately we have to monkeypatch in _dump support as the proxy
|
15
|
+
# itself normally jacks respond_to? and proxies to the actor
|
16
|
+
alias_method :__respond_to?, :respond_to?
|
17
|
+
def respond_to?(meth)
|
18
|
+
return false if meth == :marshal_dump
|
19
|
+
return true if meth == :_dump
|
20
|
+
__respond_to? meth
|
21
|
+
end
|
22
|
+
|
23
|
+
# Dump an actor proxy via its mailbox
|
24
|
+
def _dump(level)
|
25
|
+
@mailbox._dump(level)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create an actor proxy object which routes messages over DCell's overlay
|
29
|
+
# network and back to the original mailbox
|
30
|
+
def self._load(string)
|
31
|
+
mailbox = Celluloid::Mailbox._load(string)
|
32
|
+
|
33
|
+
case mailbox
|
34
|
+
when DCell::MailboxProxy
|
35
|
+
DCell::ActorProxy.new mailbox
|
36
|
+
when Celluloid::Mailbox
|
37
|
+
Celluloid::ActorProxy.new(mailbox)
|
38
|
+
else raise "funny, I did not expect to see a #{mailbox.class} here"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Mailbox
|
44
|
+
# This custom dumper registers actors with the DCell registry so they can
|
45
|
+
# be reached remotely.
|
46
|
+
def _dump(level)
|
47
|
+
mailbox_id = DCell::Router.register self
|
48
|
+
"#{mailbox_id}@#{DCell.id}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Create a mailbox proxy object which routes messages over DCell's overlay
|
52
|
+
# network and back to the original mailbox
|
53
|
+
def self._load(string)
|
54
|
+
DCell::MailboxProxy._load(string)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DCell
|
2
|
+
# Directory of nodes connected to the DCell cluster
|
3
|
+
module Directory
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# Get the URL for a particular Node ID
|
7
|
+
def get(node_id)
|
8
|
+
DCell.registry.get_node node_id
|
9
|
+
end
|
10
|
+
alias_method :[], :get
|
11
|
+
|
12
|
+
# Set the address of a particular Node ID
|
13
|
+
def set(node_id, addr)
|
14
|
+
DCell.registry.set_node node_id, addr
|
15
|
+
end
|
16
|
+
alias_method :[]=, :set
|
17
|
+
|
18
|
+
# List all of the node IDs in the directory
|
19
|
+
def all
|
20
|
+
DCell.registry.nodes
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/dcell/global.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module DCell
|
2
|
+
# Global object registry shared among all DCell nodes
|
3
|
+
module Global
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# Get a global value
|
7
|
+
def get(key)
|
8
|
+
DCell.registry.get_global key.to_s
|
9
|
+
end
|
10
|
+
alias_method :[], :get
|
11
|
+
|
12
|
+
# Set a global value
|
13
|
+
def set(key, value)
|
14
|
+
DCell.registry.set_global key.to_s, value
|
15
|
+
end
|
16
|
+
alias_method :[]=, :set
|
17
|
+
|
18
|
+
# Get the keys for all the globals in the system
|
19
|
+
def keys
|
20
|
+
DCell.registry.global_keys.map(&:to_sym)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module DCell
|
2
|
+
# A proxy object for a mailbox that delivers messages to the real mailbox on
|
3
|
+
# a remote node on a server far, far away...
|
4
|
+
class MailboxProxy
|
5
|
+
class InvalidNodeError < StandardError; end
|
6
|
+
|
7
|
+
def initialize(node_id, mailbox_id)
|
8
|
+
raise ArgumentError, "no mailbox_id given" unless mailbox_id
|
9
|
+
|
10
|
+
@node_id = node_id
|
11
|
+
@node = Node[node_id]
|
12
|
+
raise ArgumentError, "invalid node_id given" unless @node
|
13
|
+
|
14
|
+
@mailbox_id = mailbox_id
|
15
|
+
end
|
16
|
+
|
17
|
+
# name@host style address
|
18
|
+
def address
|
19
|
+
"#{@mailbox_id}@#{@node_id}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#<DCell::MailboxProxy:0x#{object_id.to_s(16)} #{address}>"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Send a message to the mailbox
|
27
|
+
def <<(message)
|
28
|
+
@node.send_message! Message::Relay.new(@mailbox_id, message)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Send a system event
|
32
|
+
def system_event(event)
|
33
|
+
@node.send_message! Message::SystemEvent.new(@mailbox_id, event)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Is the remote mailbox still alive?
|
37
|
+
def alive?
|
38
|
+
true # FIXME: hax!
|
39
|
+
end
|
40
|
+
|
41
|
+
# Custom marshaller for compatibility with Celluloid::Mailbox marshalling
|
42
|
+
def _dump(level)
|
43
|
+
"#{@mailbox_id}@#{@node_id}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Loader for custom marshal format
|
47
|
+
def self._load(string)
|
48
|
+
mailbox_id, node_id = string.split("@")
|
49
|
+
|
50
|
+
if DCell.id == node_id
|
51
|
+
# If we're on the local node, find the real mailbox
|
52
|
+
mailbox = DCell::Router.find mailbox_id
|
53
|
+
raise "tried to unmarshal dead Celluloid::Mailbox: #{mailbox_id}" unless mailbox
|
54
|
+
mailbox
|
55
|
+
else
|
56
|
+
# Create a proxy to the mailbox on the remote node
|
57
|
+
DCell::MailboxProxy.new(node_id, mailbox_id)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module DCell
|
2
|
+
class Message
|
3
|
+
attr_reader :id
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
# Memoize the original object ID so it will get marshalled
|
7
|
+
# Perhaps this should use a real UUID scheme
|
8
|
+
@id = object_id
|
9
|
+
end
|
10
|
+
|
11
|
+
# Query a node for the address of an actor
|
12
|
+
class Find < Message
|
13
|
+
attr_reader :caller, :name
|
14
|
+
|
15
|
+
def initialize(caller, name)
|
16
|
+
super()
|
17
|
+
@caller, @name = caller, name
|
18
|
+
end
|
19
|
+
|
20
|
+
def dispatch
|
21
|
+
@caller << SuccessResponse.new(@id, Celluloid::Actor[@name])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# List all registered actors
|
26
|
+
class List < Message
|
27
|
+
attr_reader :caller
|
28
|
+
|
29
|
+
def initialize(caller)
|
30
|
+
super()
|
31
|
+
@caller = caller
|
32
|
+
end
|
33
|
+
|
34
|
+
def dispatch
|
35
|
+
@caller << SuccessResponse.new(@id, Celluloid::Actor.registered)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Relay a message to the given recipient
|
40
|
+
class Relay < Message
|
41
|
+
attr_reader :recipient, :message
|
42
|
+
|
43
|
+
def initialize(recipient, message)
|
44
|
+
super()
|
45
|
+
@recipient, @message = recipient, message
|
46
|
+
end
|
47
|
+
|
48
|
+
def dispatch
|
49
|
+
DCell::Router.route @recipient, @message
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Send a system event to the given recipient
|
54
|
+
class SystemEvent < Message
|
55
|
+
attr_reader :recipient, :event
|
56
|
+
|
57
|
+
def initialize(recipient, event)
|
58
|
+
super()
|
59
|
+
@recipient, @event = recipient, event
|
60
|
+
end
|
61
|
+
|
62
|
+
def dispatch
|
63
|
+
DCell::Router.route_system_event @recipient, @event
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/dcell/node.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module DCell
|
2
|
+
# A node in a DCell cluster
|
3
|
+
class Node
|
4
|
+
include Celluloid
|
5
|
+
attr_reader :id, :addr
|
6
|
+
|
7
|
+
@nodes = {}
|
8
|
+
@lock = Mutex.new
|
9
|
+
|
10
|
+
class << self
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
# Return all available nodes in the cluster
|
14
|
+
def all
|
15
|
+
Directory.all.map do |node_id|
|
16
|
+
find node_id
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Iterate across all available nodes
|
21
|
+
def each
|
22
|
+
Directory.all.each do |node_id|
|
23
|
+
yield find node_id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Find a node by its node ID
|
28
|
+
def find(id)
|
29
|
+
node = @lock.synchronize { @nodes[id] }
|
30
|
+
return node if node
|
31
|
+
|
32
|
+
addr = Directory[id]
|
33
|
+
|
34
|
+
if addr
|
35
|
+
if id == DCell.id
|
36
|
+
node = DCell.me
|
37
|
+
else
|
38
|
+
node = Node.new(id, addr)
|
39
|
+
end
|
40
|
+
|
41
|
+
@lock.synchronize do
|
42
|
+
@nodes[id] ||= node
|
43
|
+
@nodes[id]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
alias_method :[], :find
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(id, addr)
|
51
|
+
@id, @addr = id, addr
|
52
|
+
@socket = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def finalize
|
56
|
+
@socket.close if socket
|
57
|
+
end
|
58
|
+
|
59
|
+
# Obtain the node's 0MQ socket
|
60
|
+
def socket
|
61
|
+
return @socket if @socket
|
62
|
+
|
63
|
+
@socket = DCell.zmq_context.socket(::ZMQ::PUSH)
|
64
|
+
unless ::ZMQ::Util.resultcode_ok? @socket.connect @addr
|
65
|
+
@socket.close
|
66
|
+
@socket = nil
|
67
|
+
raise "error connecting to #{addr}: #{::ZMQ::Util.error_string}"
|
68
|
+
end
|
69
|
+
|
70
|
+
@socket
|
71
|
+
end
|
72
|
+
|
73
|
+
# Find an actor registered with a given name on this node
|
74
|
+
def find(name)
|
75
|
+
request = Message::Find.new(Thread.mailbox, name)
|
76
|
+
send_message request
|
77
|
+
|
78
|
+
response = receive do |msg|
|
79
|
+
msg.respond_to?(:request_id) && msg.request_id == request.id
|
80
|
+
end
|
81
|
+
|
82
|
+
abort response.value if response.is_a? ErrorResponse
|
83
|
+
response.value
|
84
|
+
end
|
85
|
+
alias_method :[], :find
|
86
|
+
|
87
|
+
# List all registered actors on this node
|
88
|
+
def actors
|
89
|
+
request = Message::List.new(Thread.mailbox)
|
90
|
+
send_message request
|
91
|
+
|
92
|
+
response = receive do |msg|
|
93
|
+
msg.respond_to?(:request_id) && msg.request_id == request.id
|
94
|
+
end
|
95
|
+
|
96
|
+
abort response.value if response.is_a? ErrorResponse
|
97
|
+
response.value
|
98
|
+
end
|
99
|
+
alias_method :all, :actors
|
100
|
+
|
101
|
+
# Send a message to another DCell node
|
102
|
+
def send_message(message)
|
103
|
+
begin
|
104
|
+
string = Marshal.dump(message)
|
105
|
+
rescue => ex
|
106
|
+
abort ex
|
107
|
+
end
|
108
|
+
|
109
|
+
unless ::ZMQ::Util.resultcode_ok? socket.send_string string
|
110
|
+
raise "error sending 0MQ message: #{::ZMQ::Util.error_string}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
alias_method :<<, :send_message
|
114
|
+
|
115
|
+
# Friendlier inspection
|
116
|
+
def inspect
|
117
|
+
"#<DCell::Node[#{@id}] @addr=#{@addr.inspect}>"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|