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

com.epam.deltix.util.vsocket.VSChannelImpl Maven / Gradle / Ivy

There is a newer version: 6.2.9
Show newest version
/*
 * Copyright 2024 EPAM Systems, Inc
 *
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership. Licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.epam.deltix.util.vsocket;

import com.epam.deltix.util.ContextContainer;
import com.epam.deltix.util.concurrent.QuickExecutor;
import static com.epam.deltix.util.vsocket.VSProtocol.*;
import com.epam.deltix.util.io.*;
import com.epam.deltix.util.lang.Util;
import com.epam.deltix.util.memory.DataExchangeUtils;
import com.epam.deltix.util.memory.MemoryDataOutput;

import javax.annotation.CheckReturnValue;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

/**
 *
 */
final class VSChannelImpl implements VSChannel {
    private final ContextContainer contextContainer;
    private volatile VSChannelState         state = VSChannelState.NotConnected;
    
    private final VSDispatcher              dispatcher;
    private final int                       localId;
    private volatile int                    remoteId = -1;

    // for debug purposes inCapacity and outCapacity can be changed to smaller sizes
    private final int                       inCapacity; // = 1 << 15;
    private final int                       outCapacity; // = 1 << 14;

    private GapQueueInputStream             in;
    private CountingInputStream             cin;
    private DataInputStream                 din;
    
    private ChannelOutputStream             out;
    private DataOutputStream                dout;
    
    private boolean                         autoFlush = false;
    private boolean                         noDelay = false;
    private final byte []                   buffer8 = new byte[12];

    private final int                       index;
    private volatile int                    remoteIndex = -1;

    private volatile Runnable               listener;

    private final QuickExecutor.QuickTask   lnrNotifier;

    private final boolean                   compressed;

    private final Inflater                  inflater;
    private final MemoryDataOutput          infOut;

    private final Deflater                  deflater;
    private final MemoryDataOutput          defOut;

    private volatile long                   numBytesSend; // synchronized by "this"
    private final Counter                   numBytesRead = new Counter();

//    private final StringBuffer              sendLog = new StringBuffer();
//    private final StringBuffer              recievedLog = new StringBuffer();

    private final PriorityQueue   commands = new PriorityQueue(2,
            new Comparator() {
                @Override
                public int compare(ChannelCommand a, ChannelCommand b) {
                    if (a.offset == b.offset)
                        return Util.compare(a.code, b.code);

                    return a.offset > b.offset ? 1 : -1;
                }
            });

    static class Counter {
        private long count = 0;

        public synchronized long increment(long v) {
            return count += v;
        }

        public synchronized long value() {
            return count;
        }
    }

    static abstract class ChannelCommand {

        public long             offset;
        public int              code;

        public ChannelCommand(int code, long offset) {
            this.offset = offset;
            this.code = code;
        }

        public abstract void run();
    }

    public class RemoteClosing extends ChannelCommand {

        public RemoteClosing (long offset) {
            super(VSProtocol.CLOSING, offset);

            onRemoteClosing();
        }

        @Override
        public void run() {
            in.finish();
            notifyDataAvailable();
        }
    }

    public class RemoteClosed extends ChannelCommand {
        public RemoteClosed(long offset) {
            super(VSProtocol.CLOSED, offset);
        }

        @Override
        public void run() {
            onRemoteClosed();
        }
    }
    
    public VSChannelImpl (VSDispatcher dispatcher, int inCapacity, int outCapacity,
                         boolean compressed, int localId, int index, ContextContainer contextContainer) {
        if (inCapacity <= 0)
            throw new IllegalArgumentException("inCapacity");
        if (outCapacity <= 0)
            throw new IllegalArgumentException("outCapacity");

        this.dispatcher = dispatcher;
        this.localId = localId;
        this.index = index;
        this.compressed = compressed;
        this.inCapacity = inCapacity;
        this.outCapacity = outCapacity;

        this.contextContainer = contextContainer;

        inflater = compressed ? new Inflater() : null;
        infOut = compressed ? new MemoryDataOutput(inCapacity) : null;
        deflater = compressed ? new Deflater() : null;
        defOut = compressed ? new MemoryDataOutput(outCapacity) : null;

        this.out = new ChannelOutputStream(this, outCapacity);
        this.in = new GapQueueInputStream (inCapacity);

        this.cin = new CountingInputStream(this.in, inCapacity / 4) {

            @Override
            protected boolean bytesRead(long change) {
                try {
                    sendBytesRead(change);
                    return true;
                } catch (IOException e) {
                    if (!VSChannelImpl.this.in.isClosed())
                        LOGGER.log (Level.WARNING, "Error sending bytes read.", e);
                    return false;
                } catch (InterruptedException e) {
                    return false;
                }
            }
        };

        this.lnrNotifier =
            new QuickExecutor.QuickTask (contextContainer.getQuickExecutor()) {
                @Override
                public void     run () {
                    Runnable        consistentListener = listener;

                    if (consistentListener != null)
                        consistentListener.run ();
                }
            };
    }   

    public int                  getLocalId () {
        return (localId);
    }

    public int                  getRemoteId () {
        return (remoteId);
    }

    public String               getRemoteAddress() {
        return dispatcher != null ? dispatcher.getRemoteAddress() : null;
    }

    public String               getRemoteApplication() {
        return dispatcher != null ? dispatcher.getApplicationID() : null;
    }

    @Override
    public String               getClientId() {
        return dispatcher != null ? dispatcher.getClientId() : null;
    }

    public InputStream          getInputStream () {
        return (cin);
    }

    public DataInputStream      getDataInputStream () {
        if (din == null)
            din = new DataInputStream(getInputStream());

        return (din);
    }

    public VSOutputStream       getOutputStream () {
        return (out);
    }

    @Override
    public DataOutputStream     getDataOutputStream() {
        if (dout == null)
            dout = new DataOutputStream (out);

        return (dout);
    }

    public synchronized VSChannelState  getState() {
        return state;
    }

    public synchronized boolean         isClosed () {
        return state == VSChannelState.Closed || state == VSChannelState.Removed;
    }
    
    private synchronized boolean isRemoteConnected() {
        return state == VSChannelState.Connected;
    }

    public void                 close () {
        close(false);
    }
    
    public void                 close (boolean terminate) {
        //  Must close "out" before grabbing lock on "this", so that
        //  another thread sending data releases its lock on "this",
        /// held in send ().
        Util.close (in);

        //System.out.println(this + " closing having read " + numBytesRead.value());

        if (isRemoteConnected () && !terminate) {
            Util.close (out); // flush all data
        } else {
//            if (out.size() > 0)
//                LOGGER.info(this + ": closing having available bytes=" + out.size());
            out.closeNoFlush(); // do not flush data
        }

        synchronized (this) {
            if (state == VSChannelState.Removed)
                return;

            if (state == VSChannelState.Closed)
                return;

            try {
                // send 'closing' signal only when connected
                if (state != VSChannelState.NotConnected)
                    sendClosing();
                else
                    LOGGER.log(Level.FINE, this + " not connected yet");

                // if remote in 'disconnected' state - send signal to close
                if (state == VSChannelState.RemoteClosed)
                    sendClosed();

            } catch (ConnectionAbortedException x) {
                LOGGER.log (Level.FINE, "Error sending disconnect.", x);
            } catch (InterruptedException x) {
                LOGGER.log (Level.FINE, "Sending disconnect interrupted.", x);
            } catch (Exception x) {
                LOGGER.log (Level.WARNING, "Error sending disconnect", x);
            }

            state = VSChannelState.Closed;
        }
    }

    public void processCommand(int cmd, long position) {
        assert position >= 0;

        ChannelCommand command = null;
        if (cmd == VSProtocol.CLOSING)
            command = new RemoteClosing(position);
        else if (cmd == VSProtocol.CLOSED)
            command = new RemoteClosed(position);

        synchronized (commands) {
            if (command != null)
                commands.offer(command);
        }

//        if (numBytesRead > position)
//            System.out.println(this + ": command (" + cmd + ", " + position + ") is out " + numBytesRead);
//        else if (numBytesRead < position)
//            System.out.println(this + ": command (" + cmd + ", " + position + ") delayed by " + (position - numBytesRead));

        checkCommands();
    }

    private void                checkCommands() {

        long position = numBytesRead.value();

        if (!commands.isEmpty()) {
            ChannelCommand command;

            synchronized (commands) {
                command = commands.peek();
                if (command != null && command.offset == position)
                    command = commands.poll();
            }

            while (command != null && command.offset == position) {
                command.run();
                position = numBytesRead.increment(2);

                synchronized (commands) {
                    command = commands.poll();
                }
            }
        }
    }

    void                        onRemoteClosed() {
        dispatcher.channelClosed (this);

        synchronized (this) {
            state = VSChannelState.Removed;
        }
    }

    void                        onRemoteClosing() {
        // close output stream - we cannot send any data
        out.closeNoFlush();

        synchronized (this) {

            switch (state) {
                case Connected:
                    state = VSChannelState.RemoteClosed;
                    break;

                case Closed:
                    try {
                        sendClosed();
                    } catch (Throwable x) {
                        LOGGER.log (Level.WARNING, "Error sending disconnect", x);
                    }
                    break;
            }
        }
    }

    void                        onDisconnected(IOException error) {
        // finish input stream - we do not expect any data
        if (error != null)
            in.putError(error);

        in.finish();

        // close output stream - we cannot send any data
        out.closeNoFlush();

        synchronized (this) {
            switch (state) {
                case Connected:
                    state = VSChannelState.RemoteClosed;
                    break;
            }
        }

        // notify availability listener after input close
        notifyDataAvailable();
    }

//    void                        onRemoteClosing() {
//        // finish input stream - we do not expect any data
//        in.finish();
//
//        // close output stream - we cannot send any data
//        out.closeNoFlush();
//
//        boolean shouldSendClosed = false;
//
//        synchronized (this) {
//            switch (state) {
//                case Connected:
//                    state = VSChannelState.RemoteClosed;
//                    break;
//
//                case Closed:
//                    shouldSendClosed = hasTransport;
//                    break;
//
//                default:
//                    break;
////                    if (hasTransport)
////                        throw new IllegalStateException (state.name ());
//            }
//        }
//
//         // notify availability listener after state change
//        notifyDataAvailable();
//
//        if (shouldSendClosed) {
//            try {
//                sendClosed();
//            } catch (Throwable x) {
//                LOGGER.log (Level.WARNING, "Error sending disconnect", x);
//            }
//        }
//    }

//    void                    receive(long position, byte [] data, int offset, int length) throws IOException {
//        // synchronized asserts is NOT allowed - will block transport
//        try {
//            if (compressed) {
//                int size = decompress(data, offset, length);
//                in.putData(infOut.getBuffer(), 0, size);
//            } else {
//                in.putData (data, offset, length);
//                System.out.println("receive(" + offset + ", " + length + ")");
//            }
//
//            notifyDataAvailable();
//        } catch (EOFException e) {
//            // ignore
//        }
//    }

    void                    receive(long position, byte[] data, int offset, int length, int code, int socketNumber) throws IOException {
        // synchronized asserts is NOT allowed - will block transport
        boolean available = false;
        //recievedLog.append(position).append(":").append(length).append("\n\r");

        int unpackedLength = 0;
        try {
            int queuePosition = (int) (position % inCapacity);
            if (compressed) {
                synchronized (inflater) {
                    unpackedLength = decompress(data, offset, length);
                    available = in.putData(infOut.getBuffer(), 0, unpackedLength, queuePosition);
                }
            } else {
                unpackedLength = length;
                available = in.putData (data, offset, unpackedLength, queuePosition);
            }

            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.log(Level.FINEST, "Got data block " + position + ":" + (position + unpackedLength) + " (" + length + "/" + unpackedLength + ") from socket: @" + Integer.toHexString(code) + "#" + socketNumber);
            }
        } catch (EOFException e) {
            // ignore
        } finally {
            numBytesRead.increment(unpackedLength);
        }

        if (available) {
            notifyDataAvailable();
            checkCommands();
        }
    }

    private void            notifyDataAvailable() {
        if (listener != null)
            lnrNotifier.submit ();
    }

    void                   sendBytesRead (long bytes)
        throws InterruptedException, IOException
    {
        VSChannelState state = this.state;
        if (state != VSChannelState.Connected && state != VSChannelState.RemoteClosed) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, "Skipping BYTES_AVAILABLE_REPORT report: " + bytes + " because channel state is " + state);
            }
            return;
        }
        if (state == VSChannelState.RemoteClosed && LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Sending BYTES_AVAILABLE_REPORT report: " + bytes + " at state " + state + " with remoteIndex=" + remoteIndex);
        }

        DataExchangeUtils.writeInt (buffer8, 4, (int)bytes);
        DataExchangeUtils.writeInt (buffer8, 8, remoteIndex);

        final VSTransportChannel    tc = dispatcher.checkOut ();
        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.log(Level.FINEST, "Sending BYTES_AVAILABLE_REPORT report: " + ((int)bytes) + " from " + tc.getSocketIdStr());
        }
        try {
            tc.write (buffer8, 0, buffer8.length);
        } finally {
            dispatcher.checkIn (tc);
        }
    }

    void                    sendConnect ()
        throws InterruptedException, IOException
    {
        byte []                     data = new byte[17];

        DataExchangeUtils.writeUnsignedShort (data, 0, LISTENER_ID);
        DataExchangeUtils.writeUnsignedShort (data, 2, localId);
        DataExchangeUtils.writeInt (data, 4, inCapacity);
        DataExchangeUtils.writeInt (data, 8, outCapacity);
        DataExchangeUtils.writeInt (data, 12, index);
        DataExchangeUtils.writeByte(data, 16, compressed ? 1 : 0);

        final VSTransportChannel    tc = dispatcher.checkOut ();
        try {
            tc.write (data, 0, data.length);
        } finally {
            dispatcher.checkIn (tc);
        }
    }

    void                    sendClosing()
        throws InterruptedException, IOException
    {
        assert remoteId != -1;

        byte []                     data = new byte [16];

        DataExchangeUtils.writeUnsignedShort (data, 0, remoteId);
        DataExchangeUtils.writeUnsignedShort (data, 2, CLOSING);
        DataExchangeUtils.writeInt(data, 4, remoteIndex);
        DataExchangeUtils.writeLong (data, 8, numBytesSend);

        final VSTransportChannel    tc = dispatcher.checkOut ();
        try {
            tc.write (data, 0, data.length);
            numBytesSend += 2;
        } finally {
            dispatcher.checkIn (tc);
        }
        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.log(Level.FINEST, "Sending CLOSING: remoteIndex=" + remoteIndex + " numBytesSend=" + numBytesSend);
        }
    }

    void                    sendClosed ()
        throws InterruptedException, IOException
    {
        assert remoteId != -1;

        byte []                     data = new byte [16];

        DataExchangeUtils.writeUnsignedShort (data, 0, remoteId);
        DataExchangeUtils.writeUnsignedShort (data, 2, CLOSED);
        DataExchangeUtils.writeInt (data, 4, remoteIndex);
        DataExchangeUtils.writeLong(data, 8, numBytesSend);

        final VSTransportChannel    tc = dispatcher.checkOut ();
        try {
            tc.write (data, 0, data.length);
            numBytesSend += 2;
        } finally {
            dispatcher.checkIn (tc);
        }
        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.log(Level.FINEST, "Sending CLOSED: remoteIndex=" + remoteIndex + " numBytesSend=" + numBytesSend);
        }
    }

    synchronized void           send(byte [] data, int offset, int length)
        throws InterruptedException, IOException
    {
        switch (state) {
            case Connected: {
                VSTransportChannel tc = null;

                try {
                    if (compressed) {
                        tc = dispatcher.checkOut ();

                        synchronized (deflater) {
                            int compressed = compress(data, offset, length);
                            tc.write(remoteId, remoteIndex, numBytesSend, defOut.getBuffer(), 0, compressed, length);
                            numBytesSend += length;
                        }
                    } else {
                        tc = dispatcher.checkOut ();
                        tc.write(remoteId, remoteIndex, numBytesSend, data, offset, length, length);
                        numBytesSend += length;
                        //sendLog.append("sending bytes: ").append(numBytesSend);
                    }

                } finally {
                    if (tc != null)
                        dispatcher.checkIn (tc);
                }
                break;
            }

            case Closed:
            case RemoteClosed:
                throw new ChannelClosedException ();

            default:
                throw new IllegalStateException (state.name ());
        }
    }

    void                    onCapacityIncreased(int capacity) {
        out.addAvailableCapacity(capacity);
    }

    void                    onConnectionRequest(int remoteId, int remoteCapacity, int rIndex)
        throws InterruptedException, IOException
    {
        assert this.remoteId == -1;

        this.remoteIndex = rIndex;        
        this.remoteId = remoteId;

        DataExchangeUtils.writeUnsignedShort (buffer8, 0, remoteId);
        DataExchangeUtils.writeUnsignedShort (buffer8, 2, BYTES_AVAILABLE_REPORT);        

        //
        //  Send CONNECT_ACK
        //
        byte []                     data = new byte [14];
        DataExchangeUtils.writeUnsignedShort (data, 0, remoteId);
        DataExchangeUtils.writeUnsignedShort (data, 2, CONNECT_ACK);
        DataExchangeUtils.writeUnsignedShort (data, 4, localId);
        DataExchangeUtils.writeInt (data, 6, inCapacity);
        DataExchangeUtils.writeInt (data, 10, index);

        final VSTransportChannel    tc = dispatcher.checkOut ();
        try {
            tc.write (data, 0, data.length);
        } finally {
            dispatcher.checkIn (tc);
        }

        synchronized (this) {
            state = VSChannelState.Connected;
        }
        // set remote capacity after sending CONNECT_ACK to prevent closing channel
        out.setRemoteCapacity(remoteCapacity);
    }

    @CheckReturnValue
    boolean assertIndexValid(int dataSize, int incoming) {
        boolean valid = index == incoming;
        if (!valid)
            LOGGER.log (Level.SEVERE,
                    this + "[Data:" + dataSize + "] - Wrong channel (remote: " + incoming + "; local: " + index + ")");

        //LOGGER.info(this + ":" + index + " - [Data:" + dataSize + "]");
        //System.out.println(index + " [Data:" + dataSize + "]");

        assert valid : "[Data:" + dataSize + "] - Wrong channel (remote: " + incoming + "; local: " + index + ")";
        return valid;
    }

    @CheckReturnValue
    boolean assertIndexValid(String method, int incoming) {
        boolean valid = index == incoming;
        if (!valid)
            LOGGER.log (Level.SEVERE,
                    this + "[" + method + "] - Wrong channel (remote: " + incoming + "; local: " + index + ")");

        //LOGGER.info(this + ":" + index + " - [" + method + "]");
        //LOGGER.log (Level.INFO, index + " - [" + method + "]");
        assert valid : "[" + method + "] - Wrong channel (remote: " + incoming + "; local: " + index + ")";
        return valid;
    }

    void                    onRemoteConnected(int remoteId, int remoteCapacity, int rIndex) {
        assert this.remoteId == -1;

        this.remoteId = remoteId;
        this.remoteIndex = rIndex;

        DataExchangeUtils.writeUnsignedShort (buffer8, 0, remoteId);
        DataExchangeUtils.writeUnsignedShort (buffer8, 2, BYTES_AVAILABLE_REPORT);

        boolean wasClosed;
        
        synchronized (this) {
            //assert state == VSChannelState.NotConnected; //TODO: check this

            wasClosed = state == VSChannelState.Closed;
            state = VSChannelState.Connected;
        }

        out.setRemoteCapacity(remoteCapacity);

        if (wasClosed) {
            //System.out.println(this + " onRemoteConnected for closing channel");
            close();
        }
    }

    @Override
    public boolean          setAutoflush(boolean value) {
        autoFlush = value;
        return true;
    }

    @Override
    public boolean          isAutoflush() {
        return autoFlush;
    }

    @Override
    public boolean          getNoDelay() {
        return noDelay;
    }

    @Override
    public void             setNoDelay(boolean value) {
        this.noDelay = value;
        
        if (value) {
            // TODO: Ideally we should delegate creation of ChannelExecutor instances to ContextContainer but ContextContainer can't access class ChannelExecutor.
            ChannelExecutor.getInstance(contextContainer.getAffinityConfig()).addChannel(this);
        }
    }

    @Override
    public void             setAvailabilityListener(Runnable lnr) {
        listener = lnr;
    }

    @Override
    public Runnable         getAvailabilityListener() {
        return listener;
    }

    @Override
    public String           encode(String value) {
        char[] msg = value.toCharArray();
        char[] id = getClientId().toCharArray();
        char[] output = new char[msg.length];

        for (int i = 0; i < msg.length; i++) {
            int index = i % id.length;
            output[i] = (char) (msg[i] ^ id[index]);
        }

        return new String(output);
    }

    @Override
    public String           decode(String value) {
        char[] msg = value.toCharArray();
        char[] id = getClientId().toCharArray();
        char[] output = new char[msg.length];

        for (int i = 0; i < msg.length; i++)
            output[i] = (char)(msg[i] ^ id[i % id.length]);

        return new String(output);
    }

    private int compress(byte[] data, int offset, int length) {

        deflater.reset();
        deflater.setLevel(length < 128 ? Deflater.NO_COMPRESSION : 3);
        deflater.setInput(data, offset, length);
        deflater.finish();

        defOut.reset(0);
        byte[] buffer = defOut.getBuffer();
        int count = deflater.deflate(buffer);

        while (!deflater.finished()) {
            if (count >= buffer.length) {
                defOut.ensureSize(defOut.getSize() + defOut.getSize() / 2);
                buffer = defOut.getBuffer();
            }
            count += deflater.deflate(buffer, count, buffer.length - count);
        }

        return count;
    }

    private int decompress(byte[] data, int offset, int length) {

        inflater.reset();
        inflater.setInput(data, offset, length);
        infOut.reset(0);

        try {
            int count = inflater.inflate(infOut.getBuffer());
            while (!inflater.finished()) {
                infOut.ensureSize(infOut.getSize() + infOut.getSize() / 2);
                count += inflater.inflate(infOut.getBuffer(), count, infOut.getSize() - count);
            }

            return count;
        } catch (final DataFormatException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String           toString () {
        return ("VSChannel [local: " + localId + "; remote: " + (remoteId == Integer.MIN_VALUE  ? "(none)" : remoteId) + "] (" + index + ")");
    }

    int getIndex() {
        return index;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy