celluloid 0.11.1 → 0.12.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.
@@ -1,20 +1,28 @@
1
1
  module Celluloid
2
- # A proxy object returned from Celluloid::Actor.spawn/spawn_link which
3
- # dispatches calls and casts to normal Ruby objects which are running inside
4
- # of their own threads.
5
- class ActorProxy
6
- attr_reader :mailbox
2
+ # A proxy object returned from Celluloid::Actor.new/new_link which converts
3
+ # the normal Ruby method protocol into an inter-actor message protocol
4
+ class ActorProxy < AbstractProxy
5
+ attr_reader :mailbox, :thread
7
6
 
8
7
  def initialize(actor)
9
8
  @mailbox, @thread, @klass = actor.mailbox, actor.thread, actor.subject.class.to_s
9
+
10
+ @async_proxy = AsyncProxy.new(actor)
11
+ @future_proxy = FutureProxy.new(actor)
12
+ end
13
+
14
+ def class
15
+ Actor.call @mailbox, :__send__, :class
10
16
  end
11
17
 
12
18
  def _send_(meth, *args, &block)
13
19
  Actor.call @mailbox, :__send__, meth, *args, &block
14
20
  end
15
21
 
16
- def class
17
- Actor.call @mailbox, :__send__, :class
22
+ def inspect
23
+ Actor.call @mailbox, :inspect
24
+ rescue DeadActorError
25
+ "#<Celluloid::Actor(#{@klass}) dead>"
18
26
  end
19
27
 
20
28
  def name
@@ -29,14 +37,18 @@ module Celluloid
29
37
  Actor.call @mailbox, :kind_of?, klass
30
38
  end
31
39
 
32
- def respond_to?(meth)
33
- Actor.call @mailbox, :respond_to?, meth
40
+ def respond_to?(meth, include_private = false)
41
+ Actor.call @mailbox, :respond_to?, meth, include_private
34
42
  end
35
43
 
36
44
  def methods(include_ancestors = true)
37
45
  Actor.call @mailbox, :methods, include_ancestors
38
46
  end
39
47
 
48
+ def method(name)
49
+ Method.new(self, name)
50
+ end
51
+
40
52
  def alive?
41
53
  @mailbox.alive?
42
54
  end
@@ -45,48 +57,35 @@ module Celluloid
45
57
  Actor.call @mailbox, :to_s
46
58
  end
47
59
 
48
- def inspect
49
- Actor.call @mailbox, :inspect
50
- rescue DeadActorError
51
- "#<Celluloid::Actor(#{@klass}) dead>"
52
- end
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
60
+ # Obtain an async proxy or explicitly invoke a named async method
61
+ def async(method_name = nil, *args, &block)
62
+ if method_name
63
+ Actor.async @mailbox, method_name, *args, &block
64
+ else
65
+ @async_proxy
66
+ end
58
67
  end
59
68
 
60
- # Create a Celluloid::Future which calls a given method
61
- def future(method_name, *args, &block)
62
- Actor.future @mailbox, method_name, *args, &block
69
+ # Obtain a future proxy or explicitly invoke a named future method
70
+ def future(method_name = nil, *args, &block)
71
+ if method_name
72
+ Actor.future @mailbox, method_name, *args, &block
73
+ else
74
+ @future_proxy
75
+ end
63
76
  end
64
77
 
65
78
  # Terminate the associated actor
66
79
  def terminate
67
80
  terminate!
68
- join
81
+ Actor.join(self)
69
82
  nil
70
83
  end
71
84
 
72
85
  # Terminate the associated actor asynchronously
73
86
  def terminate!
74
- raise DeadActorError, "actor already terminated" unless alive?
75
- @mailbox.system_event TerminationRequest.new
76
- end
77
-
78
- # Forcibly kill a given actor
79
- def kill
80
- @thread.kill
81
- begin
82
- @mailbox.shutdown
83
- rescue DeadActorError
84
- end
85
- end
86
-
87
- # Wait for an actor to terminate
88
- def join
89
- @thread.join
87
+ ::Kernel.raise DeadActorError, "actor already terminated" unless alive?
88
+ @mailbox << TerminationRequest.new
90
89
  end
91
90
 
92
91
  # method_missing black magic to call bang predicate methods asynchronously
@@ -0,0 +1,19 @@
1
+ module Celluloid
2
+ # A proxy which sends asynchronous calls to an actor
3
+ class AsyncProxy < AbstractProxy
4
+ attr_reader :mailbox
5
+
6
+ def initialize(actor)
7
+ @mailbox, @klass = actor.mailbox, actor.subject.class.to_s
8
+ end
9
+
10
+ def inspect
11
+ "#<Celluloid::AsyncProxy(#{@klass})>"
12
+ end
13
+
14
+ # method_missing black magic to call bang predicate methods asynchronously
15
+ def method_missing(meth, *args, &block)
16
+ Actor.async @mailbox, meth, *args, &block
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Celluloid
2
+ # A proxy which creates future calls to an actor
3
+ class FutureProxy < AbstractProxy
4
+ attr_reader :mailbox
5
+
6
+ def initialize(actor)
7
+ @mailbox, @klass = actor.mailbox, actor.subject.class.to_s
8
+ end
9
+
10
+ def inspect
11
+ "#<Celluloid::FutureProxy(#{@klass})>"
12
+ end
13
+
14
+ # method_missing black magic to call bang predicate methods asynchronously
15
+ def method_missing(meth, *args, &block)
16
+ Actor.future @mailbox, meth, *args, &block
17
+ end
18
+ end
19
+ end
@@ -3,8 +3,8 @@ require 'thread'
3
3
  module Celluloid
4
4
  # The Registry allows us to refer to specific actors by human-meaningful names
5
5
  class Registry
6
- def self.root
7
- @root ||= new
6
+ class << self
7
+ attr_reader :root
8
8
  end
9
9
 
10
10
  def initialize
@@ -23,7 +23,7 @@ module Celluloid
23
23
  @registry[name.to_sym] = actor
24
24
  end
25
25
 
26
- actor.mailbox.system_event NamingRequest.new(name.to_sym)
26
+ actor.mailbox << NamingRequest.new(name.to_sym)
27
27
  end
28
28
 
29
29
  # Retrieve an actor by name
@@ -38,7 +38,7 @@ module Celluloid
38
38
 
39
39
  def delete(name)
40
40
  @registry_lock.synchronize do
41
- @registry[name.to_sym]
41
+ @registry.delete name.to_sym
42
42
  end
43
43
  end
44
44
 
@@ -48,7 +48,7 @@ module Celluloid
48
48
  end
49
49
 
50
50
  # removes and returns all registered actors as a hash of `name => actor`
51
- # can be used in testing to clear the registry
51
+ # can be used in testing to clear the registry
52
52
  def clear
53
53
  hash = nil
54
54
  @registry_lock.synchronize do
@@ -57,5 +57,8 @@ module Celluloid
57
57
  end
58
58
  hash
59
59
  end
60
+
61
+ # Create the default registry
62
+ @root = new
60
63
  end
61
64
  end
@@ -1,2 +1,8 @@
1
+ require File.expand_path('../../../spec/support/example_actor_class', __FILE__)
1
2
  require File.expand_path('../../../spec/support/actor_examples', __FILE__)
2
3
  require File.expand_path('../../../spec/support/mailbox_examples', __FILE__)
4
+
5
+ module Celluloid
6
+ # Timer accuracy enforced by the tests (50ms)
7
+ TIMER_QUANTUM = 0.05
8
+ end
@@ -28,7 +28,7 @@ module Celluloid
28
28
  # Take five, toplevel supervisor
29
29
  sleep 5 while supervisor.alive?
30
30
 
31
- Logger.error "!!! Celluloid::Group #{self} crashed. Restarting..."
31
+ Logger.error "!!! Celluloid::SupervisionGroup #{self} crashed. Restarting..."
32
32
  end
