There appears to be another referential integrity loophole in the RMI implementation similar to that of 6181943, but this time in the remote object export code path. Here is a test case:
import java.rmi.Remote;
import java.rmi.server.UnicastRemoteObject;
public class Reproduce6584016 {
public static void main(String[] args) throws Exception {
long i = 0;
try {
while (true) {
i++;
UnicastRemoteObject.exportObject(new Remote() { }, 0);
}
} catch (Exception e) {
System.err.println("Failed on iteration " + i + ":");
e.printStackTrace();
}
}
}
which has been observed to fail with the following unexpected exception (on a dual-SPARC SunBlade 2000 running Solaris 10):
Failed on iteration 8029511:
java.rmi.server.ExportException: internal error: attempt to export collected object
at sun.rmi.transport.ObjectTable.putTarget(ObjectTable.java:163)
at sun.rmi.transport.Transport.exportObject(Transport.java:74)
at sun.rmi.transport.tcp.TCPTransport.exportObject(TCPTransport.java:229)
at sun.rmi.transport.tcp.TCPEndpoint.exportObject(TCPEndpoint.java:393)
at sun.rmi.transport.LiveRef.exportObject(LiveRef.java:129)
at sun.rmi.server.UnicastServerRef.exportObject(UnicastServerRef.java:190)
at java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:293)
at java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:235)
at Reproduce6584016.main(Reproduce6584016.java:10)
As with 6181943, the implementation code makes a mistaken assumption that a method's argument values remain roots of strong reachability for the duration of the method's execution-- in this case, the following methods' arguments apply:
- "impl" argument of sun.rmi.server.UnicastServerRef.exportObject(UnicastServerRef.java:190)
- "obj" argument of java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:293)
- "obj" argument of java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:235)
but none of those argument values are used after UnicastServerRef.exportObject passes "impl" to the sun.rmi.transport.Target constructor, where it is wrapped in a weak reference. After that point, despite the argument values on the stack, the remote object can be considered only weakly reachable, so it may be garbage collected before the weak reference is later dereferenced in sun.rmi.transport.ObjectTable.putTarget, causing the unexpected ExportException.
Unlike 6181943, the conditions necessary to reproduce this bug do not seem likely to occur in real applications, because they would presumably maintain a reference to the remote object elsewhere to prevent it from being garbage collected after the exportObject invocation. But these conditions are not unreasonable in test code, and the exportObject invocation should not throw an exception because of these conditions.
Here is a version of the test case modified to repeatedly request full GCs concurrent with the test loop, which seems to reproduce the failure more quickly and reliably:
import java.rmi.Remote;
import java.rmi.server.UnicastRemoteObject;
public class Reproduce6584016 {
public static void main(String[] args) throws Exception {
(new Thread(new Runnable() {
public void run() {
while (true) {
System.gc();
try { Thread.sleep(1); } catch (InterruptedException e) { }
}
}
})).start();
long i = 0;
try {
while (true) {
i++;
UnicastRemoteObject.exportObject(new Remote() { }, 0);
}
} catch (Throwable e) {
System.err.println("Failed on iteration " + i + ":");
e.printStackTrace();
System.exit(1);
}
}
}
[terrier] 85 % java Reproduce6584016
Failed on iteration 18543:
java.rmi.server.ExportException: internal error: attempt to export collected object
at sun.rmi.transport.ObjectTable.putTarget(ObjectTable.java:181)
at sun.rmi.transport.Transport.exportObject(Transport.java:92)
at sun.rmi.transport.tcp.TCPTransport.exportObject(TCPTransport.java:247)
at sun.rmi.transport.tcp.TCPEndpoint.exportObject(TCPEndpoint.java:411)
at sun.rmi.transport.LiveRef.exportObject(LiveRef.java:147)
at sun.rmi.server.UnicastServerRef.exportObject(UnicastServerRef.java:208)
at java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:311)
at java.rmi.server.UnicastRemoteObject.exportObject(UnicastRemoteObject.java:253)
at Reproduce6584016.main(Reproduce6584016.java:18)