convex.net.MessageReceiver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of convex-peer Show documentation
Show all versions of convex-peer Show documentation
Convex Peer implementation and APIs
The newest version!
package convex.net;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import convex.core.data.Blob;
import convex.core.data.Format;
import convex.core.exceptions.BadFormatException;
import convex.net.impl.HandlerException;
import convex.peer.Config;
/**
* Class responsible for buffered accumulation of data received over a connection.
*
* ByteBuffers received must be passed in via @receiveFromChannel
*
* Passes any successfully received objects to a specified Consumer, using the same thread on which the
* MessageReceiver was called.
*
*
* "There are only two hard problems in distributed systems: 2. Exactly-once
* delivery 1. Guaranteed order of messages 2. Exactly-once delivery"
*
*
*
*
*
*/
public class MessageReceiver {
// Receive buffer must be big enough at least for one max sized message plus message header
public static final int RECEIVE_BUFFER_SIZE = Config.RECEIVE_BUFFER_SIZE;
/**
* Buffer for receiving partial messages. Maintained ready for writing.
*
* Maybe use a direct buffer since we are copying from the socket channel? But probably doesn't make any difference.
*/
private ByteBuffer buffer = ByteBuffer.allocate(RECEIVE_BUFFER_SIZE);
private final Consumer action;
private Consumer hook=null;
private final Connection connection;
private long receivedMessageCount = 0;
private static final Logger log = LoggerFactory.getLogger(MessageReceiver.class.getName());
public MessageReceiver(Consumer receiveAction, Connection pc) {
this.action = receiveAction;
this.connection = pc;
}
/**
* Get the number of messages received in total by this Receiver
* @return Count of messages received
*/
public long getReceivedCount() {
return receivedMessageCount;
}
/**
* Handles receipt of bytes from a channel. Should be called with a
* ReadableByteChannel containing bytes received.
*
* May be called multiple times during receipt of a single message, i.e. can
* handle partial message receipt.
*
* Will consume enough bytes from channel to handle exactly one message. Bytes
* will be left unconsumed on the channel if more are available.
*
* This hopefully
* creates sufficient backpressure on clients sending a lot of messages.
*
* @param chan Byte channel
* @throws IOException If IO error occurs
* @return The number of bytes read from the channel, or -1 if EOS
* @throws BadFormatException If a bad encoding is received
* @throws HandlerException If the message handler throws an unexpected Exception
*/
public synchronized int receiveFromChannel(ReadableByteChannel chan) throws IOException, BadFormatException, HandlerException {
int numRead=0;
numRead = chan.read(buffer);
if (numRead <= 0) {
// no bytes received / at end of stream
return numRead;
}
while (buffer.position()>0) {
// peek message length at start of buffer. May throw BFE.
int len = Format.peekMessageLength(buffer);
if (len<0) return numRead; // Not enough bytes for a message length yet
int lengthLength = Format.getVLCLength(len);
int totalFrameSize=lengthLength + len;
if (totalFrameSize>buffer.capacity()) {
int newSize=Math.max(totalFrameSize, buffer.position());
ByteBuffer newBuffer=ByteBuffer.allocate(newSize);
buffer.flip();
newBuffer.put(buffer);
buffer=newBuffer;
}
// Exit if we hven't got the full message yet
if (buffer.position() hook=this.hook;
if (hook!=null) {
hook.accept(message);
}
}
/**
* Sets an optional additional message receiver hook (for debugging / observability purposes)
* @param hook Hook to call when a message is received
*/
public void setHook(Consumer hook) {
this.hook = hook;
}
}