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 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