celluloid 0.15.2 → 0.16.0.pre

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