Skip to content

ORB#destroy() does not close IIOP ServerSocket / ServerSocketChannel #330

Description

@cluder

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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions