celluloid 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -357,13 +357,43 @@ send them a value in the process:
357
357
  end
358
358
  end
359
359
 
360
- The wait_for_signal method in turn calls a method called "wait". Wait suspends
360
+ The #wait_for_signal method in turn calls a method called "wait". Wait suspends
361
361
  the running method until another method of the same object calls the "signal"
362
362
  method with the same label.
363
363
 
364
- The send_signal method of this class does just that, signaling "ponycopter"
364
+ The #send_signal method of this class does just that, signaling "ponycopter"
365
365
  with the given value. This value is returned from the original wait call.
366
366
 
367
+ Protocol Interaction
368
+ --------------------
369
+
370
+ The asynchronous message protocol Celluloid uses can be used directly to add
371
+ new behavior to actors.
372
+
373
+ To send a raw asynchronous message to an actor, use:
374
+
375
+ actor.mailbox << MyMessage.new
376
+
377
+ Methods can wait on incoming MyMessage objects using the #receive method:
378
+
379
+ class MyActor
380
+ def initialize
381
+ wait_for_my_messages!
382
+ end
383
+
384
+ def wait_for_my_messages
385
+ loop do
386
+ message = receive { |msg| msg.is_a? MyMessage }
387
+ puts "Got a MyMessage: #{message.inspect}"
388
+ end
389
+ end
390
+ end
391
+
392
+ The #receive method takes a block, and yields any incoming messages which are
393
+ received by the current actor to the block, waiting for the block to return
394
+ true. Calls to #receive sleep until a message is received which makes the
395
+ block return true, at which point the matching message is returned.
396
+
367
397
  Handling I/O with Celluloid::IO
368
398
  -------------------------------
369
399
 
@@ -456,6 +486,10 @@ Here are a few rules you can follow to keep this from happening:
456
486
  using Fibers, and why can't it be solved by a block? If you've got a really
457
487
  good reason and you're feeling lucky, knock yourself out.
458
488
 
