All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nustaq.fastcast.impl.TransportDriver Maven / Gradle / Ivy

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.FCPublisher;
import org.nustaq.fastcast.api.FastCast;
import org.nustaq.fastcast.config.PhysicalTransportConf;
import org.nustaq.fastcast.config.PublisherConf;
import org.nustaq.fastcast.config.SubscriberConf;
import org.nustaq.fastcast.api.FCSubscriber;
import org.nustaq.fastcast.transport.PhysicalTransport;
import org.nustaq.fastcast.util.FCLog;
import org.nustaq.offheap.structs.FSTStructAllocator;
import org.nustaq.offheap.structs.structtypes.StructString;

import java.io.IOException;
import java.net.DatagramPacket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * Created with IntelliJ IDEA.
 * User: moelrue
 * Date: 8/13/13
 * Time: 11:40 AM
 * To change this template use File | Settings | File Templates.
 */
public class TransportDriver {

    private static final boolean RETRANS_DEBUG = false;
    public static int MAX_NUM_TOPICS = 256;

    int spinIdleLoopMicros = 1000*1000*10;
    int idleParkMicros = 500;

    volatile PhysicalTransport trans;

    ReceiveBufferDispatcher receiver[];
    PacketSendBuffer sender[];
    long lastMsg[]; // marks last flush on a topic to enable batch delay

    StructString nodeId;

    Thread receiverThread, houseKeeping;
    FSTStructAllocator alloc = new FSTStructAllocator(1);
    long autoFlushMS;
    private ConcurrentHashMap topics = new ConcurrentHashMap<>();

    int tCheckCounter = 0;
    volatile int terminationCounter = 0;

    public TransportDriver(PhysicalTransport trans, String nodeId) {
        this.trans = trans;
        this.nodeId = alloc.newStruct( new StructString(nodeId) );
        final PhysicalTransportConf tconf = trans.getConf();
        this.autoFlushMS = tconf.getAutoFlushMS();
        this.spinIdleLoopMicros = tconf.getSpinLoopMicros();
        this.idleParkMicros = tconf.getIdleParkMicros();

        receiver = new ReceiveBufferDispatcher[MAX_NUM_TOPICS];
        sender = new PacketSendBuffer[MAX_NUM_TOPICS];
        lastMsg = new long[MAX_NUM_TOPICS];

        receiverThread = new Thread("trans receiver "+ tconf.getName()) {
            public void run() {
                receiveLoop();
            }
        };
        receiverThread.start();
        houseKeeping = new Thread("trans houseKeeping "+ tconf.getName()) {
            public void run() {
                houseKeepingLoop();
            }
        };
        houseKeeping.start();
    }

    private void installReceiver(Topic chan, FCSubscriber msgListener) {
        ReceiveBufferDispatcher receiveBufferDispatcher = new ReceiveBufferDispatcher(trans.getConf().getDgramsize(), nodeId.toString(), chan, msgListener);
        if ( receiver[chan.getTopicId()] != null ) {
            throw new RuntimeException("double usage of topic "+chan.getTopicId()+" on transport "+trans.getConf().getName() );
        }
        receiver[chan.getTopicId()] = receiveBufferDispatcher;
    }

    public boolean hasReceiver(int topicId) {
        return receiver[topicId] != null;
    }

    public boolean hasSender(int topicId) {
        return sender[topicId] != null;
    }

    /**
     * installs and initializes sender thread and buffer, sets is to topicEntry given in argument !!
     * @param topicEntry
     */
    private PacketSendBuffer installSender(final Topic topicEntry) {
        if ( sender[topicEntry.getTopicId()] != null ) {
            return sender[topicEntry.getTopicId()];
        }
        PacketSendBuffer packetSendBuffer = new PacketSendBuffer(trans, nodeId.toString(), topicEntry);
        sender[topicEntry.getTopicId()] = packetSendBuffer;
        topicEntry.setSender(packetSendBuffer);
        return packetSendBuffer;
    }

