celluloid 0.5.0 → 0.6.0

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