celluloid 0.13.0 → 0.14.1

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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -2
  3. data/lib/celluloid/actor.rb +33 -30
  4. data/lib/celluloid/autostart.rb +0 -13
  5. data/lib/celluloid/calls.rb +71 -23
  6. data/lib/celluloid/condition.rb +32 -28
  7. data/lib/celluloid/core_ext.rb +3 -14
  8. data/lib/celluloid/cpu_counter.rb +1 -1
  9. data/lib/celluloid/evented_mailbox.rb +82 -0
  10. data/lib/celluloid/fsm.rb +2 -0
  11. data/lib/celluloid/future.rb +22 -31
  12. data/lib/celluloid/internal_pool.rb +13 -0
  13. data/lib/celluloid/legacy.rb +14 -13
  14. data/lib/celluloid/logging/incident_logger.rb +2 -2
  15. data/lib/celluloid/mailbox.rb +16 -0
  16. data/lib/celluloid/method.rb +7 -7
  17. data/lib/celluloid/notifications.rb +1 -1
  18. data/lib/celluloid/proxies/abstract_proxy.rb +1 -1
  19. data/lib/celluloid/proxies/actor_proxy.rb +23 -27
  20. data/lib/celluloid/proxies/async_proxy.rb +18 -6
  21. data/lib/celluloid/proxies/block_proxy.rb +29 -0
  22. data/lib/celluloid/proxies/future_proxy.rb +17 -3
  23. data/lib/celluloid/proxies/sync_proxy.rb +31 -0
  24. data/lib/celluloid/receivers.rb +1 -1
  25. data/lib/celluloid/responses.rb +13 -1
  26. data/lib/celluloid/stack_dump.rb +8 -5
  27. data/lib/celluloid/supervision_group.rb +10 -10
  28. data/lib/celluloid/system_events.rb +1 -0
  29. data/lib/celluloid/tasks/task_fiber.rb +9 -47
  30. data/lib/celluloid/tasks/task_thread.rb +9 -44
  31. data/lib/celluloid/tasks.rb +63 -2
  32. data/lib/celluloid/thread.rb +38 -0
  33. data/lib/celluloid/thread_handle.rb +5 -3
  34. data/lib/celluloid/version.rb +1 -1
  35. data/lib/celluloid.rb +84 -32
  36. data/spec/support/actor_examples.rb +92 -53
  37. data/spec/support/example_actor_class.rb +7 -1
  38. data/spec/support/mailbox_examples.rb +29 -3
  39. data/spec/support/task_examples.rb +11 -9
  40. metadata +21 -29
@@ -9,9 +9,9 @@ module Celluloid
9
9
 
10
10
  unbanged_meth = meth.to_s
11
11
  unbanged_meth.slice!(-1, 1)
12
- Actor.async @mailbox, unbanged_meth, *args, &block
12
+ async unbanged_meth, *args, &block
13
13
  else
14
- Actor.call @mailbox, meth, *args, &block
14
+ super
15
15
  end
16
16
  end
17
17
  end
@@ -27,20 +27,21 @@ module Celluloid
27
27
  unbanged_meth = meth.to_s.sub(/!$/, '')
28
28
  args.unshift unbanged_meth
29
29
 
30
- call = AsyncCall.new(:__send__, args, block)
31
- begin
32
- Thread.current[:celluloid_actor].mailbox << call
33
- rescue MailboxError
34
- # Silently swallow asynchronous calls to dead actors. There's no way
35
- # to reliably generate DeadActorErrors for async calls, so users of
36
- # async calls should find other ways to deal with actors dying
37
- # during an async call (i.e. linking/supervisors)
38
- end
39
-
30
+ async :__send__, *args, &block
40
31
  return
41
32
  end
42
33
 
43
34
  super
44
35
  end
45
36
  end
46
- end
37
+ end
38
+
39
+ class Thread
40
+ def self.mailbox
41
+ Celluloid.mailbox
42
+ end
43
+
44
+ def self.receive(timeout = nil, &block)
45
+ Celluloid.receive(timeout, &block)
46
+ end
47
+ end
@@ -49,9 +49,9 @@ module Celluloid
49
49
  @sizelimit = options[:sizelimit] || 100
50
50
 
51
51
  @buffer_mutex = Mutex.new
52
- @buffers = Hash.new do |progname_hash, progname|
52
+ @buffers = Hash.new do |progname_hash, _progname|
53
53
  @buffer_mutex.synchronize do
54
- progname_hash[progname] = Hash.new do |severity_hash, severity|
54
+ progname_hash[_progname] = Hash.new do |severity_hash, severity|
55
55
  severity_hash[severity] = RingBuffer.new(@sizelimit)
56
56
  end
57
57
  end
@@ -11,6 +11,7 @@ module Celluloid
11
11
 
12
12
  # A unique address at which this mailbox can be found
13
13
  attr_reader :address
14
+ attr_accessor :max_size
14
15
 
15
16
  def initialize
16
17
  @address = Celluloid.uuid
@@ -18,12 +19,17 @@ module Celluloid
18
19
  @mutex = Mutex.new
19
20
  @dead = false
20
21
  @condition = ConditionVariable.new
22
+ @max_size = nil
21
23
  end
22
24
 
23
25
  # Add a message to the Mailbox
24
26
  def <<(message)
25
27
  @mutex.lock
26
28
  begin
29
+ if mailbox_full
30
+ Logger.debug "Discarded message: #{message}"
31
+ return
32
+ end
27
33
  if message.is_a?(SystemEvent)
28
34
  # Silently swallow system events sent to dead actors
29
35
  return if @dead
@@ -125,5 +131,15 @@ module Celluloid
125
131
  def inspect
126
132
  "#<#{self.class}:#{object_id.to_s(16)} @messages=[#{map { |m| m.inspect }.join(', ')}]>"
127
133
  end
134
+
135
+ # Number of messages in the Mailbox
136
+ def size
137
+ @mutex.synchronize { @messages.size }
138
+ end
139
+
140
+ private
141
+ def mailbox_full
142
+ @max_size && @messages.size >= @max_size
143
+ end
128
144
  end
129
145
  end
@@ -2,23 +2,23 @@ module Celluloid
2
2
  # Method handles that route through an actor proxy
3
3
  class Method
4
4
 
5
- def initialize(actor, name)
6
- raise NameError, "undefined method `#{name}'" unless actor.respond_to? name
5
+ def initialize(proxy, name)
6
+ raise NameError, "undefined method `#{name}'" unless proxy.respond_to? name
7
7
 
8
- @actor, @name = actor, name
9
- @klass = @actor.class
8
+ @proxy, @name = proxy, name
9
+ @klass = @proxy.class
10
10
  end
11
11
 
12
12
  def arity
13
- @actor._send_(:method, @name).arity
13
+ @proxy.method_missing(:method, @name).arity
14
14
  end
15
15
 
16
16
  def call(*args, &block)
17
- @actor._send_(@name, *args, &block)
17
+ @proxy.__send__(@name, *args, &block)
18
18
  end
19
19
 
20
20
  def inspect
21
- "#<Celluloid::Method #{@klass}#{@name}>"
21
+ "#<Celluloid::Method #{@klass}##{@name}>"
22
22
  end
23
23
  end
24
24
  end
@@ -67,7 +67,7 @@ module Celluloid
67
67
  end
68
68
 
69
69
  def publish(pattern, *args)
70
- Actor.async(actor.mailbox, method, pattern, *args)
70
+ actor.async method, pattern, *args
71
71
  end
72
72
 
73
73
  def subscribed_to?(pattern)
@@ -5,7 +5,7 @@ module Celluloid
5
5
  needed = [:object_id, :__id__, :hash] - instance_methods
6
6
  if needed.any?
7
7
  include ::Kernel.dup.module_eval {
8
- undef_method *(instance_methods - needed)
8
+ undef_method(*(instance_methods - needed))
9
9
  self
10
10
  }
11
11
 
@@ -1,55 +1,54 @@
1
1
  module Celluloid
2
2
  # A proxy object returned from Celluloid::Actor.new/new_link which converts
3
3
  # the normal Ruby method protocol into an inter-actor message protocol
