celluloid 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.
@@ -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