489
+ 5. If you need to mock the behaviour of an Actor, you should mock its subject
490
+ rather than the proxy itself (#actor_subject). This ensures that any time
491
+ the subject calls methods on self, they will also be appropriately mocked.
492
+
459
493
  On Thread Safety in Ruby
460
494
  ------------------------
461
495
 
@@ -1,4 +1,6 @@
1
1
  require 'logger'
2
+ require 'thread'
3
+ require 'celluloid/fibers_are_hard'
2
4
 
3
5
  module Celluloid
4
6
  @logger = Logger.new STDERR
@@ -23,6 +25,41 @@ module Celluloid
23
25
 
24
26
  actor
25
27
  end
28
+
29
+ # Receive an asynchronous message
30
+ def receive(&block)
31
+ actor = Thread.current[:actor]
32
+ if actor
33
+ actor.receive(&block)
34
+ else
35
+ Thread.current.mailbox.receive(&block)
36
+ end
37
+ end
38
+
39
+ # Create a fiber that participates in the Celluloid protocol
40
+ def fiber(*args)
41
+ actor = Thread.current[:actor]
42
+ proxy = Thread.current[:actor_proxy]
43
+ mailbox = Thread.current[:mailbox]
44
+
45
+ Fiber.new do
46
+ Thread.current[:actor] = actor
47
+ Thread.current[:actor_proxy] = proxy
48
+ Thread.current[:mailbox] = mailbox
49
+
50
+ yield(*args)
51
+ end
52
+ end
53
+
54
+ # Resume a fiber that participates in the Celluloid protocol
55
+ def resume_fiber(fiber, value = nil)
56
+ actor = Thread.current[:actor]
57
+ if actor
58
+ actor.run_fiber fiber, value
59
+ else
60
+ fiber.resume value
61
+ end
62
+ end
26
63
  end
27
64
 
28
65
  # Class methods added to classes which include Celluloid
@@ -64,8 +101,17 @@ module Celluloid
64
101
  @exit_handler = callback.to_sym
65
102
  end
66
103
 
67
- # Obtain the exit handler method for this class
68
- def exit_handler; @exit_handler; end
104
+ # Obtain the exit handler for this actor
105
+ attr_reader :exit_handler
106
+
107
+ # Configure a custom mailbox factory
108
+ def use_mailbox(klass = nil, &block)
109
+ if block
110
+ define_method(:mailbox_factory, &block)
111
+ else
112
+ define_method(:mailbox_factory) { klass.new }
113
+ end
114
+ end
69
115
  end
70
116
 
71
117
  #
@@ -112,6 +158,18 @@ module Celluloid
112
158
  Celluloid.current_actor
113
159
  end
114
160
 
161
+ # Obtain the Ruby object the actor is wrapping. This should ONLY be used
162
+ # for a limited set of use cases like runtime metaprogramming. Interacting
163
+ # directly with the wrapped object foregoes any kind of thread safety that
164
+ # Celluloid would ordinarily provide you, and the object is guaranteed to
165
+ # be shared with at least the actor thread. Tread carefully.
166
+ def wrapped_object; self; end
167
+
168
+ # Receive an asynchronous message via the actor protocol
169
+ def receive(&block)
170
+ Celluloid.receive(&block)
171
+ end
172
+
115
173
  # Perform a blocking or computationally intensive action inside an
116
174
  # asynchronous thread pool, allowing the caller to continue processing other
117
175
  # messages in its mailbox in the meantime
@@ -151,6 +209,7 @@ require 'celluloid/core_ext'
151
209
  require 'celluloid/events'
152
210
  require 'celluloid/linking'
153
211
  require 'celluloid/mailbox'
212
+ require 'celluloid/receivers'
154
213
  require 'celluloid/registry'
155
214
  require 'celluloid/responses'
156
215
  require 'celluloid/signals'
@@ -159,6 +218,7 @@ require 'celluloid/actor'
159
218
  require 'celluloid/actor_pool'
160
219
  require 'celluloid/supervisor'
161
220
  require 'celluloid/future'
221
+ require 'celluloid/application'
162
222
 
163
223
  require 'celluloid/io'
164
224
  require 'celluloid/tcp_server'
@@ -1,18 +1,3 @@
1
- require 'thread'
2
-
3
- begin
4
- require 'fiber'
5
- rescue LoadError => ex
6
- if defined? JRUBY_VERSION
7
- raise LoadError, "Celluloid requires JRuby 1.9 mode. Please pass the --1.9 flag or set JRUBY_OPTS=--1.9"
8
- elsif defined? Rubinius
9
- # If we're on Rubinius, we can still work in 1.8 mode
10
- Fiber = Rubinius::Fiber
11
- else
12
- raise ex
13
- end
14
- end
15
-
16
1
  module Celluloid
17
2
  # Don't do Actor-like things outside Actor scope
18
3
  class NotActorError < StandardError; end
@@ -41,47 +26,67 @@ module Celluloid
41
26
  attr_reader :links
42
27
  attr_reader :mailbox
43
28
 
29
+ # Invoke a method on the given actor via its mailbox
30
+ def self.call(mailbox, meth, *args, &block)
31
+ our_mailbox = Thread.current.mailbox
32
+ call = SyncCall.new(our_mailbox, meth, args, block)
44
33
 
45
- # Wrap the given subject object with an Actor
46
- def initialize(subject)
47
- @subject = subject
48
- @mailbox = initialize_mailbox
49
- @links = Links.new
50
- @signals = Signals.new
51
- @proxy = ActorProxy.new(self, @mailbox)
52
- @running = true
53
-
54
- @thread = Pool.get
55
- @thread[:queue] << proc do
56
- initialize_thread_locals
57
- run
34
+ begin
35
+ mailbox << call
36
+ rescue MailboxError
37
+ raise DeadActorError, "attempted to call a dead actor"
58
38
  end
59
- end
60
39
 
61
- # Create the mailbox for this actor
62
- #
63
- # This implemententation is intended to be overridden in special-case
64
- # subclasses of Celluloid::Actor which use a custom mailbox
65
- def initialize_mailbox
66
- Mailbox.new
40
+ if Celluloid.actor?
41
+ # Yield to the actor scheduler, which resumes us when we get a response
42
+ response = Fiber.yield(call)
43
+ else
44
+ # Otherwise we're inside a normal thread, so block
45
+ response = our_mailbox.receive do |msg|
46
+ msg.is_a? Response and msg.call_id == call.id
47
+ end
48
+ end
49
+
50
+ response.value
67
51
  end
68
52
 
69
- # Configure thread locals for the running thread
70
- def initialize_thread_locals
71
- Thread.current[:actor] = self
72
- Thread.current[:actor_proxy] = @proxy
73
- Thread.current[:mailbox] = @mailbox
53
+ # Invoke a method asynchronously on an actor via its mailbox
54
+ def self.async(mailbox, meth, *args, &block)
55
+ our_mailbox = Thread.current.mailbox
56
+ begin
57
+ mailbox << AsyncCall.new(our_mailbox, meth, args, block)
58
+ rescue MailboxError
59
+ # Silently swallow asynchronous calls to dead actors. There's no way
60
+ # to reliably generate DeadActorErrors for async calls, so users of
61
+ # async calls should find other ways to deal with actors dying
62
+ # during an async call (i.e. linking/supervisors)
63
+ end
74
64
  end
75
65
 
76
- # Run the actor loop
77
- def run
78
- process_messages
79
- cleanup ExitEvent.new(@proxy)
80
- rescue Exception => ex
81
- @running = false
82
- handle_crash(ex)
83
- ensure
84
- Pool.put @thread
66
+ # Wrap the given subject with an Actor
67
+ def initialize(subject)
68
+ @subject = subject
69
+
70
+ if subject.respond_to? :mailbox_factory
71
+ @mailbox = subject.mailbox_factory
72
+ else
73
+ @mailbox = Celluloid::Mailbox.new
74
+ end
75
+
76
+ @links = Links.new
77
+ @signals = Signals.new
78
+ @receivers = Receivers.new
79
+ @proxy = ActorProxy.new(@mailbox)
80
+ @running = true
81
+ @pending_calls = {}
82
+
83
+ @thread = Pool.get do
84
+ Thread.current[:actor] = self
85
+ Thread.current[:actor_proxy] = @proxy
86
+ Thread.current[:mailbox] = @mailbox
87
+
88
+ run
89
+ end
85
90
  end
86
91
 
87
92
  # Is this actor alive?
@@ -105,56 +110,72 @@ module Celluloid
105
110
  @signals.wait name
106
111
  end
107
112
 
108
- #######
109
- private
110
- #######
111
-
112
- # Process incoming messages
113
- def process_messages
114
- pending_calls = {}
113
+ # Receive an asynchronous message
114
+ def receive(&block)
115
+ @receivers.receive(&block)
116
+ end
115
117
 
118
+ # Run the actor loop
119
+ def run
116
120
  while @running
117
121
  begin
118
122
  message = @mailbox.receive
119
- rescue MailboxShutdown
120
- # If the mailbox detects shutdown, exit the actor
121
- @running = false
122
123
  rescue ExitEvent => exit_event
123
- fiber = Fiber.new do
124
- initialize_thread_locals
125
- handle_exit_event exit_event
126
- end
127
-
128
- call = fiber.resume
129
- pending_calls[call] = fiber if fiber.alive?
130
-
124
+ fiber = Celluloid.fiber { handle_exit_event exit_event; nil }
125
+ run_fiber fiber
131
126
  retry
132
127
  end
133
128
 
134
- case message
135
- when Call
136
- fiber = Fiber.new do
137
- initialize_thread_locals
138
- message.dispatch(@subject)
139
- end
140
-
141
- call = fiber.resume
142
- pending_calls[call] = fiber if fiber.alive?
143
- when Response
144
- fiber = pending_calls.delete(message.call)
145
-
146
- if fiber
147
- call = fiber.resume message
148
- pending_calls[call] = fiber if fiber.alive?
149
- end
150
- end # unexpected messages are ignored
129
+ handle_message message
151
130
  end
131
+
132
+ cleanup ExitEvent.new(@proxy)
133
+ rescue MailboxShutdown
134
+ # If the mailbox detects shutdown, exit the actor
135
+ @running = false
136
+ rescue Exception => ex
137
+ @running = false
138
+ handle_crash(ex)
139
+ ensure
140
+ Pool.put @thread
141
+ end
142
+
143
+ # Run a method, handling when its Fiber is suspended
144
+ def run_fiber(fiber, value = nil)
145
+ result = fiber.resume value
146
+ if result.is_a? Celluloid::Call
147
+ @pending_calls[result.id] = fiber if fiber.alive?
148
+ elsif result
149
+ warning = "non-call returned from fiber: #{result.class}"
150
+ Celluloid.logger.debug warning if Celluloid.logger
151
+ end
152
+ nil
153
+ end
154
+
155
+ # Handle an incoming message
156
+ def handle_message(message)
157
+ case message
158
+ when Call
159
+ fiber = Celluloid.fiber { message.dispatch(@subject); nil }
160
+ run_fiber fiber
161
+ when Response
162
+ fiber = @pending_calls.delete(message.call_id)
163
+
164
+ if fiber
165
+ run_fiber fiber, message
166
+ else
167
+ warning = "spurious response to call #{message.call_id}"
168
+ Celluloid.logger.debug if Celluloid.logger
169
+ end
170
+ else
171
+ @receivers.handle_message(message)
172
+ end
173
+ message
152
174
  end
153
175
 
154
176
  # Handle exit events received by this actor
155
177
  def handle_exit_event(exit_event)
156
- klass = @subject.class
157
- exit_handler = klass.exit_handler if klass.respond_to? :exit_handler
178
+ exit_handler = @subject.class.exit_handler
158
179
  if exit_handler
159
180
  return @subject.send(exit_handler, exit_event.actor, exit_event.reason)
160
181
  end
@@ -176,6 +197,12 @@ module Celluloid
176
197
  def cleanup(exit_event)
177
198
  @mailbox.shutdown
178
199
  @links.send_event exit_event
200
+
201
+ begin
202
+ @subject.finalize if @subject.respond_to? :finalize
203
+ rescue Exception => finalizer_exception
204
+ log_error(finalizer_exception, "#{@subject.class}#finalize crashed!")
205
+ end
179
206
  end
180
207
 
181
208
  # Log errors when an actor crashes
@@ -11,20 +11,23 @@ module Celluloid
11
11
  class << self
12
12
  attr_accessor :max_idle
13
13
 
14
- def get
14
+ def get(&block)
15
15
  @lock.synchronize do
16
16
  if @pool.empty?
17
- create
17
+ thread = create
18
18
  else
19
- @pool.shift
19
+ thread = @pool.shift
20
20
  end
21
+
22
+ thread[:queue] << block
23
+ thread
21
24
  end
22
25
  end
23
26
 
24
27
  def put(thread)
25
28
  @lock.synchronize do
26
29
  if @pool.size >= @max_idle
27
- thread.kill
30
+ thread[:queue] << nil
28
31
  else
29
32
  @pool << thread
30
33
  end
@@ -35,13 +38,10 @@ module Celluloid
35
38
  queue = Queue.new
36
39
  thread = Thread.new do
37
40
  begin
38
- while true
39
- queue.pop.call
41
+ while func = queue.pop
42
+ func.call
40
43
  end
41
44
  rescue Exception => ex
42
- # Rubinius hax
43
- raise if defined?(Thread::Die) and ex.is_a? Thread::Die
44
-
45
45
  message = "Celluloid::Actor::Pool internal failure:\n"
46
46
  message << "#{ex.class}: #{ex.to_s}\n"
47
47
  message << ex.backtrace.join("\n")
@@ -3,41 +3,40 @@ module Celluloid
3
3
  # dispatches calls and casts to normal Ruby objects which are running inside
4
4
  # of their own threads.
5
5
  class ActorProxy
6
- # FIXME: not nearly enough methods are delegated here
7
6
  attr_reader :mailbox
8
7
 
9
- def initialize(actor, mailbox)
10
- @actor, @mailbox = actor, mailbox
8
+ def initialize(mailbox, klass = "Object")
9
+ @mailbox, @klass = mailbox, klass
11
10
  end
12
11
 
13
12
  def send(meth, *args, &block)
14
- __call :send, meth, *args, &block
13
+ Actor.call @mailbox, :send, meth, *args, &block
15
14
  end
16
15
 
17
16
  def respond_to?(meth)
18
- __call :respond_to?, meth
17
+ Actor.call @mailbox, :respond_to?, meth
19
18
  end
20
19
 
21
20
  def methods(include_ancestors = true)
22
- __call :methods, include_ancestors
21
+ Actor.call @mailbox, :methods, include_ancestors
23
22
  end
24
23
 
25
24
  def alive?
26
- @actor.alive?
25
+ @mailbox.alive?
27
26
  end
28
27
 
29
28
  def to_s
30
- __call :to_s
29
+ Actor.call @mailbox, :to_s
31
30
  end
32
31
 
33
32
  def inspect
34
- return __call :inspect if alive?
35
- "#<Celluloid::Actor(#{@actor.class}:0x#{@actor.object_id.to_s(16)}) dead>"
33
+ "#<Celluloid::Actor(#{@klass}) dead>" unless alive?
34
+ Actor.call @mailbox, :inspect
36
35
  end
37
36
 
38
37
  # Create a Celluloid::Future which calls a given method
39
38
  def future(method_name, *args, &block)
40
- Celluloid::Future.new { __call method_name, *args, &block }
39
+ Celluloid::Future.new { Actor.call @mailbox, method_name, *args, &block }
41
40
  end
42
41
 
43
42
  # Terminate the associated actor
@@ -61,64 +60,11 @@ module Celluloid
61
60
  # bang methods are async calls
62
61
  if meth.to_s.match(/!$/)
63
62
  unbanged_meth = meth.to_s.sub(/!$/, '')
64
- our_mailbox = Thread.current.mailbox
65
-
66
- begin
67
- @mailbox << AsyncCall.new(our_mailbox, unbanged_meth, args, block)
68
- rescue MailboxError
69
- # Silently swallow asynchronous calls to dead actors. There's no way
70
- # to reliably generate DeadActorErrors for async calls, so users of
71
- # async calls should find other ways to deal with actors dying
72
- # during an async call (i.e. linking/supervisors)
73
- end
74
-
75
- return # casts are async and return immediately
63
+ Actor.async @mailbox, unbanged_meth, *args, &block
64
+ return
76
65
  end
77
66
 
78
- __call(meth, *args, &block)
79
- end
80
-
81
- #######
82
- private
83
- #######
84
-
85
- # Make a synchronous call to the actor we're proxying to
86
- def __call(meth, *args, &block)
87
- our_mailbox = Thread.current.mailbox
88
- call = SyncCall.new(our_mailbox, meth, args, block)
89
-
90
- begin
91
- @mailbox << call
92
- rescue MailboxError
93
- raise DeadActorError, "attempted to call a dead actor"
94
- end
95
-
96
- if Celluloid.actor?
97
- # Yield to the actor scheduler, which resumes us when we get a response
98
- response = Fiber.yield(call)
99
- else
100
- # Otherwise we're inside a normal thread, so block
101
- response = our_mailbox.receive do |msg|
102
- msg.is_a? Response and msg.call == call
103
- end
104
- end
105
-
106
- case response
107
- when SuccessResponse
108
- response.value
109
- when ErrorResponse
110
- ex = response.value
111
-
112
- if ex.is_a? AbortError
113
- # Aborts are caused by caller error, so ensure they capture the
114
- # caller's backtrace instead of the receiver's
115
- raise ex.cause.class.new(ex.cause.message)
116
- else
117
- raise ex
118
- end
119
- else
120
- raise "don't know how to handle #{response.class} messages!"
121
- end
67
+ Actor.call @mailbox, meth, *args, &block
122
68
  end
123
69
  end
124
70
  end
@@ -0,0 +1,78 @@
1
+ module Celluloid
2
+ # Applications describe and manage networks of Celluloid actors
3
+ class Application
4
+ include Celluloid
5
+ trap_exit :restart_supervisor
6
+
7
+ class << self
8
+ # Actors or sub-applications to be supervised
9
+ def supervisables
10
+ @supervisables ||= []
11
+ end
12
+
13
+ # Start this application (and watch it with a supervisor)
14
+ alias_method :run!, :supervise
15
+
16
+ # Run the application in the foreground with a simple watchdog
17
+ def run
18
+ loop do
19
+ supervisor = run!
20
+
21
+ # Take five, toplevel supervisor
22
+ sleep 5 while supervisor.alive?
23
+
24
+ Celluloid.logger.error "!!! Celluloid::Application #{self} crashed. Restarting..."
25
+ end
26
+ end
27
+
28
+ # Register an actor class or a sub-application class to be launched and
29
+ # supervised while this application is running. Available options are:
30
+ #
31
+ # * as: register this application in the Celluloid::Actor[] directory
32
+ # * args: start the actor with the given arguments
33
+ def supervise(klass, options = {})
34
+ supervisables << Supervisable.new(klass, options)
35
+ end
36
+ end
37
+
38
+ # Start the application
39
+ def initialize
40
+ @supervisors = {}
41
+
42
+ # This is some serious lolcode, but like... start the supervisors for
43
+ # this application
44
+ self.class.supervisables.each do |supervisable|
45
+ supervisor = supervisable.supervise
46
+ @supervisors[supervisor] = supervisable
47
+ end
48
+ end
49
+
50
+ # Restart a crashed supervisor
51
+ def restart_supervisor(supervisor, reason)
52
+ supervisable = @supervisors.delete supervisor
53
+ raise "a supervisable went missing. This shouldn't be!" unless supervisable
54
+
55
+ supervisor = supervisable.supervise
56
+ @supervisors[supervisor] = supervisable
57
+ end
58
+
59
+ # A subcomponent of an application to be supervised
60
+ class Supervisable
61
+ attr_reader :klass, :as, :args
62
+
63
+ def initialize(klass, options = {})
64
+ @klass = klass
65
+
66
+ # Stringify keys :/
67
+ options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
68
+
69
+ @as = options['as']
70
+ @args = options['args'] || []
71
+ end
72
+
73
+ def supervise
74
+ Supervisor.new_link(@as, @klass, *@args)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,17 +1,18 @@
1
1
  module Celluloid
2
2
  # Calls represent requests to an actor
3
3
  class Call
4
- attr_reader :caller, :method, :arguments, :block
5
-
4
+ attr_reader :id, :caller, :method, :arguments, :block
5
+
6
6
  def initialize(caller, method, arguments, block)
7
+ @id = object_id # memoize object ID for serialization
7
8
  @caller, @method, @arguments, @block = caller, method, arguments, block
8
9
  end
9
-
10
+
10
11
  def check_signature(obj)
11
12
  unless obj.respond_to? @method
12
13
  raise NoMethodError, "undefined method `#{@method}' for #{obj.inspect}"
13
14
  end
14
-
15
+
15
16
  arity = obj.method(@method).arity
16
17
  if arity >= 0
17
18
  if arguments.size != arity
@@ -25,24 +26,24 @@ module Celluloid
25
26
  end
26
27
  end
27
28
  end
28
-
29
+
29
30
  # Synchronous calls wait for a response
30
31
  class SyncCall < Call
31
32
  def dispatch(obj)
32
33
  begin
33
34
  check_signature(obj)
34
35
  rescue Exception => ex
35
- respond ErrorResponse.new(self, AbortError.new(ex))
36
+ respond ErrorResponse.new(@id, AbortError.new(ex))
36
37
  return
37
38
  end
38
-
39
+
39
40
  begin
40
41
  result = obj.send @method, *@arguments, &@block
41
42
  rescue Exception => exception
42
43
  # Exceptions that occur during synchronous calls are reraised in the
43
44
  # context of the caller
44
- respond ErrorResponse.new(self, exception)
45
-
45
+ respond ErrorResponse.new(@id, exception)
46
+
46
47
  if exception.is_a? AbortError
47
48
  # Aborting indicates a protocol error on the part of the caller
48
49
  # It should crash the caller, but the exception isn't reraised
@@ -52,43 +53,48 @@ module Celluloid
52
53
  raise exception
53
54
  end
54
55
  end
55
-
56
- respond SuccessResponse.new(self, result)
57
- true
56
+
57
+ respond SuccessResponse.new(@id, result)
58
58
  end
59
-
59
+
60
60
  def cleanup
61
61
  exception = DeadActorError.new("attempted to call a dead actor")
62
- respond ErrorResponse.new(self, exception)
62
+ respond ErrorResponse.new(@id, exception)
63
63
  end
64
-
64
+
65
65
  #######
66
66
  private
67
67
  #######
68
-
68
+
69
69
  def respond(message)
70
70
  @caller << message
71
71
  rescue MailboxError
72
72
  # It's possible the caller exited or crashed before we could send a
73
73
  # response to them.
74
- end
74
+ end
75
75
  end
76
-
76
+
77
77
  # Asynchronous calls don't wait for a response
78
78
  class AsyncCall < Call
79
79
  def dispatch(obj)
80
80
  begin
81
81
  check_signature(obj)
82
82
  rescue Exception => ex
83
- obj.__log_error ex, "#{obj.class}: async call failed!"
83
+ log_error ex, "#{obj.class}: async call failed!"
84
84
  return
85
85
  end
86
-
86
+
87
87
  obj.send(@method, *@arguments, &@block)
88
88
  rescue AbortError => ex
89
89
  # Swallow aborted async calls, as they indicate the caller made a mistake
90
- obj.__log_error ex, "#{obj.class}: async call aborted!"
90
+ obj.log_error ex, "#{obj.class}: async call aborted!"
91
+ end
92
+
93
+ def log_error(ex, message)
94
+ message << "\n#{ex.class}: #{ex.to_s}\n"
95
+ message << ex.backtrace.join("\n")
96
+ Celluloid.logger.error message if Celluloid.logger
91
97
  end
92
98
  end
93
99
  end
94
-
100
+
@@ -0,0 +1,33 @@
1
+ # Let's go shopping!
2
+ begin
3
+ require 'fiber'
4
+ rescue LoadError => ex
5
+ if defined? JRUBY_VERSION
6
+ if RUBY_VERSION < "1.9.2"
7
+ raise LoadError, "Celluloid requires JRuby 1.9 mode. Please pass the --1.9 flag or set JRUBY_OPTS=--1.9"
8
+ end
9
+
10
+ # Fibers are broken on JRuby 1.6.5. This works around the issue
11
+ if JRUBY_VERSION == "1.6.5"
12
+ require 'jruby'
13
+ org.jruby.ext.fiber.FiberExtLibrary.new.load(JRuby.runtime, false)
14
+ class org::jruby::ext::fiber::ThreadFiber
15
+ field_accessor :state
16
+ end
17
+
18
+ class Fiber
19
+ def alive?
20
+ JRuby.reference(self).state != org.jruby.ext.fiber.ThreadFiberState::FINISHED
21
+ end
22
+ end
23
+ else
24
+ # Just in case subsequent JRuby releases have broken fibers :/
25
+ raise ex
26
+ end
27
+ elsif defined? Rubinius
28
+ # If we're on Rubinius, we can still work in 1.8 mode
29
+ Fiber = Rubinius::Fiber
30
+ else
31
+ raise ex
32
+ end
33
+ end
@@ -1,41 +1,15 @@
1
1
  require 'celluloid/io/waker'
2
2
  require 'celluloid/io/reactor'
3
3
  require 'celluloid/io/mailbox'
4
- require 'celluloid/io/actor'
5
4
 
6
5
  module Celluloid
7
6
  # Actors which can run alongside other I/O operations
8
7
  module IO
9
8
  def self.included(klass)
10
9
  klass.send :include, ::Celluloid
11
- klass.send :extend, IO::ClassMethods
10
+ klass.use_mailbox Celluloid::IO::Mailbox
12
11
  end
13
12
 
14
- # Class methods added to classes which include Celluloid::IO
15
- module ClassMethods
16
- # Create a new actor
17
- def new(*args, &block)
18
- proxy = IO::Actor.new(allocate).proxy
19
- proxy.send(:initialize, *args, &block)
20
- proxy
21
- end
22
-
23
- # Create a new actor and link to the current one
24
- def new_link(*args, &block)
25
- current_actor = Thread.current[:actor]
26
- raise NotActorError, "can't link outside actor context" unless current_actor
27
-
28
- proxy = IO::Actor.new(allocate).proxy
29
- current_actor.link proxy
30
- proxy.send(:initialize, *args, &block)
31
- proxy
32
- end
33
- end
34
-
35
- #
36
- # Instance methods
37
- #
38
-
39
13
  # Wait for the given IO object to become readable
40
14
  def wait_readable(io, &block)
41
15
  # Law of demeter be damned!
@@ -4,7 +4,7 @@ module Celluloid
4
4
  module IO
5
5
  # An alternative implementation of Celluloid::Mailbox using Wakers
6
6
  class Mailbox < Celluloid::Mailbox
7
- attr_reader :reactor
7
+ attr_reader :reactor, :waker
8
8
 
9
9
  def initialize
10
10
  @messages = []
@@ -50,7 +50,7 @@ module Celluloid
50
50
  end until message
51
51
 
52
52
  message
53
- rescue DeadWakerError
53
+ rescue IOError, DeadWakerError
54
54
  shutdown # force shutdown of the mailbox
55
55
  raise MailboxShutdown, "mailbox shutdown called during receive"
56
56
  end
@@ -32,7 +32,7 @@ module Celluloid
32
32
  [[readers, @readers], [writers, @writers]].each do |ios, registered|
33
33
  ios.each do |io|
34
34
  fiber = registered.delete io
35
- fiber.resume if fiber
35
+ Celluloid.resume_fiber(fiber) if fiber
36
36
  end
37
37
  end
38
38
  end
@@ -50,7 +50,7 @@ module Celluloid
50
50
  def inspect
51
51
  @lock.synchronize do
52
52
  links = @links.to_a.map { |l| "#{l.class}:#{l.object_id}" }.join(',')
53
- "#<Celluloid::Links[#{links}]>"
53
+ "#<#{self.class}[#{links}]>"
54
54
  end
55
55
  end
56
56
  end
@@ -86,6 +86,11 @@ module Celluloid
86
86
  true
87
87
  end
88
88
 
89
+ # Is the mailbox alive?
90
+ def alive?
91
+ !@dead
92
+ end
93
+
89
94
  # Cast to an array
90
95
  def to_a
91
96
  @lock.synchronize { @messages.dup }
@@ -98,7 +103,7 @@ module Celluloid
98
103
 
99
104
  # Inspect the contents of the Mailbox
100
105
  def inspect
101
- "#<Celluloid::Mailbox:#{object_id} [#{map { |m| m.inspect }.join(', ')}]>"
106
+ "#<#{self.class}:#{object_id.to_s(16)} @messages=[#{map { |m| m.inspect }.join(', ')}]>"
102
107
  end
103
108
  end
104
109
  end
@@ -0,0 +1,36 @@
1
+ module Celluloid
2
+ # Allow methods to directly interact with the actor protocol
3
+ class Receivers
4
+ def initialize
5
+ @handlers = []
6
+ end
7
+
8
+ # Receive an asynchronous message
9
+ def receive(&block)
10
+ raise ArgumentError, "receive must be given a block" unless block
11
+
12
+ @handlers << [Fiber.current, block]
13
+ Fiber.yield
14
+ end
15
+
16
+ # Handle incoming messages
17
+ def handle_message(message)
18
+ handler = nil
19
+
20
+ @handlers.each_with_index do |(fiber, block), index|
21
+ if block.call(message)
22
+ handler = index
23
+ break
24
+ end
25
+ end
26
+
27
+ if handler
28
+ fiber, _ = @handlers.delete_at handler
29
+ Celluloid.resume_fiber fiber, message
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,10 +1,10 @@
1
1
  module Celluloid
2
2
  # Responses to calls
3
3
  class Response
4
- attr_reader :call, :value
4
+ attr_reader :call_id, :value
5
5
 
6
- def initialize(call, value)
7
- @call, @value = call, value
6
+ def initialize(call_id, value)
7
+ @call_id, @value = call_id, value
8
8
  end
9
9
  end
10
10
 
@@ -12,5 +12,15 @@ module Celluloid
12
12
  class SuccessResponse < Response; end
13
13
 
14
14
  # Call was aborted due to caller error
15
- class ErrorResponse < Response; end
15
+ class ErrorResponse < Response
16
+ def value
17
+ if super.is_a? AbortError
18
+ # Aborts are caused by caller error, so ensure they capture the
19
+ # caller's backtrace instead of the receiver's
20
+ raise super.cause.class.new(super.cause.message)
21
+ else
22
+ raise super
23
+ end
24
+ end
25
+ end
16
26
  end
@@ -18,7 +18,9 @@ module Celluloid
18
18
  fibers = @waiting.delete name
19
19
  return unless fibers
20
20
 
21
- fibers.each { |fiber| fiber.resume value }
21
+ fibers.each do |fiber|
22
+ Celluloid.resume_fiber fiber, value
23
+ end
22
24
  true
23
25
  end
24
26
  end
@@ -4,37 +4,50 @@ module Celluloid
4
4
  class Supervisor
5
5
  include Celluloid
6
6
  trap_exit :restart_actor
7
-
7
+
8
8
  # Retrieve the actor this supervisor is supervising
9
9
  attr_reader :actor
10
-
10
+
11
11
  def self.supervise(klass, *args, &block)
12
12
  new(nil, klass, *args, &block)
13
13
  end
14
-
14
+
15
15
  def self.supervise_as(name, klass, *args, &block)
16
16
  new(name, klass, *args, &block)
17
17
  end
18
-
18
+
19
19
  def initialize(name, klass, *args, &block)
20
20
  @name, @klass, @args, @block = name, klass, args, block
21
21
  start_actor
22
22
  end
23
-
24
- def start_actor
25
- @actor = @klass.new_link(*@args, &@block)
23
+
24
+ def start_actor(start_attempts = 2, sleep_interval = 30)
25
+ failures = 0
26
+
27
+ begin
28
+ @actor = @klass.new_link(*@args, &@block)
29
+ rescue
30
+ failures += 1
31
+ if failures >= start_attempts
32
+ failures = 0
33
+ Celluloid.logger.warn "#{@klass} is crashing on initialize repeatedly, sleeping for #{sleep_interval} seconds"
34
+ sleep sleep_interval
35
+ end
36
+ retry
37
+ end
38
+
26
39
  Celluloid::Actor[@name] = @actor if @name
27
40
  end
28
-
41
+
29
42
  # When actors die, regardless of the reason, restart them
30
43
  def restart_actor(actor, reason)
31
44
  start_actor
32
45
  end
33
-
46
+
34
47
  def inspect
35
- str = "#<Celluloid::Supervisor(#{@klass})"
48
+ str = "#<#{self.class}(#{@klass}):0x#{object_id.to_s(16)}"
36
49
  str << " " << @args.map { |arg| arg.inspect }.join(' ') unless @args.empty?
37
50
  str << ">"
38
51
  end
39
52
  end
40
- end
53
+ end
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  def self.version; VERSION; end
4
4
  end
metadata CHANGED
@@ -1,38 +1,38 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: celluloid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
5
- prerelease:
4
+ prerelease:
5
+ version: 0.6.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Tony Arcieri
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-25 00:00:00.000000000Z
12
+ date: 2011-11-19 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70182211411320 !ruby/object:Gem::Requirement
17
- none: false
16
+ version_requirements: &2056 !ruby/object:Gem::Requirement
18
17
  requirements:
19
18
  - - ! '>='
20
19
  - !ruby/object:Gem::Version
21
20
  version: '0'
22
- type: :development
21
+ none: false
22
+ requirement: *2056
23
23
  prerelease: false
24
- version_requirements: *70182211411320
24
+ type: :development
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70182211410760 !ruby/object:Gem::Requirement
28
- none: false
27
+ version_requirements: &2074 !ruby/object:Gem::Requirement
29
28
  requirements:
30
29
  - - ! '>='
31
30
  - !ruby/object:Gem::Version
32
31
  version: 2.7.0
33
- type: :development
32
+ none: false
33
+ requirement: *2074
34
34
  prerelease: false
35
- version_requirements: *70182211410760
35
+ type: :development
36
36
  description: Celluloid is a concurrent object framework inspired by the Actor Model
37
37
  email:
38
38
  - tony@medioh.com
@@ -41,20 +41,20 @@ extensions: []
41
41
  extra_rdoc_files: []
42
42
  files:
43
43
  - README.md
44
+ - lib/celluloid.rb
44
45
  - lib/celluloid/actor.rb
45
46
  - lib/celluloid/actor_pool.rb
46
47
  - lib/celluloid/actor_proxy.rb
48
+ - lib/celluloid/application.rb
47
49
  - lib/celluloid/calls.rb
48
50
  - lib/celluloid/core_ext.rb
49
51
  - lib/celluloid/events.rb
52
+ - lib/celluloid/fibers_are_hard.rb
50
53
  - lib/celluloid/future.rb
51
- - lib/celluloid/io/actor.rb
52
- - lib/celluloid/io/mailbox.rb
53
- - lib/celluloid/io/reactor.rb
54
- - lib/celluloid/io/waker.rb
55
54
  - lib/celluloid/io.rb
56
55
  - lib/celluloid/linking.rb
57
56
  - lib/celluloid/mailbox.rb
57
+ - lib/celluloid/receivers.rb
58
58
  - lib/celluloid/registry.rb
59
59
  - lib/celluloid/responses.rb
60
60
  - lib/celluloid/rspec.rb
@@ -62,31 +62,34 @@ files:
62
62
  - lib/celluloid/supervisor.rb
63
63
  - lib/celluloid/tcp_server.rb
64
64
  - lib/celluloid/version.rb
65
- - lib/celluloid.rb
65
+ - lib/celluloid/io/mailbox.rb
66
+ - lib/celluloid/io/reactor.rb
67
+ - lib/celluloid/io/waker.rb
66
68
  homepage: https://github.com/tarcieri/celluloid
67
69
  licenses:
68
70
  - MIT
69
- post_install_message:
71
+ post_install_message:
70
72
  rdoc_options: []
71
73
  require_paths:
72
74
  - lib
73
75
  required_ruby_version: !ruby/object:Gem::Requirement
74
- none: false
75
76
  requirements:
76
77
  - - ! '>='
77
78
  - !ruby/object:Gem::Version
78
79
  version: '0'
79
- required_rubygems_version: !ruby/object:Gem::Requirement
80
80
  none: false
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
82
  requirements:
82
83
  - - ! '>='
83
84
  - !ruby/object:Gem::Version
84
85
  version: 1.3.6
86
+ none: false
85
87
  requirements: []
86
- rubyforge_project:
87
- rubygems_version: 1.8.10
88
- signing_key:
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.9
90
+ signing_key:
89
91
  specification_version: 3
90
92
  summary: Celluloid is a concurrent object framework inspired by the Actor Model
91
93
  test_files: []
92
- has_rdoc:
94
+ has_rdoc:
95
+ ...
@@ -1,10 +0,0 @@
1
- module Celluloid
2
- module IO
3
- class Actor < Celluloid::Actor
4
- # Use the special Celluloid::IO::Mailbox to handle incoming requests
5
- def initialize_mailbox
6
- Celluloid::IO::Mailbox.new
7
- end
8
- end
9
- end
10
- end