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.
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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With