4
- class ActorProxy
5
- attr_reader :mailbox, :thread
4
+ class ActorProxy < SyncProxy
5
+ attr_reader :thread
6
6
 
7
7
  def initialize(actor)
8
- @mailbox, @thread, @klass = actor.mailbox, actor.thread, actor.subject.class.to_s
8
+ @thread = actor.thread
9
9
 
10
- @async_proxy = AsyncProxy.new(actor)
11
- @future_proxy = FutureProxy.new(actor)
10
+ super(actor.mailbox, actor.subject.class.to_s)
11
+ @sync_proxy = SyncProxy.new(@mailbox, @klass)
12
+ @async_proxy = AsyncProxy.new(@mailbox, @klass)
13
+ @future_proxy = FutureProxy.new(@mailbox, @klass)
12
14
  end
13
-
14
- # allow querying the real class
15
- alias :__class__ :class
16
-
15
+
17
16
  def class
18
- Actor.call @mailbox, :__send__, :class
17
+ method_missing :__send__, :class
19
18
  end
20
19
 
21
20
  def send(meth, *args, &block)
22
- Actor.call @mailbox, :send, meth, *args, &block
21
+ method_missing :send, meth, *args, &block
23
22
  end
24
23
 
25
24
  def _send_(meth, *args, &block)
26
- Actor.call @mailbox, :__send__, meth, *args, &block
25
+ method_missing :__send__, meth, *args, &block
27
26
  end
28
27
 
29
28
  def inspect
30
- Actor.call(@mailbox, :inspect)
29
+ method_missing :inspect
31
30
  rescue DeadActorError
32
- "#<Celluloid::Actor(#{@klass}) dead>"
31
+ "#<Celluloid::ActorProxy(#{@klass}) dead>"
33
32
  end
34
33
 
35
34
  def name
36
- Actor.call @mailbox, :name
35
+ method_missing :name
37
36
  end
38
37
 
39
38
  def is_a?(klass)
40
- Actor.call @mailbox, :is_a?, klass
39
+ method_missing :is_a?, klass
41
40
  end
42
41
 
43
42
  def kind_of?(klass)
44
- Actor.call @mailbox, :kind_of?, klass
43
+ method_missing :kind_of?, klass
45
44
  end
46
45
 
47
46
  def respond_to?(meth, include_private = false)
48
- Actor.call @mailbox, :respond_to?, meth, include_private
47
+ method_missing :respond_to?, meth, include_private
49
48
  end
50
49
 
51
50
  def methods(include_ancestors = true)
52
- Actor.call @mailbox, :methods, include_ancestors
51
+ method_missing :methods, include_ancestors
53
52
  end
54
53
 
55
54
  def method(name)
@@ -61,13 +60,15 @@ module Celluloid
61
60
  end
62
61
 
63
62
  def to_s
64
- Actor.call @mailbox, :to_s
63
+ method_missing :to_s
65
64
  end
66
65
 
66
+ alias_method :sync, :method_missing
67
+
67
68
  # Obtain an async proxy or explicitly invoke a named async method
68
69
  def async(method_name = nil, *args, &block)
69
70
  if method_name
70
- Actor.async @mailbox, method_name, *args, &block
71
+ @async_proxy.method_missing method_name, *args, &block
71
72
  else
72
73
  @async_proxy
73
74
  end
@@ -76,7 +77,7 @@ module Celluloid
76
77
  # Obtain a future proxy or explicitly invoke a named future method
77
78
  def future(method_name = nil, *args, &block)
78
79
  if method_name
79
- Actor.future @mailbox, method_name, *args, &block
80
+ @future_proxy.method_missing method_name, *args, &block
80
81
  else
81
82
  @future_proxy
82
83
  end
@@ -94,10 +95,5 @@ module Celluloid
94
95
  ::Kernel.raise DeadActorError, "actor already terminated" unless alive?
95
96
  @mailbox << TerminationRequest.new
96
97
  end
97
-
98
- # method_missing black magic to call bang predicate methods asynchronously
99
- def method_missing(meth, *args, &block)
100
- Actor.call @mailbox, meth, *args, &block
101
- end
102
98
  end
103
99
  end
@@ -3,20 +3,32 @@ module Celluloid
3
3
  class AsyncProxy < AbstractProxy
