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 CHANGED
@@ -65,37 +65,41 @@ Usage
65
65
 
66
66
  To use Celluloid, define a normal Ruby class that includes Celluloid:
67
67
 
68
- require 'celluloid'
68
+ ```ruby
69
+ require 'celluloid'
69
70
 
70
- class Sheen
71
- include Celluloid
71
+ class Sheen
72
+ include Celluloid
72
73
 
73
- def initialize(name)
74
- @name = name
75
- end
74
+ def initialize(name)
75
+ @name = name
76
+ end
76
77
 
77
- def set_status(status)
78
- @status = status
79
- end
78
+ def set_status(status)
79
+ @status = status
80
+ end
80
81
 
81
- def report
82
- "#{@name} is #{@status}"
83
- end
84
- end
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
- >> charlie = Sheen.new "Charlie Sheen"
90
- => #<Celluloid::Actor(Sheen:0x00000100a312d0) @name="Charlie Sheen">
91
- >> charlie.set_status "winning!"
92
- => "winning!"
93
- >> charlie.report
94
- => "Charlie Sheen is winning!"
95
- >> charlie.set_status! "asynchronously winning!"
96
- => nil
97
- >> charlie.report
98
- => "Charlie Sheen is asynchronously winning!"
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,
@@ -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; nil }.resume
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 = Thread.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, task|
168
- tasks[task] = waitable
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; nil }.resume
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); nil }.resume
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
@@ -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
@@ -37,6 +37,8 @@ module Celluloid
37
37
 
38
38
  def initialize(*args, &block)
39
39
  @args, @block = args, block
40
+ @called = nil
41
+ @error = nil
40
42
  end
41
43
 
42
44
  def run
@@ -7,10 +7,19 @@ module Celluloid
7
7
  @waiting = {}
8
8
  end
9
9
 
10
- # Wait for the given signal name and return the associated value
11
- def wait(name)
12
- tasks = @waiting[name] ||= []
13
- tasks << Task.current
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.each { |task| task.resume(value) if task.running? }
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
@@ -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 = Thread.current[: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
- yield
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?
@@ -1,4 +1,4 @@
1
1
  module Celluloid
2
- VERSION = '0.7.0'
2
+ VERSION = '0.7.1'
3
3
  def self.version; VERSION; end
4
4
  end
@@ -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.signaled.should be_false
291
+ obj.should_not be_signaled
277
292
 
278
293
  obj.wait_for_signal!
279
- obj.signaled.should be_false
294
+ obj.should_not be_signaled
280
295
 
281
296
  obj.send_signal :foobar
282
- obj.signaled.should be_true
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", :pending => ENV['CI'] do
301
+ it "sends values along with signals" do
287
302
  obj = @signaler.new
288
- obj.signaled.should be_false
303
+ obj.should_not be_signaled
289
304
 
290
- future = Celluloid::Future.new { obj.wait_for_signal }
291
- obj.signaled.should be_false
305
+ future = obj.future(:wait_for_signal)
292
306
 
293
- obj.send_signal :foobar
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.0
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: 2011-12-29 00:00:00.000000000 Z
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: &70176959879340 !ruby/object:Gem::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: *70176959879340
24
+ version_requirements: *70351782537080
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70176959878380 !ruby/object:Gem::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: *70176959878380
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: