package com.limegroup.gnutella.io;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import junit.framework.Test;
import com.limegroup.gnutella.util.BaseTestCase;
import com.limegroup.gnutella.util.PrivilegedAccessor;
public class NIODispatcherTest extends BaseTestCase {
private int LISTEN_PORT = 9999;
private ServerSocket LISTEN_SOCKET;
private InetSocketAddress LISTEN_ADDR;
public NIODispatcherTest(String name) {
super(name);
}
public static Test suite() {
return buildTestSuite(NIODispatcherTest.class);
}
public void setUp() throws Exception {
LISTEN_SOCKET = new ServerSocket(LISTEN_PORT, 0);
LISTEN_SOCKET.setReuseAddress(true);
LISTEN_ADDR = new InetSocketAddress("127.0.0.1", LISTEN_PORT);
}
public void tearDown() throws Exception {
try {
LISTEN_SOCKET.close();
} catch(IOException ignored) {}
}
// note: this simulates a connect timeout by never actually trying to connect.
// it's really frickin' hard to try a connect and have it timeout because:
// a) if you have a socket listening & try to connect, even if it isn't accepting, it will connect
// b) if you don't have a socket listening and try to connect, it will immediate throw a ConnectException
public void testSimpleConnectTimeout() throws Exception {
StubConnectObserver observer = new StubConnectObserver();
SocketChannel channel = observer.getChannel();
NIODispatcher.instance().registerConnect(channel, observer, 3000);
assertNull(observer.getIoException());
assertNull(observer.getSocket());
assertFalse(observer.isShutdown());
Thread.sleep(3500);
assertFalse(channel.isConnected());
assertNull(observer.getSocket());
Exception iox = observer.getIoException();
assertNotNull(iox);
assertInstanceof(SocketTimeoutException.class, iox);
assertEquals("operation timed out (3000)", iox.getMessage()); // stringent, but useful.
assertTrue(observer.isShutdown());
}
public void testMultipleConnectTimeout() throws Exception {
StubConnectObserver o1 = new StubConnectObserver();
SocketChannel c1 = o1.getChannel();
StubConnectObserver o2 = new StubConnectObserver();
SocketChannel c2 = o2.getChannel();
NIODispatcher.instance().registerConnect(c1, o1, 3000);
NIODispatcher.instance().registerConnect(c2, o2, 2000);
Exception x1;
Exception x2;
Thread.sleep(2500);
x2 = o2.getIoException();
assertFalse(c2.isConnected());
assertNotNull(x2);
assertInstanceof(SocketTimeoutException.class, x2);
assertEquals("operation timed out (2000)", x2.getMessage()); // stringent, but useful.
assertNull(o2.getSocket());
assertTrue(o2.isShutdown());
assertFalse(o1.isShutdown());
assertNull(o1.getIoException());
assertNull(o1.getSocket());
Thread.sleep(1000);
x1 = o1.getIoException();
assertFalse(c1.isConnected());
assertNotNull(x1);
assertInstanceof(SocketTimeoutException.class, x1);
assertEquals("operation timed out (3000)", x1.getMessage()); // stringent, but useful.
assertNull(o1.getSocket());
assertTrue(o1.isShutdown());
c1.close();
c2.close();
}
public void testConnectPreventsTimeout() throws Exception {
StubConnectObserver observer = new StubConnectObserver();
SocketChannel channel = observer.getChannel();
NIODispatcher.instance().registerConnect(channel, observer, 3000);
channel.connect(LISTEN_ADDR);
Thread.sleep(1000);
assertTrue(channel.isConnected());
assertNull(observer.getIoException());
assertNotNull(observer.getSocket());
assertFalse(observer.isShutdown());
Thread.sleep(2500);
assertTrue(channel.isConnected());
assertEquals(0, interestOps(channel));
Exception iox = observer.getIoException();
assertNull(iox);
channel.close();
}
public void testAcceptChannel() throws Exception {
LISTEN_SOCKET.close();
ServerSocketChannel channel = ServerSocketChannel.open();
LISTEN_SOCKET = channel.socket();
channel.configureBlocking(false);
LISTEN_SOCKET.bind(new InetSocketAddress(LISTEN_PORT));
LISTEN_SOCKET.setReuseAddress(true);
StubAcceptChannelObserver observer = new StubAcceptChannelObserver();
NIODispatcher.instance().registerAccept(channel, observer);
SocketChannel c1 = new StubConnectObserver().getChannel();
SocketChannel c2 = new StubConnectObserver().getChannel();
SocketChannel c3 = new StubConnectObserver().getChannel();
assertEquals(0, observer.getChannels().size());
NIODispatcher.instance().registerConnect(c1, new StubConnectObserver(), 0);
c1.connect(LISTEN_ADDR);
Thread.sleep(300);
assertEquals(1, observer.getChannels().size());
SocketChannel r1 = observer.getNextSocketChannel();
assertEquals(c1.socket().getLocalPort(), r1.socket().getPort());
assertFalse(r1.isBlocking());
assertTrue(r1.isConnected());
c1.close();
// make sure we can handle more'n one connect at a time.
NIODispatcher.instance().registerConnect(c2, new StubConnectObserver(), 0);
NIODispatcher.instance().registerConnect(c3, new StubConnectObserver(), 0);
c2.connect(LISTEN_ADDR);
c3.connect(LISTEN_ADDR);
Thread.sleep(300);
assertEquals(2, observer.getChannels().size());
SocketChannel r2 = observer.getNextSocketChannel();
SocketChannel r3 = observer.getNextSocketChannel();
// swap r2 & r3 if we read them out of order.
if(c2.socket().getLocalPort() != r2.socket().getPort()) {
SocketChannel temp = r3;
r3 = r2;
r2 = temp;
}
assertEquals(c2.socket().getLocalPort(), r2.socket().getPort());
assertFalse(r2.isBlocking());
assertTrue(r2.isConnected());
assertEquals(c3.socket().getLocalPort(), r3.socket().getPort());
assertFalse(r3.isBlocking());
assertTrue(r3.isConnected());
c2.close();
c3.close();
}
public void testSimpleReadTimeout() throws Exception {
StubReadObserver o1 = new StubReadObserver();
SelectableChannel c1 = o1.getChannel();
o1.setReadTimeout(1023);
NIODispatcher.instance().registerRead(c1, o1);
o1.waitForIOException(2000);
assertInstanceof(SocketTimeoutException.class, o1.getIox());
assertEquals("operation timed out (1023)", o1.getIox().getMessage());
assertTrue(o1.isShutdown());
assertEquals(0, o1.getReadsHandled());
c1.close();
}
public void testNoTimeoutIfInterestOff() throws Exception {
StubReadObserver o1 = new StubReadObserver();
SelectableChannel c1 = o1.getChannel();
o1.setReadTimeout(1000);
NIODispatcher.instance().registerRead(c1, o1);
Thread.sleep(200); // let it register.
NIODispatcher.instance().interestRead(c1, false);
o1.waitForIOException(2000);
assertNull(o1.getIox());
assertFalse(o1.isShutdown());
assertEquals(0, o1.getReadsHandled());
c1.close();
}
public void testChangingTimeoutByInterest() throws Exception {
StubReadObserver o1 = new StubReadObserver();
SelectableChannel c1 = o1.getChannel();
o1.setReadTimeout(1000);
NIODispatcher.instance().registerRead(c1, o1);
Thread.sleep(200); // let it register.
o1.setReadTimeout(2005);
NIODispatcher.instance().interestRead(c1, true);
o1.waitForIOException(4000);
assertInstanceof(SocketTimeoutException.class, o1.getIox());
assertEquals("operation timed out (2005)", o1.getIox().getMessage());
assertTrue(o1.isShutdown());
assertEquals(0, o1.getReadsHandled());
c1.close();
}
public void testTimeoutUpsAfterReads() throws Exception {
StubReadConnectObserver o1 = new StubReadConnectObserver();
SocketChannel c1 = o1.getChannel();
o1.setReadTimeout(1000);
NIODispatcher.instance().registerConnect(c1, o1, 1000);
c1.connect(LISTEN_ADDR);
o1.waitForEvent(1000);
assertEquals(c1.socket(), o1.getSocket());
assertTrue(c1.isConnected());
// Now accept on the server socket.
LISTEN_SOCKET.setSoTimeout(5000);
Socket accepted = LISTEN_SOCKET.accept();
// Wait 2 seconds & make sure it didn't disco
Thread.sleep(2000);
assertTrue(c1.isConnected());
assertFalse(o1.isShutdown());
assertNull(o1.getIoException());
assertEquals(0, o1.getReadsHandled());
// Turn interest on in reading & send some data.
NIODispatcher.instance().interestRead(c1, true);
o1.setReadTimeout(2000); // change timeout length so we can make sure the exception matched
accepted.getOutputStream().write(new byte[100]);
Thread.sleep(500);
assertGreaterThanOrEquals(1, o1.getReadsHandled());
// Now make sure that since interest is still on, it'll IOX after a second.
Thread.sleep(2500);
assertInstanceof(SocketTimeoutException.class, o1.getIoException());
assertEquals("operation timed out (2000)", o1.getIoException().getMessage());
assertGreaterThanOrEquals(o1.getLastReadTime() + 1000, o1.getIoxTime());
c1.close();
}
public void testTimeoutBecomesSmaller() throws Exception {
StubReadConnectObserver o1 = new StubReadConnectObserver();
SocketChannel c1 = o1.getChannel();
NIODispatcher.instance().registerConnect(c1, o1, 1000);
c1.connect(LISTEN_ADDR);
o1.waitForEvent(1000);
assertEquals(c1.socket(), o1.getSocket());
assertTrue(c1.isConnected());
// Now accept on the server socket.
LISTEN_SOCKET.setSoTimeout(5000);
Socket accepted = LISTEN_SOCKET.accept();
// Wait 2 seconds & make sure it didn't disco
Thread.sleep(2000);
assertTrue(c1.isConnected());
assertFalse(o1.isShutdown());
assertNull(o1.getIoException());
assertEquals(0, o1.getReadsHandled());
// Turn interest on in reading & send some data.
o1.setReadTimeout(5000);
NIODispatcher.instance().interestRead(c1, true);
o1.setReadTimeout(1000); // change timeout length so we can make sure the exception matched
accepted.getOutputStream().write(new byte[100]);
Thread.sleep(500);
assertGreaterThanOrEquals(1, o1.getReadsHandled());
// Now make sure that since interest is still on, it'll IOX after a second.
Thread.sleep(1500);
assertInstanceof(SocketTimeoutException.class, o1.getIoException());
assertEquals("operation timed out (1000)", o1.getIoException().getMessage());
// Wait a bit longer and make sure the msg didn't change on the IOX
Thread.sleep(5500);
assertEquals("operation timed out (1000)", o1.getIoException().getMessage());
c1.close();
}
private int interestOps(SelectableChannel channel) throws Exception {
// peeks into the NIODispatcher to get the Selector so we can assert the interetOps
Selector selector = (Selector)PrivilegedAccessor.getValue(NIODispatcher.instance(), "selector");
return channel.keyFor(selector).interestOps();
}
}