4
4
  attr_reader :mailbox
5
5
 
6
- def initialize(actor)
7
- @mailbox, @klass = actor.mailbox, actor.subject.class.to_s
6
+ def initialize(mailbox, klass)
7
+ @mailbox, @klass = mailbox, klass
8
8
  end
9
9
 
10
10
  def inspect
11
11
  "#<Celluloid::AsyncProxy(#{@klass})>"
12
12
  end
13
13
 
14
- # method_missing black magic to call bang predicate methods asynchronously
15
14
  def method_missing(meth, *args, &block)
16
15
  if @mailbox == ::Thread.current[:celluloid_mailbox]
17
- Actor.async @mailbox, :__send__, meth, *args, &block
18
- else
19
- Actor.async @mailbox, meth, *args, &block
16
+ args.unshift meth
17
+ meth = :__send__
18
+ end
19
+
20
+ if block_given?
21
+ # FIXME: nicer exception
22
+ raise "Cannot use blocks with async yet"
23
+ end
24
+
25
+ begin
26
+ @mailbox << AsyncCall.new(meth, args, block)
27
+ rescue MailboxError
28
+ # Silently swallow asynchronous calls to dead actors. There's no way
29
+ # to reliably generate DeadActorErrors for async calls, so users of
30
+ # async calls should find other ways to deal with actors dying
31
+ # during an async call (i.e. linking/supervisors)
20
32
  end
21
33
  end
22
34
  end
@@ -0,0 +1,29 @@
1
+ module Celluloid
2
+ class BlockProxy
3
+ def initialize(call, mailbox, block)
4
+ @call = call
5
+ @mailbox = mailbox
6
+ @block = block
7
+ @execution = :sender
8
+ end
9
+ attr_writer :execution
10
+ attr_reader :call, :block
11
+
12
+ def to_proc
13
+ if @execution == :sender
14
+ lambda do |*values|
15
+ if task = Thread.current[:celluloid_task]
16
+ @mailbox << BlockCall.new(self, Actor.current.mailbox, values)
17
+ # TODO: if respond fails, the Task will never be resumed
18
+ task.suspend(:invokeblock)
19
+ else
20
+ # FIXME: better exception
21
+ raise "No task to suspend"
22
+ end
23
+ end
24
+ else
25
+ @block
26
+ end
27
+ end
28
+ end
29
+ end
@@ -3,8 +3,8 @@ module Celluloid
3
3
  class FutureProxy < AbstractProxy
4
4
  attr_reader :mailbox
5
5
 
6
- def initialize(actor)
7
- @mailbox, @klass = actor.mailbox, actor.subject.class.to_s
6
+ def initialize(mailbox, klass)
7
+ @mailbox, @klass = mailbox, klass
8
8
  end
9
9
 
10
10
  def inspect
@@ -13,7 +13,21 @@ module Celluloid
13
13
 
14
14
  # method_missing black magic to call bang predicate methods asynchronously
15
15
  def method_missing(meth, *args, &block)
16
- Actor.future @mailbox, meth, *args, &block
16
+ if block_given?
17
+ # FIXME: nicer exception
18
+ raise "Cannot use blocks with futures yet"
19
+ end
20
+
21
+ future = Future.new
22
+ call = SyncCall.new(future, meth, args, block)
23
+
24
+ begin
25
+ @mailbox << call
26
+ rescue MailboxError
27
+ raise DeadActorError, "attempted to call a dead actor"
28
+ end
29
+
30
+ future
17
31
  end
18
32
  end
19
33
  end
@@ -0,0 +1,31 @@
1
+ module Celluloid
2
+ # A proxy which sends synchronous calls to an actor
3
+ class SyncProxy < AbstractProxy
4
+ attr_reader :mailbox
5
+
6
+ def initialize(mailbox, klass)
7
+ @mailbox, @klass = mailbox, klass
8
+ end
9
+
10
+ def inspect
11
+ "#<Celluloid::SyncProxy(#{@klass})>"
12
+ end
13
+
14
+ def method_missing(meth, *args, &block)
15
+ if @mailbox == ::Thread.current[:celluloid_mailbox]
16
+ args.unshift meth
17
+ meth = :__send__
18
+ end
19
+
20
+ call = SyncCall.new(::Celluloid.mailbox, meth, args, block)
21
+
22
+ begin
23
+ @mailbox << call
24
+ rescue MailboxError
25
+ raise DeadActorError, "attempted to call a dead actor"
26
+ end
27
+
28
+ call.value
29
+ end
30
+ end
31
+ end
@@ -12,7 +12,7 @@ module Celluloid
12
12
  # Receive an asynchronous message
