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 the
UUID
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 129 def self.generator 130 @uuid ||= new 131 end
The access mode of the state file. Set it with state_file.
# File lib/uuid.rb 105 def self.mode 106 @mode 107 end
# File lib/uuid.rb 109 def self.mode=(mode) 110 @mode = mode 111 end
Create a new UUID
generator. You really only need to do this once.
# File lib/uuid.rb 247 def initialize 248 @drift = 0 249 @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i 250 @mutex = Mutex.new 251 252 state_file = self.class.state_file 253 if state_file && File.size?(state_file) then 254 next_sequence 255 else 256 @mac = mac_address 257 fail "Cannot determine MAC address from any available interface, tried with #{mac_address}" if @mac == 0 258 @sequence = rand 0x10000 259 260 # Ensure the mode is respected, even with a restrictive umask 261 File.open(state_file, 'w') { |f| f.chmod(self.class.mode) } if state_file && !File.exist?(state_file) 262 263 if state_file 264 open_lock 'wb' do |io| 265 write_state io 266 end 267 end 268 end 269 end
Call this to use a UUID
Server
. Expects address to bind to (SOCKET_NAME
is a good default)
# File lib/uuid.rb 136 def self.server=(address) 137 @uuid = Client.new(address) unless Client === @uuid 138 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 149 def self.state_file(mode = 0644) 150 return @state_file unless @state_file.nil? 151 152 @mode = mode 153 154 begin 155 require 'Win32API' 156 157 csidl_common_appdata = 0x0023 158 path = 0.chr * 260 159 get_folder_path = Win32API.new('shell32', 'SHGetFolderPath', 'LLLLP', 'L') 160 get_folder_path.call 0, csidl_common_appdata, 0, 1, path 161 162 state_dir = File.join(path.strip) 163 rescue LoadError 164 state_dir = Dir.tmpdir 165 end 166 167 @state_file = File.join(state_dir, 'ruby-uuid') 168 169 if !File.writable?(state_dir) || (File.exist?(@state_file) && !File.writable?(@state_file)) then 170 @state_file = File.expand_path('.ruby-uuid', '~') 171 end 172 173 @state_file 174 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 182 def self.state_file=(path) 183 @state_file = path 184 @mode ||= 0644 185 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 190 def self.validate(uuid) 191 return true if uuid =~ /\A[\da-f]{32}\z/i 192 return true if 193 uuid =~ /\A(urn:uuid:)?[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}\z/i 194 end
Public Instance Methods
Generates a new UUID
string using format
. See FORMATS
for a list of supported formats.
# File lib/uuid.rb 274 def generate(format = :default) 275 template = FORMATS[format] 276 277 raise ArgumentError, "invalid UUID format #{format.inspect}" unless template 278 279 # The clock must be monotonically increasing. The clock resolution is at 280 # best 100 ns (UUID spec), but practically may be lower (on my setup, 281 # around 1ms). If this method is called too fast, we don't have a 282 # monotonically increasing clock, so the solution is to just wait. 283 # 284 # It is possible for the clock to be adjusted backwards, in which case we 285 # would end up blocking for a long time. When backward clock is detected, 286 # we prevent duplicates by asking for a new sequence number and continue 287 # with the new clock. 288 289 clock = @mutex.synchronize do 290 clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0 291 292 if clock > @last_clock then 293 @drift = 0 294 @last_clock = clock 295 elsif clock == @last_clock then 296 drift = @drift += 1 297 298 if drift < 10000 then 299 @last_clock += 1 300 else 301 Thread.pass 302 nil 303 end 304 else 305 next_sequence 306 @last_clock = clock 307 end 308 end until clock 309 310 template % [ 311 clock & 0xFFFFFFFF, 312 (clock >> 32) & 0xFFFF, 313 ((clock >> 48) & 0xFFFF | VERSION_CLOCK), 314 @sequence & 0xFFFF, 315 @mac & 0xFFFFFFFFFFFF 316 ] 317 end
Uses system calls to get a mac address
# File lib/uuid.rb 229 def iee_mac_address 230 begin 231 Mac.addr.gsub(/:|-/, '').hex & 0x7FFFFFFFFFFF 232 rescue 233 0 234 end 235 end
# File lib/uuid.rb 345 def inspect 346 mac = ("%012x" % @mac).scan(/[0-9a-f]{2}/).join(':') 347 "MAC: #{mac} Sequence: #{@sequence}" 348 end
return iee_mac_address
if available, pseudo_mac_address
otherwise
# File lib/uuid.rb 240 def mac_address 241 return iee_mac_address unless iee_mac_address == 0 242 return pseudo_mac_address 243 end
Updates the state file with a new sequence number.
# File lib/uuid.rb 321 def next_sequence 322 if self.class.state_file 323 open_lock 'rb+' do |io| 324 @mac, @sequence, @last_clock = read_state(io) 325 326 io.rewind 327 io.truncate 0 328 329 @sequence += 1 330 331 write_state io 332 end 333 else 334 @sequence += 1 335 end 336 rescue Errno::ENOENT 337 open_lock 'w' do |io| 338 write_state io 339 end 340 ensure 341 @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i 342 @drift = 0 343 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 206 def pseudo_mac_address 207 sha1 = ::Digest::SHA1.new 208 256.times do 209 r = [rand(0x100000000)].pack "N" 210 sha1.update r 211 end 212 str = sha1.digest 213 r = rand 14 # 20-6 214 node = str[r, 6] || str 215 if RUBY_VERSION >= "1.9.0" 216 nnode = node.bytes.to_a 217 nnode[0] |= 0x01 218 node = '' 219 nnode.each { |s| node << s.chr } 220 else 221 node[0] |= 0x01 # multicast bit 222 end 223 node.bytes.collect{|b|b.to_s(16)}.join.hex & 0x7FFFFFFFFFFF 224 end
Protected Instance Methods
Open the state file with an exclusive lock and access mode mode
.
# File lib/uuid.rb 354 def open_lock(mode) 355 File.open self.class.state_file, mode, self.class.mode do |io| 356 begin 357 io.flock File::LOCK_EX 358 yield io 359 ensure 360 io.flock File::LOCK_UN 361 end 362 end 363 end
Read the state from io
# File lib/uuid.rb 367 def read_state(io) 368 mac1, mac2, seq, last_clock = io.read(32).unpack(STATE_FILE_FORMAT) 369 mac = (mac1 << 32) + mac2 370 371 return mac, seq, last_clock 372 end
Write that state to io
# File lib/uuid.rb 377 def write_state(io) 378 mac2 = @mac & 0xffffffff 379 mac1 = (@mac >> 32) & 0xffff 380 381 io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT) 382 end