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,3 +1,3 @@
1
1
  require 'celluloid'
2
2
 
3
- Celluloid.boot
3
+ Celluloid.start
@@ -0,0 +1,13 @@
1
+ module Celluloid
2
+ class CallChain
3
+ def self.current_id=(value)
4
+ Thread.current[:celluloid_chain_id] = value
5
+ task = Thread.current[:celluloid_task]
6
+ task.chain_id = value if task
7
+ end
8
+
9
+ def self.current_id
10
+ Thread.current[:celluloid_chain_id]
11
+ end
12
+ end
13
+ end
@@ -54,7 +54,7 @@ module Celluloid
54
54
  class SyncCall < Call
55
55
  attr_reader :sender, :task, :chain_id
56
56
 
57
- def initialize(sender, method, arguments = [], block = nil, task = Thread.current[:celluloid_task], chain_id = Thread.current[:celluloid_chain_id])
57
+ def initialize(sender, method, arguments = [], block = nil, task = Thread.current[:celluloid_task], chain_id = CallChain.current_id)
58
58
  super(method, arguments, block)
59
59
 
60
60
  @sender = sender
@@ -63,7 +63,7 @@ module Celluloid
63
63
  end
64
64
 
65
65
  def dispatch(obj)
66
- Thread.current[:celluloid_chain_id] = @chain_id
66
+ CallChain.current_id = @chain_id
67
67
  result = super(obj)
68
68
  respond SuccessResponse.new(self, result)
69
69
  rescue Exception => ex
@@ -76,7 +76,7 @@ module Celluloid
76
76
  # Otherwise, it's a bug in this actor and should be reraised
77
77
  raise unless ex.is_a?(AbortError)
78
78
  ensure
79
- Thread.current[:celluloid_chain_id] = nil
79
+ CallChain.current_id = nil
80
80
  end
81
81
 
82
82
  def cleanup
@@ -86,9 +86,6 @@ module Celluloid
86
86
 
87
87
  def respond(message)
88
88
  @sender << message
89
- rescue MailboxError
90
- # It's possible the sender exited or crashed before we could send a
91
- # response to them.
92
89
  end
93
90
 
94
91
  def value
@@ -121,13 +118,13 @@ module Celluloid
121
118
  class AsyncCall < Call
122
119
 
123
120
  def dispatch(obj)
124
- Thread.current[:celluloid_chain_id] = Celluloid.uuid
121
+ CallChain.current_id = Celluloid.uuid
125
122
  super(obj)
126
123
  rescue AbortError => ex
127
124
  # Swallow aborted async calls, as they indicate the sender made a mistake
128
125
  Logger.debug("#{obj.class}: async call `#@method` aborted!\n#{Logger.format_exception(ex.cause)}")
129
126
  ensure
130
- Thread.current[:celluloid_chain_id] = nil
127
+ CallChain.current_id = nil
131
128
  end
132
129
 
133
130
  end
@@ -1,7 +1,7 @@
1
1
  module Celluloid
2
- class ConditionError < StandardError; end
2
+ class ConditionError < Celluloid::Error; end
3
3
 
4
- # ConditionVariable-like signaling between tasks and actors
4
+ # ConditionVariable-like signaling between tasks and threads
5
5
  class Condition
6
6
  class Waiter
7
7
  def initialize(condition, task, mailbox)
@@ -23,11 +23,9 @@ module Celluloid
23
23
  end
24
24
  end
25
25
 
26
- attr_reader :owner
27
-
28
26
  def initialize
29
27
  @mutex = Mutex.new
30
- @tasks = []
28
+ @waiters = []
31
29
  end
32
30
 
33
31
  # Wait for the given signal and return the associated value
@@ -42,7 +40,7 @@ module Celluloid
42
40
  waiter = Waiter.new(self, task, Celluloid.mailbox)
43
41
 
44
42
  @mutex.synchronize do
45
- @tasks << waiter
43
+ @waiters << waiter
46
44
  end
47
45
 
48
46
  result = Celluloid.suspend :condwait, waiter
@@ -53,7 +51,7 @@ module Celluloid
53
51
  # Send a signal to the first task waiting on this condition
