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.
Files changed (44) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +4 -0
  3. data/CHANGES.md +8 -0
  4. data/Gemfile +2 -0
  5. data/README.md +209 -0
  6. data/Rakefile +3 -0
  7. data/benchmarks/messaging.rb +67 -0
  8. data/benchmarks/receiver.rb +37 -0
  9. data/dcell.gemspec +10 -3
  10. data/lib/celluloid/README +8 -0
  11. data/lib/celluloid/zmq.rb +28 -0
  12. data/lib/celluloid/zmq/mailbox.rb +13 -0
  13. data/lib/celluloid/zmq/reactor.rb +74 -0
  14. data/lib/dcell.rb +92 -2
  15. data/lib/dcell/actor_proxy.rb +4 -0
  16. data/lib/dcell/application.rb +6 -0
  17. data/lib/dcell/celluloid_ext.rb +57 -0
  18. data/lib/dcell/directory.rb +23 -0
  19. data/lib/dcell/global.rb +23 -0
  20. data/lib/dcell/mailbox_proxy.rb +61 -0
  21. data/lib/dcell/messages.rb +67 -0
  22. data/lib/dcell/node.rb +120 -0
  23. data/lib/dcell/registries/redis_adapter.rb +86 -0
  24. data/lib/dcell/registries/zk_adapter.rb +122 -0
  25. data/lib/dcell/responses.rb +16 -0
  26. data/lib/dcell/router.rb +71 -0
  27. data/lib/dcell/rspec.rb +1 -0
  28. data/lib/dcell/server.rb +80 -0
  29. data/lib/dcell/version.rb +1 -1
  30. data/spec/celluloid/zmq/mailbox_spec.rb +6 -0
  31. data/spec/dcell/actor_proxy_spec.rb +60 -0
  32. data/spec/dcell/celluloid_ext_spec.rb +21 -0
  33. data/spec/dcell/directory_spec.rb +8 -0
  34. data/spec/dcell/global_spec.rb +21 -0
  35. data/spec/dcell/node_spec.rb +23 -0
  36. data/spec/dcell/registries/redis_adapter_spec.rb +6 -0
  37. data/spec/dcell/registries/zk_adapter_spec.rb +11 -0
  38. data/spec/spec_helper.rb +16 -0
  39. data/spec/support/helpers.rb +40 -0
  40. data/spec/support/registry_examples.rb +35 -0
  41. data/spec/test_node.rb +33 -0
  42. data/tasks/rspec.task +7 -0
  43. data/tasks/zookeeper.task +58 -0
  44. metadata +111 -7
