Skip to content

Commit 09eccb0

Browse files
jeet1995Copilot
andauthored
Fix Netty ByteBuf leak in GatewayServerErrorInjector fault injection delay paths (#48880)
When fault injection simulates a response delay, the real HTTP response body was discarded without draining its ByteBuf content. Under HTTP/2, this causes ByteBuf leaks that accumulate on the long-lived parent channel. Two leak paths fixed: - delay >= timeout: doOnNext drains body before delayElement buffers it - delay < timeout: doOnDiscard releases body if downstream cancels during delay Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 388debe commit 09eccb0

2 files changed

Lines changed: 23 additions & 1 deletion

File tree

sdk/cosmos/azure-cosmos-test/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#### Breaking Changes
88

99
#### Bugs Fixed
10+
* Fixed Netty ByteBuf leak in `GatewayServerErrorInjector` fault injection delay paths under HTTP/2. - See [PR 48880](https://github.com/Azure/azure-sdk-for-java/pull/48880)
1011

1112
#### Other Changes
1213

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/implementation/faultinjection/GatewayServerErrorInjector.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.azure.cosmos.implementation.routing.PartitionKeyInternalHelper;
2222
import io.netty.channel.ConnectTimeoutException;
2323
import io.netty.handler.timeout.ReadTimeoutException;
24+
import io.netty.util.ReferenceCountUtil;
2425
import reactor.core.publisher.Mono;
2526

2627
import java.net.URI;
@@ -174,11 +175,18 @@ public Mono<HttpResponse> injectGatewayErrors(
174175
if (this.injectGatewayServerResponseDelayAfterProcessing(faultInjectionRequestArgs, delayToBeInjected)) {
175176
if (delayToBeInjected.v.toMillis() >= effectiveResponseTimeout.toMillis()) {
176177
return originalResponseMono
178+
.doOnNext(GatewayServerErrorInjector::releaseResponseBody)
177179
.delayElement(delayToBeInjected.v)
178180
.then(Mono.error(new ReadTimeoutException()));
179181
} else {
182+
// In the happy path the response flows downstream after the delay
183+
// and the caller drains the body normally. However, if the downstream
184+
// cancels during delayElement (e.g., an outer timeout fires),
185+
// delayElement silently drops the buffered HttpResponse. doOnDiscard
186+
// catches that discard and releases the body ByteBuf to prevent leaks.
180187
return originalResponseMono
181-
.delayElement(delayToBeInjected.v);
188+
.delayElement(delayToBeInjected.v)
189+
.doOnDiscard(HttpResponse.class, GatewayServerErrorInjector::releaseResponseBody);
182190
}
183191
}
184192

@@ -243,4 +251,17 @@ private GatewayFaultInjectionRequestArgs createFaultInjectionRequestArgs(
243251
serviceRequest,
244252
partitionKeyRangeIds);
245253
}
254+
255+
/**
256+
* Drains and releases the response body to prevent Netty ByteBuf leaks.
257+
* Called when a fault-injected path discards the real HTTP response (e.g.,
258+
* simulating a timeout after the response has already arrived).
259+
*/
260+
private static void releaseResponseBody(HttpResponse httpResponse) {
261+
httpResponse.body()
262+
.subscribe(
263+
buf -> ReferenceCountUtil.safeRelease(buf),
264+
ex -> { },
265+
() -> { });
266+
}
246267
}

0 commit comments

Comments
 (0)