celluloid 0.13.0 → 0.14.0.pre

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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -2
  3. data/lib/celluloid.rb +84 -32
  4. data/lib/celluloid/actor.rb +35 -30
  5. data/lib/celluloid/autostart.rb +0 -13
  6. data/lib/celluloid/calls.rb +71 -23
  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 +4 -4
  12. data/lib/celluloid/internal_pool.rb +11 -8
  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 +9 -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 +6 -4
  28. data/lib/celluloid/tasks.rb +63 -2
  29. data/lib/celluloid/tasks/task_fiber.rb +6 -46
  30. data/lib/celluloid/tasks/task_thread.rb +8 -44
  31. data/lib/celluloid/thread.rb +82 -0
  32. data/lib/celluloid/thread_handle.rb +3 -2
  33. data/lib/celluloid/version.rb +1 -1
  34. data/spec/support/actor_examples.rb +116 -53
  35. data/spec/support/example_actor_class.rb +7 -1
  36. data/spec/support/mailbox_examples.rb +29 -3
  37. data/spec/support/task_examples.rb +11 -9
  38. metadata +21 -29
@@ -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,13 @@ 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
+ future = Future.new
21
+ future.execute(@mailbox, meth, args, block)
22
+ future
17
23
  end
18
24
  end
19
25
  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 if thread.task
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
 
@@ -19,10 +19,71 @@ module Celluloid
19
19
  Task.current.suspend(status)
20
20
  end
21
21
 
22
+ attr_reader :type, :status
23
+
22
24
  # Create a new task
23
25
  def initialize(type)
24
26
  @type = type
25
27
  @status = :new
28
+
29
+ actor = Thread.current[:celluloid_actor]
30
+ chain_id = Thread.current[:celluloid_chain_id]
31
+
32
+ raise NotActorError, "can't create tasks outside of actors" unless actor
33
+
34
+ create do
35
+ begin
36
+ @status = :running
37
+ actor.setup_thread
38
+ Thread.current[:celluloid_task] = self
39
+ Thread.current[:celluloid_chain_id] = chain_id
40
+
41
+ actor.tasks << self
42
+ yield
43
+ rescue Task::TerminatedError
44
+ # Task was explicitly terminated
45
+ ensure
46
+ @status = :dead
47
+ actor.tasks.delete self
48
+ end
49
+ end
50
+ end
51
+
52
+ def create(&block)
53
+ raise "Implement #{self.class}#create"
54
+ end
55
+
56
+ # Suspend the current task, changing the status to the given argument
57
+ def suspend(status)
58
+ @status = status
59
+ value = signal
60
+
61
+ raise value if value.is_a?(Task::TerminatedError)
62
+ @status = :running
63
+
64
+ value
65
+ end
66
+
67
+ # Resume a suspended task, giving it a value to return if needed
68
+ def resume(value = nil)
69
+ deliver(value)
70
+ nil
71
+ end
72
+
73
+ # Terminate this task
74
+ def terminate
75
+ resume Task::TerminatedError.new("task was terminated") if running?
76
+ end
77
+
78
+ def backtrace
79
+ end
80
+
81
+ # Is the current task still running?
82
+ def running?; @status != :dead; end
83
+
84
+ # Nicer string inspect for tasks
85
+ def inspect
86
+ "#<#{self.class}:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
26
87
  end
27
88
  end
28
89
 
@@ -42,7 +103,7 @@ module Celluloid
42
103
  end
43
104
 
44
105
  def each(&blk)
45
- @tasks.each &blk
106
+ @tasks.each(&blk)
46
107
  end
47
108
 
48
109
  def first
@@ -56,4 +117,4 @@ module Celluloid
56
117
  end
57
118
 
58
119
  require 'celluloid/tasks/task_fiber'
59
- require 'celluloid/tasks/task_thread'
120
+ require 'celluloid/tasks/task_thread'
@@ -3,52 +3,20 @@ 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
+ yield
35
10
  end
36
11
  end
