All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.client.EAbstractConnection Maven / Gradle / Ivy

The newest version!
//
// Copyright 2015, 2016, 2020 Charles W. Rapp
//
// 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 net.sf.eBus.client;

import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SocketChannel;
import java.time.Duration;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static net.sf.eBus.client.EClient.sCoreExecutor;
import net.sf.eBus.client.sysmessages.KeyMessage;
import net.sf.eBus.client.sysmessages.SystemMessageType;
import net.sf.eBus.config.EConfigure.AbstractConfig;
import net.sf.eBus.config.EConfigure.ConnectionRole;
import net.sf.eBus.config.EConfigure.ConnectionType;
import net.sf.eBus.config.EConfigure.RemoteConnection;
import net.sf.eBus.config.EConfigure.Service;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.ENotificationMessage;
import net.sf.eBus.messages.EReplyMessage;
import net.sf.eBus.messages.ESystemMessage;
import net.sf.eBus.messages.InvalidMessageException;
import net.sf.eBus.messages.UnknownMessageException;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.messages.type.MessageType;
import net.sf.eBus.net.AsyncChannel;
import net.sf.eBus.timer.EScheduledExecutor.IETimer;
import net.sf.eBus.util.LazyString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base class for eBus TCP connections. Maintains the
 * {@link AsyncChannel channel}  connection and
 * {@link AbstractMessageWriter} instance.
 *
 * @author Charles Rapp
 */

@SuppressWarnings({"java:S1192", "java:S3457"})
/* package */ abstract class EAbstractConnection
    implements EObject
{
//---------------------------------------------------------------
// Member enums.
//

    /**
     * This enum defines the allowed connection states used by
     * all eBus connection types.
     */
    protected enum ConnectState
    {
        /**
         * The eBus connection is closed.
         */
        CLOSED,

        /**
         * eBus is in the process of connecting.
         */
        CONNECTING,

        /**
         * eBus is in the process of establishing a secure
         * UDP connection (that is, performing the DTLS opening
         * handshake).
         */
        UDP_SECURE_CONNECTING,

        /**
         * The eBus is connected. Note that this has nothing to
         * do with logging on to the remote eBus.
         */
        CONNECTED,

        /**
         * The eBus connection will be closed once all messages
         * are sent.
         */
        CLOSING,

        /**
         * The eBus connection is down and will be
         * re-established.
         */
        RECONNECTING
    } // end of enum ConnectState

//---------------------------------------------------------------
// Member data.
//

    //-----------------------------------------------------------
    // Constants.
    //

    /**
     * This value specifies that there is no limit to the
     * transmit queue size.
     */
    public static final int DEFAULT_QUEUE_SIZE = 0;

    /**
     * A message's serialized size may be at most this many
     * bytes.  Messages longer than that size result in
     * eBus connection termination.
     */
    public static final int MAX_MESSAGE_SIZE = Short.MAX_VALUE;

    /**
     * A zero bind port value means that the local end of the
     * TCP connection will be bound to any available ephemeral
     * port.
     */
    public static final int ANY_PORT = 0;

    /**
     * Wait one minute before attempting to reconnect by default.
     */
    public static final Duration DEFAULT_RECONNECT_DELAY =
        Duration.ofMinutes(1);

    /**
     * The default heartbeat rate is 0 milliseconds - which means
     * that heartbeating is off.
     */
    public static final Duration DEFAULT_HEARTBEAT_DELAY =
        Duration.ZERO;

    /**
     * Wait one minute for a heartbeat reply by default.
     */
    public static final Duration DEFAULT_HEARTBEAT_REPLY_DELAY =
        Duration.ofMinutes(1);

    /**
     * The message size takes {@value} bytes.
     */
    public static final int MESSAGE_SIZE_SIZE = 4;

    /**
     * A message header size is {@value} bytes.
     */
    protected static final int MESSAGE_HEADER_SIZE = 16;

    /**
     * Heartbeat and heartbeat reply messages are negative
     * two-byte integers.
     */
    protected static final int HEARTBEAT = -15000;

    /**
     * There is a separate reply message for a heartbeat.
     */
    protected static final int HEARTBEAT_REPLY = -8000;

    //
    // Message callback MethodHandles.
    //

    /**
     * Well known method names for processing messages.
     */
    private static final String[] CB_METHOD_NAMES =
    {
        "remoteAd",             // AdMessage.
        "remoteCancelRequest",  // CancelRequest
        "remoteClassUpdate",    // KeyMessage
        "remoteFeedStatus",     // FeedStatusMessage
        "remoteLogoff",         // LogoffMessage
        "remoteLogonComplete",  // LogonComplete
        "remoteLogon",          // Logon
        "remoteLogonReply",     // LogonReply
        "remoteRequestAck",     // RemoteAck
        "remoteSubscribe",      // SubscribeMessage
        "remotePauseRequest",   // PauseRequest
        "remotePauseReply",     // PauseReply
        "remoteResumeRequest",  // ResumeRequest
        "remoteResumeReply",    // ResumeReply
        "remoteInvalidMessage", // UDPConnectRequest
        "remoteInvalidMessage", // UDPConnectReply
        "remoteInvalidMessage", // UDPDiconnectRequest
        "remoteInvalidMessage", // UDPDisconnectReply
        "remoteNotify",         // Notification
        "remoteRequest",        // Request
        "remoteReply"           // Reply
    };

    /**
     * Pass application notification messages to "remoteNotify".
     */
    protected static final int NOTIFY_CB = 18;

    /**
     * Pass application request messages to "remoteRequest".
     */
    protected static final int REQUEST_CB = 19;

    /**
     * Pass application reply messages to "remoteReply"
     */
    protected static final int REPLY_CB = 20;

    /**
     * Message callback method handles.
     */
    protected static final MethodHandle[] MESSAGE_CB;

    /**
     * The connection is open callback.
     */
    protected static final int OPEN_CB = 0;

    /**
     * The connection is closed callback.
     */
    protected static final int CLOSE_CB = 1;

    /**
     * Call this {@code ERemoteApp} methods to report changes in
     * the connection state.
     */
    protected static final String[] CONN_CB_METHOD_NAMES =
    {
        "handleOpen",
        "handleClose"
    };

    /**
     * Callbacks for reporting when the connection is up, down,
     * or en error occurred.
     */
    protected static final MethodHandle[] CONN_CB;

    /**
     * The message type is used to serialize and de-serialize an
     * eBus message.
     */
    protected static final DataType MESSAGE_TYPE =
        DataType.findType(EMessage.class);

    //-----------------------------------------------------------
    // Statics.
    //

    /**
     * Access point for logging.
     */
    private static final Logger sLogger =
        LoggerFactory.getLogger(EAbstractConnection.class);

    /**
     * The total inbound message count for all external clients.
     */
    protected static int sTotalInCount = 0;

    /**
     * The total outbound message count for all external clients.
     */
    protected static int sTotalOutCount = 0;

    // Class static initialization
    static
    {
        final MethodHandles.Lookup lookup = MethodHandles.lookup();
        int size;
        int index = 0;

        size = CB_METHOD_NAMES.length;
        MESSAGE_CB = new MethodHandle[size];

        // Look up the message callback methods.
        try
        {
            final MethodType mt =
                MethodType.methodType(
                    void.class, EMessageHeader.class);

            for (index = 0; index < size; ++index)
            {
                MESSAGE_CB[index] =
                    lookup.findVirtual(
                        ERemoteApp.class,
                        CB_METHOD_NAMES[index],
                        mt);
            }
        }
        catch (NoSuchMethodException |
               IllegalAccessException jex)
        {
            sLogger.error("{} lookup failed",
                          CB_METHOD_NAMES[index],
                          jex);
        }

        size = CONN_CB_METHOD_NAMES.length;
        CONN_CB = new MethodHandle[size];

        // Look up the connection callback methods.
        try
        {
            final MethodType mt =
                MethodType.methodType(
                    void.class, EAbstractConnection.class);

            for (index = 0; index < size; ++index)
            {
                CONN_CB[index] =
                    lookup.findVirtual(
                        ERemoteApp.class,
                        CONN_CB_METHOD_NAMES[index],
                        mt);
            }
        }
        catch (NoSuchMethodException |
               IllegalAccessException jex)
        {
            sLogger.error("{} lookup failed",
                          CONN_CB_METHOD_NAMES[index],
                          jex);
        }
    } // end of class static initialization

    //-----------------------------------------------------------
    // Locals.
    //

    /**
     * Forward connection events (which includes messages) to
     * this listener.
     * 

* This data member is not {@code final} because * when resuming a connection it needs to be reset to the * previous {@link ERemoteApp} that was paused and not the * {@code ERemoteApp} instance created when the connection * was accepted. *

*/ protected ERemoteApp mRemoteApp; /** * The asynchronous socket on which messages are sent and * received. This field is not final because the channel * constructor takes {@code this} as an argument. That means * the socket must be created post construction. */ protected AsyncChannel mAsocket; /** * The underlying connection is of this type. */ protected final ConnectionType mConnectionType; /** * This connection was either initiated or accepted. */ protected final ConnectionRole mConnectRole; /** * Connecting to this remote address. This value does not * change once set. */ protected SocketAddress mRemoteAddress; /** * Bind the local address to this port. This value is * set when the socket is opened, so it cannot be final. */ protected SocketAddress mBindAddress; /** * Maps the class identifier to the reader responsible for * de-serializing it and posting it to the appropriate * callback. */ protected final Map mInputReaders; /** * The current connection state. */ protected final AtomicReference mState; /** * The message writer is responsible for serializing outbound * messages directly to the {@link #mAsocket socket's} * output buffer. *

* Like {@link #mAsocket}, this field must be set post * construction. *

*/ protected AbstractMessageWriter mOutputWriter; /** * When this flag is true, we are expecting a heartbeat * reply. When a heartbeat is received and this flag is * true, don't send a heartbeat in reply. When this flag is * false, send a heartbeat. *

* This flag is {@code protected} because subclasses need * to turn off the reply flag when input arrives. *

*/ protected volatile boolean mHeartbeatReplyFlag; /** * The serialized heartbeat message. */ protected final byte[] mHeartbeatData; /** * The serialized heartbeat reply message. */ protected final byte[] mHeartbeatReplyData; /** * Set to {@code true} if the connect supports pause and * {@code false} if the connection cannot be paused. */ private final boolean mPauseFlag; /** * When set to true, attempt to maintain the connection. */ private volatile boolean mReconnectFlag; /** * If the connection is lost, then reconnect after the * specified millisecond delay. */ private final AtomicReference mReconnectDelay; /** * When this timer expires, attempt to connect. */ private final AtomicReference mReconnectTimer; /** * Send heartbeats at this millisecond frequency. */ private final AtomicReference mHeartbeatDelay; /** * The heartbeat timer task. */ private final AtomicReference mHeartbeatTimer; /** * If heartbeating is turned on, then send heartbeat * messages at this millisecond rate. */ private final AtomicReference mHeartbeatReplyDelay; /** * The heart beat timer task. */ private final AtomicReference mHeartbeatReplyTimer; // // StatusReport statistics. // Not meant to be strictly accurate. // /** * Count up the number of messages received from the remote * eBus engine. */ protected int mMsgInCount; /** * Count up the number of messages sent to the remote eBus * engine. */ protected int mMsgOutCount; //--------------------------------------------------------------- // Member methods. // //----------------------------------------------------------- // Constructors. // /** * Creates a new eBus connection instance. * @param connType defines the connection type (TCP or UDP) * and secure or insecure. * @param connRole either accepted or initiated. * @param remoteApp pass events to this eBus remote * application interface. * @param pauseFlag {@code true} if the connection supports * pause. */ protected EAbstractConnection(final ConnectionType connType, final ConnectionRole connRole, final ERemoteApp remoteApp, final boolean pauseFlag) { if (remoteApp == null) { throw ( new IllegalArgumentException( "remoteApp is null")); } mRemoteApp = remoteApp; mConnectionType = connType; mConnectRole = connRole; mAsocket = null; mInputReaders = new HashMap<>(); mOutputWriter = null; mBindAddress = null; mPauseFlag = pauseFlag; mReconnectFlag = false; mReconnectDelay = new AtomicReference<>(DEFAULT_RECONNECT_DELAY); mReconnectTimer = new AtomicReference<>(); mHeartbeatTimer = new AtomicReference<>(); mHeartbeatDelay = new AtomicReference<>(DEFAULT_HEARTBEAT_DELAY); mHeartbeatReplyTimer = new AtomicReference<>(); mHeartbeatReplyFlag = false; mHeartbeatReplyDelay = new AtomicReference<>(DEFAULT_HEARTBEAT_REPLY_DELAY); mHeartbeatData = new byte[Integer.BYTES]; mHeartbeatReplyData = new byte[Integer.BYTES]; mState = new AtomicReference<>(ConnectState.CLOSED); } // end of EAbstractConnection(...) // // end of Constructors. //----------------------------------------------------------- //----------------------------------------------------------- // Abstract Method Declarations. // /** * Initializes a newly created remote connection with the * given configuration. * @param config initialize connection with this * configuration. */ protected abstract void doInitialize(RemoteConnection config); /** * Returns output writer for this connection type. * @param config remote connection configuration. * @return connection output writer. */ protected abstract AbstractMessageWriter createWriter(AbstractConfig config); /** * Establishes a connection to the given address and bind * port. Returns {@code true} if the connection is * immediately established and {@code false} if the * connection is in-progress. If {@code false} is returned, * {@link ERemoteApp#handleOpen(EAbstractConnection)} is * called when the connection is established. * @param address connect to this remote address. * @param bindAddress bind the local address to this address. * @return {@code true} if the connection is successfully * opened prior to returning. * @throws IOException * if an I/O error occurs when establishing the connection. * * @see #doOpen(SelectableChannel) * @see #doClose() * @see #doCloseNow() */ protected abstract boolean doOpen(final SocketAddress address, final SocketAddress bindAddress) throws IOException; /** * Initializes a newly accepted remote connection with the * given service configuration. * @param config initialize connection with this * configuration. */ protected abstract void doInitialize(Service config); /** * Uses a previously accepted channel to "open" the * connection. * @param socket the accepted socket connection. * @return {@code true} if the connection is successfully * opened prior to returning. * @throws IOException * if {@code socket} is closed. * * @see #doOpen(InetSocketAddress, int) * @see #doClose() * @see #doCloseNow() */ protected abstract boolean doOpen(SelectableChannel socket) throws IOException; /** * Performs the necessary post-connection configuration on * the underlying socket. * @throws IOException * if an I/O failure occurs while configuring the socket. */ protected abstract void doConnected() throws IOException; /** * Performs the actual work of closing the underlying socket * connection. This call should perform a "slow" socket close * which waits for all pending output to be transmitted * before closing the socket. * * @see #close() * @see #doCloseNow() * @see #send(EMessageHeader) */ protected abstract void doClose(); /** * Closes the eBus connection immediately. All messages * posted for transmission but not completely sent are lost. * No new messages may be sent after this point and no new * messages will be received. * @see #close * @see #send(EMessageHeader) */ protected abstract void doCloseNow(); /** * Returns {@code true} if data may be transmitted on this * connection and {@code false} otherwise. * @return {@code true} if clear to transmit a message. */ protected abstract boolean maySend(); /** * Sends the messages stored in the * {@link #mOutputWriter output buffer writer}. * @param writer outbound messages are stored in this writer. * @throws IOException * if an I/O failure occurs during the transmit. */ protected abstract void doSend(AbstractMessageWriter writer) throws IOException; /** * Sends the heartbeat bytes to the far-end. * @throws IOException * if an I/O failure occurs when transmitting the heartbeat * bytes. */ protected abstract void doSendHeartbeat() throws IOException; // // end of Abstract Method Declarations. //----------------------------------------------------------- //----------------------------------------------------------- // Timeout Handlers. // /** * Sends a heartbeat message to the far-end. */ @SuppressWarnings({"java:S1172"}) private void heartbeatTimeout() { sLogger.debug("{}: heartbeat timer expired.", mRemoteAddress); mHeartbeatTimer.set(null); mHeartbeatReplyFlag = true; // If the send fails, what's the point in starting // the reply timer? try { sLogger.trace("{}: sending heartbeat.", mRemoteAddress); doSendHeartbeat(); startHeartbeatTimer(); } catch (IOException ioex) { sLogger.warn("{}: heartbeat send failed.", mRemoteAddress, ioex); disconnected(ioex); } } // end of heartbeatTimeout() /** * Closes the connect due to the remote eBus failing to * respond to a heartbeat. */ @SuppressWarnings({"java:S1172"}) private void heartbeatReplyTimeout() { sLogger.debug("{}: heartbeat reply timer expired.", mRemoteAddress); mHeartbeatReplyTimer.set(null); // We are no longer waiting for a reply. mHeartbeatReplyFlag = false; // We have timed out waiting for the far-end to reply // to our heartbeat. Something is very wrong. // Close down this connection. mAsocket.closeNow(); disconnected(new IOException("no heartbeat reply")); } // end of heartbeatReplyTimeout() /** * Attempts to reconnect to the far-end. */ @SuppressWarnings({"java:S1172"}) private void reconnectTimeout() { sLogger.debug("{}: reconnect timer expired.", mRemoteAddress); mReconnectTimer.set(null); reconnect(); } // end of reconnectTimeout() // // end of Timeout Handlers. //----------------------------------------------------------- //----------------------------------------------------------- // Get Methods. // /** * Returns the connection type. * @return connection type. */ public final ConnectionType connectionType() { return (mConnectionType); } // end of connectionType() /** * Returns {@code true} if this connection automatically * reconnects upon unexpected connection loss. * @return {@code true} if automatic reconnection is set * and {@code false} otherwise. */ public final boolean willReconnect() { return (mReconnectFlag); } // end of willReconnect() /** * Returns {@code true} if this connection may be paused and * {@code false} if it does not support pause. * @return {@code true} if this connection may be paused. */ public final boolean willPause() { return (mPauseFlag); } // end of willPause() /** * Returns {@code true} if this connection is in the process * of connecting; {@code false} otherwise. * @return {@code true} when connecting. */ /* package */ final boolean isConnecting() { final ConnectState state = mState.get(); return (state == ConnectState.CONNECTING || state == ConnectState.UDP_SECURE_CONNECTING); } // end of isConnecting() /** * Returns {@code true} if fully connected to remote eBus * and {@code false} otherwise. * @return {@code true} if fully connected to remote eBus * and {@code false} otherwise. */ public final boolean isOpen() { return (mAsocket.isOpen()); } // end of isOpen() /** * Returns remote eBus' far-end address. * If not connected, then returns {@code null}. * @return an {@code SocketAddress} value. Will be * {@code null} if not connected. * @see #localSocketAddress */ public final SocketAddress remoteSocketAddress() { return (mRemoteAddress); } // end of remoteSocketAddress() /** * Returns this eBus connection's local address. If not * connected, then returns {@code null}. * @return the eBus connection's local address. * @see #remoteSocketAddress */ public final SocketAddress localSocketAddress() { return (mAsocket.localSocketAddress()); } // end of localSocketAddress() /** * Returns the input buffer size in bytes. * @return input buffer capacity. */ public final int inputBufferSize() { return (mAsocket.inputBufferSize()); } // end of inputBufferSize() /** * Returns the output buffer size in bytes. * @return output buffer capacity. */ public final int outputBufferSize() { return (mAsocket.outputBufferSize()); } // end of outputBufferSize() /** * Returns the maximum message queue size. * @return maximum queue size. */ public final int maxMessageQueueSize() { return (mOutputWriter.maximumSize()); } // end of maxMessageQueueSize() /** * Returns number of messages currently on the queue. * @return message queue size. */ public final int messageQueueSize() { return (mOutputWriter.transmitQueueSize()); } // end of messageQueueSize() /** * Returns how long this {@code EConnection} will * wait before attempting to reconnect. * @return Reconnect delay in milliseconds. */ public Duration reconnectDelay() { return (mReconnectDelay.get()); } // end of reconnectDelay() /** * Returns how long this {@code EConnection} will * wait before sending a heartbeat message. * @return heartbeat frequency in milliseconds. */ public Duration heartbeatDelay() { return (mHeartbeatDelay.get()); } // end of heartbeatDelay() /** * Returns the heartbeat reply delay. * @return heartbeat reply delay. in milliseconds. */ public Duration heartbeatReplyDelay() { return (mHeartbeatReplyDelay.get()); } // end of heartbeatReplyDelay() /** * Returns current outbound message sequence number. * @return outbound message sequence number. */ public final int outboundSequenceNumber() { return (mOutputWriter.outboundSequenceNumber()); } // end of outboundSequenceNumber() /** * Returns a copy of the input readers map. This is done so * that a paused connection may be restored when resumed. * @return input readers map copy. */ /* package */ final Map readers() { return (new HashMap<>(mInputReaders)); } // end of readers() // // end of Get Methods. //----------------------------------------------------------- //----------------------------------------------------------- // Set Methods. // /** * Sets the new connect state, logging the fact. * @param state new connect state. */ protected final void setState(final ConnectState state) { sLogger.trace("{}: connect state is {}.", mRemoteAddress, state); mState.set(state); } // end of setState(ConnectState) /** * When a previously paused connection is resumed, this * method is called to move this connection from its * original remote application to the now resumed remote * application instance. * @param app resumed remote application connection. * @param readers reset the readers map to the given values. */ /* package */ final void resumeConnection(final ERemoteApp app, final Map readers) { mRemoteApp = app; mInputReaders.clear(); mInputReaders.putAll(readers); } // end of resumeConnection(ERemoteApp, Map<>) /** * Creates a new {@link #mInputReaders} entry based on the * {@link KeyMessage} message. If the message contains a * class name that is not found in this JVM, then * store a {@code null} {@link MessageReader} in the map to * signal that the message class is not supported. * @param msg the class update message. */ /* package */ final void keyUpdate(final KeyMessage msg) { final int keyId = msg.keyId; MessageReader reader = null; try { @SuppressWarnings ("unchecked") final Class mc = (Class) Class.forName(msg.messageClass); final String subject = msg.messageSubject; final MessageType mt = (MessageType) DataType.findType(mc); final MethodHandle mh; final String mn; // Is this fish, fowl, or flesh? // Make that notification, request, or reply. if ((ENotificationMessage.class).isAssignableFrom(mc)) { // Notification. mh = MESSAGE_CB[NOTIFY_CB]; mn = CB_METHOD_NAMES[NOTIFY_CB]; } else if ((EReplyMessage.class).isAssignableFrom(mc)) { // Reply. mh = MESSAGE_CB[REPLY_CB]; mn = CB_METHOD_NAMES[REPLY_CB]; } else { // That leaves request. mh = MESSAGE_CB[REQUEST_CB]; mn = CB_METHOD_NAMES[REQUEST_CB]; } reader = createReader(keyId, subject, mt, mh, mn); } catch (ClassNotFoundException classex) { // Ignore and put null into the readers map. } mInputReaders.put(keyId, reader); } // end of keyUpdate(KeyMessage) // // end of Set Methods. //----------------------------------------------------------- /** * Initializes a newly created remote connection with the * given configuration. * @param config initialize connection with this * configuration. */ /* package */ final void initialize(final RemoteConnection config) { createHeartbeatData(config); doInitialize(config); mOutputWriter = createWriter(config); initializeReaders(); } // end of initialize(RemoteConnection) /** * Establishes a connection to the address specified in * {@code config}. Returns {@code true} if the connection is * established and {@code false} if the connection is * in-progress. If {@code false} is returned, * {@link ERemoteApp#handleOpen(EAbstractConnection)} is * called when the connection is established. * @param config remote connection configuration. * @return {@code true} if connection is established and * {@code false} if the connection is in-progress. * @exception IOException * if socket connection attempt threw an exception and * {@code reconnectFlag} is {@code false}. * * @see #open(SelectableChannel, Service) * @see #close() * @see #closeNow() */ @SuppressWarnings({"java:S2139"}) /* package */ final boolean open(final RemoteConnection config) throws IOException { boolean retcode = false; sLogger.debug("{}: connecting.", config.address()); try { setState(ConnectState.CONNECTING); mRemoteAddress = config.address(); mBindAddress = config.bindAddress(); retcode = doOpen(mRemoteAddress, mBindAddress); // Set these data members after calling // AsyncSocket.open() in case the socket throws an // IllegalStateException. mReconnectFlag = config.reconnectFlag(); mReconnectDelay.set(config.reconnectTime()); mHeartbeatDelay.set(config.heartbeatDelay()); mHeartbeatReplyDelay.set( config.heartbeatReplyDelay()); if (retcode) { setState(ConnectState.CONNECTED); startHeartbeatTimer(); } } catch (IOException ioex) { sLogger.warn("{}: open failed.", config.address(), ioex); // Do *not* attempt to reconnect if the initial // connect attempt fails. Reconnection is do iff // the first connect attempt succeeds. throw (ioex); } return (retcode); } // end of open(SocketAddress, ...) /** * Initializes a newly accepted remote connection with the * given configuration. * @param config initialize connection with this * configuration. */ /* package */ final void initialize(final Service config) { doInitialize(config); mOutputWriter = createWriter(config); initializeReaders(); } // end of initialize(Service) /** * Encapsulates an already connected socket accept by an * {@link EServer} instance. Will not reconnect. * @param socket the accepted socket connection. * @param config accept socket configuration. * @exception NullPointerException * if {@code socket} is {@code null}. * @exception IOException * if {@code socket} is closed. */ /* package */ final void open(final SelectableChannel socket, final SocketAddress remoteAddress, final Service config) throws IOException { final boolean retcode; Objects.requireNonNull(socket, "socket is null"); setState(ConnectState.CONNECTED); mRemoteAddress = remoteAddress; mReconnectFlag = false; mReconnectDelay.set(Duration.ZERO); mHeartbeatDelay.set(config.heartbeatDelay()); mHeartbeatReplyDelay.set(config.heartbeatReplyDelay()); sLogger.debug("{}: encapsulating socket.", mRemoteAddress); retcode = doOpen(socket); // If this is a clear text TCP connection, then start // the heartbeat timer. Otherwise, wait for the secure // handshake to complete. if (retcode) { startHeartbeatTimer(); } } // end of open(SelectableChannel, SocketAddress, Service) /** * Closes the eBus connection immediately. All messages * posted for transmission but not completely sent are lost. * No new messages may be sent after this point and no new * messages will be received. * * @see #open(SocketChannel, long, long) * @see #open(SocketAddress, int, boolean, long, long, long) * @see #close() * @see #send(EMessageHeader) */ /* package */ final void closeNow() { if (mState.get() != ConnectState.CLOSED) { setState(ConnectState.CLOSING); closeConnection(); doCloseNow(); // Discard any and all unsent messages. mOutputWriter.closed(); } } // end of closeNow() /** * Immediately closes the open socket connection and starts * the reconnect process, but only if this connection is * configured to reconnect. If this connection is not * configured to reconnect, then closes the connection only. * Does nothing if the connection is already closed. * Messages posted for transmission but not completely sent * are lost. */ /* package */ final void closeAndReconnect() { if (mAsocket.isOpen()) { stopHeartbeatTimer(); stopReconnectTimer(); sLogger.debug("{}: reconnecting after {}.", mRemoteAddress, mReconnectDelay.get()); mAsocket.closeNow(); if (mReconnectFlag) { // Starting the reconnect timer also sets the // connection state. startReconnectTimer(); } else { setState(ConnectState.CLOSED); } } } // end of closeAndReconnect() /** * Immediately closes the open socket connection and starts * the reconnect process after the specified delay. Caller * has already determined that this connection may be paused. * Does nothing if the connection is already closed. Messages * posted for transmission but not completely sent are lost. * @param delay connection pause delay. */ /* package */ final void closeAndPause(final Duration delay) { if (isOpen()) { stopHeartbeatTimer(); stopReconnectTimer(); sLogger.debug( "{}: pausing connection for {} milliseconds.", mRemoteAddress, delay.toMillis()); doCloseNow(); // Treat a pause just like a reconnect because at // this layer it behaves just like a reconnect. // Starting the pause timer also sets the connection // state. startPauseTimer(delay); } } // end of closeAndPause(Duration) /** * Resume the connection now rather than wait for the resume * timer to expire. */ /* package */ final void resumeNow() { sLogger.debug("{}: resuming connection now.", mRemoteAddress); stopReconnectTimer(); reconnect(); } // end of resumeNow() /** * Sends {@code message} to remote eBus application. Since * messages are sent asynchronously, then it is likely that * the message was not transmitted prior to this * method returning. If an application must be certain that * all messages are transmitted prior to closing the * connection, then {@link #close} should be used rather than * {@link #closeNow} as {@link #close} waits for the pending * outgoing messages to be transmitted before closing * the socket connection. Once a connection is closed, no * further messages may be sent. * @param header send this header and message. * @exception NullPointerException * if {@code header} is {@code null}. * @exception IllegalStateException * if this eBus connection is not connected. * @exception IOException * if the connection is closed. */ @SuppressWarnings({"java:S2696"}) /* package */ final void send(final EMessageHeader header) throws IOException { if (header == null) { throw (new NullPointerException("null header")); } if (!maySend()) { throw (new IllegalStateException("not in a state to transmit")); } if (sLogger.isTraceEnabled()) { sLogger.trace( "{}: sending message to remote eBus: from ID={}, to ID={}, seq num={}\n{}", mRemoteAddress, header.fromFeedId(), header.toFeedId(), header.sequenceNumber(), header.message()); } else { sLogger.debug( "{}: sending {} message to remote eBus: from ID={}, to ID={}, seq num={}.", mRemoteAddress, header.messageClass(), header.fromFeedId(), header.toFeedId(), header.sequenceNumber()); } // Give the message to the output writer and the output // writer to the socket. If the output writer queue is // full, allow the buffer overflow exception to pass up // to the caller. if (mOutputWriter.post(header)) { // But if the async socket throws a buffer overflow // exception, do NOT allow that to flow up to the // caller because this is not an exceptional // circumstance. Rather, it is the normal progression // in socket transmissions. try { doSend(mOutputWriter); ++mMsgOutCount; ++sTotalOutCount; } catch (BufferOverflowException bufex) { // Ignore. } } } // end of send(EMessageHeader) /** * Informs the listener that the connection is open. Turns * off Nagle's TCP delay algorithm and starts the heartbeat * time along the way. */ protected final void connected() { sLogger.debug("{}: connected.", mRemoteAddress); setState(ConnectState.CONNECTED); // Do post connection work. try { doConnected(); } catch (IOException ioex) { sLogger.warn("{}: error completing connection.", mRemoteAddress, ioex); } // Is this connection still connected? // Secure UDP connection goes to a UDP_SECURE_CONNECTING // state. if (mState.get() == ConnectState.CONNECTED) { // Yes, fully connected. Continue with the connected // processing. fullyConnected(); } } // end of connected() /** * Immediately closes the underlying socket and informs the * remote address that the initial connect attempt failed. * @param address connection target address. * @param reason text explaining why the connect attempt * failed. */ protected final void connectFailed(final InetSocketAddress address, final String reason) { doCloseNow(); mRemoteApp.connectFailed(address, reason); } // end of connectFailed(InetSocketAddress, String) /** * Completes the connection process by starting the heartbeat * timer and informing the listener that the connection is * up. */ protected final void fullyConnected() { startHeartbeatTimer(); // Let the listener know that we are open and ready for // business. try { final EAbstractConnection abConn = this; (CONN_CB[OPEN_CB]).invokeExact(mRemoteApp, abConn); } catch (Throwable tex) { sLogger.warn("{} exception", CONN_CB_METHOD_NAMES[OPEN_CB], tex); } } // end of fullyConnected() /** * {@code asocket} is disconnected. Stop all timers and * inform the listener. If set to reconnect, then start * the reconnect timer. * @param t The disconnection cause. */ protected final void disconnected(final Throwable t) { sLogger.debug("{}: closed.", mRemoteAddress, t); // Stop any and all timers. stopReconnectTimer(); stopHeartbeatTimer(); // Discard any and all unsent messages. mOutputWriter.closed(); // Tell the listener about this sorry state of affairs. try { final EAbstractConnection abConn = this; (CONN_CB[CLOSE_CB]).invokeExact(mRemoteApp, abConn); } catch (Throwable tex) { sLogger.warn("{} exception.", CONN_CB_METHOD_NAMES[CLOSE_CB], tex); } // Reconnect if configured to do so. startReconnectTimer(); } // end of disconnected(Throwable) /** * Starts either the heartbeat or the heartbeat reply timer * depending on whether we are expecting a heartbeat reply * or not. It is also depends on whether heartbeating is * turned on. */ protected final void startHeartbeatTimer() { Duration delay = mHeartbeatDelay.get(); final boolean isHBOn = (delay.compareTo(Duration.ZERO) > 0); IETimer hbTimer = mHeartbeatTimer.get(); IETimer hbReplyTimer = mHeartbeatReplyTimer.get(); // Turn on the heartbeat timer if: // 1. heartbeating is on. // 2. we are *not* waiting for a heartbeat reply. // 3. the heartbeat timer is not already running. if (isHBOn && !mHeartbeatReplyFlag && hbTimer == null) { mHeartbeatTimer.set( sCoreExecutor.schedule(this::heartbeatTimeout, this, delay)); sLogger.trace( "{}: started heartbeat timer, deleay {} millis.", mRemoteAddress, delay); } // Turn on the heartbeat reply timer if: // 1. heartbeating is on. // 2. we are waiting for a heartbeat reply. // 3. the heartbeat reply timer is not already // running else if (isHBOn && mHeartbeatReplyFlag && hbReplyTimer == null) { delay = mHeartbeatReplyDelay.get(); mHeartbeatReplyTimer.set( sCoreExecutor.schedule(this::heartbeatReplyTimeout, this, delay)); sLogger.trace( "{}: started heartbeat reply timer, delay {}.", mRemoteAddress, delay); } } // end of startHeartbeatTimer() /** * Stops whichever heartbeat timer is running or both * (which should never be the case). */ protected final void stopHeartbeatTimer() { closeTimer(mHeartbeatTimer); closeTimer(mHeartbeatReplyTimer); } // end of stopHeartbeatTimer() /** * Logs connection-associated exception at {@code FINE} * level. * @param t The exception to be passed to the listeners. */ protected final void logError(final Throwable t) { sLogger.debug("{}: input processing error", mRemoteAddress, t); } // end of logError(Throwable) /** * Returns input reader for given message type and subject * used to decode inbound messages. * @param keyId unique message class identifier from remote * eBus application. * @param subject message key subject. * @param mt message type, used for de-serialization. * @param mh callback target method. * @param mn callback method name. * @return message input message decoder. */ protected MessageReader createReader(final int keyId, final String subject, final MessageType mt, final MethodHandle mh, final String mn) { return (new MessageReader(keyId, subject, mt, mh, mn)); } // end of createReader(...) /** * Initializes the input message readers for eBus system * messages. */ private void initializeReaders() { final SystemMessageType[] types = SystemMessageType.values(); final int size = types.length; int keyId; int index; MessageType mt; // Add non-multicast system message input readers to map. for (index = 0; index < size; ++index) { if (!types[index].isMulticast()) { keyId = types[index].keyId(); mt = (MessageType) DataType.findType( types[index].messageClass()); mInputReaders.put( keyId, createReader(keyId, ESystemMessage.SYSTEM_SUBJECT, mt, MESSAGE_CB[index], CB_METHOD_NAMES[index])); } } } // end of initializeReaders() /** * Creates heartbeat and heartbeat reply messages using the * configured byte order. * @param config contains buffer byte order. */ private void createHeartbeatData(final RemoteConnection config) { final ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); buffer.order(config.byteOrder()); // Create the heartbeat and heartbeat reply messages. buffer.putInt(HEARTBEAT) .flip() .get(mHeartbeatData) .clear(); buffer.putInt(HEARTBEAT_REPLY) .flip() .get(mHeartbeatReplyData) .clear(); } // end of createHeartbeatData(RemoteConnection) /** * Performs the actual work of closing an eBus connection * except the underlying socket connection. */ private void closeConnection() { stopHeartbeatTimer(); mReconnectFlag = false; stopReconnectTimer(); } // end of closeConnection() /** * Attempts to connect to the remote eBus. */ private void reconnect() { // Has anything changed while waiting to reconnect? if (mState.get() == ConnectState.RECONNECTING) { sLogger.debug("{}: attempting to reconnect.", mRemoteAddress); try { setState(ConnectState.CONNECTING); if (doOpen(mRemoteAddress, mBindAddress)) { connected(); } } catch (IOException ioex) { sLogger.debug("{}: connect attempt failed.", mRemoteAddress, ioex); // Connect attempt failed. Set timer and retry // connection later. startReconnectTimer(); } } // Yes, something did change: the eBus connection was // closed. } // end of reconnect() /** * If there is no reconnect timer task running, start one. */ private void startReconnectTimer() { // If reconnecting is turned off or this eBus // connection was in the process of closing, then // don't bother reconnecting. if (!mReconnectFlag || mState.get() == ConnectState.CLOSING) { // Mark this connection as closed and stop the // connection thread - but only after delivering // all remaining events. setState(ConnectState.CLOSED); } else { final Duration delay = mReconnectDelay.get(); setState(ConnectState.RECONNECTING); // Stop the current timer if running. closeTimer(mReconnectTimer); mReconnectTimer.set( sCoreExecutor.schedule(this::reconnectTimeout, this, delay)); sLogger.trace( "{}: started reconnect timer, delay {} millis.", mRemoteAddress, delay); } } // end of startReconnectTimer() /** * Starts the reconnect timer in response to a connection * pause. The timer delay is provided in this case rather * than using the configured reconnect time. * @param delay connection pause delay. */ private void startPauseTimer(final Duration delay) { setState(ConnectState.RECONNECTING); // Stop the current timer if running. closeTimer(mReconnectTimer); mReconnectTimer.set( sCoreExecutor.schedule(this::reconnectTimeout, this, delay)); sLogger.debug("{}: started pause timer, delay {}.", mRemoteAddress, delay); } // end of startPauseTimer(Duration) /** * Stop the reconnect timer if it is running. */ private void stopReconnectTimer() { closeTimer(mReconnectTimer); } // end of stopReconnectTimer() /** * Closes given timer. If {@code IETimer.close} throws an * exception, it is caught and ignored. * @param timer close this timer and set to {@code null}. */ private static void closeTimer(final AtomicReference timer) { final IETimer t = timer.getAndSet(null); if (t != null) { try { t.close(); } catch (Exception jex) {} } } // end of closeTimer(AtomicReference); //--------------------------------------------------------------- // Inner classes. // /** * This class combines two data points: the message type for * de-serializing a particular message class and the handle * for the method responsible for processing the message. * This class extracts messages from a buffer and forwards * them to the target. */ protected class MessageReader { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Constants. // /** * Header size sans message size is {@value} bytes. */ private static final int HEADER_SIZE = 8; // bytes. //------------------------------------------------------- // Locals. // /** * A unique message key identifier received from the * remote eBus application. */ protected final int mKeyId; /** * The message key subject. Needed to construct inbound * messages. */ private final String mSubject; /** * The message type for deserializing the message class. */ private final MessageType mMessageType; /** * Forward the message to this callback method. */ private final MethodHandle mCallback; /** * The callback method name (not included in the method * handle). */ private final String mMethodName; /** * Message header size sans message size's size. */ private final int mHeaderSize; //--------------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a new message reader for the given parameters. * @param keyId unique message class identifier from * remote eBus application. * @param subject message key subject. * @param mt message type, used for de-serialization. * @param mh callback target method. * @param mn callback method name. */ protected MessageReader(final int keyId, final String subject, final MessageType mt, final MethodHandle mh, final String mn, final int headerSize) { mKeyId = keyId; mSubject = subject; mMessageType = mt; mCallback = mh; mMethodName = mn; mHeaderSize = headerSize; } // end of MessageReader(..) /** * Creates a new message reader for the given parameters. * @param keyId unique message class identifier from * remote eBus application. * @param subject message key subject. * @param mt message type, used for de-serialization. * @param mh callback target method. * @param mn callback method name. */ private MessageReader(final int keyId, final String subject, final MessageType mt, final MethodHandle mh, final String mn) { this (keyId, subject, mt, mh, mn, HEADER_SIZE); } // end of MessageReader(...) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get methods. // /** * Returns the message key identifier. * @return message key identifier. */ public final int keyId() { return (mKeyId); } // end of keyId() // // end of Get methods. //------------------------------------------------------- /** * Returns the message header extracted from the given * buffer starting at the buffer's current position. * Note that the message size and message key identifier * have already been extracted from {@code buffer}. * @param buffer extract the message from this buffer. * @param address data came from this source address. * @return the extracted message header and message. * @throws BufferUnderflowException * if {@code buffer} contains an incomplete message. * @throws UnknownMessageException * if the de-serialized class identifier references an * unknown message class. * @throws InvalidMessageException * if the message construction fails. */ @SuppressWarnings({"java:S2696"}) public final EMessageHeader extractMessage(final ByteBuffer buffer, final SocketAddress address) { final EMessage msg; final EMessageHeader retval; // Skip over header bytes and extract message first. buffer.mark() .position(buffer.position() + mHeaderSize); // Need to pass in the message subject explicitly // because it is not longer serialized. Rather the // subject is inferred from the message key // identifier. msg = (EMessage) mMessageType.deserialize(mSubject, buffer); // Reset to header start position and extract the // header. buffer.reset(); retval = extractHeader(buffer, address, msg); ++mMsgInCount; ++sTotalInCount; if (sLogger.isTraceEnabled()) { sLogger.trace( "{}: handling {} message:\n From ID: {}\n To ID: {}\n Seq Num: {}\n{}", mRemoteAddress, retval.messageClass(), retval.fromFeedId(), retval.toFeedId(), retval.sequenceNumber(), msg); } else { sLogger.debug( "{}: handling {} message (from={}, to={}).", mRemoteAddress, retval.messageClass(), retval.fromFeedId(), retval.toFeedId()); } return (retval); } // end of extractMessage(ByteBuffer) /** * Returns eBus message header containing message key, * from, and to feed identifiers, sender's socket * address, and decode message. * @param buffer extract header from this buffer at * current location. * @param address data came from this source address. * @param msg decode message. * @return decoded eBus message header. */ protected EMessageHeader extractHeader(final ByteBuffer buffer, final SocketAddress address, final EMessage msg) { final int fromFeedId = buffer.getInt(); final int toFeedId = buffer.getInt(); return (new EMessageHeader(mKeyId, fromFeedId, toFeedId, address, msg)); } // end of extractHeader(...) /** * Forwards the given message header to the specified * eBus remote application instance. * @param header forward this message header to * {@code target}. * @param target the eBus remote application instance * to receive the inbound message. */ public final void forwardMessage(final EMessageHeader header, final ERemoteApp target) { try { if (sLogger.isTraceEnabled()) { sLogger.trace( "{}", new LazyString( () -> { final StringBuilder output = new StringBuilder(); final MethodType mt = mCallback.type(); final Class[] parms = mt.parameterArray(); final int nParms = parms.length; int index; String sep; output.append(mRemoteAddress) .append(" forward ") .append(header.messageKey()) .append(" to ") .append((mt.returnType()).getName()) .append(' ') .append(parms[0].getName()) .append('.') .append(mMethodName); for (index = 1, sep = "("; index < nParms; ++index, sep = ", ") { output.append(sep) .append(parms[index].getName()); } output.append(')'); return (output.toString()); })); } mCallback.invokeExact(target, header); } catch (Throwable tex) { sLogger.warn( "Error processing message header.", tex); } } // end of forwardMessage(EMessageHeader) } // end of class MessageReader /** * This class is responsible for serializing messages * directly to a given {@link AsyncChannel channel} * {@link ByteBuffer output buffer}. */ protected abstract static class AbstractMessageWriter { //----------------------------------------------------------- // Member data. // //------------------------------------------------------- // Statics. // /** * Logging subsystem interface. */ private static final Logger sSublogger = LoggerFactory.getLogger(AbstractMessageWriter.class); //------------------------------------------------------- // Locals. // /** * This message writer works for this eBus connection. */ protected final EAbstractConnection mConnection; /** * The message queue maximum allowed size. If the value * is ≤ zero, then the queue has no maximum. Once the * maximum is reached, new messages are rejected. */ protected final int mMaxSize; /** * Post messages here until it is time to serialize them * to the socket output buffer. Then serialize as many * as possible. */ protected final Deque mTransmitQueue; /** * Tracks the {@link #mTransmitQueue transmit queue} * approximate size. This is necessary * because {@code ConcurrentLinkedDeque.size()} is a * costly operation. */ protected final AtomicInteger mTransmitQueueSize; /** * Next outbound message will have this sequence number. * This number is incremented by one after outbound * message receipt is acknowledged. */ protected int mOutboundSequenceNumber; /** * Set to {@code true} when the parent eBus connection is * doing a slow close. */ protected volatile boolean mClosingFlag; /** * Count up the number of messages transmitted. Meant for * reporting purposes only. */ protected int mTransmitCount; /** * Count up the number of discarded application messages. * Meant for reporting purposes only. */ protected int mDiscardCount; //----------------------------------------------------------- // Member methods. // //------------------------------------------------------- // Constructors. // /** * Creates a message writer instance with the given * maximum outbound message queue size and eBus * connection. * @param config connection configuration. * @param conn this message writer works for this eBus * connection. */ protected AbstractMessageWriter(final AbstractConfig config, final EAbstractConnection conn) { mConnection = conn; mMaxSize = config.messageQueueSize(); mTransmitQueue = new ConcurrentLinkedDeque<>(); mTransmitQueueSize = new AtomicInteger(); mOutboundSequenceNumber = 0; mClosingFlag = false; mTransmitCount = 0; mDiscardCount = 0; } // end of AbstractMessageWriter(int, EConnection) // // end of Constructors. //------------------------------------------------------- //------------------------------------------------------- // Get methods. // /** * Returns the transmit queue's maximum size. If * {@link #DEFAULT_QUEUE_SIZE} is returned, then the * queue size is unlimited. If a value > * {@code DEFAULT_QUEUE_SIZE} is returned, then when the * queue reaches this limit and attempts to offer a new * message, the new message is discarded and a buffer * overflow exception thrown * @return the transmit queue maximum size. */ public final int maximumSize() { return (mMaxSize); } // end of maximumSize() /** * Returns the current message transmit count. Since this * method is not synchronized the returned value is * approximate. * @return the current message transmit count. */ public final int transmitCount() { return (mTransmitCount); } // end of transmitCount() /** * Returns the number of discarded user messages. Since * this method is not synchronized the value is * approximate. * @return the number of discarded user messages. */ public final int discardCount() { return (mDiscardCount); } // end of discardCount() /** * Returns the transmit queue current size. This * is an approximate value and should not be * used to determine if the queue is empty. * @return approximate transmit queue size. */ public final int transmitQueueSize() { return (mTransmitQueueSize.get()); } // end of transmitQueueSize() /** * Returns {@code true} if this message writer has * outbound messages pending and {@code false} otherwise. * @return {@code true} if there are messages waiting to * be transmitted. */ public final boolean hasMessages() { return (!mTransmitQueue.isEmpty()); } // end of hasMessages() /** * Returns current outbound sequence number. * @return outbound sequence number. */ public int outboundSequenceNumber() { return (mOutboundSequenceNumber); } // end of outboundSequenceNumber() // // end of Get methods. //------------------------------------------------------- /** * Queues the message header. If the maximum queue * size is reached, then the header is dropped and a * buffer overflow exception is thrown. *

* If the header contains a * {@link net.sf.eBus.messages.ESystemMessage system message}, * then the message is queued despite exceeding the * maximum queue size because system message must get * through. *

*

* Returns {@code true} if this is the first message on * the queue. This means the caller is clear to send the * queued message since no other thread is currently * accessing the channel. *

* @param h enqueue this message header. * @return {@code true} if this message writer should be * posted to the socket. * @exception BufferOverflowException * if the transmit queue maximum size is reached. */ public final boolean post(final EMessageHeader h) { // Increment the queue size if-and-only-if the // message type is *not* system. final EMessage.MessageType msgType = h.messageType(); final int queueSize = (msgType == EMessage.MessageType.SYSTEM ? (mTransmitQueueSize.get() + 1) : mTransmitQueueSize.incrementAndGet()); final boolean retcode; // If the queue is *not* of unlimited size and the // queue has reached that limit, then let the caller // know about this by throwing an overflow exception. // Put always add system messages. if (mMaxSize > DEFAULT_QUEUE_SIZE && queueSize >= mMaxSize) { final BufferOverflowException bufex = new BufferOverflowException(); final IllegalStateException statex = new IllegalStateException( String.format( "message queue maximum reached (%,d)", mMaxSize)); bufex.initCause(statex); sSublogger.debug( "{} queue: queue maximum reached ({}).", mConnection.remoteSocketAddress(), mMaxSize); ++mDiscardCount; // HEAVE! throw (bufex); } // Place header on message queue. retcode = enqueue(h, queueSize); sSublogger.debug( "{} queue: added message (size={}, transmited={}, discarded={}).", mConnection.remoteSocketAddress(), queueSize, mTransmitCount, mDiscardCount); // Pass this message writer to the socket if this is // the first message on the queue and the socket // is not overflowing. return (retcode); } // end of post(EMessageHeader) /** * Returns {@code true} if this is the only message * on the transmit queue. Places message at queue's end. * @param header enqueue this message. * @param queueSize message queue size as a result of * adding message. * @return {@code true} if clear to transmit. */ protected boolean enqueue(final EMessageHeader header, final int queueSize) { mTransmitQueue.offer(header); // Pass this message writer to the socket if this is // the first message on the queue and the socket // is not overflowing. return (queueSize == 1); } // end of enqueue(EMessageHeader, int) /** * Sets the closing flag to {@code true}. This is the * only possible setting because, once set, it cannot be * changed back. */ protected final void setClosing() { mClosingFlag = true; } // end of setClosing() /** * Closes this message writer by discarding all unsent * messages. */ protected void closed() { mTransmitQueue.clear(); mTransmitQueueSize.set(0); } // end of closed() } // end of class AbstractMessageWriter } // end of class EAbstractConnection




© 2015 - 2024 Weber Informatics LLC | Privacy Policy