org.nustaq.fastcast.impl.PacketReceiveBuffer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fast-cast Show documentation
Show all versions of fast-cast Show documentation
a fast brokerless messaging library based on reliable UDP multicast
The newest version!
/**
* Copyright (c) 2014, Ruediger Moeller. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* Date: 03.01.14
* Time: 21:19
* To change this template use File | Settings | File Templates.
*/
package org.nustaq.fastcast.impl;
import org.nustaq.fastcast.api.*;
import org.nustaq.fastcast.util.FCLog;
import org.nustaq.offheap.bytez.Bytez;
import org.nustaq.offheap.bytez.malloc.MallocBytezAllocator;
import org.nustaq.offheap.structs.FSTStruct;
import org.nustaq.offheap.structs.FSTStructAllocator;
import org.nustaq.offheap.structs.structtypes.StructArray;
import org.nustaq.offheap.structs.structtypes.StructString;
/**
* Created with IntelliJ IDEA.
* User: ruedi
* Date: 8/11/13
* Time: 4:47 PM
* To change this template use File | Settings | File Templates.
*/
/**
* tracks packets and sequences sent from a single sender
*/
public class PacketReceiveBuffer {
public static final int MAX_NON_GAP_PACKET_SERIES_TO_JUSTIFY_NEW_RETRANS_ENTRY = 20;
final int topic;
final int payMaxLen;
final FSTStructAllocator packetAllocator;
final StructArray readBuffer;
long maxOrderedSeq = 0; // highest ordered
long highestSeq = 0; // highest sequence ever received
String receivesFrom; // a receiveBuffer is responsible for a single sender only
final StructString nodeId;
FCSubscriber receiver;
RetransPacket retrans; // used temporary from receive to return retrans packet
Defragmenter decoder = new Defragmenter() {
@Override
public void msgDone(long seq, Bytez b, int off, int len) {
if ( len == 1 && b.get(off) == ControlPacket.HEARTBEAT ) {
// timestamp updated anyway
} else {
receiver.messageReceived(receivesFrom,seq,b,off,len);
}
}
};
private boolean isUnordered = false;
private boolean isUnreliable = false;
private volatile boolean terminated = false;
int dGramSize;
RetransPacket retransTemplate;
DataPacket template;
volatile long lastHBMillis;
public PacketReceiveBuffer(int dataGramSizeBytes, String theNodeId, int historySize, String receivesFrom, Topic entry, FCSubscriber receiver) {
topicEntry = entry;
dGramSize = dataGramSizeBytes;
this.topic = entry.getTopicId();
this.receiver = receiver;
template = DataPacket.getTemplate(dataGramSizeBytes);
payMaxLen = template.data.length;
template.getSender().setString(theNodeId);
template.setTopic(topic);
retransTemplate = new RetransPacket();
retransTemplate.getSender().setString(theNodeId);
retransTemplate.getReceiver().setString(receivesFrom);
retransTemplate.setTopic(topic);
retransTemplate.setSeqNo(-1);
packetAllocator = new FSTStructAllocator(10, new MallocBytezAllocator());
readBuffer = packetAllocator.newArray(historySize,template);
this.nodeId = packetAllocator.newStruct( new StructString(Packet.MAX_NODE_NAME_LEN) );
this.nodeId.setString(theNodeId);
if ( readBuffer.getByteSize() > 5*1024*1024 ) {
FCLog.log("allocating read buffer for topic '"+topicEntry.getTopicId()+"' of "+(readBuffer.getByteSize()/1024/1024)+" MByte");
} else {
FCLog.log("allocating read buffer for topic '"+topicEntry.getTopicId()+"' of "+(readBuffer.getByteSize()/1024)+" KByte");
}
retrans = packetAllocator.newStruct(retransTemplate);
if ( dGramSize < retrans.getByteSize()+10 )
throw new RuntimeException("datagram size must not be smaller than "+retrans.getByteSize()+10 );
this.receivesFrom = receivesFrom;
isUnordered = topicEntry.isUnordered();
isUnreliable = topicEntry.isUnreliable();
maxDelayRetrans = topicEntry.getSubscriberConf().getMaxDelayRetransMS();
maxDelayNextRetrans = topicEntry.getSubscriberConf().getMaxDelayNextRetransMS();
}
public Topic getTopicEntry() {
return topicEntry;
}
DataPacket getPacket(long seqNo) {
return readBuffer.get((int) (seqNo%readBuffer.size()));
}
public long getMaxDelayNextRetrans() {
return maxDelayNextRetrans;
}
public void setMaxDelayNextRetrans(long maxDelayNextRetrans) {
this.maxDelayNextRetrans = maxDelayNextRetrans;
}
public long getMaxDelayRetrans() {
return maxDelayRetrans;
}
public void setMaxDelayRetrans(long maxDelayRetrans) {
this.maxDelayRetrans = maxDelayRetrans;
}
int retransCount = 0;
long firstGapDetected = 0;
long maxDelayNextRetrans = 15;
long maxDelayRetrans = 0;
boolean inInitialSync = true; // in case first packet is chained, stay in initial until complete msg is found
Topic topicEntry;
long startTime = 0;
public RetransPacket receivePacket(DataPacket packet) {
if (terminated)
return null;
updateHeartBeat(System.currentTimeMillis());
if ( maxOrderedSeq == 0 ) {
if ( startTime == 0 ) {
startTime = System.currentTimeMillis();
// return null;
} else {
// if ( System.currentTimeMillis()-startTime < 200 ) {
// System.out.println("initial dropping seq "+packet.getSeqNo()+" sender:"+packet.getSender()+" "+topicEntry.getConf().getName());
// return null;
// }
}
}
if ( isUnreliable ) {
receivePacketUnreliable(packet);
return null;
} else if ( isUnordered ) {
RetransPacket retransPacket = receivePacketUnOrdered(packet);
return retransPacket;
} else {
RetransPacket retransPacket = receivePacketOrdered(packet);
return retransPacket;
}
}
public void receivePacketUnreliable(DataPacket packet) {
long seqNo = packet.getSeqNo();
int index = (int) (seqNo % readBuffer.size());
highestSeq = Math.max(seqNo,highestSeq);
if ( maxOrderedSeq == 0 ) {
handleInitialSync(seqNo);
}
DataPacket previousPacket = getPacket(seqNo);
if ( ! previousPacket.isDecoded() && previousPacket.getSeqNo() > 0 ) { // not decoded yet => drop packet
return;
}
readBuffer.set(index,packet);
DataPacket toDecode = readBuffer.get(index);
decodePacket(toDecode);
}
private boolean isForeignPacket(DataPacket toDecode) {
StructString rec = toDecode.getReceiver();
return rec != null && rec.getLen() > 0 && ! nodeId.equals(rec);
}
public RetransPacket receivePacketUnOrdered(DataPacket packet) {
RetransPacket toReturn = null;
long seqNo = packet.getSeqNo();
int index = (int) (seqNo % readBuffer.size());
highestSeq = Math.max(seqNo,highestSeq);
long now = System.currentTimeMillis(); // FIXME: not always needed !
if ( seqNo != maxOrderedSeq+1 && firstGapDetected > 0 && now - firstGapDetected > maxDelayRetrans ) {
// generate retransmission requests
toReturn = computeRetransPacket(now);
}
if ( maxOrderedSeq == 0 ) {
handleInitialSync(seqNo);
}
DataPacket previousPacket = getPacket(seqNo);
if ( ! previousPacket.isDecoded() && previousPacket.getSeqNo() > 0 ) { // not decoded yet => drop packet
return toReturn;
}
if ( seqNo == maxOrderedSeq+1 ) {
// packet is next one
readBuffer.set(index,packet);
maxOrderedSeq = seqNo;
DataPacket toDecode = readBuffer.get(index);
decodePacket(toDecode);
// if a gap was filled => deliver continous packets
if ( ! inSync() ) { // there might be future packets in buffer
DataPacket pack = getPacket(seqNo + 1);
while ( pack.getSeqNo() == seqNo+1 ) {
// if unordered => check packages are not yet decoded
if ( ! pack.isDecoded() ) {
decodePacket(pack);
}
seqNo++;
maxOrderedSeq = seqNo;
pack = getPacket(seqNo + 1);
}
// System.out.println("done from loop highest:"+highestSeq);
highestSeq = Math.max(maxOrderedSeq,highestSeq);
if ( inSync() )
{
if ( PacketSendBuffer.RETRANSDEBUG )
FCLog.get().net("**************** in sync");
firstGapDetected = 0;
retransCount = 0;
return toReturn;
} else {
return toReturn;
}
} else {
return toReturn; // in sync
}
} else {
// gap,
if ( firstGapDetected == 0 ) {
firstGapDetected = now;
}
// if slot is free . deliver it
if ( readBuffer.get(index).isDecoded() ) {
readBuffer.set(index, packet);
decodePacket(readBuffer.get(index));
}
}
return toReturn;
}
long logBremse;
public RetransPacket receivePacketOrdered(DataPacket packet) {
if ( retransCount > 1 && PacketSendBuffer.RETRANSDEBUG ) {
long now = System.currentTimeMillis();
if ( now-logBremse > 1000 )
{
FCLog.get().warn("wait for retrans, received " + packet.getSeqNo() + " " + getTopicEntry().getSubscriberConf().getTopicId() + " waiting for " + (maxOrderedSeq + 1));
if ( packet.getSeqNo() < maxOrderedSeq ) {
FCLog.get().warn(" sent by " + packet.getSender());
}
logBremse = now;
}
}
RetransPacket toReturn = null;
long seqNo = packet.getSeqNo();
int index = (int) (seqNo % readBuffer.size());
highestSeq = Math.max(seqNo,highestSeq);
long now = System.currentTimeMillis(); // FIXME: not always needed !
// if packet has been received and stored => return. Old packet or queue is full
if ( seqNo <= maxOrderedSeq ) {
return null;
}
if ( maxOrderedSeq == 0 ) {
handleInitialSync(seqNo);
}
if ( seqNo == maxOrderedSeq+1 ) {
// packet is next packet
readBuffer.set(index,packet);
maxOrderedSeq = seqNo;
DataPacket toDecode = readBuffer.get(index);
decodePacket(toDecode);
retransCount = 0; // reset interval extension if any packet was received in sequence
// if a gap was filled => deliver continous packets
if ( ! inSync() ) { // there might be future packets in buffer
boolean onePack = true;
while( onePack ) {
onePack = false;
DataPacket pack = getPacket(seqNo + 1);
while ( pack.getSeqNo() == seqNo+1 ) {
// System.out.println("continue from buff "+(seqNo+1)+" "+pack.getSeqNo() );
decodePacket(pack);
seqNo++;
maxOrderedSeq = seqNo;
pack = getPacket(seqNo + 1);
onePack = true;
}
}
highestSeq = Math.max(maxOrderedSeq,highestSeq);
// System.out.println("highest "+highestSeq);
if ( inSync() )
{
if ( PacketSendBuffer.RETRANSDEBUG )
FCLog.get().net("**************** in sync");
// System.out.println("INSYNC");
firstGapDetected = 0;
return null;//toReturn;
} else {
if ( toReturn != null ) {
// System.out.println("returned retrans 1 "+toReturn);
}
return toReturn;
}
} else {
return null; // in sync
}
} else {
// gap detected
if ( firstGapDetected == 0 ) {
firstGapDetected = now;
toReturn = computeRetransPacket(now);
} else if ( firstGapDetected < now ) {
toReturn = computeRetransPacket(now);
}
}
// at this point it is sure packet is future packet
// store future packet
readBuffer.set(index, packet);
return toReturn;
}
private void handleInitialSync(long seqNo) {
maxOrderedSeq = seqNo-1; // ok, init only
inInitialSync = true;
FCLog.get().info("for sender "+receivesFrom+" bootstrap sequence "+getTopicEntry().getSubscriberConf().getTopicId()+" no "+seqNo);
final FCSubscriber subscriber = getTopicEntry().getSubscriber();
if ( subscriber != null ) {
subscriber.senderBootstrapped(receivesFrom,seqNo);
}
}
private RetransPacket computeRetransPacket(long now) {
RetransPacket toReturn = (RetransPacket) retrans.createCopy();
toReturn.clear();
long curSeq = maxOrderedSeq+1;
boolean anotherGapNearCurrentGap = false;
while( curSeq < highestSeq && ! toReturn.isFull() ) {
if ( getPacket(curSeq).getSeqNo() != curSeq ) {
if ( ! anotherGapNearCurrentGap )
toReturn.current().setFrom(curSeq);
curSeq++;
while( curSeq < highestSeq && ! toReturn.isFull() && getPacket(curSeq).getSeqNo() != curSeq ) {
curSeq++;
}
anotherGapNearCurrentGap = false;
// fixme: at least keep < maxOrdered
// for ( long off = curSeq; off < curSeq+MAX_NON_GAP_PACKET_SERIES_TO_JUSTIFY_NEW_RETRANS_ENTRY; off++ ) {
// if ( off < highestSeq && ! toReturn.isFull() && getPacket(off).getSeqNo() != off ) {
// anotherGapNearCurrentGap = true;
// curSeq = off;
// break;
// } else {
//
// }
// }
if ( ! anotherGapNearCurrentGap ) {
toReturn.current().setTo(curSeq);
toReturn.nextEntry();
}
} else {
curSeq++;
}
}
retransCount++;
long delay = maxDelayNextRetrans * (1+(retransCount/5));
if ( retransCount > 5 ) { // FIXME: give up at some point ?
FCLog.get().warn("retransmission retrial at " + maxOrderedSeq + " count " + retransCount + " highest " + highestSeq + " stream " + getTopicEntry().getSubscriberConf().getTopicId()+" retrans:"+toReturn+" delay:"+ delay);
}
firstGapDetected = delay + now;
return toReturn;
}
private boolean isUnordered() {
return isUnordered;
}
public boolean inSync() {
return highestSeq == maxOrderedSeq;
}
FSTStruct currentPacketBytePointer = new FSTStruct();
long debugPrevSeq = 0;
long lastPacket = 0;
FSTStruct tmpStruct = new FSTStruct();
DataPacket tmpPacket;
void decodePacket(DataPacket packet) {
// packet.dumpBytes();
if ( receiver == null )
return;
if ( isForeignPacket(packet) ) {
return;
} else
{
// System.out.println("received "+packet.getReceiver());
}
final long packetSeqNo = packet.getSeqNo();
if ( tmpPacket == null ) {
tmpPacket = packetAllocator.newPointer(DataPacket.class); // fixme: move to init
}
packet.dataPointer(tmpStruct);
final Bytez dataPacketBase = tmpStruct.getBase();
final int dataindex = (int) tmpStruct.getOffset();
final int packIndex = (int) packet.getOffset();
decodeMsgBytes(packetSeqNo, dataPacketBase, dataindex, packIndex);
}
private void decodeMsgBytes(long packetSeqNo, Bytez dataPacketBase, int dataindex, int packIndex) {
// check is valid only for non unicast traffic (else packets for other nodes will not be sent and create pseudo sequence gaps
// if (!isUnordered() && !isUnreliable() && debugPrevSeq != 0 && debugPrevSeq != packetSeqNo - 1) {
// FCLog.get().fatal("FATAL ERROR " + packetSeqNo);
// System.exit(1);
// }
debugPrevSeq = packetSeqNo;
currentPacketBytePointer.baseOn(dataPacketBase, dataindex);
while (true) {
short code = currentPacketBytePointer.getShort();
if (code > DataPacket.MAX_CODE || code < 0) {
FCLog.get().warn("foreign traffic or error, maxOrdered " + maxOrderedSeq + " packseq " + packetSeqNo + " highest " + highestSeq);
System.exit(1);
}
currentPacketBytePointer.next(2);
if (code == DataPacket.EOP) {
if (isUnordered()||isUnreliable()) {
tmpPacket.baseOn(dataPacketBase, packIndex);
tmpPacket.setDecoded(true);
}
return;
} else {
short len = currentPacketBytePointer.getShort();
currentPacketBytePointer.next(2);
if (inInitialSync) {
if (code == DataPacket.COMPLETE) {
inInitialSync = false;
}
} else {
decoder.receiveChunk(packetSeqNo, currentPacketBytePointer.getBase(), (int) currentPacketBytePointer.getOffset(), len, code == DataPacket.COMPLETE);
}
currentPacketBytePointer.next(len);
}
}
}
public void setUnreliable(boolean unreliable) {
isUnreliable = unreliable;
}
public boolean isUnreliable() {
return isUnreliable;
}
public void terminate() {
terminated = true;
// avoid late packets to crash system
new Thread("freedom") {
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
freeImmediate();
}
}.start();
}
private void freeImmediate() {
long alloced = MallocBytezAllocator.alloced.get();
packetAllocator.free();
long curr = MallocBytezAllocator.alloced.get();
FCLog.log("freed " + (alloced - curr) / 1024 / 1024 + "MB to " + curr / 1024 / 1024 + " MB");
}
/**
* reset all sequences and resync (creates unrecoverable message loss)
*/
public void resync() {
maxOrderedSeq = 0;
startTime = 0;
retransCount = 0;
firstGapDetected = 0;
debugPrevSeq = 0;
inInitialSync = true; // in case first packet is chained, stay in initial until complete msg is found
}
public void updateHeartBeat(long l) {
lastHBMillis = l;
}
public long getLastHBMillis() {
return lastHBMillis;
}
public String getReceivesFrom() {
return receivesFrom;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy