kulesa-celluloid 0.10.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+ module Celluloid
2
+ # DEPRECATED: please use Celluloid::Worker instead
3
+ class Pool
4
+ include Celluloid
5
+ trap_exit :crash_handler
6
+ attr_reader :max_actors
7
+
8
+ # Takes a class of actor to pool and a hash of options:
9
+ #
10
+ # * initial_size: how many actors to eagerly create
11
+ # * max_size: maximum number of actors (default one actor per CPU core)
12
+ # * args: an array of arguments to pass to the actor's initialize
13
+ def initialize(klass, options = {})
14
+ raise ArgumentError, "A Pool has a minimum size of 2" if options[:max_size] && options[:max_size] < 2
15
+ opts = {
16
+ :initial_size => 1,
17
+ :max_size => [Celluloid.cores, 2].max,
18
+ :args => []
19
+ }.merge(options)
20
+
21
+ @klass, @args = klass, opts[:args]
22
+ @max_actors = opts[:max_size]
23
+ @idle_actors, @running_actors = 0, 0
24
+ @actors = []
25
+
26
+ opts[:initial_size].times do
27
+ @actors << spawn
28
+ @idle_actors += 1
29
+ end
30
+ end
31
+
32
+ # Get an actor from the pool. Actors taken from the pool must be put back
33
+ # with Pool#put. Alternatively, you can use get with a block form:
34
+ #
35
+ # pool.get { |actor| ... }
36
+ #
37
+ # This will automatically return actors to the pool when the block completes
38
+ def get
39
+ if @max_actors and @running_actors == @max_actors
40
+ wait :ready
41
+ end
42
+
43
+ actor = @actors.shift
44
+ if actor
45
+ @idle_actors -= 1
46
+ else
47
+ actor = spawn
48
+ end
49
+
50
+ if block_given?
51
+ begin
52
+ yield actor
53
+ rescue => ex
54
+ end
55
+
56
+ put actor
57
+ abort ex if ex
58
+ nil
59
+ else
60
+ actor
61
+ end
62
+ end
63
+
64
+ # Return an actor to the pool
65
+ def put(actor)
66
+ begin
67
+ raise TypeError, "expecting a #{@klass} actor" unless actor.is_a? @klass
68
+ rescue DeadActorError
69
+ # The actor may have died before it was handed back to us
70
+ # We'll let the crash_handler deal with it in due time
71
+ return
72
+ end
73
+
74
+ @actors << actor
75
+ @idle_actors += 1
76
+ end
77
+
78
+ # Number of active actors in this pool
79
+ def size
80
+ @running_actors
81
+ end
82
+
83
+ # Number of idle actors in the pool
84
+ def idle_count
85
+ @idle_actors
86
+ end
87
+ alias_method :idle_size, :idle_count
88
+
89
+ # Handle crashed actors
90
+ def crash_handler(actor, reason)
91
+ @idle_actors -= 1 if @actors.delete actor
92
+ @running_actors -= 1
93
+
94
+ # If we were maxed out before...
95
+ signal :ready if @max_actors and @running_actors + 1 == @max_actors
96
+ end
97
+
98
+ # Spawn an actor of the given class
99
+ def spawn
100
+ worker = @klass.new_link(*@args)
101
+ @running_actors += 1
102
+ worker
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,70 @@
1
+ require 'set'
2
+
3
+ module Celluloid
4
+ # Allow methods to directly interact with the actor protocol
5
+ class Receivers
6
+ def initialize
7
+ @receivers = Set.new
8
+ @timers = Timers.new
9
+ end
10
+
11
+ # Receive an asynchronous message
12
+ def receive(timeout = nil, &block)
13
+ if Celluloid.exclusive?
14
+ Thread.mailbox.receive(timeout, &block)
15
+ else
16
+ receiver = Receiver.new block
17
+
18
+ if timeout
19
+ receiver.timer = @timers.add(timeout) do
20
+ @receivers.delete receiver
21
+ receiver.resume
22
+ end
23
+ end
24
+
25
+ @receivers << receiver
26
+ Task.suspend :receiving
27
+ end
28
+ end
29
+
30
+ # How long to wait until the next timer fires
31
+ def wait_interval
32
+ @timers.wait_interval
33
+ end
34
+
35
+ # Fire any pending timers
36
+ def fire_timers
37
+ @timers.fire
38
+ end
39
+
40
+ # Handle incoming messages
41
+ def handle_message(message)
42
+ receiver = @receivers.find { |r| r.match(message) }
43
+ return unless receiver
44
+
45
+ @receivers.delete receiver
46
+ @timers.cancel receiver.timer if receiver.timer
47
+ receiver.resume message
48
+ end
49
+ end
50
+
51
+ # Methods blocking on a call to receive
52
+ class Receiver
53
+ attr_accessor :timer
54
+
55
+ def initialize(block)
56
+ @block = block
57
+ @task = Task.current
58
+ @timer = nil
59
+ end
60
+
61
+ # Match a message with this receiver's block
62
+ def match(message)
63
+ @block ? @block.call(message) : true
64
+ end
65
+
66
+ def resume(message = nil)
67
+ @task.resume message
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,35 @@
1
+ require 'thread'
2
+
3
+ module Celluloid
4
+ # The Registry allows us to refer to specific actors by human-meaningful names
5
+ module Registry
6
+ @@registry = {}
7
+ @@registry_lock = Mutex.new
8
+
9
+ # Register an Actor
10
+ def []=(name, actor)
11
+ actor_singleton = class << actor; self; end
12
+ unless actor_singleton.ancestors.include? ActorProxy
13
+ raise TypeError, "not an actor"
14
+ end
15
+
16
+ @@registry_lock.synchronize do
17
+ @@registry[name.to_sym] = actor
18
+ end
19
+
20
+ actor.mailbox.system_event NamingRequest.new(name.to_sym)
21
+ end
22
+
23
+ # Retrieve an actor by name
24
+ def [](name)
25
+ @@registry_lock.synchronize do
26
+ @@registry[name.to_sym]
27
+ end
28
+ end
29
+
30
+ # List all registered actors by name
31
+ def registered
32
+ @@registry_lock.synchronize { @@registry.keys }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ module Celluloid
2
+ # Responses to calls
3
+ class Response
4
+ attr_reader :call, :value
5
+
6
+ def initialize(call, value)
7
+ @call, @value = call, value
8
+ end
9
+ end
10
+
11
+ # Call completed successfully
12
+ class SuccessResponse < Response; end
13
+
14
+ # Call was aborted due to caller error
15
+ class ErrorResponse < Response
16
+ def value
17
+ if super.is_a? AbortError
18
+ # Aborts are caused by caller error, so ensure they capture the
19
+ # caller's backtrace instead of the receiver's
20
+ raise super.cause.exception
21
+ else
22
+ raise super
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,2 @@
1
+ require File.expand_path('../../../spec/support/actor_examples', __FILE__)
2
+ require File.expand_path('../../../spec/support/mailbox_examples', __FILE__)
@@ -0,0 +1,51 @@
1
+ module Celluloid
2
+ # Event signaling between methods of the same object
3
+ class Signals
4
+ attr_reader :waiting
5
+
6
+ def initialize
7
+ @waiting = {}
8
+ end
9
+
10
+ # Wait for the given signal and return the associated value
11
+ def wait(signal)
12
+ raise "cannot wait for signals while exclusive" if Celluloid.exclusive?
13
+
14
+ tasks = @waiting[signal]
15
+ case tasks
16
+ when Array
17
+ tasks << Task.current
18
+ when NilClass
19
+ @waiting[signal] = Task.current
20
+ else
21
+ @waiting[signal] = [tasks, Task.current]
22
+ end
23
+
24
+ Task.suspend :sigwait
25
+ end
26
+
27
+ # Send a signal to all method calls waiting for the given name
28
+ # Returns true if any calls were signaled, or false otherwise
29
+ def send(name, value = nil)
30
+ tasks = @waiting.delete name
31
+
32
+ case tasks
33
+ when Array
34
+ tasks.each { |task| run_task task, value }
35
+ true if tasks.size > 0
36
+ when NilClass
37
+ false
38
+ else
39
+ run_task tasks, value
40
+ true
41
+ end
42
+ end
43
+
44
+ # Run the given task, reporting errors that occur
45
+ def run_task(task, value)
46
+ task.resume(value)
47
+ rescue => ex
48
+ Logger.crash("signaling error", ex)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,69 @@
1
+ module Celluloid
2
+ # Supervisors are actors that watch over other actors and restart them if
3
+ # they crash
4
+ class Supervisor
5
+ include Celluloid
6
+ trap_exit :restart_actor
7
+
8
+ # Retrieve the actor this supervisor is supervising
9
+ attr_reader :actor
10
+
11
+ class << self
12
+ # Define the root of the supervision tree
13
+ attr_accessor :root
14
+
15
+ def supervise(klass, *args, &block)
16
+ new(nil, klass, *args, &block)
17
+ end
18
+
19
+ def supervise_as(name, klass, *args, &block)
20
+ new(name, klass, *args, &block)
21
+ end
22
+ end
23
+
24
+ def initialize(name, klass, *args, &block)
25
+ @name, @klass, @args, @block = name, klass, args, block
26
+ @started = false
27
+
28
+ start_actor
29
+ end
30
+
31
+ def finalize
32
+ @actor.terminate if @actor and @actor.alive?
33
+ end
34
+
35
+ def start_actor(start_attempts = 3, sleep_interval = 30)
36
+ failures = 0
37
+
38
+ begin
39
+ @actor = @klass.new_link(*@args, &@block)
40
+ rescue
41
+ failures += 1
42
+ if failures >= start_attempts
43
+ failures = 0
44
+
45
+ Logger.warn("#{@klass} is crashing on initialize too quickly, sleeping for #{sleep_interval} seconds")
46
+ sleep sleep_interval
47
+ end
48
+ retry
49
+ end
50
+
51
+ @started = true
52
+ Actor[@name] = @actor if @name
53
+ end
54
+
55
+ # When actors die, regardless of the reason, restart them
56
+ def restart_actor(actor, reason)
57
+ # If the actor we're supervising exited cleanly, exit the supervisor cleanly too
58
+ terminate unless reason
59
+
60
+ start_actor if @started
61
+ end
62
+
63
+ def inspect
64
+ str = "#<#{self.class}(#{@klass}):0x#{object_id.to_s(16)}"
65
+ str << " " << @args.map { |arg| arg.inspect }.join(' ') unless @args.empty?
66
+ str << ">"
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,81 @@
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
+ class TerminatedError < StandardError; end # kill a running fiber
8
+
9
+ attr_reader :type
10
+ attr_accessor :status
11
+
12
+ # Obtain the current task
13
+ def self.current
14
+ Fiber.current.task or raise "no task for this Fiber"
15
+ end
16
+
17
+ # Suspend the running task, deferring to the scheduler
18
+ def self.suspend(status)
19
+ task = Task.current
20
+ task.status = status
21
+
22
+ result = Fiber.yield
23
+ raise TerminatedError, "task was terminated" if result == TerminatedError
24
+ task.status = :running
25
+
26
+ result
27
+ end
28
+
29
+ # Run the given block within a task
30
+ def initialize(type)
31
+ @type = type
32
+ @status = :new
33
+
34
+ actor = Thread.current[:actor]
35
+ mailbox = Thread.current[:mailbox]
36
+
37
+ @fiber = Fiber.new do
38
+ @status = :running
39
+ Thread.current[:actor] = actor
40
+ Thread.current[:mailbox] = mailbox
41
+ Fiber.current.task = self
42
+ actor.tasks << self
43
+
44
+ begin
45
+ yield
46
+ rescue TerminatedError
47
+ # Task was explicitly terminated
48
+ ensure
49
+ actor.tasks.delete self
50
+ end
51
+ end
52
+ end
53
+
54
+ # Resume a suspended task, giving it a value to return if needed
55
+ def resume(value = nil)
56
+ @fiber.resume value
57
+ nil
58
+ rescue FiberError
59
+ raise DeadTaskError, "cannot resume a dead task"
60
+ rescue RuntimeError => ex
61
+ # These occur spuriously on 1.9.3 if we shut down an actor with running tasks
62
+ return if ex.message == ""
63
+ raise
64
+ end
65
+
66
+ # Terminate this task
67
+ def terminate
68
+ resume TerminatedError if @fiber.alive?
69
+ rescue FiberError
70
+ # If we're getting this the task should already be dead
71
+ end
72
+
73
+ # Is the current task still running?
74
+ def running?; @fiber.alive?; end
75
+
76
+ # Nicer string inspect for tasks
77
+ def inspect
78
+ "<Celluloid::Task:0x#{object_id.to_s(16)} @type=#{@type.inspect}, @status=#{@status.inspect}, @running=#{@fiber.alive?}>"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,35 @@
1
+ module Celluloid
2
+ # An abstraction around threads from the InternalPool which ensures we don't
3
+ # accidentally do things to threads which have been returned to the pool,
4
+ # such as, say, killing them
5
+ class ThreadHandle
6
+ def initialize
7
+ @mutex = Mutex.new
8
+ @join = ConditionVariable.new
9
+
10
+ @thread = InternalPool.get do
11
+ begin
12
+ yield
13
+ ensure
14
+ @mutex.synchronize do
15
+ @thread = nil
16
+ @join.broadcast
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def alive?
23
+ @mutex.synchronize { @thread.alive? if @thread }
24
+ end
25
+
26
+ def kill
27
+ !!@mutex.synchronize { @thread.kill if @thread }
28
+ end
29
+
30
+ def join
31
+ @mutex.synchronize { @join.wait(@mutex) if @thread }
32
+ nil
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,110 @@
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, recurring = false, &block)
10
+ Timer.new(self, interval, recurring, 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, :recurring
79
+
80
+ def initialize(timers, interval, recurring, block)
81
+ @timers, @interval, @recurring = timers, interval, recurring
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
+ reset if recurring
106
+ @block.call
107
+ end
108
+ alias_method :call, :fire
109
+ end
110
+ end
@@ -0,0 +1,38 @@
1
+ require 'securerandom'
2
+
3
+ module Celluloid
4
+ # Clearly Ruby doesn't have enough UUID libraries
5
+ # This one aims to be fast and simple with good support for multiple threads
6
+ # If there's a better UUID library I can use with similar multithreaded
7
+ # performance, I certainly wouldn't mind using a gem for this!
8
+ module UUID
9
+ values = SecureRandom.hex(9).match(/(.{8})(.{4})(.{3})(.{3})/)
10
+ PREFIX = "#{values[1]}-#{values[2]}-4#{values[3]}-8#{values[4]}".freeze
11
+ BLOCK_SIZE = 0x10000
12
+
13
+ @counter = 0
14
+ @counter_mutex = Mutex.new
15
+
16
+ def self.generate
17
+ thread = Thread.current
18
+
19
+ unless thread.uuid_limit
20
+ @counter_mutex.synchronize do
21
+ block_base = @counter
22
+ @counter += BLOCK_SIZE
23
+ thread.uuid_counter = block_base
24
+ thread.uuid_limit = @counter - 1
25
+ end
26
+ end
27
+
28
+ counter = thread.uuid_counter
29
+ if thread.uuid_counter >= thread.uuid_limit
30
+ thread.uuid_counter = thread.uuid_limit = nil
31
+ else
32
+ thread.uuid_counter += 1
33
+ end
34
+
35
+ "#{PREFIX}-#{sprintf("%012x", counter)}".freeze
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ module Celluloid
2
+ VERSION = '0.10.2'
3
+ def self.version; VERSION; end
4
+ end