
org.testifyproject.netty.handler.codec.ReplayingDecoder Maven / Gradle / Ivy
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in org.testifyproject.testifyprojectpliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org.testifyproject/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.testifyproject.testifyproject.netty.handler.codec;
import org.testifyproject.testifyproject.netty.buffer.ByteBuf;
import org.testifyproject.testifyproject.netty.channel.ChannelHandler;
import org.testifyproject.testifyproject.netty.channel.ChannelHandlerContext;
import org.testifyproject.testifyproject.netty.channel.ChannelPipeline;
import org.testifyproject.testifyproject.netty.util.Signal;
import org.testifyproject.testifyproject.netty.util.internal.RecyclableArrayList;
import org.testifyproject.testifyproject.netty.util.internal.StringUtil;
import java.util.List;
/**
* A specialized variation of {@link ByteToMessageDecoder} which enables implementation
* of a non-blocking org.testifyproject.testifyprojectcoder in the blocking I/O paradigm.
*
* The biggest difference between {@link ReplayingDecoder} and
* {@link ByteToMessageDecoder} is that {@link ReplayingDecoder} allows you to
* implement the {@code org.testifyproject.testifyprojectcode()} and {@code org.testifyproject.testifyprojectcodeLast()} methods just like
* all required bytes were received already, rather than checking the
* availability of the required bytes. For example, the following
* {@link ByteToMessageDecoder} implementation:
*
* public class IntegerHeaderFrameDecoder extends {@link ByteToMessageDecoder} {
*
* {@code @Override}
* protected void org.testifyproject.testifyprojectcode({@link ChannelHandlerContext} ctx,
* {@link ByteBuf} buf, List<Object> out) throws Exception {
*
* if (buf.readableBytes() < 4) {
* return;
* }
*
* buf.markReaderIndex();
* int length = buf.readInt();
*
* if (buf.readableBytes() < length) {
* buf.resetReaderIndex();
* return;
* }
*
* out.add(buf.readBytes(length));
* }
* }
*
* is simplified like the following with {@link ReplayingDecoder}:
*
* public class IntegerHeaderFrameDecoder
* extends {@link ReplayingDecoder}<{@link Void}> {
*
* protected void org.testifyproject.testifyprojectcode({@link ChannelHandlerContext} ctx,
* {@link ByteBuf} buf) throws Exception {
*
* out.add(buf.readBytes(buf.readInt()));
* }
* }
*
*
* How does this work?
*
* {@link ReplayingDecoder} passes a specialized {@link ByteBuf}
* implementation which throws an {@link Error} of certain type when there's not
* enough data in the buffer. In the {@code IntegerHeaderFrameDecoder} above,
* you just assumed that there will be 4 or more bytes in the buffer when
* you call {@code buf.readInt()}. If there's really 4 bytes in the buffer,
* it will return the integer header as you expected. Otherwise, the
* {@link Error} will be raised and the control will be returned to
* {@link ReplayingDecoder}. If {@link ReplayingDecoder} catches the
* {@link Error}, then it will rewind the {@code readerIndex} of the buffer
* back to the 'initial' position (i.e. the beginning of the buffer) and call
* the {@code org.testifyproject.testifyprojectcode(..)} method again when more data is received into the
* buffer.
*
* Please note that {@link ReplayingDecoder} always throws the same cached
* {@link Error} instance to avoid the overhead of creating a new {@link Error}
* and filling its stack trace for every throw.
*
*
Limitations
*
* At the cost of the simplicity, {@link ReplayingDecoder} enforces you a few
* limitations:
*
* - Some buffer operations are prohibited.
* - Performance can be worse if the network is slow and the message
* format is org.testifyproject.testifyprojectplicated unlike the example above. In this case, your
* org.testifyproject.testifyprojectcoder might have to org.testifyproject.testifyprojectcode the same part of the message over and over
* again.
* - You must keep in mind that {@code org.testifyproject.testifyprojectcode(..)} method can be called many
* times to org.testifyproject.testifyprojectcode a single message. For example, the following code will
* not work:
*
public class MyDecoder extends {@link ReplayingDecoder}<{@link Void}> {
*
* private final Queue<Integer> values = new LinkedList<Integer>();
*
* {@code @Override}
* public void org.testifyproject.testifyprojectcode(.., {@link ByteBuf} buf, List<Object> out) throws Exception {
*
* // A message contains 2 integers.
* values.offer(buf.readInt());
* values.offer(buf.readInt());
*
* // This assertion will fail intermittently since values.offer()
* // can be called more than two times!
* assert values.size() == 2;
* out.add(values.poll() + values.poll());
* }
* }
* The correct implementation looks like the following, and you can also
* utilize the 'checkpoint' feature which is explained in org.testifyproject.testifyprojecttail in the
* next section.
* public class MyDecoder extends {@link ReplayingDecoder}<{@link Void}> {
*
* private final Queue<Integer> values = new LinkedList<Integer>();
*
* {@code @Override}
* public void org.testifyproject.testifyprojectcode(.., {@link ByteBuf} buf, List<Object> out) throws Exception {
*
* // Revert the state of the variable that might have been changed
* // since the last partial org.testifyproject.testifyprojectcode.
* values.clear();
*
* // A message contains 2 integers.
* values.offer(buf.readInt());
* values.offer(buf.readInt());
*
* // Now we know this assertion will never fail.
* assert values.size() == 2;
* out.add(values.poll() + values.poll());
* }
* }
*
*
*
* Improving the performance
*
* Fortunately, the performance of a org.testifyproject.testifyprojectplex org.testifyproject.testifyprojectcoder implementation can be
* improved significantly with the {@code checkpoint()} method. The
* {@code checkpoint()} method updates the 'initial' position of the buffer so
* that {@link ReplayingDecoder} rewinds the {@code readerIndex} of the buffer
* to the last position where you called the {@code checkpoint()} method.
*
*
Calling {@code checkpoint(T)} with an {@link Enum}
*
* Although you can just use {@code checkpoint()} method and manage the state
* of the org.testifyproject.testifyprojectcoder by yourself, the easiest way to manage the state of the
* org.testifyproject.testifyprojectcoder is to create an {@link Enum} type which represents the current state
* of the org.testifyproject.testifyprojectcoder and to call {@code checkpoint(T)} method whenever the state
* changes. You can have as many states as you want org.testifyproject.testifyprojectpending on the
* org.testifyproject.testifyprojectplexity of the message you want to org.testifyproject.testifyprojectcode:
*
*
* public enum MyDecoderState {
* READ_LENGTH,
* READ_CONTENT;
* }
*
* public class IntegerHeaderFrameDecoder
* extends {@link ReplayingDecoder}<MyDecoderState> {
*
* private int length;
*
* public IntegerHeaderFrameDecoder() {
* // Set the initial state.
* super(MyDecoderState.READ_LENGTH);
* }
*
* {@code @Override}
* protected void org.testifyproject.testifyprojectcode({@link ChannelHandlerContext} ctx,
* {@link ByteBuf} buf, List<Object> out) throws Exception {
* switch (state()) {
* case READ_LENGTH:
* length = buf.readInt();
* checkpoint(MyDecoderState.READ_CONTENT);
* case READ_CONTENT:
* ByteBuf frame = buf.readBytes(length);
* checkpoint(MyDecoderState.READ_LENGTH);
* out.add(frame);
* break;
* org.testifyproject.testifyprojectfault:
* throw new Error("Shouldn't reach here.");
* }
* }
* }
*
*
* Calling {@code checkpoint()} with no parameter
*
* An alternative way to manage the org.testifyproject.testifyprojectcoder state is to manage it by yourself.
*
* public class IntegerHeaderFrameDecoder
* extends {@link ReplayingDecoder}<{@link Void}> {
*
* private boolean readLength;
* private int length;
*
* {@code @Override}
* protected void org.testifyproject.testifyprojectcode({@link ChannelHandlerContext} ctx,
* {@link ByteBuf} buf, List<Object> out) throws Exception {
* if (!readLength) {
* length = buf.readInt();
* readLength = true;
* checkpoint();
* }
*
* if (readLength) {
* ByteBuf frame = buf.readBytes(length);
* readLength = false;
* checkpoint();
* out.add(frame);
* }
* }
* }
*
*
* Replacing a org.testifyproject.testifyprojectcoder with another org.testifyproject.testifyprojectcoder in a pipeline
*
* If you are going to write a protocol multiplexer, you will probably want to
* replace a {@link ReplayingDecoder} (protocol org.testifyproject.testifyprojecttector) with another
* {@link ReplayingDecoder}, {@link ByteToMessageDecoder} or {@link MessageToMessageDecoder}
* (actual protocol org.testifyproject.testifyprojectcoder).
* It is not possible to achieve this simply by calling
* {@link ChannelPipeline#replace(ChannelHandler, String, ChannelHandler)}, but
* some additional steps are required:
*
* public class FirstDecoder extends {@link ReplayingDecoder}<{@link Void}> {
*
* {@code @Override}
* protected void org.testifyproject.testifyprojectcode({@link ChannelHandlerContext} ctx,
* {@link ByteBuf} buf, List<Object> out) {
* ...
* // Decode the first message
* Object firstMessage = ...;
*
* // Add the second org.testifyproject.testifyprojectcoder
* ctx.pipeline().addLast("second", new SecondDecoder());
*
* if (buf.isReadable()) {
* // Hand off the remaining data to the second org.testifyproject.testifyprojectcoder
* out.add(firstMessage);
* out.add(buf.readBytes(super.actualReadableBytes()));
* } else {
* // Nothing to hand off
* out.add(firstMessage);
* }
* // Remove the first org.testifyproject.testifyprojectcoder (me)
* ctx.pipeline().remove(this);
* }
*
* @param
* the state type which is usually an {@link Enum}; use {@link Void} if state management is
* unused
*/
public abstract class ReplayingDecoder extends ByteToMessageDecoder {
static final Signal REPLAY = Signal.valueOf(ReplayingDecoder.class, "REPLAY");
private final ReplayingDecoderByteBuf replayable = new ReplayingDecoderByteBuf();
private S state;
private int checkpoint = -1;
/**
* Creates a new instance with no initial state (i.e: {@code null}).
*/
protected ReplayingDecoder() {
this(null);
}
/**
* Creates a new instance with the specified initial state.
*/
protected ReplayingDecoder(S initialState) {
state = initialState;
}
/**
* Stores the internal cumulative buffer's reader position.
*/
protected void checkpoint() {
checkpoint = internalBuffer().readerIndex();
}
/**
* Stores the internal cumulative buffer's reader position and updates
* the current org.testifyproject.testifyprojectcoder state.
*/
protected void checkpoint(S state) {
checkpoint();
state(state);
}
/**
* Returns the current state of this org.testifyproject.testifyprojectcoder.
* @return the current state of this org.testifyproject.testifyprojectcoder
*/
protected S state() {
return state;
}
/**
* Sets the current state of this org.testifyproject.testifyprojectcoder.
* @return the old state of this org.testifyproject.testifyprojectcoder
*/
protected S state(S newState) {
S oldState = state;
state = newState;
return oldState;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
RecyclableArrayList out = RecyclableArrayList.newInstance();
try {
replayable.terminate();
callDecode(ctx, internalBuffer(), out);
org.testifyproject.testifyprojectcodeLast(ctx, replayable, out);
} catch (Signal replay) {
// Ignore
replay.expect(REPLAY);
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
try {
if (cumulation != null) {
cumulation.release();
cumulation = null;
}
int size = out.size();
if (size > 0) {
fireChannelRead(ctx, out, size);
// Something was read, call fireChannelReadComplete()
ctx.fireChannelReadComplete();
}
ctx.fireChannelInactive();
} finally {
// recycle in all cases
out.recycle();
}
}
}
@Override
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List