celluloid 0.14.1 → 0.15.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -2
  3. data/lib/celluloid.rb +92 -108
  4. data/lib/celluloid/actor.rb +42 -64
  5. data/lib/celluloid/autostart.rb +1 -1
  6. data/lib/celluloid/call_chain.rb +13 -0
  7. data/lib/celluloid/calls.rb +5 -8
  8. data/lib/celluloid/condition.rb +8 -10
  9. data/lib/celluloid/cpu_counter.rb +1 -1
  10. data/lib/celluloid/evented_mailbox.rb +7 -10
  11. data/lib/celluloid/fsm.rb +1 -1
  12. data/lib/celluloid/future.rb +1 -2
  13. data/lib/celluloid/internal_pool.rb +77 -20
  14. data/lib/celluloid/legacy.rb +0 -38
  15. data/lib/celluloid/mailbox.rb +17 -10
  16. data/lib/celluloid/pool_manager.rb +1 -1
  17. data/lib/celluloid/properties.rb +24 -0
  18. data/lib/celluloid/proxies/abstract_proxy.rb +3 -0
  19. data/lib/celluloid/proxies/actor_proxy.rb +3 -32
  20. data/lib/celluloid/proxies/async_proxy.rb +4 -8
  21. data/lib/celluloid/proxies/future_proxy.rb +8 -6
  22. data/lib/celluloid/proxies/sync_proxy.rb +12 -7
  23. data/lib/celluloid/rspec.rb +3 -1
  24. data/lib/celluloid/signals.rb +7 -35
  25. data/lib/celluloid/stack_dump.rb +50 -37
  26. data/lib/celluloid/supervision_group.rb +5 -5
  27. data/lib/celluloid/task_set.rb +49 -0
  28. data/lib/celluloid/tasks.rb +67 -42
  29. data/lib/celluloid/tasks/task_fiber.rb +3 -1
  30. data/lib/celluloid/tasks/task_thread.rb +2 -3
  31. data/lib/celluloid/thread.rb +2 -0
  32. data/spec/celluloid/actor_spec.rb +5 -0
  33. data/spec/celluloid/block_spec.rb +54 -0
  34. data/spec/celluloid/calls_spec.rb +42 -0
  35. data/spec/celluloid/condition_spec.rb +65 -0
  36. data/spec/celluloid/evented_mailbox_spec.rb +34 -0
  37. data/spec/celluloid/fsm_spec.rb +107 -0
  38. data/spec/celluloid/future_spec.rb +32 -0
  39. data/spec/celluloid/internal_pool_spec.rb +52 -0
  40. data/spec/celluloid/links_spec.rb +45 -0
  41. data/spec/celluloid/logging/ring_buffer_spec.rb +38 -0
  42. data/spec/celluloid/mailbox_spec.rb +5 -0
  43. data/spec/celluloid/notifications_spec.rb +120 -0
  44. data/spec/celluloid/pool_spec.rb +52 -0
  45. data/spec/celluloid/properties_spec.rb +42 -0
  46. data/spec/celluloid/registry_spec.rb +64 -0
  47. data/spec/celluloid/stack_dump_spec.rb +35 -0
  48. data/spec/celluloid/supervision_group_spec.rb +53 -0
  49. data/spec/celluloid/supervisor_spec.rb +92 -0
  50. data/spec/celluloid/tasks/task_fiber_spec.rb +5 -0
  51. data/spec/celluloid/tasks/task_thread_spec.rb +5 -0
  52. data/spec/celluloid/thread_handle_spec.rb +22 -0
  53. data/spec/celluloid/uuid_spec.rb +11 -0
  54. data/spec/spec_helper.rb +31 -0
  55. data/spec/support/actor_examples.rb +161 -10
  56. data/spec/support/example_actor_class.rb +8 -0
  57. data/spec/support/mailbox_examples.rb +15 -3
  58. data/spec/support/task_examples.rb +2 -2
  59. metadata +28 -3
  60. data/lib/celluloid/version.rb +0 -4
@@ -1,13 +1,18 @@
1
1
  module Celluloid
2
2
  # Asked to do task-related things outside a task
3
- class NotTaskError < StandardError; end
3
+ class NotTaskError < Celluloid::Error; end
4
4
 
5
5
  # Trying to resume a dead task
6
- class DeadTaskError < StandardError; end
6
+ class DeadTaskError < Celluloid::Error; end
7
+
8
+ # Errors which should be resumed automatically
9
+ class ResumableError < Celluloid::Error; end
7
10
 
8
11
  # Tasks are interruptable/resumable execution contexts used to run methods
9
12
  class Task
10
- class TerminatedError < StandardError; end # kill a running task
13
+ class TerminatedError < ResumableError; end # kill a running task after terminate
14
+
15
+ class TimeoutError < ResumableError; end # kill a running task after timeout
11
16
 
12
17
  # Obtain the current task
13
18
  def self.current
@@ -19,15 +24,20 @@ module Celluloid
19
24
  Task.current.suspend(status)
20
25
  end
21
26
 
22
- attr_reader :type, :status
27
+ attr_reader :type, :meta, :status
28
+ attr_accessor :chain_id
23
29
 
24
30
  # Create a new task
25
- def initialize(type)
26
- @type = type
27
- @status = :new
31
+ def initialize(type, meta)
32
+ @type = type
33
+ @meta = meta
34
+ @status = :new
28
35
 
29
- actor = Thread.current[:celluloid_actor]
30
- chain_id = Thread.current[:celluloid_chain_id]
36
+ @exclusive = false
37
+ @dangerous_suspend = @meta ? @meta.delete(:dangerous_suspend) : false
38
+
39
+ actor = Thread.current[:celluloid_actor]
40
+ @chain_id = CallChain.current_id
31
41
 
32
42
  raise NotActorError, "can't create tasks outside of actors" unless actor
33
43
 
@@ -35,8 +45,9 @@ module Celluloid
35
45
  begin
36
46
  @status = :running
37
47
  actor.setup_thread
38
- Thread.current[:celluloid_task] = self
39
- Thread.current[:celluloid_chain_id] = chain_id
48
+
49
+ Thread.current[:celluloid_task] = self
50
+ CallChain.current_id = @chain_id
40
51
 
41
52
  actor.tasks << self
42
53
  yield
@@ -55,11 +66,25 @@ module Celluloid
55
66
 
56
67
  # Suspend the current task, changing the status to the given argument
57
68
  def suspend(status)
69
+ raise "Cannot suspend while in exclusive mode" if exclusive?
70
+
58
71
  @status = status
72
+
73
+ if $CELLULOID_DEBUG && @dangerous_suspend
74
+ warning = "Dangerously suspending task: "
75
+ warning << [
76
+ "type=#{@type.inspect}",
77
+ "meta=#{@meta.inspect}",
78
+ "status=#{@status.inspect}"
79
+ ].join(", ")
80
+
81
+ Logger.warn [warning, *caller[2..8]].join("\n\t")
82
+ end
83
+
59
84
  value = signal
60
85
 
61
- raise value if value.is_a?(Task::TerminatedError)
62
86
  @status = :running
87
+ raise value if value.is_a?(Celluloid::ResumableError)
63
88
 
64
89
  value
65
90
  end
@@ -70,9 +95,37 @@ module Celluloid
70
95
  nil
71
96
  end
72
97
 
98
+ # Execute a code block in exclusive mode.
99
+ def exclusive
100
+ if @exclusive
101
+ yield
102
+ else
103
+ begin
104
+ @exclusive = true
105
+ yield
106
+ ensure
107
+ @exclusive = false
108
+ end
109
+ end
110
+ end
111
+
73
112
  # Terminate this task
74
113
  def terminate
