celluloid 0.10.0 → 0.11.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
@@ -72,18 +72,28 @@ for more detailed documentation and usage notes.
72
72
  Like Celluloid? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
73
73
  or visit us on IRC at #celluloid on freenode
74
74
 
75
+ ### Is it any good?
76
+
77
+ [Yes.](http://news.ycombinator.com/item?id=3067434)
78
+
79
+ ### Is It "Production Ready™"?
80
+
81
+ Yes, many users are now running Celluloid in production by using
82
+ [Sidekiq](https://github.com/mperham/sidekiq)
83
+
75
84
  Supported Platforms
76
85
  -------------------
77
86
 
78
- Celluloid works on Ruby 1.9.3, JRuby 1.6 (in 1.9 mode), and Rubinius 2.0. JRuby
79
- or Rubinius are the preferred platforms as they support true hardware-level
80
- parallelism for Ruby threads, whereas MRI/YARV is constrained by a global
81
- interpreter lock (GIL) and can only execute one thread at a time.
87
+ Celluloid works on Ruby 1.9.3, JRuby 1.6, and Rubinius 2.0. JRuby or Rubinius
88
+ are the preferred platforms as they support true thread-level parallelism when
89
+ executing Ruby code, whereas MRI/YARV is constrained by a global interpreter
90
+ lock (GIL) and can only execute one thread at a time.
82
91
 
83
- To use JRuby in 1.9 mode, you'll need to pass the "--1.9" command line option
84
- to the JRuby executable, or set the "JRUBY_OPTS=--1.9" environment variable.
92
+ Celluloid requires Ruby 1.9 mode on all interpreters. This works out of the
93
+ box on MRI/YARV, and requires the following flags elsewhere:
85
94
 
86
- Celluloid works on Rubinius in either 1.8 or 1.9 mode.
95
+ * JRuby: --1.9 command line option, or JRUBY_OPTS=--1.9 environment variable
96
+ * rbx: -X19 command line option
87
97
 
88
98
  Additional Reading
89
99
  ------------------
@@ -73,8 +73,11 @@ module Celluloid
73
73
  actors = Actor.all
74
74
  Logger.info "Terminating #{actors.size} actors..." if actors.size > 0
75
75
 
76
+ # Attempt to shut down the supervision tree, if available
77
+ Supervisor.root.terminate if Supervisor.root
78
+
76
79
  # Actors cannot self-terminate, you must do it for them
77
- terminators = actors.each do |actor|
80
+ terminators = Actor.all.each do |actor|
78
81
  begin
79
82
  actor.future(:terminate)
80
83
  rescue DeadActorError, MailboxError
@@ -130,6 +133,25 @@ module Celluloid
130
133
  Supervisor.supervise_as(name, self, *args, &block)
131
134
  end
132
135
 
136
+ # Create a new pool of workers. Accepts the following options:
137
+ #
138
+ # * size: how many workers to create. Default is worker per CPU core
139
+ # * args: array of arguments to pass when creating a worker
140
+ #
141
+ def pool(options = {})
142
+ PoolManager.new(self, options)
143
+ end
144
+
145
+ # Same as pool, but links to the pool manager
146
+ def pool_link(options = {})
147
+ PoolManager.new_link(self, options)
148
+ end
149
+
150
+ # Run an actor in the foreground
151
+ def run(*args, &block)
152
+ new(*args, &block).join
153
+ end
154
+
133
155
  # Trap errors from actors we're linked to when they exit
134
156
  def trap_exit(callback)
135
157
  @exit_handler = callback.to_sym
@@ -174,6 +196,11 @@ module Celluloid
174
196
 
175
197
  # Raise an exception in caller context, but stay running
176
198
  def abort(cause)
199
+ cause = case cause
200
+ when String then RuntimeError.new(cause)
201
+ when Exception then cause
202
+ else raise TypeError, "Exception object/String expected, but #{cause.class} received"
203
+ end
177
204
  raise AbortError.new(cause)
178
205
  end
179
206
 
@@ -207,6 +234,11 @@ module Celluloid
207
234
  Actor.current
208
235
  end
209
236
 
237
+ # Obtain the name of the current actor
238
+ def name
239
+ Actor.name
240
+ end
241
+
210
242
  # Obtain the running tasks for this actor
211
243
  def tasks
212
244
  Thread.current[:actor].tasks.to_a
@@ -280,17 +312,28 @@ module Celluloid
280
312
  # messages in its mailbox in the meantime
281
313
  def defer(&block)
282
314
  # This implementation relies on the present implementation of
283
- # Celluloid::Future, which uses an Actor to run the block
315
+ # Celluloid::Future, which uses a thread from InternalPool to run the block
284
316
  Future.new(&block).value
285
317
  end
286
318
 
319
+ # Handle async calls within an actor itself
320
+ def async(meth, *args, &block)
321
+ Actor.async Thread.current[:actor].mailbox, meth, *args, &block
322
+ end
323
+
324
+ # Handle calls to future within an actor itself
325
+ def future(meth, *args, &block)
326
+ Actor.future Thread.current[:actor].mailbox, meth, *args, &block
327
+ end
328
+
287
329
  # Process async calls via method_missing
288
330
  def method_missing(meth, *args, &block)
289
331
  # bang methods are async calls
290
332
  if meth.to_s.match(/!$/)
291
333
  unbanged_meth = meth.to_s.sub(/!$/, '')
292
- call = AsyncCall.new(@mailbox, unbanged_meth, args, block)
334
+ args.unshift unbanged_meth
293
335
 
336
+ call = AsyncCall.new(nil, :__send__, args, block)
294
337
  begin
295
338
  Thread.current[:actor].mailbox << call
296
339
  rescue MailboxError
@@ -315,20 +358,21 @@ require 'celluloid/cpu_counter'
315
358
  require 'celluloid/events'
316
359
  require 'celluloid/fiber'
317
360
  require 'celluloid/fsm'
361
+ require 'celluloid/internal_pool'
318
362
  require 'celluloid/links'
319
363
  require 'celluloid/logger'
320
364
  require 'celluloid/mailbox'
321
- require 'celluloid/pool'
322
365
  require 'celluloid/receivers'
323
366
  require 'celluloid/registry'
324
367
  require 'celluloid/responses'
325
368
  require 'celluloid/signals'
326
369
  require 'celluloid/task'
370
+ require 'celluloid/thread_handle'
327
371
  require 'celluloid/timers'
328
- require 'celluloid/thread_pool'
329
372
  require 'celluloid/uuid'
330
373
 
331
374
  require 'celluloid/actor'
332
375
  require 'celluloid/future'
333
- require 'celluloid/group'
376
+ require 'celluloid/pool_manager'
377
+ require 'celluloid/supervision_group'
334
378
  require 'celluloid/supervisor'
@@ -20,7 +20,7 @@ module Celluloid
20
20
  # messages.
21
21
  class Actor
22
22
  extend Registry
23
- attr_reader :proxy, :tasks, :links, :mailbox
23
+ attr_reader :subject, :proxy, :tasks, :links, :mailbox, :thread, :name
24
24
 
25
25
  class << self
26
26
  # Obtain the current actor
@@ -29,7 +29,14 @@ module Celluloid
29
29
  raise NotActorError, "not in actor scope" unless actor
30
30
  actor.proxy
31
31
  end
32
-
32
+
33
+ # Obtain the name of the current actor
34
+ def name
35
+ actor = Thread.current[:actor]
36
+ raise NotActorError, "not in actor scope" unless actor
37
+ actor.name
38
+ end
39
+
33
40
  # Invoke a method on the given actor via its mailbox
34
41
  def call(mailbox, meth, *args, &block)
35
42
  call = SyncCall.new(Thread.mailbox, meth, args, block)
@@ -87,7 +94,6 @@ module Celluloid
87
94
  def initialize(subject)
88
95
  @subject = subject
89
96
  @mailbox = subject.class.mailbox_factory
90
- @proxy = ActorProxy.new(@mailbox, subject.class.to_s)
91
97
  @tasks = Set.new
92
98
  @links = Links.new
93
99
  @signals = Signals.new
@@ -95,12 +101,15 @@ module Celluloid
95
101
  @timers = Timers.new
96
102
  @running = true
97
103
  @exclusive = false
104
+ @name = nil
98
105
 
99
- @thread = ThreadPool.get do
106
+ @thread = ThreadHandle.new do
100
107
  Thread.current[:actor] = self
101
108
  Thread.current[:mailbox] = @mailbox
102
109
  run
103
110
  end
111
+
112
+ @proxy = ActorProxy.new(self)
104
113
  end
105
114
 
106
115
  # Is this actor running in exclusive mode?
@@ -145,6 +154,9 @@ module Celluloid
145
154
  rescue ExitEvent => exit_event
146
155
  Task.new(:exit_handler) { handle_exit_event exit_event }.resume
147
156
  retry
157
+ rescue NamingRequest => ex
158
+ @name = ex.name
159
+ retry
148
160
  rescue TerminationRequest
149
161
  break
150
162
  end
@@ -162,8 +174,9 @@ module Celluloid
162
174
  end
163
175
 
164
176
  shutdown
165
- rescue => ex
177
+ rescue Exception => ex
166
178
  handle_crash(ex)
179
+ raise unless ex.is_a? StandardError
167
180
  end
168
181
 
169
182
  # How long to wait until the next timer fires
@@ -249,7 +262,8 @@ module Celluloid
249
262
 
250
263
  # Run the user-defined finalizer, if one is set
251
264
  def run_finalizer
252
- @subject.finalize if @subject.respond_to? :finalize
265
+ return unless @subject.respond_to? :finalize
266
+ Task.new(:finalizer) { @subject.finalize }.resume
253
267
  rescue => ex
254
268
  Logger.crash("#{@subject.class}#finalize crashed!", ex)
255
269
  end
@@ -5,8 +5,8 @@ module Celluloid
5
5
  class ActorProxy
6
6
  attr_reader :mailbox
7
7
 
8
- def initialize(mailbox, klass = "Object")
9
- @mailbox, @klass = mailbox, klass
8
+ def initialize(actor)
9
+ @mailbox, @thread, @klass = actor.mailbox, actor.thread, actor.subject.class.to_s
10
10
  end
11
11
 
12
12
  def _send_(meth, *args, &block)
@@ -17,6 +17,10 @@ module Celluloid
17
17
  Actor.call @mailbox, :__send__, :class
18
18
  end
19
19
 
20
+ def name
21
+ Actor.call @mailbox, :name
22
+ end
23
+
20
24
  def is_a?(klass)
21
25
  Actor.call @mailbox, :is_a?, klass
22
26
  end
@@ -47,6 +51,12 @@ module Celluloid
47
51
  "#<Celluloid::Actor(#{@klass}) dead>"
48
52
  end
49
53
 
54
+ # Make an asynchronous call to an actor, for those who don't like the
55
+ # predicate syntax. TIMTOWTDI!
56
+ def async(method_name, *args, &block)
57
+ Actor.async @mailbox, method_name, *args, &block
58
+ end
59
+
50
60
  # Create a Celluloid::Future which calls a given method
51
61
  def future(method_name, *args, &block)
52
62
  Actor.future @mailbox, method_name, *args, &block
@@ -64,14 +74,27 @@ module Celluloid
64
74
  @mailbox.system_event TerminationRequest.new
65
75
  end
66
76
 
77
+ # Forcibly kill a given actor
78
+ def kill
79
+ @thread.kill
80
+ begin
81
+ @mailbox.shutdown
82
+ rescue DeadActorError
83
+ end
84
+ end
85
+
86
+ # Wait for an actor to terminate
87
+ def join
88
+ @thread.join
89
+ end
90
+
67
91
  # method_missing black magic to call bang predicate methods asynchronously
68
92
  def method_missing(meth, *args, &block)
69
- meth = meth.to_s
70
-
71
93
  # bang methods are async calls
72
94
  if meth.match(/!$/)
73
- meth.sub!(/!$/, '')
74
- Actor.async @mailbox, meth, *args, &block
95
+ unbanged_meth = meth.to_s
96
+ unbanged_meth.slice!(-1, 1)
97
+ Actor.async @mailbox, unbanged_meth, *args, &block
75
98
  else
76
99
  Actor.call @mailbox, meth, *args, &block
77
100
  end
@@ -91,14 +91,14 @@ module Celluloid
91
91
  begin
92
92
  check_signature(obj)
93
93
  rescue Exception => ex
94
- Logger.crash("#{obj.class}: async call failed!", ex)
94
+ Logger.crash("#{obj.class}: async call `#{@method}' failed!", ex)
95
95
  return
96
96
  end
97
97
 
98
98
  obj.send(@method, *@arguments, &@block)
99
99
  rescue AbortError => ex
100
100
  # Swallow aborted async calls, as they indicate the caller made a mistake
101
- Logger.crash("#{obj.class}: async call aborted!", ex)
101
+ Logger.crash("#{obj.class}: async call `#{@method}' aborted!", ex)
102
102
  end
103
103
  end
104
104
  end
@@ -10,11 +10,11 @@ class Thread
10
10
  end
11
11
 
12
12
  # Receive a message either as an actor or through the local mailbox
13
- def self.receive(&block)
13
+ def self.receive(timeout = nil, &block)
14
14
  if Celluloid.actor?
15
- Celluloid.receive(&block)
15
+ Celluloid.receive(timeout, &block)
16
16
  else
17
- mailbox.receive(&block)
17
+ mailbox.receive(timeout, &block)
18
18
  end
19
19
  end
20
20
  end
@@ -1,17 +1,26 @@
1
1
  module Celluloid
2
2
  # Exceptional system events which need to be processed out of band
3
3
  class SystemEvent < Exception; end
4
-
4
+
5
5
  # An actor has exited for the given reason
6
6
  class ExitEvent < SystemEvent
7
7
  attr_reader :actor, :reason
8
-
8
+
9
9
  def initialize(actor, reason = nil)
10
10
  @actor, @reason = actor, reason
11
11
  super reason.to_s
12
12
  end
13
13
  end
14
-
14
+
15
+ # Name an actor at the time it's registered
16
+ class NamingRequest < SystemEvent
17
+ attr_reader :name
18
+
19
+ def initialize(name)
20
+ @name = name
21
+ end
22
+ end
23
+
15
24
  # Request for an actor to terminate
16
25
  class TerminationRequest < SystemEvent; end
17
- end
26
+ end
@@ -24,9 +24,8 @@ rescue LoadError => ex
24
24
  # Just in case subsequent JRuby releases have broken fibers :/
25
25
  raise ex
26
26
  end
27
- elsif defined? Rubinius
28
- # If we're on Rubinius, we can still work in 1.8 mode
29
- Fiber = Rubinius::Fiber
27
+ elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
28
+ raise LoadError, "Celluloid requires Rubinius 1.9 mode. Please pass the -X19 flag."
30
29
  else
31
30
  raise ex
32
31
  end
@@ -13,7 +13,7 @@ module Celluloid
13
13
 
14
14
  if block
15
15
  @call = SyncCall.new(self, :call, args)
16
- ThreadPool.get do
16
+ InternalPool.get do
17
17
  begin
18
18
  @call.dispatch(block)
19
19
  rescue
@@ -36,7 +36,7 @@ module Celluloid
36
36
  end
37
37
 
38
38
  # Obtain the value for this Future
39
- def value
39
+ def value(timeout = nil)
40
40
  ready = result = nil
41
41
 
42
42
  begin
@@ -61,12 +61,16 @@ module Celluloid
61
61
  end
62
62
 
63
63
  unless ready
64
- result = Thread.receive do |msg|
64
+ result = Thread.receive(timeout) do |msg|
65
65
  msg.is_a?(Future::Result) && msg.future == self
66
66
  end
67
67
  end
68
68
 
69
- result.value
69
+ if result
70
+ result.value
71
+ else
72
+ raise "Timed out"
73
+ end
70
74
  end
71
75
  alias_method :call, :value
72
76
 
@@ -2,7 +2,7 @@ require 'thread'
2
2
 
3
3
  module Celluloid
4
4
  # Maintain a thread pool FOR SPEED!!
5
- module ThreadPool
5
+ module InternalPool
6
6
  @pool = []
7
7
  @mutex = Mutex.new
8
8
 
@@ -15,11 +15,13 @@ module Celluloid
15
15
  # Get a thread from the pool, running the given block
16
16
  def get(&block)
17
17
  @mutex.synchronize do
18
- if @pool.empty?
19
- thread = create
20
- else
21
- thread = @pool.shift
22
- end
18
+ begin
19
+ if @pool.empty?
20
+ thread = create
21
+ else
22
+ thread = @pool.shift
23
+ end
24
+ end until thread.status # handle crashed threads
23
25
 
24
26
  thread[:queue] << block
25
27
  thread
@@ -1,50 +1,32 @@
1
1
  require 'thread'
2
2
 
3
3
  module Celluloid
4
- # Thread safe storage of inter-actor links
4
+ # Linked actors send each other system events
5
5
  class Links
6
6
  include Enumerable
7
7
 
8
8
  def initialize
9
9
  @links = {}
10
- @lock = Mutex.new
11
10
  end
12
11
 
13
12
  # Add an actor to the current links
14
13
  def <<(actor)
15
- @lock.synchronize do
16
- @links[actor.mailbox.address] = actor
17
- end
18
- actor
14
+ @links[actor.mailbox.address] = actor
19
15
  end
20
16
 
21
17
  # Do links include the given actor?
22
18
  def include?(actor)
23
- @lock.synchronize do
24
- @links.has_key? actor.mailbox.address
25
- end
19
+ @links.has_key? actor.mailbox.address
26
20
  end
27
21
 
28
22
  # Remove an actor from the links
29
23
  def delete(actor)
30
- @lock.synchronize do
31
- @links.delete actor.mailbox.address
32
- end
33
- actor
24
+ @links.delete actor.mailbox.address
34
25
  end
35
26
 
36
27
  # Iterate through all links
37
28
  def each
38
- @lock.synchronize do
39
- @links.each { |_, actor| yield(actor) }
40
- end
41
- end
42
-
43
- # Map across links
44
- def map
45
- result = []
46
- each { |actor| result << yield(actor) }
47
- result
29
+ @links.each { |_, actor| yield(actor) }
48
30
  end
49
31
 
50
32
  # Send an event message to all actors
@@ -0,0 +1,62 @@
1
+ module Celluloid
2
+ # Manages a fixed-size pool of workers
3
+ # Delegates work (i.e. methods) and supervises workers
4
+ class PoolManager
5
+ include Celluloid
6
+ trap_exit :crash_handler
7
+
8
+ def initialize(worker_class, options = {})
9
+ @size = options[:size]
10
+ raise ArgumentError, "minimum pool size is 2" if @size && @size < 2
11
+
12
+ @size ||= [Celluloid.cores, 2].max
13
+ @args = options[:args] ? Array(options[:args]) : []
14
+
15
+ @worker_class = worker_class
16
+ @idle = @size.times.map { worker_class.new_link(*@args) }
17
+ end
18
+
19
+ # Execute the given method within a worker
20
+ def execute(method, *args, &block)
21
+ worker = provision_worker
22
+
23
+ begin
24
+ worker._send_ method, *args, &block
25
+ rescue => ex
26
+ abort ex
27
+ ensure
28
+ @idle << worker if worker.alive?
29
+ end
30
+ end
31
+
32
+ # Provision a new worker
33
+ def provision_worker
34
+ while @idle.empty?
35
+ # Using exclusive mode blocks incoming messages, so they don't pile
36
+ # up as waiting Celluloid::Tasks
37
+ response = exclusive { receive { |msg| msg.is_a? Response } }
38
+ Thread.current[:actor].handle_message(response)
39
+ end
40
+ @idle.shift
41
+ end
42
+
43
+ # Spawn a new worker for every crashed one
44
+ def crash_handler(actor, reason)
45
+ @idle.delete actor
46
+ return unless reason # don't restart workers that exit cleanly
47
+ @idle << @worker_class.new_link(*@args)
48
+ end
49
+
50
+ def respond_to?(method)
51
+ super || (@worker_class ? @worker_class.instance_methods.include?(method.to_sym) : false)
52
+ end
53
+
54
+ def method_missing(method, *args, &block)
55
+ if respond_to?(method)
56
+ execute method, *args, &block
57
+ else
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
@@ -16,6 +16,8 @@ module Celluloid
16
16
  @@registry_lock.synchronize do
17
17
  @@registry[name.to_sym] = actor
18
18
  end
19
+
20
+ actor.mailbox.system_event NamingRequest.new(name.to_sym)
19
21
  end
20
22
 
21
23
  # Retrieve an actor by name
@@ -0,0 +1,99 @@
1
+ module Celluloid
2
+ # Supervise collections of actors as a group
3
+ class SupervisionGroup
4
+ include Celluloid
5
+ trap_exit :restart_actor
6
+
7
+ class << self
8
+ # Actors or sub-applications to be supervised
9
+ def members
10
+ @members ||= []
11
+ end
12
+
13
+ # Start this application (and watch it with a supervisor)
14
+ alias_method :run!, :supervise
15
+
16
+ # Run the application in the foreground with a simple watchdog
17
+ def run
18
+ loop do
19
+ supervisor = run!
20
+
21
+ # Take five, toplevel supervisor
22
+ sleep 5 while supervisor.alive?
23
+
24
+ Logger.error "!!! Celluloid::Group #{self} crashed. Restarting..."
25
+ end
26
+ end
27
+
28
+ # Register an actor class or a sub-group to be launched and supervised
29
+ # Available options are:
30
+ #
31
+ # * as: register this application in the Celluloid::Actor[] directory
32
+ # * args: start the actor with the given arguments
33
+ def supervise(klass, options = {})
34
+ members << Member.new(klass, options)
35
+ end
36
+
37
+ # Register a pool of actors to be launched on group startup
38
+ def pool(klass)
39
+ members << Member.new(klass, method: 'pool_link')
40
+ end
41
+ end
42
+
43
+ # Start the group
44
+ def initialize
45
+ @actors = {}
46
+
47
+ # This is some serious lolcode, but like... start the supervisors for
48
+ # this group
49
+ self.class.members.each do |member|
50
+ actor = member.start
51
+ @actors[actor] = member
52
+ end
53
+ end
54
+
55
+ # Terminate the group
56
+ def finalize
57
+ @actors.each do |actor, _|
58
+ begin
59
+ actor.terminate
60
+ rescue DeadActorError
61
+ end
62
+ end
63
+ end
64
+
65
+ # Restart a crashed actor
66
+ def restart_actor(actor, reason)
67
+ member = @actors.delete actor
68
+ raise "a group member went missing. This shouldn't be!" unless member
69
+
70
+ # Ignore supervisors that shut down cleanly
71
+ return unless reason
72
+
73
+ actor = member.start
74
+ @actors[actor] = member
75
+ end
76
+
77
+ # A member of the group
78
+ class Member
79
+ def initialize(klass, options = {})
80
+ @klass = klass
81
+
82
+ # Stringify keys :/
83
+ options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
84
+
85
+ @name = options['as']
86
+ @args = options['args'] ? Array(options['args']) : []
87
+ @method = options['method'] || 'new_link'
88
+ end
89
+
90
+ def start
91
+ actor = @klass.send(@method, *@args)
92
+ Actor[@name] = actor if @name
93
+ end
94
+ end
95
+ end
96
+
97
+ # Legacy support for the old name
98
+ Group = SupervisionGroup
99
+ end
@@ -8,12 +8,17 @@ module Celluloid
8
8
  # Retrieve the actor this supervisor is supervising
9
9
  attr_reader :actor
10
10
 
11
- def self.supervise(klass, *args, &block)
12
- new(nil, klass, *args, &block)
13
- end
11
+ class << self
12
+ # Define the root of the supervision tree
13
+ attr_accessor :root
14
+
15
+ def supervise(klass, *args, &block)
16
+ new(nil, klass, *args, &block)
17
+ end
14
18
 
15
- def self.supervise_as(name, klass, *args, &block)
16
- new(name, klass, *args, &block)
19
+ def supervise_as(name, klass, *args, &block)
20
+ new(name, klass, *args, &block)
21
+ end
17
22
  end
18
23
 
19
24
  def initialize(name, klass, *args, &block)
@@ -23,6 +28,10 @@ module Celluloid
23
28
  start_actor
24
29
  end
25
30
 
31
+ def finalize
32
+ @actor.terminate if @actor and @actor.alive?
33
+ end
34
+
26
35
  def start_actor(start_attempts = 3, sleep_interval = 30)
27
36
  failures = 0
28
37
 
@@ -46,6 +46,7 @@ module Celluloid
46
46
  rescue TerminatedError
47
47
  # Task was explicitly terminated
48
48
  ensure
49
+ @status = :dead
49
50
  actor.tasks.delete self
50
51
  end
51
52
  end
@@ -57,10 +58,6 @@ module Celluloid
57
58
  nil
58
59
  rescue FiberError
59
60
  raise DeadTaskError, "cannot resume a dead task"
60
- rescue RuntimeError => ex
61
- # These occur spuriously on 1.9.3 if we shut down an actor with running tasks
62
- return if ex.message == ""
63
- raise
64
61
  end
65
62
 
66
63
  # Terminate this task
@@ -0,0 +1,38 @@
1
+ module Celluloid
2
+ # An abstraction around threads from the InternalPool which ensures we don't
3
+ # accidentally do things to threads which have been returned to the pool,
4
+ # such as, say, killing them
5
+ class ThreadHandle
6
+ def initialize
7
+ @mutex = Mutex.new
8
+ @join = ConditionVariable.new
9
+
10
+ @thread = InternalPool.get do
11
+ begin
12
+ yield
13
+ ensure
14
+ @mutex.synchronize do
15
+ @thread = nil
16
+ @join.broadcast
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ # Is the thread running?
23
+ def alive?
24
+ @mutex.synchronize { @thread.alive? if @thread }
25
+ end
26
+
27
+ # Forcibly kill the thread
28
+ def kill
29
+ !!@mutex.synchronize { @thread.kill if @thread }
30
+ end
31
+
32
+ # Join to a running thread, blocking until it terminates
33
+ def join
34
+ @mutex.synchronize { @join.wait(@mutex) if @thread }
35
+ nil
36
+ end
37
+ end
38
+ end
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.10.0'
2
+ VERSION = '0.11.0'
3
3
  def self.version; VERSION; end
4
4
  end
@@ -39,6 +39,10 @@ shared_context "a Celluloid Actor" do |included_module|
39
39
  abort example_crash
40
40
  end
41
41
 
42
+ def crash_with_abort_raw(reason)
43
+ abort reason
44
+ end
45
+
42
46
  def internal_hello
43
47
  external_hello
44
48
  end
@@ -59,6 +63,16 @@ shared_context "a Celluloid Actor" do |included_module|
59
63
  super || delegates?(method_name)
60
64
  end
61
65
 
66
+ def call_private
67
+ zomg_private!
68
+ end
69
+
70
+ def zomg_private
71
+ @private_called = true
72
+ end
73
+ private :zomg_private
74
+ attr_reader :private_called
75
+
62
76
  private
63
77
 
64
78
  def delegates?(method_name)
@@ -132,25 +146,44 @@ shared_context "a Celluloid Actor" do |included_module|
132
146
  end.to raise_exception(ExampleCrash)
133
147
  end
134
148
 
135
- it "raises exceptions in the caller when abort is called, but keeps running" do
136
- actor = actor_class.new "Al Pacino"
149
+ describe "when #abort is called" do
150
+ it "raises exceptions in the caller but keeps running" do
151
+ actor = actor_class.new "Al Pacino"
137
152
 
138
- e = nil
139
- line_no = nil
153
+ e = nil
154
+ line_no = nil
140
155
 
141
- expect do
142
- begin
143
- line_no = __LINE__; actor.crash_with_abort "You die motherfucker!", :bar
144
- rescue => ex
145
- e = ex
146
- raise
147
- end
148
- end.to raise_exception(ExampleCrash, "You die motherfucker!")
156
+ expect do
157
+ begin
158
+ line_no = __LINE__; actor.crash_with_abort "You die motherfucker!", :bar
159
+ rescue => ex
160
+ e = ex
161
+ raise
162
+ end
163
+ end.to raise_exception(ExampleCrash, "You die motherfucker!")
164
+
165
+ e.backtrace.any? { |line| line.include?([__FILE__, line_no].join(':')) }.should be_true # Check the backtrace is appropriate to the caller
166
+ e.foo.should be == :bar # Check the exception maintains instance variables
167
+
168
+ actor.should be_alive
169
+ end
149
170
 
150
- e.backtrace.any? { |line| line.include?([__FILE__, line_no].join(':')) }.should be_true # Check the backtrace is appropriate to the caller
151
- e.foo.should be == :bar # Check the exception maintains instance variables
171
+ it "converts strings to runtime errors" do
172
+ actor = actor_class.new "Al Pacino"
173
+ expect do
174
+ actor.crash_with_abort_raw "foo"
175
+ end.to raise_exception(RuntimeError, "foo")
176
+ end
152
177
 
153
- actor.should be_alive
178
+ it "crashes the caller if we pass neither String nor Exception" do
179
+ actor = actor_class.new "Al Pacino"
180
+ expect do
181
+ actor.crash_with_abort_raw 10
182
+ end.to raise_exception(TypeError, "Exception object/String expected, but Fixnum received")
183
+
184
+ actor.greet rescue nil # Ensure our actor has died.
185
+ actor.should_not be_alive
186
+ end
154
187
  end
155
188
 
156
189
  it "raises DeadActorError if methods are synchronously called on a dead actor" do
@@ -168,12 +201,24 @@ shared_context "a Celluloid Actor" do |included_module|
168
201
  actor.greet.should == "Hi, I'm Charlie Sheen"
169
202
  end
170
203
 
204
+ it "handles asynchronous calls via #async" do
205
+ actor = actor_class.new "Troy McClure"
206
+ actor.async :change_name, "Charlie Sheen"
207
+ actor.greet.should == "Hi, I'm Charlie Sheen"
208
+ end
209
+
171
210
  it "handles asynchronous calls to itself" do
172
211
  actor = actor_class.new "Troy McClure"
173
212
  actor.change_name_async "Charlie Sheen"
174
213
  actor.greet.should == "Hi, I'm Charlie Sheen"
175
214
  end
176
215
 
216
+ it "allows an actor to call private methods asynchronously with a bang" do
217
+ actor = actor_class.new "Troy McClure"
218
+ actor.call_private
219
+ actor.private_called.should be_true
220
+ end
221
+
177
222
  it "knows if it's inside actor scope" do
178
223
  Celluloid.should_not be_actor
179
224
  actor = actor_class.new "Troy McClure"
@@ -224,7 +269,15 @@ shared_context "a Celluloid Actor" do |included_module|
224
269
  actor = actor_class.new "Arnold Schwarzenegger"
225
270
  actor.should be_alive
226
271
  actor.terminate
227
- sleep 0.5 # hax
272
+ actor.join
273
+ actor.should_not be_alive
274
+ end
275
+
276
+ it "kills" do
277
+ actor = actor_class.new "Woody Harrelson"
278
+ actor.should be_alive
279
+ actor.kill
280
+ actor.join
228
281
  actor.should_not be_alive
229
282
  end
230
283
 
@@ -388,7 +441,7 @@ shared_context "a Celluloid Actor" do |included_module|
388
441
  received_obj.should == message
389
442
  end
390
443
 
391
- it "times out after the given interval" do
444
+ it "times out after the given interval", :pending => ENV['CI'] do
392
445
  interval = 0.1
393
446
  started_at = Time.now
394
447
 
@@ -520,10 +573,8 @@ shared_context "a Celluloid Actor" do |included_module|
520
573
  it "knows which tasks are waiting on calls to other actors" do
521
574
  actor = @klass.new
522
575
 
523
- # an alias for Celluloid::Actor#waiting_tasks
524
576
  tasks = actor.tasks
525
577
  tasks.size.should == 1
526
- tasks.first.status.should == :running
527
578
 
528
579
  future = actor.future(:blocking_call)
529
580
  sleep 0.1 # hax! waiting for ^^^ call to actually start
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.10.0
4
+ version: 0.11.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-04-02 00:00:00.000000000 Z
12
+ date: 2012-06-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70363787045800 !ruby/object:Gem::Requirement
16
+ requirement: &70191377653480 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70363787045800
24
+ version_requirements: *70191377653480
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70363787045200 !ruby/object:Gem::Requirement
27
+ requirement: &70191377602840 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70363787045200
35
+ version_requirements: *70191377602840
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: guard-rspec
38
- requirement: &70363787044600 !ruby/object:Gem::Requirement
38
+ requirement: &70191350440520 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70363787044600
46
+ version_requirements: *70191350440520
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: benchmark_suite
49
- requirement: &70363787044060 !ruby/object:Gem::Requirement
49
+ requirement: &70191350401720 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70363787044060
57
+ version_requirements: *70191350401720
58
58
  description: Celluloid enables people to build concurrent programs out of concurrent
59
59
  objects just as easily as they build sequential programs out of sequential objects
60
60
  email:
@@ -73,19 +73,20 @@ files:
73
73
  - lib/celluloid/fiber.rb
74
74
  - lib/celluloid/fsm.rb
75
75
  - lib/celluloid/future.rb
76
- - lib/celluloid/group.rb
76
+ - lib/celluloid/internal_pool.rb
77
77
  - lib/celluloid/links.rb
78
78
  - lib/celluloid/logger.rb
79
79
  - lib/celluloid/mailbox.rb
80
- - lib/celluloid/pool.rb
80
+ - lib/celluloid/pool_manager.rb
81
81
  - lib/celluloid/receivers.rb
82
82
  - lib/celluloid/registry.rb
83
83
  - lib/celluloid/responses.rb
84
84
  - lib/celluloid/rspec.rb
85
85
  - lib/celluloid/signals.rb
86
+ - lib/celluloid/supervision_group.rb
86
87
  - lib/celluloid/supervisor.rb
87
88
  - lib/celluloid/task.rb
88
- - lib/celluloid/thread_pool.rb
89
+ - lib/celluloid/thread_handle.rb
89
90
  - lib/celluloid/timers.rb
90
91
  - lib/celluloid/uuid.rb
91
92
  - lib/celluloid/version.rb
@@ -113,8 +114,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
114
  version: 1.3.6
114
115
  requirements: []
115
116
  rubyforge_project:
116
- rubygems_version: 1.8.17
117
+ rubygems_version: 1.8.10
117
118
  signing_key:
118
119
  specification_version: 3
119
120
  summary: Actor-based concurrent object framework for Ruby
120
121
  test_files: []
122
+ has_rdoc:
@@ -1,85 +0,0 @@
1
- module Celluloid
2
- # Supervise collections of actors as a group
3
- class Group
4
- include Celluloid
5
- trap_exit :restart_supervisor
6
-
7
- class << self
8
- # Actors or sub-applications to be supervised
9
- def supervisables
10
- @supervisables ||= []
11
- end
12
-
13
- # Start this application (and watch it with a supervisor)
14
- alias_method :run!, :supervise
15
-
16
- # Run the application in the foreground with a simple watchdog
17
- def run
18
- loop do
19
- supervisor = run!
20
-
21
- # Take five, toplevel supervisor
22
- sleep 5 while supervisor.alive?
23
-
24
- Logger.error "!!! Celluloid::Application #{self} crashed. Restarting..."
25
- end
26
- end
27
-
28
- # Register an actor class or a sub-application class to be launched and
29
- # supervised while this application is running. Available options are:
30
- #
31
- # * as: register this application in the Celluloid::Actor[] directory
32
- # * args: start the actor with the given arguments
33
- def supervise(klass, options = {})
34
- supervisables << Supervisable.new(klass, options)
35
- end
36
- end
37
-
38
- # Start the application
39
- def initialize
40
- @supervisors = {}
41
-
42
- # This is some serious lolcode, but like... start the supervisors for
43
- # this application
44
- self.class.supervisables.each do |supervisable|
45
- supervisor = supervisable.supervise
46
- @supervisors[supervisor] = supervisable
47
- end
48
- end
49
-
50
- # Restart a crashed supervisor
51
- def restart_supervisor(supervisor, reason)
52
- supervisable = @supervisors.delete supervisor
53
- raise "a supervisable went missing. This shouldn't be!" unless supervisable
54
-
55
- # Ignore supervisors that shut down cleanly
56
- return unless reason
57
-
58
- supervisor = supervisable.supervise
59
- @supervisors[supervisor] = supervisable
60
- end
61
-
62
- # A subcomponent of an application to be supervised
63
- class Supervisable
64
- attr_reader :klass, :as, :args
65
-
66
- def initialize(klass, options = {})
67
- @klass = klass
68
-
69
- # Stringify keys :/
70
- options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
71
-
72
- @as = options['as']
73
- @args = options['args'] || []
74
- raise ":args should be an Array" unless @args.kind_of? Array
75
- end
76
-
77
- def supervise
78
- Supervisor.new_link(@as, @klass, *@args)
79
- end
80
- end
81
- end
82
-
83
- # Legacy support for the old name. Going away soon!
84
- Application = Group
85
- end
@@ -1,105 +0,0 @@
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
- attr_reader :max_actors
7
-
8
- # Takes a class of actor to pool and a hash of options:
9
- #
10
- # * initial_size: how many actors to eagerly create
11
- # * max_size: maximum number of actors (default one actor per CPU core)
12
- # * args: an array of arguments to pass to the actor's initialize
13
- def initialize(klass, options = {})
14
- raise ArgumentError, "A Pool has a minimum size of 2" if options[:max_size] && options[:max_size] < 2
15
- opts = {
16
- :initial_size => 1,
17
- :max_size => [Celluloid.cores, 2].max,
18
- :args => []
19
- }.merge(options)
20
-
21
- @klass, @args = klass, opts[:args]
22
- @max_actors = opts[:max_size]
23
- @idle_actors, @running_actors = 0, 0
24
- @actors = []
25
-
26
- opts[:initial_size].times do
27
- @actors << spawn
28
- @idle_actors += 1
29
- end
30
- end
31
-
32
- # Get an actor from the pool. Actors taken from the pool must be put back
33
- # with Pool#put. Alternatively, you can use get with a block form:
34
- #
35
- # pool.get { |actor| ... }
36
- #
37
- # This will automatically return actors to the pool when the block completes
38
- def get
39
- if @max_actors and @running_actors == @max_actors
40
- wait :ready
41
- end
42
-
43
- actor = @actors.pop
44
- if actor
45
- @idle_actors -= 1
46
- else
47
- actor = spawn
48
- end
49
-
50
- if block_given?
51
- begin
52
- yield actor
53
- rescue => ex
54
- end
55
-
56
- put actor
57
- abort ex if ex
58
- nil
59
- else
60
- actor
61
- end
62
- end
63
-
64
- # Return an actor to the pool
65
- def put(actor)
66
- begin
67
- raise TypeError, "expecting a #{@klass} actor" unless actor.is_a? @klass
68
- rescue DeadActorError
69
- # The actor may have died before it was handed back to us
70
- # We'll let the crash_handler deal with it in due time
71
- return
72
- end
73
-
74
- @actors << actor
75
- @idle_actors += 1
76
- end
77
-
78
- # Number of active actors in this pool
79
- def size
80
- @running_actors
81
- end
82
-
83
- # Number of idle actors in the pool
84
- def idle_count
85
- @idle_actors
86
- end
87
- alias_method :idle_size, :idle_count
88
-
89
- # Handle crashed actors
90
- def crash_handler(actor, reason)
91
- @idle_actors -= 1 if @actors.delete actor
92
- @running_actors -= 1
93
-
94
- # If we were maxed out before...
95
- signal :ready if @max_actors and @running_actors + 1 == @max_actors
96
- end
97
-
98
- # Spawn an actor of the given class
99
- def spawn
100
- worker = @klass.new_link(*@args)
101
- @running_actors += 1
102
- worker
103
- end
104
- end
105
- end