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

org.objectfabric.Connection Maven / Gradle / Ivy

The newest version!
/**
 * This file is part of ObjectFabric (http://objectfabric.org).
 *
 * ObjectFabric is licensed under the Apache License, Version 2.0, the terms
 * of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html.
 * 
 * Copyright ObjectFabric Inc.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

package org.objectfabric;

import org.objectfabric.CloseCounter.Callback;
import org.objectfabric.InFlight.Provider;

@SuppressWarnings({ "serial", "rawtypes" })
abstract class Connection extends BlockQueue implements Runnable, Provider {

    // Per resource commands

    static final byte COMMAND_PERMISSION = 0;

    static final byte COMMAND_GET_KNOWN = 1;

    // TODO send delta instead of full array?
    static final byte COMMAND_ON_KNOWN = 2;

    static final byte COMMAND_GET_BLOCK = 3;

    static final byte COMMAND_CANCEL_BLOCK = 4;

    static final byte COMMAND_ON_BLOCK = 5;

    static final byte COMMAND_ACK_BLOCK = 6;

    static final byte COMMAND_SUBSCRIBE = 7;

    static final byte COMMAND_UNSUBSCRIBE = 8;

    static final byte COMMAND_UNRESOLVED = 9;

    // Shared commands

    static final byte COMMAND_HEADERS = 10;

    static final byte COMMAND_ADDRESS = 11;

    private static final Permission[] PERMISSIONS = Permission.values();

    //

    private final Location _location;

    private Session _session;

    private Headers _headers;

    // Read thread

    private Address _address;

    private final ImmutableReader _reader = new ImmutableReader(new List());

    private final byte[] _leftover = new byte[Buff.getLargestUnsplitable()];

    private int _leftoverSize = -1;

    // Write thread

    private final ImmutableWriter _writer = new ImmutableWriter(new List());

    private final Queue _buffs = new Queue();

    private final PlatformConcurrentQueue _writes = new PlatformConcurrentQueue();

    private final PlatformMap _subscribed;

    private static final int WRITE_IDLE = 0, WRITE_ONGOING = 1, WRITE_ONGOING_INTERRUPTED = 2;

    private volatile int _writeStatus;

    Connection(Location location, Headers nativeHeaders) {
        _location = location;
        _headers = nativeHeaders;

        if (Debug.THREADS) {
            ThreadAssert.exchangeGiveList(_reader, _reader.getThreadContextObjects());
            ThreadAssert.exchangeGiveList(_writer, _writer.getThreadContextObjects());
            ThreadAssert.exchangeGive(_writer, this);
        }

        post(new Write() {

            @Override
            void run(Connection connection) {
                connection._writer.putByte(TObject.SERIALIZATION_VERSION);
            }
        }, false);

        if (_location instanceof Remote) {
            final Remote remote = (Remote) _location;
            final Headers headers = remote.headers();

            if (headers != null)
                postHeaders(headers, false);

            post(new Write() {

                @Override
                void run(Connection connection) {
                    connection.write(COMMAND_ADDRESS, null, 0, null);
                }

                @Override
                int runEx(Connection connection, Queue queue, int room) {
                    Serialization.writeAddress(connection._writer, remote.address());
                    return room;
                }
            }, false);

            _subscribed = null;
        } else
            _subscribed = new PlatformMap();

        requestRun();
    }

    @Override
    void onClose(Callback callback) {
        if (_subscribed != null)
            for (ServerView view : _subscribed.values())
                view.unsubscribe(this);

        closeRead();
        closeWrite();

        super.onClose(callback);
    }

    //

    final Location location() {
        return _location;
    }

    final Address address() {
        return _address;
    }

    final PlatformMap subscribed() {
        return _subscribed;
    }

    final ImmutableWriter writer() {
        return _writer;
    }

    //

    final void post(Write write) {
        post(write, true);
    }

    final void post(Write write, boolean push) {
        _writes.add(write);

        if (Stats.ENABLED)
            Stats.Instance.ConnectionQueues.incrementAndGet();

        if (push)
            requestRun();
    }

    final void postHeaders(final Headers headers, boolean push) {
        post(new Write() {

            @Override
            void run(Connection connection) {
                connection.write(COMMAND_HEADERS, null, 0, null);
            }

            @Override
            int runEx(Connection connection, Queue queue, int room) {
                Serialization.writeHeaders(connection._writer, headers.asStrings());
                return room;
            }
        }, push);
    }

    final void postPermission(final URI uri, final Permission permission, boolean push) {
        post(new Write() {

            @Override
            void run(Connection connection) {
                connection.write(COMMAND_PERMISSION, uri.path(), 0, null);
            }

            @Override
            int runEx(Connection connection, Queue queue, int room) {
                if (!connection._writer.canWriteByte()) {
                    connection._writer.interrupt(null);
                    return 0;
                }

                connection._writer.writeByte((byte) permission.ordinal());
                return room;
            }
        }, push);
    }

    final void postSubscribe(final URI uri) {
        post(new Write() {

            @Override
            void run(Connection connection) {
                connection.write(Connection.COMMAND_SUBSCRIBE, uri.path(), 0, null);
            }
        });
    }

    final void postUnsubscribe(final URI uri) {
        post(new Write() {

            @Override
            void run(Connection connection) {
                connection.write(Connection.COMMAND_UNSUBSCRIBE, uri.path(), 0, null);
            }
        });
    }

    final void postKnown(final URI uri, final long[] ticks) {
        post(new Write() {

            @Override
            void run(Connection connection) {
                connection.write(Connection.COMMAND_ON_KNOWN, uri.path(), 0, ticks);
            }
        });
    }

    final void postGet(final URI uri, final long tick) {
        post(new Write() {

            @Override
            void run(Connection connection) {
                if (InFlight.starting(uri, tick, connection))
                    connection.write(COMMAND_GET_BLOCK, uri.path(), tick, null);
            }
        });

        if (Stats.ENABLED)
            Stats.Instance.BlockRequestsSent.incrementAndGet();
    }

    @Override
    public final void cancel(final URI uri, final long tick) {
        post(new Write() {

            @Override
            void run(Connection connection) {
                connection.write(COMMAND_CANCEL_BLOCK, uri.path(), tick, null);
            }
        });
    }

    final void postAck(final URI uri, final long tick) {
        post(new Write() {

            @Override
            void run(Connection connection) {
                connection.write(COMMAND_ACK_BLOCK, uri.path(), tick, null);
            }
        });
    }

    /*
     * IO reader thread.
     */

    final boolean resumeRead() {
        boolean started;

        if (Debug.ENABLED) {
            started = Helper.instance().startRead(this);

            if (started) {
                ThreadAssert.resume(_reader, false);

                if (Debug.THREADS)
                    ThreadAssert.exchangeTake(_reader);
            }
        }

        boolean result = !isClosingOrClosed();

        if (!result) {
            if (Debug.ENABLED)
                Helper.instance().assertReadClosing(this);

            if (Debug.THREADS && started)
                ThreadAssert.removePrivateList(_reader.getThreadContextObjects());
        }

        return result;
    }

    final void read(Buff buff) {
        int initialLimit;

        if (Debug.ENABLED)
            initialLimit = buff.limit();

        if (!Debug.RANDOMIZE_TRANSFER_LENGTHS)
            readImpl(buff);
        else {
            int limit = buff.limit();

            while (buff.position() < limit) {
                int rand = Platform.get().randomInt(limit - buff.position() + 1);
                buff.limit(Math.min(buff.position() + rand, limit));
                readImpl(buff);
                Debug.assertion(buff.remaining() == 0);
            }
        }

        if (Debug.ENABLED) {
            Debug.assertion(buff.limit() == initialLimit);
            Debug.assertion(buff.remaining() == 0);
        }
    }

    final void suspendRead() {
        if (Debug.ENABLED) {
            if (Helper.instance().stopRead(this))
                ThreadAssert.suspend(_reader);
            else if (Debug.THREADS)
                ThreadAssert.removePrivateList(_reader.getThreadContextObjects());
        }
    }

    private final void closeRead() {
        if (Debug.ENABLED) {
            if (Helper.instance().closeRead(this)) {
                Object key = new Object();
                ThreadAssert.suspend(key);
                ThreadAssert.resume(_reader, false);

                if (Debug.THREADS) {
                    ThreadAssert.exchangeTake(_reader);
                    ThreadAssert.removePrivateList(_reader.getThreadContextObjects());
                }

                ThreadAssert.resume(key);
            }
        }
    }

    private final void readImpl(Buff buff) {
        if (Debug.ENABLED)
            Debug.assertion(buff.position() >= Buff.getLargestUnsplitable());

        if (buff.remaining() > 0) {
            _reader.setBuff(buff);

            if (_leftoverSize < 0)
                _reader.startRead();
            else {
                buff.position(buff.position() - _leftoverSize);
                buff.putImmutably(_leftover, 0, _leftoverSize);
            }

            if (Debug.ENABLED)
                buff.lock(buff.limit());

            readImpl();

            _leftoverSize = buff.remaining();
            buff.getImmutably(_leftover, 0, _leftoverSize);
            buff.position(buff.limit());
        }
    }

    private static final int STEP_READ_CODE = 0;

    private static final int STEP_READ_URI = 1;

    private static final int STEP_READ_COMMAND = 2;

    private final void readImpl() {
        for (;;) {
            int step = 0;
            byte code = -1;
            String path = null;
            URI uri = null;

            if (_reader.interrupted()) {
                step = _reader.resumeInt();
                code = _reader.resumeByte();
                path = (String) _reader.resume();
                uri = (URI) _reader.resume();
            }

            switch (step) {
                case STEP_READ_CODE: {
                    if (!_reader.canReadByte()) {
                        _reader.interrupt(uri);
                        _reader.interrupt(path);
                        _reader.interruptByte(code);
                        _reader.interruptInt(STEP_READ_CODE);
                        return;
                    }

                    code = _reader.readByte(Writer.DEBUG_TAG_CONNECTION);

                    if (Debug.COMMUNICATIONS_LOG)
                        Log.write("Read command: " + getCommandString(code));
                }
                case STEP_READ_URI: {
                    if (code < COMMAND_HEADERS) { // TODO ew
                        path = _reader.readString();

                        if (_reader.interrupted()) {
                            _reader.interrupt(uri);
                            _reader.interrupt(path);
                            _reader.interruptByte(code);
                            _reader.interruptInt(STEP_READ_URI);
                            return;
                        }

                        if (_location instanceof Server)
                            uri = ((Server) _location).resolver().resolve(_address, path);
                        else
                            uri = ((Origin) _location).uris().get(path);
                    }
                }
                case STEP_READ_COMMAND: {
                    switch (code) {
                        case COMMAND_PERMISSION: {
                            if (!_reader.canReadByte()) {
                                _reader.interrupt(uri);
                                _reader.interrupt(path);
                                _reader.interruptByte(code);
                                _reader.interruptInt(STEP_READ_COMMAND);
                                return;
                            }

                            byte ordinal = _reader.readByte();

                            if (uri != null) {
                                ClientView view = (ClientView) uri.getOrCreate(_location);
                                view.readPermission(uri, PERMISSIONS[ordinal]);
                            }

                            break;
                        }
                        case COMMAND_GET_KNOWN: {
                            // TODO
                            break;
                        }
                        case COMMAND_ON_KNOWN: {
                            long[] ticks = Serialization.readTicks(_reader);

                            if (_reader.interrupted()) {
                                _reader.interrupt(uri);
                                _reader.interrupt(path);
                                _reader.interruptByte(code);
                                _reader.interruptInt(STEP_READ_COMMAND);
                                return;
                            }

                            if (uri != null) {
                                if (ticks == null)
                                    ticks = Tick.EMPTY;

                                onKnown(uri, ticks);
                            }

                            break;
                        }
                        case COMMAND_GET_BLOCK: {
                            long tick = Serialization.readTick(_reader);

                            if (_reader.interrupted()) {
                                _reader.interrupt(uri);
                                _reader.interrupt(path);
                                _reader.interruptByte(code);
                                _reader.interruptInt(STEP_READ_COMMAND);
                                return;
                            }

                            if (uri != null) {
                                ServerView view = (ServerView) uri.getOrCreate(_location);
                                view.readGetBlock(uri, tick, this);
                            }

                            if (Stats.ENABLED)
                                Stats.Instance.BlockRequestsReceived.incrementAndGet();

                            break;
                        }
                        case COMMAND_CANCEL_BLOCK: {
                            long tick = Serialization.readTick(_reader);

                            if (_reader.interrupted()) {
                                _reader.interrupt(uri);
                                _reader.interrupt(path);
                                _reader.interruptByte(code);
                                _reader.interruptInt(STEP_READ_COMMAND);
                                return;
                            }

                            if (uri != null) {
                                ServerView view = (ServerView) uri.getOrCreate(_location);
                                view.readCancelBlock(uri, tick, this);
                            }

                            break;
                        }
                        case COMMAND_ON_BLOCK: {
                            View view = null;

                            if (uri != null)
                                view = uri.getOrCreate(_location);

                            Serialization.readBlock(_reader, this, uri, view);

                            if (_reader.interrupted()) {
                                _reader.interrupt(uri);
                                _reader.interrupt(path);
                                _reader.interruptByte(code);
                                _reader.interruptInt(STEP_READ_COMMAND);
                                return;
                            }

                            break;
                        }
                        case COMMAND_ACK_BLOCK: {
                            long tick = Serialization.readTick(_reader);

                            if (_reader.interrupted()) {
                                _reader.interrupt(uri);
                                _reader.interrupt(path);
                                _reader.interruptByte(code);
                                _reader.interruptInt(STEP_READ_COMMAND);
                                return;
                            }

                            if (uri != null) {
                                ClientView view = (ClientView) uri.getOrCreate(_location);
                                view.readAck(uri, tick);
                            }

                            if (Stats.ENABLED)
                                Stats.Instance.AckReceived.incrementAndGet();

                            break;
                        }
                        case COMMAND_SUBSCRIBE: {
                            if (uri != null) {
                                if (_session != null) {
                                    final URI uri_ = uri;

                                    _session.onRequest(uri, new PermissionCallback() {

                                        @Override
                                        public void set(Permission permission) {
                                            if (permission == Permission.NONE)
                                                postPermission(uri_, permission, true);
                                            else
                                                onPermission(uri_, permission);
                                        }
                                    });
                                } else
                                    onPermission(uri, Permission.WRITE);
                            } else {
                                final String path_ = path;

                                post(new Write() {

                                    @Override
                                    void run(Connection connection) {
                                        connection.write(COMMAND_UNRESOLVED, path_, 0, null);
                                    }
                                });
                            }

                            break;
                        }
                        case COMMAND_UNSUBSCRIBE: {
                            if (uri != null) {
                                ServerView view = (ServerView) uri.getOrCreate(_location);
                                view.readUnsubscribe(uri, this);
                            }

                            break;
                        }
                        case COMMAND_UNRESOLVED: {
                            if (uri != null) {
                                ClientView view = (ClientView) uri.getOrCreate(_location);
                                view.readUnresolved(uri);
                            }

                            break;
                        }
                        case COMMAND_HEADERS: {
                            _headers = Serialization.readHeaders(_reader);

                            if (_reader.interrupted()) {
                                _reader.interrupt(uri);
                                _reader.interrupt(path);
                                _reader.interruptByte(code);
                                _reader.interruptInt(STEP_READ_COMMAND);
                                return;
                            }

                            break;
                        }
                        case COMMAND_ADDRESS: {
                            _address = Serialization.readAddress(_reader);

                            if (_reader.interrupted()) {
                                _reader.interrupt(uri);
                                _reader.interrupt(path);
                                _reader.interruptByte(code);
                                _reader.interruptInt(STEP_READ_COMMAND);
                                return;
                            }

                            _session = onConnection(_headers);
                            _headers = null;
                            break;
                        }
                        default:
                            throw new IllegalStateException();
                    }
                }
            }
        }
    }

    protected Session onConnection(Headers headers) {
        return null;
    }

    private final void onPermission(URI uri, Permission permission) {
        ((ServerView) uri.getOrCreate(_location)).onPermission(uri, this, permission);
    }

    void onKnown(URI uri, long[] ticks) {
        ClientView view = (ClientView) uri.getOrCreate(_location);
        view.readKnown(uri, ticks);
    }

    final void onBlock(URI uri, View view, long tick, Buff[] buffs, long[] removals, boolean requested, boolean ack) {
        Exception exception = uri.onBlock(view, tick, buffs, removals, requested, this, ack, null);

        if (exception != null)
            Log.write(exception);
    }

    /*
     * IO writer thread.
     */

    @Override
    protected void enqueue() {
        Platform.get().execute(this);
    }

    @Override
    public void run() {
        if (onRunStarting()) {
            if (Debug.ENABLED)
                ThreadAssert.resume(_writer, false);

            if (Debug.THREADS)
                ThreadAssert.exchangeTake(_writer);

            runMessages(false);

            if (_writeStatus == WRITE_IDLE)
                write();

            if (Debug.ENABLED)
                ThreadAssert.suspend(_writer);

            onRunEnded(false);
        }
    }

    abstract void write();

    final Queue fill(int limit) {
        boolean done = fillImpl(limit);

        if (_buffs.size() > 0) {
            _writeStatus = done ? WRITE_ONGOING : WRITE_ONGOING_INTERRUPTED;
            return _buffs;
        }

        return null;
    }

    final void writeComplete() {
        if (_writeStatus == WRITE_ONGOING)
            _writeStatus = WRITE_IDLE;
        else if (_writeStatus == WRITE_ONGOING_INTERRUPTED)
            _writeStatus = WRITE_IDLE;
        else
            throw new IllegalStateException();

        requestRun();
    }

    private final void closeWrite() {
        Object key;

        if (Debug.ENABLED) {
            ThreadAssert.suspend(key = new Object());
            ThreadAssert.resume(_writer, false);
        }

        if (Debug.THREADS)
            ThreadAssert.exchangeTake(_writer);

        if (Stats.ENABLED) {
            for (;;) {
                Write message = _writes.poll();

                if (message == null)
                    break;

                Stats.Instance.ConnectionQueues.decrementAndGet();
            }
        }

        recycleBlocks();

        while (_buffs.size() > 0)
            _buffs.poll().recycle();

        if (Debug.THREADS) {
            ThreadAssert.removePrivateList(_writer.getThreadContextObjects());
            ThreadAssert.removePrivate(this);
        }

        if (Debug.ENABLED)
            ThreadAssert.resume(key);
    }

    //

    private final boolean fillImpl(int limit) {
        int room = limit;

        for (int i = 0; i < _buffs.size(); i++)
            room -= _buffs.get(i).remaining();

        boolean done = write(_buffs, room);

        if (Debug.ENABLED) {
            for (int i = 0; i < _buffs.size(); i++) {
                Debug.assertion(_buffs.get(i).getDuplicates() > 0);
                Debug.assertion(_buffs.get(i).remaining() != 0);
            }

            int total = 0;

            for (int i = 0; i < _buffs.size(); i++)
                total += _buffs.get(i).remaining();

            Debug.assertion(total <= limit);
        }

        return done;
    }

    private final boolean write(Queue queue, int room) {
        Buff buff = Buff.getOrCreate();
        _writer.setBuff(buff);

        for (;;) {
            if (!Debug.RANDOMIZE_TRANSFER_LENGTHS)
                room = writeImpl(queue, room);
            else {
                for (;;) {
                    int position = buff.position();
                    int room1 = Platform.get().randomInt(room - 1) + 1;
                    int room2 = writeImpl(queue, room1);
                    room -= room1 - room2;
                    room -= Serialization.enqueueWritten(queue, buff);

                    if (_writer.interrupted() && room1 > Buff.getLargestUnsplitable())
                        break;

                    if (!_writer.interrupted() && buff.position() == position)
                        break;
                }
            }

            boolean exit = !_writer.interrupted() || buff.limit() < buff.capacity();
            int position = buff.position();
            buff.reset();
            buff.limit(position);

            if (Debug.ENABLED)
                buff.lock(buff.limit());

            if (buff.remaining() > 0) {
                queue.add(buff);
                room -= buff.remaining();
            } else
                buff.recycle();

            if (exit) {
                _writer.setBuff(null);
                return !_writer.interrupted();
            }

            buff = Buff.getOrCreate();
            _writer.setBuff(buff);
        }
    }

    private static final int STEP_RUN = 0;

    private static final int STEP_RUN_EX = 1;

    @SuppressWarnings("fallthrough")
    private final int writeImpl(Queue queue, int room) {
        Buff buff = _writer.getBuff();
        buff.limit(Math.min(buff.position() + room, buff.capacity()));

        for (;;) {
            int step = STEP_COMMAND;
            Write write;

            if (_writer.interrupted()) {
                step = _writer.resumeInt();
                write = (Write) _writer.resume();
            } else {
                write = _writes.poll();

                if (Stats.ENABLED && write != null)
                    Stats.Instance.ConnectionQueues.decrementAndGet();

                if (write == null)
                    write = nextBlock();

                if (write == null)
                    return room;

                if (Debug.THREADS)
                    ThreadAssert.exchangeTake(this);
            }

            switch (step) {
                case STEP_RUN: {
                    write.run(this);

                    if (_writer.interrupted()) {
                        _writer.interrupt(write);
                        _writer.interruptInt(STEP_RUN);
                        return room;
                    }
                }
                case STEP_RUN_EX: {
                    room = write.runEx(this, queue, room);

                    if (_writer.interrupted()) {
                        _writer.interrupt(write);
                        _writer.interruptInt(STEP_RUN_EX);
                        return room;
                    }
                }
            }
        }
    }

    private static final int STEP_COMMAND = 0;

    private static final int STEP_URI = 1;

    private static final int STEP_BLOCK = 2;

    private static final int STEP_SET = 3;

    final void write(byte command, String path, long tick, long[] ticks) {
        int step = STEP_COMMAND;

        if (_writer.interrupted())
            step = _writer.resumeInt();

        switch (step) {
            case STEP_COMMAND: {
                if (!_writer.canWriteByte()) {
                    _writer.interruptInt(STEP_COMMAND);
                    return;
                }

                _writer.writeByte(command, Writer.DEBUG_TAG_CONNECTION);

                if (Debug.COMMUNICATIONS_LOG)
                    Log.write("Write command: " + getCommandString(command));
            }
            case STEP_URI: {
                if (path != null) {
                    _writer.writeString(path);

                    if (_writer.interrupted()) {
                        _writer.interruptInt(STEP_URI);
                        return;
                    }
                }
            }
            case STEP_BLOCK: {
                if (tick != 0) {
                    Serialization.writeTick(_writer, tick);

                    if (_writer.interrupted()) {
                        _writer.interruptInt(STEP_BLOCK);
                        return;
                    }
                }
            }
            case STEP_SET: {
                if (ticks != null) {
                    Serialization.writeTicks(_writer, ticks);

                    if (_writer.interrupted()) {
                        _writer.interruptInt(STEP_SET);
                        return;
                    }
                }
            }
        }
    }

    static abstract class Write {

        abstract void run(Connection connection);

        int runEx(Connection connection, Queue queue, int room) {
            return room;
        }
    }

    // Debug

    public static String getCommandString(int code) {
        if (!Debug.COMMUNICATIONS)
            throw new IllegalStateException();

        switch (code) {
            case COMMAND_GET_KNOWN:
                return "GET_KNOWN";
            case COMMAND_PERMISSION:
                return "PERMISSION";
            case COMMAND_ON_KNOWN:
                return "ON_KNOWN";
            case COMMAND_GET_BLOCK:
                return "GET_BLOCK";
            case COMMAND_CANCEL_BLOCK:
                return "CANCEL_BLOCK";
            case COMMAND_ON_BLOCK:
                return "ON_BLOCK";
            case COMMAND_ACK_BLOCK:
                return "ACK_BLOCK";
            case COMMAND_SUBSCRIBE:
                return "SUBSCRIBE";
            case COMMAND_UNSUBSCRIBE:
                return "UNSUBSCRIBE";
            case COMMAND_UNRESOLVED:
                return "UNRESOLVED";
            case COMMAND_HEADERS:
                return "HEADERS";
            case COMMAND_ADDRESS:
                return "ADDRESS";
            default:
                throw new IllegalStateException();
        }
    }
}