@@ -0,0 +1,86 @@
1
+ require 'redis'
2
+ require 'redis-namespace'
3
+
4
+ module DCell
5
+ module Registry
6
+ class RedisAdapter
7
+ def initialize(options)
8
+ # Convert all options to symbols :/
9
+ options = options.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
10
+
11
+ @env = options[:env] || 'production'
12
+ @namespace = options[:namespace] || "dcell_#{@env}"
13
+
14
+ redis = Redis.new options
15
+ @redis = Redis::Namespace.new @namespace, :redis => redis
16
+
17
+ @node_registry = NodeRegistry.new(@redis)
18
+ @global_registry = GlobalRegistry.new(@redis)
19
+ end
20
+
21
+ def clear_nodes
22
+ @node_registry.clear
23
+ end
24
+
25
+ def clear_globals
26
+ @global_registry.clear
27
+ end
28
+
29
+ class NodeRegistry
30
+ def initialize(redis)
31
+ @redis = redis
32
+ end
33
+
34
+ def get(node_id)
35
+ @redis.hget 'nodes', node_id
36
+ end
37
+
38
+ def set(node_id, addr)
39
+ @redis.hset 'nodes', node_id, addr
40
+ end
41
+
42
+ def nodes
43
+ @redis.hkeys 'nodes'
44
+ end
45
+
46
+ def clear
47
+ @redis.del 'nodes'
48
+ end
49
+ end
50
+
51
+ def get_node(node_id); @node_registry.get(node_id) end
52
+ def set_node(node_id, addr); @node_registry.set(node_id, addr) end
53
+ def nodes; @node_registry.nodes end
54
+
55
+ class GlobalRegistry
56
+ def initialize(redis)
57
+ @redis = redis
58
+ end
59
+
60
+ def get(key)
61
+ string = @redis.hget 'globals', key.to_s
62
+ Marshal.load string
63
+ end
64
+
65
+ # Set a global value
66
+ def set(key, value)
67
+ string = Marshal.dump value
68
+ @redis.hset 'globals', key.to_s, string
69
+ end
70
+
71
+ # The keys to all globals in the system
72
+ def global_keys
73
+ @redis.hkeys 'globals'
74
+ end
75
+
76
+ def clear
77
+ @redis.del 'globals'
78
+ end
79
+ end
80
+
81
+ def get_global(key); @global_registry.get(key) end
82
+ def set_global(key, value); @global_registry.set(key, value) end
83
+ def global_keys; @global_registry.global_keys end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,122 @@
1
+ require 'zk'
2
+
3
+ module DCell
4
+ module Registry
5
+ class ZkAdapter
6
+ PREFIX = "/dcell"
7
+ DEFAULT_PORT = 2181
8
+
9
+ # Create a new connection to Zookeeper
10
+ #
11
+ # servers: a list of Zookeeper servers to connect to. Each server in the
12
+ # list has a host/port configuration
13
+ def initialize(options)
14
+ # Stringify keys :/
15
+ options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
16
+
17
+ @env = options['env'] || 'production'
18
+ @base_path = "#{PREFIX}/#{@env}"
19
+
20
+ # Let them specify a single server instead of many
21
+ server = options['server']
22
+ if server
23
+ servers = [server]
24
+ else
25
+ servers = options['servers']
26
+ raise "no Zookeeper servers given" unless servers
27
+ end
28
+
29
+ # Add the default Zookeeper port unless specified
30
+ servers.map! do |server|
31
+ if server[/:\d+$/]
32
+ server
33
+ else
34
+ "#{server}:#{DEFAULT_PORT}"
35
+ end
36
+ end
37
+
38
+ @zk = ZK.new(*servers)
39
+ @node_registry = NodeRegistry.new(@zk, @base_path)
40
+ @global_registry = GlobalRegistry.new(@zk, @base_path)
41
+ end
42
+
43
+ def clear_nodes
44
+ @node_registry.clear
45
+ end
46
+
47
+ def clear_globals
48
+ @global_registry.clear
49
+ end
50
+
51
+ class NodeRegistry
52
+ def initialize(zk, base_path)
53
+ @zk, @base_path = zk, "#{base_path}/nodes"
54
+ @zk.mkdir_p @base_path
55
+ end
56
+
57
+ def get(node_id)
58
+ result, _ = @zk.get("#{@base_path}/#{node_id}")
59
+ result
60
+ rescue ZK::Exceptions::NoNode
61
+ end
62
+
63
+ def set(node_id, addr)
64
+ path = "#{@base_path}/#{node_id}"
65
+ @zk.set path, addr
66
+ rescue ZK::Exceptions::NoNode
67
+ @zk.create path, addr
68
+ end
69
+
70
+ def nodes
71
+ @zk.children @base_path
72
+ end
73
+
74
+ def clear
75
+ @zk.rm_rf @base_path
76
+ @zk.mkdir_p @base_path
77
+ end
78
+ end
79
+
80
+ def get_node(node_id); @node_registry.get(node_id) end
81
+ def set_node(node_id, addr); @node_registry.set(node_id, addr) end
82
+ def nodes; @node_registry.nodes end
83
+
84
+ class GlobalRegistry
85
+ def initialize(zk, base_path)
86
+ @zk, @base_path = zk, "#{base_path}/globals"
87
+ @zk.mkdir_p @base_path
88
+ end
89
+
90
+ def get(key)
91
+ value, _ = @zk.get "#{@base_path}/#{key}"
92
+ Marshal.load value
93
+ rescue ZK::Exceptions::NoNode
94
+ end
95
+
96
+ # Set a global value
97
+ def set(key, value)
98
+ path = "#{@base_path}/#{key}"
99
+ string = Marshal.dump value
100
+
101
+ @zk.set path, string
102
+ rescue ZK::Exceptions::NoNode
103
+ @zk.create path, string
104
+ end
105
+
106
+ # The keys to all globals in the system
107
+ def global_keys
108
+ @zk.children(@base_path)
109
+ end
110
+
111
+ def clear
112
+ @zk.rm_rf @base_path
113
+ @zk.mkdir_p @base_path
114
+ end
115
+ end
116
+
117
+ def get_global(key); @global_registry.get(key) end
118
+ def set_global(key, value); @global_registry.set(key, value) end
119
+ def global_keys; @global_registry.global_keys end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,16 @@
1
+ module DCell
2
+ # Responses to calls
3
+ class Response
4
+ attr_reader :request_id, :value
5
+
6
+ def initialize(request_id, value)
7
+ @request_id, @value = request_id, value
8
+ end
9
+ end
10
+
11
+ # Request successful
12
+ class SuccessResponse < Response; end
13
+
14
+ # Request failed
15
+ class ErrorResponse < Response; end
16
+ end
@@ -0,0 +1,71 @@
1
+ require 'weakref'
2
+
3
+ module DCell
4
+ # Route incoming messages to their recipient actors
5
+ class Router
6
+ @lock = Mutex.new
7
+ @table = {}
8
+
9
+ class << self
10
+ # Enter a mailbox into the registry
11
+ def register(mailbox)
12
+ id = mailbox.object_id.to_s(16)
13
+
14
+ @lock.synchronize do
15
+ ref = @table[id]
16
+ unless ref && ref.weakref_alive?
17
+ @table[id] = WeakRef.new(mailbox)
18
+ end
19
+ end
20
+
21
+ id
22
+ end
23
+
24
+ # Find a mailbox by its ID
25
+ def find(mailbox_id)
26
+ @lock.synchronize do
27
+ ref = @table[mailbox_id]
28
+ return unless ref
29
+ begin
30
+ ref.__getobj__
31
+ rescue WeakRef::RefError
32
+ # The referenced actor is dead, so prune the registry
33
+ @table.delete mailbox_id
34
+ nil
35
+ end
36
+ end
37
+ end
38
+
39
+ # Route a message to a given mailbox ID
40
+ def route(mailbox_id, message)
41
+ recipient = find mailbox_id
42
+
43
+ if recipient
44
+ recipient << message
45
+ else
46
+ Celluloid::Logger.debug("received message for invalid actor: #{mailbox_id.inspect}")
47
+ end
48
+ end
49
+
50
+ # Route a system event to a given mailbox ID
51
+ def route_system_event(mailbox_id, event)
52
+ recipient = find mailbox_id
53
+
54
+ if recipient
55
+ recipient.system_event event
56
+ else
57
+ Celluloid::Logger.debug("received message for invalid actor: #{mailbox_id.inspect}")
58
+ end
59
+ end
60
+
61
+ # Prune all entries that point to dead objects
62
+ def gc
63
+ @lock.synchronize do
64
+ @table.each do |id, ref|
65
+ @table.delete id unless ref.weakref_alive?
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path('../../../spec/support/registry_examples', __FILE__)
@@ -0,0 +1,80 @@
1
+ module DCell
2
+ # Servers handle incoming 0MQ traffic
3
+ class Server
4
+ include Celluloid::ZMQ
5
+
6
+ # Bind to the given 0MQ address (in URL form ala tcp://host:port)
7
+ def initialize
8
+ @addr = DCell.addr
9
+ @socket = DCell.zmq_context.socket(::ZMQ::PULL)
10
+
11
+ unless ::ZMQ::Util.resultcode_ok? @socket.setsockopt(::ZMQ::LINGER, 0)
12
+ @socket.close
13
+ raise "couldn't set ZMQ::LINGER: #{::ZMQ::Util.error_string}"
14
+ end
15
+
16
+ unless ::ZMQ::Util.resultcode_ok? @socket.bind(@addr)
17
+ @socket.close
18
+ raise "couldn't bind to #{@addr}: #{::ZMQ::Util.error_string}"
19
+ end
20
+
21
+ run!
22
+ end
23
+
24
+ # Wait for incoming 0MQ messages
25
+ def run
26
+ while true
27
+ wait_readable @socket
28
+ message = ''
29
+
30
+ rc = @socket.recv_string message
31
+ if ::ZMQ::Util.resultcode_ok? rc
32
+ handle_message message
33
+ else
34
+ raise "error receiving ZMQ string: #{::ZMQ::Util.error_string}"
35
+ end
36
+ end
37
+ end
38
+
39
+ # Shut down the server
40
+ def finalize
41
+ @socket.close
42
+ end
43
+
44
+ # Handle incoming messages
45
+ def handle_message(message)
46
+ begin
47
+ message = decode_message message
48
+ rescue InvalidMessageError => ex
49
+ Celluloid::Logger.warn("couldn't decode message: #{ex.class}: #{ex}")
50
+ return
51
+ end
52
+
53
+ begin
54
+ message.dispatch
55
+ rescue Exception => ex
56
+ Celluloid::Logger.crash("DCell::Server: message dispatch failed", ex)
57
+ end
58
+ end
59
+
60
+ class InvalidMessageError < StandardError; end # undecodable message
61
+
62
+ # Decode incoming messages
63
+ def decode_message(message)
64
+ if message[0..1].unpack("CC") == [Marshal::MAJOR_VERSION, Marshal::MINOR_VERSION]
65
+ begin
66
+ Marshal.load message
67
+ rescue => ex
68
+ raise InvalidMessageError, "invalid message: #{ex}"
69
+ end
70
+ else raise InvalidMessageError, "couldn't determine message format: #{message}"
71
+ end
72
+ end
73
+
74
+ # Terminate this server
75
+ def terminate
76
+ @socket.close
77
+ super
78
+ end
79
+ end
80
+ end
data/lib/dcell/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module DCell
2
- VERSION = "0.0.0"
2
+ VERSION = "0.0.1"
3
3
  end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+ require 'celluloid/rspec'