33
33
  end
34
34
 
@@ -44,9 +44,13 @@ module Celluloid
44
44
  end
45
45
 
46
46
  # Register a pool of actors to be launched on group startup
47
- def pool(klass)
47
+ # Available options are:
48
+ #
49
+ # * as: register this application in the Celluloid::Actor[] directory
50
+ # * args: start the actor pool with the given arguments
51
+ def pool(klass, options = {})
48
52
  blocks << lambda do |group|
49
- group.pool klass
53
+ group.pool klass, options
50
54
  end
51
55
  end
52
56
  end
@@ -67,8 +71,9 @@ module Celluloid
67
71
  add(klass, :args => args, :block => block, :as => name)
68
72
  end
69
73
 
70
- def pool(klass)
71
- add(klass, :method => 'pool_link')
74
+ def pool(klass, options = {})
75
+ options[:method] = 'pool_link'
76
+ add(klass, options)
72
77
  end
73
78
 
74
79
  def add(klass, options)
@@ -106,16 +111,27 @@ module Celluloid
106
111
  options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
107
112
 
108
113
  @name = options['as']
109
- @args = options['args'] ? Array(options['args']) : []
110
114
  @block = options['block']
115
+ @args = options['args'] ? Array(options['args']) : []
111
116
  @method = options['method'] || 'new_link'
117
+ @pool = @method == 'pool_link'
118
+ @pool_size = options['size'] if @pool
112
119
 
113
120
  start
114
121
  end
115
122
  attr_reader :name, :actor
116
123
 
117
124
  def start
118
- @actor = @klass.send(@method, *@args, &@block)
125
+ # when it is a pool, then we don't splat the args
126
+ # and we need to extract the pool size if set
127
+ if @pool
128
+ options = {:args => @args}.tap do |hash|
129
+ hash[:size] = @pool_size if @pool_size
130
+ end
131
+ @actor = @klass.send(@method, options, &@block)
132
+ else
133
+ @actor = @klass.send(@method, *@args, &@block)
134
+ end
119
135
  @registry[@name] = @actor if @name
120
136
  end
121
137
 
@@ -135,7 +151,4 @@ module Celluloid
135
151
  end
136
152
  end
137
153
  end
138
-
139
- # Legacy support for the old name
140
- Group = SupervisionGroup
141
154
  end
@@ -0,0 +1,54 @@
1
+ module Celluloid
2
+ # High-priority internal system events
3
+ class SystemEvent; end
4
+
5
+ # Request to link with another actor
6
+ class LinkingRequest < SystemEvent
7
+ attr_reader :actor, :type
8
+
9
+ def initialize(actor, type)
10
+ @actor, @type = actor, type.to_sym
11
+ raise ArgumentError, "type must be link or unlink" unless [:link, :unlink].include?(@type)
12
+ end
13
+
14
+ def process(links)
15
+ case type
16
+ when :link then links << actor
17
+ when :unlink then links.delete actor
18
+ end
19
+
20
+ actor.mailbox << LinkingResponse.new(Actor.current, type)
21
+ end
22
+ end
23
+
24
+ # Response to a link request
25
+ class LinkingResponse
26
+ attr_reader :actor, :type
27
+
28
+ def initialize(actor, type)
29
+ @actor, @type = actor, type.to_sym
30
+ raise ArgumentError, "type must be link or unlink" unless [:link, :unlink].include?(@type)
31
+ end
32
+ end
33
+
34
+ # An actor has exited for the given reason
35
+ class ExitEvent < SystemEvent
36
+ attr_reader :actor, :reason
37
+
38
+ def initialize(actor, reason = nil)
39
+ @actor, @reason = actor, reason
40
+ end
41
+ end
42
+
43
+ # Name an actor at the time it's registered
44
+ class NamingRequest < SystemEvent
45
+ attr_reader :name
46
+
47
+ def initialize(name)
48
+ @name = name
49
+ end
50
+ end
51
+
52
+ # Request for an actor to terminate
53
+ class TerminationRequest < SystemEvent; end
54
+ end
@@ -1,78 +1,22 @@
1
+ require 'celluloid/tasks/task_fiber'
2
+ require 'celluloid/tasks/task_thread'
3
+
1
4
  module Celluloid
2
5
  # Trying to resume a dead task
3
6
  class DeadTaskError < StandardError; end
4
7
 
5
8
  # Tasks are interruptable/resumable execution contexts used to run methods
6
- class Task
7
- class TerminatedError < StandardError; end # kill a running fiber
8
-
9
- attr_reader :type
10
- attr_accessor :status
9
+ module Task
10
+ class TerminatedError < StandardError; end # kill a running task
11
11
 
12
12
  # Obtain the current task
13
13
  def self.current
14
- Fiber.current.task or raise "no task for this Fiber"
14
+ Thread.current[:task] or raise "not within a task context"
15
15
  end
16
16
 
17
17
  # Suspend the running task, deferring to the scheduler
18
18
  def self.suspend(status)
19
- task = Task.current
20
- task.status = status
21
-
22
- result = Fiber.yield
23
- raise TerminatedError, "task was terminated" if result == TerminatedError
24
- task.status = :running
25
-
26
- result
27
- end
28
-
29
- # Run the given block within a task
30
- def initialize(type)
31
- @type = type
32
- @status = :new
33
-
34
- actor = Thread.current[:actor]
35
- mailbox = Thread.current[:mailbox]
36
-
37
- @fiber = Fiber.new do
38
- @status = :running
39
- Thread.current[:actor] = actor
40
- Thread.current[:mailbox] = mailbox
41
- Fiber.current.task = self
42
- actor.tasks << self
43
-
44
- begin
45
- yield
46
- rescue TerminatedError
47
- # Task was explicitly terminated
48
- ensure
49
- @status = :dead
50
- actor.tasks.delete self
51
- end
52
- end
53
- end
54
-
55
- # Resume a suspended task, giving it a value to return if needed
56
- def resume(value = nil)
57
- @fiber.resume value
58
- nil
59
- rescue FiberError
60
- raise DeadTaskError, "cannot resume a dead task"
61
- end
62
-
63
- # Terminate this task
64
- def terminate
65
- resume TerminatedError if @fiber.alive?
66
- rescue FiberError
67
- # If we're getting this the task should already be dead
68
- end
69
-
70
- # Is the current task still running?
71
- def running?; @fiber.alive?; end
72
-
73
- # Nicer string inspect for tasks
74
- def inspect
75
- "<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}, @running=#{@fiber.alive?}>"
19
+ Task.current.suspend(status)
76
20
  end
77
21
  end
78
22
  end
