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,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