/** * Copyright 2016 Yahoo Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.yahoo.pulsar.common.api; import java.net.SocketAddress; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.yahoo.pulsar.common.api.proto.PulsarApi.CommandPing; import com.yahoo.pulsar.common.api.proto.PulsarApi.CommandPong; import com.yahoo.pulsar.common.api.proto.PulsarApi.ProtocolVersion; import io.netty.channel.ChannelHandlerContext; import io.netty.util.concurrent.ScheduledFuture; public abstract class PulsarHandler extends PulsarDecoder { protected ChannelHandlerContext ctx; protected SocketAddress remoteAddress; protected int remoteEndpointProtocolVersion = ProtocolVersion.v0.getNumber(); private final long keepAliveIntervalSeconds; private boolean waitingForPingResponse = false; private ScheduledFuture<?> keepAliveTask; public int getRemoteEndpointProtocolVersion() { return remoteEndpointProtocolVersion; } public PulsarHandler(int keepAliveInterval, TimeUnit unit) { this.keepAliveIntervalSeconds = unit.toSeconds(keepAliveInterval); } @Override final protected void messageReceived() { waitingForPingResponse = false; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { this.remoteAddress = ctx.channel().remoteAddress(); this.ctx = ctx; if (log.isDebugEnabled()) { log.debug("[{}] Scheduling keep-alive task every {} s", ctx.channel(), keepAliveIntervalSeconds); } if (keepAliveIntervalSeconds > 0) { this.keepAliveTask = ctx.executor().scheduleAtFixedRate(this::handleKeepAliveTimeout, keepAliveIntervalSeconds, keepAliveIntervalSeconds, TimeUnit.SECONDS); } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (keepAliveTask != null) { keepAliveTask.cancel(false); } } @Override final protected void handlePing(CommandPing ping) { // Immediately reply success to ping requests if (log.isDebugEnabled()) { log.debug("[{}] Replying back to ping message", ctx.channel()); } ctx.writeAndFlush(Commands.newPong()); } @Override final protected void handlePong(CommandPong pong) { } private void handleKeepAliveTimeout() { if (!ctx.channel().isOpen()) { return; } if (!isHandshakeCompleted()) { log.warn("[{}] Pulsar Handshake was not completed within timeout, closing connection", ctx.channel()); ctx.close(); } else if (waitingForPingResponse && ctx.channel().config().isAutoRead()) { // We were waiting for a response and another keep-alive just completed. // If auto-read was disabled, it means we stopped reading from the connection, so we might receive the Ping // response later and thus not enforce the strict timeout here. log.warn("[{}] Forcing connection to close after keep-alive timeout", ctx.channel()); ctx.close(); } else if (remoteEndpointProtocolVersion >= ProtocolVersion.v1.getNumber()) { // Send keep alive probe to peer only if it supports the ping/pong commands, added in v1 if (log.isDebugEnabled()) { log.debug("[{}] Sending ping message", ctx.channel()); } waitingForPingResponse = true; ctx.writeAndFlush(Commands.newPing()); } else { if (log.isDebugEnabled()) { log.debug("[{}] Peer doesn't support keep-alive", ctx.channel()); } } } /** * @return true if the connection is ready to use, meaning the Pulsar handshake was already completed */ protected abstract boolean isHandshakeCompleted(); private static final Logger log = LoggerFactory.getLogger(PulsarHandler.class); }