edu.umass.cs.nio.JSONMessenger.java Source code

Java tutorial

Introduction

Here is the source code for edu.umass.cs.nio.JSONMessenger.java

Source

/* Copyright (c) 2015 University of Massachusetts
 * 
 * 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.
 * 
 * Initial developer(s): V. Arun */
package edu.umass.cs.nio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedByInterruptException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.json.JSONException;
import org.json.JSONObject;

import edu.umass.cs.gigapaxos.paxospackets.ProposalPacket;
import edu.umass.cs.gigapaxos.paxospackets.RequestPacket;
import edu.umass.cs.nio.SSLDataProcessingWorker.SSL_MODES;
import edu.umass.cs.nio.interfaces.AddressMessenger;
import edu.umass.cs.nio.interfaces.Byteable;
import edu.umass.cs.nio.interfaces.InterfaceNIOTransport;
import edu.umass.cs.nio.interfaces.NodeConfig;
import edu.umass.cs.nio.interfaces.SSLMessenger;
import edu.umass.cs.utils.DelayProfiler;
import edu.umass.cs.utils.Stringer;
import edu.umass.cs.utils.Util;

/**
 * @author V. Arun
 * @param <NodeIDType>
 * 
 *            This class has support for retransmissions with exponential
 *            backoff. But you can't rely on this backoff for anything other
 *            than ephemeral traffic bursts. If you are overloaded, you are
 *            overloaded, so you must just reduce the load.
 */
public class JSONMessenger<NodeIDType> implements SSLMessenger<NodeIDType, JSONObject> {

    /**
     * The JSON key for the time when the message was sent. Used only for
     * instrumentation purposes by
     * {@link AbstractJSONPacketDemultiplexer#handleMessage(JSONObject)
     * AbstractPacketDemultiplexer.handleMessage}
     */
    public static final String SENT_TIME = "SENT_TIME";
    private static final long RTX_DELAY = 1000; // ms
    private static final int BACKOFF_FACTOR = 2;

    private final InterfaceNIOTransport<NodeIDType, JSONObject> nioTransport;
    protected final ScheduledExecutorService execpool;
    private AddressMessenger<JSONObject> clientMessenger;
    private AddressMessenger<JSONObject> sslClientMessenger;

    private final MessageNIOTransport<NodeIDType, JSONObject>[] workers;

    private Logger log = NIOTransport.getLogger();

    /**
     * @param niot
     */
    /**
     * @param niot
     */
    public JSONMessenger(final InterfaceNIOTransport<NodeIDType, JSONObject> niot) {
        this(niot, 0);
    }

    /**
     * @param niot
     * @param numWorkers
     */
    @SuppressWarnings("unchecked")
    public JSONMessenger(final InterfaceNIOTransport<NodeIDType, JSONObject> niot, int numWorkers) {
        // to not create thread pools unnecessarily
        if (niot instanceof JSONMessenger)
            this.execpool = ((JSONMessenger<NodeIDType>) niot).execpool;
        else
            this.execpool = Executors.newScheduledThreadPool(5, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = Executors.defaultThreadFactory().newThread(r);
                    thread.setName(JSONMessenger.class.getSimpleName() + niot.getMyID() + thread.getName());
                    return thread;
                }
            });
        nioTransport = (InterfaceNIOTransport<NodeIDType, JSONObject>) niot;

        this.workers = new MessageNIOTransport[numWorkers];
        for (int i = 0; i < workers.length; i++) {
            try {
                log.info((this + " starting worker with ssl mode " + this.nioTransport.getSSLMode()));
                this.workers[i] = new MessageNIOTransport<NodeIDType, JSONObject>(null, this.getNodeConfig(),
                        this.nioTransport.getSSLMode());
                this.workers[i].setName(JSONMessenger.class.getSimpleName() + niot.getMyID() + "_send_worker" + i);
            } catch (IOException e) {
                this.workers[i] = null;
                e.printStackTrace();
            }
        }
    }

    @Override
    public void send(GenericMessagingTask<NodeIDType, ?> mtask) throws IOException, JSONException {
        this.send(mtask, false);
    }

    /**
     * Send returns void because it is the "ultimate" send. It will retransmit
     * if necessary. It is inconvenient for senders to worry about
     * retransmission anyway. We may need to retransmit despite using TCP-based
     * NIO because NIO is designed to be non-blocking, so it may sometimes drop
     * messages when asked to send but the channel is congested. We use the
     * return value of NIO send to decide whether to retransmit.
     */
    protected void send(GenericMessagingTask<NodeIDType, ?> mtask, boolean useWorkers)
            throws IOException, JSONException {
        if (mtask == null || mtask.recipients == null || mtask.msgs == null) {
            return;
        }
        IOException thrown = null;
        for (Object msg : mtask.msgs) {
            if (msg == null) {
                assert (false);
                continue;
            }
            String message = null;
            try {
                if (msg instanceof JSONObject) {
                    message = ((JSONObject) (msg)).toString();
                } else if (!(msg instanceof byte[] && msg instanceof Byteable))
                    // we no longer require msg to be JSON at all
                    message = msg.toString();
            } catch (Exception je) {
                log.severe("JSONMessenger" + getMyID() + " incurred exception while decoding: " + msg);
                throw (je);
            }
            byte[] msgBytes = msg instanceof byte[] ? (byte[]) msg
                    : msg instanceof Byteable ? ((Byteable) msg).toBytes()
                            : message.getBytes(MessageNIOTransport.NIO_CHARSET_ENCODING);
            for (int r = 0; r < mtask.recipients.length; r++) {

                int sent = -1;
                try {
                    // special case provision for InetSocketAddress
                    sent = this.specialCaseSend(mtask.recipients[r], msgBytes, useWorkers);
                } catch (IOException e) {
                    if ((e instanceof ClosedByInterruptException))
                        throw e;
                    e.printStackTrace();
                    thrown = e;
                    continue; // remaining sends might succeed
                }

                // check success or failure and react accordingly
                if (sent > 0) {
                    log.log(Level.FINEST, "{0}->{1}:[{2}] ",
                            new Object[] { this, mtask.recipients[r], message != null ? message
                                    : log.isLoggable(Level.FINEST) ? new Stringer(msgBytes) : msgBytes });
                } else if (sent == 0) {
                    log.log(Level.INFO, "{0} experiencing congestion; this is not disastrous (yet)",
                            new Object[] { this });
                    Retransmitter rtxTask = new Retransmitter((mtask.recipients[r]), msgBytes, RTX_DELAY,
                            useWorkers);
                    // can't block here, so have to ignore returned future
                    execpool.schedule(rtxTask, RTX_DELAY, TimeUnit.MILLISECONDS);
                } else {
                    assert (sent == -1) : sent;
                    log.warning("Node " + this.nioTransport.getMyID() + " failed to send message to node "
                            + mtask.recipients[r] + ": " + msg);
                }
            }
        }
        if (thrown != null)
            throw thrown;
    }

    // Note: stops underlying NIOTransport as well.
    public void stop() {
        this.execpool.shutdown();
        this.nioTransport.stop();
        if (this.clientMessenger != null && this.clientMessenger != this
                && this.clientMessenger instanceof InterfaceNIOTransport)
            ((InterfaceNIOTransport<?, ?>) this.clientMessenger).stop();
        if (this.sslClientMessenger != null && this.sslClientMessenger != this
                && this.sslClientMessenger instanceof InterfaceNIOTransport)
            ((InterfaceNIOTransport<?, ?>) this.sslClientMessenger).stop();

        for (int i = 0; i < this.workers.length; i++)
            if (this.workers[i] != null && !((MessageNIOTransport<?, ?>) workers[i]).isStopped()) {
                this.workers[i].stop();
            }
    }

    public String toString() {
        return JSONMessenger.class.getSimpleName() + getMyID();
    }

    @SuppressWarnings("unchecked")
    private int specialCaseSend(Object id, byte[] msgBytes, boolean useWorkers) throws IOException {
        if (id instanceof InetSocketAddress)
            return this.sendToAddress((InetSocketAddress) id, msgBytes);
        else
            return this.sendToID((NodeIDType) id, msgBytes, useWorkers);
    }

    /**
     * We need this because NIO may drop messages when congested. Thankfully, it
     * tells us when it does that. The task below exponentially backs off with
     * each retransmission. We are probably doomed anyway if this class is
     * invoked except rarely.
     */
    private class Retransmitter implements Runnable {

        private final Object dest;
        private final byte[] msg;
        private final long delay;
        private final boolean useWorkers;

        Retransmitter(Object id, byte[] m, long d, boolean useWorkers) {
            this.dest = id;
            this.msg = m;
            this.delay = d;
            this.useWorkers = useWorkers;
        }

        @Override
        public void run() {
            int sent = 0;
            try {
                sent = specialCaseSend(this.dest, this.msg, this.useWorkers);
            } catch (IOException ioe) {
                ioe.printStackTrace();
            } finally {
                if (sent < msg.toString().length() && sent != -1) {
                    // nio can only send all or none, hence the assert
                    assert (sent == 0);
                    log.warning("Node " + nioTransport.getMyID() + "->" + dest
                            + " messenger backing off under severe congestion, Hail Mary!");
                    Retransmitter rtx = new Retransmitter(dest, msg, delay * BACKOFF_FACTOR, useWorkers);
                    execpool.schedule(rtx, delay * BACKOFF_FACTOR, TimeUnit.MILLISECONDS);
                }
                // queue clogged and !isConnected, best to give up
                else if (sent == -1) {
                    log.severe("Node " + nioTransport.getMyID() + "->" + dest
                            + " messenger dropping message as destination unreachable: " + msg);
                }
            }
        }
    }

    /**
     * Sends jsonData to node id.
     * 
     * @param id
     * @param jsonData
     * @return Return value indicates the number of bytes sent. A value of -1
     *         indicates an error.
     * @throws java.io.IOException
     */
    @Override
    public int sendToID(NodeIDType id, JSONObject jsonData) throws IOException {
        return this.nioTransport.sendToID(id, jsonData);
    }

    private int sendToID(NodeIDType id, byte[] msgBytes, boolean useWorkers) throws IOException {
        int i = (int) (Math.random() * (this.workers.length + 1));
        return (useWorkers && this.workers.length > 0 && i < this.workers.length && this.workers[i] != null)
                ? this.workers[i].sendToID(id, msgBytes)
                : this.nioTransport.sendToID(id, msgBytes);
    }

    /**
     * Sends jsonData to address.
     * 
     * @param address
     * @param jsonData
     * @return Refer {@link #sendToID(Object, JSONObject) sendToID(Object,
     *         JSONObject)}.
     * @throws IOException
     */
    @Override
    public int sendToAddress(InetSocketAddress address, JSONObject jsonData) throws IOException {
        return this.nioTransport.sendToAddress(address, jsonData);
    }

    @Override
    public NodeIDType getMyID() {
        return this.nioTransport.getMyID();
    }

    /**
     * @param pd
     *            The supplied packet demultiplexer is appended to end of the
     *            existing list. Messages will be processed by the first
     *            demultiplexer that has registered for processing the
     *            corresponding packet type and only by the first demultiplexer.
     *            <p>
     *            Note that there is no way to remove demultiplexers. All the
     *            necessary packet demultiplexers must be determined at design
     *            time. It is strongly recommended that all demultiplexers
     *            process exclusive sets of packet types. Relying on the order
     *            of chained demultiplexers is a bad idea.
     */
    @Override
    public void addPacketDemultiplexer(AbstractPacketDemultiplexer<?> pd) {
        this.nioTransport.addPacketDemultiplexer(pd);
    }

    @Override
    public void precedePacketDemultiplexer(AbstractPacketDemultiplexer<?> pd) {
        this.nioTransport.precedePacketDemultiplexer(pd);
    }

    protected InterfaceNIOTransport<NodeIDType, JSONObject> getNIOTransport() {
        return this.nioTransport;
    }

    @Override
    public AddressMessenger<JSONObject> getClientMessenger() {
        return this.clientMessenger;
    }

    private AddressMessenger<JSONObject> getClientMessengerInternal() {
        return this.clientMessenger != null ? this.clientMessenger
                : (this.nioTransport instanceof JSONMessenger<?>
                        ? ((JSONMessenger<?>) this.nioTransport).getClientMessenger()
                        : this.clientMessenger);
    }

    @Override
    public AddressMessenger<JSONObject> getSSLClientMessenger() {
        return this.sslClientMessenger;
    }

    private AddressMessenger<JSONObject> getSSLClientMessengerInternal() {
        return this.sslClientMessenger != null ? this.sslClientMessenger
                : (this.nioTransport instanceof JSONMessenger<?>
                        ? ((JSONMessenger<?>) this.nioTransport).getSSLClientMessenger()
                        : this.sslClientMessenger);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void setClientMessenger(AddressMessenger<?> clientMessenger) {
        if (this.clientMessenger != null)
            throw new IllegalStateException("Can not change client messenger once set");
        this.clientMessenger = (AddressMessenger<JSONObject>) clientMessenger;
    }

    @SuppressWarnings("unchecked")
    @Override
    public void setSSLClientMessenger(AddressMessenger<?> sslClientMessenger) {
        if (this.sslClientMessenger != null)
            throw new IllegalStateException("Can not change client messenger once set");
        this.sslClientMessenger = (AddressMessenger<JSONObject>) sslClientMessenger;
    }

    /**
     *
     */
    public static class JSONObjectWrapper extends JSONObject {
        final Object obj;

        /**
         * @param obj
         */
        public JSONObjectWrapper(Object obj) {
            super();
            this.obj = obj;
        }

        public String toString() {
            return obj.toString();
        }

        /**
         * @return The wrapped object.
         */
        public Object getObj() {
            return this.obj;
        }
    }

    /**
     *
     */
    public static class JSONObjectByteableWrapper extends JSONObjectWrapper implements Byteable {

        /**
         * @param obj
         */
        public JSONObjectByteableWrapper(Object obj) {
            super(obj);
        }

        @Override
        public byte[] toBytes() {
            try {
                return obj instanceof Byteable ? ((Byteable) obj).toBytes()
                        : obj.toString().getBytes(MessageNIOTransport.NIO_CHARSET_ENCODING);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    /**
     * A hack that relies on the fact that NIO treats JSONObject as no different
     * from any other object in that it invokes toString() and then getBytes(.)
     * to serialize and send it over the network. It is necessary that the other
     * end receiving this object be able to reconstruct it from a byte[],
     * string, or JSONObject.
     * 
     * Automatically tries to use clientMessenger.
     * 
     * @param sockAddr
     * @param message
     * @param listenSocketAddress
     * @return Number of bytes or characters written.
     * @throws JSONException
     * @throws IOException
     */
    public int sendClient(InetSocketAddress sockAddr, Object message, InetSocketAddress listenSocketAddress)
            throws JSONException, IOException {
        AddressMessenger<JSONObject> msgr = this.getClientMessenger(listenSocketAddress);
        msgr = msgr != null ? msgr : this;
        if (message instanceof byte[])
            return msgr.sendToAddress(sockAddr, (byte[]) message);
        else if (message instanceof Byteable)
            return msgr.sendToAddress(sockAddr, ((Byteable) message).toBytes());
        else
            return msgr.sendToAddress(sockAddr, new JSONObjectWrapper(message));
    }

    /**
     * @param sockAddr
     * @param message
     * @throws JSONException
     * @throws IOException
     */
    public void sendClient(InetSocketAddress sockAddr, Object message) throws JSONException, IOException {
        this.sendClient(sockAddr, message, null);
    }

    @Override
    public NodeConfig<NodeIDType> getNodeConfig() {
        return this.nioTransport.getNodeConfig();
    }

    @Override
    public SSL_MODES getSSLMode() {
        return this.nioTransport.getSSLMode();
    }

    @Override
    public int sendToID(NodeIDType id, byte[] msg) throws IOException {
        return this.nioTransport.sendToID(id, msg);
    }

    @Override
    public int sendToAddress(InetSocketAddress isa, byte[] msg) throws IOException {
        return this.nioTransport.sendToAddress(isa, msg);
    }

    public boolean isStopped() {
        return this.nioTransport.isStopped();
    }

    @Override
    public boolean isDisconnected(NodeIDType node) {
        boolean disconnected = this.nioTransport.isDisconnected(node);
        if (this.workers != null)
            for (InterfaceNIOTransport<NodeIDType, JSONObject> niot : this.workers)
                disconnected = disconnected || (niot != null && niot.isDisconnected(node));
        return disconnected;
    }

    @Override
    public AddressMessenger<JSONObject> getClientMessenger(InetSocketAddress listenSockAddr) {
        AddressMessenger<JSONObject> msgr = this.getClientMessengerInternal();
        if (listenSockAddr == null)
            return msgr; // default
        if (msgr instanceof InterfaceNIOTransport && ((InterfaceNIOTransport<?, ?>) msgr)
                .getListeningSocketAddress().getPort() == (listenSockAddr.getPort()))
            return msgr;
        // else
        msgr = this.getSSLClientMessengerInternal();
        if (msgr instanceof InterfaceNIOTransport && ((InterfaceNIOTransport<?, ?>) msgr)
                .getListeningSocketAddress().getPort() == (listenSockAddr.getPort()))
            return msgr;

        assert (this.getListeningSocketAddress().getPort() == (listenSockAddr.getPort())) : this
                .getListeningSocketAddress() + " != " + listenSockAddr;

        return this;
    }

    @Override
    public InetSocketAddress getListeningSocketAddress() {
        return this.nioTransport.getListeningSocketAddress();
    }

}