celluloid 0.13.0 → 0.14.0.pre

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