Fix HTTP/2 dependency tree corruption

Motivation:

Chrome was randomly getting stuck loading the tiles examples.
Investigation showed that the Netty flow controller thought it had
nothing to send for the connection even though some streams has queued
data and window available.

Modifications:

Fixed an accounting error where an implicitly created parent was not
being added to the dependency tree, thus it and all of its children were
orphaned from the connection's tree and would never have data written.

Result:

Fixes #6621
This commit is contained in:
Eric Anderson 2017-04-21 17:05:30 -07:00 committed by Scott Mitchell
parent c6ad9338b3
commit 799350c369
2 changed files with 38 additions and 0 deletions

View File

@ -229,6 +229,10 @@ public final class WeightedFairQueueByteDistributor implements StreamByteDistrib
newParent = new State(parentStreamId);
stateOnlyRemovalQueue.add(newParent);
stateOnlyMap.put(parentStreamId, newParent);
// Only the stream which was just added will change parents. So we only need an array of size 1.
List<ParentChangedEvent> events = new ArrayList<ParentChangedEvent>(1);
connectionState.takeChild(newParent, false, events);
notifyParentChanged(events);
}
// if activeCountForTree == 0 then it will not be in its parent's pseudoTimeQueue and thus should not be counted

View File

@ -899,4 +899,38 @@ public class WeightedFairQueueByteDistributorDependencyTreeTest extends
assertTrue(distributor.isChild(streamE.id(), streamC.id(), DEFAULT_PRIORITY_WEIGHT));
assertEquals(0, distributor.numChildren(streamE.id()));
}
// Unknown parent streams can come about in two ways:
// 1. Because the stream is old and its state was purged
// 2. This is the first reference to the stream, as implied at least by RFC7540§5.3.1:
// > A dependency on a stream that is not currently in the tree such as a stream in the
// > "idle" state results in that stream being given a default priority
@Test
public void unknownParentShouldBeCreatedUnderConnection() throws Exception {
setup(5);
// Purposefully avoid creating streamA's Http2Stream so that is it completely unknown.
// It shouldn't matter whether the ID is before or after streamB.id()
int streamAId = 1;
Http2Stream streamB = connection.local().createStream(3, false);
assertEquals(1, distributor.numChildren(connection.connectionStream().id()));
assertEquals(0, distributor.numChildren(streamB.id()));
// Build the tree
setPriority(streamB.id(), streamAId, DEFAULT_PRIORITY_WEIGHT, false);
assertEquals(1, connection.numActiveStreams());
// Level 0
assertEquals(1, distributor.numChildren(connection.connectionStream().id()));
// Level 1
assertTrue(distributor.isChild(streamAId, connection.connectionStream().id(), DEFAULT_PRIORITY_WEIGHT));
assertEquals(1, distributor.numChildren(streamAId));
// Level 2
assertTrue(distributor.isChild(streamB.id(), streamAId, DEFAULT_PRIORITY_WEIGHT));
assertEquals(0, distributor.numChildren(streamB.id()));
}
}