celluloid 0.15.2 → 0.16.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +20 -0
  3. data/README.md +29 -2
  4. data/lib/celluloid.rb +68 -73
  5. data/lib/celluloid/actor.rb +69 -123
  6. data/lib/celluloid/actor_system.rb +107 -0
  7. data/lib/celluloid/calls.rb +16 -16
  8. data/lib/celluloid/cell.rb +89 -0
  9. data/lib/celluloid/condition.rb +25 -8
  10. data/lib/celluloid/cpu_counter.rb +2 -0
  11. data/lib/celluloid/evented_mailbox.rb +2 -1
  12. data/lib/celluloid/exceptions.rb +23 -0
  13. data/lib/celluloid/future.rb +1 -1
  14. data/lib/celluloid/handlers.rb +41 -0
  15. data/lib/celluloid/internal_pool.rb +0 -3
  16. data/lib/celluloid/logger.rb +30 -0
  17. data/lib/celluloid/logging/incident_logger.rb +1 -1
  18. data/lib/celluloid/mailbox.rb +19 -18
  19. data/lib/celluloid/method.rb +8 -0
  20. data/lib/celluloid/pool_manager.rb +1 -1
  21. data/lib/celluloid/probe.rb +73 -0
  22. data/lib/celluloid/properties.rb +2 -2
  23. data/lib/celluloid/proxies/actor_proxy.rb +9 -41
  24. data/lib/celluloid/proxies/cell_proxy.rb +68 -0
  25. data/lib/celluloid/proxies/sync_proxy.rb +1 -1
  26. data/lib/celluloid/receivers.rb +1 -0
  27. data/lib/celluloid/registry.rb +1 -8
  28. data/lib/celluloid/stack_dump.rb +34 -11
  29. data/lib/celluloid/supervision_group.rb +26 -14
  30. data/lib/celluloid/tasks.rb +6 -9
  31. data/lib/celluloid/tasks/task_fiber.rb +6 -0
  32. data/lib/celluloid/tasks/task_thread.rb +2 -1
  33. data/lib/celluloid/thread_handle.rb +2 -2
  34. data/spec/celluloid/actor_spec.rb +1 -1
  35. data/spec/celluloid/actor_system_spec.rb +69 -0
  36. data/spec/celluloid/block_spec.rb +1 -1
  37. data/spec/celluloid/calls_spec.rb +1 -1
  38. data/spec/celluloid/condition_spec.rb +14 -3
  39. data/spec/celluloid/cpu_counter_spec.rb +9 -0
  40. data/spec/celluloid/fsm_spec.rb +1 -1
  41. data/spec/celluloid/future_spec.rb +1 -1
  42. data/spec/celluloid/notifications_spec.rb +1 -1
  43. data/spec/celluloid/pool_spec.rb +1 -1
  44. data/spec/celluloid/probe_spec.rb +121 -0
  45. data/spec/celluloid/registry_spec.rb +6 -6
  46. data/spec/celluloid/stack_dump_spec.rb +37 -8
  47. data/spec/celluloid/supervision_group_spec.rb +7 -1
  48. data/spec/celluloid/supervisor_spec.rb +12 -1
  49. data/spec/celluloid/tasks/task_fiber_spec.rb +1 -1
  50. data/spec/celluloid/tasks/task_thread_spec.rb +1 -1
  51. data/spec/celluloid/thread_handle_spec.rb +7 -3
  52. data/spec/spec_helper.rb +20 -7
  53. data/spec/support/actor_examples.rb +33 -15
  54. data/spec/support/mailbox_examples.rb +9 -3
  55. data/spec/support/task_examples.rb +2 -0
  56. metadata +32 -22
