Skip to content

Commit 8fb1d8e

Browse files
committed
RFC 9113 conformance: use command queue to activate streams reserved with a push promise; improved server push test cases
1 parent c63cf78 commit 8fb1d8e

5 files changed

Lines changed: 194 additions & 80 deletions

File tree

httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
import org.apache.hc.core5.http2.impl.BasicH2TransportMetrics;
8383
import org.apache.hc.core5.http2.nio.AsyncPingHandler;
8484
import org.apache.hc.core5.http2.nio.command.PingCommand;
85+
import org.apache.hc.core5.http2.nio.command.PushResponseCommand;
8586
import org.apache.hc.core5.io.CloseMode;
8687
import org.apache.hc.core5.reactor.Command;
8788
import org.apache.hc.core5.reactor.ProtocolIOSession;
@@ -499,7 +500,7 @@ public final void onOutput() throws HttpException, IOException {
499500
}
500501

501502
if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0 && remoteSettingState == SettingsHandshake.ACKED) {
502-
while (streams.size() < remoteConfig.getMaxConcurrentStreams()) {
503+
while (streams.getLocalCount() < remoteConfig.getMaxConcurrentStreams()) {
503504
final Command command = ioSession.poll();
504505
if (command == null) {
505506
break;
@@ -510,6 +511,8 @@ public final void onOutput() throws HttpException, IOException {
510511
executePing((PingCommand) command);
511512
} else if (command instanceof RequestExecutionCommand) {
512513
executeRequest((RequestExecutionCommand) command);
514+
} else if (command instanceof PushResponseCommand) {
515+
executePush((PushResponseCommand) command);
513516
}
514517
if (!outputQueue.isEmpty()) {
515518
return;
@@ -638,6 +641,23 @@ private void executeRequest(final RequestExecutionCommand requestExecutionComman
638641
}
639642
}
640643

644+
private void executePush(final PushResponseCommand pushResponseCommand) throws IOException, HttpException {
645+
if (pushResponseCommand.isCancelled()) {
646+
return;
647+
}
648+
final H2Stream stream = streams.lookupSeen(pushResponseCommand.getStreamId());
649+
if (stream != null && stream.isReserved()) {
650+
if (!stream.isLocalClosed()) {
651+
stream.activate();
652+
if (stream.isOutputReady()) {
653+
stream.produceOutput();
654+
}
655+
} else {
656+
stream.abort();
657+
}
658+
}
659+
}
660+
641661
public final void onException(final Exception cause) {
642662
try {
643663
for (;;) {
@@ -812,7 +832,7 @@ private void consumeFrame(final RawFrame frame) throws HttpException, IOExceptio
812832
if (streamId == 0) {
813833
throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id: " + streamId);
814834
}
815-
final H2Stream stream = streams.lookupValidOrNull(streamId);
835+
final H2Stream stream = streams.lookupSeen(streamId);
816836
if (stream != null) {
817837
final ByteBuffer payload = frame.getPayload();
818838
if (payload == null || payload.remaining() != 4) {
@@ -1247,7 +1267,8 @@ void appendState(final StringBuilder buf) {
12471267
.append(", connInputWindow=").append(connInputWindow)
12481268
.append(", connOutputWindow=").append(connOutputWindow)
12491269
.append(", outputQueue=").append(outputQueue.size())
1250-
.append(", streams.size=").append(streams.size())
1270+
.append(", streams.localCoubt=").append(streams.getLocalCount())
1271+
.append(", streams.remoteCount=").append(streams.getRemoteCount())
12511272
.append(", streams.lastLocal=").append(streams.getLastLocalId())
12521273
.append(", streams.lastRemote=").append(streams.getLastRemoteId());
12531274
}
@@ -1362,9 +1383,11 @@ public void push(final List<Header> headers, final AsyncPushProducer pushProduce
13621383
ensureNotClosed();
13631384
final int promisedStreamId = streams.generateStreamId();
13641385
final H2StreamChannel channel = createChannel(promisedStreamId);
1365-
streams.createReserved(channel, outgoingPushPromise(channel, pushProducer));
1386+
final H2Stream stream = streams.createReserved(channel, outgoingPushPromise(channel, pushProducer));
13661387

13671388
commitPushPromise(id, promisedStreamId, headers);
1389+
stream.markRemoteClosed();
1390+
submitCommand(new PushResponseCommand(promisedStreamId));
13681391
} finally {
13691392
ioSession.getLock().unlock();
13701393
}

httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Stream.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ boolean isRemoteClosed() {
123123
return remoteClosed;
124124
}
125125

126+
void markRemoteClosed() {
127+
remoteClosed = true;
128+
}
129+
126130
boolean isLocalClosed() {
127131
return channel.isLocalClosed();
128132
}
@@ -173,7 +177,7 @@ void consumeData(final ByteBuffer src, final boolean endOfStream) throws HttpExc
173177
}
174178

175179
boolean isOutputReady() {
176-
return !channel.isLocalClosed() && handler.isOutputReady();
180+
return !reserved && !channel.isLocalClosed() && handler.isOutputReady();
177181
}
178182

179183
void produceOutput() throws HttpException, IOException {

httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/H2Streams.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@ public H2Streams(final StreamIdGenerator idGenerator) {
6060
this.remoteCount = new AtomicInteger(0);
6161
}
6262

63-
public int size() {
64-
return streams.size();
65-
}
66-
6763
public boolean isEmpty() {
6864
return streams.isEmpty();
6965
}
@@ -80,6 +76,14 @@ public int getLastRemoteId() {
8076
return lastRemoteId.get();
8177
}
8278

79+
public int getLocalCount() {
80+
return localCount.get();
81+
}
82+
83+
public int getRemoteCount() {
84+
return remoteCount.get();
85+
}
86+
8387
private H2Stream createStream(final H2StreamChannel channel, final H2StreamHandler streamHandler) {
8488
final int streamId = channel.getId();
8589
final boolean remoteStream = isOtherSide(streamId);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
28+
package org.apache.hc.core5.http2.nio.command;
29+
30+
import java.util.concurrent.atomic.AtomicBoolean;
31+
32+
import org.apache.hc.core5.annotation.Internal;
33+
import org.apache.hc.core5.reactor.Command;
34+
import org.apache.hc.core5.util.Args;
35+
36+
/**
37+
* Activates the stream reserved with a push promise
38+
*
39+
* @since 5.4
40+
*/
41+
@Internal
42+
public final class PushResponseCommand implements Command {
43+
44+
private final int streamId;
45+
private final AtomicBoolean cancelled;
46+
47+
public PushResponseCommand(final int streamId) {
48+
this.streamId = Args.positive(streamId, "Stream Id");
49+
this.cancelled = new AtomicBoolean();
50+
}
51+
52+
public int getStreamId() {
53+
return streamId;
54+
}
55+
56+
public boolean isCancelled() {
57+
return cancelled.get();
58+
}
59+
60+
@Override
61+
public boolean cancel() {
62+
return cancelled.compareAndSet(false, true);
63+
}
64+
65+
}

0 commit comments

Comments
 (0)