JDK-6338654 : (so) BindException on rebind in certain cases w/JUnit test
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 5.0
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2005-10-19
  • Updated: 2010-04-02
  • Resolved: 2006-05-05
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.5.0_04"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_04-b05)
Java HotSpot(TM) Client VM (build 1.5.0_04-b05, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :

Microsoft Windows 2000 [Version 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
code reproducing problem is attached.  rebind does not work in this JUnit test case I believe due to close happening when in the selector.  Something is not shutting down and cleaning up the socket properly in the JVM I beleive.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run my unit test.  I wrote this specifically just to test and reproduce this issue.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
test case to pass.  I should be able to rebind the socket since this line is in the code before the first bind

client.socket().setReuseAddress(true);

and the this line occurs before the second bind

client.close();

This works in most cases but just not in this specific test case.
ACTUAL -
test case fails.  BindException on the second bind after closing the first client.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
BindException: address is already bound

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
/*
 * Created on Sep 25, 2004
 *
 * FIXME To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package biz.xsoftware.test.nio.suns;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.AbstractSelector;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.textui.TestRunner;

/**
 * @author Dean Hiller
 *
 * FIXME To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class TestCloseWhenInSelector extends TestCase {

	private static final Logger log = Logger
			.getLogger(TestCloseWhenInSelector.class.getName());
	private final static int CLIENT_PORT = 8023;
//	private final static int CLIENT_PORT2 = 8002;
	private final static int SERVER_PORT = 8020;
	private SelectorProvider provider;
	private AbstractSelector selector;
	private ServerSocketChannel serverSocket;
//	private SocketChannel serverChannel;
	private SocketChannel client;
//	private SocketChannel client2;
	
	private ByteBuffer buf = ByteBuffer.allocate(10);
	/**
	 * @param arg0
	 */
	public TestCloseWhenInSelector(String arg0) {
		super(arg0);
	}
	
	protected void setUp() throws Exception {
		provider = SelectorProvider.provider();
		selector = provider.openSelector();
		serverSocket = provider.openServerSocketChannel();
		serverSocket.socket().setReuseAddress(true);
		client = provider.openSocketChannel();
		client.socket().setReuseAddress(true);
	}
	protected void tearDown() throws Exception {
	}
	
	public void testCloseWhenInSelector2() throws Throwable {
		InetAddress loopBack = InetAddress.getByName("127.0.0.1");
		InetSocketAddress clientAddr = new InetSocketAddress(loopBack, CLIENT_PORT);
		InetSocketAddress serverAddr = new InetSocketAddress(loopBack, SERVER_PORT);
		
		//serverSocket.configureBlocking(false);
		serverSocket.socket().bind(serverAddr);
		client.socket().bind(clientAddr);

		client.connect(serverAddr);
		client.configureBlocking(false);
		log.info("connecting client socket");

		Thread.sleep(1000);
		
		serverSocket.configureBlocking(true);
		log.info("about to accept");
		SocketChannel serverChannel = serverSocket.accept();
		log.info("accepted");
		serverChannel.configureBlocking(false);
		log.info("client socket connected");
		
		serverChannel.register(selector, SelectionKey.OP_READ);
		PollingThread2 server = new PollingThread2();
		server.start();
		
		client.finishConnect();
		
		log.info("write data to server");
		ByteBuffer b = ByteBuffer.allocate(10);
		b.putChar('d');
		b.putChar('e');
		b.flip();
		log.info("write bytes");
		int i = client.write(b);
		log.info("wrote bytes="+i);
		
		//wait for other thread to get into the selector...
		Thread.sleep(1000);
		log.info("1. closing client channel");
		client.close();
		log.info("1. closed client channel");

		server.waitForCompletion();
		
		client = provider.openSocketChannel();
		client.socket().setReuseAddress(true);
		client.socket().bind(clientAddr);

		client.connect(serverAddr);
		client.configureBlocking(false);
		
		
	}
	
	private class PollingThread2 extends Thread {
		private Throwable t = null;
		private boolean socketClosed = false;

		public void run() {
			try {
				log.info("STARTING RUN LOOP");
			
				while(true) {
					runLoop();
				}
			} catch (Exception e) {
				t = e;
				log.log(Level.WARNING, "Test failure", e);
			}
		}
		/**
		 *
		 */
		synchronized public void waitForCompletion() throws Throwable {
			if(!socketClosed)
				this.wait();
			
			if(t != null)
				throw t;
		}
		
		protected void runLoop() throws Exception {
			log.info("going into selector");
			int numKeys = selector.select();
			log.info("coming out with new keys="+numKeys);
			Set<SelectionKey> keySet = selector.selectedKeys();
			log.info("keySet size="+keySet.size());
			
			Iterator<SelectionKey> iter = keySet.iterator();
			SelectionKey theKey = iter.next();
			log.info("in loop iter.next="+theKey+" isVal="+theKey.isValid()+" acc="+theKey.isAcceptable()+" read="+theKey.isReadable());
			if(theKey.isReadable()) {
				SocketChannel channel = (SocketChannel)theKey.channel();
				log.info("reading bytes");
				int b = 5;
				while(b > 0) {
					b = channel.read(buf);
					if(b < 0) {
						channel.close();
						synchronized(this) {
							socketClosed = true;
							this.notifyAll();
						}
					}
					log.info("bytes read="+b);
				}
			}
			if(iter.hasNext())
				throw new RuntimeException("Fail test, iterator should only have one key");
		}
	}
	
	public static void main(String[] args) {
		TestSuite suite = new TestSuite();
		suite.addTest(new TestCloseWhenInSelector("testCloseWhenInSelector2"));
		TestRunner.run(suite);
	}
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
I believe it is to pray that close is not called when in the selector or something....not sure the specifics of it.  not really a work around here.

Comments
EVALUATION This isn't a bug. The TCP/IP protocol requires a unique 4-tuple to distinguish connections (4-tuple = local-addr/port and remote-addr/port). This test binds a SocketChannel to localhost:8023 and connects it to localhost:8020. The connection is closed which causes it to go into the TIME_WAIT state for a period of time (2MSL). In this test a second SocketChannel is immediately re-bound to localhost:8023 - the bind succeeds because the SO_REUSEADDR option is enabled (as it happens it would succeed on Windows anyway because the SO_REUSEADDR socket isn't required to rebind to a port when a previous connection is in TIME_WAIT). Anyway, the test attempts to connect this SocketChannel to localhost:8020 and that is required to fail because that 4-tuple is still in use. The only confusing thing is that the connect method is throwing BindException - the reason for that is that connect returns winsock error WSAEADDRINUSE (or EADDRINUSE on other platforms). We faithfully map that error to BindException. It would be less confusing if we threw a ConnectException with a more useful exception message and we will examine this for Dolphin. See 6421091 for a similar issue.
05-05-2006