37
12
 
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
13
+ def signal
14
+ Fiber.yield
46
15
  end
47
16
 
48
17
  # Resume a suspended task, giving it a value to return if needed
49
- def resume(value = nil)
18
+ def deliver(value)
50
19
  @fiber.resume value
51
- nil
52
20
  rescue SystemStackError => ex
53
21
  raise FiberStackError, "#{ex} (please see https://github.com/celluloid/celluloid/wiki/Fiber-stack-errors)"
54
22
  rescue FiberError => ex
@@ -57,17 +25,9 @@ module Celluloid
57
25
 
58
26
  # Terminate this task
59
27
  def terminate
60
- resume Task::TerminatedError.new("task was terminated") if @fiber.alive?
28
+ super
61
29
  rescue FiberError
62
30
  # If we're getting this the task should already be dead
63
31
  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
32
  end
73
33
  end
@@ -1,62 +1,37 @@
1
1
  module Celluloid
2
2
  # Tasks with a Thread backend
3
3
  class TaskThread < Task
4
- attr_reader :type, :status
5
-
6
4
  # Run the given block within a task
7
5
  def initialize(type)
8
- super
9
-
10
6
  @resume_queue = Queue.new
11
7
  @exception_queue = Queue.new
12
8
  @yield_mutex = Mutex.new
13
9
  @yield_cond = ConditionVariable.new
14
10
 
15
- actor = Thread.current[:celluloid_actor]
16
- mailbox = Thread.current[:celluloid_mailbox]
17
- chain_id = Thread.current[:celluloid_chain_id]
18
-
19
- raise NotActorError, "can't create tasks outside of actors" unless actor
11
+ super
12
+ end
20
13
 
14
+ def create
21
15
  @thread = Celluloid.internal_pool.get do
22
16
  begin
23
17
  ex = @resume_queue.pop
24
18
  raise ex if ex.is_a?(Task::TerminatedError)
25
19
 
26
- @status = :running
27
- Thread.current[:celluloid_actor] = actor
28
- Thread.current[:celluloid_mailbox] = mailbox
29
- Thread.current[:celluloid_task] = self
30
- Thread.current[:celluloid_chain_id] = chain_id
31
-
32
- actor.tasks << self
33
20
  yield
34
- rescue Task::TerminatedError
35
- # Task was explicitly terminated
36
21
  rescue Exception => ex
37
22
  @exception_queue << ex
38
23
  ensure
39
- @status = :dead
40
- actor.tasks.delete self
41
24
  @yield_cond.signal
42
25
  end
43
26
  end
44
27
  end
45
28
 
46
- # Suspend the current task, changing the status to the given argument
47
- def suspend(status)
48
- @status = status
29
+ def signal
49
30
  @yield_cond.signal
50
- value = @resume_queue.pop
51
-
52
- raise value if value.is_a?(Task::TerminatedError)
53
- @status = :running
54
-
55
- value
31
+ @resume_queue.pop
56
32
  end
57
33
 
58
- # Resume a suspended task, giving it a value to return if needed
59
- def resume(value = nil)
34
+ def deliver(value)
60
35
  raise DeadTaskError, "cannot resume a dead task" unless @thread.alive?
61
36
 
62
37
  @yield_mutex.synchronize do
@@ -66,23 +41,12 @@ module Celluloid
66
41
  raise @exception_queue.pop
67
42
  end
68
43
  end
69
-
70
- nil
71
44
  rescue ThreadError
72
45
  raise DeadTaskError, "cannot resume a dead task"
73
46
  end
74
47
 
75
- # Terminate this task
76
- def terminate
77
- resume Task::TerminatedError.new("task was terminated") if @thread.alive?
78
- end
79
-
80
- # Is the current task still running?
81
- def running?; @status != :dead; end
82
-
83
- # Nicer string inspect for tasks
84
- def inspect
85
- "<Celluloid::TaskThread:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
48
+ def backtrace
49
+ @thread.backtrace
86
50
  end
87
51
  end
88
52
  end