package org.eclipse.jetty.io.nio;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.ConnectedEndPoint;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class SelectChannelEndPointTest
{
protected SelectChannelEndPoint _lastEndp;
protected ServerSocketChannel _connector;
protected QueuedThreadPool _threadPool = new QueuedThreadPool();
protected SelectorManager _manager = new SelectorManager()
{
@Override
public boolean dispatch(Runnable task)
{
return _threadPool.dispatch(task);
}
@Override
protected void endPointClosed(SelectChannelEndPoint endpoint)
{
}
@Override
protected void endPointOpened(SelectChannelEndPoint endpoint)
{
}
@Override
protected void endPointUpgraded(ConnectedEndPoint endpoint, org.eclipse.jetty.io.Connection oldConnection)
{
}
@Override
public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
{
return SelectChannelEndPointTest.this.newConnection(channel,endpoint);
}
@Override
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
{
SelectChannelEndPoint endp = new SelectChannelEndPoint(channel,selectSet,key,2000);
endp.setConnection(selectSet.getManager().newConnection(channel,endp, key.attachment()));
_lastEndp=endp;
return endp;
}
};
// Must be volatile or the test may fail spuriously
private volatile int _blockAt=0;
@Before
public void startManager() throws Exception
{
_connector = ServerSocketChannel.open();
_connector.socket().bind(null);
_threadPool.start();
_manager.start();
}
@After
public void stopManager() throws Exception
{
_manager.stop();
_threadPool.stop();
_connector.close();
}
protected Socket newClient() throws IOException
{
return new Socket(_connector.socket().getInetAddress(),_connector.socket().getLocalPort());
}
protected AsyncConnection newConnection(SocketChannel channel, EndPoint endpoint)
{
return new TestConnection(endpoint);
}
public class TestConnection extends AbstractConnection implements AsyncConnection
{
NIOBuffer _in = new IndirectNIOBuffer(32*1024);
NIOBuffer _out = new IndirectNIOBuffer(32*1024);
public TestConnection(EndPoint endp)
{
super(endp);
}
public org.eclipse.jetty.io.Connection handle() throws IOException
{
boolean progress=true;
while(progress)
{
progress=false;
_in.compact();
if (_in.space()>0 && _endp.fill(_in)>0)
progress=true;
while (_blockAt>0 && _in.length()>0 && _in.length()<_blockAt)
{
_endp.blockReadable(10000);
if (_in.space()>0 && _endp.fill(_in)>0)
progress=true;
}
if (_in.hasContent() && _in.skip(_out.put(_in))>0)
progress=true;
if (_out.hasContent() && _endp.flush(_out)>0)
progress=true;
_out.compact();
if (!_out.hasContent() && _endp.isInputShutdown())
_endp.shutdownOutput();
}
return this;
}
public boolean isIdle()
{
return false;
}
public boolean isSuspended()
{
return false;
}
public void onClose()
{
// System.err.println("onClose");
}
public void onInputShutdown() throws IOException
{
// System.err.println("onInputShutdown");
}
}
@Test
public void testEcho() throws Exception
{
Socket client = newClient();
client.setSoTimeout(500);
SocketChannel server = _connector.accept();
server.configureBlocking(false);
_manager.register(server);
// Write client to server
client.getOutputStream().write("HelloWorld".getBytes("UTF-8"));
// Verify echo server to client
for (char c : "HelloWorld".toCharArray())
{
int b = client.getInputStream().read();
assertTrue(b>0);
assertEquals(c,(char)b);
}
// wait for read timeout
long start=System.currentTimeMillis();
try
{
client.getInputStream().read();
Assert.fail();
}
catch(SocketTimeoutException e)
{
long duration = System.currentTimeMillis()-start;
Assert.assertThat("timeout duration", duration, greaterThanOrEqualTo(400L));
}
// write then shutdown
client.getOutputStream().write("Goodbye Cruel TLS".getBytes("UTF-8"));
// Verify echo server to client
for (char c : "Goodbye Cruel TLS".toCharArray())
{
int b = client.getInputStream().read();
Assert.assertThat("expect valid char integer", b, greaterThan(0));
assertEquals("expect characters to be same", c,(char)b);
}
client.close();
int i=0;
while (server.isOpen())
{
assert(i++<10);
Thread.sleep(10);
}
}
@Test
public void testShutdown() throws Exception
{
Socket client = newClient();
client.setSoTimeout(500);
SocketChannel server = _connector.accept();
server.configureBlocking(false);
_manager.register(server);
// Write client to server
client.getOutputStream().write("HelloWorld".getBytes("UTF-8"));
// Verify echo server to client
for (char c : "HelloWorld".toCharArray())
{
int b = client.getInputStream().read();
assertTrue(b>0);
assertEquals(c,(char)b);
}
// wait for read timeout
long start=System.currentTimeMillis();
try
{
client.getInputStream().read();
Assert.fail();
}
catch(SocketTimeoutException e)
{
assertTrue(System.currentTimeMillis()-start>=400);
}
// write then shutdown
client.getOutputStream().write("Goodbye Cruel TLS".getBytes("UTF-8"));
client.shutdownOutput();
// Verify echo server to client
for (char c : "Goodbye Cruel TLS".toCharArray())
{
int b = client.getInputStream().read();
assertTrue(b>0);
assertEquals(c,(char)b);
}
// Read close
assertEquals(-1,client.getInputStream().read());
}
@Test
public void testBlockIn() throws Exception
{
Socket client = newClient();
SocketChannel server = _connector.accept();
server.configureBlocking(false);
_manager.register(server);
OutputStream clientOutputStream = client.getOutputStream();
InputStream clientInputStream = client.getInputStream();
int specifiedTimeout = 400;
client.setSoTimeout(specifiedTimeout);
// Write 8 and cause block for 10
_blockAt=10;
clientOutputStream.write("12345678".getBytes("UTF-8"));
clientOutputStream.flush();
Thread.sleep(2 * specifiedTimeout);
// No echo as blocking for 10
long start=System.currentTimeMillis();
try
{
int b = clientInputStream.read();
Assert.fail("Should have timed out waiting for a response, but read "+b);
}
catch(SocketTimeoutException e)
{
int elapsed = Long.valueOf(System.currentTimeMillis() - start).intValue();
System.err.println("blocked for " + elapsed+ "ms");
Assert.assertThat("Expected timeout", elapsed, greaterThanOrEqualTo(3*specifiedTimeout/4));
}
// write remaining characters
clientOutputStream.write("90ABCDEF".getBytes("UTF-8"));
clientOutputStream.flush();
// Verify echo server to client
for (char c : "1234567890ABCDEF".toCharArray())
{
int b = clientInputStream.read();
assertTrue(b>0);
assertEquals(c,(char)b);
}
}
@Test
public void testIdle() throws Exception
{
Socket client = newClient();
client.setSoTimeout(3000);
SocketChannel server = _connector.accept();
server.configureBlocking(false);
_manager.register(server);
// Write client to server
client.getOutputStream().write("HelloWorld".getBytes("UTF-8"));
// Verify echo server to client
for (char c : "HelloWorld".toCharArray())
{
int b = client.getInputStream().read();
assertTrue(b>0);
assertEquals(c,(char)b);
}
// Set Max idle
_lastEndp.setMaxIdleTime(500);
// read until idle shutdown received
long start=System.currentTimeMillis();
int b=client.getInputStream().read();
assertEquals(-1,b);
long idle=System.currentTimeMillis()-start;
assertTrue(idle>400);
assertTrue(idle<2000);
// But endpoint is still open.
assertTrue(_lastEndp.isOpen());
// Wait for another idle callback
Thread.sleep(2000);
// endpoint is closed.
assertFalse(_lastEndp.isOpen());
}
@Test
public void testStress() throws Exception
{
Socket client = newClient();
client.setSoTimeout(30000);
SocketChannel server = _connector.accept();
server.configureBlocking(false);
_manager.register(server);
int writes = 100000;
final byte[] bytes="HelloWorld".getBytes("UTF-8");
final CountDownLatch latch = new CountDownLatch(writes);
final InputStream in = new BufferedInputStream(client.getInputStream());
final long start = System.currentTimeMillis();
client.getOutputStream().write(bytes);
client.getOutputStream().flush();
new Thread()
{
public void run()
{
try
{
while (latch.getCount()>0)
{
// Verify echo server to client
for (byte b0 : bytes)
{
int b = in.read();
assertTrue(b>0);
assertEquals(0xff&b0,b);
}
latch.countDown();
}
}
catch(Throwable e)
{
System.err.println("latch="+latch.getCount());
System.err.println("time="+(System.currentTimeMillis()-start));
e.printStackTrace();
}
}
}.start();
// Write client to server
for (int i=1;i<writes;i++)
{
client.getOutputStream().write(bytes);
Thread.yield();
}
client.getOutputStream().flush();
assertTrue(latch.await(100,TimeUnit.SECONDS));
}
}