    long lastTimeoutCheck = System.currentTimeMillis();
    private void houseKeepingLoop() {
        ArrayList lostSenders = new ArrayList<>();
        while (!isTerminated()) {
            try {
                long now = System.currentTimeMillis();
                if ( now - lastTimeoutCheck > 2000 ) { // timouts < 2 seconds not supported (and do not make sense ..)
                    lastTimeoutCheck = now;
                    for (int i = 0; i < receiver.length; i++) {
                        ReceiveBufferDispatcher receiveBufferDispatcher = receiver[i];
                        if (receiveBufferDispatcher != null) {
                            Topic topicEntry = receiveBufferDispatcher.getTopicEntry();
                            lostSenders.clear();
                            List timedOutSenders = topicEntry.getTimedOutSenders(lostSenders, now, topicEntry.getHbTimeoutMS());
                            if (timedOutSenders != null && timedOutSenders.size() > 0) {
                                if ( ! isTerminated() )
                                    cleanup(timedOutSenders, i);
                            }
                        }
                    }
                }
                for (int i = 0; i < sender.length; i++) {
                    PacketSendBuffer packetSendBuffer = sender[i];
                    if ( packetSendBuffer != null ) {
                        long lastFlush = packetSendBuffer.lastMsgFlush;
                        if ( lastMsg[i] == 0 )
                            lastMsg[i] = lastFlush;
                        else if ( lastMsg[i] == lastFlush) {
                            // no flush since last turnaround, generate a flush
                            if ( ! isTerminated() )
                                packetSendBuffer.flush();
                        } else {
                            lastMsg[i] = lastFlush;
                        }
                    }
                }
                try {
                    Thread.sleep(autoFlushMS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        terminationCounter++;
    }

    private boolean isTerminated() {
        return trans == emptyTransport;
    }

    Packet receivedPacket;
    private void receiveLoop()
    {
        byte[] receiveBuf = new byte[trans.getConf().getDgramsize()];
        DatagramPacket p = new DatagramPacket(receiveBuf,receiveBuf.length);
        ByteBuffer buff = ByteBuffer.wrap(p.getData(), p.getOffset(), p.getLength());
        receivedPacket = alloc.newStruct(new Packet());
        long idleNanos = System.nanoTime();
        receivedPacket.baseOn(receiveBuf, 0);
        while(true) {
            boolean idle = true;
            try {
                buff.position(0);
                if (receiveDatagram(buff, receiveBuf)) {
                    idleNanos = System.nanoTime();
                    idle = false;
                } else {
                    if ( System.nanoTime()-idleNanos > spinIdleLoopMicros*1000) {
                        if ( ! trans.isBlocking() && idleParkMicros > 0)
                            LockSupport.parkNanos(1000*idleParkMicros);
                    } else {
                        idle = false;
                        if ( (ThreadLocalRandom.current().nextInt()&1) == 0 ) {
                            idleNanos++;
                        } else {
                            idleNanos--;
                        }
                    }
                }
                tCheckCounter++;
                if ( tCheckCounter == 100000 || idle || spinIdleLoopMicros == 0 ) {
                    tCheckCounter = 0;
                    if ( isTerminated() ) {
                        break;
                    }
                }
            } catch (Throwable e) {
                FCLog.log(e);
            }
        }
        while( terminationCounter < 1 ) { // wait for housekeeping to finish
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(500));
        }
        alloc.free();
        for (int i = 0; i < receiver.length; i++) {
            ReceiveBufferDispatcher receiveBufferDispatcher = receiver[i];
            if ( receiveBufferDispatcher != null )
                receiveBufferDispatcher.cleanupTopic();
        }
    }

    public void terminate() {
        PhysicalTransport oldTrans = trans;
        trans = emptyTransport;
        oldTrans.close();
    }

    private boolean receiveDatagram(ByteBuffer p, byte wrappedArr[]) throws IOException {
        if ( trans.receive(p) ) {

            boolean selfSent = receivedPacket.getSender().equals(nodeId);
            if ( ! selfSent) {
                // debug
                if ( RETRANS_DEBUG ) {
                    Class debugtype = receivedPacket.getPointedClass();
                    if (debugtype == RetransPacket.class) {
                        RetransPacket retransPacket = receivedPacket.cast().detach();
                        System.out.println("retrans received " + retransPacket);
                    }
                }
                //

                int topic = receivedPacket.getTopic();

                if ( topic > MAX_NUM_TOPICS || topic < 0 ) {
                    FCLog.get().warn("foreign traffic");
                    return true;
                }
                if ( receiver[topic] == null && sender[topic] == null) {
                    return true;
                }

                Class type = receivedPacket.getPointedClass();
                StructString receivedPacketReceiver = receivedPacket.getReceiver();
                if ( type == DataPacket.class )
                {
                    if ( receiver[topic] == null )
                        return true;
                    dispatchDataPacket(receivedPacket, topic);
                } else if ( type == RetransPacket.class )
                {
                    if ( sender[topic] == null )
                        return true;
                    if (receivedPacketReceiver == null || ! receivedPacketReceiver.equals(nodeId)) {
                        return true;
                    }
                    if (RETRANS_DEBUG)
                        System.out.println("retrans dispatched ");
                    dispatchRetransmissionRequest(receivedPacket, topic);
                } else if (type == ControlPacket.class )
                {
                    if (isForeignReceiver(receivedPacketReceiver)) {
                        return true;
                    }
                    ControlPacket control = receivedPacket.cast();
                    if ( control.getType() == ControlPacket.DROPPED )
                    {
                        ReceiveBufferDispatcher receiveBufferDispatcher = receiver[topic];
                        if ( receiveBufferDispatcher != null ) {
                            FCLog.get().warn(nodeId+" has been dropped by "+receivedPacket.getSender()+" on service "+receiveBufferDispatcher.getTopicEntry().getTopicId());
                            FCSubscriber service = receiveBufferDispatcher.getTopicEntry().getSubscriber();
                            if ( service != null ) {
                                if ( service.dropped() ) { // retry if returns true
                                    FCLog.get().warn("..resyncing..");
                                    PacketReceiveBuffer buffer = receiveBufferDispatcher.getBuffer(receivedPacket.getSender());
                                    if ( buffer != null ) {
                                        buffer.resync();
                                    } else {
                                        FCLog.get().warn("unexpected null buffer");
                                    }
                                } else {
                                    // topic is lost forever now ..
                                    receiver[topic] = null;
                                    receiveBufferDispatcher.cleanupTopic();
                                }
                            }
                        }
                    }
                    // heartbeats are sent as regular data packets in order to have initialSync to happen correctly
//                    else if ( control.getType() == ControlPacket.HEARTBEAT ) {
//                        ReceiveBufferDispatcher receiveBufferDispatcher = receiver[topic];
//                        if ( receiveBufferDispatcher != null ) {
//                            PacketReceiveBuffer buffer = receiveBufferDispatcher.getBuffer(control.getSender());
//                            if ( buffer != null ) {
//                                buffer.updateHeartBeat(control.getSeqNo(),System.currentTimeMillis());
//                            }
//                        }
//                    }
                }
                return true;
            } else {
                return false;
            }
        }
        return false;
    }

    private boolean isForeignReceiver(StructString receivedPacketReceiver) {
        return receivedPacketReceiver != null && receivedPacketReceiver.getLen() > 0 && ! receivedPacketReceiver.equals(nodeId);
    }

    DataPacket tmpP;
    private void dispatchDataPacket(Packet receivedPacket, int topic) throws IOException {
        PacketReceiveBuffer buffer = receiver[topic].getBuffer(receivedPacket.getSender());
        tmpP = receivedPacket.cast().detachTo(tmpP); // avoid alloc
        RetransPacket retransRequest = buffer.receivePacket(tmpP);
        if ( retransRequest != null ) {
            // packet is valid just in this thread
            if ( PacketSendBuffer.RETRANSDEBUG )
                FCLog.get().info("send retrans request " + retransRequest + " " + retransRequest.getClzId());
            // FIXME: ALLOC
            trans.sendControl(retransRequest.getBase().toBytes(retransRequest.getOffset(), retransRequest.getByteSize()), 0, retransRequest.getByteSize());
        }
    }

    private void dispatchRetransmissionRequest(Packet receivedPacket, int topic) throws IOException {
        RetransPacket retransPacket = (RetransPacket) receivedPacket.cast().detach();
        sender[topic].addRetransmissionRequest(retransPacket, trans);
    }

    void cleanup(List timedOutSenders, int topic) {
        for (int i = 0; i < timedOutSenders.size(); i++) {
            String s = timedOutSenders.get(i);
            ReceiveBufferDispatcher receiveBufferDispatcher = receiver[topic];
//            receiver[topic] = null; wrong: disallows reconnect
            FCLog.get().info("stopped receiving heartbeats from "+s);
            if ( receiveBufferDispatcher != null ) {
                receiveBufferDispatcher.cleanup(s);
            }
        }
    }

    public void subscribe( String subsConf, FCSubscriber subscriber ) {
        subscribe(FastCast.getFastCast().getSubscriberConf(subsConf),subscriber);
    }

    public void subscribe( SubscriberConf subsConf, FCSubscriber subscriber ) {
        Topic topicEntry = topics.get(subsConf.getTopicId());
        if ( topicEntry == null )
            topicEntry = new Topic(null,null);
        if ( topicEntry.getSubscriberConf() != null ) {
            throw new RuntimeException("already a sender registered at "+subsConf.getTopicId());
        }
        topicEntry.setSubscriberConf(subsConf);
        topicEntry.setChannelDispatcher(this);
        topicEntry.setSubscriber(subscriber);
        installReceiver(topicEntry, subscriber);
    }

    public FCPublisher publish( String pubConf ) {
        return publish(FastCast.getFastCast().getPublisherConf(pubConf));
    }

    public FCPublisher publish( PublisherConf pubConf ) {
        Topic topicEntry = topics.get(pubConf.getTopicId());
        if ( topicEntry == null )
            topicEntry = new Topic(null,null);
        if ( topicEntry.getPublisherConf() != null ) {
            throw new RuntimeException("already a sender registered at "+pubConf.getTopicId());
        }
        topicEntry.setChannelDispatcher(this);
        topicEntry.setPublisherConf(pubConf);
        topics.put(pubConf.getTopicId(), topicEntry);
        final PacketSendBuffer packetSendBuffer = installSender(topicEntry);
        return packetSendBuffer;
    }

    public ReceiveBufferDispatcher getReceiver(int topicId) {
        return receiver[topicId];
    }

    PhysicalTransport emptyTransport = new PhysicalTransport() {
        @Override
        public boolean receive(ByteBuffer pack) throws IOException {
            return false;
        }

        @Override
        public void sendControl(byte[] bytes, int off, int len) throws IOException {

        }

        @Override
        public void sendControl(ByteBuffer b) throws IOException {

        }

        @Override
        public void send(byte[] bytes, int off, int len) throws IOException {

        }


        @Override
        public void send(ByteBuffer b) throws IOException {

        }

        @Override
        public void join() throws IOException {

        }

        @Override
        public PhysicalTransportConf getConf() {
            return null;
        }

        @Override
        public void close() {

        }

        @Override
        public boolean isBlocking() {
            return false;
        }
    };

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy