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