INT-4465: Fix delay in close propagation with NIO

JIRA: https://jira.spring.io/browse/INT-4465

There is a one second delay before a socket close is propagated if there is an active
assembler. This is generally only a problem with deserializers that use EOF to signal
message end (such as the `ByteArrayElasticRawDeserializer`).

Attempt to insert an EOF marker into the buffer queue so that the `getNextBuffer()` will
exit immediately on `close()` if it is blocked awaiting a buffer.

**cherry-pick to 5.0.x, 4.3.x**

(cherry picked from commit d9186f1)

# Conflicts:
#	spring-integration-ip/src/test/java/org/springframework/integration/ip/tcp/connection/TcpNioConnectionTests.java
This commit is contained in:
Gary Russell
2018-05-09 10:39:29 -04:00
committed by Artem Bilan
parent c625f9e2c1
commit f912aadb4b
2 changed files with 54 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,6 +58,8 @@ public class TcpNioConnection extends TcpConnectionSupport {
private static final long DEFAULT_PIPE_TIMEOUT = 60000;
private static final byte[] EOF = new byte[0]; // EOF marker buffer
private final SocketChannel socketChannel;
private final ChannelOutputStream channelOutputStream;
@@ -703,7 +705,7 @@ public class TcpNioConnection extends TcpConnectionSupport {
while (buffer == null) {
try {
buffer = this.buffers.poll(1, TimeUnit.SECONDS);
if (buffer == null && this.isClosed) {
if (buffer == EOF || (buffer == null && this.isClosed)) {
return null;
}
}
@@ -747,6 +749,12 @@ public class TcpNioConnection extends TcpConnectionSupport {
public void close() throws IOException {
super.close();
this.isClosed = true;
try {
this.buffers.offer(EOF, TcpNioConnection.this.pipeTimeout, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override

View File

@@ -17,6 +17,7 @@
package org.springframework.integration.ip.tcp.connection;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -65,6 +66,7 @@ import javax.net.SocketFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Level;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
@@ -91,6 +93,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;
import org.springframework.util.StopWatch;
/**
@@ -129,6 +132,7 @@ public class TcpNioConnectionTests {
Socket s = server.accept();
// block so we fill the buffer
done.await(10, TimeUnit.SECONDS);
s.close();
}
catch (Exception e) {
e.printStackTrace();
@@ -822,6 +826,46 @@ public class TcpNioConnectionTests {
factory.stop();
}
@Test
@Ignore // Timing is too short for CI/Travis
public void testNoDelayOnClose() throws Exception {
TcpNioServerConnectionFactory cf = new TcpNioServerConnectionFactory(0);
final CountDownLatch reading = new CountDownLatch(1);
final StopWatch watch = new StopWatch();
cf.setDeserializer(is -> {
reading.countDown();
watch.start();
is.read();
is.read();
watch.stop();
return null;
});
cf.registerListener(m -> false);
final CountDownLatch listening = new CountDownLatch(1);
cf.setApplicationEventPublisher(new ApplicationEventPublisher() {
@Override
public void publishEvent(Object e) {
listening.countDown();
}
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
});
cf.afterPropertiesSet();
cf.start();
assertTrue(listening.await(10, TimeUnit.SECONDS));
Socket socket = SocketFactory.getDefault().createSocket("localhost", cf.getPort());
socket.getOutputStream().write("x".getBytes());
assertTrue(reading.await(10, TimeUnit.SECONDS));
socket.close();
cf.stop();
assertThat(watch.getLastTaskTimeMillis(), lessThan(950L));
}
private void readFully(InputStream is, byte[] buff) throws IOException {
for (int i = 0; i < buff.length; i++) {
buff[i] = (byte) is.read();