celluloid 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -2,17 +2,22 @@ module Celluloid
2
2
  module Logger
3
3
  module_function
4
4
 
5
- # Print a debug message
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
- # Print a warning message
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
- # Print an error message
20
+ # Send an error message
16
21
  def error(string)
17
22
  Celluloid.logger.error(string) if Celluloid.logger
18
23
  end
@@ -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
- @condition.wait(@lock) unless message
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?(Celluloid::SystemEvent)
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?(Celluloid::SystemEvent)
86
+ raise message if message.is_a? SystemEvent
75
87
  message
76
88
  end
77
89
 
@@ -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
- @handlers = []
7
+ @receivers = Set.new
8
+ @timers = Timers.new
6
9
  end
7
10
 
8
11
  # Receive an asynchronous message
9
- def receive(&block)
10
- raise ArgumentError, "receive must be given a block" unless block
12
+ def receive(timeout = nil, &block)
13
+ receiver = Receiver.new block
11
14
 
12
- @handlers << [Fiber.current, block]
13
- Fiber.yield
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
- handler = nil
38
+ receiver = @receivers.find { |r| r.match(message) }
39
+ return unless receiver
19
40
 
20
- @handlers.each_with_index do |(fiber, block), index|
21
- if block.call(message)
22
- handler = index
23
- break
24
- end
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
- if handler
28
- fiber, _ = @handlers.delete_at handler
29
- fiber.resume message
30
- true
31
- else
32
- false
33
- end
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
@@ -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?(Celluloid::ActorProxy)
13
- raise ArgumentError, "not an actor"
12
+ unless actor_singleton.ancestors.include? ActorProxy
13
+ raise TypeError, "not an actor"
14
14
  end
15
15
 
16
16
  @@registry_lock.synchronize do
@@ -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
- fibers = @waiting[name] ||= []
11
- fibers << Fiber.current
12
- Fiber.yield
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
- fibers = @waiting.delete name
19
- return unless fibers
20
-
21
- fibers.each { |fiber| fiber.resume value }
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
@@ -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 = 2, sleep_interval = 30)
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
- Celluloid::Logger.crash("#{@klass} is crashing on initialize repeatedly, sleeping for #{sleep_interval} seconds", ex)
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
- Celluloid::Actor[@name] = @actor if @name
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
@@ -14,7 +14,8 @@ module Celluloid
14
14
  # Run the TCP server event loop
15
15
  def run
16
16
  while true
17
- wait_readable(@server) { on_connect @server.accept }
17
+ wait_readable(@server)
18
+ on_connect @server.accept
18
19
  end
19
20
  end
20
21
 
@@ -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