HTTP/2 remove PriorityStreamByteDistributor
Motivation: PriorityStreamByteDistributor is now obsolete and can be replaced by WeightedFairQueueByteDistributor. Modifications: - Remove PriorityStreamByteDistributor and use WeightedFairQueueByteDistributor by default. Result: PriorityStreamByteDistributor no longer has to be maintained and is replaced by a better algorithm.
This commit is contained in:
parent
9ac430f16f
commit
72accceeac
@ -293,7 +293,6 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
private short weight = DEFAULT_PRIORITY_WEIGHT;
|
private short weight = DEFAULT_PRIORITY_WEIGHT;
|
||||||
private DefaultStream parent;
|
private DefaultStream parent;
|
||||||
private IntObjectMap<DefaultStream> children = IntCollections.emptyMap();
|
private IntObjectMap<DefaultStream> children = IntCollections.emptyMap();
|
||||||
private int totalChildWeights;
|
|
||||||
private int prioritizableForTree = 1;
|
private int prioritizableForTree = 1;
|
||||||
private boolean resetSent;
|
private boolean resetSent;
|
||||||
private boolean headerSent;
|
private boolean headerSent;
|
||||||
@ -360,11 +359,6 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
return weight;
|
return weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public final int totalChildWeights() {
|
|
||||||
return totalChildWeights;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final DefaultStream parent() {
|
public final DefaultStream parent() {
|
||||||
return parent;
|
return parent;
|
||||||
@ -573,10 +567,6 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
|
|
||||||
final void weight(short weight) {
|
final void weight(short weight) {
|
||||||
if (weight != this.weight) {
|
if (weight != this.weight) {
|
||||||
if (parent != null) {
|
|
||||||
int delta = weight - this.weight;
|
|
||||||
parent.totalChildWeights += delta;
|
|
||||||
}
|
|
||||||
final short oldWeight = this.weight;
|
final short oldWeight = this.weight;
|
||||||
this.weight = weight;
|
this.weight = weight;
|
||||||
for (int i = 0; i < listeners.size(); i++) {
|
for (int i = 0; i < listeners.size(); i++) {
|
||||||
@ -601,10 +591,8 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
// It will either be added directly in this method, or after this method is called...but it will be added.
|
// It will either be added directly in this method, or after this method is called...but it will be added.
|
||||||
initChildren();
|
initChildren();
|
||||||
if (streamToRetain == null) {
|
if (streamToRetain == null) {
|
||||||
totalChildWeights = 0;
|
|
||||||
prioritizableForTree = isPrioritizable() ? 1 : 0;
|
prioritizableForTree = isPrioritizable() ? 1 : 0;
|
||||||
} else {
|
} else {
|
||||||
totalChildWeights = streamToRetain.weight();
|
|
||||||
// prioritizableForTree does not change because it is assumed all children node will still be
|
// prioritizableForTree does not change because it is assumed all children node will still be
|
||||||
// descendants through an exclusive priority tree operation.
|
// descendants through an exclusive priority tree operation.
|
||||||
children.put(streamToRetain.id(), streamToRetain);
|
children.put(streamToRetain.id(), streamToRetain);
|
||||||
@ -628,7 +616,6 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
// may not be successful and may return null. This is because when an exclusive dependency is processed
|
// may not be successful and may return null. This is because when an exclusive dependency is processed
|
||||||
// the children are removed in a previous recursive call but the child's parent link is updated here.
|
// the children are removed in a previous recursive call but the child's parent link is updated here.
|
||||||
if (oldParent != null && oldParent.children.remove(child.id()) != null) {
|
if (oldParent != null && oldParent.children.remove(child.id()) != null) {
|
||||||
oldParent.totalChildWeights -= child.weight();
|
|
||||||
if (!child.isDescendantOf(oldParent)) {
|
if (!child.isDescendantOf(oldParent)) {
|
||||||
oldParent.decrementPrioritizableForTree(child.prioritizableForTree());
|
oldParent.decrementPrioritizableForTree(child.prioritizableForTree());
|
||||||
if (oldParent.prioritizableForTree() == 0) {
|
if (oldParent.prioritizableForTree() == 0) {
|
||||||
@ -647,7 +634,6 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
|
|
||||||
final Http2Stream oldChild = children.put(child.id(), child);
|
final Http2Stream oldChild = children.put(child.id(), child);
|
||||||
assert oldChild == null : "A stream with the same stream ID was already in the child map.";
|
assert oldChild == null : "A stream with the same stream ID was already in the child map.";
|
||||||
totalChildWeights += child.weight();
|
|
||||||
incrementPrioritizableForTree(child.prioritizableForTree(), oldParent);
|
incrementPrioritizableForTree(child.prioritizableForTree(), oldParent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -669,7 +655,6 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
events.add(new ParentChangedEvent(child, child.parent()));
|
events.add(new ParentChangedEvent(child, child.parent()));
|
||||||
notifyParentChanging(child, null);
|
notifyParentChanging(child, null);
|
||||||
child.parent = null;
|
child.parent = null;
|
||||||
totalChildWeights -= child.weight();
|
|
||||||
decrementPrioritizableForTree(child.prioritizableForTree());
|
decrementPrioritizableForTree(child.prioritizableForTree());
|
||||||
|
|
||||||
// Move up any grand children to be directly dependent on this node.
|
// Move up any grand children to be directly dependent on this node.
|
||||||
|
@ -60,7 +60,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2RemoteFlowController(Http2Connection connection, final Listener listener) {
|
public DefaultHttp2RemoteFlowController(Http2Connection connection, final Listener listener) {
|
||||||
this(connection, new PriorityStreamByteDistributor(connection), listener);
|
this(connection, new WeightedFairQueueByteDistributor(connection), listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultHttp2RemoteFlowController(Http2Connection connection,
|
public DefaultHttp2RemoteFlowController(Http2Connection connection,
|
||||||
|
@ -49,7 +49,6 @@ import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
|||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the default implementation for processing inbound frame events and delegates to a
|
* Provides the default implementation for processing inbound frame events and delegates to a
|
||||||
|
@ -169,11 +169,6 @@ public interface Http2Stream {
|
|||||||
*/
|
*/
|
||||||
short weight();
|
short weight();
|
||||||
|
|
||||||
/**
|
|
||||||
* The total of the weights of all children of this stream.
|
|
||||||
*/
|
|
||||||
int totalChildWeights();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parent (i.e. the node in the priority tree on which this node depends), or {@code null}
|
* The parent (i.e. the node in the priority tree on which this node depends), or {@code null}
|
||||||
* if this is the root node (i.e. the connection, itself).
|
* if this is the root node (i.e. the connection, itself).
|
||||||
|
@ -1,424 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 The Netty Project
|
|
||||||
*
|
|
||||||
* The Netty Project licenses this file to you 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 io.netty.handler.codec.http2;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.streamableBytes;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
|
||||||
import static java.lang.Math.max;
|
|
||||||
import static java.lang.Math.min;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link StreamByteDistributor} that implements the HTTP/2 priority tree algorithm for allocating
|
|
||||||
* bytes for all streams in the connection.
|
|
||||||
*/
|
|
||||||
public final class PriorityStreamByteDistributor implements StreamByteDistributor {
|
|
||||||
private final Http2Connection connection;
|
|
||||||
private final Http2Connection.PropertyKey stateKey;
|
|
||||||
private final WriteVisitor writeVisitor = new WriteVisitor();
|
|
||||||
|
|
||||||
public PriorityStreamByteDistributor(Http2Connection connection) {
|
|
||||||
this.connection = checkNotNull(connection, "connection");
|
|
||||||
|
|
||||||
// Add a state for the connection.
|
|
||||||
stateKey = connection.newKey();
|
|
||||||
connection.connectionStream().setProperty(stateKey,
|
|
||||||
new PriorityState(connection.connectionStream()));
|
|
||||||
|
|
||||||
// Register for notification of new streams.
|
|
||||||
connection.addListener(new Http2ConnectionAdapter() {
|
|
||||||
@Override
|
|
||||||
public void onStreamAdded(Http2Stream stream) {
|
|
||||||
stream.setProperty(stateKey, new PriorityState(stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStreamClosed(Http2Stream stream) {
|
|
||||||
state(stream).close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPriorityTreeParentChanged(Http2Stream stream, Http2Stream oldParent) {
|
|
||||||
Http2Stream parent = stream.parent();
|
|
||||||
if (parent != null) {
|
|
||||||
long delta = state(stream).unallocatedStreamableBytesForTree();
|
|
||||||
if (delta != 0) {
|
|
||||||
state(parent).unallocatedStreamableBytesForTreeChanged(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPriorityTreeParentChanging(Http2Stream stream, Http2Stream newParent) {
|
|
||||||
Http2Stream parent = stream.parent();
|
|
||||||
if (parent != null) {
|
|
||||||
long delta = state(stream).unallocatedStreamableBytesForTree();
|
|
||||||
if (delta != 0) {
|
|
||||||
state(parent).unallocatedStreamableBytesForTreeChanged(-delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateStreamableBytes(StreamState streamState) {
|
|
||||||
state(streamState.stream()).updateStreamableBytes(streamableBytes(streamState),
|
|
||||||
streamState.hasFrame());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean distribute(int maxBytes, Writer writer) throws Http2Exception {
|
|
||||||
checkNotNull(writer, "writer");
|
|
||||||
if (maxBytes > 0) {
|
|
||||||
allocateBytesForTree(connection.connectionStream(), maxBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Need to write even if maxBytes == 0 in order to handle the case of empty frames.
|
|
||||||
writeVisitor.writeAllocatedBytes(writer);
|
|
||||||
|
|
||||||
return state(connection.connectionStream()).unallocatedStreamableBytesForTree() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For testing only.
|
|
||||||
*/
|
|
||||||
int unallocatedStreamableBytes(Http2Stream stream) {
|
|
||||||
return state(stream).unallocatedStreamableBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For testing only.
|
|
||||||
*/
|
|
||||||
long unallocatedStreamableBytesForTree(Http2Stream stream) {
|
|
||||||
return state(stream).unallocatedStreamableBytesForTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This will allocate bytes by stream weight and priority for the entire tree rooted at {@code
|
|
||||||
* parent}, but does not write any bytes. The connection window is generally distributed amongst
|
|
||||||
* siblings according to their weight, however we need to ensure that the entire connection
|
|
||||||
* window is used (assuming streams have >= connection window bytes to send) and we may need
|
|
||||||
* some sort of rounding to accomplish this.
|
|
||||||
*
|
|
||||||
* @param parent The parent of the tree.
|
|
||||||
* @param connectionWindowSize The connection window this is available for use at this point in
|
|
||||||
* the tree.
|
|
||||||
* @return The number of bytes actually allocated.
|
|
||||||
*/
|
|
||||||
private int allocateBytesForTree(Http2Stream parent, int connectionWindowSize) {
|
|
||||||
PriorityState state = state(parent);
|
|
||||||
if (state.unallocatedStreamableBytesForTree() <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// If the number of streamable bytes for this tree will fit in the connection window
|
|
||||||
// then there is no need to prioritize the bytes...everyone sends what they have
|
|
||||||
if (state.unallocatedStreamableBytesForTree() <= connectionWindowSize) {
|
|
||||||
SimpleChildFeeder childFeeder = new SimpleChildFeeder(connectionWindowSize);
|
|
||||||
forEachChild(parent, childFeeder);
|
|
||||||
return childFeeder.bytesAllocated;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChildFeeder childFeeder = new ChildFeeder(parent, connectionWindowSize);
|
|
||||||
// Iterate once over all children of this parent and try to feed all the children.
|
|
||||||
forEachChild(parent, childFeeder);
|
|
||||||
|
|
||||||
// Now feed any remaining children that are still hungry until the connection
|
|
||||||
// window collapses.
|
|
||||||
childFeeder.feedHungryChildren();
|
|
||||||
|
|
||||||
return childFeeder.bytesAllocated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void forEachChild(Http2Stream parent, Http2StreamVisitor childFeeder) {
|
|
||||||
try {
|
|
||||||
parent.forEachChild(childFeeder);
|
|
||||||
} catch (Http2Exception e) {
|
|
||||||
// Should never happen since the feeder doesn't throw.
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PriorityState state(Http2Stream stream) {
|
|
||||||
return checkNotNull(stream, "stream").getProperty(stateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link Http2StreamVisitor} that performs the HTTP/2 priority algorithm to distribute the
|
|
||||||
* available connection window appropriately to the children of a given stream.
|
|
||||||
*/
|
|
||||||
private final class ChildFeeder implements Http2StreamVisitor {
|
|
||||||
final int maxSize;
|
|
||||||
int totalWeight;
|
|
||||||
int connectionWindow;
|
|
||||||
int nextTotalWeight;
|
|
||||||
int nextConnectionWindow;
|
|
||||||
int bytesAllocated;
|
|
||||||
Http2Stream[] stillHungry;
|
|
||||||
int nextTail;
|
|
||||||
|
|
||||||
ChildFeeder(Http2Stream parent, int connectionWindow) {
|
|
||||||
maxSize = parent.numChildren();
|
|
||||||
totalWeight = parent.totalChildWeights();
|
|
||||||
this.connectionWindow = connectionWindow;
|
|
||||||
this.nextConnectionWindow = connectionWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean visit(Http2Stream child) {
|
|
||||||
// In order to make progress toward the connection window due to possible rounding errors, we make sure
|
|
||||||
// that each stream (with data to send) is given at least 1 byte toward the connection window.
|
|
||||||
int connectionWindowChunk =
|
|
||||||
max(1, (int) (connectionWindow * (child.weight() / (double) totalWeight)));
|
|
||||||
int bytesForTree = min(nextConnectionWindow, connectionWindowChunk);
|
|
||||||
|
|
||||||
PriorityState state = state(child);
|
|
||||||
int bytesForChild = min(state.unallocatedStreamableBytes(), bytesForTree);
|
|
||||||
|
|
||||||
// Allocate the bytes to this child.
|
|
||||||
if (bytesForChild > 0) {
|
|
||||||
state.allocate(bytesForChild);
|
|
||||||
bytesAllocated += bytesForChild;
|
|
||||||
nextConnectionWindow -= bytesForChild;
|
|
||||||
bytesForTree -= bytesForChild;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate any remaining bytes to the children of this stream.
|
|
||||||
if (bytesForTree > 0) {
|
|
||||||
int childBytesAllocated = allocateBytesForTree(child, bytesForTree);
|
|
||||||
bytesAllocated += childBytesAllocated;
|
|
||||||
nextConnectionWindow -= childBytesAllocated;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextConnectionWindow > 0) {
|
|
||||||
// If this subtree still wants to send then it should be re-considered to take bytes that are unused by
|
|
||||||
// sibling nodes. This is needed because we don't yet know if all the peers will be able to use all of
|
|
||||||
// their "fair share" of the connection window, and if they don't use it then we should divide their
|
|
||||||
// unused shared up for the peers who still want to send.
|
|
||||||
if (state.unallocatedStreamableBytesForTree() > 0) {
|
|
||||||
stillHungry(child);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void feedHungryChildren() {
|
|
||||||
if (stillHungry == null) {
|
|
||||||
// There are no hungry children to feed.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
totalWeight = nextTotalWeight;
|
|
||||||
connectionWindow = nextConnectionWindow;
|
|
||||||
|
|
||||||
// Loop until there are not bytes left to stream or the connection window has collapsed.
|
|
||||||
for (int tail = nextTail; tail > 0 && connectionWindow > 0;) {
|
|
||||||
nextTotalWeight = 0;
|
|
||||||
nextTail = 0;
|
|
||||||
|
|
||||||
// Iterate over the children that are currently still hungry.
|
|
||||||
for (int head = 0; head < tail && nextConnectionWindow > 0; ++head) {
|
|
||||||
if (!visit(stillHungry[head])) {
|
|
||||||
// The connection window has collapsed, break out of the loop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connectionWindow = nextConnectionWindow;
|
|
||||||
totalWeight = nextTotalWeight;
|
|
||||||
tail = nextTail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that the given child is still hungry (i.e. still has streamable bytes that can
|
|
||||||
* fit within the current connection window).
|
|
||||||
*/
|
|
||||||
void stillHungry(Http2Stream child) {
|
|
||||||
ensureSpaceIsAllocated(nextTail);
|
|
||||||
stillHungry[nextTail++] = child;
|
|
||||||
nextTotalWeight += child.weight();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that the {@link #stillHungry} array is properly sized to hold the given index.
|
|
||||||
*/
|
|
||||||
void ensureSpaceIsAllocated(int index) {
|
|
||||||
if (stillHungry == null) {
|
|
||||||
// Initial size is 1/4 the number of children. Clipping the minimum at 2, which will over allocate if
|
|
||||||
// maxSize == 1 but if this was true we shouldn't need to re-allocate because the 1 child should get
|
|
||||||
// all of the available connection window.
|
|
||||||
stillHungry = new Http2Stream[max(2, maxSize >>> 2)];
|
|
||||||
} else if (index == stillHungry.length) {
|
|
||||||
// Grow the array by a factor of 2.
|
|
||||||
stillHungry = Arrays.copyOf(stillHungry, min(maxSize, stillHungry.length << 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simplified version of {@link ChildFeeder} that is only used when all streamable bytes fit
|
|
||||||
* within the available connection window.
|
|
||||||
*/
|
|
||||||
private final class SimpleChildFeeder implements Http2StreamVisitor {
|
|
||||||
int bytesAllocated;
|
|
||||||
int connectionWindow;
|
|
||||||
|
|
||||||
SimpleChildFeeder(int connectionWindow) {
|
|
||||||
this.connectionWindow = connectionWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean visit(Http2Stream child) {
|
|
||||||
PriorityState childState = state(child);
|
|
||||||
int bytesForChild = childState.unallocatedStreamableBytes();
|
|
||||||
|
|
||||||
if (bytesForChild > 0 || childState.hasFrame()) {
|
|
||||||
childState.allocate(bytesForChild);
|
|
||||||
bytesAllocated += bytesForChild;
|
|
||||||
connectionWindow -= bytesForChild;
|
|
||||||
}
|
|
||||||
int childBytesAllocated = allocateBytesForTree(child, connectionWindow);
|
|
||||||
bytesAllocated += childBytesAllocated;
|
|
||||||
connectionWindow -= childBytesAllocated;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The remote flow control state for a single stream.
|
|
||||||
*/
|
|
||||||
private final class PriorityState {
|
|
||||||
final Http2Stream stream;
|
|
||||||
boolean hasFrame;
|
|
||||||
int streamableBytes;
|
|
||||||
int allocated;
|
|
||||||
long unallocatedStreamableBytesForTree;
|
|
||||||
|
|
||||||
PriorityState(Http2Stream stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively increments the {@link #unallocatedStreamableBytesForTree()} for this branch in
|
|
||||||
* the priority tree starting at the current node.
|
|
||||||
*/
|
|
||||||
void unallocatedStreamableBytesForTreeChanged(long delta) {
|
|
||||||
unallocatedStreamableBytesForTree += delta;
|
|
||||||
if (!stream.isRoot()) {
|
|
||||||
state(stream.parent()).unallocatedStreamableBytesForTreeChanged(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void allocate(int bytes) {
|
|
||||||
allocated += bytes;
|
|
||||||
|
|
||||||
if (bytes != 0) {
|
|
||||||
// Also artificially reduce the streamable bytes for this tree to give the appearance
|
|
||||||
// that the data has been written. This will be restored before the allocated bytes are
|
|
||||||
// actually written.
|
|
||||||
unallocatedStreamableBytesForTreeChanged(-bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the number of bytes that have been allocated to this stream by the priority
|
|
||||||
* algorithm.
|
|
||||||
*/
|
|
||||||
void resetAllocated() {
|
|
||||||
allocate(-allocated);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateStreamableBytes(int newStreamableBytes, boolean hasFrame) {
|
|
||||||
assert hasFrame || newStreamableBytes == 0;
|
|
||||||
this.hasFrame = hasFrame;
|
|
||||||
|
|
||||||
int delta = newStreamableBytes - streamableBytes;
|
|
||||||
if (delta != 0) {
|
|
||||||
streamableBytes = newStreamableBytes;
|
|
||||||
|
|
||||||
// Update this branch of the priority tree if the streamable bytes have changed for this node.
|
|
||||||
unallocatedStreamableBytesForTreeChanged(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() {
|
|
||||||
// Unallocate all bytes.
|
|
||||||
resetAllocated();
|
|
||||||
|
|
||||||
// Clear the streamable bytes.
|
|
||||||
updateStreamableBytes(0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasFrame() {
|
|
||||||
return hasFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
int unallocatedStreamableBytes() {
|
|
||||||
return streamableBytes - allocated;
|
|
||||||
}
|
|
||||||
|
|
||||||
long unallocatedStreamableBytesForTree() {
|
|
||||||
return unallocatedStreamableBytesForTree;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A connection stream visitor that delegates to the user provided visitor.
|
|
||||||
*/
|
|
||||||
private final class WriteVisitor implements Http2StreamVisitor {
|
|
||||||
private boolean iterating;
|
|
||||||
private Writer writer;
|
|
||||||
|
|
||||||
void writeAllocatedBytes(Writer writer) throws Http2Exception {
|
|
||||||
if (iterating) {
|
|
||||||
throw connectionError(INTERNAL_ERROR, "byte distribution re-entry error");
|
|
||||||
}
|
|
||||||
this.writer = writer;
|
|
||||||
try {
|
|
||||||
iterating = true;
|
|
||||||
connection.forEachActiveStream(this);
|
|
||||||
} finally {
|
|
||||||
iterating = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean visit(Http2Stream stream) throws Http2Exception {
|
|
||||||
PriorityState state = state(stream);
|
|
||||||
int allocated = state.allocated;
|
|
||||||
|
|
||||||
// Unallocate all bytes for this stream.
|
|
||||||
state.resetAllocated();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Write the allocated bytes.
|
|
||||||
writer.write(stream, allocated);
|
|
||||||
} catch (Throwable t) { // catch Throwable in case any unchecked re-throw tricks are used.
|
|
||||||
// Stop calling the visitor and close the connection as exceptions from the writer are not supported.
|
|
||||||
// If we don't close the connection there is risk that our internal state may be corrupted.
|
|
||||||
throw connectionError(INTERNAL_ERROR, t, "byte distribution write error");
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to iterate across all streams to ensure that we reset the allocated bytes.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -339,7 +339,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
Http2Stream p = client.connectionStream();
|
Http2Stream p = client.connectionStream();
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(5, p.prioritizableForTree());
|
assertEquals(5, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 1
|
// Level 1
|
||||||
p = child(p, streamA.id());
|
p = child(p, streamA.id());
|
||||||
@ -347,7 +346,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(0, p.parent().id());
|
assertEquals(0, p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(4, p.prioritizableForTree());
|
assertEquals(4, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 2
|
// Level 2
|
||||||
p = child(p, streamD.id());
|
p = child(p, streamD.id());
|
||||||
@ -355,7 +353,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamA.id(), p.parent().id());
|
assertEquals(streamA.id(), p.parent().id());
|
||||||
assertEquals(2, p.numChildren());
|
assertEquals(2, p.numChildren());
|
||||||
assertEquals(3, p.prioritizableForTree());
|
assertEquals(3, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 3
|
// Level 3
|
||||||
p = child(p, streamB.id());
|
p = child(p, streamB.id());
|
||||||
@ -363,13 +360,11 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamD.id(), p.parent().id());
|
assertEquals(streamD.id(), p.parent().id());
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
p = child(p.parent(), streamC.id());
|
p = child(p.parent(), streamC.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(streamD.id(), p.parent().id());
|
assertEquals(streamD.id(), p.parent().id());
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -392,7 +387,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
Http2Stream p = client.connectionStream();
|
Http2Stream p = client.connectionStream();
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(5, p.prioritizableForTree());
|
assertEquals(5, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 1
|
// Level 1
|
||||||
p = child(p, streamA.id());
|
p = child(p, streamA.id());
|
||||||
@ -400,7 +394,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(0, p.parent().id());
|
assertEquals(0, p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(4, p.prioritizableForTree());
|
assertEquals(4, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 2
|
// Level 2
|
||||||
p = child(p, streamC.id());
|
p = child(p, streamC.id());
|
||||||
@ -408,7 +401,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamA.id(), p.parent().id());
|
assertEquals(streamA.id(), p.parent().id());
|
||||||
assertEquals(2, p.numChildren());
|
assertEquals(2, p.numChildren());
|
||||||
assertEquals(3, p.prioritizableForTree());
|
assertEquals(3, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 3
|
// Level 3
|
||||||
p = child(p, streamB.id());
|
p = child(p, streamB.id());
|
||||||
@ -416,13 +408,11 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamC.id(), p.parent().id());
|
assertEquals(streamC.id(), p.parent().id());
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
p = child(p.parent(), streamD.id());
|
p = child(p.parent(), streamD.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(streamC.id(), p.parent().id());
|
assertEquals(streamC.id(), p.parent().id());
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -449,7 +439,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
Http2Stream p = client.connectionStream();
|
Http2Stream p = client.connectionStream();
|
||||||
assertEquals(2, p.numChildren());
|
assertEquals(2, p.numChildren());
|
||||||
assertEquals(7, p.prioritizableForTree());
|
assertEquals(7, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 1
|
// Level 1
|
||||||
p = child(p, streamE.id());
|
p = child(p, streamE.id());
|
||||||
@ -457,13 +446,11 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(0, p.parent().id());
|
assertEquals(0, p.parent().id());
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
p = child(p.parent(), streamA.id());
|
p = child(p.parent(), streamA.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(0, p.parent().id());
|
assertEquals(0, p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(5, p.prioritizableForTree());
|
assertEquals(5, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 2
|
// Level 2
|
||||||
p = child(p, streamF.id());
|
p = child(p, streamF.id());
|
||||||
@ -471,7 +458,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamA.id(), p.parent().id());
|
assertEquals(streamA.id(), p.parent().id());
|
||||||
assertEquals(2, p.numChildren());
|
assertEquals(2, p.numChildren());
|
||||||
assertEquals(4, p.prioritizableForTree());
|
assertEquals(4, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 3
|
// Level 3
|
||||||
p = child(p, streamB.id());
|
p = child(p, streamB.id());
|
||||||
@ -479,13 +465,11 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamF.id(), p.parent().id());
|
assertEquals(streamF.id(), p.parent().id());
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
p = child(p.parent(), streamC.id());
|
p = child(p.parent(), streamC.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(streamF.id(), p.parent().id());
|
assertEquals(streamF.id(), p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(2, p.prioritizableForTree());
|
assertEquals(2, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 4
|
// Level 4
|
||||||
p = child(p, streamD.id());
|
p = child(p, streamD.id());
|
||||||
@ -493,7 +477,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamC.id(), p.parent().id());
|
assertEquals(streamC.id(), p.parent().id());
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -637,7 +620,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
Http2Stream p = client.connectionStream();
|
Http2Stream p = client.connectionStream();
|
||||||
assertEquals(4, p.prioritizableForTree());
|
assertEquals(4, p.prioritizableForTree());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 1
|
// Level 1
|
||||||
p = child(p, streamA.id());
|
p = child(p, streamA.id());
|
||||||
@ -645,7 +627,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(3, p.prioritizableForTree());
|
assertEquals(3, p.prioritizableForTree());
|
||||||
assertEquals(0, p.parent().id());
|
assertEquals(0, p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 2
|
// Level 2
|
||||||
p = child(p, streamB.id());
|
p = child(p, streamB.id());
|
||||||
@ -653,7 +634,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(2, p.prioritizableForTree());
|
assertEquals(2, p.prioritizableForTree());
|
||||||
assertEquals(streamA.id(), p.parent().id());
|
assertEquals(streamA.id(), p.parent().id());
|
||||||
assertEquals(2, p.numChildren());
|
assertEquals(2, p.numChildren());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 3
|
// Level 3
|
||||||
p = child(p, streamC.id());
|
p = child(p, streamC.id());
|
||||||
@ -661,13 +641,11 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(streamB.id(), p.parent().id());
|
assertEquals(streamB.id(), p.parent().id());
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
p = child(p.parent(), streamD.id());
|
p = child(p.parent(), streamD.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(streamB.id(), p.parent().id());
|
assertEquals(streamB.id(), p.parent().id());
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -696,7 +674,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
Http2Stream p = client.connectionStream();
|
Http2Stream p = client.connectionStream();
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(2, p.prioritizableForTree());
|
assertEquals(2, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 1
|
// Level 1
|
||||||
p = child(p, streamA.id());
|
p = child(p, streamA.id());
|
||||||
@ -704,7 +681,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(0, p.parent().id());
|
assertEquals(0, p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 2
|
// Level 2
|
||||||
p = child(p, streamB.id());
|
p = child(p, streamB.id());
|
||||||
@ -712,7 +688,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamA.id(), p.parent().id());
|
assertEquals(streamA.id(), p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 3
|
// Level 3
|
||||||
p = child(p, streamC.id());
|
p = child(p, streamC.id());
|
||||||
@ -720,7 +695,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamB.id(), p.parent().id());
|
assertEquals(streamB.id(), p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 4
|
// Level 4
|
||||||
p = child(p, streamE.id());
|
p = child(p, streamE.id());
|
||||||
@ -758,7 +732,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
Http2Stream p = client.connectionStream();
|
Http2Stream p = client.connectionStream();
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(3, p.prioritizableForTree());
|
assertEquals(3, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 1
|
// Level 1
|
||||||
p = child(p, streamA.id());
|
p = child(p, streamA.id());
|
||||||
@ -766,7 +739,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(0, p.parent().id());
|
assertEquals(0, p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(2, p.prioritizableForTree());
|
assertEquals(2, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 2
|
// Level 2
|
||||||
p = child(p, streamB.id());
|
p = child(p, streamB.id());
|
||||||
@ -774,7 +746,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamA.id(), p.parent().id());
|
assertEquals(streamA.id(), p.parent().id());
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(2, p.prioritizableForTree());
|
assertEquals(2, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 3
|
// Level 3
|
||||||
p = child(p, streamC.id());
|
p = child(p, streamC.id());
|
||||||
@ -782,7 +753,6 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
assertEquals(streamB.id(), p.parent().id());
|
assertEquals(streamB.id(), p.parent().id());
|
||||||
assertEquals(2, p.numChildren());
|
assertEquals(2, p.numChildren());
|
||||||
assertEquals(2, p.prioritizableForTree());
|
assertEquals(2, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 4
|
// Level 4
|
||||||
p = child(p, streamE.id());
|
p = child(p, streamE.id());
|
||||||
@ -853,44 +823,37 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
Http2Stream p = client.connectionStream();
|
Http2Stream p = client.connectionStream();
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(7, p.prioritizableForTree());
|
assertEquals(7, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 1
|
// Level 1
|
||||||
p = child(p, streamD.id());
|
p = child(p, streamD.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(2, p.numChildren());
|
assertEquals(2, p.numChildren());
|
||||||
assertEquals(6, p.prioritizableForTree());
|
assertEquals(6, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 2
|
// Level 2
|
||||||
p = child(p, streamF.id());
|
p = child(p, streamF.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
p = child(p.parent(), streamA.id());
|
p = child(p.parent(), streamA.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(2, p.numChildren());
|
assertEquals(2, p.numChildren());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 3
|
// Level 3
|
||||||
p = child(p, streamB.id());
|
p = child(p, streamB.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
p = child(p.parent(), streamC.id());
|
p = child(p.parent(), streamC.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(2, p.prioritizableForTree());
|
assertEquals(2, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 4;
|
// Level 4;
|
||||||
p = child(p, streamE.id());
|
p = child(p, streamE.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -951,45 +914,38 @@ public class DefaultHttp2ConnectionTest {
|
|||||||
Http2Stream p = client.connectionStream();
|
Http2Stream p = client.connectionStream();
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(7, p.prioritizableForTree());
|
assertEquals(7, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 1
|
// Level 1
|
||||||
p = child(p, streamD.id());
|
p = child(p, streamD.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(6, p.prioritizableForTree());
|
assertEquals(6, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 2
|
// Level 2
|
||||||
p = child(p, streamA.id());
|
p = child(p, streamA.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(3, p.numChildren());
|
assertEquals(3, p.numChildren());
|
||||||
assertEquals(5, p.prioritizableForTree());
|
assertEquals(5, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 3
|
// Level 3
|
||||||
p = child(p, streamB.id());
|
p = child(p, streamB.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
p = child(p.parent(), streamF.id());
|
p = child(p.parent(), streamF.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
p = child(p.parent(), streamC.id());
|
p = child(p.parent(), streamC.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(1, p.numChildren());
|
assertEquals(1, p.numChildren());
|
||||||
assertEquals(2, p.prioritizableForTree());
|
assertEquals(2, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
|
|
||||||
// Level 4;
|
// Level 4;
|
||||||
p = child(p, streamE.id());
|
p = child(p, streamE.id());
|
||||||
assertNotNull(p);
|
assertNotNull(p);
|
||||||
assertEquals(0, p.numChildren());
|
assertEquals(0, p.numChildren());
|
||||||
assertEquals(1, p.prioritizableForTree());
|
assertEquals(1, p.prioritizableForTree());
|
||||||
assertEquals(p.numChildren() * DEFAULT_PRIORITY_WEIGHT, p.totalChildWeights());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,701 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2015 The Netty Project
|
|
||||||
*
|
|
||||||
* The Netty Project licenses this file to you 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 io.netty.handler.codec.http2;
|
|
||||||
|
|
||||||
import io.netty.util.collection.IntObjectHashMap;
|
|
||||||
import io.netty.util.collection.IntObjectMap;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.mockito.AdditionalMatchers;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import org.mockito.invocation.InvocationOnMock;
|
|
||||||
import org.mockito.stubbing.Answer;
|
|
||||||
import org.mockito.verification.VerificationMode;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertSame;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.anyInt;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Matchers.same;
|
|
||||||
import static org.mockito.Mockito.atMost;
|
|
||||||
import static org.mockito.Mockito.doAnswer;
|
|
||||||
import static org.mockito.Mockito.doNothing;
|
|
||||||
import static org.mockito.Mockito.doThrow;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests for {@link PriorityStreamByteDistributor}.
|
|
||||||
*/
|
|
||||||
public class PriorityStreamByteDistributorTest {
|
|
||||||
private static final int STREAM_A = 1;
|
|
||||||
private static final int STREAM_B = 3;
|
|
||||||
private static final int STREAM_C = 5;
|
|
||||||
private static final int STREAM_D = 7;
|
|
||||||
private static final int STREAM_E = 9;
|
|
||||||
|
|
||||||
private Http2Connection connection;
|
|
||||||
private PriorityStreamByteDistributor distributor;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private StreamByteDistributor.Writer writer;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() throws Http2Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
|
|
||||||
connection = new DefaultHttp2Connection(false);
|
|
||||||
distributor = new PriorityStreamByteDistributor(connection);
|
|
||||||
|
|
||||||
// Assume we always write all the allocated bytes.
|
|
||||||
doAnswer(new Answer<Void>() {
|
|
||||||
@Override
|
|
||||||
public Void answer(InvocationOnMock in) throws Throwable {
|
|
||||||
Http2Stream stream = (Http2Stream) in.getArguments()[0];
|
|
||||||
int numBytes = (Integer) in.getArguments()[1];
|
|
||||||
int streamableBytes = distributor.unallocatedStreamableBytes(stream) - numBytes;
|
|
||||||
updateStream(stream.id(), streamableBytes, streamableBytes > 0);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).when(writer).write(any(Http2Stream.class), anyInt());
|
|
||||||
|
|
||||||
connection.local().createStream(STREAM_A, false);
|
|
||||||
connection.local().createStream(STREAM_B, false);
|
|
||||||
Http2Stream streamC = connection.local().createStream(STREAM_C, false);
|
|
||||||
Http2Stream streamD = connection.local().createStream(STREAM_D, false);
|
|
||||||
streamC.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false);
|
|
||||||
streamD.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void bytesUnassignedAfterProcessing() throws Http2Exception {
|
|
||||||
updateStream(STREAM_A, 1, true);
|
|
||||||
updateStream(STREAM_B, 2, true);
|
|
||||||
updateStream(STREAM_C, 3, true);
|
|
||||||
updateStream(STREAM_D, 4, true);
|
|
||||||
|
|
||||||
assertFalse(write(10));
|
|
||||||
verifyWrite(STREAM_A, 1);
|
|
||||||
verifyWrite(STREAM_B, 2);
|
|
||||||
verifyWrite(STREAM_C, 3);
|
|
||||||
verifyWrite(STREAM_D, 4);
|
|
||||||
|
|
||||||
assertFalse(write(10));
|
|
||||||
verifyWrite(STREAM_A, 0);
|
|
||||||
verifyWrite(STREAM_B, 0);
|
|
||||||
verifyWrite(STREAM_C, 0);
|
|
||||||
verifyWrite(STREAM_D, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void connectionErrorForWriterException() throws Http2Exception {
|
|
||||||
updateStream(STREAM_A, 1, true);
|
|
||||||
updateStream(STREAM_B, 2, true);
|
|
||||||
updateStream(STREAM_C, 3, true);
|
|
||||||
updateStream(STREAM_D, 4, true);
|
|
||||||
|
|
||||||
Exception fakeException = new RuntimeException("Fake exception");
|
|
||||||
doThrow(fakeException).when(writer).write(same(stream(STREAM_C)), eq(3));
|
|
||||||
|
|
||||||
try {
|
|
||||||
write(10);
|
|
||||||
fail("Expected an exception");
|
|
||||||
} catch (Http2Exception e) {
|
|
||||||
assertFalse(Http2Exception.isStreamError(e));
|
|
||||||
assertEquals(Http2Error.INTERNAL_ERROR, e.error());
|
|
||||||
assertSame(fakeException, e.getCause());
|
|
||||||
}
|
|
||||||
|
|
||||||
verifyWrite(atMost(1), STREAM_A, 1);
|
|
||||||
verifyWrite(atMost(1), STREAM_B, 2);
|
|
||||||
verifyWrite(STREAM_C, 3);
|
|
||||||
verifyWrite(atMost(1), STREAM_D, 4);
|
|
||||||
|
|
||||||
doNothing().when(writer).write(same(stream(STREAM_C)), eq(3));
|
|
||||||
write(10);
|
|
||||||
verifyWrite(STREAM_A, 1);
|
|
||||||
verifyWrite(STREAM_B, 2);
|
|
||||||
verifyWrite(times(2), STREAM_C, 3);
|
|
||||||
verifyWrite(STREAM_D, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we block A which allows bytes to be written by C and D. Here's a view of the tree (stream A is
|
|
||||||
* blocked).
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* [A] B
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void blockedStreamShouldSpreadDataToChildren() throws Http2Exception {
|
|
||||||
// A cannot stream.
|
|
||||||
updateStream(STREAM_B, 10, true);
|
|
||||||
updateStream(STREAM_C, 10, true);
|
|
||||||
updateStream(STREAM_D, 10, true);
|
|
||||||
|
|
||||||
// Write up to 10 bytes.
|
|
||||||
assertTrue(write(10));
|
|
||||||
|
|
||||||
// A is not written
|
|
||||||
verifyWrite(STREAM_A, 0);
|
|
||||||
|
|
||||||
// B is partially written
|
|
||||||
verifyWrite(STREAM_B, 5);
|
|
||||||
|
|
||||||
// Verify that C and D each shared half of A's allowance. Since A's allowance (5) cannot
|
|
||||||
// be split evenly, one will get 3 and one will get 2.
|
|
||||||
verifyWrite(STREAM_C, 3);
|
|
||||||
verifyWrite(STREAM_D, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we block B which allows all bytes to be written by A. A should not share the data with its children
|
|
||||||
* since it's not blocked.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A [B]
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void childrenShouldNotSendDataUntilParentBlocked() throws Http2Exception {
|
|
||||||
// B cannot stream.
|
|
||||||
updateStream(STREAM_A, 10, true);
|
|
||||||
updateStream(STREAM_C, 10, true);
|
|
||||||
updateStream(STREAM_D, 10, true);
|
|
||||||
|
|
||||||
// Write up to 10 bytes.
|
|
||||||
assertTrue(write(10));
|
|
||||||
|
|
||||||
// A is assigned all of the bytes.
|
|
||||||
verifyWrite(STREAM_A, 10);
|
|
||||||
verifyWrite(STREAM_B, 0);
|
|
||||||
verifyWrite(STREAM_C, 0);
|
|
||||||
verifyWrite(STREAM_D, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we block B which allows all bytes to be written by A. Once A is complete, it will spill over the
|
|
||||||
* remaining of its portion to its children.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A [B]
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void parentShouldWaterFallDataToChildren() throws Http2Exception {
|
|
||||||
// B cannot stream.
|
|
||||||
updateStream(STREAM_A, 5, true);
|
|
||||||
updateStream(STREAM_C, 10, true);
|
|
||||||
updateStream(STREAM_D, 10, true);
|
|
||||||
|
|
||||||
// Write up to 10 bytes.
|
|
||||||
assertTrue(write(10));
|
|
||||||
|
|
||||||
verifyWrite(STREAM_A, 5);
|
|
||||||
verifyWrite(STREAM_B, 0);
|
|
||||||
verifyWrite(STREAM_C, 3);
|
|
||||||
verifyWrite(STREAM_D, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we verify re-prioritizing a stream. We start out with B blocked:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A [B]
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* We then re-prioritize D so that it's directly off of the connection and verify that A and D split the written
|
|
||||||
* bytes between them.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* /|\
|
|
||||||
* / | \
|
|
||||||
* A [B] D
|
|
||||||
* /
|
|
||||||
* C
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void reprioritizeShouldAdjustOutboundFlow() throws Http2Exception {
|
|
||||||
// B cannot stream.
|
|
||||||
updateStream(STREAM_A, 10, true);
|
|
||||||
updateStream(STREAM_C, 10, true);
|
|
||||||
updateStream(STREAM_D, 10, true);
|
|
||||||
|
|
||||||
// Re-prioritize D as a direct child of the connection.
|
|
||||||
setPriority(STREAM_D, 0, DEFAULT_PRIORITY_WEIGHT, false);
|
|
||||||
|
|
||||||
assertTrue(write(10));
|
|
||||||
|
|
||||||
verifyWrite(STREAM_A, 5);
|
|
||||||
verifyWrite(STREAM_B, 0);
|
|
||||||
verifyWrite(STREAM_C, 0);
|
|
||||||
verifyWrite(STREAM_D, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that the maximum allowed amount the flow controller allows to be sent is always fully allocated if
|
|
||||||
* the streams have at least this much data to send. See https://github.com/netty/netty/issues/4266.
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / | \
|
|
||||||
* / | \
|
|
||||||
* A(0) B(0) C(0)
|
|
||||||
* /
|
|
||||||
* D(> allowed to send in 1 allocation attempt)
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void unstreamableParentsShouldFeedHungryChildren() throws Http2Exception {
|
|
||||||
// Setup the priority tree.
|
|
||||||
setPriority(STREAM_A, 0, (short) 32, false);
|
|
||||||
setPriority(STREAM_B, 0, (short) 16, false);
|
|
||||||
setPriority(STREAM_C, 0, (short) 16, false);
|
|
||||||
setPriority(STREAM_D, STREAM_A, (short) 16, false);
|
|
||||||
|
|
||||||
final int writableBytes = 100;
|
|
||||||
|
|
||||||
// Send enough so it can not be completely written out
|
|
||||||
final int expectedUnsentAmount = 1;
|
|
||||||
updateStream(STREAM_D, writableBytes + expectedUnsentAmount, true);
|
|
||||||
|
|
||||||
assertTrue(write(writableBytes));
|
|
||||||
|
|
||||||
verifyWrite(STREAM_D, writableBytes);
|
|
||||||
assertEquals(expectedUnsentAmount, streamableBytesForTree(stream(STREAM_D)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we root all streams at the connection, and then verify that data is split appropriately based on
|
|
||||||
* weight (all available data is the same).
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / / \ \
|
|
||||||
* A B C D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void writeShouldPreferHighestWeight() throws Http2Exception {
|
|
||||||
// Root the streams at the connection and assign weights.
|
|
||||||
setPriority(STREAM_A, 0, (short) 50, false);
|
|
||||||
setPriority(STREAM_B, 0, (short) 200, false);
|
|
||||||
setPriority(STREAM_C, 0, (short) 100, false);
|
|
||||||
setPriority(STREAM_D, 0, (short) 100, false);
|
|
||||||
|
|
||||||
updateStream(STREAM_A, 1000, true);
|
|
||||||
updateStream(STREAM_B, 1000, true);
|
|
||||||
updateStream(STREAM_C, 1000, true);
|
|
||||||
updateStream(STREAM_D, 1000, true);
|
|
||||||
|
|
||||||
assertTrue(write(1000));
|
|
||||||
|
|
||||||
// A is assigned all of the bytes.
|
|
||||||
int allowedError = 10;
|
|
||||||
verifyWriteWithDelta(STREAM_A, 109, allowedError);
|
|
||||||
verifyWriteWithDelta(STREAM_B, 445, allowedError);
|
|
||||||
verifyWriteWithDelta(STREAM_C, 223, allowedError);
|
|
||||||
verifyWriteWithDelta(STREAM_D, 223, allowedError);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we root all streams at the connection, and then verify that data is split equally among the stream,
|
|
||||||
* since they all have the same weight.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / / \ \
|
|
||||||
* A B C D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void samePriorityShouldDistributeBasedOnData() throws Http2Exception {
|
|
||||||
// Root the streams at the connection with the same weights.
|
|
||||||
setPriority(STREAM_A, 0, DEFAULT_PRIORITY_WEIGHT, false);
|
|
||||||
setPriority(STREAM_B, 0, DEFAULT_PRIORITY_WEIGHT, false);
|
|
||||||
setPriority(STREAM_C, 0, DEFAULT_PRIORITY_WEIGHT, false);
|
|
||||||
setPriority(STREAM_D, 0, DEFAULT_PRIORITY_WEIGHT, false);
|
|
||||||
|
|
||||||
updateStream(STREAM_A, 400, true);
|
|
||||||
updateStream(STREAM_B, 500, true);
|
|
||||||
updateStream(STREAM_C, 0, true);
|
|
||||||
updateStream(STREAM_D, 700, true);
|
|
||||||
|
|
||||||
assertTrue(write(999));
|
|
||||||
|
|
||||||
verifyWrite(STREAM_A, 333);
|
|
||||||
verifyWrite(STREAM_B, 333);
|
|
||||||
verifyWrite(STREAM_C, 0);
|
|
||||||
verifyWrite(STREAM_D, 333);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we verify the priority bytes for each sub tree at each node are correct
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A B
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void subTreeBytesShouldBeCorrect() throws Http2Exception {
|
|
||||||
Http2Stream stream0 = connection.connectionStream();
|
|
||||||
Http2Stream streamA = connection.stream(STREAM_A);
|
|
||||||
Http2Stream streamB = connection.stream(STREAM_B);
|
|
||||||
Http2Stream streamC = connection.stream(STREAM_C);
|
|
||||||
Http2Stream streamD = connection.stream(STREAM_D);
|
|
||||||
|
|
||||||
// Send a bunch of data on each stream.
|
|
||||||
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
|
|
||||||
streamSizes.put(STREAM_A, (Integer) 400);
|
|
||||||
streamSizes.put(STREAM_B, (Integer) 500);
|
|
||||||
streamSizes.put(STREAM_C, (Integer) 600);
|
|
||||||
streamSizes.put(STREAM_D, (Integer) 700);
|
|
||||||
|
|
||||||
updateStream(STREAM_A, streamSizes.get(STREAM_A), true);
|
|
||||||
updateStream(STREAM_B, streamSizes.get(STREAM_B), true);
|
|
||||||
updateStream(STREAM_C, streamSizes.get(STREAM_C), true);
|
|
||||||
updateStream(STREAM_D, streamSizes.get(STREAM_D), true);
|
|
||||||
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes,
|
|
||||||
Arrays.asList(STREAM_A, STREAM_B, STREAM_C, STREAM_D)),
|
|
||||||
streamableBytesForTree(stream0));
|
|
||||||
assertEquals(
|
|
||||||
calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_A, STREAM_C, STREAM_D)),
|
|
||||||
streamableBytesForTree(streamA));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
|
|
||||||
streamableBytesForTree(streamB));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
|
|
||||||
streamableBytesForTree(streamC));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
|
|
||||||
streamableBytesForTree(streamD));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we shift the priority tree and verify priority bytes for each subtree are correct
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A B
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* After the tree shift:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* |
|
|
||||||
* A
|
|
||||||
* |
|
|
||||||
* B
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void subTreeBytesShouldBeCorrectWithRestructure() throws Http2Exception {
|
|
||||||
Http2Stream stream0 = connection.connectionStream();
|
|
||||||
Http2Stream streamA = connection.stream(STREAM_A);
|
|
||||||
Http2Stream streamB = connection.stream(STREAM_B);
|
|
||||||
Http2Stream streamC = connection.stream(STREAM_C);
|
|
||||||
Http2Stream streamD = connection.stream(STREAM_D);
|
|
||||||
|
|
||||||
// Send a bunch of data on each stream.
|
|
||||||
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
|
|
||||||
streamSizes.put(STREAM_A, (Integer) 400);
|
|
||||||
streamSizes.put(STREAM_B, (Integer) 500);
|
|
||||||
streamSizes.put(STREAM_C, (Integer) 600);
|
|
||||||
streamSizes.put(STREAM_D, (Integer) 700);
|
|
||||||
|
|
||||||
updateStream(STREAM_A, streamSizes.get(STREAM_A), true);
|
|
||||||
updateStream(STREAM_B, streamSizes.get(STREAM_B), true);
|
|
||||||
updateStream(STREAM_C, streamSizes.get(STREAM_C), true);
|
|
||||||
updateStream(STREAM_D, streamSizes.get(STREAM_D), true);
|
|
||||||
|
|
||||||
streamB.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, true);
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes,
|
|
||||||
Arrays.asList(STREAM_A, STREAM_B, STREAM_C, STREAM_D)),
|
|
||||||
streamableBytesForTree(stream0));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes,
|
|
||||||
Arrays.asList(STREAM_A, STREAM_B, STREAM_C, STREAM_D)),
|
|
||||||
streamableBytesForTree(streamA));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B, STREAM_C, STREAM_D)),
|
|
||||||
streamableBytesForTree(streamB));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
|
|
||||||
streamableBytesForTree(streamC));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
|
|
||||||
streamableBytesForTree(streamD));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we add a node to the priority tree and verify
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A B
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* After the tree shift:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A B
|
|
||||||
* |
|
|
||||||
* E
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void subTreeBytesShouldBeCorrectWithAddition() throws Http2Exception {
|
|
||||||
Http2Stream stream0 = connection.connectionStream();
|
|
||||||
Http2Stream streamA = connection.stream(STREAM_A);
|
|
||||||
Http2Stream streamB = connection.stream(STREAM_B);
|
|
||||||
Http2Stream streamC = connection.stream(STREAM_C);
|
|
||||||
Http2Stream streamD = connection.stream(STREAM_D);
|
|
||||||
|
|
||||||
Http2Stream streamE = connection.local().createStream(STREAM_E, false);
|
|
||||||
streamE.setPriority(STREAM_A, DEFAULT_PRIORITY_WEIGHT, true);
|
|
||||||
|
|
||||||
// Send a bunch of data on each stream.
|
|
||||||
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
|
|
||||||
streamSizes.put(STREAM_A, (Integer) 400);
|
|
||||||
streamSizes.put(STREAM_B, (Integer) 500);
|
|
||||||
streamSizes.put(STREAM_C, (Integer) 600);
|
|
||||||
streamSizes.put(STREAM_D, (Integer) 700);
|
|
||||||
streamSizes.put(STREAM_E, (Integer) 900);
|
|
||||||
|
|
||||||
updateStream(STREAM_A, streamSizes.get(STREAM_A), true);
|
|
||||||
updateStream(STREAM_B, streamSizes.get(STREAM_B), true);
|
|
||||||
updateStream(STREAM_C, streamSizes.get(STREAM_C), true);
|
|
||||||
updateStream(STREAM_D, streamSizes.get(STREAM_D), true);
|
|
||||||
updateStream(STREAM_E, streamSizes.get(STREAM_E), true);
|
|
||||||
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes,
|
|
||||||
Arrays.asList(STREAM_A, STREAM_B, STREAM_C, STREAM_D, STREAM_E)),
|
|
||||||
streamableBytesForTree(stream0));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes,
|
|
||||||
Arrays.asList(STREAM_A, STREAM_E, STREAM_C, STREAM_D)),
|
|
||||||
streamableBytesForTree(streamA));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
|
|
||||||
streamableBytesForTree(streamB));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
|
|
||||||
streamableBytesForTree(streamC));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
|
|
||||||
streamableBytesForTree(streamD));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_E, STREAM_C, STREAM_D)),
|
|
||||||
streamableBytesForTree(streamE));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we close an internal stream in the priority tree but tree should not change
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A B
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void subTreeBytesShouldBeCorrectWithInternalStreamClose() throws Http2Exception {
|
|
||||||
Http2Stream stream0 = connection.connectionStream();
|
|
||||||
Http2Stream streamA = connection.stream(STREAM_A);
|
|
||||||
Http2Stream streamB = connection.stream(STREAM_B);
|
|
||||||
Http2Stream streamC = connection.stream(STREAM_C);
|
|
||||||
Http2Stream streamD = connection.stream(STREAM_D);
|
|
||||||
|
|
||||||
// Send a bunch of data on each stream.
|
|
||||||
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
|
|
||||||
streamSizes.put(STREAM_A, (Integer) 400);
|
|
||||||
streamSizes.put(STREAM_B, (Integer) 500);
|
|
||||||
streamSizes.put(STREAM_C, (Integer) 600);
|
|
||||||
streamSizes.put(STREAM_D, (Integer) 700);
|
|
||||||
|
|
||||||
updateStream(STREAM_A, streamSizes.get(STREAM_A), true);
|
|
||||||
updateStream(STREAM_B, streamSizes.get(STREAM_B), true);
|
|
||||||
updateStream(STREAM_C, streamSizes.get(STREAM_C), true);
|
|
||||||
updateStream(STREAM_D, streamSizes.get(STREAM_D), true);
|
|
||||||
|
|
||||||
streamA.close();
|
|
||||||
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_B, STREAM_C, STREAM_D)),
|
|
||||||
streamableBytesForTree(stream0));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_C, STREAM_D)),
|
|
||||||
streamableBytesForTree(streamA));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
|
|
||||||
streamableBytesForTree(streamB));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_C)),
|
|
||||||
streamableBytesForTree(streamC));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
|
|
||||||
streamableBytesForTree(streamD));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In this test, we close a leaf stream in the priority tree and verify
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A B
|
|
||||||
* / \
|
|
||||||
* C D
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* After the close:
|
|
||||||
* <pre>
|
|
||||||
* 0
|
|
||||||
* / \
|
|
||||||
* A B
|
|
||||||
* |
|
|
||||||
* D
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void subTreeBytesShouldBeCorrectWithLeafStreamClose() throws Http2Exception {
|
|
||||||
Http2Stream stream0 = connection.connectionStream();
|
|
||||||
Http2Stream streamA = connection.stream(STREAM_A);
|
|
||||||
Http2Stream streamB = connection.stream(STREAM_B);
|
|
||||||
Http2Stream streamC = connection.stream(STREAM_C);
|
|
||||||
Http2Stream streamD = connection.stream(STREAM_D);
|
|
||||||
|
|
||||||
// Send a bunch of data on each stream.
|
|
||||||
final IntObjectMap<Integer> streamSizes = new IntObjectHashMap<Integer>(4);
|
|
||||||
streamSizes.put(STREAM_A, (Integer) 400);
|
|
||||||
streamSizes.put(STREAM_B, (Integer) 500);
|
|
||||||
streamSizes.put(STREAM_C, (Integer) 600);
|
|
||||||
streamSizes.put(STREAM_D, (Integer) 700);
|
|
||||||
|
|
||||||
updateStream(STREAM_A, streamSizes.get(STREAM_A), true);
|
|
||||||
updateStream(STREAM_B, streamSizes.get(STREAM_B), true);
|
|
||||||
updateStream(STREAM_C, streamSizes.get(STREAM_C), true);
|
|
||||||
updateStream(STREAM_D, streamSizes.get(STREAM_D), true);
|
|
||||||
|
|
||||||
streamC.close();
|
|
||||||
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_A, STREAM_B, STREAM_D)),
|
|
||||||
streamableBytesForTree(stream0));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Arrays.asList(STREAM_A, STREAM_D)),
|
|
||||||
streamableBytesForTree(streamA));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_B)),
|
|
||||||
streamableBytesForTree(streamB));
|
|
||||||
assertEquals(0, streamableBytesForTree(streamC));
|
|
||||||
assertEquals(calculateStreamSizeSum(streamSizes, Collections.singletonList(STREAM_D)),
|
|
||||||
streamableBytesForTree(streamD));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Http2Stream stream(int streamId) {
|
|
||||||
return connection.stream(streamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateStream(final int streamId, final int pendingBytes, final boolean hasFrame) {
|
|
||||||
final Http2Stream stream = stream(streamId);
|
|
||||||
distributor.updateStreamableBytes(new StreamByteDistributor.StreamState() {
|
|
||||||
@Override
|
|
||||||
public Http2Stream stream() {
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int pendingBytes() {
|
|
||||||
return pendingBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasFrame() {
|
|
||||||
return hasFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int windowSize() {
|
|
||||||
return pendingBytes;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPriority(int streamId, int parent, int weight, boolean exclusive) throws Http2Exception {
|
|
||||||
stream(streamId).setPriority(parent, (short) weight, exclusive);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long streamableBytesForTree(Http2Stream stream) {
|
|
||||||
return distributor.unallocatedStreamableBytesForTree(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean write(int numBytes) throws Http2Exception {
|
|
||||||
return distributor.distribute(numBytes, writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyWrite(int streamId, int numBytes) {
|
|
||||||
verify(writer).write(same(stream(streamId)), eq(numBytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyWrite(VerificationMode mode, int streamId, int numBytes) {
|
|
||||||
verify(writer, mode).write(same(stream(streamId)), eq(numBytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyWriteWithDelta(int streamId, int numBytes, int delta) {
|
|
||||||
verify(writer).write(same(stream(streamId)), (int) AdditionalMatchers.eq(numBytes, delta));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static long calculateStreamSizeSum(IntObjectMap<Integer> streamSizes, List<Integer> streamIds) {
|
|
||||||
long sum = 0;
|
|
||||||
for (Integer streamId : streamIds) {
|
|
||||||
Integer streamSize = streamSizes.get(streamId);
|
|
||||||
if (streamSize != null) {
|
|
||||||
sum += streamSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user