@@ -0,0 +1,65 @@
1
+ module Celluloid
2
+ # Tasks with a Fiber backend
3
+ class TaskFiber
4
+ attr_reader :type, :status
5
+
6
+ # Run the given block within a task
7
+ def initialize(type)
8
+ @type = type
9
+ @status = :new
10
+
11
+ actor, mailbox = Thread.current[:actor], Thread.current[:mailbox]
12
+ raise NotActorError, "can't create tasks outside of actors" unless actor
13
+
14
+ @fiber = Fiber.new do
15
+ @status = :running
16
+ Thread.current[:actor] = actor
17
+ Thread.current[:mailbox] = mailbox
18
+ Thread.current[:task] = self
19
+ actor.tasks << self
20
+
21
+ begin
22
+ yield
23
+ rescue Task::TerminatedError
24
+ # Task was explicitly terminated
25
+ ensure
26
+ @status = :dead
27
+ actor.tasks.delete self
28
+ end
29
+ end
30
+ end
31
+
32
+ # Suspend the current task, changing the status to the given argument
33
+ def suspend(status)
34
+ @status = status
35
+ result = Fiber.yield
36
+ raise result if result.is_a?(Task::TerminatedError)
37
+ @status = :running
38
+
39
+ result
40
+ end
41
+
42
+ # Resume a suspended task, giving it a value to return if needed
43
+ def resume(value = nil)
44
+ @fiber.resume value
45
+ nil
46
+ rescue FiberError
47
+ raise DeadTaskError, "cannot resume a dead task"
48
+ end
49
+
50
+ # Terminate this task
51
+ def terminate
52
+ resume Task::TerminatedError.new("task was terminated") if @fiber.alive?
53
+ rescue FiberError
54
+ # If we're getting this the task should already be dead
55
+ end
56
+
57
+ # Is the current task still running?
58
+ def running?; @fiber.alive?; end
59
+
60
+ # Nicer string inspect for tasks
61
+ def inspect
62
+ "<Celluloid::TaskFiber:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,78 @@
1
+ module Celluloid
2
+ # Tasks with a Thread backend
3
+ class TaskThread
4
+ attr_reader :type, :status
5
+
6
+ # Run the given block within a task
7
+ def initialize(type)
8
+ @type = type
9
+ @status = :new
10
+
11
+ @resume_queue = Queue.new
12
+ @yield_mutex = Mutex.new
13
+ @yield_cond = ConditionVariable.new
14
+
15
+ actor, mailbox = Thread.current[:actor], Thread.current[:mailbox]
16
+ raise NotActorError, "can't create tasks outside of actors" unless actor
17
+
18
+ @thread = InternalPool.get do
19
+ begin
20
+ unless @resume_queue.pop.is_a?(Task::TerminatedError)
21
+ @status = :running
22
+ Thread.current[:actor] = actor
23
+ Thread.current[:mailbox] = mailbox
24
+ Thread.current[:task] = self
25
+ actor.tasks << self
26
+
27
+ yield
28
+ end
29
+ rescue Task::TerminatedError
30
+ # Task was explicitly terminated
31
+ ensure
32
+ @status = :dead
33
+ actor.tasks.delete self
34
+ @yield_cond.signal
35
+ end
36
+ end
37
+ end
38
+
39
+ # Suspend the current task, changing the status to the given argument
40
+ def suspend(status)
41
+ @status = status
42
+ @yield_cond.signal
43
+ value = @resume_queue.pop
44
+
45
+ raise value if value.is_a?(Task::TerminatedError)
46
+ @status = :running
47
+
48
+ value
49
+ end
50
+
51
+ # Resume a suspended task, giving it a value to return if needed
52
+ def resume(value = nil)
53
+ raise DeadTaskError, "cannot resume a dead task" unless @thread.alive?
54
+
55
+ @yield_mutex.synchronize do
56
+ @resume_queue.push(value)
57
+ @yield_cond.wait(@yield_mutex)
58
+ end
59
+
60
+ nil
61
+ rescue ThreadError
62
+ raise DeadTaskError, "cannot resume a dead task"
63
+ end
64
+
65
+ # Terminate this task
66
+ def terminate
67
+ resume Task::TerminatedError.new("task was terminated") if @thread.alive?
68
+ end
69
+
70
+ # Is the current task still running?
71
+ def running?; @status != :dead; end
72
+
73
+ # Nicer string inspect for tasks
74
+ def inspect
75
+ "<Celluloid::TaskThread:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
76
+ end
77
+ end
78
+ end
@@ -27,12 +27,13 @@ module Celluloid
27
27
  # Forcibly kill the thread
28
28
  def kill
29
29
  !!@mutex.synchronize { @thread.kill if @thread }
30
+ self
30
31
  end
31
32
 
32
33
  # Join to a running thread, blocking until it terminates
33
34
  def join
34
35
  @mutex.synchronize { @join.wait(@mutex) if @thread }
35
- nil
36
+ self
36
37
  end
37
38
  end
38
39
  end
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.11.1'
2
+ VERSION = '0.12.0'
3
3
  def self.version; VERSION; end
4
4
  end