@@ -0,0 +1,107 @@
1
+ module Celluloid
2
+ class ActorSystem
3
+ extend Forwardable
4
+
5
+ def initialize
6
+ @internal_pool = InternalPool.new
7
+ @registry = Registry.new
8
+ end
9
+ attr_reader :registry
10
+
11
+ # Launch default services
12
+ # FIXME: We should set up the supervision hierarchy here
13
+ def start
14
+ within do
15
+ Celluloid::Notifications::Fanout.supervise_as :notifications_fanout
16
+ Celluloid::IncidentReporter.supervise_as :default_incident_reporter, STDERR
17
+ end
18
+ true
19
+ end
20
+
21
+ def within
22
+ old = Thread.current[:celluloid_actor_system]
23
+ Thread.current[:celluloid_actor_system] = self
24
+ yield
25
+ ensure
26
+ Thread.current[:celluloid_actor_system] = old
27
+ end
28
+
29
+ def get_thread
30
+ @internal_pool.get do
31
+ Thread.current[:celluloid_actor_system] = self
32
+ yield
33
+ end
34
+ end
35
+
36
+ def stack_dump
37
+ Celluloid::StackDump.new(@internal_pool)
38
+ end
39
+
40
+ def_delegators "@registry", :[], :get, :[]=, :set, :delete
41
+
42
+ def registered
43
+ @registry.names
44
+ end
45
+
46
+ def clear_registry
47
+ @registry.clear
48
+ end
49
+
50
+ def running
51
+ actors = []
52
+ @internal_pool.each do |t|
53
+ next unless t.role == :actor
54
+ actors << t.actor.behavior_proxy if t.actor && t.actor.respond_to?(:behavior_proxy)
55
+ end
56
+ actors
57
+ end
58
+
59
+ def running?
60
+ @internal_pool.running?
61
+ end
62
+
63
+ # Shut down all running actors
64
+ def shutdown
65
+ actors = running
66
+ Timeout.timeout(shutdown_timeout) do
67
+ Logger.debug "Terminating #{actors.size} #{(actors.size > 1) ? 'actors' : 'actor'}..." if actors.size > 0
68
+
69
+ # Actors cannot self-terminate, you must do it for them
70
+ actors.each do |actor|
71
+ begin
72
+ actor.terminate!
73
+ rescue DeadActorError
74
+ end
75
+ end
76
+
77
+ actors.each do |actor|
78
+ begin
79
+ Actor.join(actor)
80
+ rescue DeadActorError
81
+ end
82
+ end
83
+
84
+ @internal_pool.shutdown
85
+ end
86
+ rescue Timeout::Error
87
+ Logger.error("Couldn't cleanly terminate all actors in #{shutdown_timeout} seconds!")
88
+ actors.each do |actor|
89
+ begin
90
+ Actor.kill(actor)
91
+ rescue DeadActorError, MailboxDead
92
+ end
93
+ end
94
+ ensure
95
+ @internal_pool.kill
96
+ clear_registry
97
+ end
98
+
99
+ def assert_inactive
100
+ @internal_pool.assert_inactive
101
+ end
102
+
103
+ def shutdown_timeout
104
+ Celluloid.shutdown_timeout
105
+ end
106
+ end
107
+ end
@@ -21,32 +21,28 @@ module Celluloid
21
21
  end
22
22
 
23
23
  def dispatch(obj)
24
+ check(obj)
24
25
  _block = @block && @block.to_proc
25
26
  obj.public_send(@method, *@arguments, &_block)
26
- rescue NoMethodError => ex
27
- # Abort if the sender made a mistake
28
- raise AbortError.new(ex) unless obj.respond_to? @method
29
-
30
- # Otherwise something blew up. Crash this actor
31
- raise
32
- rescue ArgumentError => ex
33
- # Abort if the sender made a mistake
27
+ end
28
+
29
+ def check(obj)
30
+ raise NoMethodError, "undefined method `#{@method}' for #{obj.inspect}" unless obj.respond_to? @method
31
+
34
32
  begin
35
33
  arity = obj.method(@method).arity
36
34
  rescue NameError
37
- # In theory this shouldn't happen, but just in case
38
- raise AbortError.new(ex)
35
+ return
39
36
  end
40
37
 
41
38
  if arity >= 0
42
- raise AbortError.new(ex) if @arguments.size != arity
39
+ raise ArgumentError, "wrong number of arguments (#{@arguments.size} for #{arity})" if @arguments.size != arity
43
40
  elsif arity < -1
44
41
  mandatory_args = -arity - 1
45
- raise AbortError.new(ex) if arguments.size < mandatory_args
42
+ raise ArgumentError, "wrong number of arguments (#{@arguments.size} for #{mandatory_args}+)" if arguments.size < mandatory_args
46
43
  end
47
-
48
- # Otherwise something blew up. Crash this actor
49
- raise
44
+ rescue => ex
45
+ raise AbortError.new(ex)
50
46
  end
51
47
  end
52
48
 
@@ -88,8 +84,12 @@ module Celluloid
88
84
  @sender << message
89
85
  end
90
86
 
87
+ def response
88
+ Celluloid.suspend(:callwait, self)
89
+ end
90
+
91
91
  def value
92
- Celluloid.suspend(:callwait, self).value
92
+ response.value
93
93
  end
94
94
 
95
95
  def wait