3
+
4
+ describe Celluloid::ZMQ::Mailbox do
5
+ it_behaves_like "a Celluloid Mailbox"
6
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe DCell::ActorProxy do
4
+ before :all do
5
+ @node = DCell::Node['test_node']
6
+ @node.id.should == 'test_node'
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
+ context :linking do
30
+ before :each do
31
+ @local_actor = LocalActor.new
32
+ end
33
+
34
+ it "links to remote actors" do
35
+ @local_actor.link @remote_actor
36
+ @local_actor.linked_to?(@remote_actor).should be_true
37
+ @remote_actor.linked_to?(@local_actor).should be_true
38
+ end
39
+
40
+ it "unlinks from remote actors" do
41
+ @local_actor.link @remote_actor
42
+ @local_actor.unlink @remote_actor
43
+
44
+ @local_actor.linked_to?(@remote_actor).should be_false
45
+ @remote_actor.linked_to?(@local_actor).should be_false
46
+ end
47
+
48
+ it "traps exit messages from other actors" do
49
+ @local_actor.link @remote_actor
50
+
51
+ expect do
52
+ @remote_actor.crash
53
+ end.to raise_exception(RuntimeError)
54
+
55
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
56
+ @local_actor.crash_reason.should be_a(RuntimeError)
57
+ @local_actor.crash_reason.to_s.should == "the spec purposely crashed me :("
58
+ end
59
+ end
60
+ end