54
52
  def signal(value = nil)
55
53
  @mutex.synchronize do
56
- if waiter = @tasks.shift
54
+ if waiter = @waiters.shift
57
55
  waiter << SignalConditionRequest.new(waiter.task, value)
58
56
  else
59
57
  Logger.debug("Celluloid::Condition signaled spuriously")
@@ -61,11 +59,11 @@ module Celluloid
61
59
  end
62
60
  end
63
61
 
64
- # Broadcast a value to all waiting tasks
62
+ # Broadcast a value to all waiting tasks and threads
65
63
  def broadcast(value = nil)
66
64
  @mutex.synchronize do
67
- @tasks.each { |waiter| waiter << SignalConditionRequest.new(waiter.task, value) }
68
- @tasks.clear
65
+ @waiters.each { |waiter| waiter << SignalConditionRequest.new(waiter.task, value) }
66
+ @waiters.clear
69
67
  end
70
68
  end
71
69
 
@@ -4,7 +4,7 @@ module Celluloid
4
4
  module CPUCounter
5
5
  case RbConfig::CONFIG['host_os'][/^[A-Za-z]+/]
6
6
  when 'darwin'
7
- @cores = Integer(`sysctl hw.ncpu`[/\d+/])
7
+ @cores = Integer(`/usr/sbin/sysctl hw.ncpu`[/\d+/])
8
8
  when 'linux'
9
9
  @cores = if File.exists?("/sys/devices/system/cpu/present")
10
10
  File.read("/sys/devices/system/cpu/present").split('-').last.to_i+1
@@ -13,26 +13,23 @@ module Celluloid
13
13
  def <<(message)
14
14
  @mutex.lock
15
15
  begin
16
- if mailbox_full
17
- Logger.debug "Discarded message: #{message}"
16
+ if mailbox_full || @dead
17
+ dead_letter(message)
18
18
  return
19
19
  end
20
20
  if message.is_a?(SystemEvent)
21
- # Silently swallow system events sent to dead actors
22
- return if @dead
23
-
24
21
  # SystemEvents are high priority messages so they get added to the
25
22
  # head of our message queue instead of the end
26
23
  @messages.unshift message
27
24
  else
28
- raise MailboxError, "dead recipient" if @dead
29
25
  @messages << message
30
26
  end
31
27
 
32
28
  current_actor = Thread.current[:celluloid_actor]
33
29
  @reactor.wakeup unless current_actor && current_actor.mailbox == self
34
30
  rescue IOError
35
- raise MailboxError, "dead recipient"
31
+ Logger.crash "reactor crashed", $!
32
+ dead_letter(message)
36
33
  ensure
37
34
  @mutex.unlock rescue nil
38
35
  end
@@ -59,7 +56,6 @@ module Celluloid
59
56
 
60
57
  message
61
58
  rescue IOError
62
- shutdown # force shutdown of the mailbox
63
59
  raise MailboxShutdown, "mailbox shutdown called during receive"
64
60
  end
65
61
 
@@ -75,8 +71,9 @@ module Celluloid
75
71
 
76
72
  # Cleanup any IO objects this Mailbox may be using
77
73
  def shutdown
78
- @reactor.shutdown
79
- super
74
+ super do
75
+ @reactor.shutdown
76
+ end
80
77
  end
81
78
  end
82
79
  end
@@ -13,7 +13,7 @@ module Celluloid
13
13
  # #
14
14
  # machine = MyMachine.new(current_actor)
15
15
  module FSM
16
- class UnattachedError < StandardError; end # Not attached to an actor
16
+ class UnattachedError < Celluloid::Error; end # Not attached to an actor
17
17
 
18
18
  DEFAULT_STATE = :default # Default state name unless one is explicitly set
19
19
 
@@ -8,8 +8,7 @@ module Celluloid
8
8
  return super unless block
9
9
 
10
10
  future = new
11
- Celluloid.internal_pool.get do
12
- Thread.current.role = :future
11
+ Celluloid::ThreadHandle.new(:future) do
13
12
  begin
14
13
  call = SyncCall.new(future, :call, args)
15
14
  call.dispatch(block)