13
13
  def receive(timeout = nil, &block)
14
14
  if Celluloid.exclusive?
15
- Thread.mailbox.receive(timeout, &block)
15
+ Celluloid.mailbox.receive(timeout, &block)
16
16
  else
17
17
  receiver = Receiver.new block
18
18
 
@@ -15,7 +15,7 @@ module Celluloid
15
15
  # Call completed successfully
16
16
  class SuccessResponse < Response; end
17
17
 
18
- # Call was aborted due to caller error
18
+ # Call was aborted due to sender error
19
19
  class ErrorResponse < Response
20
20
  def value
21
21
  ex = super
@@ -29,4 +29,16 @@ module Celluloid
29
29
  raise ex
30
30
  end
31
31
  end
32
+
33
+ class BlockResponse
34
+ def initialize(call, result)
35
+ @call = call
36
+ @result = result
37
+ end
38
+
39
+ def dispatch
40
+ @call.task.resume(@result)
41
+ end
42
+ end
43
+
32
44
  end
@@ -1,7 +1,7 @@
1
1
  module Celluloid
2
2
  class StackDump
3
3
 
4
- class TaskState < Struct.new(:task_class, :status)
4
+ class TaskState < Struct.new(:task_class, :status, :backtrace)
5
5
  end
6
6
 
7
7
  class ActorState
@@ -24,8 +24,9 @@ module Celluloid
24
24
 
25
25
  def snapshot
26
26
  Thread.list.each do |thread|
27
- if actor = thread[:celluloid_actor]
28
- @actors << snapshot_actor(actor)
27
+ if thread.celluloid?
28
+ next unless thread.role == :actor
29
+ @actors << snapshot_actor(thread.actor) if thread.actor
29
30
  else
30
31
  @threads << snapshot_thread(thread)
31
32
  end
@@ -42,7 +43,7 @@ module Celluloid
42
43
  state.status = :idle
43
44
  else
44
45
  state.status = :running
45
- state.tasks = tasks.collect { |t| TaskState.new(t.class, t.status) }
46
+ state.tasks = tasks.collect { |t| TaskState.new(t.class, t.status, t.backtrace) }
46
47
  end
47
48
 
48
49
  state.backtrace = actor.thread.backtrace if actor.thread
@@ -62,16 +63,18 @@ module Celluloid
62
63
 
63
64
  if actor.status == :idle
64
65
  string << "State: Idle (waiting for messages)\n"
66
+ display_backtrace actor.backtrace, string
65
67
  else
66
68
  string << "State: Running (executing tasks)\n"
69
+ display_backtrace actor.backtrace, string
67
70
  string << "Tasks:\n"
68
71
 
69
72
  actor.tasks.each_with_index do |task, i|
70
73
  string << " #{i+1}) #{task.task_class}: #{task.status}\n"
74
+ display_backtrace task.backtrace, string
71
75
  end
72
76
  end
73
77
 
74
- display_backtrace actor.backtrace, string
75
78
  output.print string
76
79
  end
77
80
 
@@ -12,9 +12,9 @@ module Celluloid
12
12
 
13
13
  # Start this application (and watch it with a supervisor)
14
14
  def run!
15
- group = new do |group|
15
+ group = new do |_group|
16
16
  blocks.each do |block|
17
- block.call(group)
17
+ block.call(_group)
18
18
  end
19
19
  end
20
20
  group
@@ -63,6 +63,8 @@ module Celluloid
63
63
  yield current_actor if block_given?
64
64
  end
65
65
 
66
+ execute_block_on_receiver :initialize, :supervise, :supervise_as
67
+
66
68
  def supervise(klass, *args, &block)
