celluloid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,97 @@
1
+ require 'thread'
2
+
3
+ module Celluloid
4
+ class MailboxError < StandardError; end # you can't message the dead
5
+
6
+ # Actors communicate with asynchronous messages. Messages are buffered in
7
+ # Mailboxes until Actors can act upon them.
8
+ class Mailbox
9
+ include Enumerable
10
+
11
+ def initialize
12
+ @messages = []
13
+ @lock = Mutex.new
14
+ @waker = Waker.new
15
+ end
16
+
17
+ # Add a message to the Mailbox
18
+ def <<(message)
19
+ @lock.synchronize do
20
+ @messages << message
21
+ @waker.signal
22
+ end
23
+ nil
24
+ rescue WakerError
25
+ raise MailboxError, "dead recipient"
26
+ end
27
+
28
+ # Add a high-priority system event to the Mailbox
29
+ def system_event(event)
30
+ @lock.synchronize do
31
+ @messages.unshift event
32
+
33
+ begin
34
+ @waker.signal
35
+ rescue WakerError
36
+ # Silently fail if messages are sent to dead actors
37
+ end
38
+ end
39
+ nil
40
+ end
41
+
42
+ # Receive a message from the Mailbox
43
+ def receive(&block)
44
+ message = nil
45
+
46
+ begin
47
+ @waker.wait
48
+ message = locate(&block)
49
+ raise message if message.is_a?(Celluloid::SystemEvent)
50
+ end while message.nil?
51
+
52
+ message
53
+ rescue Celluloid::WakerError
54
+ cleanup # force cleanup of the mailbox
55
+ raise MailboxError, "mailbox cleanup called during receive"
56
+ end
57
+
58
+ # Locate and remove a message from the mailbox which matches the given filter
59
+ def locate
60
+ @lock.synchronize do
61
+ if block_given?
62
+ index = @messages.index do |msg|
63
+ yield(msg) || msg.is_a?(Celluloid::SystemEvent)
64
+ end
65
+
66
+ @messages.slice!(index, 1).first if index
67
+ else
68
+ @messages.shift
69
+ end
70
+ end
71
+ end
72
+
73
+ # Cleanup any IO objects this Mailbox may be using
74
+ def cleanup
75
+ @lock.synchronize do
76
+ @waker.cleanup
77
+ @messages.each { |msg| msg.cleanup if msg.respond_to? :cleanup }
78
+ @messages.clear
79
+ end
80
+ end
81
+
82
+ # Cast to an array
83
+ def to_a
84
+ @lock.synchronize { @messages.dup }
85
+ end
86
+
87
+ # Iterate through the mailbox
88
+ def each(&block)
89
+ to_a.each(&block)
90
+ end
91
+
92
+ # Inspect the contents of the Mailbox
93
+ def inspect
94
+ "#<Celluloid::Mailbox[#{map { |m| m.inspect }.join(', ')}]>"
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,33 @@
1
+ require 'thread'
2
+
3
+ module Celluloid
4
+ # The Registry allows us to refer to specific actors by human-meaningful names
5
+ module Registry
6
+ @@registry = {}
7
+ @@registry_lock = Mutex.new
8
+
9
+ # Register an Actor
10
+ def []=(name, actor)
11
+ actor_singleton = class << actor; self; end
12
+ unless actor_singleton.ancestors.include?(Celluloid::ActorProxy)
13
+ raise ArgumentError, "not an actor"
14
+ end
15
+
16
+ @@registry_lock.synchronize do
17
+ @@registry[name.to_sym] = actor
18
+ end
19
+ end
20
+
21
+ # Retrieve an actor by name
22
+ def [](name)
23
+ @@registry_lock.synchronize do
24
+ @@registry[name.to_sym]
25
+ end
26
+ end
27
+ end
28
+
29
+ # Extend the actor module with the registry methods
30
+ module Actor
31
+ extend Registry
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ module Celluloid
2
+ # Responses to calls
3
+ class Response
4
+ attr_reader :call, :value
5
+
6
+ def initialize(call, value)
7
+ @call, @value = call, value
8
+ end
9
+ end
10
+
11
+ # Call completed successfully
12
+ class SuccessResponse < Response; end
13
+
14
+ # Call was aborted due to caller error
15
+ class ErrorResponse < Response; end
16
+ end
@@ -0,0 +1,40 @@
1
+ module Celluloid
2
+ # Supervisors are actors that watch over other actors and restart them if
3
+ # they crash
4
+ class Supervisor
5
+ include Actor
6
+ trap_exit :restart_actor
7
+
8
+ # Retrieve the actor this supervisor is supervising
9
+ attr_reader :actor
10
+
11
+ def self.supervise(klass, *args, &block)
12
+ spawn(nil, klass, *args, &block)
13
+ end
14
+
15
+ def self.supervise_as(name, klass, *args, &block)
16
+ spawn(name, klass, *args, &block)
17
+ end
18
+
19
+ def initialize(name, klass, *args, &block)
20
+ @name, @klass, @args, @block = name, klass, args, block
21
+ start_actor
22
+ end
23
+
24
+ def start_actor
25
+ @actor = @klass.spawn_link(*@args, &@block)
26
+ Celluloid::Actor[@name] = @actor if @name
27
+ end
28
+
29
+ # When actors die, regardless of the reason, restart them
30
+ def restart_actor(actor, reason)
31
+ start_actor
32
+ end
33
+
34
+ def inspect
35
+ str = "#<Celluloid::Supervisor(#{@klass})"
36
+ str << " " << @args.map { |arg| arg.inspect }.join(' ') unless @args.empty?
37
+ str << ">"
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ module Celluloid
2
+ class WakerError < StandardError; end # You can't wake the dead
3
+
4
+ # Wakes up sleepy threads so that they can check their mailbox
5
+ # Works like a ConditionVariable, except it's implemented as an IO object so
6
+ # that it can be multiplexed alongside other IO objects.
7
+ class Waker
8
+ PAYLOAD = "\0" # the payload doesn't matter. each byte is a signal
9
+ def initialize
10
+ @receiver, @sender = IO.pipe
11
+ end
12
+
13
+ # Wakes up the thread that is waiting for this Waker
14
+ def signal
15
+ @sender << PAYLOAD
16
+ nil
17
+ rescue IOError, Errno::EPIPE, Errno::EBADF
18
+ raise WakerError, "waker is already dead"
19
+ end
20
+
21
+ # Wait for another thread to signal this Waker
22
+ def wait
23
+ byte = @receiver.read(1)
24
+ raise WakerError, "can't wait on a dead waker" unless byte == PAYLOAD
25
+ rescue IOError
26
+ raise WakerError, "can't wait on a dead waker"
27
+ end
28
+
29
+ # Return the IO object which will be readable when this Waker is signaled
30
+ def io
31
+ @receiver
32
+ end
33
+
34
+ # Clean up the IO objects associated with this waker
35
+ def cleanup
36
+ @receiver.close rescue nil
37
+ @sender.close rescue nil
38
+ nil
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Actor do
4
+ before do
5
+ class MyActor
6
+ include Celluloid::Actor
7
+ attr_reader :name
8
+
9
+ def initialize(name)
10
+ @name = name
11
+ end
12
+
13
+ def change_name(new_name)
14
+ @name = new_name
15
+ end
16
+
17
+ def greet
18
+ "Hi, I'm #{@name}"
19
+ end
20
+
21
+ class Crash < StandardError; end
22
+ def crash
23
+ raise Crash, "the spec purposely crashed me :("
24
+ end
25
+
26
+ def crash_with_abort(reason)
27
+ abort Crash.new(reason)
28
+ end
29
+ end
30
+ end
31
+
32
+ it "handles synchronous calls" do
33
+ actor = MyActor.spawn "Troy McClure"
34
+ actor.greet.should == "Hi, I'm Troy McClure"
35
+ end
36
+
37
+ it "raises NoMethodError when a nonexistent method is called" do
38
+ actor = MyActor.spawn "Billy Bob Thornton"
39
+
40
+ proc do
41
+ actor.the_method_that_wasnt_there
42
+ end.should raise_exception(NoMethodError)
43
+ end
44
+
45
+ it "reraises exceptions which occur during synchronous calls in the caller" do
46
+ actor = MyActor.spawn "James Dean" # is this in bad taste?
47
+
48
+ proc do
49
+ actor.crash
50
+ end.should raise_exception(MyActor::Crash)
51
+ end
52
+
53
+ it "raises exceptions in the caller when abort is called, but keeps running" do
54
+ actor = MyActor.spawn "Al Pacino"
55
+
56
+ proc do
57
+ actor.crash_with_abort MyActor::Crash.new("You die motherfucker!")
58
+ end.should raise_exception(MyActor::Crash)
59
+
60
+ actor.should be_alive
61
+ end
62
+
63
+ it "raises DeadActorError if methods are synchronously called on a dead actor" do
64
+ actor = MyActor.spawn "James Dean"
65
+ actor.crash rescue nil
66
+
67
+ proc do
68
+ actor.greet
69
+ end.should raise_exception(Celluloid::DeadActorError)
70
+ end
71
+
72
+ it "handles asynchronous calls" do
73
+ actor = MyActor.spawn "Troy McClure"
74
+ actor.change_name! "Charlie Sheen"
75
+ actor.greet.should == "Hi, I'm Charlie Sheen"
76
+ end
77
+
78
+ it "knows when it's running as an actor" do
79
+ obj = MyActor.new "I'm an object"
80
+ obj.actor?.should be_false
81
+
82
+ actor = MyActor.spawn "Troy McClure"
83
+ actor.actor?.should be_true
84
+ end
85
+
86
+ it "inspects properly" do
87
+ actor = MyActor.spawn "Troy McClure"
88
+ actor.inspect.should match(/Celluloid::Actor\(MyActor/)
89
+ actor.inspect.should include('@name="Troy McClure"')
90
+ actor.inspect.should_not include("@celluloid")
91
+ end
92
+
93
+ context :linking do
94
+ before :each do
95
+ @kevin = MyActor.spawn "Kevin Bacon" # Some six degrees action here
96
+ @charlie = MyActor.spawn "Charlie Sheen"
97
+ end
98
+
99
+ it "links to other actors" do
100
+ @kevin.link @charlie
101
+ @kevin.linked_to?(@charlie).should be_true
102
+ @charlie.linked_to?(@kevin).should be_true
103
+ end
104
+
105
+ it "unlinks from other actors" do
106
+ @kevin.link @charlie
107
+ @kevin.unlink @charlie
108
+
109
+ @kevin.linked_to?(@charlie).should be_false
110
+ @charlie.linked_to?(@kevin).should be_false
111
+ end
112
+
113
+ it "traps exit messages from other actors" do
114
+ class Boss # like a boss
115
+ include Celluloid::Actor
116
+ trap_exit :lambaste_subordinate
117
+
118
+ def initialize(name)
119
+ @name = name
120
+ @subordinate_lambasted = false
121
+ end
122
+
123
+ def subordinate_lambasted?; @subordinate_lambasted; end
124
+
125
+ def lambaste_subordinate(actor, reason)
126
+ @subordinate_lambasted = true
127
+ end
128
+ end
129
+
130
+ chuck = Boss.spawn "Chuck Lorre"
131
+ chuck.link @charlie
132
+
133
+ proc do
134
+ @charlie.crash
135
+ end.should raise_exception(MyActor::Crash)
136
+
137
+ sleep 0.1 # hax to prevent a race between exit handling and the next call
138
+ chuck.should be_subordinate_lambasted
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Future do
4
+ it "creates future objects that can be retrieved later" do
5
+ future = Celluloid::Future() { 40 + 2 }
6
+ future.value.should == 42
7
+ end
8
+
9
+ it "passes arguments to future blocks" do
10
+ future = Celluloid::Future(40) { |n| n + 2 }
11
+ future.value.should == 42
12
+ end
13
+
14
+ it "reraises exceptions that occur when the value is retrieved" do
15
+ class ExampleError < StandardError; end
16
+
17
+ future = Celluloid::Future() { raise ExampleError, "oh noes crash!" }
18
+ proc { future.value }.should raise_exception(ExampleError)
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ class TestEvent < Celluloid::SystemEvent; end
4
+
5
+ describe Celluloid::Mailbox do
6
+ before :each do
7
+ @mailbox = Celluloid::Mailbox.new
8
+ end
9
+
10
+ it "receives messages" do
11
+ message = :ohai
12
+
13
+ @mailbox << message
14
+ @mailbox.receive.should == message
15
+ end
16
+
17
+ it "raises system events when received" do
18
+ @mailbox.system_event TestEvent.new("example")
19
+
20
+ proc do
21
+ @mailbox.receive
22
+ end.should raise_exception(TestEvent)
23
+ end
24
+
25
+ it "prioritizes system events over other messages" do
26
+ @mailbox << :dummy1
27
+ @mailbox << :dummy2
28
+ @mailbox.system_event TestEvent.new("example")
29
+
30
+ proc do
31
+ @mailbox.receive
32
+ end.should raise_exception(TestEvent)
33
+ end
34
+
35
+ it "selectively receives messages with a block" do
36
+ class Foo; end
37
+ class Bar; end
38
+ class Baz; end
39
+
40
+ foo, bar, baz = Foo.new, Bar.new, Baz.new
41
+
42
+ @mailbox << baz
43
+ @mailbox << foo
44
+ @mailbox << bar
45
+
46
+ @mailbox.receive { |msg| msg.is_a? Foo }.should == foo
47
+ @mailbox.receive { |msg| msg.is_a? Bar }.should == bar
48
+ @mailbox.receive.should == baz
49
+ end
50
+ end