@@ -3,34 +3,76 @@ require 'thread'
3
3
  module Celluloid
4
4
  # Maintain a thread pool FOR SPEED!!
5
5
  class InternalPool
6
- attr_accessor :busy_size, :idle_size, :max_idle
6
+ attr_accessor :max_idle
7
7
 
8
8
  def initialize
9
- @pool = []
9
+ @group = ThreadGroup.new
10
10
  @mutex = Mutex.new
11
- @busy_size = @idle_size = 0
11
+ @threads = []
12
12
 
13
- reset
14
- end
15
-
16
- def reset
17
13
  # TODO: should really adjust this based on usage
18
14
  @max_idle = 16
15
+ @running = true
16
+ end
17
+
18
+ def busy_size
19
+ @threads.select(&:busy).size
20
+ end
21
+
22
+ def idle_size
23
+ @threads.reject(&:busy).size
24
+ end
25
+
26
+ def assert_running
27
+ unless running?
28
+ raise Error, "Thread pool is not running"
29
+ end
30
+ end
31
+
32
+ def assert_inactive
33
+ if active?
34
+ message = "Thread pool is still active"
35
+ if defined?(JRUBY_VERSION)
36
+ Celluloid.logger.warn message
37
+ else
38
+ raise Error, message
39
+ end
40
+ end
41
+ end
42
+
43
+ def running?
44
+ @running
45
+ end
46
+
47
+ def active?
48
+ to_a.any?
49
+ end
50
+
51
+ def each
52
+ @threads.each do |thread|
53
+ yield thread
54
+ end
55
+ end
56
+
57
+ def to_a
58
+ @threads
19
59
  end
20
60
 
21
61
  # Get a thread from the pool, running the given block
22
62
  def get(&block)
23
63
  @mutex.synchronize do
64
+ assert_running
65
+
24
66
  begin
25
- if @pool.empty?
67
+ idle = @threads.reject(&:busy)
68
+ if idle.empty?
26
69
  thread = create
27
70
  else
28
- thread = @pool.shift
29
- @idle_size -= 1
71
+ thread = idle.first
30
72
  end
31
73
  end until thread.status # handle crashed threads
32
74
 
33
- @busy_size += 1
75
+ thread.busy = true
34
76
  thread[:celluloid_queue] << block
35
77
  thread
36
78
  end
@@ -39,13 +81,12 @@ module Celluloid
39
81
  # Return a thread to the pool
40
82
  def put(thread)
41
83
  @mutex.synchronize do
42
- if @pool.size >= @max_idle
84
+ thread.busy = false
85
+ if idle_size >= @max_idle
43
86
  thread[:celluloid_queue] << nil
87
+ @threads.delete(thread)
44
88
  else
45
89
  clean_thread_locals(thread)
46
- @pool << thread
47
- @idle_size += 1
48
- @busy_size -= 1
49
90
  end
50
91
  end
51
92
  end
@@ -66,6 +107,8 @@ module Celluloid
66
107
  end
67
108
 
68
109
  thread[:celluloid_queue] = queue
110
+ @threads << thread
111
+ @group.add(thread)
69
112
  thread
70
113
  end
71
114
 
@@ -81,13 +124,27 @@ module Celluloid
81
124
 
82
125
  def shutdown
83
126
  @mutex.synchronize do
84
- @max_idle = 0
85
- @pool.each do |thread|
127
+ finalize
128
+ @threads.each do |thread|
86
129
  thread[:celluloid_queue] << nil
87
130
  end
88
131
  end
89
132
  end
90
- end
91
133
 
92
- self.internal_pool = InternalPool.new
93
- end
134
+ def kill
135
+ @mutex.synchronize do
136
+ finalize
137
+ @running = false
138
+
139
+ @threads.shift.kill until @threads.empty?
140
+ @group.list.each(&:kill)
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def finalize
147
+ @max_idle = 0
148
+ end
149
+ end
150
+ end
@@ -1,41 +1,3 @@
1
- module Celluloid
2
- class ActorProxy
3
- # method_missing black magic to call bang predicate methods asynchronously
4
- def method_missing(meth, *args, &block)
5
- # bang methods are async calls
6
- if meth.match(/!$/)
7
- Logger.deprecate("'bang method'-style async syntax is deprecated and will be removed in Celluloid 1.0." +
8
- "Call async methods with 'actor.async.method'.")
9
-
10
- unbanged_meth = meth.to_s
11
- unbanged_meth.slice!(-1, 1)
12
- async unbanged_meth, *args, &block
13
- else
14
- super
15
- end
16
- end
17
- end
18
-
19
- module InstanceMethods
20
- # Process async calls via method_missing
21
- def method_missing(meth, *args, &block)
22
- # bang methods are async calls
23
- if meth.to_s.match(/!$/)
24
- Logger.deprecate("'bang method'-style async syntax is deprecated and will be removed in Celluloid 1.0." +
25
- "Call async methods with 'actor.async.method'.")
26
-
27
- unbanged_meth = meth.to_s.sub(/!$/, '')
28
- args.unshift unbanged_meth
29
-
30
- async :__send__, *args, &block
31
- return
32
- end
33
-
34
- super
35
- end
36
- end
37
- end
38
-
39
1
  class Thread
40
2
  def self.mailbox
41
3
  Celluloid.mailbox
@@ -1,8 +1,8 @@
1
1
  require 'thread'
2
2
 
3
3
  module Celluloid
4
- class MailboxError < StandardError; end # you can't message the dead
5
- class MailboxShutdown < StandardError; end # raised if the mailbox can no longer be used
4
+ class MailboxDead < Celluloid::Error; end # you can't receive from the dead
5
+ class MailboxShutdown < Celluloid::Error; end # raised if the mailbox can no longer be used
6
6
 
7
7
  # Actors communicate with asynchronous messages. Messages are buffered in
8
8
  # Mailboxes until Actors can act upon them.
@@ -26,19 +26,15 @@ module Celluloid
26
26
  def <<(message)
27
27
  @mutex.lock
28
28
  begin
29
- if mailbox_full
30
- Logger.debug "Discarded message: #{message}"
29
+ if mailbox_full || @dead
30
+ dead_letter(message)
31
31
  return
32
32
  end
33
33
  if message.is_a?(SystemEvent)
34
- # Silently swallow system events sent to dead actors
35
- return if @dead
36
-
37
34
  # SystemEvents are high priority messages so they get added to the
38
35
  # head of our message queue instead of the end
39
36
  @messages.unshift message
40
37
  else
41
- raise MailboxError, "dead recipient" if @dead
42
38
  @messages << message
43
39
  end
44
40
 
@@ -55,7 +51,7 @@ module Celluloid
55
51
 
56
52
  @mutex.lock
57
53
  begin
58
- raise MailboxError, "attempted to receive from a dead mailbox" if @dead
54
+ raise MailboxDead, "attempted to receive from a dead mailbox" if @dead
59
55
 
60
56
  begin
61
57
  message = next_message(&block)
@@ -99,8 +95,11 @@ module Celluloid
99
95
 
100
96
  # Shut down this mailbox and clean up its contents
101
97
  def shutdown
98
+ raise MailboxDead, "mailbox already shutdown" if @dead
99
+
102
100
  @mutex.lock
103
101
  begin
102
+ yield if block_given?
104
103
  messages = @messages
105
104
  @messages = []
106
105
  @dead = true
@@ -108,7 +107,10 @@ module Celluloid
108
107
  @mutex.unlock rescue nil
109
108
  end
110
109
 
111
- messages.each { |msg| msg.cleanup if msg.respond_to? :cleanup }
110
+ messages.each do |msg|
111
+ dead_letter msg
112
+ msg.cleanup if msg.respond_to? :cleanup
113
+ end
112
114
  true
113
115
  end
114
116
 
@@ -138,6 +140,11 @@ module Celluloid
138
140
  end
139
141
 
140
142
  private
143
+
144
+ def dead_letter(message)
145
+ Logger.debug "Discarded message (mailbox is dead): #{message}"
146
+ end
147
+
141
148
  def mailbox_full
142
149
  @max_size && @messages.size >= @max_size
143
150
  end