celluloid 0.7.0 → 0.7.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 +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:
|