celluloid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ module Celluloid
2
+ VERSION = File.read File.expand_path('../../VERSION', __FILE__)
3
+
4
+ def self.version; VERSION; end
5
+ end
6
+
7
+ require 'celluloid/actor'
8
+ require 'celluloid/actor_proxy'
9
+ require 'celluloid/calls'
10
+ require 'celluloid/core_ext'
11
+ require 'celluloid/events'
12
+ require 'celluloid/linking'
13
+ require 'celluloid/mailbox'
14
+ require 'celluloid/registry'
15
+ require 'celluloid/responses'
16
+ require 'celluloid/supervisor'
17
+ require 'celluloid/waker'
18
+
19
+ require 'celluloid/future'
@@ -0,0 +1,179 @@
1
+ module Celluloid
2
+ # Raised when trying to do Actor-like things outside Actor-space
3
+ class NotActorError < StandardError; end
4
+
5
+ # Raised when we're asked to do something to a dead actor
6
+ class DeadActorError < StandardError; end
7
+
8
+ # Raised when the caller makes an error that shouldn't crash this actor
9
+ class AbortError < StandardError
10
+ attr_reader :cause
11
+
12
+ def initialize(cause)
13
+ @cause = cause
14
+ super "caused by #{cause.inspect}: #{cause.to_s}"
15
+ end
16
+ end
17
+
18
+ # Actors are Celluloid's concurrency primitive. They're implemented as
19
+ # normal Ruby objects wrapped in threads which communicate with asynchronous
20
+ # messages. The implementation is inspired by Erlang's gen_server
21
+ module Actor
22
+ attr_reader :mailbox
23
+
24
+ # Methods added to classes which include Celluloid::Actor
25
+ module ClassMethods
26
+ # Retrieve the exit handler method for this class
27
+ attr_reader :exit_handler
28
+
29
+ # Create a new actor
30
+ def spawn(*args, &block)
31
+ actor = allocate
32
+ proxy = actor.__start_actor
33
+ proxy.send(:initialize, *args, &block)
34
+ proxy
35
+ end
36
+
37
+ # Create a new actor and link to the current one
38
+ def spawn_link(*args, &block)
39
+ current_actor = Thread.current[:actor]
40
+ raise NotActorError, "can't link outside actor context" unless current_actor
41
+
42
+ # FIXME: this is a bit repetitive with the code above
43
+ actor = allocate
44
+ proxy = actor.__start_actor
45
+ current_actor.link actor
46
+ proxy.send(:initialize, *args, &block)
47
+
48
+ proxy
49
+ end
50
+
51
+ # Create a supervisor which ensures an instance of an actor will restart
52
+ # an actor if it fails
53
+ def supervise(*args, &block)
54
+ Celluloid::Supervisor.supervise(self, *args, &block)
55
+ end
56
+
57
+ # Create a supervisor which ensures an instance of an actor will restart
58
+ # an actor if it fails, and keep the actor registered under a given name
59
+ def supervise_as(name, *args, &block)
60
+ Celluloid::Supervisor.supervise_as(name, self, *args, &block)
61
+ end
62
+
63
+ # Trap errors from actors we're linked to when they exit
64
+ def trap_exit(callback)
65
+ @exit_handler = callback.to_sym
66
+ end
67
+ end
68
+
69
+ # Instance methods added to the public API
70
+ module InstanceMethods
71
+ # Is this object functioning as an actor?
72
+ def actor?
73
+ !!@mailbox
74
+ end
75
+
76
+ # Is this actor alive?
77
+ def alive?
78
+ @thread.alive?
79
+ end
80
+
81
+ # Raise an exception in caller context, but stay running
82
+ def abort(cause)
83
+ raise AbortError.new(cause)
84
+ end
85
+
86
+ def inspect
87
+ return super unless actor?
88
+ str = "#<Celluloid::Actor(#{self.class}:0x#{self.object_id.to_s(16)})"
89
+
90
+ ivars = []
91
+ instance_variables.each do |ivar|
92
+ next if %w(@mailbox @links @proxy @thread).include? ivar.to_s
93
+ ivars << "#{ivar}=#{instance_variable_get(ivar).inspect}"
94
+ end
95
+
96
+ str << " " << ivars.join(' ') unless ivars.empty?
97
+ str << ">"
98
+ end
99
+ end
100
+
101
+ # Internal methods not intended as part of the public API
102
+ module InternalMethods
103
+ # Actor-specific initialization and startup
104
+ def __start_actor(*args, &block)
105
+ @mailbox = Mailbox.new
106
+ @links = Links.new
107
+ @proxy = ActorProxy.new(self, @mailbox)
108
+
109
+ @thread = Thread.new do
110
+ Thread.current[:actor] = self
111
+ Thread.current[:mailbox] = @mailbox
112
+ __run_actor
113
+ end
114
+
115
+ @proxy
116
+ end
117
+
118
+ # Run the actor
119
+ def __run_actor
120
+ __process_messages
121
+ rescue Exception => ex
122
+ __handle_crash(ex)
123
+ end
124
+
125
+ # Process incoming messages
126
+ def __process_messages
127
+ while true # instead of loop, for speed!
128
+ begin
129
+ call = @mailbox.receive
130
+ rescue ExitEvent => event
131
+ __handle_exit(event)
132
+ retry
133
+ end
134
+
135
+ call.dispatch(self)
136
+ end
137
+ end
138
+
139
+ # Handle exit events received by this actor
140
+ def __handle_exit(exit_event)
141
+ exit_handler = self.class.exit_handler
142
+ raise exit_event.reason unless exit_handler
143
+
144
+ send exit_handler, exit_event.actor, exit_event.reason
145
+ end
146
+
147
+ # Handle any exceptions that occur within a running actor
148
+ def __handle_crash(exception)
149
+ __log_error(exception)
150
+ @mailbox.cleanup
151
+
152
+ # Report the exit event to all actors we're linked to
153
+ exit_event = ExitEvent.new(@proxy, exception)
154
+
155
+ # Propagate the error to all linked actors
156
+ @links.each do |actor|
157
+ actor.mailbox.system_event exit_event
158
+ end
159
+ rescue Exception => handler_exception
160
+ __log_error(handler_exception, "/!\\ EXCEPTION IN ERROR HANDLER /!\\")
161
+ ensure
162
+ Thread.current.exit
163
+ end
164
+
165
+ # Log errors when an actor crashes
166
+ # FIXME: This should probably thunk to a real logger
167
+ def __log_error(ex, prefix = "!!! CRASH")
168
+ puts "#{prefix} #{self.class}: #{ex.class}: #{ex.to_s}\n#{ex.backtrace.join("\n")}"
169
+ end
170
+ end
171
+
172
+ def self.included(klass)
173
+ klass.extend ClassMethods
174
+ klass.send :include, InstanceMethods
175
+ klass.send :include, InternalMethods
176
+ klass.send :include, Linking
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,81 @@
1
+ module Celluloid
2
+ # A proxy object returned from Celluloid::Actor.spawn/spawn_link which
3
+ # dispatches calls and casts to normal Ruby objects which are running inside
4
+ # of their own threads.
5
+ class ActorProxy
6
+ # FIXME: not nearly enough methods are delegated here
7
+ attr_reader :mailbox
8
+
9
+ def initialize(actor, mailbox)
10
+ @actor, @mailbox = actor, mailbox
11
+ end
12
+
13
+ def send(meth, *args, &block)
14
+ __call :send, meth, *args, &block
15
+ end
16
+
17
+ def respond_to?(meth)
18
+ __call :respond_to?, meth
19
+ end
20
+
21
+ def methods(include_ancestors = true)
22
+ __call :methods, include_ancestors
23
+ end
24
+
25
+ def alive?
26
+ @actor.alive?
27
+ end
28
+
29
+ def inspect
30
+ return __call :inspect if alive?
31
+ "#<Celluloid::Actor(#{@actor.class}:0x#{@actor.object_id.to_s(16)}) dead>"
32
+ end
33
+
34
+ # method_missing black magic to call bang predicate methods asynchronously
35
+ def method_missing(meth, *args, &block)
36
+ # bang methods are async calls
37
+ if meth.to_s.match(/!$/)
38
+ unbanged_meth = meth.to_s.sub(/!$/, '')
39
+ our_mailbox = Thread.current.mailbox
40
+
41
+ begin
42
+ @mailbox << AsyncCall.new(our_mailbox, unbanged_meth, args, block)
43
+ rescue MailboxError
44
+ # Silently swallow asynchronous calls to dead actors. There's no way
45
+ # to reliably generate DeadActorErrors for async calls, so users of
46
+ # async calls should find other ways to deal with actors dying
47
+ # during an async call (i.e. linking/supervisors)
48
+ end
49
+
50
+ return # casts are async and return immediately
51
+ end
52
+
53
+ __call(meth, *args, &block)
54
+ end
55
+
56
+ # Make a synchronous call to the actor we're proxying to
57
+ def __call(meth, *args, &block)
58
+ our_mailbox = Thread.current.mailbox
59
+ call = SyncCall.new(our_mailbox, meth, args, block)
60
+
61
+ begin
62
+ @mailbox << call
63
+ rescue MailboxError
64
+ raise DeadActorError, "attempted to call a dead actor"
65
+ end
66
+
67
+ response = our_mailbox.receive do |msg|
68
+ msg.is_a? Response and msg.call == call
69
+ end
70
+
71
+ case response
72
+ when SuccessResponse
73
+ response.value
74
+ when ErrorResponse
75
+ raise response.value
76
+ else
77
+ raise "don't know how to handle #{response.class} messages!"
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,56 @@
1
+ module Celluloid
2
+ # Calls represent requests to an actor
3
+ class Call
4
+ attr_reader :caller, :method, :arguments, :block
5
+
6
+ def initialize(caller, method, arguments, block)
7
+ @caller, @method, @arguments, @block = caller, method, arguments, block
8
+ end
9
+ end
10
+
11
+ # Synchronous calls wait for a response
12
+ class SyncCall < Call
13
+ def dispatch(obj)
14
+ unless obj.respond_to? @method
15
+ exception = NoMethodError.new("undefined method `#{@method}' for #{obj.inspect}")
16
+ @caller << ErrorResponse.new(self, exception)
17
+ return
18
+ end
19
+
20
+ begin
21
+ result = obj.send @method, *@arguments, &@block
22
+ rescue AbortError => exception
23
+ # Aborting indicates a protocol error on the part of the caller
24
+ # It should crash the caller, but the exception isn't reraised
25
+ @caller << ErrorResponse.new(self, exception.cause)
26
+ return
27
+ rescue Exception => exception
28
+ # Exceptions that occur during synchronous calls are reraised in the
29
+ # context of the caller
30
+ @caller << ErrorResponse.new(self, exception)
31
+
32
+ # They should also crash the actor where they occurred
33
+ raise exception
34
+ end
35
+
36
+ @caller << SuccessResponse.new(self, result)
37
+ true
38
+ end
39
+
40
+ def cleanup
41
+ exception = DeadActorError.new("attempted to call a dead actor")
42
+ @caller << ErrorResponse.new(self, exception)
43
+ end
44
+ end
45
+
46
+ # Asynchronous calls don't wait for a response
47
+ class AsyncCall < Call
48
+ def dispatch(obj)
49
+ obj.send(@method, *@arguments, &@block) if obj.respond_to? @method
50
+ rescue AbortError
51
+ # Swallow aborted async calls, as they indicate the caller made a mistake
52
+ # FIXME: this should probably get logged
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,6 @@
1
+ # Monkeypatch Thread to allow lazy access to its Celluloid::Mailbox
2
+ class Thread
3
+ def mailbox
4
+ @mailbox ||= Celluloid::Mailbox.new
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ module Celluloid
2
+ # Exceptional system events which need to be processed out of band
3
+ class SystemEvent < Exception; end
4
+
5
+ # An actor has exited for the given reason
6
+ class ExitEvent < SystemEvent
7
+ attr_reader :actor, :reason
8
+
9
+ def initialize(actor, reason)
10
+ @actor, @reason = actor, reason
11
+ super reason.to_s
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ module Celluloid
2
+ def self.Future(*args, &block)
3
+ future = Celluloid::Future.spawn(*args, &block)
4
+ future.run!
5
+ future
6
+ end
7
+
8
+ class Future
9
+ include Celluloid::Actor
10
+
11
+ def initialize(*args, &block)
12
+ @args, @block = args, block
13
+ end
14
+
15
+ def run
16
+ @called = true
17
+ @value = @block[*@args]
18
+ rescue Exception => error
19
+ @error = error
20
+ end
21
+
22
+ def value
23
+ raise "not run yet" unless @called
24
+ abort @error if @error
25
+ @value
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,75 @@
1
+ require 'set'
2
+ require 'thread'
3
+
4
+ module Celluloid
5
+ # Thread safe storage of inter-actor links
6
+ class Links
7
+ include Enumerable
8
+
9
+ def initialize
10
+ @links = Set.new
11
+ @lock = Mutex.new
12
+ end
13
+
14
+ def <<(actor)
15
+ @lock.synchronize do
16
+ @links << actor
17
+ end
18
+ actor
19
+ end
20
+
21
+ def include?(actor)
22
+ @lock.synchronize do
23
+ @links.include? actor
24
+ end
25
+ end
26
+
27
+ def delete(actor)
28
+ @lock.synchronize do
29
+ @links.delete actor
30
+ end
31
+ actor
32
+ end
33
+
34
+ def each(&block)
35
+ @lock.synchronize do
36
+ @links.each(&block)
37
+ end
38
+ end
39
+
40
+ def inspect
41
+ @lock.synchronize do
42
+ links = @links.to_a.map { |l| "#{l.class}:#{l.object_id}" }.join(',')
43
+ "#<Celluloid::Links[#{links}]>"
44
+ end
45
+ end
46
+ end
47
+
48
+ # Support for linking actors together so they can crash or react to errors
49
+ module Linking
50
+ # Link this actor to another, allowing it to crash or react to errors
51
+ def link(actor)
52
+ actor.notify_link(@proxy)
53
+ self.notify_link(actor)
54
+ end
55
+
56
+ # Remove links to another actor
57
+ def unlink(actor)
58
+ actor.notify_unlink(@proxy)
59
+ self.notify_unlink(actor)
60
+ end
61
+
62
+ def notify_link(actor)
63
+ @links << actor
64
+ end
65
+
66
+ def notify_unlink(actor)
67
+ @links.delete actor
68
+ end
69
+
70
+ # Is this actor linked to another?
71
+ def linked_to?(actor)
72
+ @links.include? actor
73
+ end
74
+ end
75
+ end