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 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: