celluloid 0.9.0 → 0.9.1
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 +63 -115
- data/lib/celluloid.rb +32 -7
- data/lib/celluloid/actor.rb +7 -0
- data/lib/celluloid/actor_proxy.rb +4 -4
- data/lib/celluloid/calls.rb +11 -6
- data/lib/celluloid/core_ext.rb +2 -0
- data/lib/celluloid/cpu_counter.rb +16 -0
- data/lib/celluloid/fsm.rb +14 -2
- data/lib/celluloid/group.rb +1 -0
- data/lib/celluloid/pool.rb +2 -2
- data/lib/celluloid/responses.rb +4 -4
- data/lib/celluloid/timers.rb +6 -5
- data/lib/celluloid/uuid.rb +38 -0
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +68 -6
- metadata +25 -11
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-

|
2
2
|
=========
|
3
|
-
[](http://travis-ci.org/celluloid/celluloid)
|
4
|
+
[](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
|
-
|
17
|
-
|
18
|
-
|
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
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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/
|
45
|
-
to EventMachine (
|
46
|
-
[Celluloid::IO](https://github.com/
|
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/
|
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.
|
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
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-------------------------
|
data/lib/celluloid.rb
CHANGED
@@ -3,7 +3,7 @@ require 'thread'
|
|
3
3
|
require 'timeout'
|
4
4
|
|
5
5
|
module Celluloid
|
6
|
-
SHUTDOWN_TIMEOUT =
|
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
|
-
|
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
|
-
|
87
|
+
terminators.each do |terminator|
|
72
88
|
begin
|
73
|
-
|
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.
|
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.
|
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
|
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'
|
data/lib/celluloid/actor.rb
CHANGED
@@ -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
|
13
|
-
Actor.call @mailbox, :
|
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, :
|
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
|
-
|
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.
|
data/lib/celluloid/calls.rb
CHANGED
@@ -12,7 +12,16 @@ module Celluloid
|
|
12
12
|
raise NoMethodError, "undefined method `#{@method}' for #{obj.inspect}"
|
13
13
|
end
|
14
14
|
|
15
|
-
|
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
|
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
|
data/lib/celluloid/core_ext.rb
CHANGED
@@ -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
|
data/lib/celluloid/fsm.rb
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
module Celluloid
|
2
|
-
#
|
3
|
-
# Inspired by Erlang's gen_fsm
|
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
|
data/lib/celluloid/group.rb
CHANGED
data/lib/celluloid/pool.rb
CHANGED
@@ -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
|
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 =>
|
15
|
+
:max_size => Celluloid.cores,
|
16
16
|
:args => []
|
17
17
|
}.merge(options)
|
18
18
|
|
data/lib/celluloid/responses.rb
CHANGED
@@ -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.
|
20
|
+
raise super.cause.exception
|
21
21
|
else
|
22
22
|
raise super
|
23
23
|
end
|
data/lib/celluloid/timers.rb
CHANGED
@@ -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
|
data/lib/celluloid/version.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
shared_context "a Celluloid Actor" do |included_module|
|
2
|
-
class ExampleCrash < StandardError
|
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
|
-
|
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
|
-
|
111
|
-
|
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
|
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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *70275106519080
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
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: *
|
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: &
|
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: *
|
47
|
-
description: Celluloid
|
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/
|
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:
|
119
|
+
summary: Actor-based concurrent object framework for Ruby
|
106
120
|
test_files: []
|
107
121
|
has_rdoc:
|