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?
open
,bind
,listen
andaccept
and thenclose
? In your particular case I don't thinkSO_REUSEADDR
isn't bad per se. As it's unlikely there's any stray unacked data that's going to be lingering around. – Dulosisss.setReuseAddress(false);
, so that would naturally fail (ALL sockets have to callsetReuseAddress(true);
, not just one of them.. Tough the code fromsun.nio.ch.ServerSocketChannelImpl.setOption
you quote still callsNet.setSocketOption(this.fd, Net.UNSPEC, paramSocketOption, paramT);
, which should properly set SO_REUSEADDR - you don't need Net.useExclusiveBind() to return true. Your unit test need to callss.setReuseAddress(true);
, and you certainly should close the socket you've accepted. – ShawanasetReuseAddtess(true)
and (b) not do anything at all withSocket
orconnect()
oraccwot()
, all of which is creating a second socket bound to the same port. It is needless and pointless. Voting to close as 'cannot reproduce'. – Quintain