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

tech.ytsaurus.client.bus.BusProtocolHandler Maven / Gradle / Ivy

The newest version!
package tech.ytsaurus.client.bus;

import java.nio.channels.ClosedChannelException;
import java.util.ArrayDeque;
import java.util.Deque;

import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tech.ytsaurus.core.GUID;

class BusProtocolHandler extends ChannelDuplexHandler {
    private static final Logger logger = LoggerFactory.getLogger(BusProtocolHandler.class);

    private final Bus bus;
    private final BusListenerWrapper wrappedListener;
    private final Deque deliveryQueue = new ArrayDeque<>();

    private static class DeliveryEntry {
        private final GUID packetId;
        private final ChannelPromise promise;

        DeliveryEntry(GUID packetId, ChannelPromise promise) {
            this.packetId = packetId;
            this.promise = promise;
        }

        public GUID getPacketId() {
            return packetId;
        }

        public ChannelPromise getPromise() {
            return promise;
        }
    }

    BusProtocolHandler(Bus bus, BusListener listener) {
        this.bus = bus;
        this.wrappedListener = new BusListenerWrapper(listener);
    }

    private void abortDelivery(Throwable cause) {
        DeliveryEntry entry;
        while ((entry = deliveryQueue.pollFirst()) != null) {
            entry.getPromise().tryFailure(cause);
        }
    }

    /**
     * Возвращает bus, ассоциированный с данным обработчиком
     */
    public Bus getBus() {
        return bus;
    }

    /**
     * Возвращает listener, ассоциированный с данным обработчиком
     */
    public BusListener getListener() {
        return wrappedListener.getListener();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.debug("Unhandled exception", cause);
        try {
            try {
                try {
                    abortDelivery(cause);
                } finally {
                    wrappedListener.onException(bus, cause);
                }
            } finally {
                if (bus instanceof BusLifecycle) {
                    ((BusLifecycle) bus).channelFailed(cause);
                }
            }
        } finally {
            ctx.close();
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        if (bus instanceof BusLifecycle) {
            ((BusLifecycle) bus).channelConnected();
        }
        wrappedListener.onConnect(bus);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        try {
            try {
                abortDelivery(new ClosedChannelException());
            } finally {
                wrappedListener.onDisconnect(bus);
            }
        } finally {
            if (bus instanceof BusLifecycle) {
                ((BusLifecycle) bus).channelDisconnected();
            }
        }
        super.channelInactive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof BusPacket) {
            BusPacket packet = (BusPacket) msg;
            switch (packet.getType()) {
                case ACK:
                    DeliveryEntry entry = deliveryQueue.peekFirst();
                    if (entry == null) {
                        ctx.close();
                        throw new IllegalStateException(
                                "Received unexpected ack for packet " + packet.getPacketId());
                    }
                    if (!entry.getPacketId().equals(packet.getPacketId())) {
                        ctx.close();
                        throw new IllegalStateException("Received unexpected ack for packet " + packet.getPacketId()
                                + " while waiting for packet " + entry.getPacketId());
                    }
                    deliveryQueue.removeFirst();
                    entry.getPromise().trySuccess();
                    break;
                case MESSAGE:
                    if (packet.hasFlags(BusPacketFlags.REQUEST_ACK)) {
                        ctx.writeAndFlush(
                                new BusPacket(BusPacketType.ACK, BusPacketFlags.NONE, packet.getPacketId()));
                    }
                    wrappedListener.onMessage(bus, packet.getMessage());
                    break;
                default:
                    ctx.close();
                    throw new IllegalStateException("Unexpected packet received: " + packet.getType());
            }
        } else {
            super.channelRead(ctx, msg);
        }
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        if (msg instanceof BusOutgoingMessage) {
            BusOutgoingMessage outgoingMessage = (BusOutgoingMessage) msg;
            if (promise.isDone()) {
                // Если на promise вызывали cancel, то даже не пытаемся отсылать пакет
                return;
            }
            if (!ctx.channel().isActive()) {
                // Если канал сейчас не активен, то даже не пытаемся отсылать пакет
                promise.tryFailure(new ClosedChannelException());
                return;
            }
            GUID packetId = outgoingMessage.getPacketId();
            short flags = BusPacketFlags.NONE;
            BusDeliveryTracking level = outgoingMessage.getLevel();
            ChannelPromise writePromise = promise;
            switch (level) {
                case NONE:
                    // Создаём новый promise на запись
                    writePromise = ctx.newPromise();
                    break;
                case FULL:
                    // Добавляем пакет в очередь на подтверждение
                    writePromise = ctx.newPromise();
                    flags |= BusPacketFlags.REQUEST_ACK;
                    deliveryQueue.add(new DeliveryEntry(packetId, promise));
                    break;
                default:
                    break;
            }
            // Формируем пакет с сообщением и пишем его в контекст
            BusPacket packet = new BusPacket(BusPacketType.MESSAGE, flags, packetId, outgoingMessage.getMessage());
            ctx.write(packet, writePromise);
            if (level == BusDeliveryTracking.NONE) {
                // На уровне NONE завершаем promise не дожидаясь записи
                promise.trySuccess();
            }
        } else {
            super.write(ctx, msg, promise);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy