class UUID
Generating UUIDs¶ ↑
Call generate
to generate a new UUID
. The method returns a string in one of three formats. The default format is 36 characters long, and contains the 32 hexadecimal octets and hyphens separating the various value parts. The :compact
format omits the hyphens, while the :urn
format adds the :urn:uuid
prefix.
For example:
uuid = UUID.new 10.times do p uuid.generate end
UUIDs in Brief¶ ↑
UUID
(universally unique identifier) are guaranteed to be unique across time and space.
A UUID
is 128 bit long, and consists of a 60-bit time value, a 16-bit sequence number and a 48-bit node identifier.
The time value is taken from the system clock, and is monotonically incrementing. However, since it is possible to set the system clock backward, a sequence number is added. The sequence number is incremented each time the UUID
generator is started. The combination guarantees that identifiers created on the same machine are unique with a high degree of probability.
Note that due to the structure of the UUID
and the use of sequence number, there is no guarantee that UUID
values themselves are monotonically incrementing. The UUID
value cannot itself be used to sort based on order of creation.
To guarantee that UUIDs are unique across all machines in the network, the IEEE 802 MAC address of the machine’s network interface card is used as the node identifier.
For more information see RFC 4122.
Constants
- CLOCK_GAPS
Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time ticks.
- CLOCK_MULTIPLIER
Clock multiplier. Converts Time (resolution: seconds) to
UUID
clock (resolution: 10ns)- FORMATS
Formats supported by the
UUID
generator.:default
-
Produces 36 characters, including hyphens separating the
UUID
value parts :compact
-
Produces a 32 digits (hexadecimal) value with no hyphens
:urn
-
Adds the prefix
urn:uuid:
to the default format
- SOCKET_NAME
You don’t have to use this, it’s just a good default.
- STATE_FILE_FORMAT
MAC address (48 bits), sequence number and last clock
- VERSION
- VERSION_CLOCK
Version
number stamped into theUUID
to identify it as time-based.
Public Class Methods
Returns the UUID
generator used by generate. Useful if you need to mess with it, e.g. force next sequence when forking (e.g. Unicorn, Resque):
after_fork do
UUID.generator.next_sequence
end
# File lib/uuid.rb 138 def self.generator 139 @uuid ||= new 140 end
The access mode of the state file. Set it with state_file.
# File lib/uuid.rb 114 def self.mode 115 @mode 116 end
# File lib/uuid.rb 118 def self.mode=(mode) 119 @mode = mode 120 end
Create a new UUID
generator. You really only need to do this once.
# File lib/uuid.rb 256 def initialize 257 @drift = 0 258 @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i 259 @mutex = Mutex.new 260 261 state_file = self.class.state_file 262 if state_file && File.size?(state_file) then 263 next_sequence 264 else 265 @mac = mac_address 266 fail "Cannot determine MAC address from any available interface, tried with #{mac_address}" if @mac == 0 267 @sequence = rand 0x10000 268 269 # Ensure the mode is respected, even with a restrictive umask 270 File.open(state_file, 'w') { |f| f.chmod(self.class.mode) } if state_file && !File.exist?(state_file) 271 272 if state_file 273 open_lock 'wb' do |io| 274 write_state io 275 end 276 end 277 end 278 end
Call this to use a UUID
Server
. Expects address to bind to (SOCKET_NAME
is a good default)
# File lib/uuid.rb 145 def self.server=(address) 146 @uuid = Client.new(address) unless Client === @uuid 147 end
Creates an empty state file in Dir.tmpdir/ruby-uuid or the windows common application data directory using mode 0644. Call with a different mode before creating a UUID
generator if you want to open access beyond your user by default.
If the default state dir is not writable, UUID
falls back to ~/.ruby-uuid.
State files are not portable across machines.
# File lib/uuid.rb 158 def self.state_file(mode = 0644) 159 return @state_file unless @state_file.nil? 160 161 @mode = mode 162 163 begin 164 require 'Win32API' 165 166 csidl_common_appdata = 0x0023 167 path = 0.chr * 260 168 get_folder_path = Win32API.new('shell32', 'SHGetFolderPath', 'LLLLP', 'L') 169 get_folder_path.call 0, csidl_common_appdata, 0, 1, path 170 171 state_dir = File.join(path.strip) 172 rescue LoadError 173 state_dir = Dir.tmpdir 174 end 175 176 @state_file = File.join(state_dir, 'ruby-uuid') 177 178 if !File.writable?(state_dir) || (File.exist?(@state_file) && !File.writable?(@state_file)) then 179 @state_file = File.expand_path('.ruby-uuid', '~') 180 end 181 182 @state_file 183 end
Specify the path of the state file. Use this if you need a different location for your state file.
Set to false if your system cannot use a state file (e.g. many shared hosts).
# File lib/uuid.rb 191 def self.state_file=(path) 192 @state_file = path 193 @mode ||= 0644 194 end
Returns true if uuid
is in compact, default or urn formats. Does not validate the layout (RFC 4122 section 4) of the UUID
.
# File lib/uuid.rb 199 def self.validate(uuid) 200 return true if uuid =~ /\A[\da-f]{32}\z/i 201 return true if 202 uuid =~ /\A(urn:uuid:)?[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}\z/i 203 end
Public Instance Methods
Generates a new UUID
string using format
. See FORMATS
for a list of supported formats.
# File lib/uuid.rb 283 def generate(format = :default) 284 template = FORMATS[format] 285 286 raise ArgumentError, "invalid UUID format #{format.inspect}" unless template 287 288 # The clock must be monotonically increasing. The clock resolution is at 289 # best 100 ns (UUID spec), but practically may be lower (on my setup, 290 # around 1ms). If this method is called too fast, we don't have a 291 # monotonically increasing clock, so the solution is to just wait. 292 # 293 # It is possible for the clock to be adjusted backwards, in which case we 294 # would end up blocking for a long time. When backward clock is detected, 295 # we prevent duplicates by asking for a new sequence number and continue 296 # with the new clock. 297 298 clock = @mutex.synchronize do 299 clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0 300 301 if clock > @last_clock then 302 @drift = 0 303 @last_clock = clock 304 elsif clock == @last_clock then 305 drift = @drift += 1 306 307 if drift < 10000 then 308 @last_clock += 1 309 else 310 Thread.pass 311 nil 312 end 313 else 314 next_sequence 315 @last_clock = clock 316 end 317 end until clock 318 319 template % [ 320 clock & 0xFFFFFFFF, 321 (clock >> 32) & 0xFFFF, 322 ((clock >> 48) & 0xFFFF | VERSION_CLOCK), 323 @sequence & 0xFFFF, 324 @mac & 0xFFFFFFFFFFFF 325 ] 326 end
Uses system calls to get a mac address
# File lib/uuid.rb 238 def iee_mac_address 239 begin 240 Mac.addr.gsub(/:|-/, '').hex & 0x7FFFFFFFFFFF 241 rescue 242 0 243 end 244 end
# File lib/uuid.rb 354 def inspect 355 mac = ("%012x" % @mac).scan(/[0-9a-f]{2}/).join(':') 356 "MAC: #{mac} Sequence: #{@sequence}" 357 end
return iee_mac_address
if available, pseudo_mac_address
otherwise
# File lib/uuid.rb 249 def mac_address 250 return iee_mac_address unless iee_mac_address == 0 251 return pseudo_mac_address 252 end
Updates the state file with a new sequence number.
# File lib/uuid.rb 330 def next_sequence 331 if self.class.state_file 332 open_lock 'rb+' do |io| 333 @mac, @sequence, @last_clock = read_state(io) 334 335 io.rewind 336 io.truncate 0 337 338 @sequence += 1 339 340 write_state io 341 end 342 else 343 @sequence += 1 344 end 345 rescue Errno::ENOENT 346 open_lock 'w' do |io| 347 write_state io 348 end 349 ensure 350 @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i 351 @drift = 0 352 end
Generate a pseudo MAC address because we have no pure-ruby way to know the MAC address of the NIC this system uses. Note that cheating with pseudo arresses here is completely legal: see Section 4.5 of RFC4122 for details.
This implementation is shamelessly stolen from
https://github.com/spectra/ruby-uuid/blob/master/uuid.rb
Thanks spectra.
# File lib/uuid.rb 215 def pseudo_mac_address 216 sha1 = ::Digest::SHA1.new 217 256.times do 218 r = [rand(0x100000000)].pack "N" 219 sha1.update r 220 end 221 str = sha1.digest 222 r = rand 14 # 20-6 223 node = str[r, 6] || str 224 if RUBY_VERSION >= "1.9.0" 225 nnode = node.bytes.to_a 226 nnode[0] |= 0x01 227 node = '' 228 nnode.each { |s| node << s.chr } 229 else 230 node[0] |= 0x01 # multicast bit 231 end 232 node.bytes.collect{|b|b.to_s(16)}.join.hex & 0x7FFFFFFFFFFF 233 end
Protected Instance Methods
Open the state file with an exclusive lock and access mode mode
.
# File lib/uuid.rb 363 def open_lock(mode) 364 File.open self.class.state_file, mode, self.class.mode do |io| 365 begin 366 io.flock File::LOCK_EX 367 yield io 368 ensure 369 io.flock File::LOCK_UN 370 end 371 end 372 end
Read the state from io
# File lib/uuid.rb 376 def read_state(io) 377 mac1, mac2, seq, last_clock = io.read(32).unpack(STATE_FILE_FORMAT) 378 mac = (mac1 << 32) + mac2 379 380 return mac, seq, last_clock 381 end
Write that state to io
# File lib/uuid.rb 386 def write_state(io) 387 mac2 = @mac & 0xffffffff 388 mac1 = (@mac >> 32) & 0xffff 389 390 io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT) 391 end