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:
committed by
Artem Bilan
parent
f11dd87f68
commit
06a1d503be
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user