INT-2876 Use Custom InputStream For NIO

Previously TcpNioConnection used a pair of Piped(In|Out)putStreams
to pass data from the read event to the deserializer.

However, it is possible to get a 'Broken Pipe' exception if the
last thread that wrote to the pipe terminates.

Replace the use of this pair of streams with an inner class
ChannelInputStream, which provides the same functionality, but
with no dependency on threading.

Uses a BlockingQueue to store read buffers which are consumed by
the message assembler thread (read from the InputStream in the
Deserializer). To avoid OOM conditions, the queue is limited to
5 "unread" buffers; and a timeout will occur if the reader
doesn't consume some data to free up space in the queue.
This commit is contained in:
Gary Russell
2013-01-09 16:23:43 -05:00
committed by Mark Fisher
parent a8aea3df17
commit 462e0f8cb1
4 changed files with 163 additions and 81 deletions

View File

@@ -86,6 +86,7 @@ public class ConnectionToConnectionTests {
TcpConnection connection = client.getConnection();
connection.send(MessageBuilder.withPayload("Test").build());
Message<?> message = serverSideChannel.receive(10000);
assertNotNull(message);
MessageHistory history = MessageHistory.read(message);
//org.springframework.integration.test.util.TestUtils
Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "looper", 0);
@@ -105,6 +106,7 @@ public class ConnectionToConnectionTests {
TcpConnection connection = client.getConnection();
connection.send(MessageBuilder.withPayload("Test").build());
Message<?> message = serverSideChannel.receive(10000);
assertNotNull(message);
MessageHistory history = MessageHistory.read(message);
//org.springframework.integration.test.util.TestUtils
Properties componentHistoryRecord = TestUtils.locateComponentInHistory(history, "looper", 0);

View File

@@ -45,84 +45,84 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class TcpConfigInboundGatewayTests {
static AbstractApplicationContext staticContext;
@Autowired
AbstractApplicationContext ctx;
@Autowired
@Qualifier(value="crLfServer")
AbstractServerConnectionFactory crLfServer;
@Autowired
@Qualifier(value="stxEtxServer")
AbstractServerConnectionFactory stxEtxServer;
@Autowired
@Qualifier(value="lengthHeaderServer")
AbstractServerConnectionFactory lengthHeaderServer;
@Autowired
@Qualifier(value="javaSerialServer")
AbstractServerConnectionFactory javaSerialServer;
@Autowired
@Qualifier(value="crLfClient")
AbstractClientConnectionFactory crLfClient;
@Autowired
@Qualifier(value="stxEtxClient")
AbstractClientConnectionFactory stxEtxClient;
@Autowired
@Qualifier(value="lengthHeaderClient")
AbstractClientConnectionFactory lengthHeaderClient;
@Autowired
@Qualifier(value="javaSerialClient")
AbstractClientConnectionFactory javaSerialClient;
@Autowired
@Qualifier(value="crLfServerNio")
AbstractServerConnectionFactory crLfServerNio;
@Autowired
@Qualifier(value="stxEtxServerNio")
AbstractServerConnectionFactory stxEtxServerNio;
@Autowired
@Qualifier(value="lengthHeaderServerNio")
AbstractServerConnectionFactory lengthHeaderServerNio;
@Autowired
@Qualifier(value="javaSerialServerNio")
AbstractServerConnectionFactory javaSerialServerNio;
@Autowired
@Qualifier(value="crLfClientNio")
AbstractClientConnectionFactory crLfClientNio;
@Autowired
@Qualifier(value="stxEtxClientNio")
AbstractClientConnectionFactory stxEtxClientNio;
@Autowired
@Qualifier(value="lengthHeaderClientNio")
AbstractClientConnectionFactory lengthHeaderClientNio;
@Autowired
@Qualifier(value="javaSerialClientNio")
AbstractClientConnectionFactory javaSerialClientNio;
@Autowired
@Qualifier(value="gatewayCrLf")
TcpInboundGateway gatewayCrLf;
@Autowired
@Qualifier(value="gatewayStxEtx")
TcpInboundGateway gatewayStxEtx;
@Autowired
@Qualifier(value="gatewayLength")
TcpInboundGateway gatewayLength;
@@ -134,11 +134,11 @@ public class TcpConfigInboundGatewayTests {
@Autowired
@Qualifier(value="gatewayCrLfNio")
TcpInboundGateway gatewayCrLfNio;
@Autowired
@Qualifier(value="gatewayStxEtxNio")
TcpInboundGateway gatewayStxEtxNio;
@Autowired
@Qualifier(value="gatewayLengthNio")
TcpInboundGateway gatewayLengthNio;
@@ -156,8 +156,8 @@ public class TcpConfigInboundGatewayTests {
@Test
public void testCrLfNio() throws Exception {
waitListening(gatewayCrLf);
Socket socket = SocketFactory.getDefault().createSocket("localhost", crLfServer.getPort());
waitListening(gatewayCrLfNio);
Socket socket = SocketFactory.getDefault().createSocket("localhost", crLfServerNio.getPort());
crLfGuts(socket);
}
@@ -285,7 +285,7 @@ public class TcpConfigInboundGatewayTests {
throw new Exception("Gateway failed to listen");
}
}
}
@Before
@@ -294,7 +294,7 @@ public class TcpConfigInboundGatewayTests {
staticContext = ctx;
}
}
@AfterClass
public static void shutDown() {
staticContext.close();

View File

@@ -26,7 +26,6 @@ import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
@@ -62,8 +61,6 @@ import org.springframework.integration.test.util.TestUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.ReflectionUtils.MethodFilter;
/**
@@ -260,29 +257,26 @@ public class TcpNioConnectionTests {
doAnswer(new Answer<Integer>() {
public Integer answer(InvocationOnMock invocation) throws Throwable {
ByteBuffer buffer = (ByteBuffer) invocation.getArguments()[0];
buffer.position(1025);
return 1025;
buffer.position(1);
return 1;
}
}).when(channel).read(Mockito.any(ByteBuffer.class));
when(socket.getReceiveBufferSize()).thenReturn(1024);
final TcpNioConnection connection = new TcpNioConnection(channel, false, false);
connection.setTaskExecutor(exec);
connection.setPipeTimeout(200);
ReflectionUtils.doWithMethods(TcpNioConnection.class, new MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
method.setAccessible(true);
try {
method.invoke(connection, (Object[]) null);
}
catch (InvocationTargetException e) {
throw (RuntimeException) e.getCause();
}
Method method = TcpNioConnection.class.getDeclaredMethod("doRead");
method.setAccessible(true);
// Nobody reading, should timeout on 6th write.
try {
for (int i = 0; i < 6; i++) {
method.invoke(connection);
}
}, new MethodFilter() {
public boolean matches(Method method) {
return method.getName().equals("doRead");
}
});
}
catch (Exception e) {
e.printStackTrace();
throw (Exception) e.getCause();
}
return null;
}
});
@@ -291,7 +285,7 @@ public class TcpNioConnectionTests {
fail("Expected exception, got " + o);
}
catch (ExecutionException e) {
assertEquals("Timed out writing to pipe, probably due to insufficient threads in " +
assertEquals("Timed out writing to ChannelInputStream, probably due to insufficient threads in " +
"a fixed thread pool; consider increasing this task executor pool size", e.getCause()
.getMessage());
}
@@ -319,28 +313,23 @@ public class TcpNioConnectionTests {
connection.setTaskExecutor(exec);
connection.registerListener(new TcpListener(){
public boolean onMessage(Message<?> message) {
System.out.println(message);
messageLatch.countDown();
return false;
}
});
connection.setMapper(new TcpMessageMapper());
connection.setDeserializer(new ByteArrayCrLfSerializer());
ReflectionUtils.doWithMethods(TcpNioConnection.class, new MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
method.setAccessible(true);
try {
method.invoke(connection, (Object[]) null);
}
catch (InvocationTargetException e) {
throw (RuntimeException) e.getCause();
}
Method method = TcpNioConnection.class.getDeclaredMethod("doRead");
method.setAccessible(true);
try {
for (int i = 0; i < 20; i++) {
method.invoke(connection);
}
}, new MethodFilter() {
public boolean matches(Method method) {
return method.getName().equals("doRead");
}
});
}
catch (Exception e) {
e.printStackTrace();
throw (Exception) e.getCause();
}
return null;
}
});