Skip to content

Commit 124191f

Browse files
martinuyRealCLanger
authored andcommitted
8284585: PushPromiseContinuation test fails intermittently in timeout
Reviewed-by: abakhtin Backport-of: 65da38d844760f7d17a143f8b4d5e25ea0144e27
1 parent b66c031 commit 124191f

File tree

4 files changed

+93
-16
lines changed

4 files changed

+93
-16
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -763,7 +763,7 @@ void processFrame(Http2Frame frame) throws IOException {
763763
}
764764

765765
Stream<?> stream = getStream(streamid);
766-
if (stream == null) {
766+
if (stream == null && pushContinuationState == null) {
767767
// Should never receive a frame with unknown stream id
768768

769769
if (frame instanceof HeaderFrame) {
@@ -803,7 +803,11 @@ void processFrame(Http2Frame frame) throws IOException {
803803
if (pushContinuationState != null) {
804804
if (frame instanceof ContinuationFrame cf) {
805805
try {
806-
handlePushContinuation(stream, cf);
806+
if (streamid == pushContinuationState.pushContFrame.streamid())
807+
handlePushContinuation(stream, cf);
808+
else
809+
protocolError(ErrorFrame.PROTOCOL_ERROR, "Received a Continuation Frame with an " +
810+
"unexpected stream id");
807811
} catch (UncheckedIOException e) {
808812
debug.log("Error handling Push Promise with Continuation: " + e.getMessage(), e);
809813
protocolError(ErrorFrame.PROTOCOL_ERROR, e.getMessage());
@@ -890,8 +894,6 @@ private <T> void handlePushContinuation(Stream<T> parent, ContinuationFrame cf)
890894

891895
private <T> void completePushPromise(int promisedStreamid, Stream<T> parent, HttpHeaders headers)
892896
throws IOException {
893-
// Perhaps the following checks could be moved to handlePushPromise()
894-
// to reset the PushPromise stream earlier?
895897
HttpRequestImpl parentReq = parent.request;
896898
if (promisedStreamid != nextPushStream) {
897899
resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR);

test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import java.util.List;
6060
import java.util.Map;
6161
import java.util.concurrent.CompletableFuture;
62+
import java.util.concurrent.CompletionException;
6263
import java.util.concurrent.ConcurrentHashMap;
6364
import java.util.concurrent.ConcurrentMap;
6465
import java.util.function.BiPredicate;
@@ -71,7 +72,7 @@
7172
import jdk.httpclient.test.lib.http2.Http2TestServerConnection;
7273

7374
import static java.nio.charset.StandardCharsets.UTF_8;
74-
import static org.testng.Assert.assertEquals;
75+
import static org.testng.Assert.*;
7576

7677
public class PushPromiseContinuation {
7778

@@ -102,7 +103,7 @@ public void setup() throws Exception {
102103

103104
// Need to have a custom exchange supplier to manage the server's push
104105
// promise with continuation flow
105-
server.setExchangeSupplier(Http2LPPTestExchangeImpl::new);
106+
server.setExchangeSupplier(Http2PushPromiseContinuationExchangeImpl::new);
106107

107108
System.err.println("PushPromiseContinuation: Server listening on port " + server.getAddress().getPort());
108109
server.start();
@@ -171,6 +172,31 @@ public void testThreeContinuations() {
171172
verify(resp);
172173
}
173174

175+
@Test
176+
public void testSendHeadersOnPushPromiseStream() throws Exception {
177+
// This test server sends a push promise that should be followed by a continuation but
178+
// incorrectly sends on Response Headers while the client awaits the continuation.
179+
Http2TestServer faultyServer = new Http2TestServer(false, 0);
180+
faultyServer.addHandler(new ServerPushHandler(), "/");
181+
faultyServer.setExchangeSupplier(Http2PushPromiseHeadersExchangeImpl::new);
182+
System.err.println("PushPromiseContinuation: FaultyServer listening on port " + faultyServer.getAddress().getPort());
183+
faultyServer.start();
184+
185+
int faultyPort = faultyServer.getAddress().getPort();
186+
URI faultyUri = new URI("http://localhost:" + faultyPort + "/");
187+
188+
HttpClient client = HttpClient.newHttpClient();
189+
// Server is making a request to an incorrect URI
190+
HttpRequest hreq = HttpRequest.newBuilder(faultyUri).version(HttpClient.Version.HTTP_2).GET().build();
191+
CompletableFuture<HttpResponse<String>> cf =
192+
client.sendAsync(hreq, HttpResponse.BodyHandlers.ofString(UTF_8), pph);
193+
194+
CompletionException t = expectThrows(CompletionException.class, () -> cf.join());
195+
assertEquals(t.getCause().getClass(), IOException.class, "Expected an IOException but got " + t.getCause());
196+
System.err.println("Client received the following expected exception: " + t.getCause());
197+
faultyServer.stop();
198+
}
199+
174200
private void verify(HttpResponse<String> resp) {
175201
assertEquals(resp.statusCode(), 200);
176202
assertEquals(resp.body(), mainResponseBody);
@@ -191,15 +217,46 @@ private void verify(HttpResponse<String> resp) {
191217
}
192218
}
193219

194-
static class Http2LPPTestExchangeImpl extends Http2TestExchangeImpl {
220+
static class Http2PushPromiseHeadersExchangeImpl extends Http2TestExchangeImpl {
221+
222+
Http2PushPromiseHeadersExchangeImpl(int streamid, String method, HttpHeaders reqheaders, HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, SSLSession sslSession, BodyOutputStream os, Http2TestServerConnection conn, boolean pushAllowed) {
223+
super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession, os, conn, pushAllowed);
224+
}
225+
226+
227+
@Override
228+
public void serverPush(URI uri, HttpHeaders headers, InputStream content) {
229+
HttpHeadersBuilder headersBuilder = new HttpHeadersBuilder();
230+
headersBuilder.setHeader(":method", "GET");
231+
headersBuilder.setHeader(":scheme", uri.getScheme());
232+
headersBuilder.setHeader(":authority", uri.getAuthority());
233+
headersBuilder.setHeader(":path", uri.getPath());
234+
for (Map.Entry<String,List<String>> entry : headers.map().entrySet()) {
235+
for (String value : entry.getValue())
236+
headersBuilder.addHeader(entry.getKey(), value);
237+
}
238+
HttpHeaders combinedHeaders = headersBuilder.build();
239+
OutgoingPushPromise pp = new OutgoingPushPromise(streamid, uri, combinedHeaders, content);
240+
// Indicates to the client that a continuation should be expected
241+
pp.setFlag(0x0);
242+
try {
243+
conn.addToOutputQ(pp);
244+
// writeLoop will spin up thread to read the InputStream
245+
} catch (IOException ex) {
246+
System.err.println("TestServer: pushPromise exception: " + ex);
247+
}
248+
}
249+
}
250+
251+
static class Http2PushPromiseContinuationExchangeImpl extends Http2TestExchangeImpl {
195252

196253
HttpHeadersBuilder pushPromiseHeadersBuilder;
197254
List<ContinuationFrame> cfs;
198255

199-
Http2LPPTestExchangeImpl(int streamid, String method, HttpHeaders reqheaders,
200-
HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is,
201-
SSLSession sslSession, BodyOutputStream os,
202-
Http2TestServerConnection conn, boolean pushAllowed) {
256+
Http2PushPromiseContinuationExchangeImpl(int streamid, String method, HttpHeaders reqheaders,
257+
HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is,
258+
SSLSession sslSession, BodyOutputStream os,
259+
Http2TestServerConnection conn, boolean pushAllowed) {
203260
super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession, os, conn, pushAllowed);
204261
}
205262

@@ -253,18 +310,15 @@ public void serverPush(URI uri, HttpHeaders headers, InputStream content) {
253310
HttpHeaders pushPromiseHeaders = pushPromiseHeadersBuilder.build();
254311
testHeaders = testHeadersBuilder.build();
255312
// Create the Push Promise Frame
256-
OutgoingPushPromise pp = new OutgoingPushPromise(streamid, uri, pushPromiseHeaders, content);
313+
OutgoingPushPromise pp = new OutgoingPushPromise(streamid, uri, pushPromiseHeaders, content, cfs);
314+
257315
// Indicates to the client that a continuation should be expected
258316
pp.setFlag(0x0);
259317

260318
try {
261319
// Schedule push promise and continuation for sending
262320
conn.addToOutputQ(pp);
263321
System.err.println("Server: Scheduled a Push Promise to Send");
264-
for (ContinuationFrame cf : cfs) {
265-
conn.addToOutputQ(cf);
266-
System.err.println("Server: Scheduled a Continuation to Send");
267-
}
268322
} catch (IOException ex) {
269323
System.err.println("Server: pushPromise exception: " + ex);
270324
}

test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestServerConnection.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,11 @@ private void handlePush(OutgoingPushPromise op) throws IOException {
951951
nextPushStreamId += 2;
952952
pp.streamid(op.parentStream);
953953
writeFrame(pp);
954+
// No need to check for END_HEADERS flag here to allow for tests to simulate bad server side
955+
// behavior i.e Continuation Frames included with END_HEADERS flag set
956+
for (Http2Frame cf : op.getContinuations())
957+
writeFrame(cf);
958+
954959
final InputStream ii = op.is;
955960
final BodyOutputStream oo = new BodyOutputStream(
956961
promisedStreamid,

test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/OutgoingPushPromise.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import java.io.InputStream;
2727
import java.net.URI;
2828
import java.net.http.HttpHeaders;
29+
import java.util.List;
30+
31+
import jdk.internal.net.http.frame.ContinuationFrame;
2932
import jdk.internal.net.http.frame.Http2Frame;
3033

3134
// will be converted to a PushPromiseFrame in the writeLoop
@@ -35,16 +38,29 @@ public class OutgoingPushPromise extends Http2Frame {
3538
final URI uri;
3639
final InputStream is;
3740
final int parentStream; // not the pushed streamid
41+
private final List<Http2Frame> continuations;
3842

3943
public OutgoingPushPromise(int parentStream,
4044
URI uri,
4145
HttpHeaders headers,
4246
InputStream is) {
47+
this(parentStream, uri, headers, is, List.of());
48+
}
49+
50+
public OutgoingPushPromise(int parentStream,
51+
URI uri,
52+
HttpHeaders headers,
53+
InputStream is,
54+
List<ContinuationFrame> continuations) {
4355
super(0,0);
4456
this.uri = uri;
4557
this.headers = headers;
4658
this.is = is;
4759
this.parentStream = parentStream;
60+
this.continuations = List.copyOf(continuations);
4861
}
4962

63+
public List<Http2Frame> getContinuations() {
64+
return continuations;
65+
}
5066
}

0 commit comments

Comments
 (0)