celluloid 0.7.0 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +27 -23
- data/lib/celluloid/actor.rb +11 -6
- data/lib/celluloid/core_ext.rb +7 -0
- data/lib/celluloid/future.rb +2 -0
- data/lib/celluloid/signals.rb +29 -6
- data/lib/celluloid/task.rb +24 -4
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +27 -9
- metadata +7 -6
data/README.md
CHANGED
@@ -65,37 +65,41 @@ Usage
|
|
65
65
|
|
66
66
|
To use Celluloid, define a normal Ruby class that includes Celluloid:
|
67
67
|
|
68
|
-
|
68
|
+
```ruby
|
69
|
+
require 'celluloid'
|
69
70
|
|
70
|
-
|
71
|
-
|
71
|
+
class Sheen
|
72
|
+
include Celluloid
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
74
|
+
def initialize(name)
|
75
|
+
@name = name
|
76
|
+
end
|
76
77
|
|
77
|
-
|
78
|
-
|
79
|
-
|
78
|
+
def set_status(status)
|
79
|
+
@status = status
|
80
|
+
end
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
def report
|
83
|
+
"#{@name} is #{@status}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
85
87
|
|
86
88
|
Now when you create new instances of this class, they're actually concurrent
|
87
89
|
objects, each running in their own thread:
|
88
90
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
91
|
+
```ruby
|
92
|
+
>> charlie = Sheen.new "Charlie Sheen"
|
93
|
+
=> #<Celluloid::Actor(Sheen:0x00000100a312d0) @name="Charlie Sheen">
|
94
|
+
>> charlie.set_status "winning!"
|
95
|
+
=> "winning!"
|
96
|
+
>> charlie.report
|
97
|
+
=> "Charlie Sheen is winning!"
|
98
|
+
>> charlie.set_status! "asynchronously winning!"
|
99
|
+
=> nil
|
100
|
+
>> charlie.report
|
101
|
+
=> "Charlie Sheen is asynchronously winning!"
|
102
|
+
```
|
99
103
|
|
100
104
|
You can call methods on this concurrent object just like you would any other
|
101
105
|
Ruby object. The Sheen#set_status method works exactly like you'd expect,
|
data/lib/celluloid/actor.rb
CHANGED
@@ -116,7 +116,7 @@ module Celluloid
|
|
116
116
|
begin
|
117
117
|
message = @mailbox.receive(timeout)
|
118
118
|
rescue ExitEvent => exit_event
|
119
|
-
Task.new(:exit_handler) { handle_exit_event exit_event
|
119
|
+
Task.new(:exit_handler) { handle_exit_event exit_event }.resume
|
120
120
|
retry
|
121
121
|
end
|
122
122
|
|
@@ -161,11 +161,15 @@ module Celluloid
|
|
161
161
|
# handing them the one we're using internally across threads, a definite
|
162
162
|
# thread safety shared state no-no
|
163
163
|
tasks = {}
|
164
|
-
current_task =
|
164
|
+
current_task = Task.current rescue nil
|
165
165
|
tasks[current_task] = :running if current_task
|
166
166
|
|
167
|
-
@signals.waiting.each do |waitable,
|
168
|
-
|
167
|
+
@signals.waiting.each do |waitable, waiters|
|
168
|
+
if waiters.is_a? Enumerable
|
169
|
+
waiters.each { |waiter| tasks[waiter] = waitable }
|
170
|
+
else
|
171
|
+
tasks[waiters] = waitable
|
172
|
+
end
|
169
173
|
end
|
170
174
|
|
171
175
|
tasks
|
@@ -174,7 +178,7 @@ module Celluloid
|
|
174
178
|
# Schedule a block to run at the given time
|
175
179
|
def after(interval)
|
176
180
|
@timers.add(interval) do
|
177
|
-
Task.new(:timer) { yield
|
181
|
+
Task.new(:timer) { yield }.resume
|
178
182
|
end
|
179
183
|
end
|
180
184
|
|
@@ -189,7 +193,7 @@ module Celluloid
|
|
189
193
|
def handle_message(message)
|
190
194
|
case message
|
191
195
|
when Call
|
192
|
-
Task.new(:message_handler) { message.dispatch(@subject)
|
196
|
+
Task.new(:message_handler) { message.dispatch(@subject) }.resume
|
193
197
|
when Response
|
194
198
|
handled_successfully = signal [:call, message.call_id], message
|
195
199
|
|
@@ -226,6 +230,7 @@ module Celluloid
|
|
226
230
|
def cleanup(exit_event)
|
227
231
|
@mailbox.shutdown
|
228
232
|
@links.send_event exit_event
|
233
|
+
tasks.each { |task, _| task.terminate }
|
229
234
|
|
230
235
|
begin
|
231
236
|
@subject.finalize if @subject.respond_to? :finalize
|
data/lib/celluloid/core_ext.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'celluloid/fiber'
|
2
|
+
|
1
3
|
# Monkeypatch Thread to allow lazy access to its Celluloid::Mailbox
|
2
4
|
class Thread
|
3
5
|
# Retrieve the mailbox for the current thread or lazily initialize it
|
@@ -5,3 +7,8 @@ class Thread
|
|
5
7
|
current[:mailbox] ||= Celluloid::Mailbox.new
|
6
8
|
end
|
7
9
|
end
|
10
|
+
|
11
|
+
class Fiber
|
12
|
+
# Celluloid::Task associated with this Fiber
|
13
|
+
attr_accessor :task
|
14
|
+
end
|
data/lib/celluloid/future.rb
CHANGED
data/lib/celluloid/signals.rb
CHANGED
@@ -7,10 +7,19 @@ module Celluloid
|
|
7
7
|
@waiting = {}
|
8
8
|
end
|
9
9
|
|
10
|
-
# Wait for the given signal
|
11
|
-
def wait(
|
12
|
-
tasks = @waiting[
|
13
|
-
|
10
|
+
# Wait for the given signal and return the associated value
|
11
|
+
def wait(signal)
|
12
|
+
tasks = @waiting[signal]
|
13
|
+
|
14
|
+
case tasks
|
15
|
+
when Array
|
16
|
+
tasks << Task.current
|
17
|
+
when NilClass
|
18
|
+
@waiting[signal] = Task.current
|
19
|
+
else
|
20
|
+
@waiting[signal] = [tasks, Task.current]
|
21
|
+
end
|
22
|
+
|
14
23
|
Task.suspend
|
15
24
|
end
|
16
25
|
|
@@ -18,10 +27,24 @@ module Celluloid
|
|
18
27
|
# Returns true if any calls were signaled, or false otherwise
|
19
28
|
def send(name, value = nil)
|
20
29
|
tasks = @waiting.delete name
|
21
|
-
return unless tasks
|
22
30
|
|
23
|
-
tasks
|
31
|
+
case tasks
|
32
|
+
when Array
|
33
|
+
tasks.each { |task| run_task task, value }
|
34
|
+
when NilClass
|
35
|
+
Logger.debug("spurious signal: #{name}")
|
36
|
+
else
|
37
|
+
run_task tasks, value
|
38
|
+
end
|
39
|
+
|
24
40
|
value
|
25
41
|
end
|
42
|
+
|
43
|
+
# Run the given task, reporting errors that occur
|
44
|
+
def run_task(task, value)
|
45
|
+
task.resume(value)
|
46
|
+
rescue => ex
|
47
|
+
Celluloid::Logger.crash("signaling error", ex)
|
48
|
+
end
|
26
49
|
end
|
27
50
|
end
|
data/lib/celluloid/task.rb
CHANGED
@@ -4,18 +4,22 @@ module Celluloid
|
|
4
4
|
|
5
5
|
# Tasks are interruptable/resumable execution contexts used to run methods
|
6
6
|
class Task
|
7
|
+
class TerminatedError < StandardError; end # kill a running fiber
|
8
|
+
|
7
9
|
attr_reader :type # what type of task is this?
|
8
10
|
|
9
11
|
# Obtain the current task
|
10
12
|
def self.current
|
11
|
-
task =
|
13
|
+
task = Fiber.current.task
|
12
14
|
raise "not in task scope" unless task
|
13
15
|
task
|
14
16
|
end
|
15
17
|
|
16
18
|
# Suspend the running task, deferring to the scheduler
|
17
19
|
def self.suspend(value = nil)
|
18
|
-
Fiber.yield(value)
|
20
|
+
result = Fiber.yield(value)
|
21
|
+
raise TerminatedError, "task was terminated" if result == TerminatedError
|
22
|
+
result
|
19
23
|
end
|
20
24
|
|
21
25
|
# Run the given block within a task
|
@@ -28,9 +32,14 @@ module Celluloid
|
|
28
32
|
@fiber = Fiber.new do
|
29
33
|
Thread.current[:actor] = actor
|
30
34
|
Thread.current[:mailbox] = mailbox
|
31
|
-
Thread.current[:task] = self
|
32
35
|
|
33
|
-
|
36
|
+
Fiber.current.task = self
|
37
|
+
|
38
|
+
begin
|
39
|
+
yield
|
40
|
+
rescue TerminatedError
|
41
|
+
# Task was explicitly terminated
|
42
|
+
end
|
34
43
|
end
|
35
44
|
end
|
36
45
|
|
@@ -40,6 +49,17 @@ module Celluloid
|
|
40
49
|
nil
|
41
50
|
rescue FiberError
|
42
51
|
raise DeadTaskError, "cannot resume a dead task"
|
52
|
+
rescue RuntimeError => ex
|
53
|
+
# These occur spuriously on 1.9.3 if we shut down an actor with running tasks
|
54
|
+
return if ex.message == ""
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
|
58
|
+
# Terminate this task
|
59
|
+
def terminate
|
60
|
+
resume TerminatedError
|
61
|
+
rescue FiberError
|
62
|
+
# If we're getting this the task should already be dead
|
43
63
|
end
|
44
64
|
|
45
65
|
# Is the current task still running?
|
data/lib/celluloid/version.rb
CHANGED
@@ -253,44 +253,62 @@ shared_context "a Celluloid Actor" do |included_module|
|
|
253
253
|
before do
|
254
254
|
@signaler = Class.new do
|
255
255
|
include included_module
|
256
|
-
attr_reader :signaled
|
257
256
|
|
258
257
|
def initialize
|
258
|
+
@waiting = false
|
259
259
|
@signaled = false
|
260
260
|
end
|
261
261
|
|
262
262
|
def wait_for_signal
|
263
|
+
raise "already signaled" if @signaled
|
264
|
+
|
265
|
+
@waiting = true
|
266
|
+
signal :future
|
267
|
+
|
263
268
|
value = wait :ponycopter
|
269
|
+
|
270
|
+
@waiting = false
|
264
271
|
@signaled = true
|
265
272
|
value
|
266
273
|
end
|
267
274
|
|
275
|
+
def wait_for_future
|
276
|
+
return true if @waiting
|
277
|
+
wait :future
|
278
|
+
end
|
279
|
+
|
268
280
|
def send_signal(value)
|
269
281
|
signal :ponycopter, value
|
270
282
|
end
|
283
|
+
|
284
|
+
def waiting?; @waiting end
|
285
|
+
def signaled?; @signaled end
|
271
286
|
end
|
272
287
|
end
|
273
288
|
|
274
289
|
it "allows methods within the same object to signal each other" do
|
275
290
|
obj = @signaler.new
|
276
|
-
obj.
|
291
|
+
obj.should_not be_signaled
|
277
292
|
|
278
293
|
obj.wait_for_signal!
|
279
|
-
obj.
|
294
|
+
obj.should_not be_signaled
|
280
295
|
|
281
296
|
obj.send_signal :foobar
|
282
|
-
obj.
|
297
|
+
obj.should be_signaled
|
283
298
|
end
|
284
299
|
|
285
300
|
# FIXME: This is deadlocking on Travis, and may still have issues
|
286
|
-
it "sends values along with signals"
|
301
|
+
it "sends values along with signals" do
|
287
302
|
obj = @signaler.new
|
288
|
-
obj.
|
303
|
+
obj.should_not be_signaled
|
289
304
|
|
290
|
-
future =
|
291
|
-
obj.signaled.should be_false
|
305
|
+
future = obj.future(:wait_for_signal)
|
292
306
|
|
293
|
-
obj.
|
307
|
+
obj.wait_for_future
|
308
|
+
obj.should be_waiting
|
309
|
+
obj.should_not be_signaled
|
310
|
+
|
311
|
+
obj.send_signal(:foobar).should be_true
|
294
312
|
future.value.should == :foobar
|
295
313
|
end
|
296
314
|
end
|
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.7.
|
4
|
+
version: 0.7.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:
|
12
|
+
date: 2012-01-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &70351782537080 !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: *70351782537080
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70351782536080 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: 2.7.0
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70351782536080
|
36
36
|
description: Celluloid is a concurrent object framework inspired by the Actor Model
|
37
37
|
email:
|
38
38
|
- tony@medioh.com
|
@@ -93,3 +93,4 @@ signing_key:
|
|
93
93
|
specification_version: 3
|
94
94
|
summary: Celluloid is a concurrent object framework inspired by the Actor Model
|
95
95
|
test_files: []
|
96
|
+
has_rdoc:
|