celluloid 0.14.1 → 0.15.0.pre

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