/**
* Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com)
*
* 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.linkedin.pinot.transport.netty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.linkedin.pinot.common.response.ServerInstance;
import com.linkedin.pinot.transport.common.AsyncResponseFuture;
import com.linkedin.pinot.transport.common.Callback;
import com.linkedin.pinot.transport.common.NoneType;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.util.Timer;
/**
* A Netty standalone connection. This will be managed as a resource in a pool to reuse
* connection. This is not thread-safe so multiple requests cannot be sent simultaneously.
* This class provides an async API to send requests and wait for response.
*/
public abstract class NettyClientConnection {
protected static Logger LOGGER = LoggerFactory.getLogger(NettyTCPClientConnection.class);
/**
* Client Connection State
*/
public enum State {
INIT,
CONNECTED,
REQUEST_WRITTEN,
REQUEST_SENT,
ERROR,
GOT_RESPONSE;
public boolean isValidTransition(State nextState) {
switch (nextState) {
case INIT:
return false; // Init state happens only as the first transition
case CONNECTED:
return this == State.INIT; // We do not reconnect with same NettyClientConnection object. We create new one
case REQUEST_WRITTEN:
return (this == State.CONNECTED) || (this == State.GOT_RESPONSE);
case REQUEST_SENT:
return this == State.REQUEST_WRITTEN;
case ERROR:
return true;
case GOT_RESPONSE:
return this == State.REQUEST_SENT;
}
return false;
}
};
protected final ServerInstance _server;
protected final EventLoopGroup _eventGroup;
protected Bootstrap _bootstrap;
protected volatile Channel _channel;
// State of the request/connection
protected volatile State _connState;
protected final long _connId;
// Timer for tracking read-timeouts
protected final Timer _timer;
// Callback to notify if a response has been successfully received or error
protected volatile Callback<NoneType> _requestCallback;
public NettyClientConnection(ServerInstance server, EventLoopGroup eventGroup, Timer timer, long connId) {
_connState = State.INIT;
_server = server;
_connId = connId;
_timer = timer;
_eventGroup = eventGroup;
}
public long getConnId() {
return _connId;
}
/**
* Connect to the server. Returns false if unable to connect to the server.
*/
public abstract boolean connect();
/**
* Close the client connection
*/
public abstract void close() throws InterruptedException;
/**
* API to send a request asynchronously.
* @param serializedRequest serialized payload to send the request
* @param requestId Request Id
* @param timeoutMs Timeout in milli-seconds. If timeout < 0, then no timeout
* @return Future to return the response returned from the server.
*/
public abstract ResponseFuture sendRequest(ByteBuf serializedRequest, long requestId, long timeoutMs);
public void setRequestCallback(Callback<NoneType> callback) {
_requestCallback = callback;
}
/**
* Future Handle provided to the request sender to asynchronously wait for response.
* We use guava API for implementing Futures.
*/
public static class ResponseFuture extends AsyncResponseFuture<ByteBuf> {
public ResponseFuture(ServerInstance key, String ctxt) {
super(key, ctxt);
}
/**
* Error Future which is used to represent a future which failed with error.
* @param key Key for the future
* @param error Throwable for the response
*/
public ResponseFuture(ServerInstance key, Throwable error, String ctxt) {
super(key, error, ctxt);
}
}
/**
* Validates if the underlying channel is active
* @return
*/
public boolean validate() {
if (null == _channel) {
return false;
}
return _channel.isActive();
}
public ServerInstance getServer() {
return _server;
}
}