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

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

The newest version!
/*
 * Copyright 2023 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 static com.epam.deltix.util.vsocket.VSProtocol.*;

import com.epam.deltix.util.concurrent.QuickExecutor;
import com.epam.deltix.util.lang.*;
import com.epam.deltix.util.memory.DataExchangeUtils;

import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import java.io.*;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;

/**
 *  Similar to DataSocket, but stripped of much special logic.
 */
class VSTransportChannel implements Runnable, Disposable {
    // Set to true whenever the channel is checked out
    @GuardedBy("dispatcher.freeChannels")
    boolean checkedOut = false;

    private byte[]                      buffer = new byte[4096];
    private final byte[]                header = new byte[16];
    
    private final VSDispatcher          dispatcher;
    final VSocket                       socket;

    private final VSocketInputStream    vin;
    private final DataInputStream       din;
    private final VSocketOutputStream   out;

    private final byte[]                keepAlive = new byte[2];
    private final byte[]                bytesReport = new byte[10];
    private final byte[]                ping = new byte[2];

    private volatile boolean            closed = false;
    volatile long                       latency = Long.MAX_VALUE;

    private volatile long               reported;

    private final Thread                thread;

    private final QuickExecutor.QuickTask completeTask;

    @Nonnull
    private QuickExecutor.QuickTask createCompleteTask(QuickExecutor quickExecutor) {
        return new QuickExecutor.QuickTask(quickExecutor) {
            @Override
            public void run() throws InterruptedException {
                long bytesRead;
                synchronized (out) {
                    bytesRead = vin.getBytesRead();
                    if (bytesRead == reported) {
                        // We already reported this value
                        return;
                    }
                    DataExchangeUtils.writeLong(bytesReport, 2, (reported = bytesRead));
                    out.write(bytesReport, 0, bytesReport.length);
                }
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.log(Level.FINEST, "Sent BYTES_RECIEVED report: " + bytesRead + " from " + socket.getSocketIdStr());
                }
            }
        };
    }

    VSTransportChannel(VSDispatcher dispatcher, final VSocket socket, ThreadFactory threadFactory) throws IOException {
        this.dispatcher = dispatcher;
        this.socket = socket;
        this.completeTask = createCompleteTask(dispatcher.getQuickExecutor());

        DataExchangeUtils.writeUnsignedShort(keepAlive, 0, KEEP_ALIVE);
        DataExchangeUtils.writeUnsignedShort(ping, 0, PING);
        DataExchangeUtils.writeUnsignedShort(bytesReport, 0, BYTES_RECIEVED);

        this.vin = socket.getInputStream();
        this.din = new DataInputStream (vin);
        this.out = socket.getOutputStream();

        this.thread = threadFactory.newThread(this);
        this.thread.setName("VSTransportChannel for " + socket);
    }

    private synchronized void   onException (Throwable x) {
        dispatcher.transportStopped (this, x);
        close();
    }

    public void                 write(int id, int index, long position, byte[] data, int offset, int length, int unpackedLength) {
        assert id >= 0 && length > 0;
        
        if (length == 0)
            LOGGER.log (Level.WARNING, "Writing zero length packet");

        if (out.isBroken()) {
            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.log(Level.FINEST, "Write to a broken transport " + socket.getSocketIdStr());
            }
        }

        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.log(Level.FINEST, "Sending data block " + position + ":" + (position + unpackedLength) + " (" + length + "/" + unpackedLength + ") to socket: " + socket.getSocketIdStr());
        }

        synchronized (out) {
            DataExchangeUtils.writeUnsignedShort(header, 0, id);
            DataExchangeUtils.writeUnsignedShort(header, 2, length);
            DataExchangeUtils.writeInt(header, 4, index);
            DataExchangeUtils.writeLong(header, 8, position);

            out.writeTwoArrays (header, 0, header.length, data, offset, length);
        }
    }

    public void                 write (byte [] data) {
        write (data, 0, data.length);
    }

    public void                 write (byte [] data, int offset, int length) {
        if (length == 0)
            LOGGER.log (Level.WARNING, "Zero length packet");

        synchronized (out) {
            out.write (data, offset, length);
        }
    }

    public void                 keepAlive () {
        write(keepAlive);
    }

    private long                ping () {
        long l = latency = System.nanoTime();
        write(ping);
        return l;
    }

    @Override
    public void                 run () {
        int     index;
        long    offset;
        Thread currentThread = Thread.currentThread();
        assert currentThread == this.thread;

        try {
            for (;;) {
                vin.complete();

                if (currentThread.isInterrupted())
                    throw new InterruptedException();

                if (vin.getBytesRead() - reported > VSocketOutputStream.CAPACITY / 4)
                    completeTask.submit();
                
                int destId = din.readUnsignedShort ();
                //System.out.println(this.socket + ": signal = " + destId);

                if (destId == LISTENER_ID) {    // Virtual connection request
                    int             code = din.readUnsignedShort ();

                    int inCapacity = din.readInt();
                    int outCapacity = din.readInt();
                    int rIndex = din.readInt();
                    boolean compressed = din.readByte() == 1;

                    VSChannelImpl local = dispatcher.newChannel (outCapacity, inCapacity, compressed);
                    try {
                        local.onConnectionRequest(code, inCapacity, rIndex);
                        dispatcher.connectionListener.connectionAccepted (dispatcher.getQuickExecutor(), local);
                    } catch (Throwable x) {
                        // No reason to shutdown this transport channel
                        LOGGER.log (Level.SEVERE, "Exception sending ACK", x);
                        local.close ();
                    }
                }
                else if (destId == BYTES_RECIEVED) {
                    long size = din.readLong ();
                    out.confirm(size);
                    if (LOGGER.isLoggable(Level.FINEST)) {
                        LOGGER.log(Level.FINEST, "Got BYTES_RECIEVED report: " + size + " in " + socket.getSocketIdStr());
                    }
                }
                else if (destId == KEEP_ALIVE) {
                    // keep alive signal - do nothing
                }
                else if (destId == PING) {
                    if (latency != Long.MAX_VALUE)
                        latency = System.nanoTime() - latency;
                    else
                        write(ping);
                }
                else if (destId == DISPATCHER_CLOSE) {
                    dispatcher.onRemoteClosed();
                }
                else {
                    int             code = din.readUnsignedShort ();
                    VSChannelImpl   c = dispatcher.getChannel (destId);

                    if (c == null && code != CLOSING) {
                        if (code == BYTES_AVAILABLE_REPORT) {
                            if (LOGGER.isLoggable(Level.FINE)) {
                                LOGGER.log(Level.FINE, "Got BYTES_AVAILABLE_REPORT for missing (recently closed?) channel " + destId);
                            }
                        } else {
                            LOGGER.log(Level.SEVERE, code + ": No local channel for " + destId);
                        }
                    }

                    switch (code) {
                        case CONNECT_ACK:
                            int     remoteId = din.readUnsignedShort ();
                            int     remoteCapacity = din.readInt ();
                            int     remoteIndex = din.readInt ();

                            assert c != null;

                            c.onRemoteConnected(remoteId, remoteCapacity, remoteIndex);
                            break;

                        case CLOSING:
                            index = din.readInt();
                            offset = din.readLong();

                            if (c != null) {
                                boolean valid = c.assertIndexValid("CLOSING", index);
                                if (valid) {
                                    c.processCommand(code, offset);
                                }
                            }

                            break;
                        
                        case CLOSED:
                            index = din.readInt();
                            offset = din.readLong();

                            if (c != null) {
                                boolean valid = c.assertIndexValid("CLOSED", index);
                                if (valid) {
                                    c.processCommand(code, offset);
                                }
                            }

                            break;

                        case BYTES_AVAILABLE_REPORT :
                            int available = din.readInt();
                            index = din.readInt();

                            assert (available >= 0);

                            if (c != null) {
                                boolean valid = index == c.getIndex();
                                // make sure that we mark that bytes reports as read,
                                vin.complete();
                                // because code below may not return
                                if (valid) {
                                    c.onCapacityIncreased(available);
                                    if (LOGGER.isLoggable(Level.FINEST)) {
                                        LOGGER.log(Level.FINEST, "Got BYTES_AVAILABLE_REPORT report: " + available + " in " + socket.getSocketIdStr());
                                    }
                                } else {
                                    LOGGER.log(Level.FINE, "Got BYTES_AVAILABLE_REPORT for wrong channel " + destId);
                                }
                            }
                            break;

                        default:
                            index = din.readInt();
                            offset = din.readLong();

                            if (c == null) {
                                LOGGER.log (Level.INFO, "Skipping bytes (no channel): " + code);
                                din.skipBytes (code);
                            } else if (!c.assertIndexValid(code, index)) {
                                LOGGER.log (Level.WARNING, "Skipping bytes (wrong channel): " + code);
                                din.skipBytes (code);
                            } else  {
                                int     capacity = buffer.length;

                                if (capacity < code)
                                    buffer = new byte [Util.doubleUntilAtLeast (capacity, code)];

                                if (code == 0)
                                    LOGGER.log (Level.WARNING, "Unknown zero-length data for channel: " + destId);

                                din.readFully (buffer, 0, code);
                                c.receive(offset, buffer, 0, code, socket.getCode(), socket.getSocketNumber());
                            }
                    }
                }
            }
        } catch (InterruptedException e) {
            if (LOGGER.isLoggable(Level.FINE))
                LOGGER.log (Level.FINE, "Interrupted" , e);
        } catch (Throwable x) {
            if (currentThread.isInterrupted())
                LOGGER.log (Level.FINE, this + ": Interrupted.");
            else
                onException (x);
        } finally {
            closed = true;
        }
    }

    public long                 getLatency() {
        long l = ping();
        while (latency == l && !closed)
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
            }

        if (closed)
            return Long.MAX_VALUE;

        return latency;
    }

    @Override
    public void                 close () {
        //flusher.interrupt();
        this.thread.interrupt();
        Util.close (socket);
    }

    public void                 start() {
        thread.start();
    }

    @Override
    public String toString() {
        return getClass().getName() +"@" + Integer.toHexString(hashCode()) + " of socket " + socket.getSocketIdStr();
    }

    public String getSocketIdStr() {
        return socket.getSocketIdStr();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy