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

com.oracle.coherence.common.internal.net.socketbus.SocketMessageBus Maven / Gradle / Ivy

There is a newer version: 24.03
Show newest version
/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */
package com.oracle.coherence.common.internal.net.socketbus;


import com.oracle.coherence.common.base.Disposable;
import com.oracle.coherence.common.internal.net.ProtocolIdentifiers;
import com.oracle.coherence.common.internal.util.HeapDump;
import com.oracle.coherence.common.io.Buffers;
import com.oracle.coherence.common.net.exabus.MessageBus;
import com.oracle.coherence.common.net.exabus.EndPoint;
import com.oracle.coherence.common.net.exabus.Event;
import com.oracle.coherence.common.net.exabus.util.UrlEndPoint;
import com.oracle.coherence.common.net.exabus.util.SimpleEvent;
import com.oracle.coherence.common.io.BufferSequence;
import com.oracle.coherence.common.io.BufferManager;
import com.oracle.coherence.common.io.BufferSequenceInputStream;
import com.oracle.coherence.common.util.MemorySize;

import java.io.IOException;
import java.io.DataInput;
import java.nio.ByteBuffer;

import java.util.Arrays;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import java.util.LinkedList;
import java.util.zip.CRC32;

import java.net.SocketException;

import java.util.logging.Level;


/**
 * SocketMessageBus is a reliable MessageBus implementation based
 * upon sockets.
 *
 * @author mf  2010.12.03
 */
public class SocketMessageBus
        extends BufferedSocketBus
        implements MessageBus
    {
    // ----- constructors ---------------------------------------------------

    /**
     * Construct a SocketMessageBus.
     *
     *
     * @param driver     the socket driver
     * @param pointLocal the local endpoint
     *
     * @throws IOException if an I/O error occurs
     */
    public SocketMessageBus(SocketBusDriver driver, UrlEndPoint pointLocal)
            throws IOException
        {
        super(driver, pointLocal);
        }


    // ----- AbstractSocketBus methods --------------------------------------

    /**
     * {@inheritDoc}
     */
    protected String getProtocolName()
        {
        return getSocketDriver().getDependencies().getMessageBusProtocol();
        }

    /**
     * {@inheritDoc}
     */
    protected int getProtocolIdentifier()
        {
        return ProtocolIdentifiers.SOCKET_MESSAGE_BUS;
        }


    // ----- MessageBus methods ---------------------------------------------

    /**
     * {@inheritDoc}
     */
    public void send(EndPoint peer, BufferSequence bufseq, Object receipt)
        {
        if (bufseq == null)
            {
            throw new NullPointerException("Null BufferSequence for message:  " + receipt);
            }

        MessageConnection conn = (MessageConnection) ensureConnection(peer);
        if (conn.getProtocolVersion() < 0 && conn.deferSend(bufseq, receipt))
            {
            // we haven't finished handshaking yet
            return;
            }

        AtomicInteger cWriters = conn.m_cWritersWaiting;

        cWriters.getAndIncrement(); // track threads waiting to use the connection
        synchronized (conn)
            {
            cWriters.getAndDecrement();

            conn.ensureValid().evaluateAutoFlush(conn.isFlushInProgress(), conn.isFlushRequired(),
                    conn.send(bufseq, receipt));
            }
        }


    // ----- MessageConnection interface ------------------------------------

    /**
     * {@inheritDoc}
     */
    @Override
    protected Connection makeConnection(UrlEndPoint peer)
        {
        return new MessageConnection(peer);
        }

    /**
     * MessageConnection implements a reliable message connection,
     */
    public class MessageConnection
        extends BufferedConnection
        {
        /**
         * Create a MessageConnection for the specified peer.
         *
         * @param peer  the peer
         */
        public MessageConnection(UrlEndPoint peer)
            {
            super(peer);
            }

        /**
         * Return the threshold at which to stop reading additional data from
         * the socket, based on the number of undisposed bytes in the event
         * queue.
         *
         * @return  the threshold in bytes
         */
        protected long getReadThrottleThreshold()
            {
            long cb = m_cbReadThreshold;

            if (cb <= 0)
                {
                try
                    {
                    // threshold is a multiple of the underlying buffer size
                    // the assumption is that any given socket read could
                    // return a full buffers worth of data, and that should
                    // not be considered to be "excessive", but once we've
                    // fallen a number of buffers worth behind it is time to
                    // throttle.  While this could be made a configuration
                    // setting, it is not necessary since the underlying
                    // buffer size is already configurable and will directly
                    // influence this setting.
                    m_cbReadThreshold = cb = getReceiveBufferSize() * 8;
                    }
                catch (SocketException e) {}

                if (cb <= 0)
                    {
                    // not yet connected
                    cb = 64 * 1024;
                    }
                }

            return cb;
            }

        /**
         * {@inheritDoc}
         */
        protected int processReads(boolean fReady)
                throws IOException
            {
            if (f_fBacklogLocal.get() && m_cbEventQueue.get() > getReadThrottleThreshold())
                {
                // if a backlog has been declared *and* we've exceeded the read threshold then stop reading; once
                // the backlog is cleared (BACKLOG_EXCESSIVE event consumed) then we'll be awoken so we can continue.
                // Note if we only checked f_fBacklogLocal then we'd end up hurting performance as at the time the backlog
                // was cleared we'd have no messages ready to be processed.  If we only checked m_cbEventQueue then we'd
                // risk stalling indefinitely as we could cross the limit temporarily and uncross it before a backlog
                // was ever declared.  Thus we check both.
                return 0;
                }
            else if (fReady)
                {
                ReadBatch batch = m_readBatch;
                if (batch == null)
                    {
                    batch = m_readBatch = new ReadBatch();
                    batch.m_cbRequired = getMessageHeaderSize();
                    }

                batch.read();

                if (batch.m_fHeader && batch.m_cbReadable == 0)
                    {
                    // we've fully read the current message and there was no partial message
                    // in the buffer, so it could potentially be awhile before we use this
                    // connection again.  Avoid needlessly holding onto the read buffer memory.
                    // Note, in a high throughput case it is likely that we'll have read at least some
                    // of the next message and thus avoid the cost associated with allocation/disposal

                    batch.dispose();
                    m_readBatch = null;
                    return OP_READ;
                    }
                else
                    {
                    // we've at least partially read the next message, assume the remainder will be here
                    // soon
                    return OP_READ | OP_EAGER;
                    }
                }

            return OP_READ;
            }

        @Override
        public void dispose()
            {
            ReadBatch batch = m_readBatch;
            if (batch != null)
                {
                m_readBatch = null;
                batch.dispose();
                }
            ByteBuffer bufferHdr = m_bufferMsgHdr;
            if (bufferHdr != null)
                {
                getSocketDriver().getDependencies().getBufferManager().release(bufferHdr);
                m_bufferMsgHdr = null;
                }

            super.dispose();
            }

        /**
         * Schedule a send of the specified BufferSequence.
         *
         * @param bufseq   the BufferSequence
         * @param receipt  the optional receipt
         *
         * @return the length of the current WriteBatch
         */
        public long send(BufferSequence bufseq, Object receipt)
            {
            long cbMsg    = bufseq.getLength();
            int  nProt    = getProtocolVersion();
            int  cbHeader = getMessageHeaderSize();

            if (cbMsg < 0 || (nProt < 5 && cbMsg > Integer.MAX_VALUE))
                {
                throw new UnsupportedOperationException(
                        "unsupported message size " + cbMsg);
                }

            WriteBatch batch = m_batchWriteUnflushed;
            if (batch == null)
                {
                batch = m_batchWriteUnflushed = new WriteBatch();
                }

            if (m_bufferMsgHdr == null)
                {
                m_bufferMsgHdr = getSocketDriver().getDependencies()
                        .getBufferManager().acquire(cbHeader * 1024); //4KB or 24KB for version 5

                int cbCap = m_bufferMsgHdr.capacity();
                m_bufferMsgHdr.limit(cbCap - (cbCap % cbHeader));
                }

            // prepend the message data with it's header
            ByteBuffer bufferMsgHdr = m_bufferMsgHdr;
            if (bufferMsgHdr.remaining() > cbHeader)
                {
                ByteBuffer buffHeader = bufferMsgHdr.slice();
                buffHeader.limit(buffHeader.position() + cbHeader);
                bufferMsgHdr.position(bufferMsgHdr.position() + cbHeader);

                batch.append(buffHeader, /*fRecycle*/ false, bufseq, receipt);
                }
            else // bufferMsgHdr.remaining() == getMessageHeaderSize()
                {
                // write last chunk in append and recycle
                batch.append(bufferMsgHdr, /*fRecycle*/ true, bufseq, receipt);
                m_bufferMsgHdr = null;
                }

            ++m_cMsgUserOut;
            if (receipt == null)
                {
                ++m_cReceiptsNull;
                }
            return batch.getLength();
            }

        @Override
        protected void populateMessageHeader(ByteBuffer bufHead, ByteBuffer[] aBuffer, int of, int cBuffers, long cbBuffer)
            {
            int nProt = getProtocolVersion();
            int nPos  = bufHead.position();
            if (nProt > 4)
                {
                CRC32 crc32    = f_crcTx;
                int   lCrcBody = 0;
                int   nLimit   = bufHead.limit();

                // write message length
                bufHead.putLong(nPos, cbBuffer);
                nPos += 8;

                // write body CRC
                if (crc32 != null)
                    {
                    crc32.reset();
                    lCrcBody = Buffers.updateCrc(crc32, aBuffer, of, cbBuffer);
                    lCrcBody = lCrcBody == 0 ? 1 : lCrcBody;
                    }

                bufHead.putInt(nPos, lCrcBody);

                nPos += 4;

                // write header CRC
                int lCrcHeader = 0;
                if (crc32 != null)
                    {
                    crc32.reset();
                    bufHead.limit(nPos); // set limit for crc calc
                    lCrcHeader = Buffers.updateCrc(crc32, bufHead);
                    lCrcHeader = lCrcHeader == 0 ? 1 : lCrcHeader;
                    bufHead.limit(nLimit); // reset limit for crc write
                    }

                bufHead.putInt(nPos, lCrcHeader);
                }
            else
                {
                bufHead.putInt(nPos, (int) cbBuffer);
                }
            }

        /**
         * Return message header size based on protocol version.
         *
         * @return message header size
         */
        public int getMessageHeaderSize()
            {
            int nProt = getProtocolVersion();
            if (nProt > 4)
                {
                return 16;  // 8B body length + 4B body crc, 4B header crc
                }
            else if (nProt >= 0)
                {
                return 4; // 4B body length
                }
            else
                {
                throw new IllegalStateException("connection is not ready!");
                }
            }

        @Override
        protected int getReceiptSize()
            {
            int nProt = getProtocolVersion();

            return nProt > 4 ? 25 : 13;  // header + body (9 bytes)
            }

        @Override
        protected void setProtocolVersion(int nProt)
            {
            // drain the requests sent before this point
            synchronized (this)
                {
                super.setProtocolVersion(nProt);

                LinkedList> queue = m_queuePreNegotiate;
                if (queue != null)
                    {
                    if (m_fBacklog)
                        {
                        m_fBacklog = false;
                        emitEvent(new SimpleEvent(Event.Type.BACKLOG_NORMAL, getPeer()));
                        }

                    Pair pair = null;
                    try
                        {
                        while (true)
                            {
                            pair = queue.poll();
                            if (pair == null)
                                {
                                break;
                                }
                            send(pair.getKey(), pair.getValue());
                            }
                        flush();

                        m_queuePreNegotiate = null;
                        }
                    catch (Throwable e)
                        {
                        queue.addFirst(pair);
                        scheduleDisconnect(e);
                        }
                    }
                }
            }

        @Override
        public void drainReceipts()
            {
            synchronized (this)
                {
                LinkedList> queue = m_queuePreNegotiate;
                m_queuePreNegotiate = null;
                if (queue != null)
                    {
                    for (Pair pair : queue)
                        {
                        Object oReceipt = pair.getValue();
                        if (oReceipt != null)
                            {
                            addEvent(new SimpleEvent(Event.Type.RECEIPT, getPeer(), oReceipt));
                            }
                        }

                    // need to change backlog status, see connection.deferSend
                    if (m_fBacklog)
                        {
                        m_fBacklog = false;
                        emitEvent(new SimpleEvent(Event.Type.BACKLOG_NORMAL, getPeer()));
                        }
                    }

                super.drainReceipts();
                }
            }

        @Override
        public String toString()
            {
            ReadBatch batch            = m_readBatch;
            long      cReceiptsEmitted = m_cReceiptsEmitted;
            long      cReceiptsNull    = m_cReceiptsNull;
            return super.toString() + ", bufferedIn(" + (batch == null ? "" : batch) + "), " +
                    "msgs(in=" + m_cMsgUserIn +
                    ", out=" + m_cReceiptsEmitted + (cReceiptsNull == 0 ? "" : "[" + (cReceiptsEmitted + cReceiptsNull) + "]") +
                    "/" + m_cMsgUserOut + ")";
            }

        // ----- inner class: ReadBatch -------------------------------------

        /**
         * ReadBatch handles the reading and processing of the inbound byte
         * stream.
         */
        public class ReadBatch
                extends AtomicReference
                implements Disposable, SharedBuffer.Disposer
            {
            /**
             * Ensure that m_aBuffer has sufficient writable capacity.
             *
             * @param cb  the required write capacity
             *
             * @return the ensured buffer array
             */
            public ByteBuffer[] ensureCapacity(long cb)
                {
                BufferManager manager         = getSocketDriver().getDependencies().getBufferManager();
                ByteBuffer[]  aBuffer         = m_aBuffer;
                int           ofReadable      = m_ofReadable;
                int           ofWritable      = m_ofWritable;
                long          cbWritable      = m_cbWritable;
                int           cBufferWritable = m_cBufferWritable;
                int           cBuffer         = aBuffer.length;
                long          cbAlloc         = cb - cbWritable;
                long          cbMin           = Math.max(getPacketSize(), 16*1024); // minimum read buffer size, used to reduce the number of read system calls

                while (cbAlloc > 0)
                    {
                    int ofEnd = ofWritable + cBufferWritable;
                    int ofAlloc;
                    if (ofEnd < cBuffer && aBuffer[ofEnd] == null)
                        {
                        // next block has not been allocated
                        ofAlloc = ofEnd;
                        }
                    else if (ofEnd + 1 < cBuffer)
                        {
                        // there are free slots after the write tail
                        ofAlloc = ofEnd + 1;
                        }
                    else if (ofReadable > 0)
                        {
                        // there are free slots at the head, compact and allocate
                        // at the tail; shifting everything back by ofReadable
                        ofAlloc = ofEnd - ofReadable;
                        System.arraycopy(aBuffer, ofReadable, aBuffer, 0, ofAlloc);
                        Arrays.fill(aBuffer, ofAlloc, ofAlloc + ofReadable, null);
                        ofWritable -= ofReadable;
                        ofEnd      -= ofReadable;
                        ofReadable  = 0;
                        }
                    else
                        {
                        // there are no free slots, new array is required
                        ByteBuffer[] aBufferNew = new ByteBuffer[cBuffer * 2];
                        System.arraycopy(aBuffer, 0, aBufferNew, 0, cBuffer);

                        aBuffer = aBufferNew;
                        ofAlloc = cBuffer;
                        cBuffer = aBuffer.length;
                        }

                    // attempt to allocate at least the required amount plus a bit of extra
                    long cbStop = Math.max(cbMin, cbWritable + cbAlloc + getMessageHeaderSize());
                    for (int i = ofAlloc; i < cBuffer && cbWritable < cbStop; ++i)
                        {
                        ByteBuffer buff = getAndSet(null);
                        if (buff == null)
                            {
                            buff = manager.acquirePref((int) Math.min(Integer.MAX_VALUE, cbStop - cbWritable));
                            }
                        buff.clear().mark();
                        int cbBuff  = buff.remaining();
                        cbAlloc    -= Math.min(cbBuff, cbAlloc);
                        cbWritable += cbBuff;
                        ++cBufferWritable;
                        aBuffer[i] = buff;
                        }
                    }

                m_aBuffer         = aBuffer;
                m_ofReadable      = ofReadable;
                m_ofWritable      = ofWritable;
                m_cbWritable      = cbWritable;
                m_cBufferWritable = cBufferWritable;

                return aBuffer;
                }

            /**
             * {@inheritDoc}
             */
            @Override
            public void dispose()
                {
                // we must dispose of buffers in m_aBuffer which have not
                // already been handed out as messages, specifically everything
                // after m_ofReadable has to be released

                int                         of         = m_ofReadable;
                SharedBuffer                buffShared = m_bufferShared;
                ByteBuffer[]                aBuffer    = m_aBuffer;
                ByteBuffer                  buffer0    = aBuffer[of];
                if (buffShared != null && buffShared.get() == buffer0)
                    {
                    buffShared.dispose();
                    ++of;
                    }
                BufferManager manager = getSocketDriver().getDependencies().getBufferManager();
                for (int c = m_ofWritable + m_cBufferWritable; of < c; ++of)
                    {
                    manager.release(aBuffer[of]);
                    }
                ByteBuffer buf = getAndSet(Buffers.getEmptyBuffer());
                if (buf != null)
                    {
                    manager.release(buf);
                    }
                }


            @Override
            public void dispose(ByteBuffer buffer)
                {
                if (!compareAndSet(null, buffer))
                    {
                    getSocketDriver().getDependencies().getBufferManager().release(buffer);
                    }
                }

            /**
             * Process reads.
             *
             * @throws IOException on an I/O error
             */
            public void read()
                    throws IOException
                {
                long         cbAlloc = Math.abs(m_cbRequired) - m_cbWritable;
                ByteBuffer[] aBuffer = cbAlloc > 0 ? ensureCapacity(cbAlloc) : m_aBuffer;
                int          of      = m_ofWritable;
                int          cBuffer = m_cBufferWritable;

                long cb = MessageConnection.this.read(aBuffer, of, cBuffer);
                if (cb > 0)
                    {
                    for (; cBuffer > 0 && !aBuffer[of].hasRemaining(); ++of, --cBuffer)
                        {
                        aBuffer[of].reset(); // prepare for reading
                        }

                    m_ofWritable       = of;
                    m_cBufferWritable  = cBuffer;
                    m_cbWritable      -= cb;
                    long cbReady = m_cbReadable += cb;

                    if (cbReady >= Math.abs(m_cbRequired))
                        {
                        if (cBuffer > 0 && aBuffer[of].position() > 0)
                            {
                            ByteBuffer buffLast  = aBuffer[of];
                            int        iPosWrite = buffLast.position();
                            // the last buffer is partially readable
                            try
                                {
                                buffLast.limit(iPosWrite).reset();
                                onReady();
                                }
                            finally
                                {
                                // by definition buffLast still has some space
                                // left to write into, reset the position data;
                                // any message referncing this buffer would be
                                // doing so through a slice of a SharedBuffer
                                buffLast.mark();
                                buffLast.limit(buffLast.capacity()).position(iPosWrite);
                                }
                            }
                        else
                            {
                            onReady();
                            }
                        }
                    }
                else if (cb < 0)
                    {
                    migrate(new IOException("input shutdown"));
                    }
                }


            /**
             * Process the next message(s) from the already read data.
             *
             * @throws IOException on an I/O error
             */
            public void onReady()
                    throws IOException
                {
                boolean          fFlush      = false;
                long             cbReadable  = m_cbReadable;
                long             cbRequired  = m_cbRequired;
                boolean          fHeader     = m_fHeader;
                final AtomicLong cbEvents    = m_cbEventQueue;
                long             cMsgIn      = m_cMsgIn;
                long             cMsgInUser  = m_cMsgUserIn;
                long             cMsgInSkip  = m_cMsgInSkip;
                SharedBuffer     buffShared0 = m_bufferShared;
                long             lCrcBody    = m_lCrcBodyNext;
                int              cbHeader    = getMessageHeaderSize();
                int              nProt       = getProtocolVersion();
                CRC32            crc32       = f_crcRx;

                ByteBuffer       buffer0;

                if (buffShared0 == null)
                    {
                    buffShared0 = m_bufferShared = new SharedBuffer(buffer0 = m_aBuffer[m_ofReadable], this);
                    }
                else
                    {
                    buffer0 = buffShared0.get();
                    }

                try
                    {
                    // process all available headers and messages
                    while (cbReadable >= Math.abs(cbRequired))
                        {
                        if (fHeader)
                            {
                            // ofReadable is left as is, the buffers will be part
                            // of the message, though not accessible
                            fHeader     = false;
                            cbReadable -= cbHeader;

                            int          of    = m_ofReadable;
                            ByteBuffer[] aBuff = m_aBuffer;

                            long lCrcHeaderRx;  // header CRC received
                            long lCrcHeaderCalc = 0; // recalculated header CRC
                            if (nProt > 4)
                                {
                                if (crc32 != null)
                                    {
                                    crc32.reset();
                                    lCrcHeaderCalc = Buffers.updateCrc(crc32, aBuff, of, cbHeader - 4);
                                    lCrcHeaderCalc = lCrcHeaderCalc == 0 ? 1 : lCrcHeaderCalc;
                                    }

                                cbRequired     = Buffers.getLong(aBuff, of);
                                lCrcBody       = Buffers.getInt(aBuff, of);
                                lCrcHeaderRx   = Buffers.getInt(aBuff, of);

                                if (lCrcHeaderCalc != lCrcHeaderRx && lCrcHeaderRx != 0 && lCrcHeaderCalc != 0)
                                    {
                                    // CRC does not match, migrate connection
                                    throw new IOException("incorrect CRC, corrupted header buffer; CRC read: " +
                                            lCrcHeaderRx + " CRC re-calculated: " + lCrcHeaderCalc);
                                    }
                                }
                            else
                                {
                                cbRequired = Buffers.getInt(aBuff, of);
                                }
                            }
                        else // message body
                            {
                            long cbMsg = Math.abs(cbRequired);

                            Event event;
                            long lCrcBodyCalc = 0;
                            if (buffer0.remaining() >= cbMsg) // highly optimized single buffer message
                                {
                                if (crc32 != null && lCrcBody != 0)
                                    {
                                    crc32.reset();
                                    lCrcBodyCalc = Buffers.updateCrc(crc32, buffer0, cbMsg);
                                    lCrcBodyCalc = lCrcBodyCalc == 0 ? 1 : lCrcBodyCalc;
                                    if (lCrcBodyCalc != lCrcBody)
                                        {
                                        // checksum does not match, migrate connection
                                        throw new IOException("incorrect CRC, corrupted message buffer; CRC read: " +
                                                lCrcBody + " CRC re-calculated: " + lCrcBodyCalc);
                                        }
                                    }

                                int nPos = buffer0.position();
                                event = new SingleBufferMessageEvent(MessageConnection.this,
                                            buffShared0.attach(), nPos, (int) cbMsg);
                                buffer0.position(nPos + (int) cbMsg);
                                }
                            else // multi-buffer message
                                {
                                if (crc32 != null && lCrcBody != 0)
                                    {
                                    crc32.reset();
                                    lCrcBodyCalc = Buffers.updateCrc(crc32, m_aBuffer, m_ofReadable, cbMsg);
                                    lCrcBodyCalc = lCrcBodyCalc == 0 ? 1 : lCrcBodyCalc;
                                    if (lCrcBodyCalc != lCrcBody)
                                        {
                                        //  checksum does not match, migrate connection
                                        throw new IOException("incorrect checksum, corrupted message buffer");
                                        }
                                    }

                                event = makeMultiBufferMessageEvent(cbMsg);


                                // re-read updated fields
                                buffShared0 = m_bufferShared;
                                buffer0     = buffShared0.get();
                                }

                            cbEvents.addAndGet(cbMsg);

                            if (cMsgInSkip == 0)
                                {
                                fFlush = true;

                                if (cbRequired >= 0)
                                    {
                                    ++cMsgIn;
                                    ++cMsgInUser;
                                    addEvent(event);
                                    }
                                else if (onControlMessage(event))
                                    {
                                    ++cMsgIn;
                                    }
                                else // onControl returned false; thus it was a SYNC message, re-read skip count
                                    {
                                    cMsgInSkip = m_cMsgInSkip;
                                    }
                                }
                            else // we have post migration messages to skip
                                {
                                --cMsgInSkip;
                                getLogger().log(Level.FINER, "{0} skipping" + (cbRequired < 0 ? " control " : " ")
                                                + "message of {1} bytes from {2} after migration, {3} remain to be skipped on {4}",
                                        new Object[]{getLocalEndPoint(), cbMsg, getPeer(), cMsgInSkip, MessageConnection.this});

                                if (cMsgInSkip == 0)
                                    {
                                    getLogger().log(Level.FINE, "{0} resuming migrated connection with {1}",
                                            new Object[]{getLocalEndPoint(), MessageConnection.this});
                                    }
                                event.dispose();
                                }

                            fHeader     = true;
                            cbReadable -= cbMsg;
                            cbRequired  = cbHeader;
                            }
                        }
                    }
                finally
                    {
                    m_cMsgUserIn = cMsgInUser;
                    m_cMsgIn       = cMsgIn;
                    m_cMsgInSkip   = cMsgInSkip;
                    m_cbReadable   = cbReadable;
                    m_cbRequired   = cbRequired;
                    m_fHeader      = fHeader;
                    m_lCrcBodyNext = lCrcBody;

                    if (fFlush)
                        {
                        if (cbEvents.get() > (getReadThrottleThreshold() * 2) / 3)
                            {
                            // At 2/3 of the threshold we consider ourselves "backlogged",
                            // we will add a task to eventLast to end this backlog
                            // when the event is consumed. Note that we will stop
                            // adding to the backlog when we hit the threshold, which
                            // means that the event queue should have another 1/3 worth
                            // of content when we end the backlog, hopefully prevent
                            // the queues from being bursty.

                            // logic is factored out for hot-spotting benefit
                            issueLocalBacklog();
                            }

                        flushEvents();
                        }
                    }
                }

            /**
             * Extract a multi-buffer message from an array of ByteBuffers.
             *
             * @param cbMsg  the message length
             *
             * @return the message event
             */
            protected MultiBufferMessageEvent makeMultiBufferMessageEvent(long cbMsg)
                {
                ByteBuffer[] aBuffer    = m_aBuffer;
                SharedBuffer buffShared = m_bufferShared;
                int          ofReadable = m_ofReadable;
                long         cbEnd      = cbMsg;
                int          ofEnd      = ofReadable;

                // find the end buffer
                for (int cbBuf = aBuffer[ofEnd].remaining();
                     cbEnd > cbBuf;
                     cbBuf = aBuffer[++ofEnd].remaining())
                    {
                    cbEnd -= cbBuf;
                    }

                int cBufferMsg = (ofEnd - ofReadable) + 1;

                ByteBuffer buffer0 = aBuffer[ofReadable];
                ByteBuffer bufferN = aBuffer[ofEnd];

                int iLimitN = bufferN.limit();
                bufferN.limit(bufferN.position() + (int) cbEnd);

                ByteBuffer[] aBufferMsg = new ByteBuffer[cBufferMsg];

                // copy middle buffers
                System.arraycopy(aBuffer, ofReadable + 1, aBufferMsg, 1, cBufferMsg - 2);

                // no slice required here, this is the last
                // message to use this buffer
                aBufferMsg[0] = buffer0;
                SharedBuffer buffShared0 = buffShared;

                // replace buffShared with new SharedBuffer around bufferN
                m_bufferShared = buffShared = new SharedBuffer(bufferN, this);

                aBufferMsg[cBufferMsg - 1] = bufferN.slice();

                MultiBufferMessageEvent event = new MultiBufferMessageEvent(
                        MessageConnection.this,
                        getSocketDriver().getDependencies().getBufferManager(),
                        aBufferMsg, 0, cBufferMsg, cbMsg,
                        buffShared0.attach(), buffShared.attach());
                buffShared0.dispose();

                // update the read position restore the limit of the
                // last buffer (for next message)
                bufferN.position(bufferN.limit()).limit(iLimitN);

                for (; ofReadable < ofEnd; ++ofReadable)
                    {
                    aBuffer[ofReadable] = null; // allow for GC
                    }

                m_ofReadable = ofReadable;

                return event;
                }

            /**
             * Handle a control event.
             *
             * @param event  the control event
             *
             * @return true if this should count as a received message
             *
             * @throws IOException on an I/O error
             */
            public boolean onControlMessage(Event event)
                    throws IOException
                {
                try
                    {
                    if (event.getType() != Event.Type.MESSAGE)
                        {
                        throw new IllegalStateException(
                                "unexpected control event: " + event);
                        }

                    DataInput in = new BufferSequenceInputStream((BufferSequence)
                                    event.getContent());
                    byte nType = in.readByte();
                    switch (nType)
                        {
                        case MSG_RECEIPT:
                            processReceipt(in);
                            return true;

                        case MSG_SYNC:
                            processSync(in);
                            return false; // SYNC is not a resendable message and as such does not contribute to the message count

                        default:
                            String sDump = HeapDump.dumpHeapForBug("28240730");
                            getLogger().log(makeRecord(Level.WARNING,
                                    "{0} received a corrupt message from {1}; collected {2} for analysis",
                                    getLocalEndPoint(), getPeer(), sDump));

                            throw new IllegalStateException("unexpected control message type: " + nType);
                        }

                    }
                finally
                    {
                    event.dispose();
                    }
                }

            @Override
            public String toString()
                {
                return "ready=" + new MemorySize(m_cbReadable) +
                        ", pending=" + new MemorySize(Math.abs(m_cbRequired)) +
                        ", free=" + new MemorySize(m_cbWritable);
                }


            // ----- data members ---------------------------------------

            /**
             * The ByteBuffer array.
             */
            protected ByteBuffer[] m_aBuffer = new ByteBuffer[2];

            /**
             * The offset of the first buffer to read into (i.e. write to).
             */
            protected int m_ofWritable;

            /**
             * The number of unfilled buffers remaining, starting at m_ofWritable.
             */
            protected int m_cBufferWritable;

            /**
             * The number of writable bytes.
             */
            protected long m_cbWritable;

            /**
             * The array offset of the first readable buffer.
             */
            protected int m_ofReadable;

            /**
             * The number of readable bytes.
             */
            protected long m_cbReadable;

            /**
             * The number of bytes required for the next header or message.
             * 

* The value will be negative for control messages. */ protected long m_cbRequired; /** * The CRC for next message. */ protected long m_lCrcBodyNext; /** * True if waiting for a message header, false if waiting for * a message. */ protected boolean m_fHeader = true; /** * Shared buffer protecting remainder of buffer at m_ofReadable. */ protected SharedBuffer m_bufferShared; } // ----- helpers ------------------------------------------------ /** * Called as part of disposing a MessageEvent. * * @param bufseq the pre-disposed message */ public void onMessageDispose(BufferSequence bufseq) { AtomicLong atomicCb = m_cbEventQueue; long cbSeq = bufseq.getLength(); long cbCur; // Decrement the outstanding byte count by the length of the disposed // sequence. Ensure that the count doesn't go negative, which could // happen do to artificially zeroing out the count as part of the // inbound flow-control processing (COH-5379), see issueLocalBacklog do { cbCur = atomicCb.get(); } while (!atomicCb.compareAndSet(cbCur, Math.max (0, cbCur - cbSeq))); } @Override public void onMigration() { super.onMigration(); ReadBatch batchRead = m_readBatch; if (batchRead != null) { // discard any partially read messages, they will be resent in their entirety on the new connection getLogger().log(Level.FINER, "{0} discarding partial message from {1} consisting of {2} out of {3} bytes on {4}", new Object[] {getLocalEndPoint(), getPeer(), batchRead.m_cbReadable, batchRead.m_cbRequired, MessageConnection.this}); batchRead.dispose(); m_readBatch = null; } } /** * Defer send if handshake is not complete. * * @param bufSeq the message * @param receipt the optional receipt * * @return true if the send has been deferred */ protected boolean deferSend(BufferSequence bufSeq, Object receipt) { synchronized (this) { ensureValid(); if (getProtocolVersion() < 0) { LinkedList> queue = m_queuePreNegotiate; if (queue == null) { queue = m_queuePreNegotiate = new LinkedList<>(); // emit the BACKLOG_EXCESSIVE event to prevent OOM in case of delayed handshake invoke(() -> { synchronized (this) { if (!m_fBacklog && getProtocolVersion() < 0) { m_fBacklog = true; emitEvent(new SimpleEvent( Event.Type.BACKLOG_EXCESSIVE, getPeer())); } } }); } queue.add(new Pair<>(bufSeq, receipt)); if (m_state == ConnectionState.DEFUNCT) { invoke(this::drainReceipts); } return true; } } return false; } // ----- data members ------------------------------------------- /** * The read buffer data. */ protected ReadBatch m_readBatch; /** * The limit on how much inbound data to buffer. */ protected long m_cbReadThreshold; /** * ByteBuffer to write Message headers. */ protected ByteBuffer m_bufferMsgHdr; /** * The total number emitted messages; */ protected long m_cMsgUserIn; /** * The total number of messages given to the connection to send. */ protected long m_cMsgUserOut; /** * The total number of null receipts given with sends. */ protected long m_cReceiptsNull; /** * The queue that holds the requests with bufseq and receipt before * connection is ready */ protected LinkedList> m_queuePreNegotiate = null; } // ----- inner class: TaskEvent ---------------------------- /** * TaskEvent is a wrapper around a normal event, but utilizes * the mandatory dispose() call to run a number of tasks on behalf * of the bus, hopefully from the application rather then the bus * thread. */ public static class TaskEvent implements Event { public TaskEvent(Event event, Runnable... aTask) { m_event = event; m_aTask = aTask; } public Type getType() { return m_event.getType(); } public EndPoint getEndPoint() { return m_event.getEndPoint(); } public Object getContent() { return m_event.getContent(); } public Object dispose(boolean fTakeContent) { Object o = m_event.dispose(fTakeContent); for (Runnable task : m_aTask) { task.run(); } return o; } public void dispose() { dispose(/*fTakeContent*/ false); } public String toString() { return m_event.toString(); } private final Event m_event; private final Runnable[] m_aTask; } /** * Wakeup all connections. */ protected void wakeupConnections() { for (Connection conn : getConnections()) { try { conn.wakeup(); } catch (IOException e) { conn.onException(e); } } } /** * Put the bus in the local backlog state, issuing any requisite events. */ protected void issueLocalBacklog() { if (f_fBacklogLocal.compareAndSet(false, true)) { // emit backlog task event addEvent(new TaskEvent(new SimpleEvent(Event.Type.BACKLOG_EXCESSIVE, getLocalEndPoint()), new Runnable() { public void run() { // Now that this event has been consumed we assume that all prior // events have also been consumed, ending the backlog // emit end backlog event emitEvent(new SimpleEvent( Event.Type.BACKLOG_NORMAL, getLocalEndPoint())); // Note: artificially resetting to zero here // is a work around for an issue that occurs // if events are disposed out of order // (as is done by Coherence). // Essentially we treat the processing/disposal of the BACKLOG_EXCESSIVE // event as the application telling us "I'm keeping whatever I'm holding, so stop counting it against me" m_cbEventQueue.set(0); // backlog can only be true since this is the only way to set it to false, we simply // have to emit the backlog event first to ensure that we keep our backlog events // in the proper order f_fBacklogLocal.set(false); // one or more connections may have disabled reads during the backlog, re-enable them // now that the backlog has been cleared wakeupConnections(); } })); } // else; already in the backlog state } /** * A helper class to store a pair. */ public static class Pair { public Pair(K key, V value) { m_key = key; m_value = value; } public K getKey() { return m_key; } public V getValue() { return m_value; } private K m_key; private V m_value; } // ----- data members --------------------------------------------------- /** * True if we are locally backlogged. */ protected final AtomicBoolean f_fBacklogLocal = new AtomicBoolean(); /** * The approximate size of the data in the event queue. */ protected final AtomicLong m_cbEventQueue = new AtomicLong(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy