Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added: UUID.generator returns the current UUID generator. Particularl…

…y useful for calling next_sequence on the generator when forking a process.

Added: UUID::Server and UUID::Client so you can have one process serving you UUIDs.

Added: UUID command line tool.  Yay!
  • Loading branch information...
commit de10af13ed2cdb9ef149ed2e0c990c481cffe7c8 1 parent 67a7da8
@assaf authored
View
6 CHANGELOG
@@ -1,3 +1,9 @@
+2.3.0 (2010-04-07)
+* Added: UUID.generator returns the current UUID generator. Particularly useful for calling
+ next_sequence on the generator when forking a process.
+* Added: UUID::Server and UUID::Client so you can have one process serving you UUIDs.
+* Added: UUID command line tool. Yay!
+
2.2.0 (2010-02-18)
* Added: set UUID.state_file = false if you cannot use a state file (e.g. shared hosting environment)
View
29 README.rdoc
@@ -76,15 +76,34 @@ Engine) you can simple turn it off:
State files are not portable across machines.
+Note: when using a forking server (Unicorn, Resque, Pipemaster, etc) you don't
+want your forked processes using the same sequence number. Make sure to
+increment the sequence number each time a worker forks.
-== Latest and Greatest
+For example, in config/unicorn.rb:
+
+ after_fork do |server, worker|
+ UUID.generator.next_sequence
+ end
+
+
+== Command Line
+
+You can run uuid from the command line, generating new UUID to stdout:
-Stable release are made through RubyForge, to upgrade simply:
+ $ uuid
- gem upgrade uuid
+Multiple UUIDs in a sequence, separated with a newline:
-You can subscribe for notifications of new releases through the reliable-msg
-project page at http://rubyforge.org/projects/reliable-msg
+ $ uuid --count 10
+
+You can also run client and server from the command line
+
+ $ uuid --server &
+ $ uuid --socket /var/lib/uuid.sock
+
+
+== Latest and Greatest
Source code and documentation hosted on Github: http://github.com/assaf/uuid
View
18 Rakefile
@@ -8,24 +8,6 @@ desc "Default Task"
task :default => :test
-desc "If you're building from sources, run this task first to setup the necessary dependencies"
-task 'setup' do
- missing = spec.dependencies.select { |dep| Gem::SourceIndex.from_installed_gems.search(dep).empty? }
- missing.each do |dep|
- if Gem::SourceIndex.from_installed_gems.search(dep).empty?
- puts "Installing #{dep.name} ..."
- rb_bin = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
- args = []
- args << rb_bin << '-S' << 'gem' << 'install' << dep.name
- args << '--version' << dep.version_requirements.to_s
- args << '--source' << 'http://gems.rubyforge.org'
- args << '--install-dir' << ENV['GEM_HOME'] if ENV['GEM_HOME']
- sh *args
- end
- end
-end
-
-
desc "Run all test cases"
Rake::TestTask.new do |test|
test.verbose = true
View
55 bin/uuid
@@ -0,0 +1,55 @@
+#!/usr/bin/env ruby
+require "uuid"
+require "optparse"
+
+address = nil
+count = 1
+format = :default
+server = false
+
+opts = OptionParser.new("", 24, ' ') do |opts|
+ opts.banner = "Usage: #{File.basename($0)} [options]"
+
+ opts.separator "\nOptions:"
+ opts.on("-s", "--socket {HOST:PORT|PATH}",
+ "communicate on HOST:PORT or PATH (default: #{UUID::SOCKET_NAME})") do |value|
+ address = value
+ end
+
+ opts.on("-S", "--server", "run as a server") do |value|
+ server = value ? true : false
+ end
+
+ opts.on("-F", "--format {FORMAT}", "UUID format (client only)") do |value|
+ format = value.to_sym
+ end
+
+ opts.on("-C", "--count {COUNT}", "returns give number of UUIDs") do |value|
+ count = value.to_i
+ end
+
+ opts.on("-h", "--help", "Show this message") do
+ puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '')
+ exit
+ end
+
+ opts.on("-v", "--version", "Show version") do
+ puts "UUID v#{UUID::VERSION}"
+ exit
+ end
+
+ opts.parse! ARGV
+end
+
+
+if server
+ $stdout << "Starting UUID server on #{address}\n"
+ UUID::Server.new.listen(address || UUID::SOCKET_NAME)
+else
+ UUID.server = address if address
+ $stdout << UUID.generate(format)
+ (count - 1).times do
+ $stdout.putc "\n"
+ $stdout << UUID.generate(format)
+ end
+end
View
131 lib/uuid.rb
@@ -9,8 +9,7 @@
require 'fileutils'
require 'thread'
require 'tmpdir'
-
-require 'rubygems'
+require 'socket'
require 'macaddr'
@@ -123,6 +122,24 @@ def self.generate(format = :default)
end
##
+ # 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
+ def self.generator
+ @uuid ||= new
+ end
+
+ ##
+ # Call this to use a UUID Server. Expects address to bind to (SOCKET_NAME is
+ # a good default)
+ def self.server=(address)
+ @uuid = Client.new(address) unless Client === @uuid
+ end
+
+ ##
# 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
@@ -313,4 +330,114 @@ def write_state(io)
io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT)
end
+
+ # You don't have to use this, it's just a good default.
+ SOCKET_NAME ="/var/lib/uuid.sock"
+
+ # With UUID server you don't have to worry about multiple processes
+ # synchronizing over the state file, calling next_sequence when forking a
+ # process and other things you're probably not worried about (because
+ # statistically they're very unlikely to break your code).
+ #
+ # But if you are worried about and thought to yourself, "what would a simple
+ # UUID server look like?", here's the answer. The protocol is dead simple:
+ # client sends a byte, server responds with a UUID. Can use TCP or domain
+ # sockets.
+ class Server
+
+ # Create new server. Nothing interesting happens until you call listen.
+ def initialize()
+ @generator = UUID.new
+ end
+
+ # Start the server listening on the specific address. Blocks and never
+ # returns. Address can be:
+ # - A Socket object
+ # - UNIX domain socket name (e.g. /var/run/uuid.sock, must start with /)
+ # - IP address, colon, port (e.g. localhost:1337)
+ def listen(address)
+ sock = bind(address)
+ while client = sock.accept
+ Thread.start(client) do |client|
+ while client.read 1
+ client.write @generator.generate
+ end
+ end
+ end
+ end
+
+ # Returns UNIXServer or TCPServer from address. Returns argument if not a
+ # string, so can pass through (see #listen).
+ def bind(address)
+ return address unless String === address
+ if address[0] == ?/
+ if File.exist?(address)
+ raise ArgumentError, "#{address} is not a socket" unless File.socket?(address)
+ File.unlink(address)
+ end
+ sock = UNIXServer.new(address)
+ File.chmod 0666, address
+ elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
+ sock = TCPServer.new($1, $2.to_i)
+ else
+ raise ArgumentError, "Don't know how to bind #{address}"
+ end
+ sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
+ sock
+ end
+
+ end
+
+
+ # Every server needs a client. Client provides you with the single ultimate
+ # method: #generate. Typically you'll use this instead of the local UUID
+ # generator:
+ # UUID.server = UUID::SOCKET_NAME
+ class Client
+
+ def initialize(address)
+ @socket = connect(address)
+ at_exit { close }
+ end
+
+ # Talks to server and returns new UUID in specified format.
+ def generate(format = :default)
+ @socket.write "\0"
+ uuid = @socket.read(36)
+ return uuid if format == :default
+ template = FORMATS[format]
+ raise ArgumentError, "invalid UUID format #{format.inspect}" unless template
+ template % uuid.split("-").map { |p| p.to_i(16) }
+ end
+
+ # Returns UNIXSocket or TCPSocket from address. Returns argument if not a
+ # string, so can pass through.
+ def connect(address)
+ return address unless String === address
+ if address[0] == ?/
+ sock = UNIXSocket.new(address)
+ elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
+ sock = TCPSocket.new($1, $2.to_i)
+ else
+ raise ArgumentError, "Don't know how to connect to #{address}"
+ end
+ sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, 1) if defined?(TCP_NODELAY)
+ sock
+ end
+
+ def next_sequence #:nodoc: Stubbed to do nothing.
+ end
+
+ def inspect
+ @socket ? "Server on #{Socket.unpack_sockaddr_in(@socket.getsockname).reverse!.join(':')}" : "Connection closed"
+ end
+
+ # Close the socket.
+ def close
+ @socket.shutdown if @socket
+ @socket = nil
+ end
+
+ end
+
end
View
4 uuid.gemspec
@@ -1,6 +1,6 @@
spec = Gem::Specification.new do |spec|
spec.name = 'uuid'
- spec.version = '2.2.0'
+ spec.version = '2.3.0'
spec.summary = "UUID generator"
spec.description = <<-EOF
UUID generator for producing universally unique identifiers based on RFC 4122
@@ -12,6 +12,8 @@ EOF
spec.homepage = 'http://github.com/assaf/uuid'
spec.files = Dir['{bin,test,lib,docs}/**/*'] + ['README.rdoc', 'MIT-LICENSE', 'Rakefile', 'CHANGELOG', 'uuid.gemspec']
+ spec.executables = "uuid"
+
spec.has_rdoc = true
spec.rdoc_options << '--main' << 'README.rdoc' << '--title' << 'UUID generator' << '--line-numbers'
'--webcvs' << 'http://github.com/assaf/uuid'
Please sign in to comment.
Something went wrong with that request. Please try again.