celluloid 0.6.2 → 0.7.0
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 +37 -482
- data/lib/celluloid.rb +45 -18
- data/lib/celluloid/actor.rb +64 -22
- data/lib/celluloid/actor_pool.rb +1 -1
- data/lib/celluloid/actor_proxy.rb +1 -1
- data/lib/celluloid/application.rb +1 -1
- data/lib/celluloid/calls.rb +2 -2
- data/lib/celluloid/fiber.rb +2 -31
- data/lib/celluloid/fsm.rb +141 -0
- data/lib/celluloid/future.rb +10 -13
- data/lib/celluloid/logger.rb +8 -3
- data/lib/celluloid/mailbox.rb +16 -4
- data/lib/celluloid/receivers.rb +49 -19
- data/lib/celluloid/registry.rb +2 -2
- data/lib/celluloid/signals.rb +12 -10
- data/lib/celluloid/supervisor.rb +7 -4
- data/lib/celluloid/task.rb +53 -0
- data/lib/celluloid/tcp_server.rb +2 -1
- data/lib/celluloid/timers.rb +109 -0
- data/lib/celluloid/version.rb +1 -1
- data/spec/support/actor_examples.rb +453 -0
- data/spec/support/mailbox_examples.rb +52 -0
- metadata +11 -11
- data/lib/celluloid/io.rb +0 -24
- data/lib/celluloid/io/mailbox.rb +0 -65
- data/lib/celluloid/io/reactor.rb +0 -63
- data/lib/celluloid/io/waker.rb +0 -43
data/lib/celluloid/future.rb
CHANGED
@@ -1,23 +1,19 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
3
|
module Celluloid
|
4
|
-
# Create a new Celluloid::Future object, allowing a block to be computed in
|
5
|
-
# the background and its return value obtained later
|
6
|
-
def self.Future(*args, &block)
|
7
|
-
Celluloid::Future.new(*args, &block)
|
8
|
-
end
|
9
|
-
|
10
4
|
# Celluloid::Future objects allow methods and blocks to run in the
|
11
5
|
# background, their values requested later
|
12
6
|
class Future
|
7
|
+
# Create a new Celluloid::Future object, allowing a block to be computed in
|
8
|
+
# the background and its return value obtained later
|
13
9
|
def initialize(*args, &block)
|
14
10
|
@lock = Mutex.new
|
15
11
|
@value_obtained = false
|
16
|
-
|
12
|
+
|
17
13
|
@runner = Runner.new(*args, &block)
|
18
14
|
@runner.run!
|
19
15
|
end
|
20
|
-
|
16
|
+
|
21
17
|
# Obtain the value for this Future
|
22
18
|
def value
|
23
19
|
@lock.synchronize do
|
@@ -26,18 +22,19 @@ module Celluloid
|
|
26
22
|
@runner.terminate
|
27
23
|
@value_obtained = true
|
28
24
|
end
|
29
|
-
|
25
|
+
|
30
26
|
@value
|
31
27
|
end
|
32
28
|
end
|
33
|
-
|
29
|
+
alias_method :call, :value
|
30
|
+
|
34
31
|
# Inspect this Celluloid::Future
|
35
32
|
alias_method :inspect, :to_s
|
36
|
-
|
33
|
+
|
37
34
|
# Runner is an internal class which executes the given block/method
|
38
35
|
class Runner
|
39
36
|
include Celluloid
|
40
|
-
|
37
|
+
|
41
38
|
def initialize(*args, &block)
|
42
39
|
@args, @block = args, block
|
43
40
|
end
|
@@ -58,4 +55,4 @@ module Celluloid
|
|
58
55
|
end
|
59
56
|
end
|
60
57
|
end
|
61
|
-
end
|
58
|
+
end
|
data/lib/celluloid/logger.rb
CHANGED
@@ -2,17 +2,22 @@ module Celluloid
|
|
2
2
|
module Logger
|
3
3
|
module_function
|
4
4
|
|
5
|
-
#
|
5
|
+
# Send a debug message
|
6
6
|
def debug(string)
|
7
7
|
Celluloid.logger.debug(string) if Celluloid.logger
|
8
8
|
end
|
9
9
|
|
10
|
-
#
|
10
|
+
# Send a info message
|
11
|
+
def info(string)
|
12
|
+
Celluloid.logger.info(string) if Celluloid.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
# Send a warning message
|
11
16
|
def warn(string)
|
12
17
|
Celluloid.logger.warn(string) if Celluloid.logger
|
13
18
|
end
|
14
19
|
|
15
|
-
#
|
20
|
+
# Send an error message
|
16
21
|
def error(string)
|
17
22
|
Celluloid.logger.error(string) if Celluloid.logger
|
18
23
|
end
|
data/lib/celluloid/mailbox.rb
CHANGED
@@ -42,7 +42,7 @@ module Celluloid
|
|
42
42
|
end
|
43
43
|
|
44
44
|
# Receive a message from the Mailbox
|
45
|
-
def receive(&block)
|
45
|
+
def receive(timeout = nil, &block)
|
46
46
|
message = nil
|
47
47
|
|
48
48
|
@lock.synchronize do
|
@@ -50,7 +50,19 @@ module Celluloid
|
|
50
50
|
|
51
51
|
begin
|
52
52
|
message = next_message(&block)
|
53
|
-
|
53
|
+
|
54
|
+
unless message
|
55
|
+
if timeout
|
56
|
+
now = Time.now
|
57
|
+
wait_until ||= now + timeout
|
58
|
+
wait_interval = wait_until - now
|
59
|
+
return if wait_interval < 0
|
60
|
+
else
|
61
|
+
wait_interval = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
@condition.wait(@lock, wait_interval)
|
65
|
+
end
|
54
66
|
end until message
|
55
67
|
end
|
56
68
|
|
@@ -63,7 +75,7 @@ module Celluloid
|
|
63
75
|
|
64
76
|
if block_given?
|
65
77
|
index = @messages.index do |msg|
|
66
|
-
yield(msg) || msg.is_a?(
|
78
|
+
yield(msg) || msg.is_a?(SystemEvent)
|
67
79
|
end
|
68
80
|
|
69
81
|
message = @messages.slice!(index, 1).first if index
|
@@ -71,7 +83,7 @@ module Celluloid
|
|
71
83
|
message = @messages.shift
|
72
84
|
end
|
73
85
|
|
74
|
-
raise message if message.is_a?
|
86
|
+
raise message if message.is_a? SystemEvent
|
75
87
|
message
|
76
88
|
end
|
77
89
|
|
data/lib/celluloid/receivers.rb
CHANGED
@@ -1,36 +1,66 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
module Celluloid
|
2
4
|
# Allow methods to directly interact with the actor protocol
|
3
5
|
class Receivers
|
4
6
|
def initialize
|
5
|
-
@
|
7
|
+
@receivers = Set.new
|
8
|
+
@timers = Timers.new
|
6
9
|
end
|
7
10
|
|
8
11
|
# Receive an asynchronous message
|
9
|
-
def receive(&block)
|
10
|
-
|
12
|
+
def receive(timeout = nil, &block)
|
13
|
+
receiver = Receiver.new block
|
11
14
|
|
12
|
-
|
13
|
-
|
15
|
+
if timeout
|
16
|
+
receiver.timer = @timers.add(timeout) do
|
17
|
+
@receivers.delete receiver
|
18
|
+
receiver.resume
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
@receivers << receiver
|
23
|
+
Task.suspend
|
24
|
+
end
|
25
|
+
|
26
|
+
# How long to wait until the next timer fires
|
27
|
+
def wait_interval
|
28
|
+
@timers.wait_interval
|
29
|
+
end
|
30
|
+
|
31
|
+
# Fire any pending timers
|
32
|
+
def fire_timers
|
33
|
+
@timers.fire
|
14
34
|
end
|
15
35
|
|
16
36
|
# Handle incoming messages
|
17
37
|
def handle_message(message)
|
18
|
-
|
38
|
+
receiver = @receivers.find { |r| r.match(message) }
|
39
|
+
return unless receiver
|
19
40
|
|
20
|
-
@
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
41
|
+
@receivers.delete receiver
|
42
|
+
@timers.cancel receiver.timer if receiver.timer
|
43
|
+
receiver.resume message
|
44
|
+
end
|
45
|
+
end
|
26
46
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
47
|
+
# Methods blocking on a call to receive
|
48
|
+
class Receiver
|
49
|
+
attr_accessor :timer
|
50
|
+
|
51
|
+
def initialize(block)
|
52
|
+
@block = block
|
53
|
+
@task = Task.current
|
54
|
+
@timer = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Match a message with this receiver's block
|
58
|
+
def match(message)
|
59
|
+
@block.call(message) if @block
|
60
|
+
end
|
61
|
+
|
62
|
+
def resume(message = nil)
|
63
|
+
@task.resume message
|
34
64
|
end
|
35
65
|
end
|
36
66
|
end
|
data/lib/celluloid/registry.rb
CHANGED
@@ -9,8 +9,8 @@ module Celluloid
|
|
9
9
|
# Register an Actor
|
10
10
|
def []=(name, actor)
|
11
11
|
actor_singleton = class << actor; self; end
|
12
|
-
unless actor_singleton.ancestors.include?
|
13
|
-
raise
|
12
|
+
unless actor_singleton.ancestors.include? ActorProxy
|
13
|
+
raise TypeError, "not an actor"
|
14
14
|
end
|
15
15
|
|
16
16
|
@@registry_lock.synchronize do
|
data/lib/celluloid/signals.rb
CHANGED
@@ -1,25 +1,27 @@
|
|
1
1
|
module Celluloid
|
2
2
|
# Event signaling between methods of the same object
|
3
3
|
class Signals
|
4
|
+
attr_reader :waiting
|
5
|
+
|
4
6
|
def initialize
|
5
7
|
@waiting = {}
|
6
8
|
end
|
7
|
-
|
9
|
+
|
8
10
|
# Wait for the given signal name and return the associated value
|
9
11
|
def wait(name)
|
10
|
-
|
11
|
-
|
12
|
-
|
12
|
+
tasks = @waiting[name] ||= []
|
13
|
+
tasks << Task.current
|
14
|
+
Task.suspend
|
13
15
|
end
|
14
|
-
|
16
|
+
|
15
17
|
# Send a signal to all method calls waiting for the given name
|
16
18
|
# Returns true if any calls were signaled, or false otherwise
|
17
19
|
def send(name, value = nil)
|
18
|
-
|
19
|
-
return unless
|
20
|
-
|
21
|
-
|
20
|
+
tasks = @waiting.delete name
|
21
|
+
return unless tasks
|
22
|
+
|
23
|
+
tasks.each { |task| task.resume(value) if task.running? }
|
22
24
|
value
|
23
25
|
end
|
24
26
|
end
|
25
|
-
end
|
27
|
+
end
|
data/lib/celluloid/supervisor.rb
CHANGED
@@ -18,10 +18,12 @@ module Celluloid
|
|
18
18
|
|
19
19
|
def initialize(name, klass, *args, &block)
|
20
20
|
@name, @klass, @args, @block = name, klass, args, block
|
21
|
+
@started = false
|
22
|
+
|
21
23
|
start_actor
|
22
24
|
end
|
23
25
|
|
24
|
-
def start_actor(start_attempts =
|
26
|
+
def start_actor(start_attempts = 3, sleep_interval = 30)
|
25
27
|
failures = 0
|
26
28
|
|
27
29
|
begin
|
@@ -31,18 +33,19 @@ module Celluloid
|
|
31
33
|
if failures >= start_attempts
|
32
34
|
failures = 0
|
33
35
|
|
34
|
-
|
36
|
+
Logger.warn("#{@klass} is crashing on initialize too quickly, sleeping for #{sleep_interval} seconds")
|
35
37
|
sleep sleep_interval
|
36
38
|
end
|
37
39
|
retry
|
38
40
|
end
|
39
41
|
|
40
|
-
|
42
|
+
@started = true
|
43
|
+
Actor[@name] = @actor if @name
|
41
44
|
end
|
42
45
|
|
43
46
|
# When actors die, regardless of the reason, restart them
|
44
47
|
def restart_actor(actor, reason)
|
45
|
-
start_actor
|
48
|
+
start_actor if @started
|
46
49
|
end
|
47
50
|
|
48
51
|
def inspect
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Trying to resume a dead task
|
3
|
+
class DeadTaskError < StandardError; end
|
4
|
+
|
5
|
+
# Tasks are interruptable/resumable execution contexts used to run methods
|
6
|
+
class Task
|
7
|
+
attr_reader :type # what type of task is this?
|
8
|
+
|
9
|
+
# Obtain the current task
|
10
|
+
def self.current
|
11
|
+
task = Thread.current[:task]
|
12
|
+
raise "not in task scope" unless task
|
13
|
+
task
|
14
|
+
end
|
15
|
+
|
16
|
+
# Suspend the running task, deferring to the scheduler
|
17
|
+
def self.suspend(value = nil)
|
18
|
+
Fiber.yield(value)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Run the given block within a task
|
22
|
+
def initialize(type)
|
23
|
+
@type = type
|
24
|
+
|
25
|
+
actor = Thread.current[:actor]
|
26
|
+
mailbox = Thread.current[:mailbox]
|
27
|
+
|
28
|
+
@fiber = Fiber.new do
|
29
|
+
Thread.current[:actor] = actor
|
30
|
+
Thread.current[:mailbox] = mailbox
|
31
|
+
Thread.current[:task] = self
|
32
|
+
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Resume a suspended task, giving it a value to return if needed
|
38
|
+
def resume(value = nil)
|
39
|
+
@fiber.resume value
|
40
|
+
nil
|
41
|
+
rescue FiberError
|
42
|
+
raise DeadTaskError, "cannot resume a dead task"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Is the current task still running?
|
46
|
+
def running?; @fiber.alive?; end
|
47
|
+
|
48
|
+
# Nicer string inspect for tasks
|
49
|
+
def inspect
|
50
|
+
"<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @running=#{@fiber.alive?}>"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/celluloid/tcp_server.rb
CHANGED
@@ -0,0 +1,109 @@
|
|
1
|
+
module Celluloid
|
2
|
+
# Low precision timers implemented in pure Ruby
|
3
|
+
class Timers
|
4
|
+
def initialize
|
5
|
+
@timers = []
|
6
|
+
end
|
7
|
+
|
8
|
+
# Call the given block after the given interval
|
9
|
+
def add(interval, &block)
|
10
|
+
Timer.new(self, interval, block)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Wait for the next timer and fire it
|
14
|
+
def wait
|
15
|
+
return if @timers.empty?
|
16
|
+
|
17
|
+
interval = wait_interval
|
18
|
+
sleep interval if interval >= Timer::QUANTUM
|
19
|
+
fire
|
20
|
+
end
|
21
|
+
|
22
|
+
# Interval to wait until when the next timer will fire
|
23
|
+
def wait_interval
|
24
|
+
@timers.first.time - Time.now unless empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Fire all timers that are ready
|
28
|
+
def fire
|
29
|
+
return if @timers.empty?
|
30
|
+
|
31
|
+
time = Time.now + Timer::QUANTUM
|
32
|
+
while not empty? and time > @timers.first.time
|
33
|
+
timer = @timers.shift
|
34
|
+
timer.call
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Insert a timer into the active timers
|
39
|
+
def insert(timer)
|
40
|
+
@timers.insert(index(timer), timer)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Remove a given timer from the set we're monitoring
|
44
|
+
def cancel(timer)
|
45
|
+
@timers.delete timer
|
46
|
+
end
|
47
|
+
|
48
|
+
# Are there any timers pending?
|
49
|
+
def empty?
|
50
|
+
@timers.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Index where a timer would be located in the sorted timers array
|
54
|
+
def index(timer)
|
55
|
+
l, r = 0, @timers.size - 1
|
56
|
+
|
57
|
+
while l <= r
|
58
|
+
m = (r + l) / 2
|
59
|
+
if timer < @timers.at(m)
|
60
|
+
r = m - 1
|
61
|
+
else
|
62
|
+
l = m + 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
l
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# An individual timer set to fire a given proc at a given time
|
70
|
+
class Timer
|
71
|
+
include Comparable
|
72
|
+
|
73
|
+
# The timer system is guaranteed (at least by the specs) to be this precise
|
74
|
+
# during normal operation. Long blocking calls within actors will delay the
|
75
|
+
# firing of timers
|
76
|
+
QUANTUM = 0.02
|
77
|
+
|
78
|
+
attr_reader :interval, :time
|
79
|
+
|
80
|
+
def initialize(timers, interval, block)
|
81
|
+
@timers, @interval = timers, interval
|
82
|
+
@block = block
|
83
|
+
|
84
|
+
reset
|
85
|
+
end
|
86
|
+
|
87
|
+
def <=>(other)
|
88
|
+
@time <=> other.time
|
89
|
+
end
|
90
|
+
|
91
|
+
# Cancel this timer
|
92
|
+
def cancel
|
93
|
+
@timers.cancel self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Reset this timer
|
97
|
+
def reset
|
98
|
+
@timers.cancel self if defined?(@time)
|
99
|
+
@time = Time.now + @interval
|
100
|
+
@timers.insert self
|
101
|
+
end
|
102
|
+
|
103
|
+
# Fire the block
|
104
|
+
def fire
|
105
|
+
@block.call
|
106
|
+
end
|
107
|
+
alias_method :call, :fire
|
108
|
+
end
|
109
|
+
end
|