75
- resume Task::TerminatedError.new("task was terminated") if running?
114
+ raise "Cannot terminate an exclusive task" if exclusive?
115
+
116
+ if running?
117
+ Celluloid.logger.warn "Terminating task: type=#{@type.inspect}, meta=#{@meta.inspect}, status=#{@status.inspect}"
118
+ exception = Task::TerminatedError.new("task was terminated")
119
+ exception.set_backtrace(caller)
120
+ resume exception
121
+ else
122
+ raise DeadTaskError, "task is already dead"
123
+ end
124
+ end
125
+
126
+ # Is this task running in exclusive mode?
127
+ def exclusive?
128
+ @exclusive
76
129
  end
77
130
 
78
131
  def backtrace
@@ -83,35 +136,7 @@ module Celluloid
83
136
 
84
137
  # Nicer string inspect for tasks
85
138
  def inspect
86
- "#<#{self.class}:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}>"
87
- end
88
- end
89
-
90
- class TaskSet
91
- include Enumerable
92
-
93
- def initialize
94
- @tasks = Set.new
95
- end
96
-
97
- def <<(task)
98
- @tasks += [task]
99
- end
100
-
101
- def delete(task)
102
- @tasks -= [task]
103
- end
104
-
105
- def each(&blk)
106
- @tasks.each(&blk)
107
- end
108
-
109
- def first
110
- @tasks.first
111
- end
112
-
113
- def empty?
114
- @tasks.empty?
139
+ "#<#{self.class}:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @meta=#{@meta.inspect}, @status=#{@status.inspect}>"
115
140
  end
116
141
  end
117
142
  end
@@ -1,13 +1,15 @@
1
1
  module Celluloid
2
- class FiberStackError < StandardError; end
2
+ class FiberStackError < Celluloid::Error; end
3
3
 
4
4
  # Tasks with a Fiber backend
5
5
  class TaskFiber < Task
6
6
 
7
7
  def create
8
+ queue = Thread.current[:celluloid_queue]
8
9
  @fiber = Fiber.new do
9
10
  # FIXME: cannot use the writer as specs run inside normal Threads
10
11
  Thread.current[:celluloid_role] = :actor
12
+ Thread.current[:celluloid_queue] = queue
11
13
  yield
12
14
  end
13
15
  end
@@ -2,7 +2,7 @@ module Celluloid
2
2
  # Tasks with a Thread backend
3
3
  class TaskThread < Task
4
4
  # Run the given block within a task
5
- def initialize(type)
5
+ def initialize(type, meta)
6
6
  @resume_queue = Queue.new
7
7
  @exception_queue = Queue.new
8
8
  @yield_mutex = Mutex.new
@@ -12,8 +12,7 @@ module Celluloid
12
12
  end
13
13
 
14
14
  def create
15
- @thread = Celluloid.internal_pool.get do
16
- Thread.current.role = :task
15
+ @thread = Celluloid::ThreadHandle.new(:task) do
17
16
  begin
18
17
  ex = @resume_queue.pop
19
18
  raise ex if ex.is_a?(Task::TerminatedError)
@@ -6,6 +6,8 @@ module Celluloid
6
6
  true
7
7
  end
8
8
 
9
+ attr_accessor :busy
10
+
9
11
  # Obtain the role of this thread
10
12
  def role
11
13
  self[:celluloid_role]
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid do
4
+ it_behaves_like "a Celluloid Actor", Celluloid
5
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Blocks" do
4
+ class MyBlockActor
5
+ include Celluloid
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+ attr_reader :name
11
+
12
+ def ask_for_something(other)
13
+ sender_actor = current_actor
14
+ $data << [:outside, @name, current_actor.name]
15
+ other.do_something_and_callback do |value|
16
+ $data << [:yielded, @name, current_actor.name]
17
+ $data << self.receive_result(:self)
18
+ $data << current_actor.receive_result(:current_actor)
19
+ $data << sender_actor.receive_result(:sender)
20
+ "somevalue"
21
+ end
22
+ end
23
+
24
+ def do_something_and_callback
25
+ $data << [:something, @name, current_actor.name]
26
+ $data << yield(:foo)
27
+ end
28
+
29
+ def receive_result(result)
30
+ [result, @name, current_actor.name]
31
+ end
32
+ end
33
+
34
+ it "works" do
35
+ $data = []
36
+
37
+ a1 = MyBlockActor.new("one")
38
+ a2 = MyBlockActor.new("two")
39
+
40
+ a1.ask_for_something a2
41
+
42
+ expected = [
43
+ [:outside, "one", "one"],
44
+ [:something, "two", "two"],
45
+ [:yielded, "one", "one"],
46
+ [:self, "one", "one"],
47
+ [:current_actor, "one", "one"],
48
+ [:sender, "one", "one"],
49
+ "somevalue",
50
+ ]
51
+
52
+ $data.should eq(expected)
53
+ end
54
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::SyncCall do
4
+ class CallExampleActor
5
+ include Celluloid
6
+
7
+ def initialize(next_actor = nil)
8
+ @next = next_actor
9
+ end
10
+
11
+ def actual_method; end
12
+
13
+ def chained_call_ids
14
+ [call_chain_id, @next.call_chain_id]
15
+ end
16
+ end
17
+
18
+ let(:actor) { CallExampleActor.new }
19
+
20
+ it "aborts with NoMethodError when a nonexistent method is called" do
21
+ expect do
22
+ actor.the_method_that_wasnt_there
23
+ end.to raise_exception(NoMethodError)
24
+
25
+ actor.should be_alive
26
+ end
27
+
28
+ it "aborts with ArgumentError when a method is called with too many arguments" do
29
+ expect do
30
+ actor.actual_method("with too many arguments")
31
+ end.to raise_exception(ArgumentError)
32
+
33
+ actor.should be_alive
34
+ end
35
+
36
+ it "preserves call chains across synchronous calls" do
37
+ actor2 = CallExampleActor.new(actor)
38
+
39
+ uuid, next_actor_uuid = actor2.chained_call_ids
40
+ uuid.should eq next_actor_uuid
41
+ end
42
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::Condition do
4
+ class ConditionExample
5
+ include Celluloid
6
+
7
+ attr_reader :condition, :signaled_times
8
+
9
+ def initialize
10
+ @condition = Condition.new
11
+
12
+ @waiting = false
13
+ @signaled_times = 0
14
+ end
15
+
16
+ def signal_condition(condition, value)
17
+ condition.signal value
18
+ end
19
+
20
+ def wait_for_condition
21
+ @waiting = true
22
+ begin
23
+ value = @condition.wait
24
+ @signaled_times += 1
25
+ ensure
26
+ @waiting = false
27
+ end
28
+
29
+ value
30
+ end
31
+
32
+ def waiting?; @waiting end
33
+ end
34
+
35
+ let(:actor) { ConditionExample.new }
36
+ after { actor.terminate rescue nil }
37
+
38
+ it "sends signals" do
39
+ 3.times { actor.async.wait_for_condition }
40
+ actor.signaled_times.should be_zero
41
+
42
+ actor.condition.signal
43
+ actor.signaled_times.should be(1)
44
+ end
45
+
46
+ it "broadcasts signals" do
47
+ 3.times { actor.async.wait_for_condition }
48
+ actor.signaled_times.should be_zero
49
+
50
+ actor.condition.broadcast
51
+ actor.signaled_times.should be(3)
52
+ end
53
+
54
+ it "sends values along with signals" do
55
+ future = actor.future(:wait_for_condition)
56
+ actor.condition.signal(:example_value)
57
+ future.value.should be(:example_value)
58
+ end
59
+
60
+ it "supports waiting outside actors" do
61
+ condition = Celluloid::Condition.new
62
+ actor.async.signal_condition condition, :value
63
+ condition.wait.should eq(:value)
64
+ end
65
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ class TestEventedMailbox < Celluloid::EventedMailbox
4
+ class Reactor
5
+ def initialize
6
+ @condition = ConditionVariable.new
7
+ @mutex = Mutex.new
8
+ end
9
+
10
+ def wakeup
11
+ @mutex.synchronize do
12
+ @condition.signal
13
+ end
14
+ end
15
+
16
+ def run_once(timeout)
17
+ @mutex.synchronize do
18
+ @condition.wait(@mutex, timeout)
19
+ end
20
+ end
21
+
22
+ def shutdown
23
+ end
24
+ end
25
+
26
+ def initialize
27
+ super(Reactor)
28
+ end
29
+ end
30
+
31
+ describe Celluloid::EventedMailbox do
32
+ subject { TestEventedMailbox.new }
33
+ it_behaves_like "a Celluloid Mailbox"
34
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ describe Celluloid::FSM do
4
+ before :all do
5
+ class TestMachine
6
+ include Celluloid::FSM
7
+
8
+ def initialize
9
+ super
10
+ @fired = false
11
+ end
12
+
13
+ state :callbacked do
14
+ @fired = true
15
+ end
16
+
17
+ state :pre_done, :to => :done
18
+ state :another, :done
19
+
20
+ def fired?; @fired end
21
+ end
22
+
23
+ class DummyActor
24
+ include Celluloid
25
+ end
26
+
27
+ class CustomDefaultMachine
28
+ include Celluloid::FSM
29
+
30
+ default_state :foobar
31
+ end
32
+ end
33
+
34
+ subject { TestMachine.new }
35
+
36
+ it "starts in the default state" do
37
+ subject.state.should eq(TestMachine.default_state)
38
+ end
39
+
40
+ it "transitions between states" do
41
+ subject.state.should_not be :done
42
+ subject.transition :done
43
+ subject.state.should be :done
44
+ end
45
+
46
+ it "fires callbacks for states" do
47
+ subject.should_not be_fired
48
+ subject.transition :callbacked
49
+ subject.should be_fired
50
+ end
51
+
52
+ it "allows custom default states" do
53
+ CustomDefaultMachine.new.state.should be :foobar
54
+ end
55
+
56
+ it "supports constraints on valid state transitions" do
57
+ subject.transition :pre_done
58
+ expect { subject.transition :another }.to raise_exception ArgumentError
59
+ end
60
+
61
+ it "transitions to states after a specified delay" do
62
+ interval = Celluloid::TIMER_QUANTUM * 10
63
+
64
+ subject.attach DummyActor.new
65
+ subject.transition :another
66
+ subject.transition :done, :delay => interval
67
+
68
+ subject.state.should be :another
69
+ sleep interval + Celluloid::TIMER_QUANTUM
70
+
71
+ subject.state.should be :done
72
+ end
73
+
74
+ it "cancels delayed state transitions if another transition is made" do
75
+ interval = Celluloid::TIMER_QUANTUM * 10
76
+
77
+ subject.attach DummyActor.new
78
+ subject.transition :another
79
+ subject.transition :done, :delay => interval
80
+
81
+ subject.state.should be :another
82
+ subject.transition :pre_done
83
+ sleep interval + Celluloid::TIMER_QUANTUM
84
+
85
+ subject.state.should be :pre_done
86
+ end
87
+
88
+ context "actor is not set" do
89
+ context "transition is delayed" do
90
+ it "raises an unattached error" do
91
+ expect { subject.transition :another, :delay => 100 } \
92
+ .to raise_error(Celluloid::FSM::UnattachedError)
93
+ end
94
+ end
95
+ end
96
+
97
+ context "transitioning to an invalid state" do
98
+ it "raises an argument error" do
99
+ expect { subject.transition :invalid_state }.to raise_error(ArgumentError)
100
+ end
101
+
102
+ it "should not call transition! if the state is :default" do
103
+ subject.should_not_receive :transition!
104
+ subject.transition :default
105
+ end
106
+ end
107
+ end