Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wait till a TCP port is really (natively) closed in Java?

Why am I asking it?

You can skip the story if you want. Still, some might be interested.

I have an embedded ZooKeeper server in Java. In the unit tests, I assign ports to the test servers dynamically. Before assigning the port I check if it is unused by opening up a ServerSocket, then closing it.

It happens from time to time, that in the unit tests I get BindException when I start up my server (it cannot be that I assign the same port to two servers since I use File locks as well for mutual exclusion). It turned out that the reason is, that for the port check I open up the port, then I close it and it waits for a while in operating system level till the port can be reopened.

There is however an option (StandardSocketOptions.SO_REUSEADDR) which can tell for the Java socket, that an old socket in TIMED_WAIT state can be reused. After checking the ZooKeeper code it is actually set to true (see org.apache.zookeeper.server.NIOServerCnxnFactory.configure(InetSocketAddress, int)):

@Override
public void configure(InetSocketAddress addr, int maxcc) throws IOException {
    configureSaslLogin();

    thread = new Thread(this, "NIOServerCxn.Factory:" + addr);
    thread.setDaemon(true);
    maxClientCnxns = maxcc;
    this.ss = ServerSocketChannel.open();
    ss.socket().setReuseAddress(true);
    LOG.info("binding to port " + addr);
    ss.socket().bind(addr);
    ss.configureBlocking(false);
    ss.register(selector, SelectionKey.OP_ACCEPT);
}

My test however prooves that it does not work. I get BindException (under Linux JDK 1.7.0_60).

After checking the ServerSocketChannel implementation (JDK 1.7.0_60) I realized, that this NEVER works under linux. See sun.nio.ch.ServerSocketChannelImpl.setOption(SocketOption<T>, T):

public <T> ServerSocketChannel setOption(SocketOption<T> paramSocketOption, T paramT) throws IOException
{
    if (paramSocketOption == null)
        throw new NullPointerException();
    if (!(supportedOptions().contains(paramSocketOption)))
        throw new UnsupportedOperationException("'" + paramSocketOption + "' not supported");
    synchronized (this.stateLock) {
        if (!(isOpen()))
            throw new ClosedChannelException();
        if ((paramSocketOption == StandardSocketOptions.SO_REUSEADDR) && (Net.useExclusiveBind()))
        {
            this.isReuseAddress = ((Boolean)paramT).booleanValue();
        }
        else {
            Net.setSocketOption(this.fd, Net.UNSPEC, paramSocketOption, paramT);
        }
        return this;
    }
}

Unfortunately Net.useExclusiveBind() will never give back true under linux, if you check its source in the hopefully similar OpenJDK it depends on Net.isExclusiveBindAvailable(), which is -1 under Linux.

Do you have a workaround?

Is there a way in Java to wait till a port is really natively closed apart from opening up a ServerSocket without SO_REUSEADDR and checking if I get BindException? Of course, that is not a solution since then I have to close that ServerSocket again. Why is there nothing in Java like closing a socket in blocking mode, which would return only when the socket is really on the operating system level closed?

like image 249
Gábor Lipták Avatar asked Jul 01 '15 13:07

Gábor Lipták


1 Answers

I am afraid your analysis of the sun.nio.ch.ServerSocketChannelImpl.setOption(SocketOption<T>, T) implementation is not correct.

The "exclusive bind" stuff is used on Windows only, and not relevant for other platforms. You can check this in the sources of the sun.nio.ch.Net class. Specifically, see the comments for isExclusiveBindAvailable().

When exclusive binding is not available, ServerSocketChannelImpl.setOption just calls Net.setSocketOption which at the end will end up calling the native setsockopt function.

BTW the server-side socket might not be in TIME_WAIT state, but also in FIN_WAIT_1 or FIN_WAIT_2 (waiting for the client side to acknowledge the close). This in-depth description of the TCP state machine may be helpful.

like image 129
Grodriguez Avatar answered Oct 12 '22 03:10

Grodriguez