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.
@@ -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