celluloid 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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