Skip to content

Commit 43ca55d

Browse files
author
Sophie Guo
committed
Fix memory leak for batch delete
1 parent 7b5f74b commit 43ca55d

File tree

2 files changed

+94
-1
lines changed

2 files changed

+94
-1
lines changed

ambry-frontend/src/main/java/com/github/ambry/frontend/s3/S3BatchDeleteHandler.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,19 @@ private Callback<Long> parseRequestBodyAndDeleteCallback(RetainingAsyncWritableC
187187
*/
188188
public S3MessagePayload.S3BatchDeleteObjects deserializeRequest(RetainingAsyncWritableChannel channel)
189189
throws RestServiceException {
190+
ByteBuf byteBuffer = null;
190191
try {
191-
ByteBuf byteBuffer = channel.consumeContentAsByteBuf();
192+
byteBuffer = channel.consumeContentAsByteBuf();
192193
byte[] byteArray = new byte[byteBuffer.readableBytes()];
193194
byteBuffer.readBytes(byteArray);
194195
return new XmlMapper().readValue(byteArray, S3MessagePayload.S3BatchDeleteObjects.class);
195196
} catch (Exception e) {
196197
logger.trace("s3batchdelete failed to deserialize request");
197198
throw new RestServiceException("failed to deserialize", e, RestServiceErrorCode.BadRequest);
199+
} finally {
200+
if (byteBuffer != null) {
201+
byteBuffer.release();
202+
}
198203
}
199204
}
200205
}

ambry-frontend/src/test/java/com/github/ambry/frontend/S3BatchDeleteHandlerTest.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
*/
1515
package com.github.ambry.frontend;
1616
import com.github.ambry.commons.InMemNamedBlobDbFactory;
17+
import com.github.ambry.commons.RetainingAsyncWritableChannel;
18+
import io.netty.buffer.ByteBuf;
19+
import io.netty.buffer.Unpooled;
1720
import java.nio.charset.StandardCharsets;
1821

1922
import com.codahale.metrics.MetricRegistry;
@@ -42,17 +45,22 @@
4245
import com.github.ambry.router.FutureResult;
4346
import com.github.ambry.router.InMemoryRouter;
4447
import com.github.ambry.router.ReadableStreamChannel;
48+
import com.github.ambry.utils.NettyByteBufLeakHelper;
4549
import com.github.ambry.utils.TestUtils;
4650
import java.nio.ByteBuffer;
4751
import java.util.Arrays;
4852
import java.util.Collections;
4953
import java.util.LinkedList;
5054
import java.util.Properties;
5155
import org.json.JSONObject;
56+
import org.junit.After;
57+
import org.junit.Before;
5258
import org.junit.Test;
59+
import org.mockito.Mockito;
5360

5461
import static com.github.ambry.frontend.s3.S3Constants.*;
5562
import static org.junit.Assert.*;
63+
import static org.mockito.Mockito.*;
5664

5765

5866
public class S3BatchDeleteHandlerTest {
@@ -67,6 +75,8 @@ public class S3BatchDeleteHandlerTest {
6775
private FrontendConfig frontendConfig;
6876
private NamedBlobPutHandler namedBlobPutHandler;
6977
private S3BatchDeleteHandler s3BatchDeleteHandler;
78+
private final NettyByteBufLeakHelper nettyByteBufLeakHelper = new NettyByteBufLeakHelper();
79+
7080

7181
public S3BatchDeleteHandlerTest() throws Exception {
7282
account = ACCOUNT_SERVICE.createAndAddRandomAccount();
@@ -83,6 +93,16 @@ public S3BatchDeleteHandlerTest() throws Exception {
8393
performPutOperation(KEY_NAME_3, KEY_NAME, container, account);
8494
}
8595

96+
@Before
97+
public void before() {
98+
nettyByteBufLeakHelper.beforeTest();
99+
}
100+
101+
@After
102+
public void after() {
103+
nettyByteBufLeakHelper.afterTest();
104+
}
105+
86106
@Test
87107
public void deleteObjectTest() throws Exception {
88108
String uri = String.format("/s3/%s/%s", account.getName(), container.getName());
@@ -123,6 +143,74 @@ public void deleteObjectTest() throws Exception {
123143
assertEquals("Mismatch on status", ResponseStatus.Ok, restResponseChannel.getStatus());
124144
}
125145

146+
@Test
147+
public void testBufferReleaseInDeserializeRequest() throws Exception {
148+
// Arrange
149+
S3BatchDeleteHandler handler = new S3BatchDeleteHandler(null, null);
150+
RetainingAsyncWritableChannel channel = mock(RetainingAsyncWritableChannel.class);
151+
ByteBuf mockByteBuf = mock(ByteBuf.class);
152+
153+
String validXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
154+
"<Delete xmlns=\"http://s3.amazonaws.com/doc/2006-03-01\">" +
155+
"<Object>" +
156+
"<Key>key-success</Key>" +
157+
"</Object>" +
158+
"<Object>" +
159+
"<Key>key-error</Key>" +
160+
"</Object>" +
161+
"<Object>" +
162+
"<Key>key-error2</Key>" +
163+
"</Object>" +
164+
"<Object>" +
165+
"<Key>key-success-2</Key>" +
166+
"</Object>" +
167+
"</Delete>";
168+
169+
byte[] xmlBytes = validXml.getBytes();
170+
171+
when(channel.consumeContentAsByteBuf()).thenReturn(mockByteBuf);
172+
when(mockByteBuf.readableBytes()).thenReturn(xmlBytes.length);
173+
doAnswer(invocation -> {
174+
byte[] buffer = invocation.getArgument(0);
175+
System.arraycopy(xmlBytes, 0, buffer, 0, xmlBytes.length);
176+
return null;
177+
}).when(mockByteBuf).readBytes(Mockito.any(byte[].class));
178+
179+
S3MessagePayload.S3BatchDeleteObjects result = handler.deserializeRequest(channel);
180+
181+
assertNotNull(result);
182+
verify(mockByteBuf, times(1)).release(); // Verify that release() was called
183+
}
184+
185+
@Test
186+
public void testDeserializeRequestWithRealChannel() throws Exception {
187+
// Arrange
188+
String validXml =
189+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<Delete xmlns=\"http://s3.amazonaws.com/doc/2006-03-01\">"
190+
+ "<Object>" + "<Key>key-success</Key>" + "</Object>" + "<Object>" + "<Key>key-error</Key>" + "</Object>"
191+
+ "</Delete>";
192+
193+
ByteBuf byteBuf = Unpooled.wrappedBuffer(validXml.getBytes());
194+
RetainingAsyncWritableChannel channel = new RetainingAsyncWritableChannel();
195+
channel.write(byteBuf, (result, exception) -> {
196+
if (exception != null) {
197+
fail("Failed to write to channel: " + exception.getMessage());
198+
}
199+
});
200+
201+
S3BatchDeleteHandler handler = new S3BatchDeleteHandler(null, null);
202+
203+
// Act
204+
S3MessagePayload.S3BatchDeleteObjects result = handler.deserializeRequest(channel);
205+
206+
// Assert
207+
assertNotNull(result);
208+
assertEquals(2, result.getObjects().size());
209+
assertEquals("key-success", result.getObjects().get(0).getKey());
210+
assertEquals("key-error", result.getObjects().get(1).getKey());
211+
}
212+
213+
126214
@Test
127215
public void malformedXMLRequestTest() throws Exception {
128216
String uri = String.format("/s3/%s/%s", account.getName(), container.getName());

0 commit comments

Comments
 (0)