Author: markt Date: Thu Nov 24 14:39:52 2016 New Revision: 1771156 URL: http://svn.apache.org/viewvc?rev=1771156&view=rev Log: Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=60386 Implement a more sophisticated pruning algorithm for removing closed streams from the priority tree to ensure that the tree does not grow too large.
Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java tomcat/trunk/java/org/apache/coyote/http2/Stream.java tomcat/trunk/webapps/docs/changelog.xml Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1771156&r1=1771155&r2=1771156&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Thu Nov 24 14:39:52 2016 @@ -1007,38 +1007,88 @@ class Http2UpgradeHandler extends Abstra } // Need to try and close some streams. - // Use this Set to keep track of streams that might be part of the - // priority tree. Only remove these if we absolutely have to. - TreeSet<Integer> additionalCandidates = new TreeSet<>(); + // Try to close streams in this order + // 1. Completed streams used for a request with no children + // 2. Completed streams used for a request with children + // 3. Closed final streams + // + // Steps 1 and 2 will always be completed. + // Step 3 will be completed to the minimum extent necessary to bring the + // total number of streams under the limit. + + // Use these sets to track the different classes of streams + TreeSet<Integer> candidatesStepOne = new TreeSet<>(); + TreeSet<Integer> candidatesStepTwo = new TreeSet<>(); + TreeSet<Integer> candidatesStepThree = new TreeSet<>(); Iterator<Entry<Integer,Stream>> entryIter = streams.entrySet().iterator(); - while (entryIter.hasNext() && toClose > 0) { + while (entryIter.hasNext()) { Entry<Integer,Stream> entry = entryIter.next(); Stream stream = entry.getValue(); - // Never remove active streams or streams with children - if (stream.isActive() || stream.getChildStreams().size() > 0) { + // Never remove active streams + if (stream.isActive()) { continue; } + if (stream.isClosedFinal()) { // This stream went from IDLE to CLOSED and is likely to have - // been created by the client as part of the priority tree. Keep - // it if possible. - additionalCandidates.add(entry.getKey()); + // been created by the client as part of the priority tree. + candidatesStepThree.add(entry.getKey()); + } else if (stream.getChildStreams().size() == 0) { + // Closed, no children + candidatesStepOne.add(entry.getKey()); } else { + // Closed, with children + candidatesStepTwo.add(entry.getKey()); + } + } + + // Process the step one list + Iterator<Integer> stepOneIter = candidatesStepOne.iterator(); + while (stepOneIter.hasNext()) { + Integer streamIdToRemove = stepOneIter.next(); + // Remove this childless stream + Stream removedStream = streams.remove(streamIdToRemove); + removedStream.detachFromParent(); + toClose--; + if (log.isDebugEnabled()) { + log.debug(sm.getString("upgradeHandler.pruned", connectionId, streamIdToRemove)); + } + + // Did this make the parent childless? + AbstractStream parent = removedStream.getParentStream(); + while (parent instanceof Stream && !((Stream) parent).isActive() && + !((Stream) parent).isClosedFinal() && parent.getChildStreams().size() == 0) { + streams.remove(parent.getIdentifier()); + parent.detachFromParent(); + toClose--; if (log.isDebugEnabled()) { - log.debug(sm.getString("upgradeHandler.pruned", connectionId, entry.getKey())); + log.debug(sm.getString("upgradeHandler.pruned", connectionId, streamIdToRemove)); } - entryIter.remove(); - toClose--; + // Also need to remove this stream from the p2 list + candidatesStepTwo.remove(parent.getIdentifier()); + parent = parent.getParentStream(); } } - while (toClose > 0 && additionalCandidates.size() > 0) { - Integer pruned = additionalCandidates.pollLast(); + // Process the P2 list + Iterator<Integer> stepTwoIter = candidatesStepTwo.iterator(); + while (stepTwoIter.hasNext()) { + Integer streamIdToRemove = stepTwoIter.next(); + removeStreamFromPriorityTree(streamIdToRemove); + toClose--; if (log.isDebugEnabled()) { - log.debug(sm.getString("upgradeHandler.prunedPriority", connectionId, pruned)); + log.debug(sm.getString("upgradeHandler.pruned", connectionId, streamIdToRemove)); + } + } + + while (toClose > 0 && candidatesStepThree.size() > 0) { + Integer streamIdToRemove = candidatesStepThree.pollLast(); + removeStreamFromPriorityTree(streamIdToRemove); + toClose--; + if (log.isDebugEnabled()) { + log.debug(sm.getString("upgradeHandler.prunedPriority", connectionId, streamIdToRemove)); } - toClose++; } if (toClose > 0) { @@ -1048,6 +1098,30 @@ class Http2UpgradeHandler extends Abstra } + private void removeStreamFromPriorityTree(Integer streamIdToRemove) { + Stream streamToRemove = streams.remove(streamIdToRemove); + // Move the removed Stream's children to the removed Stream's + // parent. + Set<Stream> children = streamToRemove.getChildStreams(); + if (streamToRemove.getChildStreams().size() == 1) { + // Shortcut + streamToRemove.getChildStreams().iterator().next().rePrioritise( + streamToRemove.getParentStream(), streamToRemove.getWeight()); + } else { + int totalWeight = 0; + for (Stream child : children) { + totalWeight += child.getWeight(); + } + for (Stream child : children) { + streamToRemove.getChildStreams().iterator().next().rePrioritise( + streamToRemove.getParentStream(), + streamToRemove.getWeight() * child.getWeight() / totalWeight); + } + } + streamToRemove.detachFromParent(); + } + + void push(Request request, Stream associatedStream) throws IOException { Stream pushStream = createLocalStream(request); Modified: tomcat/trunk/java/org/apache/coyote/http2/Stream.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Stream.java?rev=1771156&r1=1771155&r2=1771156&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/coyote/http2/Stream.java (original) +++ tomcat/trunk/java/org/apache/coyote/http2/Stream.java Thu Nov 24 14:39:52 2016 @@ -130,6 +130,22 @@ class Stream extends AbstractStream impl } + /* + * Used when removing closed streams from the tree and we know there is no + * need to check for circular references. + */ + final void rePrioritise(AbstractStream parent, int weight) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("stream.reprioritisation.debug", + getConnectionId(), getIdentifier(), Boolean.FALSE, + parent.getIdentifier(), Integer.toString(weight))); + } + + parent.addChild(this); + this.weight = weight; + } + + final void receiveReset(long errorCode) { if (log.isDebugEnabled()) { log.debug(sm.getString("stream.reset.receive", getConnectionId(), getIdentifier(), Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1771156&r1=1771155&r2=1771156&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Thu Nov 24 14:39:52 2016 @@ -107,6 +107,11 @@ Ensure that the availability of configured upgrade protocols that require ALPN is correctly reported during Tomcat start. (markt) </fix> + <fix> + <bug>60386</bug>: Implement a more sophisticated pruning algorithm for + removing closed streams from the priority tree to ensure that the tree + does not grow too large. (markt) + </fix> </changelog> </subsection> <subsection name="Web applications"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org