Skip to content

Commit e3b548a

Browse files
committed
8257736: InputStream from BodyPublishers.ofInputStream() leaks when IOE happens
Reviewed-by: dfuchs, chegar
1 parent 978bed6 commit e3b548a

File tree

2 files changed

+168
-16
lines changed

2 files changed

+168
-16
lines changed

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

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -398,38 +398,54 @@ public static class StreamIterator implements Iterator<ByteBuffer> {
398398
// return error;
399399
// }
400400

401-
private int read() {
401+
private int read() throws IOException {
402402
if (eof)
403403
return -1;
404404
nextBuffer = bufSupplier.get();
405405
nextBuffer.clear();
406406
byte[] buf = nextBuffer.array();
407407
int offset = nextBuffer.arrayOffset();
408408
int cap = nextBuffer.capacity();
409-
try {
410-
int n = is.read(buf, offset, cap);
411-
if (n == -1) {
412-
eof = true;
413-
is.close();
414-
return -1;
415-
}
416-
//flip
417-
nextBuffer.limit(n);
418-
nextBuffer.position(0);
419-
return n;
420-
} catch (IOException ex) {
409+
int n = is.read(buf, offset, cap);
410+
if (n == -1) {
411+
eof = true;
421412
return -1;
422413
}
414+
//flip
415+
nextBuffer.limit(n);
416+
nextBuffer.position(0);
417+
return n;
418+
}
419+
420+
/**
421+
* Close stream in this instance.
422+
* UncheckedIOException may be thrown if IOE happens at InputStream::close.
423+
*/
424+
private void closeStream() {
425+
try {
426+
is.close();
427+
} catch (IOException e) {
428+
throw new UncheckedIOException(e);
429+
}
423430
}
424431

425432
@Override
426433
public synchronized boolean hasNext() {
427434
if (need2Read) {
428-
haveNext = read() != -1;
429-
if (haveNext) {
435+
try {
436+
haveNext = read() != -1;
437+
if (haveNext) {
438+
need2Read = false;
439+
}
440+
} catch (IOException e) {
441+
haveNext = false;
430442
need2Read = false;
443+
throw new UncheckedIOException(e);
444+
} finally {
445+
if (!haveNext) {
446+
closeStream();
447+
}
431448
}
432-
return haveNext;
433449
}
434450
return haveNext;
435451
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2021, NTT DATA.
4+
*
5+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
6+
*
7+
* This code is free software; you can redistribute it and/or modify it
8+
* under the terms of the GNU General Public License version 2 only, as
9+
* published by the Free Software Foundation.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
/*
27+
* @test
28+
* @bug 8257736
29+
* @modules java.net.http
30+
* java.base/sun.net.www.http
31+
* java.net.http/jdk.internal.net.http.common
32+
* java.net.http/jdk.internal.net.http.frame
33+
* java.net.http/jdk.internal.net.http.hpack
34+
* @library http2/server
35+
* @build Http2TestServer Http2TestExchange
36+
* @compile HttpServerAdapters.java
37+
* @run testng/othervm StreamCloseTest
38+
*/
39+
40+
import com.sun.net.httpserver.HttpServer;
41+
42+
import java.io.InputStream;
43+
import java.io.IOException;
44+
import java.net.http.HttpClient;
45+
import java.net.http.HttpClient.Redirect;
46+
import java.net.http.HttpClient.Version;
47+
import java.net.http.HttpRequest;
48+
import java.net.http.HttpRequest.BodyPublishers;
49+
import java.net.http.HttpResponse.BodyHandlers;
50+
import java.net.InetAddress;
51+
import java.net.InetSocketAddress;
52+
import java.net.URI;
53+
54+
import org.testng.annotations.AfterTest;
55+
import org.testng.annotations.BeforeTest;
56+
import org.testng.annotations.Test;
57+
import org.testng.Assert;
58+
59+
public class StreamCloseTest {
60+
61+
private static class TestInputStream extends InputStream {
62+
private final boolean exceptionTest;
63+
private volatile boolean closeCalled;
64+
65+
public TestInputStream(boolean exceptionTest) {
66+
super();
67+
this.exceptionTest = exceptionTest;
68+
this.closeCalled = false;
69+
}
70+
71+
@Override
72+
public int read() throws IOException {
73+
if (exceptionTest) {
74+
throw new IOException("test");
75+
}
76+
return -1;
77+
}
78+
79+
@Override
80+
public void close() throws IOException {
81+
closeCalled = true;
82+
super.close();
83+
}
84+
}
85+
86+
private static HttpClient client;
87+
88+
private static HttpRequest.Builder requestBuilder;
89+
90+
private static HttpServerAdapters.HttpTestServer httpTestServer;
91+
92+
@BeforeTest
93+
public void setup() throws Exception {
94+
InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
95+
httpTestServer = HttpServerAdapters.HttpTestServer.of(HttpServer.create(sa, 0));
96+
httpTestServer.addHandler(new HttpServerAdapters.HttpTestEchoHandler(), "/");
97+
URI uri = URI.create("http://" + httpTestServer.serverAuthority() + "/");
98+
httpTestServer.start();
99+
100+
client = HttpClient.newBuilder()
101+
.version(Version.HTTP_1_1)
102+
.followRedirects(Redirect.ALWAYS)
103+
.build();
104+
requestBuilder = HttpRequest.newBuilder(uri);
105+
}
106+
107+
@AfterTest
108+
public void teardown() throws Exception {
109+
httpTestServer.stop();
110+
}
111+
112+
@Test
113+
public void normallyCloseTest() throws Exception{
114+
TestInputStream in = new TestInputStream(false);
115+
HttpRequest request = requestBuilder.copy()
116+
.POST(BodyPublishers.ofInputStream(() -> in))
117+
.build();
118+
client.send(request, BodyHandlers.discarding());
119+
Assert.assertTrue(in.closeCalled, "InputStream was not closed!");
120+
}
121+
122+
@Test
123+
public void closeTestOnException() throws Exception{
124+
TestInputStream in = new TestInputStream(true);
125+
HttpRequest request = requestBuilder.copy()
126+
.POST(BodyPublishers.ofInputStream(() -> in))
127+
.build();
128+
try {
129+
client.send(request, BodyHandlers.discarding());
130+
} catch (IOException e) { // expected
131+
Assert.assertTrue(in.closeCalled, "InputStream was not closed!");
132+
return;
133+
}
134+
Assert.fail("IOException should be occurred!");
135+
}
136+
}

0 commit comments

Comments
 (0)