celluloid 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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 the
10
- Actor model. Celluloid gives you thread-backed objects that run concurrently,
11
- providing the simplicity of Ruby objects for the most common use cases, but
12
- also the ability to call methods _asynchronously_, allowing the receiver to do
13
- things in the background while the caller carries on with its business.
14
- These concurrent objects are called "actors". Actors are somewhere in between
15
- the kind of object you're typically used to working with and a network service.
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 method call is available.
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.
@@ -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
- raise response.value
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
@@ -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
- unless obj.respond_to? @method
15
- exception = NoMethodError.new("undefined method `#{@method}' for #{obj.inspect}")
16
- @caller << ErrorResponse.new(self, exception)
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
- @caller << ErrorResponse.new(self, exception)
44
+ respond ErrorResponse.new(self, exception)
31
45
 
32
- # They should also crash the actor where they occurred
33
- raise exception
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
- @caller << SuccessResponse.new(self, result)
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
- @caller << ErrorResponse.new(self, exception)
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
- obj.send(@method, *@arguments, &@block) if obj.respond_to? @method
50
- rescue AbortError
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
- # FIXME: this should probably get logged
90
+ obj.__log_error ex, "#{obj.class}: async call aborted!"
53
91
  end
54
92
  end
55
93
  end
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.2'
3
3
  def self.version; VERSION; end
4
4
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: celluloid
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.2.1
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-07-14 00:00:00 -07:00
13
+ date: 2011-09-28 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency