celluloid 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|