@@ -0,0 +1,89 @@
1
+ module Celluloid
2
+ OWNER_IVAR = :@celluloid_owner # reference to owning actor
3
+
4
+ # Wrap the given subject with an Cell
5
+ class Cell
6
+ class ExitHandler
7
+ def initialize(behavior, subject, method_name)
8
+ @behavior = behavior
9
+ @subject = subject
10
+ @method_name = method_name
11
+ end
12
+
13
+ def call(event)
14
+ @behavior.task(:exit_handler, @method_name) do
15
+ @subject.send(@method_name, event.actor, event.reason)
16
+ end
17
+ end
18
+ end
19
+
20
+ def initialize(subject, options, actor_options)
21
+ @actor = Actor.new(self, actor_options)
22
+ @subject = subject
23
+ @receiver_block_executions = options[:receiver_block_executions]
24
+ @exclusive_methods = options[:exclusive_methods]
25
+ @finalizer = options[:finalizer]
26
+
27
+ @subject.instance_variable_set(OWNER_IVAR, @actor)
28
+
29
+ if exit_handler_name = options[:exit_handler_name]
30
+ @actor.exit_handler = ExitHandler.new(self, @subject, exit_handler_name)
31
+ end
32
+
33
+ @actor.handle(Call) do |message|
34
+ invoke(message)
35
+ end
36
+ @actor.handle(BlockCall) do |message|
37
+ task(:invoke_block) { message.dispatch }
38
+ end
39
+ @actor.handle(BlockResponse, Response) do |message|
40
+ message.dispatch
41
+ end
42
+
43
+ @actor.start
44
+ @proxy = (options[:proxy_class] || CellProxy).new(@actor.proxy, @actor.mailbox, @subject.class.to_s)
45
+ end
46
+ attr_reader :proxy, :subject
47
+
48
+ def invoke(call)
49
+ meth = call.method
50
+ if meth == :__send__
51
+ meth = call.arguments.first
52
+ end
53
+ if @receiver_block_executions && meth
54
+ if @receiver_block_executions.include?(meth.to_sym)
55
+ call.execute_block_on_receiver
56
+ end
57
+ end
58
+
59
+ task(:call, meth, :dangerous_suspend => meth == :initialize) {
60
+ call.dispatch(@subject)
61
+ }
62
+ end
63
+
64
+ def task(task_type, method_name = nil, meta = nil, &block)
65
+ meta ||= {}
66
+ meta.merge!(:method_name => method_name)
67
+ @actor.task(task_type, meta) do
68
+ if @exclusive_methods && method_name && @exclusive_methods.include?(method_name.to_sym)
69
+ Celluloid.exclusive { yield }
70
+ else
71
+ yield
72
+ end
73
+ end
74
+ end
75
+
76
+ # Run the user-defined finalizer, if one is set
77
+ def shutdown
78
+ return unless @finalizer && @subject.respond_to?(@finalizer, true)
79
+
80
+ task(:finalizer, @finalizer, :dangerous_suspend => true) do
81
+ begin
82
+ @subject.__send__(@finalizer)
83
+ rescue => ex
84
+ Logger.crash("#{@subject.class} finalizer crashed!", ex)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -4,10 +4,11 @@ module Celluloid
4
4
  # ConditionVariable-like signaling between tasks and threads
5
5
  class Condition
6
6
  class Waiter
7
- def initialize(condition, task, mailbox)
7
+ def initialize(condition, task, mailbox, timeout)
8
8
  @condition = condition
9
9
  @task = task
10
10
  @mailbox = mailbox
11
+ @timeout = timeout
11
12
  end
12
13
  attr_reader :condition, :task
13
14
 
@@ -16,9 +17,14 @@ module Celluloid
16
17
  end
17
18
 
18
19
  def wait
19
- message = @mailbox.receive do |msg|
20
- msg.is_a?(SignalConditionRequest) && msg.task == Thread.current
21
- end
20
+ begin
21
+ message = @mailbox.receive(@timeout) do |msg|
22
+ msg.is_a?(SignalConditionRequest) && msg.task == Thread.current
23
+ end
24
+ rescue TimeoutError
25
+ raise ConditionError, "timeout after #{@timeout.inspect} seconds"
26
+ end until message
27
+
22
28
  message.value
23
29
  end
24
30
  end
@@ -29,21 +35,30 @@ module Celluloid
29
35
  end
30
36
 
31
37
  # Wait for the given signal and return the associated value
32
- def wait
38
+ def wait(timeout = nil)
33
39
  raise ConditionError, "cannot wait for signals while exclusive" if Celluloid.exclusive?
34
40
 
35
- if Thread.current[:celluloid_actor]
41
+ if actor = Thread.current[:celluloid_actor]
36
42
  task = Task.current
43
+ if timeout
44
+ bt = caller
45
+ timer = actor.timers.after(timeout) do
46
+ exception = ConditionError.new("timeout after #{timeout.inspect} seconds")
47
+ exception.set_backtrace bt
48
+ task.resume exception
49
+ end
50
+ end
37
51
  else
38
52
  task = Thread.current
39
53
  end
40
- waiter = Waiter.new(self, task, Celluloid.mailbox)
54
+ waiter = Waiter.new(self, task, Celluloid.mailbox, timeout)
41
55
 
42
56
  @mutex.synchronize do
43
57
  @waiters << waiter
44
58
  end
45
59
 
46
60
  result = Celluloid.suspend :condwait, waiter
61
+ timer.cancel if timer
47
62
  raise result if result.is_a? ConditionError
48
63
  result
49
64
  end
@@ -54,7 +69,9 @@ module Celluloid
54
69
  if waiter = @waiters.shift
55
70
  waiter << SignalConditionRequest.new(waiter.task, value)
56
71
  else
57
- Logger.debug("Celluloid::Condition signaled spuriously")
72
+ Logger.with_backtrace(caller(3)) do |logger|
73
+ logger.debug("Celluloid::Condition signaled spuriously")
74
+ end
58
75
  end
59
76
  end
60
77
  end
@@ -13,6 +13,8 @@ module Celluloid
13
13
  end
14
14
  when 'mingw', 'mswin'
15
15
  @cores = Integer(ENV["NUMBER_OF_PROCESSORS"][/\d+/])
16
+ when 'freebsd'
17
+ @cores = Integer(`sysctl hw.ncpu`[/\d+/])
16
18
  else
17
19
  @cores = nil
18
20
  end
@@ -42,10 +42,11 @@ module Celluloid
42
42
 
43
43
  until message
44
44
  if timeout
45
+ # TODO: use hitimes/timers instead of Time.now
45
46
  now = Time.now
46
47
  wait_until ||= now + timeout
47
48
  wait_interval = wait_until - now
48
- return if wait_interval < 0
49
+ raise(TimeoutError, "mailbox timeout exceeded", nil) if wait_interval <= 0
49
50
  else
50
51
  wait_interval = nil
51
52
  end
@@ -0,0 +1,23 @@
1
+ module Celluloid
2
+ # Base class of all Celluloid errors
3
+ Error = Class.new(StandardError)
4
+
5
+ # Don't do Actor-like things outside Actor scope
6
+ NotActorError = Class.new(Celluloid::Error)
7
+
8
+ # Trying to do something to a dead actor
9
+ DeadActorError = Class.new(Celluloid::Error)
10
+
11
+ # A timeout occured before the given request could complete
12
+ TimeoutError = Class.new(Celluloid::Error)
13
+
14
+ # The sender made an error, not the current actor
15
+ class AbortError < Celluloid::Error
16
+ attr_reader :cause
17
+
18
+ def initialize(cause)
19
+ @cause = cause
20
+ super "caused by #{cause.inspect}: #{cause.to_s}"
21
+ end
22
+ end
23
+ end
@@ -8,7 +8,7 @@ module Celluloid
8
8
  return super unless block
9
9
 
10
10
  future = new
11
- Celluloid::ThreadHandle.new(:future) do
11
+ Celluloid::ThreadHandle.new(Celluloid.actor_system, :future) do
12
12
  begin
13
13
  call = SyncCall.new(future, :call, args)
14
14
  call.dispatch(block)
@@ -0,0 +1,41 @@
1
+ require 'set'
2
+
3
+ module Celluloid
4
+ class Handlers
5
+ def initialize
6
+ @handlers = Set.new
7
+ end
8
+
9
+ def handle(*patterns, &block)
10
+ patterns.each do |pattern|
11
+ handler = Handler.new pattern, block
12
+ @handlers << handler
13
+ end
14
+ end
15
+
16
+ # Handle incoming messages
17
+ def handle_message(message)
18
+ if handler = @handlers.find { |h| h.match(message) }
19
+ handler.call message
20
+ handler
21
+ end
22
+ end
23
+ end
24
+
25
+ # Methods blocking on a call to receive
26
+ class Handler
27
+ def initialize(pattern, block)
28
+ @pattern = pattern
29
+ @block = block
30
+ end
31
+
32
+ # Match a message with this receiver's block
33
+ def match(message)
34
+ @pattern === message
35
+ end
36
+
37
+ def call(message)
38
+ @block.call message
39
+ end
40
+ end
41
+ end
@@ -6,7 +6,6 @@ module Celluloid
6
6
  attr_accessor :max_idle
7
7
 
8
8
  def initialize
9
- @group = ThreadGroup.new
10
9
  @mutex = Mutex.new
11
10
  @threads = []
12
11
 
@@ -108,7 +107,6 @@ module Celluloid
108
107
 
109
108
  thread[:celluloid_queue] = queue
110
109
  @threads << thread
111
- @group.add(thread)
112
110
  thread
113
111
  end
114
112
 
@@ -137,7 +135,6 @@ module Celluloid
137
135
  @running = false
138
136
 
139
137
  @threads.shift.kill until @threads.empty?
140
- @group.list.each(&:kill)
141
138
  end
142
139
  end
143
140