Optimizing user-defined stream properties.
Motivation: Streams currently maintain a hash map of user-defined properties, which has been shown to add significant memory overhead as well as being a performance bottleneck for lookup of frequently used properties. Modifications: Modifying the connection/stream to use an array as the storage of user-defined properties, indexed by the class that identifies the index into the array where the property is stored. Result: Stream processing performance should be improved.
This commit is contained in:
parent
b426fb1618
commit
70a2608325
@ -38,16 +38,6 @@ import io.netty.util.ByteString;
|
|||||||
* stream. The compression provided by this class will be applied to the data for the entire stream.
|
* stream. The compression provided by this class will be applied to the data for the entire stream.
|
||||||
*/
|
*/
|
||||||
public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionEncoder {
|
public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionEncoder {
|
||||||
private static final Http2ConnectionAdapter CLEAN_UP_LISTENER = new Http2ConnectionAdapter() {
|
|
||||||
@Override
|
|
||||||
public void onStreamRemoved(Http2Stream stream) {
|
|
||||||
final EmbeddedChannel compressor = stream.getProperty(CompressorHttp2ConnectionEncoder.class);
|
|
||||||
if (compressor != null) {
|
|
||||||
cleanup(stream, compressor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static final int DEFAULT_COMPRESSION_LEVEL = 6;
|
public static final int DEFAULT_COMPRESSION_LEVEL = 6;
|
||||||
public static final int DEFAULT_WINDOW_BITS = 15;
|
public static final int DEFAULT_WINDOW_BITS = 15;
|
||||||
public static final int DEFAULT_MEM_LEVEL = 8;
|
public static final int DEFAULT_MEM_LEVEL = 8;
|
||||||
@ -55,6 +45,7 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
|
|||||||
private final int compressionLevel;
|
private final int compressionLevel;
|
||||||
private final int windowBits;
|
private final int windowBits;
|
||||||
private final int memLevel;
|
private final int memLevel;
|
||||||
|
private final Http2Connection.PropertyKey propertyKey;
|
||||||
|
|
||||||
public CompressorHttp2ConnectionEncoder(Http2ConnectionEncoder delegate) {
|
public CompressorHttp2ConnectionEncoder(Http2ConnectionEncoder delegate) {
|
||||||
this(delegate, DEFAULT_COMPRESSION_LEVEL, DEFAULT_WINDOW_BITS, DEFAULT_MEM_LEVEL);
|
this(delegate, DEFAULT_COMPRESSION_LEVEL, DEFAULT_WINDOW_BITS, DEFAULT_MEM_LEVEL);
|
||||||
@ -76,15 +67,23 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
|
|||||||
this.windowBits = windowBits;
|
this.windowBits = windowBits;
|
||||||
this.memLevel = memLevel;
|
this.memLevel = memLevel;
|
||||||
|
|
||||||
connection().addListener(CLEAN_UP_LISTENER);
|
propertyKey = connection().newKey();
|
||||||
|
connection().addListener(new Http2ConnectionAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onStreamRemoved(Http2Stream stream) {
|
||||||
|
final EmbeddedChannel compressor = stream.getProperty(propertyKey);
|
||||||
|
if (compressor != null) {
|
||||||
|
cleanup(stream, compressor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChannelFuture writeData(final ChannelHandlerContext ctx, final int streamId, ByteBuf data, int padding,
|
public ChannelFuture writeData(final ChannelHandlerContext ctx, final int streamId, ByteBuf data, int padding,
|
||||||
final boolean endOfStream, ChannelPromise promise) {
|
final boolean endOfStream, ChannelPromise promise) {
|
||||||
final Http2Stream stream = connection().stream(streamId);
|
final Http2Stream stream = connection().stream(streamId);
|
||||||
final EmbeddedChannel channel = stream == null ? null :
|
final EmbeddedChannel channel = stream == null ? null : (EmbeddedChannel) stream.getProperty(propertyKey);
|
||||||
(EmbeddedChannel) stream.getProperty(CompressorHttp2ConnectionEncoder.class);
|
|
||||||
if (channel == null) {
|
if (channel == null) {
|
||||||
// The compressor may be null if no compatible encoding type was found in this stream's headers
|
// The compressor may be null if no compatible encoding type was found in this stream's headers
|
||||||
return super.writeData(ctx, streamId, data, padding, endOfStream, promise);
|
return super.writeData(ctx, streamId, data, padding, endOfStream, promise);
|
||||||
@ -216,7 +215,7 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EmbeddedChannel compressor = stream.getProperty(CompressorHttp2ConnectionEncoder.class);
|
EmbeddedChannel compressor = stream.getProperty(propertyKey);
|
||||||
if (compressor == null) {
|
if (compressor == null) {
|
||||||
if (!endOfStream) {
|
if (!endOfStream) {
|
||||||
ByteString encoding = headers.get(CONTENT_ENCODING);
|
ByteString encoding = headers.get(CONTENT_ENCODING);
|
||||||
@ -226,7 +225,7 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
|
|||||||
try {
|
try {
|
||||||
compressor = newContentCompressor(encoding);
|
compressor = newContentCompressor(encoding);
|
||||||
if (compressor != null) {
|
if (compressor != null) {
|
||||||
stream.setProperty(CompressorHttp2ConnectionEncoder.class, compressor);
|
stream.setProperty(propertyKey, compressor);
|
||||||
ByteString targetContentEncoding = getTargetContentEncoding(encoding);
|
ByteString targetContentEncoding = getTargetContentEncoding(encoding);
|
||||||
if (IDENTITY.equals(targetContentEncoding)) {
|
if (IDENTITY.equals(targetContentEncoding)) {
|
||||||
headers.remove(CONTENT_ENCODING);
|
headers.remove(CONTENT_ENCODING);
|
||||||
@ -256,7 +255,7 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
|
|||||||
* @param stream The stream for which {@code compressor} is the compressor for
|
* @param stream The stream for which {@code compressor} is the compressor for
|
||||||
* @param compressor The compressor for {@code stream}
|
* @param compressor The compressor for {@code stream}
|
||||||
*/
|
*/
|
||||||
private static void cleanup(Http2Stream stream, EmbeddedChannel compressor) {
|
void cleanup(Http2Stream stream, EmbeddedChannel compressor) {
|
||||||
if (compressor.finish()) {
|
if (compressor.finish()) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
final ByteBuf buf = compressor.readOutbound();
|
final ByteBuf buf = compressor.readOutbound();
|
||||||
@ -267,7 +266,7 @@ public class CompressorHttp2ConnectionEncoder extends DecoratingHttp2ConnectionE
|
|||||||
buf.release();
|
buf.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stream.removeProperty(CompressorHttp2ConnectionEncoder.class);
|
stream.removeProperty(propertyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,6 +37,7 @@ import io.netty.handler.codec.http2.Http2Stream.State;
|
|||||||
import io.netty.util.collection.IntObjectHashMap;
|
import io.netty.util.collection.IntObjectHashMap;
|
||||||
import io.netty.util.collection.IntObjectMap;
|
import io.netty.util.collection.IntObjectMap;
|
||||||
import io.netty.util.collection.PrimitiveCollections;
|
import io.netty.util.collection.PrimitiveCollections;
|
||||||
|
import io.netty.util.internal.EmptyArrays;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
import io.netty.util.internal.SystemPropertyUtil;
|
import io.netty.util.internal.SystemPropertyUtil;
|
||||||
import io.netty.util.internal.logging.InternalLogger;
|
import io.netty.util.internal.logging.InternalLogger;
|
||||||
@ -44,11 +45,10 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
|
|||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -59,9 +59,11 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultHttp2Connection.class);
|
private static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultHttp2Connection.class);
|
||||||
// Fields accessed by inner classes
|
// Fields accessed by inner classes
|
||||||
final IntObjectMap<Http2Stream> streamMap = new IntObjectHashMap<Http2Stream>();
|
final IntObjectMap<Http2Stream> streamMap = new IntObjectHashMap<Http2Stream>();
|
||||||
|
final PropertyKeyRegistry propertyKeyRegistry = new PropertyKeyRegistry();
|
||||||
final ConnectionStream connectionStream = new ConnectionStream();
|
final ConnectionStream connectionStream = new ConnectionStream();
|
||||||
final DefaultEndpoint<Http2LocalFlowController> localEndpoint;
|
final DefaultEndpoint<Http2LocalFlowController> localEndpoint;
|
||||||
final DefaultEndpoint<Http2RemoteFlowController> remoteEndpoint;
|
final DefaultEndpoint<Http2RemoteFlowController> remoteEndpoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The initial size of the children map is chosen to be conservative on initial memory allocations under
|
* The initial size of the children map is chosen to be conservative on initial memory allocations under
|
||||||
* the assumption that most streams will have a small number of children. This choice may be
|
* the assumption that most streams will have a small number of children. This choice may be
|
||||||
@ -265,11 +267,28 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PropertyKey newKey() {
|
||||||
|
return propertyKeyRegistry.newKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that the key is valid and returns it as the internal {@link DefaultPropertyKey} type.
|
||||||
|
*
|
||||||
|
* @throws NullPointerException if the key is {@code null}.
|
||||||
|
* @throws ClassCastException if the key is not of type {@link DefaultPropertyKey}.
|
||||||
|
* @throws IllegalArgumentException if the key was not created by this connection.
|
||||||
|
*/
|
||||||
|
final DefaultPropertyKey verifyKey(PropertyKey key) {
|
||||||
|
return checkNotNull((DefaultPropertyKey) key, "key").verifyConnection(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple stream implementation. Streams can be compared to each other by priority.
|
* Simple stream implementation. Streams can be compared to each other by priority.
|
||||||
*/
|
*/
|
||||||
private class DefaultStream implements Http2Stream {
|
private class DefaultStream implements Http2Stream {
|
||||||
private final int id;
|
private final int id;
|
||||||
|
private final PropertyMap properties = new PropertyMap();
|
||||||
private State state;
|
private State state;
|
||||||
private short weight = DEFAULT_PRIORITY_WEIGHT;
|
private short weight = DEFAULT_PRIORITY_WEIGHT;
|
||||||
private DefaultStream parent;
|
private DefaultStream parent;
|
||||||
@ -277,14 +296,10 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
private int totalChildWeights;
|
private int totalChildWeights;
|
||||||
private int prioritizableForTree = 1;
|
private int prioritizableForTree = 1;
|
||||||
private boolean resetSent;
|
private boolean resetSent;
|
||||||
private PropertyMap data;
|
|
||||||
private FlowControlState localFlowState;
|
|
||||||
private FlowControlState remoteFlowState;
|
|
||||||
|
|
||||||
DefaultStream(int id, State state) {
|
DefaultStream(int id, State state) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
data = new LazyPropertyMap(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -297,26 +312,6 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public FlowControlState localFlowState() {
|
|
||||||
return localFlowState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void localFlowState(FlowControlState state) {
|
|
||||||
localFlowState = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FlowControlState remoteFlowState() {
|
|
||||||
return remoteFlowState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remoteFlowState(FlowControlState state) {
|
|
||||||
remoteFlowState = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isResetSent() {
|
public boolean isResetSent() {
|
||||||
return resetSent;
|
return resetSent;
|
||||||
@ -329,18 +324,18 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Object setProperty(Object key, Object value) {
|
public final <V> V setProperty(PropertyKey key, V value) {
|
||||||
return data.put(key, value);
|
return properties.add(verifyKey(key), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final <V> V getProperty(Object key) {
|
public final <V> V getProperty(PropertyKey key) {
|
||||||
return data.get(key);
|
return properties.get(verifyKey(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final <V> V removeProperty(Object key) {
|
public final <V> V removeProperty(PropertyKey key) {
|
||||||
return data.remove(key);
|
return properties.remove(verifyKey(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -694,74 +689,44 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows the data map to be lazily initialized for {@link DefaultStream}.
|
* Provides the lazy initialization for the {@link DefaultStream} data map.
|
||||||
*/
|
*/
|
||||||
private interface PropertyMap {
|
private class PropertyMap {
|
||||||
Object put(Object key, Object value);
|
Object[] values = EmptyArrays.EMPTY_OBJECTS;
|
||||||
|
|
||||||
<V> V get(Object key);
|
<V> V add(DefaultPropertyKey key, V value) {
|
||||||
|
resizeIfNecessary(key.index);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
V prevValue = (V) values[key.index];
|
||||||
|
values[key.index] = value;
|
||||||
|
return prevValue;
|
||||||
|
}
|
||||||
|
|
||||||
<V> V remove(Object key);
|
@SuppressWarnings("unchecked")
|
||||||
}
|
<V> V get(DefaultPropertyKey key) {
|
||||||
|
if (key.index >= values.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (V) values[key.index];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
@SuppressWarnings("unchecked")
|
||||||
* Provides actual {@link HashMap} functionality for {@link DefaultStream}'s application data.
|
<V> V remove(DefaultPropertyKey key) {
|
||||||
*/
|
V prevValue = null;
|
||||||
private static final class DefaultProperyMap implements PropertyMap {
|
if (key.index < values.length) {
|
||||||
private final Map<Object, Object> data;
|
prevValue = (V) values[key.index];
|
||||||
|
values[key.index] = null;
|
||||||
|
}
|
||||||
|
return prevValue;
|
||||||
|
}
|
||||||
|
|
||||||
DefaultProperyMap(int initialSize) {
|
void resizeIfNecessary(int index) {
|
||||||
data = new HashMap<Object, Object>(initialSize);
|
if (index >= values.length) {
|
||||||
}
|
values = Arrays.copyOf(values, propertyKeyRegistry.size());
|
||||||
|
}
|
||||||
@Override
|
}
|
||||||
public Object put(Object key, Object value) {
|
|
||||||
return data.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public <V> V get(Object key) {
|
|
||||||
return (V) data.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public <V> V remove(Object key) {
|
|
||||||
return (V) data.remove(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides the lazy initialization for the {@link DefaultStream} data map.
|
|
||||||
*/
|
|
||||||
private static final class LazyPropertyMap implements PropertyMap {
|
|
||||||
private static final int DEFAULT_INITIAL_SIZE = 4;
|
|
||||||
private final DefaultStream stream;
|
|
||||||
|
|
||||||
LazyPropertyMap(DefaultStream stream) {
|
|
||||||
this.stream = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object put(Object key, Object value) {
|
|
||||||
stream.data = new DefaultProperyMap(DEFAULT_INITIAL_SIZE);
|
|
||||||
return stream.data.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <V> V get(Object key) {
|
|
||||||
stream.data = new DefaultProperyMap(DEFAULT_INITIAL_SIZE);
|
|
||||||
return stream.data.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <V> V remove(Object key) {
|
|
||||||
stream.data = new DefaultProperyMap(DEFAULT_INITIAL_SIZE);
|
|
||||||
return stream.data.remove(key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1190,4 +1155,43 @@ public class DefaultHttp2Connection implements Http2Connection {
|
|||||||
return pendingIterations == 0;
|
return pendingIterations == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link PropertyKey} that specifies the index position of the property.
|
||||||
|
*/
|
||||||
|
final class DefaultPropertyKey implements PropertyKey {
|
||||||
|
private final int index;
|
||||||
|
|
||||||
|
DefaultPropertyKey(int index) {
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultPropertyKey verifyConnection(Http2Connection connection) {
|
||||||
|
if (connection != DefaultHttp2Connection.this) {
|
||||||
|
throw new IllegalArgumentException("Using a key that was not created by this connection");
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registry of all stream property keys known by this connection.
|
||||||
|
*/
|
||||||
|
private class PropertyKeyRegistry {
|
||||||
|
final List<DefaultPropertyKey> keys = new ArrayList<DefaultPropertyKey>(4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new property key.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
DefaultPropertyKey newKey() {
|
||||||
|
DefaultPropertyKey key = new DefaultPropertyKey(keys.size());
|
||||||
|
keys.add(key);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
int size() {
|
||||||
|
return keys.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,11 @@ import static io.netty.handler.codec.http2.Http2Exception.streamError;
|
|||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.http2.Http2Exception.CompositeStreamException;
|
import io.netty.handler.codec.http2.Http2Exception.CompositeStreamException;
|
||||||
import io.netty.handler.codec.http2.Http2Exception.StreamException;
|
import io.netty.handler.codec.http2.Http2Exception.StreamException;
|
||||||
import io.netty.handler.codec.http2.Http2Stream.FlowControlState;
|
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,6 +45,7 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
|
|
||||||
private final Http2Connection connection;
|
private final Http2Connection connection;
|
||||||
private final Http2FrameWriter frameWriter;
|
private final Http2FrameWriter frameWriter;
|
||||||
|
private final Http2Connection.PropertyKey stateKey;
|
||||||
private ChannelHandlerContext ctx;
|
private ChannelHandlerContext ctx;
|
||||||
private volatile float windowUpdateRatio;
|
private volatile float windowUpdateRatio;
|
||||||
private volatile int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
private volatile int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||||
@ -60,8 +61,9 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
windowUpdateRatio(windowUpdateRatio);
|
windowUpdateRatio(windowUpdateRatio);
|
||||||
|
|
||||||
// Add a flow state for the connection.
|
// Add a flow state for the connection.
|
||||||
connection.connectionStream().localFlowState(
|
stateKey = connection.newKey();
|
||||||
new DefaultState(connection.connectionStream(), initialWindowSize));
|
connection.connectionStream()
|
||||||
|
.setProperty(stateKey, new DefaultState(connection.connectionStream(), initialWindowSize));
|
||||||
|
|
||||||
// Register for notification of new streams.
|
// Register for notification of new streams.
|
||||||
connection.addListener(new Http2ConnectionAdapter() {
|
connection.addListener(new Http2ConnectionAdapter() {
|
||||||
@ -69,14 +71,14 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
public void onStreamAdded(Http2Stream stream) {
|
public void onStreamAdded(Http2Stream stream) {
|
||||||
// Unconditionally used the reduced flow control state because it requires no object allocation
|
// Unconditionally used the reduced flow control state because it requires no object allocation
|
||||||
// and the DefaultFlowState will be allocated in onStreamActive.
|
// and the DefaultFlowState will be allocated in onStreamActive.
|
||||||
stream.localFlowState(REDUCED_FLOW_STATE);
|
stream.setProperty(stateKey, REDUCED_FLOW_STATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStreamActive(Http2Stream stream) {
|
public void onStreamActive(Http2Stream stream) {
|
||||||
// Need to be sure the stream's initial window is adjusted for SETTINGS
|
// Need to be sure the stream's initial window is adjusted for SETTINGS
|
||||||
// frames which may have been exchanged while it was in IDLE
|
// frames which may have been exchanged while it was in IDLE
|
||||||
stream.localFlowState(new DefaultState(stream, initialWindowSize));
|
stream.setProperty(stateKey, new DefaultState(stream, initialWindowSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -96,7 +98,7 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
// Unconditionally reduce the amount of memory required for flow control because there is no
|
// Unconditionally reduce the amount of memory required for flow control because there is no
|
||||||
// object allocation costs associated with doing so and the stream will not have any more
|
// object allocation costs associated with doing so and the stream will not have any more
|
||||||
// local flow control state to keep track of anymore.
|
// local flow control state to keep track of anymore.
|
||||||
stream.localFlowState(REDUCED_FLOW_STATE);
|
stream.setProperty(stateKey, REDUCED_FLOW_STATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -117,6 +119,16 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
return initialWindowSize;
|
return initialWindowSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int windowSize(Http2Stream stream) {
|
||||||
|
return state(stream).windowSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int initialWindowSize(Http2Stream stream) {
|
||||||
|
return state(stream).initialWindowSize();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta) throws Http2Exception {
|
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta) throws Http2Exception {
|
||||||
checkNotNull(ctx, "ctx");
|
checkNotNull(ctx, "ctx");
|
||||||
@ -230,11 +242,12 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
}
|
}
|
||||||
|
|
||||||
private FlowState connectionState() {
|
private FlowState connectionState() {
|
||||||
return (FlowState) connection.connectionStream().localFlowState();
|
return connection.connectionStream().getProperty(stateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FlowState state(Http2Stream stream) {
|
private FlowState state(Http2Stream stream) {
|
||||||
return (FlowState) checkNotNull(stream, "stream").localFlowState();
|
checkNotNull(stream, "stream");
|
||||||
|
return stream.getProperty(stateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isClosed(Http2Stream stream) {
|
private static boolean isClosed(Http2Stream stream) {
|
||||||
@ -352,8 +365,7 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void returnProcessedBytes(int delta) throws Http2Exception {
|
||||||
public void returnProcessedBytes(int delta) throws Http2Exception {
|
|
||||||
if (processedWindow - delta < window) {
|
if (processedWindow - delta < window) {
|
||||||
throw streamError(stream.id(), INTERNAL_ERROR,
|
throw streamError(stream.id(), INTERNAL_ERROR,
|
||||||
"Attempting to return too many bytes for stream %d", stream.id());
|
"Attempting to return too many bytes for stream %d", stream.id());
|
||||||
@ -410,6 +422,7 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
* be exchanged.
|
* be exchanged.
|
||||||
*/
|
*/
|
||||||
private static final FlowState REDUCED_FLOW_STATE = new FlowState() {
|
private static final FlowState REDUCED_FLOW_STATE = new FlowState() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int windowSize() {
|
public int windowSize() {
|
||||||
return 0;
|
return 0;
|
||||||
@ -466,11 +479,6 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
// the peer has not yet acknowledged this peer being activated.
|
// the peer has not yet acknowledged this peer being activated.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void returnProcessedBytes(int delta) throws Http2Exception {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endOfStream(boolean endOfStream) {
|
public void endOfStream(boolean endOfStream) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
@ -478,9 +486,14 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstraction around {@link FlowControlState} which provides specific extensions used by local flow control.
|
* An abstraction which provides specific extensions used by local flow control.
|
||||||
*/
|
*/
|
||||||
private interface FlowState extends FlowControlState {
|
private interface FlowState {
|
||||||
|
|
||||||
|
int windowSize();
|
||||||
|
|
||||||
|
int initialWindowSize();
|
||||||
|
|
||||||
void window(int initialWindowSize);
|
void window(int initialWindowSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -516,18 +529,13 @@ public class DefaultHttp2LocalFlowController implements Http2LocalFlowController
|
|||||||
*/
|
*/
|
||||||
void incrementFlowControlWindows(int delta) throws Http2Exception;
|
void incrementFlowControlWindows(int delta) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the processed bytes for this stream.
|
|
||||||
*/
|
|
||||||
void returnProcessedBytes(int delta) throws Http2Exception;
|
|
||||||
|
|
||||||
void endOfStream(boolean endOfStream);
|
void endOfStream(boolean endOfStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a means to iterate over all active streams and increment the flow control windows.
|
* Provides a means to iterate over all active streams and increment the flow control windows.
|
||||||
*/
|
*/
|
||||||
private static final class WindowUpdateVisitor implements Http2StreamVisitor {
|
private final class WindowUpdateVisitor implements Http2StreamVisitor {
|
||||||
private CompositeStreamException compositeException;
|
private CompositeStreamException compositeException;
|
||||||
private final int delta;
|
private final int delta;
|
||||||
|
|
||||||
|
@ -18,13 +18,13 @@ import static io.netty.handler.codec.http2.Http2CodecUtil.CONNECTION_STREAM_ID;
|
|||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
|
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
|
||||||
import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR;
|
import static io.netty.handler.codec.http2.Http2Error.FLOW_CONTROL_ERROR;
|
||||||
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
||||||
import static io.netty.handler.codec.http2.Http2Stream.State.IDLE;
|
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.streamError;
|
import static io.netty.handler.codec.http2.Http2Exception.streamError;
|
||||||
|
import static io.netty.handler.codec.http2.Http2Stream.State.IDLE;
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.http2.Http2Stream.FlowControlState;
|
|
||||||
import io.netty.handler.codec.http2.Http2Stream.State;
|
import io.netty.handler.codec.http2.Http2Stream.State;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
@ -35,7 +35,7 @@ import java.util.Deque;
|
|||||||
* Basic implementation of {@link Http2RemoteFlowController}.
|
* Basic implementation of {@link Http2RemoteFlowController}.
|
||||||
*/
|
*/
|
||||||
public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowController {
|
public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowController {
|
||||||
private static final Http2StreamVisitor WRITE_ALLOCATED_BYTES = new Http2StreamVisitor() {
|
private final Http2StreamVisitor WRITE_ALLOCATED_BYTES = new Http2StreamVisitor() {
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(Http2Stream stream) {
|
public boolean visit(Http2Stream stream) {
|
||||||
state(stream).writeAllocatedBytes();
|
state(stream).writeAllocatedBytes();
|
||||||
@ -43,6 +43,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final Http2Connection connection;
|
private final Http2Connection connection;
|
||||||
|
private final Http2Connection.PropertyKey stateKey;
|
||||||
private int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
private int initialWindowSize = DEFAULT_WINDOW_SIZE;
|
||||||
private ChannelHandlerContext ctx;
|
private ChannelHandlerContext ctx;
|
||||||
private boolean needFlush;
|
private boolean needFlush;
|
||||||
@ -51,7 +52,8 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
this.connection = checkNotNull(connection, "connection");
|
this.connection = checkNotNull(connection, "connection");
|
||||||
|
|
||||||
// Add a flow state for the connection.
|
// Add a flow state for the connection.
|
||||||
connection.connectionStream().remoteFlowState(
|
stateKey = connection.newKey();
|
||||||
|
connection.connectionStream().setProperty(stateKey,
|
||||||
new DefaultState(connection.connectionStream(), initialWindowSize));
|
new DefaultState(connection.connectionStream(), initialWindowSize));
|
||||||
|
|
||||||
// Register for notification of new streams.
|
// Register for notification of new streams.
|
||||||
@ -60,7 +62,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
public void onStreamAdded(Http2Stream stream) {
|
public void onStreamAdded(Http2Stream stream) {
|
||||||
// If the stream state is not open then the stream is not yet eligible for flow controlled frames and
|
// If the stream state is not open then the stream is not yet eligible for flow controlled frames and
|
||||||
// only requires the ReducedFlowState. Otherwise the full amount of memory is required.
|
// only requires the ReducedFlowState. Otherwise the full amount of memory is required.
|
||||||
stream.remoteFlowState(stream.state() == IDLE ?
|
stream.setProperty(stateKey, stream.state() == IDLE ?
|
||||||
new ReducedState(stream) :
|
new ReducedState(stream) :
|
||||||
new DefaultState(stream, 0));
|
new DefaultState(stream, 0));
|
||||||
}
|
}
|
||||||
@ -69,10 +71,11 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
public void onStreamActive(Http2Stream stream) {
|
public void onStreamActive(Http2Stream stream) {
|
||||||
// If the object was previously created, but later activated then we have to ensure
|
// If the object was previously created, but later activated then we have to ensure
|
||||||
// the full state is allocated and the proper initialWindowSize is used.
|
// the full state is allocated and the proper initialWindowSize is used.
|
||||||
if (stream.remoteFlowState().getClass() == DefaultState.class) {
|
AbstractState state = state(stream);
|
||||||
state(stream).window(initialWindowSize);
|
if (state.getClass() == DefaultState.class) {
|
||||||
|
state.window(initialWindowSize);
|
||||||
} else {
|
} else {
|
||||||
stream.remoteFlowState(new DefaultState(state(stream), initialWindowSize));
|
stream.setProperty(stateKey, new DefaultState(state, initialWindowSize));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +90,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
// decrease the amount of memory required for this stream because no flow controlled frames can
|
// decrease the amount of memory required for this stream because no flow controlled frames can
|
||||||
// be exchanged on this stream
|
// be exchanged on this stream
|
||||||
if (stream.prioritizableForTree() != 0) {
|
if (stream.prioritizableForTree() != 0) {
|
||||||
stream.remoteFlowState(new ReducedState(state));
|
stream.setProperty(stateKey, new ReducedState(state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +164,16 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
return initialWindowSize;
|
return initialWindowSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int windowSize(Http2Stream stream) {
|
||||||
|
return state(stream).windowSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int initialWindowSize(Http2Stream stream) {
|
||||||
|
return state(stream).initialWindowSize();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta) throws Http2Exception {
|
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta) throws Http2Exception {
|
||||||
if (stream.id() == CONNECTION_STREAM_ID) {
|
if (stream.id() == CONNECTION_STREAM_ID) {
|
||||||
@ -209,12 +222,12 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
return state(stream).streamableBytesForTree();
|
return state(stream).streamableBytesForTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AbstractState state(Http2Stream stream) {
|
private AbstractState state(Http2Stream stream) {
|
||||||
return (AbstractState) checkNotNull(stream, "stream").remoteFlowState();
|
return (AbstractState) checkNotNull(stream, "stream").getProperty(stateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AbstractState connectionState() {
|
private AbstractState connectionState() {
|
||||||
return (AbstractState) connection.connectionStream().remoteFlowState();
|
return (AbstractState) connection.connectionStream().getProperty(stateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -261,7 +274,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
* @param connectionWindowSize The connection window this is available for use at this point in the tree.
|
* @param connectionWindowSize The connection window this is available for use at this point in the tree.
|
||||||
* @return An object summarizing the write and allocation results.
|
* @return An object summarizing the write and allocation results.
|
||||||
*/
|
*/
|
||||||
static int allocateBytesForTree(Http2Stream parent, int connectionWindowSize) throws Http2Exception {
|
int allocateBytesForTree(Http2Stream parent, int connectionWindowSize) throws Http2Exception {
|
||||||
AbstractState state = state(parent);
|
AbstractState state = state(parent);
|
||||||
if (state.streamableBytesForTree() <= 0) {
|
if (state.streamableBytesForTree() <= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -289,7 +302,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
* A {@link Http2StreamVisitor} that performs the HTTP/2 priority algorithm to distribute the available connection
|
* A {@link Http2StreamVisitor} that performs the HTTP/2 priority algorithm to distribute the available connection
|
||||||
* window appropriately to the children of a given stream.
|
* window appropriately to the children of a given stream.
|
||||||
*/
|
*/
|
||||||
private static final class ChildFeeder implements Http2StreamVisitor {
|
private final class ChildFeeder implements Http2StreamVisitor {
|
||||||
final int maxSize;
|
final int maxSize;
|
||||||
int totalWeight;
|
int totalWeight;
|
||||||
int connectionWindow;
|
int connectionWindow;
|
||||||
@ -399,7 +412,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
* A simplified version of {@link ChildFeeder} that is only used when all streamable bytes fit within the
|
* A simplified version of {@link ChildFeeder} that is only used when all streamable bytes fit within the
|
||||||
* available connection window.
|
* available connection window.
|
||||||
*/
|
*/
|
||||||
private static final class SimpleChildFeeder implements Http2StreamVisitor {
|
private final class SimpleChildFeeder implements Http2StreamVisitor {
|
||||||
int bytesAllocated;
|
int bytesAllocated;
|
||||||
int connectionWindow;
|
int connectionWindow;
|
||||||
|
|
||||||
@ -437,35 +450,35 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
// Set to true if cancel() was called.
|
// Set to true if cancel() was called.
|
||||||
private boolean cancelled;
|
private boolean cancelled;
|
||||||
|
|
||||||
public DefaultState(Http2Stream stream, int initialWindowSize) {
|
DefaultState(Http2Stream stream, int initialWindowSize) {
|
||||||
super(stream);
|
super(stream);
|
||||||
window(initialWindowSize);
|
window(initialWindowSize);
|
||||||
pendingWriteQueue = new ArrayDeque<FlowControlled>(2);
|
pendingWriteQueue = new ArrayDeque<FlowControlled>(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultState(AbstractState existingState, int initialWindowSize) {
|
DefaultState(AbstractState existingState, int initialWindowSize) {
|
||||||
super(existingState);
|
super(existingState);
|
||||||
window(initialWindowSize);
|
window(initialWindowSize);
|
||||||
pendingWriteQueue = new ArrayDeque<FlowControlled>(2);
|
pendingWriteQueue = new ArrayDeque<FlowControlled>(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int windowSize() {
|
int windowSize() {
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int initialWindowSize() {
|
int initialWindowSize() {
|
||||||
return initialWindowSize;
|
return initialWindowSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void window(int initialWindowSize) {
|
void window(int initialWindowSize) {
|
||||||
window = initialWindowSize;
|
window = initialWindowSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void allocate(int bytes) {
|
void allocate(int bytes) {
|
||||||
allocated += bytes;
|
allocated += bytes;
|
||||||
// Also artificially reduce the streamable bytes for this tree to give the appearance
|
// 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
|
// that the data has been written. This will be restored before the allocated bytes are
|
||||||
@ -474,7 +487,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeAllocatedBytes() {
|
void writeAllocatedBytes() {
|
||||||
int numBytes = allocated;
|
int numBytes = allocated;
|
||||||
|
|
||||||
// Restore the number of streamable bytes to this branch.
|
// Restore the number of streamable bytes to this branch.
|
||||||
@ -493,7 +506,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int incrementStreamWindow(int delta) throws Http2Exception {
|
int incrementStreamWindow(int delta) throws Http2Exception {
|
||||||
if (delta > 0 && Integer.MAX_VALUE - delta < window) {
|
if (delta > 0 && Integer.MAX_VALUE - delta < window) {
|
||||||
throw streamError(stream.id(), FLOW_CONTROL_ERROR,
|
throw streamError(stream.id(), FLOW_CONTROL_ERROR,
|
||||||
"Window size overflow for stream: %d", stream.id());
|
"Window size overflow for stream: %d", stream.id());
|
||||||
@ -510,28 +523,28 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int writableWindow() {
|
int writableWindow() {
|
||||||
return min(window, connectionWindowSize());
|
return min(window, connectionWindowSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int streamableBytes() {
|
int streamableBytes() {
|
||||||
return max(0, min(pendingBytes - allocated, window));
|
return max(0, min(pendingBytes - allocated, window));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int streamableBytesForTree() {
|
int streamableBytesForTree() {
|
||||||
return streamableBytesForTree;
|
return streamableBytesForTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enqueueFrame(FlowControlled frame) {
|
void enqueueFrame(FlowControlled frame) {
|
||||||
incrementPendingBytes(frame.size());
|
incrementPendingBytes(frame.size());
|
||||||
pendingWriteQueue.offer(frame);
|
pendingWriteQueue.offer(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasFrame() {
|
boolean hasFrame() {
|
||||||
return !pendingWriteQueue.isEmpty();
|
return !pendingWriteQueue.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,7 +556,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancel() {
|
void cancel() {
|
||||||
cancel(null);
|
cancel(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -568,7 +581,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int writeBytes(int bytes) {
|
int writeBytes(int bytes) {
|
||||||
int bytesAttempted = 0;
|
int bytesAttempted = 0;
|
||||||
while (hasFrame()) {
|
while (hasFrame()) {
|
||||||
int maxBytes = min(bytes - bytesAttempted, writableWindow());
|
int maxBytes = min(bytes - bytesAttempted, writableWindow());
|
||||||
@ -683,94 +696,94 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
* The remote flow control state for a single stream that is not in a state where flow controlled frames cannot
|
* The remote flow control state for a single stream that is not in a state where flow controlled frames cannot
|
||||||
* be exchanged.
|
* be exchanged.
|
||||||
*/
|
*/
|
||||||
private static final class ReducedState extends AbstractState {
|
private final class ReducedState extends AbstractState {
|
||||||
public ReducedState(Http2Stream stream) {
|
ReducedState(Http2Stream stream) {
|
||||||
super(stream);
|
super(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReducedState(AbstractState existingState) {
|
ReducedState(AbstractState existingState) {
|
||||||
super(existingState);
|
super(existingState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int windowSize() {
|
int windowSize() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int initialWindowSize() {
|
int initialWindowSize() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int writableWindow() {
|
int writableWindow() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int streamableBytes() {
|
int streamableBytes() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int streamableBytesForTree() {
|
int streamableBytesForTree() {
|
||||||
return streamableBytesForTree;
|
return streamableBytesForTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeAllocatedBytes() {
|
void writeAllocatedBytes() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cancel() {
|
void cancel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void window(int initialWindowSize) {
|
void window(int initialWindowSize) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int incrementStreamWindow(int delta) throws Http2Exception {
|
int incrementStreamWindow(int delta) throws Http2Exception {
|
||||||
// This operation needs to be supported during the initial settings exchange when
|
// This operation needs to be supported during the initial settings exchange when
|
||||||
// the peer has not yet acknowledged this peer being activated.
|
// the peer has not yet acknowledged this peer being activated.
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int writeBytes(int bytes) {
|
int writeBytes(int bytes) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enqueueFrame(FlowControlled frame) {
|
void enqueueFrame(FlowControlled frame) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void allocate(int bytes) {
|
void allocate(int bytes) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasFrame() {
|
boolean hasFrame() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstraction around {@link FlowControlState} which provides specific extensions used by remote flow control.
|
* An abstraction which provides specific extensions used by remote flow control.
|
||||||
*/
|
*/
|
||||||
private abstract static class AbstractState implements FlowControlState {
|
private abstract class AbstractState {
|
||||||
protected final Http2Stream stream;
|
protected final Http2Stream stream;
|
||||||
protected int streamableBytesForTree;
|
protected int streamableBytesForTree;
|
||||||
|
|
||||||
public AbstractState(Http2Stream stream) {
|
AbstractState(Http2Stream stream) {
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbstractState(AbstractState existingState) {
|
AbstractState(AbstractState existingState) {
|
||||||
this.stream = existingState.stream();
|
this.stream = existingState.stream();
|
||||||
this.streamableBytesForTree = existingState.streamableBytesForTree();
|
this.streamableBytesForTree = existingState.streamableBytesForTree();
|
||||||
}
|
}
|
||||||
@ -778,7 +791,7 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
/**
|
/**
|
||||||
* The stream this state is associated with.
|
* The stream this state is associated with.
|
||||||
*/
|
*/
|
||||||
public final Http2Stream stream() {
|
final Http2Stream stream() {
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,72 +799,76 @@ public class DefaultHttp2RemoteFlowController implements Http2RemoteFlowControll
|
|||||||
* Recursively increments the {@link #streamableBytesForTree()} for this branch in the priority tree starting
|
* Recursively increments the {@link #streamableBytesForTree()} for this branch in the priority tree starting
|
||||||
* at the current node.
|
* at the current node.
|
||||||
*/
|
*/
|
||||||
public final void incrementStreamableBytesForTree(int numBytes) {
|
final void incrementStreamableBytesForTree(int numBytes) {
|
||||||
streamableBytesForTree += numBytes;
|
streamableBytesForTree += numBytes;
|
||||||
if (!stream.isRoot()) {
|
if (!stream.isRoot()) {
|
||||||
state(stream.parent()).incrementStreamableBytesForTree(numBytes);
|
state(stream.parent()).incrementStreamableBytesForTree(numBytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract int windowSize();
|
||||||
|
|
||||||
|
abstract int initialWindowSize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write bytes allocated bytes for this stream.
|
* Write bytes allocated bytes for this stream.
|
||||||
*/
|
*/
|
||||||
public abstract void writeAllocatedBytes();
|
abstract void writeAllocatedBytes();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of pending bytes for this node that will fit within the
|
* Returns the number of pending bytes for this node that will fit within the
|
||||||
* {@link #availableWindow()}. This is used for the priority algorithm to determine the aggregate
|
* {@link #writableWindow()}. This is used for the priority algorithm to determine the aggregate
|
||||||
* number of bytes that can be written at each node. Each node only takes into account its
|
* number of bytes that can be written at each node. Each node only takes into account its
|
||||||
* stream window so that when a change occurs to the connection window, these values need
|
* stream window so that when a change occurs to the connection window, these values need
|
||||||
* not change (i.e. no tree traversal is required).
|
* not change (i.e. no tree traversal is required).
|
||||||
*/
|
*/
|
||||||
public abstract int streamableBytes();
|
abstract int streamableBytes();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the {@link #streamableBytes()} for the entire tree rooted at this node.
|
* Get the {@link #streamableBytes()} for the entire tree rooted at this node.
|
||||||
*/
|
*/
|
||||||
public abstract int streamableBytesForTree();
|
abstract int streamableBytesForTree();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any operations that may be pending are cleared and the status of these operations is failed.
|
* Any operations that may be pending are cleared and the status of these operations is failed.
|
||||||
*/
|
*/
|
||||||
public abstract void cancel();
|
abstract void cancel();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the {@link #availableWindow()} size.
|
* Reset the window size for this stream.
|
||||||
*/
|
*/
|
||||||
public abstract void window(int initialWindowSize);
|
abstract void window(int initialWindowSize);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the flow control window for this stream by the given delta and returns the new value.
|
* Increments the flow control window for this stream by the given delta and returns the new value.
|
||||||
*/
|
*/
|
||||||
public abstract int incrementStreamWindow(int delta) throws Http2Exception;
|
abstract int incrementStreamWindow(int delta) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the maximum writable window (minimum of the stream and connection windows).
|
* Returns the maximum writable window (minimum of the stream and connection windows).
|
||||||
*/
|
*/
|
||||||
public abstract int writableWindow();
|
abstract int writableWindow();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes up to the number of bytes from the pending queue. May write less if limited by the writable window, by
|
* Writes up to the number of bytes from the pending queue. May write less if limited by the writable window, by
|
||||||
* the number of pending writes available, or because a frame does not support splitting on arbitrary
|
* the number of pending writes available, or because a frame does not support splitting on arbitrary
|
||||||
* boundaries.
|
* boundaries.
|
||||||
*/
|
*/
|
||||||
public abstract int writeBytes(int bytes);
|
abstract int writeBytes(int bytes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the {@code frame} to the pending queue and increments the pending byte count.
|
* Adds the {@code frame} to the pending queue and increments the pending byte count.
|
||||||
*/
|
*/
|
||||||
public abstract void enqueueFrame(FlowControlled frame);
|
abstract void enqueueFrame(FlowControlled frame);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increment the number of bytes allocated to this stream by the priority algorithm
|
* Increment the number of bytes allocated to this stream by the priority algorithm
|
||||||
*/
|
*/
|
||||||
public abstract void allocate(int bytes);
|
abstract void allocate(int bytes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether or not there are frames in the pending queue.
|
* Indicates whether or not there are frames in the pending queue.
|
||||||
*/
|
*/
|
||||||
public abstract boolean hasFrame();
|
abstract boolean hasFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import static io.netty.handler.codec.http.HttpHeaderValues.X_GZIP;
|
|||||||
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
||||||
import static io.netty.handler.codec.http2.Http2Exception.streamError;
|
import static io.netty.handler.codec.http2.Http2Exception.streamError;
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
@ -38,19 +39,11 @@ import io.netty.util.ByteString;
|
|||||||
* stream. The decompression provided by this class will be applied to the data for the entire stream.
|
* stream. The decompression provided by this class will be applied to the data for the entire stream.
|
||||||
*/
|
*/
|
||||||
public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecorator {
|
public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecorator {
|
||||||
private static final Http2ConnectionAdapter CLEAN_UP_LISTENER = new Http2ConnectionAdapter() {
|
|
||||||
@Override
|
|
||||||
public void onStreamRemoved(Http2Stream stream) {
|
|
||||||
final Http2Decompressor decompressor = decompressor(stream);
|
|
||||||
if (decompressor != null) {
|
|
||||||
cleanup(stream, decompressor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Http2Connection connection;
|
private final Http2Connection connection;
|
||||||
private final boolean strict;
|
private final boolean strict;
|
||||||
private boolean flowControllerInitialized;
|
private boolean flowControllerInitialized;
|
||||||
|
final Http2Connection.PropertyKey propertyKey;
|
||||||
|
|
||||||
public DelegatingDecompressorFrameListener(Http2Connection connection, Http2FrameListener listener) {
|
public DelegatingDecompressorFrameListener(Http2Connection connection, Http2FrameListener listener) {
|
||||||
this(connection, listener, true);
|
this(connection, listener, true);
|
||||||
@ -62,7 +55,16 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor
|
|||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.strict = strict;
|
this.strict = strict;
|
||||||
|
|
||||||
connection.addListener(CLEAN_UP_LISTENER);
|
propertyKey = connection.newKey();
|
||||||
|
connection.addListener(new Http2ConnectionAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onStreamRemoved(Http2Stream stream) {
|
||||||
|
final Http2Decompressor decompressor = decompressor(stream);
|
||||||
|
if (decompressor != null) {
|
||||||
|
cleanup(stream, decompressor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -210,7 +212,7 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor
|
|||||||
final EmbeddedChannel channel = newContentDecompressor(contentEncoding);
|
final EmbeddedChannel channel = newContentDecompressor(contentEncoding);
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
decompressor = new Http2Decompressor(channel);
|
decompressor = new Http2Decompressor(channel);
|
||||||
stream.setProperty(Http2Decompressor.class, decompressor);
|
stream.setProperty(propertyKey, decompressor);
|
||||||
// Decode the content and remove or replace the existing headers
|
// Decode the content and remove or replace the existing headers
|
||||||
// so that the message looks like a decoded message.
|
// so that the message looks like a decoded message.
|
||||||
ByteString targetContentEncoding = getTargetContentEncoding(contentEncoding);
|
ByteString targetContentEncoding = getTargetContentEncoding(contentEncoding);
|
||||||
@ -237,8 +239,8 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Http2Decompressor decompressor(Http2Stream stream) {
|
Http2Decompressor decompressor(Http2Stream stream) {
|
||||||
return (Http2Decompressor) (stream == null? null : stream.getProperty(Http2Decompressor.class));
|
return stream == null ? null : (Http2Decompressor) stream.getProperty(propertyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -248,7 +250,7 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor
|
|||||||
* @param stream The stream for which {@code decompressor} is the decompressor for
|
* @param stream The stream for which {@code decompressor} is the decompressor for
|
||||||
* @param decompressor The decompressor for {@code stream}
|
* @param decompressor The decompressor for {@code stream}
|
||||||
*/
|
*/
|
||||||
private static void cleanup(Http2Stream stream, Http2Decompressor decompressor) {
|
private void cleanup(Http2Stream stream, Http2Decompressor decompressor) {
|
||||||
final EmbeddedChannel channel = decompressor.decompressor();
|
final EmbeddedChannel channel = decompressor.decompressor();
|
||||||
if (channel.finish()) {
|
if (channel.finish()) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@ -259,7 +261,7 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor
|
|||||||
buf.release();
|
buf.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
decompressor = stream.removeProperty(Http2Decompressor.class);
|
decompressor = stream.removeProperty(propertyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -286,7 +288,7 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor
|
|||||||
/**
|
/**
|
||||||
* A decorator around the local flow controller that converts consumed bytes from uncompressed to compressed.
|
* A decorator around the local flow controller that converts consumed bytes from uncompressed to compressed.
|
||||||
*/
|
*/
|
||||||
private static final class ConsumedBytesConverter implements Http2LocalFlowController {
|
private final class ConsumedBytesConverter implements Http2LocalFlowController {
|
||||||
private final Http2LocalFlowController flowController;
|
private final Http2LocalFlowController flowController;
|
||||||
|
|
||||||
ConsumedBytesConverter(Http2LocalFlowController flowController) {
|
ConsumedBytesConverter(Http2LocalFlowController flowController) {
|
||||||
@ -303,6 +305,16 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor
|
|||||||
return flowController.initialWindowSize();
|
return flowController.initialWindowSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int windowSize(Http2Stream stream) {
|
||||||
|
return flowController.windowSize(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int initialWindowSize(Http2Stream stream) {
|
||||||
|
return flowController.initialWindowSize(stream);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta)
|
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta)
|
||||||
throws Http2Exception {
|
throws Http2Exception {
|
||||||
@ -330,12 +342,12 @@ public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecor
|
|||||||
flowController.consumeBytes(ctx, stream, numBytes);
|
flowController.consumeBytes(ctx, stream, numBytes);
|
||||||
} catch (Http2Exception e) {
|
} catch (Http2Exception e) {
|
||||||
if (copy != null) {
|
if (copy != null) {
|
||||||
stream.setProperty(Http2Decompressor.class, copy);
|
stream.setProperty(propertyKey, copy);
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
if (copy != null) {
|
if (copy != null) {
|
||||||
stream.setProperty(Http2Decompressor.class, copy);
|
stream.setProperty(propertyKey, copy);
|
||||||
}
|
}
|
||||||
throw new Http2Exception(INTERNAL_ERROR,
|
throw new Http2Exception(INTERNAL_ERROR,
|
||||||
"Error while returning bytes to flow control window", t);
|
"Error while returning bytes to flow control window", t);
|
||||||
|
@ -278,6 +278,17 @@ public interface Http2Connection {
|
|||||||
Endpoint<? extends Http2FlowController> opposite();
|
Endpoint<? extends Http2FlowController> opposite();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A key to be used for associating application-defined properties with streams within this connection.
|
||||||
|
*/
|
||||||
|
interface PropertyKey {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new key that is unique within this {@link Http2Connection}.
|
||||||
|
*/
|
||||||
|
PropertyKey newKey();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a listener of stream life-cycle events.
|
* Adds a listener of stream life-cycle events.
|
||||||
*/
|
*/
|
||||||
|
@ -22,8 +22,8 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
public interface Http2FlowController {
|
public interface Http2FlowController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the initial flow control window and updates all stream windows (but not the connection
|
* Sets the connection-wide initial flow control window and updates all stream windows (but not the connection
|
||||||
* window) by the delta.
|
* stream window) by the delta.
|
||||||
* <p>
|
* <p>
|
||||||
* This method is used to apply the {@code SETTINGS_INITIAL_WINDOW_SIZE} value for an
|
* This method is used to apply the {@code SETTINGS_INITIAL_WINDOW_SIZE} value for an
|
||||||
* {@code SETTINGS} frame.
|
* {@code SETTINGS} frame.
|
||||||
@ -34,11 +34,24 @@ public interface Http2FlowController {
|
|||||||
void initialWindowSize(int newWindowSize) throws Http2Exception;
|
void initialWindowSize(int newWindowSize) throws Http2Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the initial flow control window size that is used as the basis for new stream flow
|
* Gets the connection-wide initial flow control window size that is used as the basis for new stream flow
|
||||||
* control windows.
|
* control windows.
|
||||||
*/
|
*/
|
||||||
int initialWindowSize();
|
int initialWindowSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the portion of the flow control window for the given stream that is currently available for sending/receiving
|
||||||
|
* frames which are subject to flow control. This quantity is measured in number of bytes.
|
||||||
|
*/
|
||||||
|
int windowSize(Http2Stream stream);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the initial flow control window size for the given stream. This quantity is measured in number of bytes. Note
|
||||||
|
* the unavailable window portion can be calculated by {@link #initialWindowSize()} - {@link
|
||||||
|
* #windowSize(Http2Stream)}.
|
||||||
|
*/
|
||||||
|
int initialWindowSize(Http2Stream stream);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increments the size of the stream's flow control window by the given delta.
|
* Increments the size of the stream's flow control window by the given delta.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -33,24 +33,6 @@ public interface Http2Stream {
|
|||||||
CLOSED
|
CLOSED
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the state which flow controller implementations are expected to track.
|
|
||||||
*/
|
|
||||||
interface FlowControlState {
|
|
||||||
/**
|
|
||||||
* Get the portion of the flow control window that is available for sending/receiving frames which are subject
|
|
||||||
* to flow control. This quantity is measured in number of bytes.
|
|
||||||
*/
|
|
||||||
int windowSize();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the initial flow control window size. This quantity is measured in number of bytes.
|
|
||||||
* Note the unavailable window portion can be calculated by
|
|
||||||
* {@link #initialWindowSize()} - {@link #windowSize()}.
|
|
||||||
*/
|
|
||||||
int initialWindowSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the unique identifier for this stream within the connection.
|
* Gets the unique identifier for this stream within the connection.
|
||||||
*/
|
*/
|
||||||
@ -61,26 +43,6 @@ public interface Http2Stream {
|
|||||||
*/
|
*/
|
||||||
State state();
|
State state();
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the state as related to the {@link Http2LocalFlowController}.
|
|
||||||
*/
|
|
||||||
FlowControlState localFlowState();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the state as related to the {@link Http2LocalFlowController}.
|
|
||||||
*/
|
|
||||||
void localFlowState(FlowControlState state);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the state as related to {@link Http2RemoteFlowController}.
|
|
||||||
*/
|
|
||||||
FlowControlState remoteFlowState();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the state as related to {@link Http2RemoteFlowController}.
|
|
||||||
*/
|
|
||||||
void remoteFlowState(FlowControlState state);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens this stream, making it available via {@link Http2Connection#forEachActiveStream(Http2StreamVisitor)} and
|
* Opens this stream, making it available via {@link Http2Connection#forEachActiveStream(Http2StreamVisitor)} and
|
||||||
* transition state to:
|
* transition state to:
|
||||||
@ -140,17 +102,17 @@ public interface Http2Stream {
|
|||||||
* Associates the application-defined data with this stream.
|
* Associates the application-defined data with this stream.
|
||||||
* @return The value that was previously associated with {@code key}, or {@code null} if there was none.
|
* @return The value that was previously associated with {@code key}, or {@code null} if there was none.
|
||||||
*/
|
*/
|
||||||
Object setProperty(Object key, Object value);
|
<V> V setProperty(Http2Connection.PropertyKey key, V value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns application-defined data if any was associated with this stream.
|
* Returns application-defined data if any was associated with this stream.
|
||||||
*/
|
*/
|
||||||
<V> V getProperty(Object key);
|
<V> V getProperty(Http2Connection.PropertyKey key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns and removes application-defined data if any was associated with this stream.
|
* Returns and removes application-defined data if any was associated with this stream.
|
||||||
*/
|
*/
|
||||||
<V> V removeProperty(Object key);
|
<V> V removeProperty(Http2Connection.PropertyKey key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates an priority for this stream. Calling this method may affect the straucture of the
|
* Updates an priority for this stream. Calling this method may affect the straucture of the
|
||||||
|
@ -67,6 +67,7 @@ public class DefaultHttp2LocalFlowControllerTest {
|
|||||||
|
|
||||||
connection = new DefaultHttp2Connection(false);
|
connection = new DefaultHttp2Connection(false);
|
||||||
controller = new DefaultHttp2LocalFlowController(connection, frameWriter, updateRatio);
|
controller = new DefaultHttp2LocalFlowController(connection, frameWriter, updateRatio);
|
||||||
|
connection.local().flowController(controller);
|
||||||
|
|
||||||
connection.local().createStream(STREAM_ID, false);
|
connection.local().createStream(STREAM_ID, false);
|
||||||
}
|
}
|
||||||
@ -320,7 +321,7 @@ public class DefaultHttp2LocalFlowControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int window(int streamId) throws Http2Exception {
|
private int window(int streamId) throws Http2Exception {
|
||||||
return stream(streamId).localFlowState().windowSize();
|
return controller.windowSize(stream(streamId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Http2Stream stream(int streamId) {
|
private Http2Stream stream(int streamId) {
|
||||||
|
@ -86,6 +86,7 @@ public class DefaultHttp2RemoteFlowControllerTest {
|
|||||||
|
|
||||||
connection = new DefaultHttp2Connection(false);
|
connection = new DefaultHttp2Connection(false);
|
||||||
controller = new DefaultHttp2RemoteFlowController(connection);
|
controller = new DefaultHttp2RemoteFlowController(connection);
|
||||||
|
connection.remote().flowController(controller);
|
||||||
|
|
||||||
connection.local().createStream(STREAM_A, false);
|
connection.local().createStream(STREAM_A, false);
|
||||||
connection.local().createStream(STREAM_B, false);
|
connection.local().createStream(STREAM_B, false);
|
||||||
@ -1222,7 +1223,7 @@ public class DefaultHttp2RemoteFlowControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int window(int streamId) throws Http2Exception {
|
private int window(int streamId) throws Http2Exception {
|
||||||
return stream(streamId).remoteFlowState().windowSize();
|
return controller.windowSize(stream(streamId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void incrementWindowSize(int streamId, int delta) throws Http2Exception {
|
private void incrementWindowSize(int streamId, int delta) throws Http2Exception {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package io.netty.microbench.http2;
|
package io.netty.microbench.http2;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.http2.Http2Exception;
|
import io.netty.handler.codec.http2.Http2Exception;
|
||||||
@ -35,6 +36,16 @@ public final class NoopHttp2LocalFlowController implements Http2LocalFlowControl
|
|||||||
return MAX_INITIAL_WINDOW_SIZE;
|
return MAX_INITIAL_WINDOW_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int windowSize(Http2Stream stream) {
|
||||||
|
return MAX_INITIAL_WINDOW_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int initialWindowSize(Http2Stream stream) {
|
||||||
|
return MAX_INITIAL_WINDOW_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta)
|
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta)
|
||||||
throws Http2Exception {
|
throws Http2Exception {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package io.netty.microbench.http2;
|
package io.netty.microbench.http2;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
|
import static io.netty.handler.codec.http2.Http2CodecUtil.MAX_INITIAL_WINDOW_SIZE;
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.http2.Http2Exception;
|
import io.netty.handler.codec.http2.Http2Exception;
|
||||||
import io.netty.handler.codec.http2.Http2RemoteFlowController;
|
import io.netty.handler.codec.http2.Http2RemoteFlowController;
|
||||||
@ -24,6 +25,7 @@ public final class NoopHttp2RemoteFlowController implements Http2RemoteFlowContr
|
|||||||
public static final NoopHttp2RemoteFlowController INSTANCE = new NoopHttp2RemoteFlowController();
|
public static final NoopHttp2RemoteFlowController INSTANCE = new NoopHttp2RemoteFlowController();
|
||||||
|
|
||||||
private NoopHttp2RemoteFlowController() { }
|
private NoopHttp2RemoteFlowController() { }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialWindowSize(int newWindowSize) throws Http2Exception {
|
public void initialWindowSize(int newWindowSize) throws Http2Exception {
|
||||||
}
|
}
|
||||||
@ -33,6 +35,16 @@ public final class NoopHttp2RemoteFlowController implements Http2RemoteFlowContr
|
|||||||
return MAX_INITIAL_WINDOW_SIZE;
|
return MAX_INITIAL_WINDOW_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int windowSize(Http2Stream stream) {
|
||||||
|
return MAX_INITIAL_WINDOW_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int initialWindowSize(Http2Stream stream) {
|
||||||
|
return MAX_INITIAL_WINDOW_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta)
|
public void incrementWindowSize(ChannelHandlerContext ctx, Http2Stream stream, int delta)
|
||||||
throws Http2Exception {
|
throws Http2Exception {
|
||||||
|
Loading…
Reference in New Issue
Block a user