celluloid 0.2.1 → 0.2.2
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.
- data/README.md +17 -10
- data/lib/celluloid/actor.rb +25 -3
- data/lib/celluloid/actor_proxy.rb +10 -2
- data/lib/celluloid/calls.rb +55 -17
- data/lib/celluloid/version.rb +1 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -6,13 +6,16 @@ Celluloid
|
|
6
6
|
> computers on a network, only able to communicate with messages"
|
7
7
|
> _--Alan Kay, creator of Smalltalk, on the meaning of "object oriented programming"_
|
8
8
|
|
9
|
-
Celluloid is a concurrent object framework for Ruby inspired by Erlang and
|
10
|
-
Actor model. Celluloid gives you thread-backed objects that run
|
11
|
-
providing the simplicity of Ruby objects for the most common
|
12
|
-
also the ability to call methods _asynchronously_, allowing
|
13
|
-
things in the background while the caller carries on
|
14
|
-
These concurrent objects are called "actors". Actors are
|
15
|
-
the kind of object you're typically used to working
|
9
|
+
Celluloid is a concurrent object framework for Ruby inspired by Erlang and
|
10
|
+
the Actor model. Celluloid gives you thread-backed objects that run
|
11
|
+
concurrently, providing the simplicity of Ruby objects for the most common
|
12
|
+
use cases, but also the ability to call methods _asynchronously_, allowing
|
13
|
+
the receiver to do things in the background while the caller carries on
|
14
|
+
with its business. These concurrent objects are called "actors". Actors are
|
15
|
+
somewhere in between the kind of object you're typically used to working
|
16
|
+
with and a network service.
|
17
|
+
|
18
|
+
Like Celluloid? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
|
16
19
|
|
17
20
|
Supported Platforms
|
18
21
|
-------------------
|
@@ -87,7 +90,8 @@ exceptions will crash the receiver, and making an asynchronous call to a
|
|
87
90
|
crashed object will not raise an error.
|
88
91
|
|
89
92
|
However, you can still handle errors created by asynchronous calls using
|
90
|
-
two features of Celluloid called _supervisors_ and _linking_.
|
93
|
+
two features of Celluloid called _supervisors_ and _linking_. See the
|
94
|
+
corresponding sections below for more information.
|
91
95
|
|
92
96
|
Futures
|
93
97
|
-------
|
@@ -107,7 +111,10 @@ report method from the charlie object used in the above example using a future:
|
|
107
111
|
The call to charlie.future immediately returns a Celluloid::Future object,
|
108
112
|
regardless of how long it takes to execute the "report" method. To obtain
|
109
113
|
the result of the call to "report", we call the _value_ method of the
|
110
|
-
future object. This call will block until the
|
114
|
+
future object. This call will block until the value returned from the method
|
115
|
+
call is available (i.e. the method has finished executing). If an exception
|
116
|
+
occured during the method call, the call to future.value will reraise the
|
117
|
+
same exception.
|
111
118
|
|
112
119
|
Futures also allow you to background the computation of any block:
|
113
120
|
|
@@ -367,4 +374,4 @@ Contributing to Celluloid
|
|
367
374
|
Copyright
|
368
375
|
---------
|
369
376
|
|
370
|
-
Copyright (c) 2011 Tony Arcieri. See LICENSE.txt for further details.
|
377
|
+
Copyright (c) 2011 Tony Arcieri. See LICENSE.txt for further details.
|
data/lib/celluloid/actor.rb
CHANGED
@@ -60,8 +60,7 @@ module Celluloid
|
|
60
60
|
def spawn_link(*args, &block)
|
61
61
|
current_actor = Thread.current[:actor]
|
62
62
|
raise NotActorError, "can't link outside actor context" unless current_actor
|
63
|
-
|
64
|
-
# FIXME: this is a bit repetitive with the code above
|
63
|
+
|
65
64
|
actor = allocate
|
66
65
|
proxy = actor.__start_actor
|
67
66
|
current_actor.link actor
|
@@ -139,6 +138,30 @@ module Celluloid
|
|
139
138
|
def wait(name)
|
140
139
|
@_signals.wait name
|
141
140
|
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Async calls
|
144
|
+
#
|
145
|
+
|
146
|
+
def method_missing(meth, *args, &block)
|
147
|
+
# bang methods are async calls
|
148
|
+
if meth.to_s.match(/!$/)
|
149
|
+
unbanged_meth = meth.to_s.sub(/!$/, '')
|
150
|
+
|
151
|
+
begin
|
152
|
+
@_mailbox << AsyncCall.new(@_mailbox, unbanged_meth, args, block)
|
153
|
+
rescue MailboxError
|
154
|
+
# Silently swallow asynchronous calls to dead actors. There's no way
|
155
|
+
# to reliably generate DeadActorErrors for async calls, so users of
|
156
|
+
# async calls should find other ways to deal with actors dying
|
157
|
+
# during an async call (i.e. linking/supervisors)
|
158
|
+
end
|
159
|
+
|
160
|
+
return # casts are async and return immediately
|
161
|
+
end
|
162
|
+
|
163
|
+
super
|
164
|
+
end
|
142
165
|
end
|
143
166
|
|
144
167
|
# Internal methods not intended as part of the public API
|
@@ -242,7 +265,6 @@ module Celluloid
|
|
242
265
|
end
|
243
266
|
|
244
267
|
# Log errors when an actor crashes
|
245
|
-
# FIXME: This should probably thunk to a real logger
|
246
268
|
def __log_error(ex, message = "#{self.class} crashed!")
|
247
269
|
message << "\n#{ex.class}: #{ex.to_s}\n"
|
248
270
|
message << ex.backtrace.join("\n")
|
@@ -92,12 +92,20 @@ module Celluloid
|
|
92
92
|
msg.is_a? Response and msg.call == call
|
93
93
|
end
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
case response
|
97
97
|
when SuccessResponse
|
98
98
|
response.value
|
99
99
|
when ErrorResponse
|
100
|
-
|
100
|
+
ex = response.value
|
101
|
+
|
102
|
+
if ex.is_a? AbortError
|
103
|
+
# Aborts are caused by caller error, so ensure they capture the
|
104
|
+
# caller's backtrace instead of the receiver's
|
105
|
+
raise ex.cause.class.new(ex.cause.message)
|
106
|
+
else
|
107
|
+
raise ex
|
108
|
+
end
|
101
109
|
else
|
102
110
|
raise "don't know how to handle #{response.class} messages!"
|
103
111
|
end
|
data/lib/celluloid/calls.rb
CHANGED
@@ -6,50 +6,88 @@ module Celluloid
|
|
6
6
|
def initialize(caller, method, arguments, block)
|
7
7
|
@caller, @method, @arguments, @block = caller, method, arguments, block
|
8
8
|
end
|
9
|
+
|
10
|
+
def check_signature(obj)
|
11
|
+
unless obj.respond_to? @method
|
12
|
+
raise NoMethodError, "undefined method `#{@method}' for #{obj.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
arity = obj.method(@method).arity
|
16
|
+
if arity >= 0
|
17
|
+
if arguments.size != arity
|
18
|
+
raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{arity})"
|
19
|
+
end
|
20
|
+
elsif arity < -1
|
21
|
+
mandatory_args = -arity - 1
|
22
|
+
if arguments.size < mandatory_args
|
23
|
+
raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{mandatory_args})"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
9
27
|
end
|
10
28
|
|
11
29
|
# Synchronous calls wait for a response
|
12
30
|
class SyncCall < Call
|
13
31
|
def dispatch(obj)
|
14
|
-
|
15
|
-
|
16
|
-
|
32
|
+
begin
|
33
|
+
check_signature(obj)
|
34
|
+
rescue Exception => ex
|
35
|
+
respond ErrorResponse.new(self, AbortError.new(ex))
|
17
36
|
return
|
18
37
|
end
|
19
38
|
|
20
39
|
begin
|
21
40
|
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
41
|
rescue Exception => exception
|
28
42
|
# Exceptions that occur during synchronous calls are reraised in the
|
29
43
|
# context of the caller
|
30
|
-
|
44
|
+
respond ErrorResponse.new(self, exception)
|
31
45
|
|
32
|
-
|
33
|
-
|
46
|
+
if exception.is_a? AbortError
|
47
|
+
# Aborting indicates a protocol error on the part of the caller
|
48
|
+
# It should crash the caller, but the exception isn't reraised
|
49
|
+
return
|
50
|
+
else
|
51
|
+
# Otherwise, it's a bug in this actor and should be reraised
|
52
|
+
raise exception
|
53
|
+
end
|
34
54
|
end
|
35
|
-
|
36
|
-
|
55
|
+
|
56
|
+
respond SuccessResponse.new(self, result)
|
37
57
|
true
|
38
58
|
end
|
39
59
|
|
40
60
|
def cleanup
|
41
61
|
exception = DeadActorError.new("attempted to call a dead actor")
|
42
|
-
|
62
|
+
respond ErrorResponse.new(self, exception)
|
43
63
|
end
|
64
|
+
|
65
|
+
#######
|
66
|
+
private
|
67
|
+
#######
|
68
|
+
|
69
|
+
def respond(message)
|
70
|
+
@caller << message
|
71
|
+
rescue MailboxError
|
72
|
+
# It's possible the caller exited or crashed before we could send a
|
73
|
+
# response to them.
|
74
|
+
end
|
44
75
|
end
|
45
76
|
|
46
77
|
# Asynchronous calls don't wait for a response
|
47
78
|
class AsyncCall < Call
|
48
79
|
def dispatch(obj)
|
49
|
-
|
50
|
-
|
80
|
+
begin
|
81
|
+
check_signature(obj)
|
82
|
+
rescue Exception => ex
|
83
|
+
obj.__log_error ex, "#{obj.class}: async call failed!"
|
84
|
+
return
|
85
|
+
end
|
86
|
+
|
87
|
+
obj.send(@method, *@arguments, &@block)
|
88
|
+
rescue AbortError => ex
|
51
89
|
# Swallow aborted async calls, as they indicate the caller made a mistake
|
52
|
-
#
|
90
|
+
obj.__log_error ex, "#{obj.class}: async call aborted!"
|
53
91
|
end
|
54
92
|
end
|
55
93
|
end
|
data/lib/celluloid/version.rb
CHANGED
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: celluloid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.2.
|
5
|
+
version: 0.2.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Tony Arcieri
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-
|
13
|
+
date: 2011-09-28 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|