Class: UUID

Inherits:
Object
  • Object
show all
Defined in:
lib/uuid.rb

Overview

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.

Constant Summary collapse

VERSION =
'2.0.0'
CLOCK_MULTIPLIER =

Clock multiplier. Converts Time (resolution: seconds) to UUID clock (resolution: 10ns)

10000000
CLOCK_GAPS =

Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time ticks.

100000
VERSION_CLOCK =

Version number stamped into the UUID to identify it as time-based.

0x0100
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

{
  :compact => '%08x%04x%04x%04x%012x',
  :default => '%08x-%04x-%04x-%04x-%012x',
  :urn     => 'urn:uuid:%08x-%04x-%04x-%04x-%012x',
}
STATE_FILE_FORMAT =

MAC address (48 bits), sequence number and last clock

'SLLQ'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeUUID

Create a new UUID generator. You really only need to do this once.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/uuid.rb', line 162

def initialize
  @drift = 0
  @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
  @mutex = Mutex.new

  if File.exist? self.class.state_file then
    File.open self.class.state_file, 'r', @mode do |io|
      @mac, @sequence, = read_state io
    end
  else
    config = ''

    @mac = Mac.addr.gsub(':', '').hex & 0x7FFFFFFFFFFF
    @sequence = rand 0x10000

    open_lock 'w' do |io| write_state io end
  end
end

Class Method Details

.generate(format = :default) ⇒ Object

Generates a new UUID string using format. See FORMATS for a list of supported formats.



117
118
119
120
# File 'lib/uuid.rb', line 117

def self.generate(format = :default)
  @uuid ||= new
  @uuid.generate formate
end

.modeObject

The access mode of the state file. Set it with state_file.



109
110
111
# File 'lib/uuid.rb', line 109

def self.mode
  @mode
end

.state_file(mode = 0644) ⇒ Object

Creates an empty state file in /var/tmp/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.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/uuid.rb', line 132

def self.state_file(mode = 0644)
  return @state_file if @state_file

  @mode = mode

  begin
    require 'Win32API'

    csidl_common_appdata = 0x0023
    path = 0.chr * 260
    get_folder_path = Win32API.new 'shell32', 'SHGetFolderPath', 'LLLLP', 'L'
    get_folder_path.call 0, csidl_common_appdata, 0, 1, path

    state_dir = File.join path.strip
  rescue LoadError
    state_dir = File.join '', 'var', 'tmp'
  end

  if File.writable? state_dir then
    @state_file = File.join state_dir, 'ruby-uuid'
  else
    @state_file = File.expand_path File.join('~', '.ruby-uuid')
  end

  @state_file
end

Instance Method Details

#generate(format = :default) ⇒ Object

Generates a new UUID string using format. See FORMATS for a list of supported formats.

Raises:

  • (ArgumentError)


185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/uuid.rb', line 185

def generate(format = :default)
  template = FORMATS[format]

  raise ArgumentError, "invalid UUID format #{format.inspect}" if
    template.nil?

  # The clock must be monotonically increasing. The clock resolution is at
  # best 100 ns (UUID spec), but practically may be lower (on my setup,
  # around 1ms). If this method is called too fast, we don't have a
  # monotonically increasing clock, so the solution is to just wait.
  #
  # It is possible for the clock to be adjusted backwards, in which case we
  # would end up blocking for a long time. When backward clock is detected,
  # we prevent duplicates by asking for a new sequence number and continue
  # with the new clock.

  clock = @mutex.synchronize do
    clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0

    if clock > @last_clock then
      @drift = 0
      @last_clock = clock
    elsif clock = @last_clock then
      drift = @drift += 1

      if drift < 10000 then
        @last_clock += 1
      else
        Thread.pass
        nil
      end
    else
      next_sequence
      @last_clock = clock
    end
  end until clock

  template % [
      clock        & 0xFFFFFFFF,
     (clock >> 32) & 0xFFFF,
    ((clock >> 48) & 0xFFFF | VERSION_CLOCK),
    @sequence      & 0xFFFF,
    @mac           & 0xFFFFFFFFFFFF
  ]
end

#inspectObject



287
288
289
290
# File 'lib/uuid.rb', line 287

def inspect
  mac = ("%08x" % @mac).scan(/[0-9a-f]{2}/).join(':')
  "MAC: #{mac}  Sequence: #{@sequence}"
end

#next_sequence(config = nil) ⇒ Object

Updates the state file with a new sequence number.



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/uuid.rb', line 234

def next_sequence(config = nil)
  open_lock 'r+' do |io|
    @mac, @sequence, @last_clock = read_state io

    io.rewind
    io.truncate 0

    @sequence += 1

    write_state io
  end
rescue Errno::ENOENT
  open_lock 'w' do |io| write_state io end
ensure
  @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
  @drift = 0
end

#open_lock(mode) ⇒ Object

Open the state file with an exclusive lock and access mode mode.



255
256
257
258
259
260
261
262
263
264
265
# File 'lib/uuid.rb', line 255

def open_lock(mode)
  File.open self.class.state_file, mode, self.class.mode do |io|
    begin
      io.flock File::LOCK_EX

      yield io
    ensure
      io.flock File::LOCK_UN
    end
  end
end

#read_state(io) ⇒ Object

Read the state from io



270
271
272
273
274
275
# File 'lib/uuid.rb', line 270

def read_state(io)
  mac1, mac2, seq, last_clock = io.read(32).unpack STATE_FILE_FORMAT
  mac = (mac1 << 32) + mac2

  return mac, seq, last_clock
end

#write_state(io) ⇒ Object

Write that state to io



280
281
282
283
284
285
# File 'lib/uuid.rb', line 280

def write_state(io)
  mac2 =  @mac        & 0xffffffff
  mac1 = (@mac >> 32) & 0xffff

  io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT)
end