INT-4029: TCP: Add Buffer Pooling to Deserializers

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

Support the use of buffer pools in the deserializer code to
allow buffer reuse.
This commit is contained in:
Gary Russell
2016-05-11 12:29:14 -04:00
committed by Artem Bilan
parent f11dd87f68
commit 06a1d503be
9 changed files with 229 additions and 44 deletions

View File

@@ -75,22 +75,6 @@ public abstract class AbstractByteArraySerializer implements
}
}
/**
* Copy size bytes to a new buffer exactly size bytes long.
* @param buffer The buffer containing the data.
* @param size The number of bytes to copy.
* @return The new buffer, or the buffer parameter if it is
* already the correct size.
*/
protected byte[] copyToSizedArray(byte[] buffer, int size) {
if (size == buffer.length) {
return buffer;
}
byte[] assembledData = new byte[size];
System.arraycopy(buffer, 0, assembledData, 0, size);
return assembledData;
}
protected void publishEvent(Exception cause, byte[] buffer, int offset) {
TcpDeserializationExceptionEvent event = new TcpDeserializationExceptionEvent(this, cause, buffer, offset);
if (this.applicationEventPublisher != null) {

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ip.tcp.serializer;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.integration.util.SimplePool;
import org.springframework.integration.util.SimplePool.PoolItemCallback;
import org.springframework.util.Assert;
/**
* Base class for deserializers that cannot determine the buffer size needed.
* Optionally pools buffers.
*
* @author Gary Russell
* @since 4.3
*
*/
public abstract class AbstractPooledBufferByteArraySerializer extends AbstractByteArraySerializer {
private SimplePool<byte[]> pool;
private long poolWaitTimeout = Long.MAX_VALUE;
/**
* Set the pool size for deserialization buffers.
* @param size the size, -1 for unlimited.
* @since 4.3
*/
public void setPoolSize(int size) {
Assert.isNull(this.pool, "Cannot change pool size once set");
this.pool = new SimplePool<byte[]>(size, new PoolItemCallback<byte[]>() {
@Override
public byte[] createForPool() {
return new byte[getMaxMessageSize()];
}
@Override
public boolean isStale(byte[] item) {
return false; // never stale
}
@Override
public void removedFromPool(byte[] item) {
}
});
this.pool.setWaitTimeout(this.poolWaitTimeout);
}
/**
* Set the pool wait timeout if a pool is configured, default unlimited.
* @param poolWaitTimeout the timeout.
*/
public void setPoolWaitTimeout(long poolWaitTimeout) {
this.poolWaitTimeout = poolWaitTimeout;
if (this.pool != null) {
this.pool.setWaitTimeout(poolWaitTimeout);
}
}
@Override
public final byte[] deserialize(InputStream inputStream) throws IOException {
byte[] buffer = this.pool == null ? new byte[this.maxMessageSize] : this.pool.getItem();
try {
return doDeserialize(inputStream, buffer);
}
finally {
if (this.pool != null) {
this.pool.releaseItem(buffer);
}
}
}
/**
* @param inputStream the input stream.
* @param buffer the raw working buffer (maxMessageSize).
* @return the decoded bytes.
* @throws IOException an io exception.
* @since 4.3
*/
protected abstract byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException;
/**
* Copy size bytes to a new buffer exactly size bytes long. If a pool is not
* in use and the array is already the correct length, it is simply returned.
* @param buffer The buffer containing the data.
* @param size The number of bytes to copy.
* @return The new buffer, or the buffer parameter if it is
* already the correct size and there is no pool.
*/
protected byte[] copyToSizedArray(byte[] buffer, int size) {
if (size == buffer.length && this.pool == null) {
return buffer;
}
byte[] assembledData = new byte[size];
System.arraycopy(buffer, 0, assembledData, 0, size);
return assembledData;
}
}

View File

@@ -28,7 +28,7 @@ import java.io.OutputStream;
* @author Gary Russell
* @since 2.0
*/
public class ByteArrayCrLfSerializer extends AbstractByteArraySerializer {
public class ByteArrayCrLfSerializer extends AbstractPooledBufferByteArraySerializer {
private static final byte[] CRLF = "\r\n".getBytes();
@@ -39,8 +39,7 @@ public class ByteArrayCrLfSerializer extends AbstractByteArraySerializer {
* being read).
*/
@Override
public byte[] deserialize(InputStream inputStream) throws IOException {
byte[] buffer = new byte[this.maxMessageSize];
public byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException {
int n = this.fillToCrLf(inputStream, buffer);
return this.copyToSizedArray(buffer, n);
}

View File

@@ -41,7 +41,7 @@ import java.net.SocketTimeoutException;
* @since 2.0.3
*
*/
public class ByteArrayRawSerializer extends AbstractByteArraySerializer {
public class ByteArrayRawSerializer extends AbstractPooledBufferByteArraySerializer {
private final boolean treatTimeoutAsEndOfMessage;
@@ -66,8 +66,7 @@ public class ByteArrayRawSerializer extends AbstractByteArraySerializer {
}
@Override
public byte[] deserialize(InputStream inputStream) throws IOException {
byte[] buffer = new byte[this.maxMessageSize];
protected byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException {
int n = 0;
int bite = 0;
if (logger.isDebugEnabled()) {
@@ -90,15 +89,13 @@ public class ByteArrayRawSerializer extends AbstractByteArraySerializer {
}
break;
}
buffer[n++] = (byte) bite;
if (n >= this.maxMessageSize) {
throw new IOException("Socket was not closed before max message length: "
+ this.maxMessageSize);
}
buffer[n++] = (byte) bite;
}
byte[] assembledData = new byte[n];
System.arraycopy(buffer, 0, assembledData, 0, n);
return assembledData;
return copyToSizedArray(buffer, n);
}
catch (SoftEndOfStreamException e) {
throw e;

View File

@@ -28,7 +28,7 @@ import java.io.OutputStream;
* @author Gary Russell
* @since 2.2
*/
public class ByteArraySingleTerminatorSerializer extends AbstractByteArraySerializer {
public class ByteArraySingleTerminatorSerializer extends AbstractPooledBufferByteArraySerializer {
private final byte terminator;
@@ -43,8 +43,7 @@ public class ByteArraySingleTerminatorSerializer extends AbstractByteArraySerial
* being read).
*/
@Override
public byte[] deserialize(InputStream inputStream) throws IOException {
byte[] buffer = new byte[this.maxMessageSize];
protected byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException {
int n = 0;
int bite;
if (logger.isDebugEnabled()) {
@@ -67,9 +66,7 @@ public class ByteArraySingleTerminatorSerializer extends AbstractByteArraySerial
+ this.maxMessageSize);
}
}
byte[] assembledData = new byte[n];
System.arraycopy(buffer, 0, assembledData, 0, n);
return assembledData;
return copyToSizedArray(buffer, n);
}
catch (SoftEndOfStreamException e) {
throw e;

View File

@@ -30,7 +30,7 @@ import org.springframework.integration.mapping.MessageMappingException;
* @author Gary Russell
* @since 2.0
*/
public class ByteArrayStxEtxSerializer extends AbstractByteArraySerializer {
public class ByteArrayStxEtxSerializer extends AbstractPooledBufferByteArraySerializer {
public static final int STX = 0x02;
@@ -45,18 +45,16 @@ public class ByteArrayStxEtxSerializer extends AbstractByteArraySerializer {
*
*/
@Override
public byte[] deserialize(InputStream inputStream) throws IOException {
public byte[] doDeserialize(InputStream inputStream, byte[] buffer) throws IOException {
int bite = inputStream.read();
if (bite < 0) {
throw new SoftEndOfStreamException("Stream closed between payloads");
}
byte[] buffer = null;
int n = 0;
try {
if (bite != STX) {
throw new MessageMappingException("Expected STX to begin message");
}
buffer = new byte[this.maxMessageSize];
while ((bite = inputStream.read()) != ETX) {
checkClosure(bite);
buffer[n++] = (byte) bite;
@@ -65,9 +63,7 @@ public class ByteArrayStxEtxSerializer extends AbstractByteArraySerializer {
+ this.maxMessageSize);
}
}
byte[] assembledData = new byte[n];
System.arraycopy(buffer, 0, assembledData, 0, n);
return assembledData;
return copyToSizedArray(buffer, n);
}
catch (IOException e) {
publishEvent(e, buffer, n);

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.integration.ip.tcp.serializer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Set;
import org.junit.Test;
import org.springframework.integration.test.util.TestUtils;
/**
* @author Gary Russell
* @since 4.3
*
*/
public class PooledDeserializationTests {
@Test
public void testCRLF() throws IOException {
ByteArrayCrLfSerializer deser = new ByteArrayCrLfSerializer();
deser.setPoolSize(2);
ByteArrayInputStream bais = new ByteArrayInputStream("foo\r\n".getBytes());
for (int i = 0; i < 5; i++) {
bais.reset();
byte[] bytes = deser.deserialize(bais);
assertEquals("foo", new String(bytes));
}
try {
deser.deserialize(bais);
fail("Expected SoftEndOfStreamException");
}
catch (SoftEndOfStreamException e) {
// expected
}
assertEquals(1, TestUtils.getPropertyValue(deser, "pool.allocated", Set.class).size());
assertEquals(0, TestUtils.getPropertyValue(deser, "pool.inUse", Set.class).size());
}
@Test
public void testRawMaxMessageSizeEqualDontReturnPooledItem() throws IOException {
ByteArrayRawSerializer deser = new ByteArrayRawSerializer();
deser.setPoolSize(2);
deser.setMaxMessageSize(3);
ByteArrayInputStream bais = new ByteArrayInputStream("foo".getBytes());
byte[] bytes = deser.deserialize(bais);
assertEquals("foo", new String(bytes));
assertEquals(1, TestUtils.getPropertyValue(deser, "pool.allocated", Set.class).size());
assertEquals(0, TestUtils.getPropertyValue(deser, "pool.inUse", Set.class).size());
assertNotSame(bytes, TestUtils.getPropertyValue(deser, "pool.allocated", Set.class).iterator().next());
}
}