java.rmi.server.UID not unique
I need to generate unique id's. According to the documentation, java.rmi.server.UID is guaranteed to generate ID's unique to a physical computer. however, I need to run multiple instances of a program on the same physical computer and if they start up within the same millisecond I get the same UID.
Test program:
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.rmi.server.UID;
/*
* UIDTest.java
*
* Attempts to create a unique ID.
*
*/
public class UIDTest {
String makeUniqueId() {
String firstPart = "localhost:127.0.0.1";
try {
InetAddress myInetAddr = InetAddress.getLocalHost();
firstPart = myInetAddr.getHostName() + ":" + myInetAddr.getHostAddress();
} catch (UnknownHostException e) {
}
UID uid = new UID();
return firstPart + ":" + uid;
}
public static void main(String[] args) {
UIDTest myTest = new UIDTest();
String myUniqueId = myTest.makeUniqueId();
System.out.println("Generated Unique ID = " + myUniqueId);
System.out.println("Random number: " + java.lang.Math.random());
System.out.println("Object's IdentityHashCode: " + System.identityHashCode(myTest));
}
}
Batch file for Microsoft Windows:
start cmd /k java UIDTest
start cmd /k java UIDTest
Both instances produce the same output:
Generated Unique ID = MyCPU:111.22.33.44:b8df17:10cef9e1c0d:-8000
Random number: 0.8841158985227917
Object's IdentityHashCode: 27837671
When you run this you may find that sometimes both instances generate the same result and sometimes not. I need a guaranteed unique result.
[1753 byte] By [
mluttona] at [2007-9-25]

java.util.UUID @since 1.5
Until management allows us to qualify and upgrade all the servers, I'm stuck with 1.4.Does 5.0 implement UID according to spec?
I believe so. http://java.sun.com/j2se/1.5.0/docs/api/java/util/UUID.html.
OTOH I don't understand why you're getting the original problem. java.rmi.server.UID is supposed to be unique w.r.t. time, and it uses a SecureRandom (for the VMID) which should practically never repeat, and a sleep to ensure that it is unique by at least a granularity of one second, not just a millisecond.
The problem never happened when we ran the two instances on two physical computers, because I add the computer's IP address to the UID. It would not happen if we ran two threads within the same JVM because UID is unique within a JVM.
But we run two JVM's on the same physical box. One JVM does not affect the other. They both start at the same time, hence random() on each returns the same result. They both start at the same virtual address in their respective process spaces.
java.rmi.server.UID is guaranteed by definition to be unique within a physical computer. From the docs:
"A UID instance contains three primitive values:
unique, an int that uniquely identifies the VM that this UID was generated in, with respect to its host and at the time represented by the time value (an example implementation of the unique value would be a process identifier) ...."
This version (Sun Java HotSpot 1.4.2_08) breaks this guarantee and 1.5 appears to do the same. It does not use the process identifier.
Our workaround is to tell the operations staff to start one process, wait a few seconds and then start the other. Sometimes they do this; sometimes they don't.
I am looking for a pure Java workaround that can distinguish between two separate JVM's. The only thing I can think of at this time is to use JNI to get the underlying process ID. I don't want to make it non-portable.
Also I don't see any guarantee in the java.util.UUID docs that UUID is unique for two separate processes in the same physical computer -- the guarantee that jama.rmi.server.UID makes and HotSpot breaks. The links to "UUIDs and GUIDs" and "ISO/IEC 11578:1996" in the cited API doc are both broken so I can't see whether they address this issue.
OK, maybe I should restate this as a puzzle.
Write any Java 1.4 program (not using JNI) such that:
1. when run in two separate processes on the same physical computer, both starting up at the same instant (within a few microseconds), is guaranteed by the language to produce different outputs, and
2. when thus done using Sun's HotSpot JVM 1.4, actually does produce different outputs.
My program fails #2 because of a bug in the JVM.
A bit of a hack, I know, but should be portable.
Claim a range of ports (just two ports as you describe it) for your app. When the app starts up, it attempts to open a ServerSocket on the lowest numbered port in the range. It walks up the port numbers until it finds one that's not already open, opens it, and keeps it open for its entire life.
It can use the port number as a salt, or use the offset from the base port number as a time to sleep before generating the UID.
Or just pass different command line args to distinguish the two apps.
Or run a third process whose job is simply to act as a UID broker, and have these guys ask it for a UID.
Or play with java.io.File.createTempFile, though you may run into the same problem. You'd have to investigate the details yourself.
Thanks! That solution WILL work in our application because:
1. Ports are a resource on the physical machine that cannot be shared across virtual machines.
2. Java can get a port without using JNI.
3. The server runs only one application (two or more instances) so we don't have to worry about interfering with some other application.
4. The application uses TCP/IP so presence of a TCP/IP stack is guaranteed.
Here is the complete solution:
// Uniquer.java
/**
* Class that finds a port exclusive to this virtual machine within the
* physical machine. Singleton.
*
*/
public class Uniquer {
public static final int startPort = 8123;
public static final int endPort = 8133; // Last plus one
// Singleton.
private static Uniquer uniq = new Uniquer();
private final ServerSocket ssock;
private final int port;
// Private constructor
private Uniquer() {
//System.out.println("Creating Uniquer");
ServerSocket s2 = null;
int pt = -1;// If fails to find one
try {
for (pt = startPort; pt < endPort; pt++) {
try {
s2 = new ServerSocket(pt);
//System.out.println(pt);
break; // Exit loop
} catch (Exception e) {
// Throws java.net.BindException
//System.out.println(pt + ":" + e.toString());
// Loop back
}
}
} finally {
ssock = s2;
port = pt;
}
}
// Get port
public int getPort() {
return this.port;
}
// Get instance
public static Uniquer instance() {
return uniq;
}
}
// UIDTest.java
/**
*
* main class
*
*/
public class UIDTest {
// Singleton Uniquer created at startup
protected static Uniquer uniq;
static {
uniq = Uniquer.instance();
}
String makeUniqueId() {
String firstPart = "localhost:127.0.0.1";
try {
InetAddress myInetAddr = InetAddress.getLocalHost();
firstPart = myInetAddr.getHostName() + ":" + myInetAddr.getHostAddress();
} catch (UnknownHostException e) {
}
UID uid = new UID();
int uniquePort = uniq.getPort();
return firstPart + ":" + uid + ":" + uniquePort;
}
public static void main(String[] args) {
//System.out.println("Starting up");
UIDTest myTest = new UIDTest();
String myUniqueId = myTest.makeUniqueId();
System.out.println("Generated Unique ID = " + myUniqueId);
System.out.println("Random number: " + java.lang.Math.random());
System.out.println("Object's IdentityHashCode: " + System.identityHashCode(myTest));
System.out.println("Unique port: " + uniq.getPort());
}
}
Here is the output when run twice at the same time:
Instance 1:
Generated Unique ID = MyCPU:111.22.33.44:1729854:10cfe14298a:-8000:8123
Random number: 0.8659601035554275
Object's IdentityHashCode: 33482492
Unique port: 8123
Instance 2:
Generated Unique ID = MyCPU:111.22.33.44:1729854:10cfe14298a:-8000:8124
Random number: 0.8659601035554275
Object's IdentityHashCode: 33482492
Unique port: 8124
Thanks again.
> Thanks! That solution WILL work in our application> because:Cool. I'm glad I was able to help.