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

com.twitter.http2.HttpConnection Maven / Gradle / Ivy

There is a newer version: 0.17.11
Show newest version
/*
 * Copyright 2015 Twitter, Inc.
 *
 * 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.twitter.http2;

import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

import io.netty.channel.ChannelPromise;
import io.netty.util.internal.EmptyArrays;

import static com.twitter.http2.HttpCodecUtil.HTTP_DEFAULT_WEIGHT;
import static com.twitter.http2.HttpCodecUtil.HTTP_CONNECTION_STREAM_ID;

final class HttpConnection {

    private static final HttpProtocolException STREAM_CLOSED =
            new HttpProtocolException("Stream closed");

    static {
        STREAM_CLOSED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
    }

    private final AtomicInteger activeLocalStreams = new AtomicInteger();
    private final AtomicInteger activeRemoteStreams = new AtomicInteger();
    private final Map streams = new ConcurrentHashMap();

    private final AtomicInteger sendWindowSize;
    private final AtomicInteger receiveWindowSize;

    public HttpConnection(int sendWindowSize, int receiveWindowSize) {
        streams.put(HTTP_CONNECTION_STREAM_ID, new Node(null));
        this.sendWindowSize = new AtomicInteger(sendWindowSize);
        this.receiveWindowSize = new AtomicInteger(receiveWindowSize);
    }

    int numActiveStreams(boolean remote) {
        if (remote) {
            return activeRemoteStreams.get();
        } else {
            return activeLocalStreams.get();
        }
    }

    boolean noActiveStreams() {
        return activeRemoteStreams.get() + activeLocalStreams.get() == 0;
    }

    void acceptStream(
            int streamId, boolean remoteSideClosed, boolean localSideClosed,
            int streamSendWindowSize, int streamReceiveWindowSize, boolean remote) {
        StreamState state = null;
        if (!remoteSideClosed || !localSideClosed) {
            state = new StreamState(
                    remoteSideClosed, localSideClosed, streamSendWindowSize, streamReceiveWindowSize);
        }
        Node node = new Node(state);
        node.parent = streams.get(HTTP_CONNECTION_STREAM_ID);
        streams.put(streamId, node);
        if (state != null) {
            if (remote) {
                activeRemoteStreams.incrementAndGet();
            } else {
                activeLocalStreams.incrementAndGet();
            }
        }
    }

    private StreamState removeActiveStream(int streamId, boolean remote) {
        Node stream = streams.remove(streamId);
        if (stream != null && stream.state != null) {
            StreamState state = stream.state;
            stream.close();
            if (remote) {
                activeRemoteStreams.decrementAndGet();
            } else {
                activeLocalStreams.decrementAndGet();
            }
            return state;
        }
        return null;
    }

    void removeStream(int streamId, boolean remote) {
        StreamState state = removeActiveStream(streamId, remote);
        if (state != null) {
            state.clearPendingWrites(STREAM_CLOSED);
        }
    }

    boolean isRemoteSideClosed(int streamId) {
        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        return state == null || state.isRemoteSideClosed();
    }

    void closeRemoteSide(int streamId, boolean remote) {
        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        if (state != null) {
            state.closeRemoteSide();
            if (state.isLocalSideClosed()) {
                removeActiveStream(streamId, remote);
            }
        }
    }

    boolean isLocalSideClosed(int streamId) {
        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        return state == null || state.isLocalSideClosed();
    }

    void closeLocalSide(int streamId, boolean remote) {
        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        if (state != null) {
            state.closeLocalSide();
            if (state.isRemoteSideClosed()) {
                removeActiveStream(streamId, remote);
            }
        }
    }

    int getSendWindowSize(int streamId) {
        if (streamId == HTTP_CONNECTION_STREAM_ID) {
            return sendWindowSize.get();
        }

        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        return state != null ? state.getSendWindowSize() : -1;
    }

    int updateSendWindowSize(int streamId, int deltaWindowSize) {
        if (streamId == HTTP_CONNECTION_STREAM_ID) {
            return sendWindowSize.addAndGet(deltaWindowSize);
        }

        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        return state != null ? state.updateSendWindowSize(deltaWindowSize) : -1;
    }

    int updateReceiveWindowSize(int streamId, int deltaWindowSize) {
        if (streamId == HTTP_CONNECTION_STREAM_ID) {
            return receiveWindowSize.addAndGet(deltaWindowSize);
        }

        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        if (state == null) {
            return -1;
        }
        if (deltaWindowSize > 0) {
            state.setReceiveWindowSizeLowerBound(0);
        }
        return state.updateReceiveWindowSize(deltaWindowSize);
    }

    int getReceiveWindowSizeLowerBound(int streamId) {
        if (streamId == HTTP_CONNECTION_STREAM_ID) {
            return 0;
        }

        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        return state != null ? state.getReceiveWindowSizeLowerBound() : 0;
    }

    void updateAllSendWindowSizes(int deltaWindowSize) {
        for (Node stream : streams.values()) {
            StreamState state = stream.state;
            if (state != null) {
                state.updateSendWindowSize(deltaWindowSize);
            }
        }
    }

    void updateAllReceiveWindowSizes(int deltaWindowSize) {
        for (Node stream : streams.values()) {
            StreamState state = stream.state;
            if (state != null) {
                state.updateReceiveWindowSize(deltaWindowSize);
                if (deltaWindowSize < 0) {
                    state.setReceiveWindowSizeLowerBound(deltaWindowSize);
                }
            }
        }
    }

    boolean putPendingWrite(int streamId, PendingWrite evt) {
        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        return state != null && state.putPendingWrite(evt);
    }

    PendingWrite getPendingWrite(int streamId) {
        if (streamId == HTTP_CONNECTION_STREAM_ID) {
            Node connection = streams.get(HTTP_CONNECTION_STREAM_ID);
            return getPendingWrite(connection);
        }

        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        return state != null ? state.getPendingWrite() : null;
    }

    PendingWrite getPendingWrite(Node node) {
        PendingWrite e = null;
        if (node.state != null && node.state.getSendWindowSize() > 0) {
            e = node.state.getPendingWrite();
        }
        if (e == null) {
            for (Node child : node.children) {
                e = getPendingWrite(child);
                if (e != null) {
                    break;
                }
            }
        }
        return e;
    }

    PendingWrite removePendingWrite(int streamId) {
        Node stream = streams.get(streamId);
        StreamState state = stream == null ? null : stream.state;
        return state != null ? state.removePendingWrite() : null;
    }

    /**
     * Set the priority of the stream.
     */
    boolean setPriority(int streamId, boolean exclusive, int dependency, int weight) {
        Node stream = streams.get(streamId);
        if (stream == null) {
            // stream closed?
            return false;
        }

        Node parent = streams.get(dependency);
        if (parent == null) {
            // garbage collected?
            stream.parent.removeDependent(stream);

            // set to default priority
            Node root = streams.get(HTTP_CONNECTION_STREAM_ID);
            root.addDependent(false, stream);
            stream.setWeight(HTTP_DEFAULT_WEIGHT);
            return false;
        }

        // check if we need to restructure the tree
        if (parent == stream.parent) {
            if (exclusive) {
                // move dependents to stream
                parent.addDependent(true, stream);
            }
        } else {
            stream.parent.removeDependent(stream);
            parent.addDependent(exclusive, stream);
        }

        stream.setWeight(weight);
        return true;
    }

    private static final class StreamState {

        private boolean remoteSideClosed;
        private boolean localSideClosed;
        private final AtomicInteger sendWindowSize;
        private final AtomicInteger receiveWindowSize;
        private int receiveWindowSizeLowerBound;
        private final ConcurrentLinkedQueue pendingWriteQueue =
                new ConcurrentLinkedQueue();

        StreamState(
                boolean remoteSideClosed, boolean localSideClosed,
                int sendWindowSize, int receiveWindowSize) {
            this.remoteSideClosed = remoteSideClosed;
            this.localSideClosed = localSideClosed;
            this.sendWindowSize = new AtomicInteger(sendWindowSize);
            this.receiveWindowSize = new AtomicInteger(receiveWindowSize);
        }

        boolean isRemoteSideClosed() {
            return remoteSideClosed;
        }

        void closeRemoteSide() {
            remoteSideClosed = true;
        }

        boolean isLocalSideClosed() {
            return localSideClosed;
        }

        void closeLocalSide() {
            localSideClosed = true;
        }

        int getSendWindowSize() {
            return sendWindowSize.get();
        }

        int updateSendWindowSize(int deltaWindowSize) {
            return sendWindowSize.addAndGet(deltaWindowSize);
        }

        int updateReceiveWindowSize(int deltaWindowSize) {
            return receiveWindowSize.addAndGet(deltaWindowSize);
        }

        int getReceiveWindowSizeLowerBound() {
            return receiveWindowSizeLowerBound;
        }

        void setReceiveWindowSizeLowerBound(int receiveWindowSizeLowerBound) {
            this.receiveWindowSizeLowerBound = receiveWindowSizeLowerBound;
        }

        boolean putPendingWrite(PendingWrite msg) {
            return pendingWriteQueue.offer(msg);
        }

        PendingWrite getPendingWrite() {
            return pendingWriteQueue.peek();
        }

        PendingWrite removePendingWrite() {
            return pendingWriteQueue.poll();
        }

        void clearPendingWrites(Throwable cause) {
            for (; ; ) {
                PendingWrite pendingWrite = pendingWriteQueue.poll();
                if (pendingWrite == null) {
                    break;
                }
                pendingWrite.fail(cause);
            }
        }
    }

    private static final class Node {

        private static final Comparator COMPARATOR = new WeightComparator();

        public Node parent; // the dependency of the stream
        public int weight;  // the weight of the dependency

        // Children should be iterator in weighted order
        public Set children = new TreeSet(COMPARATOR); // the dependents of the stream
        public int dependentWeights; // the total weight of all the dependents of the stream

        public StreamState state;

        public Node(StreamState state) {
            this.state = state;
        }

        public void close() {
            this.state = null;
        }

        public void setWeight(int weight) {
            // Remove and re-add parent to maintain comparator order
            parent.removeDependent(this);
            this.weight = weight;
            parent.addDependent(false, this);
        }

        public void addDependent(boolean exclusive, Node node) {
            removeDependent(node);
            if (exclusive) {
                for (Node child : children) {
                    node.addDependent(false, child);
                }
                children.clear();
                dependentWeights = 0;
            }
            children.add(node);
            dependentWeights += node.weight;
        }

        public void removeDependent(Node node) {
            if (children.remove(node)) {
                dependentWeights -= node.weight;
            }
        }
    }

    private static final class WeightComparator implements Comparator {
        @Override
        public int compare(Node n1, Node n2) {
            return n2.weight - n1.weight;
        }
    }

    public static final class PendingWrite {
        public final HttpDataFrame httpDataFrame;
        public final ChannelPromise promise;

        PendingWrite(HttpDataFrame httpDataFrame, ChannelPromise promise) {
            this.httpDataFrame = httpDataFrame;
            this.promise = promise;
        }

        void fail(Throwable cause) {
            // httpDataFrame.release();
            promise.setFailure(cause);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy