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:
committed by
Mark Fisher
parent
a8aea3df17
commit
462e0f8cb1
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user