67
69
  add(klass, :args => args, :block => block)
68
70
  end
@@ -95,8 +97,8 @@ module Celluloid
95
97
 
96
98
  # Restart a crashed actor
97
99
  def restart_actor(actor, reason)
98
- member = @members.find do |member|
99
- member.actor == actor
100
+ member = @members.find do |_member|
101
+ _member.actor == actor
100
102
  end
101
103
  raise "a group member went missing. This shouldn't be!" unless member
102
104
 
@@ -127,13 +129,11 @@ module Celluloid
127
129
  # when it is a pool, then we don't splat the args
128
130
  # and we need to extract the pool size if set
129
131
  if @pool
130
- options = {:args => @args}.tap do |hash|
131
- hash[:size] = @pool_size if @pool_size
132
- end
133
- @actor = @klass.send(@method, options, &@block)
134
- else
135
- @actor = @klass.send(@method, *@args, &@block)
132
+ options = {:args => @args}
133
+ options[:size] = @pool_size if @pool_size
134
+ @args = [options]
136
135
  end
136
+ @actor = @klass.send(@method, *@args, &@block)
137
137
  @registry[@name] = @actor if @name
138
138
  end
139
139
 
@@ -57,6 +57,7 @@ module Celluloid
57
57
  def initialize(task, value)
58
58
  @task, @value = task, value
59
59
  end
60
+ attr_reader :task, :value
60
61
 
61
62
  def call
62
63
  @task.resume(@value)
@@ -3,52 +3,22 @@ module Celluloid
3
3
 
4
4
  # Tasks with a Fiber backend
5
5
  class TaskFiber < Task
6
- attr_reader :type, :status
7
-
8
- # Run the given block within a task
9
- def initialize(type)
10
- super
11
-
12
- actor = Thread.current[:celluloid_actor]
13
- mailbox = Thread.current[:celluloid_mailbox]
14
- chain_id = Thread.current[:celluloid_chain_id]
15
-
16
- raise NotActorError, "can't create tasks outside of actors" unless actor
17
6
 
7
+ def create
18
8
  @fiber = Fiber.new do
19
- @status = :running
20
- Thread.current[:celluloid_actor] = actor
21
- Thread.current[:celluloid_mailbox] = mailbox
22
- Thread.current[:celluloid_task] = self
23
- Thread.current[:celluloid_chain_id] = chain_id
24
-
25
- actor.tasks << self
26
-
27
- begin
28
- yield
29
- rescue Task::TerminatedError
30
- # Task was explicitly terminated
31
- ensure
32
- @status = :dead
33
- actor.tasks.delete self
34
- end
9
+ # FIXME: cannot use the writer as specs run inside normal Threads
10
+ Thread.current[:celluloid_role] = :actor
11
+ yield
35
12
  end
36
13
  end
37
14
 
38
- # Suspend the current task, changing the status to the given argument
39
- def suspend(status)
40
- @status = status
41
- result = Fiber.yield
42
- raise result if result.is_a?(Task::TerminatedError)
43
- @status = :running
44
-
45
- result
15
+ def signal
16
+ Fiber.yield
46
17
  end
47
18
 
48
19
  # Resume a suspended task, giving it a value to return if needed
49
- def resume(value = nil)
20
+ def deliver(value)
50
21
  @fiber.resume value
51
- nil
52
22
  rescue SystemStackError => ex
53
23
  raise FiberStackError, "#{ex} (please see https://github.com/celluloid/celluloid/wiki/Fiber-stack-errors)"
54
24
  rescue FiberError => ex
@@ -57,17 +27,9 @@ module Celluloid
57
27
 
58
28
  # Terminate this task
59
29
  def terminate
60
- resume Task::TerminatedError.new("task was terminated") if @fiber.alive?
30
+ super
61
31
  rescue FiberError
62
32
  # If we're getting this the task should already be dead
63
33
  end
64
-
65
- # Is the current task still running?
66
- def running?; @fiber.alive?; end
67
-
68
- # Nicer string inspect for tasks
69
- def inspect
70
- "<Celluloid::TaskFiber:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
71
- end
72
34
  end
73
- end
35
+ end