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

org.yamcs.replication.MasterChannelHandler Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs.replication;

import static org.yamcs.replication.ReplicationServer.workerGroup;

import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;

import org.yamcs.logging.Log;
import org.yamcs.replication.ReplicationMaster.SlaveServer;
import org.yamcs.replication.protobuf.Request;
import org.yamcs.replication.protobuf.Response;
import org.yamcs.replication.protobuf.TimeMessage;
import org.yamcs.replication.protobuf.Wakeup;
import org.yamcs.time.TimeService;
import org.yamcs.utils.DecodingException;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.concurrent.ScheduledFuture;

/**
 * 
 * runs on the master side sending data to slave
 *
 */
public class MasterChannelHandler extends ChannelInboundHandlerAdapter {
    final ReplicationMaster replMaster;
    final TimeService timeService;
    final Log log;

    Request req;
    private ChannelHandlerContext channelHandlerContext;
    ChannelFuture dataHandlingFuture;
    ReplicationFile currentFile;
    long nextTxToSend;
    ReplicationTail fileTail;
    SlaveServer slaveServer;
    private ScheduledFuture timeMsgFuture;


    // called when we are TCP client
    public MasterChannelHandler(TimeService timeService, ReplicationMaster master, SlaveServer slaveServer) {
        this.replMaster = master;
        this.slaveServer = slaveServer;
        this.req = null;
        this.timeService = timeService;
        log = new Log(MasterChannelHandler.class, master.getYamcsInstance());
    }

    // called when we are connected to a TCP server
    public MasterChannelHandler(TimeService timeService, ReplicationMaster master, Request req) {
        this.replMaster = master;
        this.req = req;
        this.timeService = timeService;
        log = new Log(MasterChannelHandler.class, master.getYamcsInstance());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object o) {
        ByteBuf nettyBuf = (ByteBuf) o;
        try {
            doChannelRead(ctx, nettyBuf);
        } finally {
            nettyBuf.release();
        }
    }

    private void doChannelRead(ChannelHandlerContext ctx, ByteBuf nettyBuf) {
        ByteBuffer buf = nettyBuf.nioBuffer();
        Message msg;
        try {
            msg = Message.decode(buf);
        } catch (DecodingException e) {
            log.warn("Failed to decode message", e);
            ctx.close();
            return;
        }

        if (msg.type == Message.REQUEST) {
            this.req = (Request) msg.protoMsg;
            processRequest();
        } else if (msg.type == Message.RESPONSE) {
            Response resp = (Response) msg.protoMsg;
            if (resp.getResult() != 0) {
                log.warn("Received negative response: {}, closing the connection", resp.getErrorMsg());
                ctx.close();
                return;
            } else {
                log.info("Received response {}", resp);
            }
        } else {
            log.warn("Unexpected message type {} received, closing the connection", msg.type);
            ctx.close();
        }
    }

    // if tcpRole=Server, called when we have been added to the pipeline by the ReplicationServer
    // if tcpRole=Client, called when we have been added to the pipeline by the ReplicationClient
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        this.channelHandlerContext = ctx;
        if (req != null) {// this is the request in the constructor, if tcpRole=Server
            processRequest();
        }
    }

    public void shutdown() {
        channelHandlerContext.close();
        if (timeMsgFuture != null) {
            timeMsgFuture.cancel(true);
        }
    }

    private void processRequest() {
        if (dataHandlingFuture != null) {
            dataHandlingFuture.cancel(true);
        }
        if (req.hasStartTxId()) {
            nextTxToSend = req.getStartTxId();
        } else {
            log.info("The slave did not provide a startTxId, starting from 0");
            nextTxToSend = 0;
        }
        scheduleTimeMsgs();
        goToNextFile();
    }

    private void scheduleTimeMsgs() {
        long timeMsgFreqMillis = replMaster.timeMsgFreqMillis;
        this.timeMsgFuture = channelHandlerContext.executor().scheduleAtFixedRate(this::sendTimeMsg,
                0, timeMsgFreqMillis, TimeUnit.MILLISECONDS);
    }

    void goToNextFile() {
        log.trace("Looking for a new file for transaction {}", nextTxToSend);
        currentFile = replMaster.getFile(nextTxToSend);
        if (currentFile == null) {
            log.warn("next TX to send {} is in the future, checking back in 60 seconds", nextTxToSend);
            workerGroup.schedule(() -> goToNextFile(), 60, TimeUnit.SECONDS);
            return;
        }
        log.trace("Found file with firstTxId={} nextTxId={}", currentFile.getFirstId(), currentFile.getNextTxId());
        if (nextTxToSend < currentFile.getFirstId()) {
            log.warn("Requested start from {} but the first available transaction is {}. Replaying from there",
                    nextTxToSend, currentFile.getFirstId());
            nextTxToSend = currentFile.getFirstId();
        } else if (nextTxToSend > currentFile.getFirstId()) {
            // start from the middle of the file, write first the metadata
            Iterator it = currentFile.metadataIterator();
            while (it.hasNext()) {
                ByteBuffer buf = it.next();
                long txId = buf.getLong(buf.position() + 8);
                if (txId >= nextTxToSend) {
                    break;
                }
                log.debug("Sending metadata TX{} length: {} ", txId, buf.remaining());
                ByteBuf bb = Unpooled.wrappedBuffer(buf);
                channelHandlerContext.writeAndFlush(bb);
            }
        }
        fileTail = null;
        sendMoreData();
    }

    void sendMoreData() {
        if (!channelHandlerContext.channel().isActive()) {
            return;
        }
        if (fileTail == null) {
            fileTail = currentFile.tail(nextTxToSend);
        } else {
            currentFile.getNewData(fileTail);
        }

        log.trace("nextTxToSend: {}, FileTail: {} ", nextTxToSend, fileTail);
        if (fileTail.nextTxId == nextTxToSend) {// no more data available
            if (fileTail.eof) { // file, full, go to next file
                goToNextFile();
            } else { // check back in 200 millisec
                workerGroup.schedule(() -> sendMoreData(), 200, TimeUnit.MILLISECONDS);
            }
        } else {// got some data, send it and check back for more once the data has been sent
            ByteBuf buf = Unpooled.wrappedBuffer(fileTail.buf);
            dataHandlingFuture = channelHandlerContext.writeAndFlush(buf).addListener(a -> {
                fileTail.buf.position(fileTail.buf.limit());
                nextTxToSend = fileTail.nextTxId;
                sendMoreData();
            });
        }
    }

    public long getNextTxId() {
        return nextTxToSend;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.warn("Caught exception {}", cause.getMessage());
    }

    private void sendTimeMsg() {
        TimeMessage tm = TimeMessage.newBuilder()
                .setLocalTime(System.currentTimeMillis())
                .setMissionTime(timeService.getMissionTime())
                .setSpeed(timeService.getSpeed())
                .build();
        Message msg = Message.get(tm);
        ByteBuf bb = Unpooled.wrappedBuffer(msg.encode());
        channelHandlerContext.writeAndFlush(bb);
    }

    /**
     * this is called when the TCP connection is established, only when we are working as TCP client in the other case
     * the ReplicationServer adds us to the pipeline after the connection is established)
     * 

* Send a Wakeup message */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); log.debug("Connection {} opened, sending a wakeup message", ctx.channel().remoteAddress()); Wakeup wp = Wakeup.newBuilder().setYamcsInstance(slaveServer.instance).build(); Message msg = Message.get(wp); ctx.writeAndFlush(Unpooled.wrappedBuffer(msg.encode())); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { log.info("Replication connection {} closed", ctx.channel().remoteAddress()); super.channelInactive(ctx); if (dataHandlingFuture != null) { dataHandlingFuture.cancel(true); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy