dcell 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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