NioSocketConnector leaks the last open NioSocketSession after close
-------------------------------------------------------------------

                 Key: DIRMINA-827
                 URL: https://issues.apache.org/jira/browse/DIRMINA-827
             Project: MINA
          Issue Type: Bug
          Components: Core
    Affects Versions: 2.0.2
         Environment: Windows 7/Solaris SPARC/Solaris x86
Java versions 6u18 & 6u24
            Reporter: Matt Yates
            Priority: Critical


My company's program uses MINA makes multiple (usually) simultaneous 
connections to various other machines, while reusing the same 
NioSocketConnector for each new connection.  For better or worse, we store 
various objects in the IoSession's attributes, which we expect to get released 
on close, however, said session remains in memory until either a new connection 
is made or the connector is disposed.  After writing the simplest Connector 
program I could (I have several servers available on my network, so I did not 
write a matching Acceptor), and performing some profiling and debugging, I was 
able to confirm the leak and identify the issue.

Below is my Connector test program

{code}
public class MinaMain {
    private static final Logger LOGGER = 
LoggerFactory.getLogger(MinaMain.class);

    public static void main(String[] args) throws InterruptedException {
        LOGGER.trace("Waiting for YourKit to start");
        Thread.sleep(15000);

        NioSocketConnector connector = new NioSocketConnector();
        connector.setHandler(new IoHandlerAdapter());
        closeSession(getConnectedSession(connector));

//        LOGGER.info("Creating and closing 5 sessions in series");
//
//        for (int x = 0; x < 5; x++) {
//            IoSession session = getConnectedSession(connector);
//
//            if (session == null) {
//                continue;
//            }
//
//            closeSession(session);
//        }
//
//        LOGGER.info("Creating 5 sessions and then closing 5 sessions");
//        IoSession[] sessions = new IoSession[5];
//
//        for (int x = 0; x < 5; x++) {
//            sessions[x] = getConnectedSession(connector);
//        }
//
//        for (int x = 0; x < 5; x++) {
//            IoSession session = sessions[x];
//
//            if (session != null) {
//                closeSession(session);
//                sessions[x] = null;
//            }
//        }

        LOGGER.info("Test complete. Sleeping for 60s");
        Thread.sleep(60000);
    }

    private static IoSession getConnectedSession(IoConnector connector) throws 
InterruptedException {
        IoSession session = null;

        try {
            ConnectFuture future = connector.connect(new 
InetSocketAddress("134.64.37.183", 11109));

            future.addListener(new IoFutureListener<ConnectFuture>() {
                @Override
                public void operationComplete(ConnectFuture future) {
                    LOGGER.debug("Connection completed callback for session " + 
future.getSession().getId());
                }
            });

            future.awaitUninterruptibly();
            LOGGER.debug("Connection created for session " + 
future.getSession().getId());
            session = future.getSession();
            Thread.sleep(15000);
        }
        catch (Exception e) {
            LOGGER.error("Failed to connect", e);
        }

        return session;
    }

    private static void closeSession(IoSession session) throws 
InterruptedException {
        try {
            CloseFuture closeFuture = session.getCloseFuture();

            closeFuture.addListener(new IoFutureListener<CloseFuture>() {
                @Override
                public void operationComplete(CloseFuture future) {
                    LOGGER.debug("Session closed callback for session " + 
future.getSession().getId());
                }
            });

            LOGGER.debug("Attempting to close session " + session.getId());
            session.close(false);

            LOGGER.debug("IoSession.close(false) returned. Awaiting 
uninterruptibily for session " + session.getId());
            closeFuture.awaitUninterruptibly();

            LOGGER.debug("Close completed for session " + session.getId());
        }
        catch (Exception e) {
            LOGGER.error("Failed to close session " + session.getId());
            session.close(true);
        }

        Thread.sleep(15000);
    }
}
{/code}

Attached is the hprof file of my program, taken after the program has run 
(during the 60 second sleep at the end) and a screen shot of the GC Root of the 
leaked NioSocketSession.

After a couple days of debugging, I've identified why this is happening.  Below 
is a snippet from the AbstractPollingIoConnector.Connector thread

{code}private class Connector implements Runnable {

        public void run() {
            int nHandles = 0;
            while (selectable) {
                try {
                    // the timeout for select shall be smaller of the connect
                    // timeout or 1 second...
                    int timeout = (int)Math.min(getConnectTimeoutMillis(), 
1000L);
                    int selected = select(timeout);

                    nHandles += registerNew();

                    if (selected > 0) {
                        nHandles -= processConnections(selectedHandles());
                    }

                    processTimedOutSessions(allHandles());

                    nHandles -= cancelKeys();

                    if (nHandles == 0) {
                        synchronized (lock) {
                            if (connectQueue.isEmpty()) {
                                connector = null;
                                break;
                            }
                        }
                    }
                }

...
{/code}

When IoSession.close(boolean) is called, the above Connector thread cancels the 
SelectionKey associated with the session, which reduces the number of handles 
from 1 to 0.  Immediately afterwards, the thread stops executing because there 
are no handles and nothing in the connetQueue, however, the previously 
cancelled key is still in the Selector because its cancelledKeys Set is not 
processed until the next call to select(int)

Calling dispose on the IoConnector restarts the Connector thread, which calls 
Selector.select(int), at which time, the Selector cleans out its canclledKeys.
Creating a new connection causes the same thing to happen, because the new 
session also recreates the AbstractPollingIoConnector.Connector thread, as 
there is a new handle to be processed.

--
This message is automatically generated by JIRA.
For more information on JIRA, see: http://www.atlassian.com/software/jira

Reply via email to