/*
* Copyright (C) 2012-2016 Facebook, Inc.
*
* 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 com.facebook.nifty.core;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TTransportException;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Implementation of {@link TTransport} that buffers the output of a single message,
* so that an async client can grab the buffer and send it
*
* Allows for reusing the same transport to write multiple messages via
* {@link com.facebook.nifty.core.TChannelBufferOutputTransport#resetOutputBuffer()}
*/
@NotThreadSafe
public class TChannelBufferOutputTransport extends TTransport
{
private static final int DEFAULT_MINIMUM_SIZE = 1024;
// This threshold sets how many times the buffer must be under-utilized before we'll
// reclaim some memory by reallocating it with half the current size
private static final int UNDER_USE_THRESHOLD = 5;
private ChannelBuffer outputBuffer;
private final int minimumSize;
private int bufferUnderUsedCounter;
public TChannelBufferOutputTransport()
{
this.minimumSize = DEFAULT_MINIMUM_SIZE;
outputBuffer = ChannelBuffers.dynamicBuffer(this.minimumSize);
}
public TChannelBufferOutputTransport(int minimumSize)
{
this.minimumSize = Math.min(DEFAULT_MINIMUM_SIZE, minimumSize);
outputBuffer = ChannelBuffers.dynamicBuffer(this.minimumSize);
}
@Override
public boolean isOpen()
{
return true;
}
@Override
public void open() throws TTransportException
{
throw new UnsupportedOperationException();
}
@Override
public void close()
{
throw new UnsupportedOperationException();
}
@Override
public int read(byte[] buf, int off, int len) throws TTransportException
{
throw new UnsupportedOperationException();
}
@Override
public void write(byte[] buf, int off, int len) throws TTransportException
{
outputBuffer.writeBytes(buf, off, len);
}
/**
* Resets the state of this transport so it can be used to write more messages
*/
public void resetOutputBuffer()
{
int shrunkenSize = shrinkBufferSize();
if (outputBuffer.writerIndex() < shrunkenSize) {
// Less than the shrunken size of the buffer was actually used, so increment
// the under-use counter
++bufferUnderUsedCounter;
}
else {
// More than the shrunken size of the buffer was actually used, reset
// the counter so we won't shrink the buffer soon
bufferUnderUsedCounter = 0;
}
if (shouldShrinkBuffer()) {
outputBuffer = ChannelBuffers.dynamicBuffer(shrunkenSize);
bufferUnderUsedCounter = 0;
} else {
outputBuffer.clear();
}
}
public ChannelBuffer getOutputBuffer()
{
return outputBuffer;
}
/**
* Checks whether we should shrink the buffer, which should happen if we've under-used it
* UNDER_USE_THRESHOLD times in a row
*/
@SuppressWarnings("PMD.UselessParentheses")
private boolean shouldShrinkBuffer()
{
// We want to shrink the buffer if it has been under-utilized UNDER_USE_THRESHOLD
// times in a row, and the size after shrinking would not be smaller than the minimum size
return bufferUnderUsedCounter > UNDER_USE_THRESHOLD &&
shrinkBufferSize() >= minimumSize;
}
/**
* Returns the size the buffer will be if we shrink it
*/
private int shrinkBufferSize()
{
return outputBuffer.capacity() >> 1;
}
}