package org.corfudb.infrastructure; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.corfudb.protocols.wireprotocol.CorfuMsg; import org.corfudb.protocols.wireprotocol.CorfuMsgType; import org.corfudb.protocols.wireprotocol.CorfuPayloadMsg; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * The netty server router routes incoming messages to registered roles using * the * Created by mwei on 12/1/15. */ @Slf4j @ChannelHandler.Sharable public class NettyServerRouter extends ChannelInboundHandlerAdapter implements IServerRouter { public static final String PREFIX_EPOCH = "SERVER_EPOCH"; public static final String KEY_EPOCH = "CURRENT"; ExecutorService handlerWorkers = Executors.newWorkStealingPool(Runtime.getRuntime().availableProcessors()); /** * This map stores the mapping from message type to netty server handler. */ Map<CorfuMsgType, AbstractServer> handlerMap; BaseServer baseServer; /** * The epoch of this router. This is managed by the base server implementation. */ @Getter @Setter long serverEpoch; public NettyServerRouter(Map<String, Object> opts) { handlerMap = new ConcurrentHashMap<>(); baseServer = new BaseServer(); addServer(baseServer); } /** * Add a new netty server handler to the router. * * @param server The server to add. */ public void addServer(AbstractServer server) { // Iterate through all types of CorfuMsgType, registering the handler server.getHandler().getHandledTypes() .forEach(x -> { handlerMap.put(x, server); log.trace("Registered {} to handle messages of type {}", server, x); }); } public void removeServer(AbstractServer server) { // Iterate through all types of CorfuMsgType, un-registering the handler server.getHandler().getHandledTypes() .forEach(x -> { handlerMap.remove(x, server); log.trace("Un-Registered {} to handle messages of type {}", server, x); }); } /** * Send a netty message through this router, setting the fields in the outgoing message. * * @param ctx Channel handler context to use. * @param inMsg Incoming message to respond to. * @param outMsg Outgoing message. */ public void sendResponse(ChannelHandlerContext ctx, CorfuMsg inMsg, CorfuMsg outMsg) { outMsg.copyBaseFields(inMsg); ctx.writeAndFlush(outMsg); log.trace("Sent response: {}", outMsg); } /** * Validate the epoch of a CorfuMsg, and send a WRONG_EPOCH response if * the server is in the wrong epoch. Ignored if the message type is reset (which * is valid in any epoch). * * @param msg The incoming message to validate. * @param ctx The context of the channel handler. * @return True, if the epoch is correct, but false otherwise. */ public boolean validateEpoch(CorfuMsg msg, ChannelHandlerContext ctx) { long serverEpoch = getServerEpoch(); if (!msg.getMsgType().ignoreEpoch && msg.getEpoch() != serverEpoch) { sendResponse(ctx, msg, new CorfuPayloadMsg<>(CorfuMsgType.WRONG_EPOCH, serverEpoch)); log.trace("Incoming message with wrong epoch, got {}, expected {}, message was: {}", msg.getEpoch(), serverEpoch, msg); return false; } return true; } /** * Handle an incoming message read on the channel. * * @param ctx Channel handler context * @param msg The incoming message on that channel. */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { try { // The incoming message should have been transformed to a CorfuMsg earlier in the pipeline. CorfuMsg m = ((CorfuMsg) msg); // We get the handler for this message from the map AbstractServer handler = handlerMap.get(m.getMsgType()); if (handler == null) { // The message was unregistered, we are dropping it. log.warn("Received unregistered message {}, dropping", m); } else { if (validateEpoch(m, ctx)) { // Route the message to the handler. log.trace("Message routed to {}: {}", handler.getClass().getSimpleName(), msg); handlerWorkers.submit(() -> handler.handleMessage(m, ctx, this)); } } } catch (Exception e) { log.error("Exception during read!", e); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.error("Error in handling inbound message, {}", cause); ctx.close(); } }