Problem
In our project, we are using the GlassFish CORBA ORB libraries (org.glassfish.corba:glassfish-corba-orb, tested on both 5.0.0 and 5.0.2) in a plain Java SE application (standalone, without any Jakarta EE / Java EE application server).
Our Java process is a long-running service that does not terminate when the ORB is stopped. The ORB is dynamically initialized and stopped during the lifecycle of the JVM (e.g. stopping or restarting the CORBA workers in our application). Because the JVM continues running, the server port remains open.
Now after the restart, CORBA opens a new random port, but clients trying to connect to the old port hang because the old socket is still accepting TCP connections instead of refusing them.
e.g. after orb.destroy(), 'netstat -aon' still shows the port open and listening.
This might relate to previously reported issues #29 and #26.
Workaround
Using reflection to manually call close on the acceptors and selector does close the port.
Possible Cause
Looking at TransportManagerImpl and AcceptorImpl in glassfish-corba-orb (5.0.0 / 5.0.2):
When orb.destroy() is called, it invokes TransportManagerImpl.close().
TransportManagerImpl.close() iterates over outboundConnectionCaches and inboundConnectionCaches and closes them, but it never closes the registered Acceptor instances:
// TransportManagerImpl.java
public void close() {
for (OutboundConnectionCache cache : outboundConnectionCaches.values()) {
cache.close();
}
for (InboundConnectionCache cache : inboundConnectionCaches.values()) {
cache.close();
}
getSelector(0).close();
// The registered Acceptors (and their ServerSockets) are never closed here
}
Minimal Reproducer Test (JUnit 5)
Here is a self-contained test reproducing the behavior:
import org.junit.jupiter.api.Test;
import org.omg.CORBA.ORB;
import org.omg.CORBA.ServerRequest;
import org.omg.PortableServer.DynamicImplementation;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAHelper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.fail;
/**
* Minimal, self-contained test case demonstrating the server socket port leak
* in GlassFish CORBA ORB (5.0.0 & 5.0.2) after orb.destroy().
*/
public class MinimalPortLeakReproducerTest {
static class TestServant extends DynamicImplementation {
@Override
public void invoke(ServerRequest request) {
}
@Override
public String[] _all_interfaces(POA poa, byte[] objectId) {
return new String[] { "IDL:TestServant:1.0" };
}
}
private static String getNetstatOutput(int port) {
StringBuilder sb = new StringBuilder();
try {
boolean isWindows = System.getProperty("os.name", "").toLowerCase().contains("win");
ProcessBuilder pb = isWindows
? new ProcessBuilder("cmd.exe", "/c", "netstat -aon | findstr :" + port)
: new ProcessBuilder("sh", "-c", "netstat -an | grep " + port);
Process p = pb.start();
java.nio.charset.Charset charset = isWindows ? java.nio.charset.Charset.forName("Cp850") : java.nio.charset.StandardCharsets.UTF_8;
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(p.getInputStream(), charset))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(" ").append(line).append(System.lineSeparator());
}
}
p.waitFor();
} catch (Exception e) {
sb.append(" (Could not run netstat: ").append(e.getMessage()).append(")");
}
return sb.length() > 0 ? sb.toString() : " (No socket found via netstat)\n";
}
@Test
public void testServerPortLeakAfterDestroy() throws Exception {
Properties props = new Properties();
props.put("org.omg.CORBA.ORBClass", "com.sun.corba.ee.impl.orb.ORBImpl");
props.put("org.omg.CORBA.ORBSingletonClass", "com.sun.corba.ee.impl.orb.ORBSingleton");
ORB orb = ORB.init(new String[0], props);
POA rootPOA = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
rootPOA.the_POAManager().activate();
// Register servant to open server port
TestServant servant = new TestServant();
org.omg.CORBA.Object ref = rootPOA.servant_to_reference(servant);
String ior = orb.object_to_string(ref);
// Get server port from acceptor
com.sun.corba.ee.spi.orb.ORB eeOrb = (com.sun.corba.ee.spi.orb.ORB) orb;
int serverPort = eeOrb.getTransportManager().getAcceptors().iterator().next().getPort();
System.out.println("Servant IOR: " + ior);
System.out.println("Active Server Port: " + serverPort);
// Verify that server port is reachable
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress("localhost", serverPort), 1000);
}
// Standard shutdown
rootPOA.destroy(true, true);
orb.shutdown(true);
orb.destroy();
Thread.sleep(2000);
// Query OS netstat to prove the socket is still in LISTENING state
String netstatOutput = getNetstatOutput(serverPort);
System.out.println("\n[netstat output for port " + serverPort + " after orb.destroy()]:");
System.out.println(netstatOutput.isEmpty() ? " (No socket found)" : netstatOutput);
System.out.println();
// The port should be closed.
// Problem: Port is still open, rebind throws BindException.
try (ServerSocket rebindSocket = new ServerSocket(serverPort)) {
System.out.println("SUCCESS: Port was released and rebind succeeded.");
} catch (IOException e) {
fail("BUG REPRODUCED: Port " + serverPort + " is still bound after orb.destroy()! (" + e.getMessage() + ")\n"
+ "OS netstat still reports:\n" + netstatOutput);
}
}
}
org.opentest4j.AssertionFailedError: Port 64539 is still bound after orb.destroy()! (Address already in use: bind)
at MinimalPortLeakReproducerTest.testServerPortLeakAfterDestroy(MinimalPortLeakReproducerTest.java:58)
Problem
In our project, we are using the GlassFish CORBA ORB libraries (
org.glassfish.corba:glassfish-corba-orb, tested on both5.0.0and5.0.2) in a plain Java SE application (standalone, without any Jakarta EE / Java EE application server).Our Java process is a long-running service that does not terminate when the ORB is stopped. The ORB is dynamically initialized and stopped during the lifecycle of the JVM (e.g. stopping or restarting the CORBA workers in our application). Because the JVM continues running, the server port remains open.
Now after the restart, CORBA opens a new random port, but clients trying to connect to the old port hang because the old socket is still accepting TCP connections instead of refusing them.
e.g. after orb.destroy(), 'netstat -aon' still shows the port open and listening.
This might relate to previously reported issues #29 and #26.
Workaround
Using reflection to manually call close on the acceptors and selector does close the port.
Possible Cause
Looking at
TransportManagerImplandAcceptorImplinglassfish-corba-orb(5.0.0 / 5.0.2):When
orb.destroy()is called, it invokesTransportManagerImpl.close().TransportManagerImpl.close()iterates overoutboundConnectionCachesandinboundConnectionCachesand closes them, but it never closes the registeredAcceptorinstances:Minimal Reproducer Test (JUnit 5)
Here is a self-contained test reproducing the behavior:
org.opentest4j.AssertionFailedError: Port 64539 is still bound after orb.destroy()! (Address already in use: bind)
at MinimalPortLeakReproducerTest.testServerPortLeakAfterDestroy(MinimalPortLeakReproducerTest.java:58)