Skip to content

Commit cd8f18b

Browse files
authored
MAPREDUCE-7503. Fix ByteBuf leaks in TestShuffleChannelHandler (#7500) Contributed by Istvan Toth.
Signed-off-by: Shilun Fan <[email protected]>
1 parent b73796a commit cd8f18b

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/java/org/apache/hadoop/mapred/TestShuffleChannelHandler.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import io.netty.handler.ssl.SslContextBuilder;
4545
import io.netty.handler.ssl.SslHandler;
4646
import io.netty.handler.stream.ChunkedWriteHandler;
47+
import io.netty.util.ReferenceCounted;
4748
import io.netty.util.concurrent.GlobalEventExecutor;
4849

4950
import java.io.ByteArrayOutputStream;
@@ -61,6 +62,7 @@
6162
import java.util.List;
6263
import java.util.Objects;
6364
import java.util.concurrent.ConcurrentHashMap;
65+
import java.util.concurrent.ExecutionException;
6466
import java.util.concurrent.TimeUnit;
6567

6668
import javax.crypto.SecretKey;
@@ -115,6 +117,7 @@ public void testGetMapsChunkedFileSSl() throws Exception {
115117
final LinkedList<Object> unencryptedMessages = new LinkedList<>();
116118
final EmbeddedChannel shuffle = t.createShuffleHandlerSSL(unencryptedMessages);
117119
t.testGetAllAttemptsForReduce0NoKeepAlive(unencryptedMessages, shuffle);
120+
drainChannel(shuffle);
118121
}
119122

120123
@Test
@@ -192,8 +195,10 @@ public void testIncompatibleShuffleVersion() {
192195

193196
assertEquals(getExpectedHttpResponse(HttpResponseStatus.BAD_REQUEST).toString(),
194197
actual.toString());
198+
tryRelease(actual);
195199

196200
assertFalse(shuffle.isActive(), "closed"); // known-issue
201+
drainChannel(decoder);
197202
}
198203

199204
@Test
@@ -210,11 +215,13 @@ public void testInvalidMapNoIndexFile() {
210215
}
211216

212217
DefaultHttpResponse actual = decoder.readInbound();
218+
drainChannel(decoder);
213219
assertFalse(actual.headers().get(CONTENT_LENGTH).isEmpty());
214220
actual.headers().set(CONTENT_LENGTH, 0);
215221

216222
assertEquals(getExpectedHttpResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR).toString(),
217223
actual.toString());
224+
tryRelease(actual);
218225

219226
assertFalse(shuffle.isActive(), "closed");
220227
}
@@ -237,15 +244,36 @@ public void testInvalidMapNoDataFile() {
237244
}
238245

239246
DefaultHttpResponse actual = decoder.readInbound();
247+
drainChannel(decoder);
240248
assertFalse(actual.headers().get(CONTENT_LENGTH).isEmpty());
241249
actual.headers().set(CONTENT_LENGTH, 0);
242250

243251
assertEquals(getExpectedHttpResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR).toString(),
244252
actual.toString());
253+
tryRelease(actual);
245254

246255
assertFalse(shuffle.isActive(), "closed");
247256
}
248257

258+
private void drainChannel(EmbeddedChannel ch) {
259+
Object o;
260+
while((o = ch.readInbound())!=null) {
261+
tryRelease(o);
262+
}
263+
while((o = ch.readOutbound())!=null) {
264+
tryRelease(o);
265+
}
266+
}
267+
268+
private void tryRelease(Object obj) {
269+
if (obj instanceof ReferenceCounted) {
270+
ReferenceCounted bb = (ReferenceCounted) obj;
271+
if (bb.refCnt() > 0) {
272+
bb.release(bb.refCnt());
273+
}
274+
}
275+
}
276+
249277
private DefaultHttpResponse getExpectedHttpResponse(HttpResponseStatus status) {
250278
DefaultHttpResponse response = new DefaultHttpResponse(HTTP_1_1, status);
251279
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
@@ -365,8 +393,8 @@ private void testGetAllAttemptsForReduce0NoKeepAlive(
365393
assertFalse(shuffle.isActive(), "no keep-alive");
366394
}
367395

368-
private void testKeepAlive(java.util.Queue<Object> messages,
369-
EmbeddedChannel shuffle) throws IOException {
396+
private void testKeepAlive(java.util.Queue<Object> messages, EmbeddedChannel shuffle)
397+
throws IOException, InterruptedException, ExecutionException {
370398
final FullHttpRequest req1 = createRequest(
371399
getUri(TEST_JOB_ID, 0, Collections.singletonList(TEST_ATTEMPT_1), true));
372400
shuffle.writeInbound(req1);
@@ -375,6 +403,7 @@ private void testKeepAlive(java.util.Queue<Object> messages,
375403
getAttemptData(new Attempt(TEST_ATTEMPT_1, TEST_DATA_A))
376404
);
377405
assertTrue(shuffle.isActive(), "keep-alive");
406+
drainChannel(shuffle);
378407
messages.clear();
379408

380409
final FullHttpRequest req2 = createRequest(
@@ -385,6 +414,7 @@ private void testKeepAlive(java.util.Queue<Object> messages,
385414
getAttemptData(new Attempt(TEST_ATTEMPT_2, TEST_DATA_B))
386415
);
387416
assertTrue(shuffle.isActive(), "keep-alive");
417+
drainChannel(shuffle);
388418
messages.clear();
389419

390420
final FullHttpRequest req3 = createRequest(
@@ -395,6 +425,7 @@ private void testKeepAlive(java.util.Queue<Object> messages,
395425
getAttemptData(new Attempt(TEST_ATTEMPT_3, TEST_DATA_C))
396426
);
397427
assertFalse(shuffle.isActive(), "no keep-alive");
428+
drainChannel(shuffle);
398429
}
399430

400431
private ArrayList<ByteBuf> getAllAttemptsForReduce0() throws IOException {
@@ -431,11 +462,13 @@ private void assertResponse(java.util.Queue<Object> outboundMessages,
431462
decodeChannel.writeInbound(actualBytes);
432463
Object obj = decodeChannel.readInbound();
433464
LOG.info("Decoded object: {}", obj);
465+
drainChannel(decodeChannel);
434466

435467
if (i == 0) {
436468
DefaultHttpResponse resp = (DefaultHttpResponse) obj;
437469
assertEquals(response.toString(), resp.toString());
438470
}
471+
tryRelease(obj);
439472
if (i > 0 && i <= content.size()) {
440473
assertEquals(ByteBufUtil.prettyHexDump(content.get(i - 1)),
441474
actualHexdump, "data should match");

hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/java/org/apache/hadoop/mapred/TestShuffleHandlerBase.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ public void setup() throws IOException {
8484

8585
@AfterEach
8686
public void teardown() {
87+
//Trigger GC so that we get the leak warnings early
88+
System.gc();
89+
try {
90+
// Wait for logger to flush
91+
Thread.sleep(1000);
92+
} catch (InterruptedException e) {
93+
}
8794
System.setOut(standardOut);
8895
System.out.print(outputStreamCaptor);
8996
// For this to work ch.qos.logback.classic is needed for some reason

0 commit comments

Comments
 (0)