celluloid 0.8.0 → 0.9.0

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
@@ -1,6 +1,7 @@
1
- Celluloid
1
+ ![Celluloid](https://github.com/tarcieri/celluloid/raw/master/logo.png)
2
2
  =========
3
- [![Build Status](http://travis-ci.org/tarcieri/celluloid.png)](http://travis-ci.org/tarcieri/celluloid) [![Dependency Status](https://gemnasium.com/tarcieri/celluloid.png)](https://gemnasium.com/tarcieri/celluloid)
3
+ [![Build Status](https://secure.travis-ci.org/tarcieri/celluloid.png?branch=master)](http://travis-ci.org/tarcieri/celluloid)
4
+ [![Dependency Status](https://gemnasium.com/tarcieri/celluloid.png)](https://gemnasium.com/tarcieri/celluloid)
4
5
 
5
6
  > "I thought of objects being like biological cells and/or individual
6
7
  > computers on a network, only able to communicate with messages"
@@ -137,12 +138,18 @@ and [linking](https://github.com/tarcieri/celluloid/wiki/linking)
137
138
  [Please see the Celluloid Wiki](https://github.com/tarcieri/celluloid/wiki)
138
139
  for additional usage information.
139
140
 
141
+ Suggested Reading
142
+ -----------------
143
+
144
+ * [Concurrent Object-Oriented Programming in Python with ATOM](http://python.org/workshops/1997-10/proceedings/atom/)
145
+
140
146
  Contributing to Celluloid
141
147
  -------------------------
142
148
 
143
- * Fork Celluloid on github
149
+ * Fork this repository on github
144
150
  * Make your changes and send me a pull request
145
- * If I like them I'll merge them and give you commit access to my repository
151
+ * If I like them I'll merge them
152
+ * If I've accepted a patch, feel free to ask for commit access
146
153
 
147
154
  License
148
155
  -------
@@ -1,7 +1,9 @@
1
1
  require 'logger'
2
2
  require 'thread'
3
+ require 'timeout'
3
4
 
4
5
  module Celluloid
6
+ SHUTDOWN_TIMEOUT = 60 # How long actors have to terminate
5
7
  @logger = Logger.new STDERR
6
8
 
7
9
  class << self
@@ -16,6 +18,11 @@ module Celluloid
16
18
  !!Thread.current[:actor]
17
19
  end
18
20
 
21
+ # Is current actor running in exclusive mode?
22
+ def exclusive?
23
+ actor? and Thread.current[:actor].exclusive?
24
+ end
25
+
19
26
  # Obtain the currently running actor (if one exists)
20
27
  def current_actor
21
28
  actor = Thread.current[:actor]
@@ -43,14 +50,43 @@ module Celluloid
43
50
  Kernel.sleep interval
44
51
  end
45
52
  end
53
+
54
+ # Define an exception handler for actor crashes
55
+ def exception_handler(&block)
56
+ Logger.exception_handler(&block)
57
+ end
58
+
59
+ # Shut down all running actors
60
+ # FIXME: This should probably attempt a graceful shutdown of the supervision
61
+ # tree before iterating through all actors and telling them to terminate.
62
+ def shutdown
63
+ Timeout.timeout(SHUTDOWN_TIMEOUT) do
64
+ futures = Actor.all.each do |actor|
65
+ begin
66
+ actor.future(:terminate)
67
+ rescue DeadActorError, MailboxError
68
+ end
69
+ end
70
+
71
+ futures.each do |future|
72
+ begin
73
+ future.value
74
+ rescue DeadActorError, MailboxError
75
+ end
76
+ end
77
+ end
78
+ end
46
79
  end
47
80
 
81
+ # Terminate all actors at exit
82
+ at_exit { shutdown }
83
+
48
84
  # Class methods added to classes which include Celluloid
49
85
  module ClassMethods
50
86
  # Create a new actor
51
87
  def new(*args, &block)
52
88
  proxy = Actor.new(allocate).proxy
53
- proxy.send(:initialize, *args, &block)
89
+ proxy.send(:__send__, :initialize, *args, &block)
54
90
  proxy
55
91
  end
56
92
  alias_method :spawn, :new
@@ -62,7 +98,7 @@ module Celluloid
62
98
 
63
99
  proxy = Actor.new(allocate).proxy
64
100
  current_actor.link proxy
65
- proxy.send(:initialize, *args, &block)
101
+ proxy.send(:__send__, :initialize, *args, &block)
66
102
  proxy
67
103
  end
68
104
  alias_method :spawn_link, :new_link
@@ -90,11 +126,26 @@ module Celluloid
90
126
  # Configure a custom mailbox factory
91
127
  def use_mailbox(klass = nil, &block)
92
128
  if block
93
- define_method(:mailbox_factory, &block)
129
+ @mailbox_factory = block
130
+ else
131
+ @mailbox_factory = proc { klass.new }
132
+ end
133
+ end
134
+
135
+ # Create a mailbox for this actor
136
+ def mailbox_factory
137
+ if defined?(@mailbox_factory)
138
+ @mailbox_factory.call
139
+ elsif defined?(super)
140
+ super
94
141
  else
95
- define_method(:mailbox_factory) { klass.new }
142
+ Mailbox.new
96
143
  end
97
144
  end
145
+
146
+ def ===(other)
147
+ other.kind_of? self
148
+ end
98
149
  end
99
150
 
100
151
  #
@@ -193,7 +244,13 @@ module Celluloid
193
244
  Celluloid.sleep(interval)
194
245
  end
195
246
 
196
- # Call a block after a given interval
247
+ # Run given block in an exclusive mode: all synchronous calls block the whole
248
+ # actor, not only current message processing.
249
+ def exclusive(&block)
250
+ Thread.current[:actor].exclusive(&block)
251
+ end
252
+
253
+ # Call a block after a given interval, returning a Celluloid::Timer object
197
254
  def after(interval, &block)
198
255
  Thread.current[:actor].after(interval, &block)
199
256
  end
@@ -207,11 +264,6 @@ module Celluloid
207
264
  Future.new(&block).value
208
265
  end
209
266
 
210
- # Deprecated, do not use
211
- def async
212
- raise "Celluloid#async is defunct. Please use #defer instead"
213
- end
214
-
215
267
  # Process async calls via method_missing
216
268
  def method_missing(meth, *args, &block)
217
269
  # bang methods are async calls
@@ -245,6 +297,7 @@ require 'celluloid/fsm'
245
297
  require 'celluloid/links'
246
298
  require 'celluloid/logger'
247
299
  require 'celluloid/mailbox'
300
+ require 'celluloid/pool'
248
301
  require 'celluloid/receivers'
249
302
  require 'celluloid/registry'
250
303
  require 'celluloid/responses'
@@ -22,58 +22,64 @@ module Celluloid
22
22
  extend Registry
23
23
  attr_reader :proxy, :tasks, :links, :mailbox
24
24
 
25
- # Invoke a method on the given actor via its mailbox
26
- def self.call(mailbox, meth, *args, &block)
27
- call = SyncCall.new(Thread.mailbox, meth, args, block)
25
+ class << self
26
+ # Invoke a method on the given actor via its mailbox
27
+ def call(mailbox, meth, *args, &block)
28
+ call = SyncCall.new(Thread.mailbox, meth, args, block)
28
29
 
29
- begin
30
- mailbox << call
31
- rescue MailboxError
32
- raise DeadActorError, "attempted to call a dead actor"
33
- end
30
+ begin
31
+ mailbox << call
32
+ rescue MailboxError
33
+ raise DeadActorError, "attempted to call a dead actor"
34
+ end
34
35
 
35
- if Celluloid.actor?
36
- # The current task will be automatically resumed when we get a response
37
- Task.suspend :callwait
38
- else
39
- # Otherwise we're inside a normal thread, so block
40
- response = Thread.mailbox.receive do |msg|
41
- msg.respond_to?(:call) and msg.call == call
36
+ if Celluloid.actor? and not Celluloid.exclusive?
37
+ # The current task will be automatically resumed when we get a response
38
+ Task.suspend(:callwait).value
39
+ else
40
+ # Otherwise we're inside a normal thread, so block
41
+ response = Thread.mailbox.receive do |msg|
42
+ msg.respond_to?(:call) and msg.call == call
43
+ end
44
+
45
+ response.value
42
46
  end
47
+ end
43
48
 
44
- response.value
49
+ # Invoke a method asynchronously on an actor via its mailbox
50
+ def async(mailbox, meth, *args, &block)
51
+ begin
52
+ mailbox << AsyncCall.new(Thread.mailbox, meth, args, block)
53
+ rescue MailboxError
54
+ # Silently swallow asynchronous calls to dead actors. There's no way
55
+ # to reliably generate DeadActorErrors for async calls, so users of
56
+ # async calls should find other ways to deal with actors dying
57
+ # during an async call (i.e. linking/supervisors)
58
+ end
45
59
  end
46
- end
47
60
 
48
- # Invoke a method asynchronously on an actor via its mailbox
49
- def self.async(mailbox, meth, *args, &block)
50
- begin
51
- mailbox << AsyncCall.new(Thread.mailbox, meth, args, block)
52
- rescue MailboxError
53
- # Silently swallow asynchronous calls to dead actors. There's no way
54
- # to reliably generate DeadActorErrors for async calls, so users of
55
- # async calls should find other ways to deal with actors dying
56
- # during an async call (i.e. linking/supervisors)
61
+ # Call a method asynchronously and retrieve its value later
62
+ def future(mailbox, meth, *args, &block)
63
+ future = Future.new
64
+ future.execute(mailbox, meth, args, block)
65
+ future
57
66
  end
58
- end
59
67
 
60
- # Call a method asynchronously and retrieve its value later
61
- def self.future(mailbox, meth, *args, &block)
62
- future = Future.new
63
- future.execute(mailbox, meth, args, block)
64
- future
68
+ # Obtain all running actors in the system
69
+ def all
70
+ actors = []
71
+ Thread.list.each do |t|
72
+ actor = t[:actor]
73
+ actors << actor.proxy if actor
74
+ end
75
+ actors
76
+ end
65
77
  end
66
78
 
67
79
  # Wrap the given subject with an Actor
68
80
  def initialize(subject)
69
- @subject = subject
70
-
71
- if subject.respond_to? :mailbox_factory
72
- @mailbox = subject.mailbox_factory
73
- else
74
- @mailbox = Mailbox.new
75
- end
76
-
81
+ @subject = subject
82
+ @mailbox = subject.class.mailbox_factory
77
83
  @proxy = ActorProxy.new(@mailbox, subject.class.to_s)
78
84
  @tasks = Set.new
79
85
  @links = Links.new
@@ -81,11 +87,11 @@ module Celluloid
81
87
  @receivers = Receivers.new
82
88
  @timers = Timers.new
83
89
  @running = true
90
+ @exclusive = false
84
91
 
85
92
  @thread = ThreadPool.get do
86
93
  Thread.current[:actor] = self
87
94
  Thread.current[:mailbox] = @mailbox
88
-
89
95
  run
90
96
  end
91
97
  end
@@ -95,6 +101,19 @@ module Celluloid
95
101
  @running
96
102
  end
97
103
 
104
+ # Is this actor running in exclusive mode?
105
+ def exclusive?
106
+ @exclusive
107
+ end
108
+
109
+ # Execute a code block in exclusive mode.
110
+ def exclusive
111
+ @exclusive = true
112
+ yield
113
+ ensure
114
+ @exclusive = false
115
+ end
116
+
98
117
  # Terminate this actor
99
118
  def terminate
100
119
  @running = false
@@ -118,32 +137,30 @@ module Celluloid
118
137
 
119
138
  # Run the actor loop
120
139
  def run
121
- while @running
122
- begin
123
- message = @mailbox.receive(timeout)
124
- rescue ExitEvent => exit_event
125
- Task.new(:exit_handler) { handle_exit_event exit_event }.resume
126
- retry
127
- end
128
-
129
- if message
130
- handle_message message
131
- else
132
- # No message indicates a timeout
133
- @timers.fire
134
- @receivers.fire_timers
140
+ begin
141
+ while @running
142
+ begin
143
+ message = @mailbox.receive(timeout)
144
+ rescue ExitEvent => exit_event
145
+ Task.new(:exit_handler) { handle_exit_event exit_event }.resume
146
+ retry
147
+ end
148
+
149
+ if message
150
+ handle_message message
151
+ else
152
+ # No message indicates a timeout
153
+ @timers.fire
154
+ @receivers.fire_timers
155
+ end
135
156
  end
157
+ rescue MailboxShutdown
158
+ # If the mailbox detects shutdown, exit the actor
136
159
  end
137
160
 
138
- cleanup ExitEvent.new(@proxy)
139
- rescue MailboxShutdown
140
- # If the mailbox detects shutdown, exit the actor
141
- @running = false
142
- rescue Exception => ex
143
- @running = false
161
+ shutdown
162
+ rescue => ex
144
163
  handle_crash(ex)
145
- ensure
146
- ThreadPool.put @thread
147
164
  end
148
165
 
149
166
  # How long to wait until the next timer fires
@@ -169,9 +186,13 @@ module Celluloid
169
186
 
170
187
  # Sleep for the given amount of time
171
188
  def sleep(interval)
172
- task = Task.current
173
- @timers.add(interval) { task.resume }
174
- Task.suspend :sleeping
189
+ if Celluloid.exclusive?
190
+ Kernel.sleep(interval)
191
+ else
192
+ task = Task.current
193
+ @timers.add(interval) { task.resume }
194
+ Task.suspend :sleeping
195
+ end
175
196
  end
176
197
 
177
198
  # Handle an incoming message
@@ -180,7 +201,7 @@ module Celluloid
180
201
  when Call
181
202
  Task.new(:message_handler) { message.dispatch(@subject) }.resume
182
203
  when Response
183
- message.call.task.resume message.value
204
+ message.call.task.resume message
184
205
  else
185
206
  @receivers.handle_message(message)
186
207
  end
@@ -202,22 +223,34 @@ module Celluloid
202
223
  # Handle any exceptions that occur within a running actor
203
224
  def handle_crash(exception)
204
225
  Logger.crash("#{@subject.class} crashed!", exception)
205
- cleanup ExitEvent.new(@proxy, exception)
206
- rescue Exception => ex
226
+ shutdown ExitEvent.new(@proxy, exception)
227
+ rescue => ex
207
228
  Logger.crash("#{@subject.class}: ERROR HANDLER CRASHED!", ex)
208
229
  end
209
230
 
210
231
  # Handle cleaning up this actor after it exits
232
+ def shutdown(exit_event = ExitEvent.new(@proxy))
233
+ run_finalizer
234
+ cleanup exit_event
235
+ ensure
236
+ Thread.current[:actor] = nil
237
+ Thread.current[:mailbox] = nil
238
+ end
239
+
240
+ # Run the user-defined finalizer, if one is set
241
+ def run_finalizer
242
+ @subject.finalize if @subject.respond_to? :finalize
243
+ rescue => ex
244
+ Logger.crash("#{@subject.class}#finalize crashed!", ex)
245
+ end
246
+
247
+ # Clean up after this actor
211
248
  def cleanup(exit_event)
212
249
  @mailbox.shutdown
213
250
  @links.send_event exit_event
214
251
  tasks.each { |task| task.terminate }
215
-
216
- begin
217
- @subject.finalize if @subject.respond_to? :finalize
218
- rescue Exception => ex
219
- Logger.crash("#{@subject.class}#finalize crashed!", ex)
220
- end
252
+ rescue => ex
253
+ Logger.crash("#{@subject.class}: CLEANUP CRASHED!", ex)
221
254
  end
222
255
  end
223
256
  end
@@ -17,6 +17,14 @@ module Celluloid
17
17
  Actor.call @mailbox, :send, :class
18
18
  end
19
19
 
20
+ def is_a?(klass)
21
+ Actor.call @mailbox, :is_a?, klass
22
+ end
23
+
24
+ def kind_of?(klass)
25
+ Actor.call @mailbox, :kind_of?, klass
26
+ end
27
+
20
28
  def respond_to?(meth)
21
29
  Actor.call @mailbox, :respond_to?, meth
22
30
  end
@@ -18,8 +18,6 @@ module Celluloid
18
18
  @call.dispatch(block)
19
19
  rescue
20
20
  # Exceptions in blocks will get raised when the value is retrieved
21
- ensure
22
- ThreadPool.put Thread.current
23
21
  end
24
22
  end
25
23
  else
@@ -52,6 +52,9 @@ module Celluloid
52
52
  supervisable = @supervisors.delete supervisor
53
53
  raise "a supervisable went missing. This shouldn't be!" unless supervisable
54
54
 
55
+ # Ignore supervisors that shut down cleanly
56
+ return unless reason
57
+
55
58
  supervisor = supervisable.supervise
56
59
  @supervisors[supervisor] = supervisable
57
60
  end
@@ -1,5 +1,6 @@
1
1
  module Celluloid
2
2
  module Logger
3
+ @exception_handlers = []
3
4
  module_function
4
5
 
5
6
  # Send a debug message
@@ -24,9 +25,29 @@ module Celluloid
24
25
 
25
26
  # Handle a crash
26
27
  def crash(string, exception)
27
- string += "\n#{exception.class}: #{exception.to_s}\n"
28
- string << exception.backtrace.join("\n")
29
- error(string)
28
+ string << "\n" << format_exception(exception)
29
+ error string
30
+
31
+ @exception_handlers.each do |handler|
32
+ begin
33
+ handler.call(exception)
34
+ rescue => ex
35
+ error "EXCEPTION HANDLER CRASHED:\n" << format_exception(ex)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Format an exception message
41
+ def format_exception(exception)
42
+ str = "#{exception.class}: #{exception.to_s}\n"
43
+ str << exception.backtrace.join("\n")
44
+ end
45
+
46
+ # Define an exception handler
47
+ # NOTE: These should be defined at application start time
48
+ def exception_handler(&block)
49
+ @exception_handlers << block
50
+ nil
30
51
  end
31
52
  end
32
53
  end
@@ -14,59 +14,65 @@ module Celluloid
14
14
 
15
15
  def initialize
16
16
  @messages = []
17
- @lock = Mutex.new
17
+ @mutex = Mutex.new
18
18
  @dead = false
19
19
  @condition = ConditionVariable.new
20
20
  end
21
21
 
22
22
  # Add a message to the Mailbox
23
23
  def <<(message)
24
- @lock.lock
25
- raise MailboxError, "dead recipient" if @dead
24
+ @mutex.lock
25
+ begin
26
+ raise MailboxError, "dead recipient" if @dead
26
27
 
27
- @messages << message
28
- @condition.signal
29
- nil
30
- ensure @lock.unlock
28
+ @messages << message
29
+ @condition.signal
30
+ nil
31
+ ensure @mutex.unlock
32
+ end
31
33
  end
32
34
 
33
35
  # Add a high-priority system event to the Mailbox
34
36
  def system_event(event)
35
- @lock.lock
36
- unless @dead # Silently fail if messages are sent to dead actors
37
- @messages.unshift event
38
- @condition.signal
37
+ @mutex.lock
38
+ begin
39
+ unless @dead # Silently fail if messages are sent to dead actors
40
+ @messages.unshift event
41
+ @condition.signal
42
+ end
43
+ nil
44
+ ensure @mutex.unlock
39
45
  end
40
- nil
41
- ensure @lock.unlock
42
46
  end
43
47
 
44
48
  # Receive a message from the Mailbox
45
49
  def receive(timeout = nil, &block)
46
50
  message = nil
47
51
 
48
- @lock.lock
49
- raise MailboxError, "attempted to receive from a dead mailbox" if @dead
50
-
52
+ @mutex.lock
51
53
  begin
52
- message = next_message(&block)
53
-
54
- unless message
55
- if timeout
56
- now = Time.now
57
- wait_until ||= now + timeout
58
- wait_interval = wait_until - now
59
- return if wait_interval < 0
60
- else
61
- wait_interval = nil
54
+ raise MailboxError, "attempted to receive from a dead mailbox" if @dead
55
+
56
+ begin
57
+ message = next_message(&block)
58
+
59
+ unless message
60
+ if timeout
61
+ now = Time.now
62
+ wait_until ||= now + timeout
63
+ wait_interval = wait_until - now
64
+ return if wait_interval <= 0
65
+ else
66
+ wait_interval = nil
67
+ end
68
+
69
+ @condition.wait(@mutex, wait_interval)
62
70
  end
71
+ end until message
63
72
 
64
- @condition.wait(@lock, wait_interval)
65
- end
66
- end until message
67
-
68
- message
69
- ensure @lock.unlock
73
+ message
74
+ ensure @mutex.unlock
75
+ end
70
76
  end
71
77
 
72
78
  # Retrieve the next message in the mailbox
@@ -89,16 +95,16 @@ module Celluloid
89
95
 
90
96
  # Shut down this mailbox and clean up its contents
91
97
  def shutdown
92
- messages = nil
93
-
94
- @lock.lock
95
- messages = @messages
96
- @messages = []
97
- @dead = true
98
+ @mutex.lock
99
+ begin
100
+ messages = @messages
101
+ @messages = []
102
+ @dead = true
103
+ ensure @mutex.unlock
104
+ end
98
105
 
99
106
  messages.each { |msg| msg.cleanup if msg.respond_to? :cleanup }
100
107
  true
101
- ensure @lock.unlock
102
108
  end
103
109
 
104
110
  # Is the mailbox alive?
@@ -108,9 +114,7 @@ module Celluloid
108
114
 
109
115
  # Cast to an array
110
116
  def to_a
111
- @lock.lock
112
- @messages.dup
113
- ensure @lock.unlock
117
+ @mutex.synchronize { @messages.dup }
114
118
  end
115
119
 
116
120
  # Iterate through the mailbox
@@ -0,0 +1,103 @@
1
+ module Celluloid
2
+ # Pools provide groups of actors which can service requests
3
+ class Pool
4
+ include Celluloid
5
+ trap_exit :crash_handler
6
+
7
+ # Takes a class of actor to pool and a hash of options:
8
+ #
9
+ # * initial_size: how many actors to eagerly create
10
+ # * max_size: maximum number of actors (default nil, unlimited)
11
+ # * args: an array of arguments to pass to the actor's initialize
12
+ def initialize(klass, options = {})
13
+ opts = {
14
+ :initial_size => 1,
15
+ :max_size => nil,
16
+ :args => []
17
+ }.merge(options)
18
+
19
+ @klass, @args = klass, opts[:args]
20
+ @max_actors = opts[:max_size]
21
+ @idle_actors, @running_actors = 0, 0
22
+ @actors = []
23
+
24
+ opts[:initial_size].times do
25
+ @actors << spawn
26
+ @idle_actors += 1
27
+ end
28
+ end
29
+
30
+ # Get an actor from the pool. Actors taken from the pool must be put back
31
+ # with Pool#put. Alternatively, you can use get with a block form:
32
+ #
33
+ # pool.get { |actor| ... }
34
+ #
35
+ # This will automatically return actors to the pool when the block completes
36
+ def get
37
+ if @max_actors and @running_actors == @max_actors
38
+ wait :ready
39
+ end
40
+
41
+ actor = @actors.pop
42
+ if actor
43
+ @idle_actors -= 1
44
+ else
45
+ actor = spawn
46
+ end
47
+
48
+ if block_given?
49
+ begin
50
+ yield actor
51
+ rescue => ex
52
+ end
53
+
54
+ put actor
55
+ abort ex if ex
56
+ nil
57
+ else
58
+ actor
59
+ end
60
+ end
61
+
62
+ # Return an actor to the pool
63
+ def put(actor)
64
+ begin
65
+ raise TypeError, "expecting a #{@klass} actor" unless actor.is_a? @klass
66
+ rescue DeadActorError
67
+ # The actor may have died before it was handed back to us
68
+ # We'll let the crash_handler deal with it in due time
69
+ return
70
+ end
71
+
72
+ @actors << actor
73
+ @idle_actors += 1
74
+ end
75
+
76
+ # Number of active actors in this pool
77
+ def size
78
+ @running_actors
79
+ end
80
+
81
+ # Number of idle actors in the pool
82
+ def idle_count
83
+ @idle_actors
84
+ end
85
+ alias_method :idle_size, :idle_count
86
+
87
+ # Handle crashed actors
88
+ def crash_handler(actor, reason)
89
+ @idle_actors -= 1 if @actors.delete actor
90
+ @running_actors -= 1
91
+
92
+ # If we were maxed out before...
93
+ signal :ready if @max_actors and @running_actors + 1 == @max_actors
94
+ end
95
+
96
+ # Spawn an actor of the given class
97
+ def spawn
98
+ worker = @klass.new_link(*@args)
99
+ @running_actors += 1
100
+ worker
101
+ end
102
+ end
103
+ end
@@ -10,17 +10,21 @@ module Celluloid
10
10
 
11
11
  # Receive an asynchronous message
12
12
  def receive(timeout = nil, &block)
13
- receiver = Receiver.new block
13
+ if Celluloid.exclusive?
14
+ Thread.mailbox.receive(timeout, &block)
15
+ else
16
+ receiver = Receiver.new block
14
17
 
15
- if timeout
16
- receiver.timer = @timers.add(timeout) do
17
- @receivers.delete receiver
18
- receiver.resume
18
+ if timeout
19
+ receiver.timer = @timers.add(timeout) do
20
+ @receivers.delete receiver
21
+ receiver.resume
22
+ end
19
23
  end
20
- end
21
24
 
22
- @receivers << receiver
23
- Task.suspend :receiving
25
+ @receivers << receiver
26
+ Task.suspend :receiving
27
+ end
24
28
  end
25
29
 
26
30
  # How long to wait until the next timer fires
@@ -9,8 +9,9 @@ module Celluloid
9
9
 
10
10
  # Wait for the given signal and return the associated value
11
11
  def wait(signal)
12
- tasks = @waiting[signal]
12
+ raise "cannot wait for signals while exclusive" if Celluloid.exclusive?
13
13
 
14
+ tasks = @waiting[signal]
14
15
  case tasks
15
16
  when Array
16
17
  tasks << Task.current
@@ -28,7 +28,7 @@ module Celluloid
28
28
 
29
29
  begin
30
30
  @actor = @klass.new_link(*@args, &@block)
31
- rescue => ex
31
+ rescue
32
32
  failures += 1
33
33
  if failures >= start_attempts
34
34
  failures = 0
@@ -45,6 +45,9 @@ module Celluloid
45
45
 
46
46
  # When actors die, regardless of the reason, restart them
47
47
  def restart_actor(actor, reason)
48
+ # If the actor we're supervising exited cleanly, exit the supervisor cleanly too
49
+ terminate unless reason
50
+
48
51
  start_actor if @started
49
52
  end
50
53
 
@@ -4,7 +4,7 @@ module Celluloid
4
4
  # Maintain a thread pool FOR SPEED!!
5
5
  module ThreadPool
6
6
  @pool = []
7
- @lock = Mutex.new
7
+ @mutex = Mutex.new
8
8
 
9
9
  # TODO: should really adjust this based on usage
10
10
  @max_idle = 16
@@ -14,7 +14,7 @@ module Celluloid
14
14
 
15
15
  # Get a thread from the pool, running the given block
16
16
  def get(&block)
17
- @lock.synchronize do
17
+ @mutex.synchronize do
18
18
  if @pool.empty?
19
19
  thread = create
20
20
  else
@@ -28,7 +28,7 @@ module Celluloid
28
28
 
29
29
  # Return a thread to the pool
30
30
  def put(thread)
31
- @lock.synchronize do
31
+ @mutex.synchronize do
32
32
  if @pool.size >= @max_idle
33
33
  thread[:queue] << nil
34
34
  else
@@ -41,14 +41,17 @@ module Celluloid
41
41
  def create
42
42
  queue = Queue.new
43
43
  thread = Thread.new do
44
- begin
45
- while func = queue.pop
46
- func.call
44
+ while proc = queue.pop
45
+ begin
46
+ proc.call
47
+ rescue => ex
48
+ Logger.crash("thread crashed", ex)
47
49
  end
48
- rescue Exception => ex
49
- Logger.crash("#{self} internal failure", ex)
50
+
51
+ put thread
50
52
  end
51
53
  end
54
+
52
55
  thread[:queue] = queue
53
56
  thread
54
57
  end
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.8.0'
2
+ VERSION = '0.9.0'
3
3
  def self.version; VERSION; end
4
4
  end
@@ -49,6 +49,15 @@ shared_context "a Celluloid Actor" do |included_module|
49
49
  actor.class.should == actor_class
50
50
  end
51
51
 
52
+ it "compares with the actor's class in a case statement" do
53
+ case actor_class.new("Troy McClure")
54
+ when actor_class
55
+ true
56
+ else
57
+ false
58
+ end.should be_true
59
+ end
60
+
52
61
  it "handles synchronous calls" do
53
62
  actor = actor_class.new "Troy McClure"
54
63
  actor.greet.should == "Hi, I'm Troy McClure"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: celluloid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-24 00:00:00.000000000 Z
12
+ date: 2012-02-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70145052114120 !ruby/object:Gem::Requirement
16
+ requirement: &70248586219120 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,21 +21,21 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70145052114120
24
+ version_requirements: *70248586219120
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70145052113520 !ruby/object:Gem::Requirement
27
+ requirement: &70248586218540 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
- - - ~>
30
+ - - ! '>='
31
31
  - !ruby/object:Gem::Version
32
- version: 2.7.0
32
+ version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70145052113520
35
+ version_requirements: *70248586218540
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: benchmark_suite
38
- requirement: &70145052113020 !ruby/object:Gem::Requirement
38
+ requirement: &70248586217360 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70145052113020
46
+ version_requirements: *70248586217360
47
47
  description: Celluloid is a concurrent object framework inspired by the Actor Model
48
48
  email:
49
49
  - tony.arcieri@gmail.com
@@ -64,6 +64,7 @@ files:
64
64
  - lib/celluloid/links.rb
65
65
  - lib/celluloid/logger.rb
66
66
  - lib/celluloid/mailbox.rb
67
+ - lib/celluloid/pool.rb
67
68
  - lib/celluloid/receivers.rb
68
69
  - lib/celluloid/registry.rb
69
70
  - lib/celluloid/responses.rb