org.commoncrawl.rpc.base.internal.AsyncClientChannel.java Source code

Java tutorial

Introduction

Here is the source code for org.commoncrawl.rpc.base.internal.AsyncClientChannel.java

Source

/**
 * Copyright 2008 - CommonCrawl Foundation
 * 
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 **/

package org.commoncrawl.rpc.base.internal;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.util.StringUtils;
import org.commoncrawl.async.EventLoop;
import org.commoncrawl.async.Timer;
import org.commoncrawl.common.Environment;
import org.commoncrawl.io.NIOBufferList;
import org.commoncrawl.io.NIOBufferListInputStream;
import org.commoncrawl.io.NIOBufferListOutputStream;
import org.commoncrawl.io.NIOClientSocket;
import org.commoncrawl.io.NIOClientSocketListener;
import org.commoncrawl.io.NIOClientTCPSocket;
import org.commoncrawl.io.NIOSocket;
import org.commoncrawl.rpc.base.shared.BinaryProtocol;
import org.commoncrawl.rpc.base.shared.RPCException;
import org.commoncrawl.rpc.base.shared.RPCStruct;

/**
 * 
 * @author rana
 *
 */
public class AsyncClientChannel implements NIOClientSocketListener, Comparable<AsyncClientChannel> {

    public static interface ConnectionCallback {

        /** called when the Outgoing Channel is connected **/
        void OutgoingChannelConnected(AsyncClientChannel channel);

        /**
         * called when the Channel is disconnected
         * 
         * @return true if you want to resend outgoing message, false to clear
         *         (cancel) outgoing messages
         */
        boolean OutgoingChannelDisconnected(AsyncClientChannel channel);
    }

    private static long _lastChannelId = 0;

    private long _channelId = 0;

    public static final Log LOG = LogFactory.getLog("org.commoncrawl.rpc.AsyncClientChannel");

    private static int INITIAL_RECONNECT_DELAY = 1000;
    private static int MAX_RECONNECT_DELAY = 5000;

    private EventLoop _client;
    // private String _path;
    private int _lastRequestId = 0;
    private Map<Integer, AsyncRequest<RPCStruct, RPCStruct>> _requestMap = Collections
            .synchronizedMap(new HashMap<Integer, AsyncRequest<RPCStruct, RPCStruct>>());

    // list of requests that were already sent
    private LinkedList<AsyncRequest<RPCStruct, RPCStruct>> _sendQueue = new LinkedList<AsyncRequest<RPCStruct, RPCStruct>>();

    // open for business or not
    private boolean _isOpen = false;
    private InetSocketAddress _localAddress;
    private InetSocketAddress _address;
    private NIOClientSocket _socket;
    private int _reconnectDelay = 0;
    private Timer _reconnectTimer;
    private NIOBufferList _output = new NIOBufferList();
    private NIOBufferList _input = new NIOBufferList();
    private NIOBufferListInputStream _inputStream = new NIOBufferListInputStream(_input);
    private NIOBufferListOutputStream _outputStream = new NIOBufferListOutputStream(_output);

    Frame.Decoder _decoder = new Frame.Decoder(_inputStream);
    Frame.Encoder _encoder = new Frame.Encoder(_outputStream);

    /** back pointer to server channel is this is an inoming client channel **/
    AsyncServerChannel _serverChannel = null;
    /** connection callback **/
    ConnectionCallback _connectionCallback;;

    ByteBuffer readBufferDirect = ByteBuffer.allocateDirect(8096 * 4);

    ByteBuffer writeBufferDirect = ByteBuffer.allocateDirect(8096 * 4);

    // constructor
    public AsyncClientChannel(EventLoop client, InetSocketAddress localAddress, InetSocketAddress address,
            ConnectionCallback callback) throws IOException {

        synchronized (AsyncClientChannel.class) {
            _channelId = ++_lastChannelId;
        }

        _client = client;
        // _path = servicePath;
        _address = address;
        _localAddress = localAddress;
        _connectionCallback = callback;
    }

    public AsyncClientChannel(NIOClientTCPSocket socket, AsyncServerChannel serverChannel) throws IOException {

        synchronized (AsyncClientChannel.class) {
            _channelId = ++_lastChannelId;
        }

        // incoming channels are technically always in an open state ...
        _isOpen = true;
        _serverChannel = serverChannel;
        _socket = socket;
        _client = _serverChannel._asyncDispatcher;
        _address = socket.getSocketAddress();

        // setup the listener relationship
        socket.setListener(this);
        // register for an initial read on the socket ...
        _client.getSelector().registerForRead(_socket);
    }

    private synchronized void cancelOutgoingMessages() {

        LinkedList<AsyncRequest<RPCStruct, RPCStruct>> tempList = new LinkedList<AsyncRequest<RPCStruct, RPCStruct>>();

        tempList.addAll(_sendQueue);

        _sendQueue.clear();

        for (AsyncRequest<RPCStruct, RPCStruct> request : tempList) {
            request.setStatus(AsyncRequest.Status.Error_RPCFailed);
            if (request.getCallback() != null) {
                request.getCallback().requestComplete(request);
            }
        }
        _sendQueue.clear();
    }

    public synchronized void close() throws IOException {
        if (_isOpen) {
            _isOpen = false;
            disconnect(true);
        }
    }

    @Override
    public int compareTo(AsyncClientChannel o) {
        long comparisonResult = this._channelId - o._channelId;
        return (comparisonResult < 0 ? -1 : (comparisonResult > 0) ? 1 : 0);
    }

    private synchronized void connect() throws IOException {

        _reconnectTimer = null;
        _socket = new NIOClientTCPSocket(this._localAddress, this);
        _socket.connect(_address);

        getClient().getSelector().registerForConnect(_socket);

    }

    // @Override
    public synchronized void Connected(NIOClientSocket theSocket) throws IOException {

        _reconnectDelay = INITIAL_RECONNECT_DELAY;

        if (_sendQueue.size() != 0) {

            // swap out lists ....
            LinkedList<AsyncRequest<RPCStruct, RPCStruct>> temp = _sendQueue;
            _sendQueue = new LinkedList<AsyncRequest<RPCStruct, RPCStruct>>();
            // and resend all messages ...
            for (AsyncRequest<RPCStruct, RPCStruct> request : temp) {
                try {
                    sendRequest(request);
                } catch (RPCException e) {
                    LOG.error(e);
                    // fail this request ...
                    if (request.getCallback() != null) {
                        request.setStatus(AsyncRequest.Status.Error_RPCFailed);
                        request.setErrorDesc("RPC Failed During Resend");
                        request.getCallback().requestComplete(request);
                    }
                }
            }

            getClient().getSelector().registerForWrite(_socket);
        }
        if (_connectionCallback != null)
            _connectionCallback.OutgoingChannelConnected(this);
    }

    private synchronized void disconnect(boolean flushQueues) {

        if (_reconnectTimer != null) {
            _client.cancelTimer(_reconnectTimer);
            _reconnectTimer = null;
        }

        // disconnect the underlying socket
        if (_socket != null) {
            _client.getSelector().cancelRegistration(_socket);
            _socket.close();
            _socket = null;
        }

        _output.reset();
        _input.reset();
        _inputStream.reset();
        _decoder.reset();
        _output.reset();
        _outputStream.reset();
        _encoder.reset();

        if (flushQueues) {
            cancelOutgoingMessages();
        }
        _requestMap.clear();
    }

    // @Override
    public synchronized void Disconnected(NIOSocket theSocket, Exception disconnectReason) throws IOException {
        // if incoming channel then a disconnect is the end of the line ...
        if (isIncomingChannel()) {
            // close the socket completely ...
            disconnect(true);
            // and notify server channel (parent)
            getServerChannel().ClientChannelDisconnected(this);
        }
        // on the other hand, if this is an outgoing channel, attempt to reconnect
        // to the target...
        else {

            if (_connectionCallback != null) {
                if (_connectionCallback.OutgoingChannelDisconnected(this) == false) {
                    cancelOutgoingMessages();
                }
            }

            // check to see if the channel is still in an OPEN state ...
            // someone may have explicitly CLOSED the channel as a result of the
            // callback ...
            if (isOpen()) {
                reconnect();
            }
        }
    }

    public synchronized void Excepted(NIOSocket socket, Exception e) {
        LOG.error("Runtime Error on Socket:" + StringUtils.stringifyException(e));
        try {
            Disconnected(socket, e);
        } catch (IOException e2) {
            LOG.error(StringUtils.stringifyException(e2));
        }
    }

    public long getChannelId() {
        return _channelId;
    }

    public EventLoop getClient() {
        return _client;
    }

    AsyncServerChannel getServerChannel() {
        return _serverChannel;
    }

    synchronized boolean isIncomingChannel() {
        return (_serverChannel != null);
    }

    /**
     * is this channel in an OPEN state - I.E. was open called on the channel ?
     * NOT to be confused with being in a CONNECTED state.
     * **/
    public synchronized boolean isOpen() {
        return _isOpen;
    }

    public synchronized void open() throws IOException {
        if (!_isOpen) {
            _isOpen = true;
            reconnect();
        } else {
            LOG.error("open called on already open channel");
            throw new IOException("Channel Alread Open");
        }
    }

    // @Override
    public synchronized int Readable(NIOClientSocket theSocket) throws IOException {

        if (!_socket.isOpen()) {
            LOG.warn("Readable callback called on closed socket");
            return -1;
        }

        int totalBytesRead = 0;

        try {
            // first read everything we can from the socket
            int singleReadAmount = 0;

            do {

                ByteBuffer buffer = _input.getWriteBuf();

                singleReadAmount = _socket.read(buffer);

                /*
                 * if (buffer.isDirect()) { singleReadAmount = _socket.read(buffer); }
                 * else { readBufferDirect.clear();
                 * readBufferDirect.limit(Math.min(buffer
                 * .remaining(),readBufferDirect.limit())); singleReadAmount =
                 * _socket.read(readBufferDirect); readBufferDirect.flip(); if
                 * (singleReadAmount > 0) { buffer.put(readBufferDirect); } }
                 */

                if (singleReadAmount > 0) {
                    _input.write(buffer);
                    totalBytesRead += singleReadAmount;
                }

            } while (singleReadAmount > 0);

            if (totalBytesRead != 0) {
                _input.flush();
            }
            // if this is an outgoing channel, then read response frames ...
            if (!isIncomingChannel()) {
                // next read incoming frames as appropriate ...
                readResponseFrames();
            } else {
                readRequestFrames();
            }

            ;
            // reset the selection state

            // if the output buffer has data that needs to go out ...
            if (_output.isDataAvailable()) {
                getClient().getSelector().registerForReadAndWrite(_socket);
            }
            // otherwise, we may be waiting for response frames ...
            // or this is an incoming socket, in which case we are always in a
            // readable state ...
            else if (_sendQueue.size() != 0 || isIncomingChannel()) {
                // if so, make socket readable ...
                getClient().getSelector().registerForRead(_socket);
            }
        } catch (IOException e) {
            LOG.error("IOException in Readable callback:" + StringUtils.stringifyException(e));
            e.printStackTrace();
            reconnect();
        }

        return (totalBytesRead == 0) ? -1 : totalBytesRead;
    }

    @SuppressWarnings("unchecked")
    private synchronized void readRequestFrames() throws IOException {

        Frame.IncomingFrame incoming = null;

        while ((incoming = _decoder.getNextRequestFrame()) != null) {

            try {
                getServerChannel().dispatchRequest(this, incoming);
            } catch (RPCException e) {
                LOG.error("RPCException thrown during dispatchRequest:" + e.toString());
                e.printStackTrace();
                // if an RPC Exception is thrown at this point, we need to immediately
                // fail this request ...
                AsyncContext<RPCStruct, RPCStruct> dummyContext = new AsyncContext<RPCStruct, RPCStruct>(
                        getServerChannel(), this, incoming._requestId, null, null);

                dummyContext.setStatus(AsyncRequest.Status.Error_RPCFailed);
                dummyContext.setErrorDesc(e.toString());
                try {
                    sendResponse(dummyContext);
                } catch (RPCException e1) {
                    LOG.error("RPCException while sending <FAILED> response::" + e.toString());
                    e.printStackTrace();
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private synchronized void readResponseFrames() throws IOException {

        Frame.IncomingFrame incoming = null;

        while ((incoming = _decoder.getNextResponseFrame()) != null) {

            // lookup the request id in the sent map ...
            AsyncRequest associatedRequest = _requestMap.get(incoming._requestId);

            if (associatedRequest != null) {

                _requestMap.remove(incoming._requestId);

                synchronized (_sendQueue) {
                    _sendQueue.remove(associatedRequest);
                }

                try {

                    // set status based on incoming stastus code ...
                    associatedRequest.setStatus(AsyncRequest.Status.values()[incoming._status]);
                    // and if success ... retrieve payload ...

                    if (associatedRequest.getStatus() == AsyncRequest.Status.Success) {
                        // if found, deserialize the payload into the message object
                        associatedRequest.getOutput().deserialize(new DataInputStream(incoming._payload),
                                new BinaryProtocol());
                    }
                    // and if server error ...
                    else if (associatedRequest.getStatus() == AsyncRequest.Status.Error_ServerError) {
                        // attempt to read error desc if present ...
                        if (incoming._payload.available() != 0) {
                            associatedRequest.setErrorDesc((new DataInputStream(incoming._payload)).readUTF());
                        }
                    }

                } catch (IOException e) {
                    LOG.error("IOException in readResponseFrame:" + StringUtils.stringifyException(e));
                    associatedRequest.setStatus(AsyncRequest.Status.Error_RPCFailed);
                }

                // and initiate the callback
                if (associatedRequest.getCallback() != null) {
                    associatedRequest.getCallback().requestComplete(associatedRequest);
                }
            } else {
                LOG.error("Orphaned request found in readResponseFrame");
            }

        }
    }

    public synchronized void reconnect() throws IOException {

        disconnect(false);

        // if reconnect delay is zero (bootstrap stage only)
        // then connect immediately...
        if (_reconnectDelay == 0) {
            _reconnectDelay = INITIAL_RECONNECT_DELAY;
            connect();
        }
        // otherwise schedule a timer base on current reconnect interval
        else {
            _reconnectTimer = new Timer(_reconnectDelay, false,

                    new Timer.Callback() {

                        // @Override
                        public void timerFired(Timer timer) {
                            try {
                                if (Environment.detailLogEnabled())
                                    LOG.debug("Reconnecting to:" + _address);
                                connect();
                            } catch (IOException e) {
                                e.printStackTrace();
                                LOG.error("Reconnect threw exception:" + e.toString());
                            }
                        }
                    });
            // register the timer
            _client.setTimer(_reconnectTimer);
        }
        // either way, increase subsequent reconnect interval
        _reconnectDelay = Math.min(MAX_RECONNECT_DELAY, _reconnectDelay * 2);
    }

    @SuppressWarnings("unchecked")
    public synchronized void sendRequest(AsyncRequest request) throws RPCException {

        int requestId = 0;

        try {
            synchronized (this) {
                requestId = ++_lastRequestId;
            }

            request.setRequestId(requestId);

            _requestMap.put(requestId, request);

            _encoder.encodeRequest(request);
            _sendQueue.add(request);

            if (_socket != null && _socket.isOpen()) {
                getClient().getSelector().registerForReadAndWrite(_socket);
            }

        } catch (IOException e) {

            _requestMap.remove(requestId);

            e.printStackTrace();

            LOG.error("IOException during sendRequest:" + e.toString());

            throw new RPCException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public synchronized void sendResponse(AsyncContext context) throws RPCException {

        // LOG.info("Sending Response");
        if (_socket == null || !_socket.isOpen()) {
            LOG.error("sendResponse invoked on closed channel");
            throw new RPCException("Invoking RPC Response on Closed Channel.");
        }

        try {
            _encoder.encodeResponse(context);
            getClient().getSelector().registerForReadAndWrite(_socket);

        } catch (IOException e) {
            LOG.error("IOException during encodeResponse in sendResponse::" + e.toString());
            e.printStackTrace();
            throw new RPCException(e);
        }
    }

    @Override
    public String toString() {
        if (_address != null) {
            return "RPCChannel(" + _address.toString() + ")";
        } else {
            return "Uninitialized ClientRPCChannel";
        }
    }

    // @Override
    public synchronized void Writeable(NIOClientSocket theSocket) throws IOException {
        if (!_socket.isOpen()) {
            LOG.warn("Writeable callback called on closed socket");
            return;
        }

        int amountWritten = 0;

        try {

            do {

                amountWritten = 0;

                ByteBuffer bufferToWrite = _output.read();

                if (bufferToWrite != null) {

                    try {

                        amountWritten = _socket.write(bufferToWrite);
                        /*
                         * if (bufferToWrite.isDirect()) { amountWritten =
                         * _socket.write(bufferToWrite); } else { writeBufferDirect.clear();
                         * bufferToWrite.mark(); writeBufferDirect.put(bufferToWrite);
                         * writeBufferDirect.flip(); amountWritten =
                         * _socket.write(writeBufferDirect); bufferToWrite.reset();
                         * bufferToWrite.position(bufferToWrite.position() + amountWritten);
                         * }
                         */

                    } catch (IOException e) {
                        throw e;
                    }

                    if (bufferToWrite.remaining() > 0) {
                        _output.putBack(bufferToWrite);
                        break;
                    }
                }
            } while (amountWritten > 0);

            if (_output.isDataAvailable()) {
                getClient().getSelector().registerForReadAndWrite(_socket);
            } else if (_sendQueue.size() != 0 || isIncomingChannel()) {
                getClient().getSelector().registerForRead(_socket);
            }
        } catch (IOException e) {
            LOG.error("IOException in Writeable callback:" + e.toString());
            reconnect();
            throw e;
        }
    }
}