celluloid 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
- ![Celluloid](https://github.com/tarcieri/celluloid/raw/master/logo.png)
1
+ ![Celluloid](https://github.com/celluloid/celluloid/raw/master/logo.png)
2
2
  =========
3
- [![Build Status](https://secure.travis-ci.org/tarcieri/celluloid.png?branch=master)](http://travis-ci.org/tarcieri/celluloid)
4
- [![Dependency Status](https://gemnasium.com/tarcieri/celluloid.png)](https://gemnasium.com/tarcieri/celluloid)
3
+ [![Build Status](https://secure.travis-ci.org/celluloid/celluloid.png?branch=master)](http://travis-ci.org/celluloid/celluloid)
4
+ [![Dependency Status](https://gemnasium.com/celluloid/celluloid.png)](https://gemnasium.com/celluloid/celluloid)
5
5
 
6
6
  > "I thought of objects being like biological cells and/or individual
7
7
  > computers on a network, only able to communicate with messages"
@@ -11,41 +11,62 @@ Celluloid provides a simple and natural way to build fault-tolerant concurrent
11
11
  programs in Ruby. With Celluloid, you can build systems out of concurrent
12
12
  objects just as easily as you build sequential programs out of regular objects.
13
13
  Recommended for any developer, including novices, Celluloid should help ease
14
- your worries about building multithreaded Ruby programs:
15
-
16
- * __Automatic synchronization:__ Celluloid synchronizes access to instance
17
- variables by using a special proxy object system and messaging model.
18
- * __[Futures](https://github.com/tarcieri/celluloid/wiki/futures):__
14
+ your worries about building multithreaded Ruby programs.
15
+
16
+ Much of the difficulty with building concurrent programs in Ruby arises because
17
+ the object-oriented mechanisms for structuring code, such as classes and
18
+ inheritance, are separate from the concurrency mechanisms, such as threads and
19
+ locks. Celluloid combines these into a single structure, an active object
20
+ running within a thread, called an "actor".
21
+
22
+ By combining concurrency with object oriented programming, Celluloid frees you
23
+ up from worry about where to use threads and locks. Celluloid combines them
24
+ together into a single concurrent object oriented programming model,
25
+ encapsulating state in concurrent objects and thus avoiding many of the
26
+ problems associated with multithreaded programming. Celluloid provides many
27
+ features which make concurrent programming simple, easy, and fun:
28
+
29
+ * __Automatic "deadlock-free" synchronization:__ Celluloid uses a concurrent
30
+ object model which combines method dispatch and thread synchronization.
31
+ Each actor is a concurrent object running in its own thread, and every method
32
+ invocation is wrapped in a fiber that can be suspended whenever it calls
33
+ out to other actors, and resumed when the response is available. This means
34
+ methods which are waiting for responses from other actors, external messages,
35
+ or other system events (including I/O with Celluloid::IO) can be suspended
36
+ and will never block other methods that are ready to run. This won't prevent
37
+ bugs in Celluloid, bugs in other thread-safe libraries you use, and even
38
+ certain "dangerous" features of Celluloid from causing your program to
39
+ deadlock, but in general, programs built with Celluloid will be naturally
40
+ immune to deadlocks.
41
+
42
+ * __Fault-tolerance:__ Celluloid has taken to heart many of Erlang's ideas
43
+ about fault-tolerance in order to enable self-healing applications.
44
+ The central idea: have you tried turning it off and on again? Celluloid
45
+ takes care of rebooting subcomponents of your application when they crash,
46
+ whether it's a single actor, or large (potentially multi-tiered) groups of
47
+ actors that are all interdependent. This means rather that worrying about
48
+ rescuing every last exception, you can just sit back, relax, and let parts
49
+ of your program crash, knowing Celluloid will automatically reboot them in
50
+ a clean state. Celluloid provides its own implementation of the core
51
+ fault-tolerance concepts in Erlang including [linking](https://github.com/celluloid/celluloid/wiki/Linking),
52
+ [supervisors](https://github.com/celluloid/celluloid/wiki/Supervisors),
53
+ and [supervision trees](https://github.com/celluloid/celluloid/wiki/Groups).
54
+
55
+ * __[Futures](https://github.com/celluloid/celluloid/wiki/futures):__
19
56
  Ever wanted to call a method "in the background" and retrieve the
20
- value it returns later? Celluloid futures allow you to do that. When you
21
- ask for a method's return value it's returned if it's immediately available
22
- or blocks if the method is still running.
23
- * __[Supervisors](https://github.com/tarcieri/celluloid/wiki/supervisors):__
24
- Celluloid can monitor your concurrent objects and
25
- automatically restart them when they crash. You can also link concurrent
26
- objects together into groups that will crash and restart as a group,
27
- ensuring that after a crash all interdependent objects are in a clean and
28
- consistent state.
29
-
30
- Under the hood, Celluloid wraps regular objects in threads that talk to each
31
- other using messages. These concurrent objects are called "actors". When a
32
- caller wants another actor to execute a method, it literally sends it a
33
- message object telling it what method to execute. The receiver listens on its
34
- mailbox, gets the request, runs the method, and sends the caller the result.
35
- The receiver processes messages in its inbox one-at-a-time, which means that
36
- you don't need to worry about synchronizing access to an object's instance
37
- variables.
38
-
39
- In addition to that, Celluloid also gives you the ability to call methods
40
- _asynchronously_, so the receiver to do things in the background for you
41
- without the caller having to sit around waiting for the result.
57
+ value it returns later? Celluloid futures do just that. It's like
58
+ calling ahead to a restaurant to place an order, so they can work
59
+ on preparing your food while you're on your way to pick it up.
60
+ When you ask for a method's return value, it's returned immediately
61
+ if the method has already completed, or otherwise the current method is
62
+ suspended until the value becomes available.
42
63
 
43
64
  You can also build distributed systems with Celluloid using its
44
- [sister project DCell](https://github.com/tarcieri/dcell). Evented IO similar
45
- to EventMachine (albeit with a synchronous API) is available through the
46
- [Celluloid::IO](https://github.com/tarcieri/celluloid-io) library.
65
+ [sister project DCell](https://github.com/celluloid/dcell). Evented IO similar
66
+ to EventMachine (with a synchronous API) is available through the
67
+ [Celluloid::IO](https://github.com/celluloid/celluloid-io) library.
47
68
 
48
- [Please see the Celluloid Wiki](https://github.com/tarcieri/celluloid/wiki)
69
+ [Please see the Celluloid Wiki](https://github.com/celluloid/celluloid/wiki)
49
70
  for more detailed documentation and usage notes.
50
71
 
51
72
  Like Celluloid? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
@@ -54,94 +75,21 @@ or visit us on IRC at #celluloid on freenode
54
75
  Supported Platforms
55
76
  -------------------
56
77
 
57
- Celluloid works on Ruby 1.9.2+, JRuby 1.6 (in 1.9 mode), and Rubinius 2.0. JRuby
78
+ Celluloid works on Ruby 1.9.3, JRuby 1.6 (in 1.9 mode), and Rubinius 2.0. JRuby
58
79
  or Rubinius are the preferred platforms as they support true hardware-level
59
- parallelism when running Ruby code, whereas MRI/YARV is constrained by a global
60
- interpreter lock (GIL).
80
+ parallelism for Ruby threads, whereas MRI/YARV is constrained by a global
81
+ interpreter lock (GIL) and can only execute one thread at a time.
61
82
 
62
83
  To use JRuby in 1.9 mode, you'll need to pass the "--1.9" command line option
63
84
  to the JRuby executable, or set the "JRUBY_OPTS=--1.9" environment variable.
64
85
 
65
86
  Celluloid works on Rubinius in either 1.8 or 1.9 mode.
66
87
 
67
- Usage
68
- -----
69
-
70
- To use Celluloid, define a normal Ruby class that includes Celluloid:
71
-
72
- ```ruby
73
- require 'celluloid'
74
-
75
- class Sheen
76
- include Celluloid
77
-
78
- def initialize(name)
79
- @name = name
80
- end
81
-
82
- def set_status(status)
83
- @status = status
84
- end
85
-
86
- def report
87
- "#{@name} is #{@status}"
88
- end
89
- end
90
- ```
91
-
92
- Now when you create new instances of this class, they're actually concurrent
93
- objects, each running in their own thread:
94
-
95
- ```ruby
96
- >> charlie = Sheen.new "Charlie Sheen"
97
- => #<Celluloid::Actor(Sheen:0x00000100a312d0) @name="Charlie Sheen">
98
- >> charlie.set_status "winning!"
99
- => "winning!"
100
- >> charlie.report
101
- => "Charlie Sheen is winning!"
102
- >> charlie.set_status! "asynchronously winning!"
103
- => nil
104
- >> charlie.report
105
- => "Charlie Sheen is asynchronously winning!"
106
- ```
107
-
108
- You can call methods on this concurrent object just like you would any other
109
- Ruby object. The Sheen#set_status method works exactly like you'd expect,
110
- returning the last expression evaluated.
111
-
112
- However, Celluloid's secret sauce kicks in when you call banged predicate
113
- methods (i.e. methods ending in !). Even though the Sheen class has no
114
- set_status! method, you can still call it. Why is this? Because bang methods
115
- have a special meaning in Celluloid. (Note: this also means you can't define
116
- bang methods on Celluloid classes and expect them to be callable from other
117
- objects)
118
-
119
- Adding a bang to the end of a method instructs Celluloid that you would like
120
- for the given method to be called _asynchronously_. This means that rather
121
- than the caller waiting for a response, the caller sends a message to the
122
- concurrent object that you'd like the given method invoked, and then the
123
- caller proceeds without waiting for a response. The concurrent object
124
- receiving the message will then process the method call in the background.
125
-
126
- Adding a bang to a method name is a convention in Ruby used to indicate that
127
- the method is in some way "dangerous", and in Celluloid this is no exception.
128
- You have no guarantees that just because you made an asynchronous call it was
129
- ever actually invoked. Asynchronous calls will never raise an exception, even
130
- if an exception occurs when the receiver is processing it. Worse, unhandled
131
- exceptions will crash the receiver, and making an asynchronous call to a
132
- crashed object will not raise an error.
133
-
134
- However, you can still handle errors created by asynchronous calls using
135
- two features of Celluloid called [supervisors](https://github.com/tarcieri/celluloid/wiki/supervisors)
136
- and [linking](https://github.com/tarcieri/celluloid/wiki/linking)
137
-
138
- [Please see the Celluloid Wiki](https://github.com/tarcieri/celluloid/wiki)
139
- for additional usage information.
140
-
141
- Suggested Reading
142
- -----------------
143
-
144
- * [Concurrent Object-Oriented Programming in Python with ATOM](http://python.org/workshops/1997-10/proceedings/atom/)
88
+ Additional Reading
89
+ ------------------
90
+
91
+ * [Concurrent Object-Oriented Programming in Python with ATOM](http://python.org/workshops/1997-10/proceedings/atom/):
92
+ a similar system to Celluloid written in Python
145
93
 
146
94
  Contributing to Celluloid
147
95
  -------------------------
@@ -3,7 +3,7 @@ require 'thread'
3
3
  require 'timeout'
4
4
 
5
5
  module Celluloid
6
- SHUTDOWN_TIMEOUT = 60 # How long actors have to terminate
6
+ SHUTDOWN_TIMEOUT = 120 # How long actors have to terminate
7
7
  @logger = Logger.new STDERR
8
8
 
9
9
  class << self
@@ -51,6 +51,18 @@ module Celluloid
51
51
  end
52
52
  end
53
53
 
54
+ # Generate a Universally Unique Identifier
55
+ def uuid
56
+ UUID.generate
57
+ end
58
+
59
+ # Obtain the number of CPUs in the system
60
+ def cores
61
+ CPUCounter.cores
62
+ end
63
+ alias_method :cpus, :cores
64
+ alias_method :ncpus, :cores
65
+
54
66
  # Define an exception handler for actor crashes
55
67
  def exception_handler(&block)
56
68
  Logger.exception_handler(&block)
@@ -61,19 +73,25 @@ module Celluloid
61
73
  # tree before iterating through all actors and telling them to terminate.
62
74
  def shutdown
63
75
  Timeout.timeout(SHUTDOWN_TIMEOUT) do
64
- futures = Actor.all.each do |actor|
76
+ actors = Actor.all
77
+ Logger.info "Terminating #{actors.size} actors..." if actors.size > 0
78
+
79
+ # Actors cannot self-terminate, you must do it for them
80
+ terminators = actors.each do |actor|
65
81
  begin
66
82
  actor.future(:terminate)
67
83
  rescue DeadActorError, MailboxError
68
84
  end
69
85
  end
70
86
 
71
- futures.each do |future|
87
+ terminators.each do |terminator|
72
88
  begin
73
- future.value
89
+ terminator.value
74
90
  rescue DeadActorError, MailboxError
75
91
  end
76
92
  end
93
+
94
+ Logger.info "Shutdown completed cleanly"
77
95
  end
78
96
  end
79
97
  end
@@ -86,7 +104,7 @@ module Celluloid
86
104
  # Create a new actor
87
105
  def new(*args, &block)
88
106
  proxy = Actor.new(allocate).proxy
89
- proxy.send(:__send__, :initialize, *args, &block)
107
+ proxy._send_(:initialize, *args, &block)
90
108
  proxy
91
109
  end
92
110
  alias_method :spawn, :new
@@ -98,7 +116,7 @@ module Celluloid
98
116
 
99
117
  proxy = Actor.new(allocate).proxy
100
118
  current_actor.link proxy
101
- proxy.send(:__send__, :initialize, *args, &block)
119
+ proxy._send_(:initialize, *args, &block)
102
120
  proxy
103
121
  end
104
122
  alias_method :spawn_link, :new_link
@@ -255,6 +273,11 @@ module Celluloid
255
273
  Thread.current[:actor].after(interval, &block)
256
274
  end
257
275
 
276
+ # Call a block every given interval, returning a Celluloid::Timer object
277
+ def every(interval, &block)
278
+ Thread.current[:actor].every(interval, &block)
279
+ end
280
+
258
281
  # Perform a blocking or computationally intensive action inside an
259
282
  # asynchronous thread pool, allowing the caller to continue processing other
260
283
  # messages in its mailbox in the meantime
@@ -280,7 +303,7 @@ module Celluloid
280
303
  # during an async call (i.e. linking/supervisors)
281
304
  end
282
305
 
283
- return # casts are async and return immediately
306
+ return
284
307
  end
285
308
 
286
309
  super
@@ -291,6 +314,7 @@ require 'celluloid/version'
291
314
  require 'celluloid/actor_proxy'
292
315
  require 'celluloid/calls'
293
316
  require 'celluloid/core_ext'
317
+ require 'celluloid/cpu_counter'
294
318
  require 'celluloid/events'
295
319
  require 'celluloid/fiber'
296
320
  require 'celluloid/fsm'
@@ -305,6 +329,7 @@ require 'celluloid/signals'
305
329
  require 'celluloid/task'
306
330
  require 'celluloid/timers'
307
331
  require 'celluloid/thread_pool'
332
+ require 'celluloid/uuid'
308
333
 
309
334
  require 'celluloid/actor'
310
335
  require 'celluloid/future'
@@ -184,6 +184,13 @@ module Celluloid
184
184
  end
185
185
  end
186
186
 
187
+ # Schedule a block to run at the given time
188
+ def every(interval)
189
+ @timers.add(interval, true) do
190
+ Task.new(:timer) { yield }.resume
191
+ end
192
+ end
193
+
187
194
  # Sleep for the given amount of time
188
195
  def sleep(interval)
189
196
  if Celluloid.exclusive?
@@ -9,12 +9,12 @@ module Celluloid
9
9
  @mailbox, @klass = mailbox, klass
10
10
  end
11
11
 
12
- def send(meth, *args, &block)
13
- Actor.call @mailbox, :send, meth, *args, &block
12
+ def _send_(meth, *args, &block)
13
+ Actor.call @mailbox, :__send__, meth, *args, &block
14
14
  end
15
15
 
16
16
  def class
17
- Actor.call @mailbox, :send, :class
17
+ Actor.call @mailbox, :__send__, :class
18
18
  end
19
19
 
20
20
  def is_a?(klass)
@@ -57,7 +57,7 @@ module Celluloid
57
57
  raise DeadActorError, "actor already terminated" unless alive?
58
58
 
59
59
  begin
60
- send :terminate
60
+ _send_ :terminate
61
61
  rescue DeadActorError
62
62
  # In certain cases this is thrown during termination. This is likely
63
63
  # a bug in Celluloid's internals, but it shouldn't affect the caller.
@@ -12,7 +12,16 @@ module Celluloid
12
12
  raise NoMethodError, "undefined method `#{@method}' for #{obj.inspect}"
13
13
  end
14
14
 
15
- arity = obj.method(@method).arity
15
+ begin
16
+ arity = obj.method(@method).arity
17
+ rescue NameError
18
+ # If the object claims it responds to a method, but it doesn't exist,
19
+ # then we have to assume method_missing will do what it says
20
+ @arguments.unshift(@method)
21
+ @method = :method_missing
22
+ return
23
+ end
24
+
16
25
  if arity >= 0
17
26
  if arguments.size != arity
18
27
  raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{arity})"
@@ -38,7 +47,7 @@ module Celluloid
38
47
  def dispatch(obj)
39
48
  begin
40
49
  check_signature(obj)
41
- rescue Exception => ex
50
+ rescue => ex
42
51
  respond ErrorResponse.new(self, AbortError.new(ex))
43
52
  return
44
53
  end
@@ -68,10 +77,6 @@ module Celluloid
68
77
  respond ErrorResponse.new(self, exception)
69
78
  end
70
79
 
71
- #######
72
- private
73
- #######
74
-
75
80
  def respond(message)
76
81
  @caller << message
77
82
  rescue MailboxError
@@ -2,6 +2,8 @@ require 'celluloid/fiber'
2
2
 
3
3
  # Monkeypatch Thread to allow lazy access to its Celluloid::Mailbox
4
4
  class Thread
5
+ attr_accessor :uuid_counter, :uuid_limit
6
+
5
7
  # Retrieve the mailbox for the current thread or lazily initialize it
6
8
  def self.mailbox
7
9
  current[:mailbox] ||= Celluloid::Mailbox.new
@@ -0,0 +1,16 @@
1
+ require 'rbconfig'
2
+
3
+ module Celluloid
4
+ module CPUCounter
5
+ case RbConfig::CONFIG['host_os'][/^[A-Za-z]+/]
6
+ when 'darwin'
7
+ @cores = Integer(`sysctl hw.ncpu`[/\d+/])
8
+ when 'linux'
9
+ @cores = File.read("/proc/cpuinfo").scan(/core id\s+: \d+/).uniq.size
10
+ else
11
+ @cores = nil
12
+ end
13
+
14
+ def self.cores; @cores; end
15
+ end
16
+ end
@@ -1,6 +1,17 @@
1
1
  module Celluloid
2
- # Turn concurrent objects into finite state machines
3
- # Inspired by Erlang's gen_fsm. See http://www.erlang.org/doc/man/gen_fsm.html
2
+ # Simple finite state machines with integrated Celluloid timeout support
3
+ # Inspired by Erlang's gen_fsm (http://www.erlang.org/doc/man/gen_fsm.html)
4
+ #
5
+ # Basic usage:
6
+ #
7
+ # class MyMachine
8
+ # include Celluloid::FSM # NOTE: this does NOT pull in the Celluloid module
9
+ # end
10
+ #
11
+ # Inside an actor:
12
+ #
13
+ # #
14
+ # machine = MyMachine.new(current_actor)
4
15
  module FSM
5
16
  class UnattachedError < StandardError; end # Not attached to an actor
6
17
 
@@ -52,6 +63,7 @@ module Celluloid
52
63
  def initialize(actor = nil)
53
64
  @state = self.class.default_state
54
65
  @actor = actor
66
+ @actor ||= Celluloid.current_actor if Celluloid.actor?
55
67
  end
56
68
 
57
69
  # Obtain the current state of the FSM
@@ -71,6 +71,7 @@ module Celluloid
71
71
 
72
72
  @as = options['as']
73
73
  @args = options['args'] || []
74
+ raise ":args should be an Array" unless @args.kind_of? Array
74
75
  end
75
76
 
76
77
  def supervise
@@ -7,12 +7,12 @@ module Celluloid
7
7
  # Takes a class of actor to pool and a hash of options:
8
8
  #
9
9
  # * initial_size: how many actors to eagerly create
10
- # * max_size: maximum number of actors (default nil, unlimited)
10
+ # * max_size: maximum number of actors (default one actor per CPU core)
11
11
  # * args: an array of arguments to pass to the actor's initialize
12
12
  def initialize(klass, options = {})
13
13
  opts = {
14
14
  :initial_size => 1,
15
- :max_size => nil,
15
+ :max_size => Celluloid.cores,
16
16
  :args => []
17
17
  }.merge(options)
18
18
 
@@ -2,22 +2,22 @@ module Celluloid
2
2
  # Responses to calls
3
3
  class Response
4
4
  attr_reader :call, :value
5
-
5
+
6
6
  def initialize(call, value)
7
7
  @call, @value = call, value
8
8
  end
9
9
  end
10
-
10
+
11
11
  # Call completed successfully
12
12
  class SuccessResponse < Response; end
13
-
13
+
14
14
  # Call was aborted due to caller error
15
15
  class ErrorResponse < Response
16
16
  def value
17
17
  if super.is_a? AbortError
18
18
  # Aborts are caused by caller error, so ensure they capture the
19
19
  # caller's backtrace instead of the receiver's
20
- raise super.cause.class.new(super.cause.message)
20
+ raise super.cause.exception
21
21
  else
22
22
  raise super
23
23
  end
@@ -6,8 +6,8 @@ module Celluloid
6
6
  end
7
7
 
8
8
  # Call the given block after the given interval
9
- def add(interval, &block)
10
- Timer.new(self, interval, block)
9
+ def add(interval, recurring = false, &block)
10
+ Timer.new(self, interval, recurring, block)
11
11
  end
12
12
 
13
13
  # Wait for the next timer and fire it
@@ -75,10 +75,10 @@ module Celluloid
75
75
  # firing of timers
76
76
  QUANTUM = 0.02
77
77
 
78
- attr_reader :interval, :time
78
+ attr_reader :interval, :time, :recurring
79
79
 
80
- def initialize(timers, interval, block)
81
- @timers, @interval = timers, interval
80
+ def initialize(timers, interval, recurring, block)
81
+ @timers, @interval, @recurring = timers, interval, recurring
82
82
  @block = block
83
83
 
84
84
  reset
@@ -102,6 +102,7 @@ module Celluloid
102
102
 
103
103
  # Fire the block
104
104
  def fire
105
+ reset if recurring
105
106
  @block.call
106
107
  end
107
108
  alias_method :call, :fire
@@ -0,0 +1,38 @@
1
+ require 'securerandom'
2
+
3
+ module Celluloid
4
+ # Clearly Ruby doesn't have enough UUID libraries
5
+ # This one aims to be fast and simple with good support for multiple threads
6
+ # If there's a better UUID library I can use with similar multithreaded
7
+ # performance, I certainly wouldn't mind using a gem for this!
8
+ module UUID
9
+ values = SecureRandom.hex(9).match(/(.{8})(.{4})(.{3})(.{3})/)
10
+ PREFIX = "#{values[1]}-#{values[2]}-4#{values[3]}-8#{values[4]}".freeze
11
+ BLOCK_SIZE = 0x10000
12
+
13
+ @counter = 0
14
+ @counter_mutex = Mutex.new
15
+
16
+ def self.generate
17
+ thread = Thread.current
18
+
19
+ unless thread.uuid_limit
20
+ @counter_mutex.synchronize do
21
+ block_base = @counter
22
+ @counter += BLOCK_SIZE
23
+ thread.uuid_counter = block_base
24
+ thread.uuid_limit = @counter - 1
25
+ end
26
+ end
27
+
28
+ counter = thread.uuid_counter
29
+ if thread.uuid_counter >= thread.uuid_limit
30
+ thread.uuid_counter = thread.uuid_limit = nil
31
+ else
32
+ thread.uuid_counter += 1
33
+ end
34
+
35
+ "#{PREFIX}-#{sprintf("%012x", counter)}".freeze
36
+ end
37
+ end
38
+ end
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.9.0'
2
+ VERSION = '0.9.1'
3
3
  def self.version; VERSION; end
4
4
  end
@@ -1,5 +1,7 @@
1
1
  shared_context "a Celluloid Actor" do |included_module|
2
- class ExampleCrash < StandardError; end
2
+ class ExampleCrash < StandardError
3
+ attr_accessor :foo
4
+ end
3
5
 
4
6
  let :actor_class do
5
7
  Class.new do
@@ -8,6 +10,7 @@ shared_context "a Celluloid Actor" do |included_module|
8
10
 
9
11
  def initialize(name)
10
12
  @name = name
13
+ @delegate = [:bar]
11
14
  end
12
15
 
13
16
  def change_name(new_name)
@@ -30,8 +33,10 @@ shared_context "a Celluloid Actor" do |included_module|
30
33
  raise ExampleCrash, "the spec purposely crashed me :("
31
34
  end
32
35
 
33
- def crash_with_abort(reason)
34
- abort ExampleCrash.new(reason)
36
+ def crash_with_abort(reason, foo = nil)
37
+ example_crash = ExampleCrash.new(reason)
38
+ example_crash.foo = foo
39
+ abort example_crash
35
40
  end
36
41
 
37
42
  def internal_hello
@@ -41,6 +46,24 @@ shared_context "a Celluloid Actor" do |included_module|
41
46
  def external_hello
42
47
  "Hello"
43
48
  end
49
+
50
+ def method_missing(method_name, *args, &block)
51
+ if delegates?(method_name)
52
+ @delegate.send method_name, *args, &block
53
+ else
54
+ super
55
+ end
56
+ end
57
+
58
+ def respond_to?(method_name)
59
+ super || delegates?(method_name)
60
+ end
61
+
62
+ private
63
+
64
+ def delegates?(method_name)
65
+ @delegate.respond_to?(method_name)
66
+ end
44
67
  end
45
68
  end
46
69
 
@@ -87,6 +110,12 @@ shared_context "a Celluloid Actor" do |included_module|
87
110
  ponycopter.greet_by_proxy(actor).should == "Hi, I'm a ponycopter!"
88
111
  end
89
112
 
113
+ it "properly handles method_missing" do
114
+ actor = actor_class.new "Method Missing"
115
+ actor.should respond_to(:first)
116
+ actor.first.should be == :bar
117
+ end
118
+
90
119
  it "raises NoMethodError when a nonexistent method is called" do
91
120
  actor = actor_class.new "Billy Bob Thornton"
92
121
 
@@ -106,9 +135,20 @@ shared_context "a Celluloid Actor" do |included_module|
106
135
  it "raises exceptions in the caller when abort is called, but keeps running" do
107
136
  actor = actor_class.new "Al Pacino"
108
137
 
138
+ e = nil
139
+ line_no = nil
140
+
109
141
  expect do
110
- actor.crash_with_abort ExampleCrash.new("You die motherfucker!")
111
- end.to raise_exception(ExampleCrash)
142
+ begin
143
+ line_no = __LINE__; actor.crash_with_abort "You die motherfucker!", :bar
144
+ rescue => ex
145
+ e = ex
146
+ raise
147
+ end
148
+ end.to raise_exception(ExampleCrash, "You die motherfucker!")
149
+
150
+ e.backtrace.any? { |line| line.include?([__FILE__, line_no].join(':')) }.should be_true # Check the backtrace is appropriate to the caller
151
+ e.foo.should be == :bar # Check the exception maintains instance variables
112
152
 
113
153
  actor.should be_alive
114
154
  end
@@ -379,7 +419,13 @@ shared_context "a Celluloid Actor" do |included_module|
379
419
  after(n) { @fired = true }
380
420
  end
381
421
 
382
- def fired?; @fired end
422
+ def fire_every(n)
423
+ @fired = 0
424
+ every(n) { @fired += 1 }
425
+ end
426
+
427
+ def fired?; !!@fired end
428
+ def fired; @fired end
383
429
  end
384
430
  end
385
431
 
@@ -412,6 +458,22 @@ shared_context "a Celluloid Actor" do |included_module|
412
458
  actor.should be_fired
413
459
  end
414
460
 
461
+ it "schedules recurring timers which fire in the future" do
462
+ actor = @klass.new
463
+
464
+ interval = Celluloid::Timer::QUANTUM * 10
465
+ started_at = Time.now
466
+
467
+ timer = actor.fire_every(interval)
468
+ actor.fired.should be == 0
469
+
470
+ sleep(interval + Celluloid::Timer::QUANTUM) # wonky! #/
471
+ actor.fired.should be == 1
472
+
473
+ 2.times { sleep(interval + Celluloid::Timer::QUANTUM) } # wonky! #/
474
+ actor.fired.should be == 3
475
+ end
476
+
415
477
  it "cancels timers before they fire" do
416
478
  actor = @klass.new
417
479
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: celluloid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-21 00:00:00.000000000 Z
12
+ date: 2012-03-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70248586219120 !ruby/object:Gem::Requirement
16
+ requirement: &70275106519080 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70248586219120
24
+ version_requirements: *70275106519080
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70248586218540 !ruby/object:Gem::Requirement
27
+ requirement: &70275106518620 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,21 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70248586218540
35
+ version_requirements: *70275106518620
36
+ - !ruby/object:Gem::Dependency
37
+ name: guard-rspec
38
+ requirement: &70275106518200 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70275106518200
36
47
  - !ruby/object:Gem::Dependency
37
48
  name: benchmark_suite
38
- requirement: &70248586217360 !ruby/object:Gem::Requirement
49
+ requirement: &70275106517780 !ruby/object:Gem::Requirement
39
50
  none: false
40
51
  requirements:
41
52
  - - ! '>='
@@ -43,8 +54,9 @@ dependencies:
43
54
  version: '0'
44
55
  type: :development
45
56
  prerelease: false
46
- version_requirements: *70248586217360
47
- description: Celluloid is a concurrent object framework inspired by the Actor Model
57
+ version_requirements: *70275106517780
58
+ description: Celluloid enables people to build concurrent programs out of concurrent
59
+ objects just as easily as they build sequential programs out of sequential objects
48
60
  email:
49
61
  - tony.arcieri@gmail.com
50
62
  executables: []
@@ -56,6 +68,7 @@ files:
56
68
  - lib/celluloid/actor_proxy.rb
57
69
  - lib/celluloid/calls.rb
58
70
  - lib/celluloid/core_ext.rb
71
+ - lib/celluloid/cpu_counter.rb
59
72
  - lib/celluloid/events.rb
60
73
  - lib/celluloid/fiber.rb
61
74
  - lib/celluloid/fsm.rb
@@ -74,11 +87,12 @@ files:
74
87
  - lib/celluloid/task.rb
75
88
  - lib/celluloid/thread_pool.rb
76
89
  - lib/celluloid/timers.rb
90
+ - lib/celluloid/uuid.rb
77
91
  - lib/celluloid/version.rb
78
92
  - lib/celluloid.rb
79
93
  - spec/support/actor_examples.rb
80
94
  - spec/support/mailbox_examples.rb
81
- homepage: https://github.com/tarcieri/celluloid
95
+ homepage: https://github.com/celluloid/celluloid
82
96
  licenses:
83
97
  - MIT
84
98
  post_install_message:
@@ -102,6 +116,6 @@ rubyforge_project:
102
116
  rubygems_version: 1.8.10
103
117
  signing_key:
104
118
  specification_version: 3
105
- summary: Celluloid is a concurrent object framework inspired by the Actor Model
119
+ summary: Actor-based concurrent object framework for Ruby
106
120
  test_files: []
107
121
  has_rdoc: