kulesa-celluloid 0.10.2
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 +116 -0
- data/lib/celluloid/actor.rb +279 -0
- data/lib/celluloid/actor_proxy.rb +95 -0
- data/lib/celluloid/calls.rb +105 -0
- data/lib/celluloid/core_ext.rb +25 -0
- data/lib/celluloid/cpu_counter.rb +16 -0
- data/lib/celluloid/events.rb +26 -0
- data/lib/celluloid/fiber.rb +32 -0
- data/lib/celluloid/fsm.rb +151 -0
- data/lib/celluloid/future.rb +110 -0
- data/lib/celluloid/group.rb +90 -0
- data/lib/celluloid/internal_pool.rb +62 -0
- data/lib/celluloid/links.rb +61 -0
- data/lib/celluloid/logger.rb +53 -0
- data/lib/celluloid/mailbox.rb +134 -0
- data/lib/celluloid/pool.rb +105 -0
- data/lib/celluloid/receivers.rb +70 -0
- data/lib/celluloid/registry.rb +35 -0
- data/lib/celluloid/responses.rb +26 -0
- data/lib/celluloid/rspec.rb +2 -0
- data/lib/celluloid/signals.rb +51 -0
- data/lib/celluloid/supervisor.rb +69 -0
- data/lib/celluloid/task.rb +81 -0
- data/lib/celluloid/thread_handle.rb +35 -0
- data/lib/celluloid/timers.rb +110 -0
- data/lib/celluloid/uuid.rb +38 -0
- data/lib/celluloid/version.rb +4 -0
- data/lib/celluloid/worker.rb +78 -0
- data/lib/celluloid.rb +355 -0
- data/spec/support/actor_examples.rb +565 -0
- data/spec/support/mailbox_examples.rb +52 -0
- metadata +142 -0
@@ -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,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
|