Skip to content

Commit 92b7424

Browse files
authored
Fix connection leak in rest proxy when return type is void or mono<void> (Azure#30072)
* Fix connection leak in rest proxy when return type is void or mono<void> * changelog.
1 parent 3f0138c commit 92b7424

File tree

7 files changed

+82
-20
lines changed

7 files changed

+82
-20
lines changed

sdk/core/azure-core-perf/src/main/java/com/azure/core/perf/BinaryDataSendTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public void run() {
2828

2929
@Override
3030
public Mono<Void> runAsync() {
31-
return service.setBinaryData(endpoint, id, binaryDataSupplier.get(), length)
32-
.then();
31+
return Mono.fromSupplier(binaryDataSupplier)
32+
.flatMap(data -> service.setBinaryData(endpoint, id, data, length));
3333
}
3434
}

sdk/core/azure-core-perf/src/main/java/com/azure/core/perf/PipelineSendTest.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,23 @@ public void run() {
4848

4949
@Override
5050
public Mono<Void> runAsync() {
51-
HttpHeaders headers = new HttpHeaders();
52-
headers.set("Content-Length", contentLengthHeaderValue);
53-
HttpRequest httpRequest = new HttpRequest(
54-
HttpMethod.PUT, targetURL, headers, binaryDataSupplier.get().toFluxByteBuffer());
55-
// Context with azure-eagerly-read-response=true makes sure
56-
// that response is disposed to prevent connection leak.
57-
// There's no response body in this scenario anyway.
58-
return httpPipeline.send(httpRequest, context)
59-
.map(httpResponse -> {
60-
if (httpResponse.getStatusCode() / 100 != 2) {
61-
throw new IllegalStateException("Endpoint didn't return 2xx http status code.");
62-
}
63-
return httpResponse;
64-
})
65-
.then();
51+
return Mono.fromSupplier(binaryDataSupplier)
52+
.flatMap(data -> {
53+
HttpHeaders headers = new HttpHeaders();
54+
headers.set("Content-Length", contentLengthHeaderValue);
55+
HttpRequest httpRequest = new HttpRequest(
56+
HttpMethod.PUT, targetURL, headers, data);
57+
// Context with azure-eagerly-read-response=true makes sure
58+
// that response is disposed to prevent connection leak.
59+
// There's no response body in this scenario anyway.
60+
return httpPipeline.send(httpRequest, context)
61+
.map(httpResponse -> {
62+
if (httpResponse.getStatusCode() / 100 != 2) {
63+
throw new IllegalStateException("Endpoint didn't return 2xx http status code.");
64+
}
65+
return httpResponse;
66+
})
67+
.then();
68+
});
6669
}
6770
}

sdk/core/azure-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
### Bugs Fixed
1010

11+
- Fixed bug where `RestProxy` could leak connection if service method returned `Mono<Void>` or `void`.
12+
1113
### Other Changes
1214

1315
## 1.30.0 (2022-06-30)

sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/rest/AsyncRestProxy.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ private Object handleRestReturnType(final Mono<HttpResponseDecoder.HttpDecodedRe
206206
final Type monoTypeParam = TypeUtil.getTypeArgument(returnType);
207207
if (TypeUtil.isTypeOrSubTypeOf(monoTypeParam, Void.class)) {
208208
// ProxyMethod ReturnType: Mono<Void>
209-
result = asyncExpectedResponse.then();
209+
result = asyncExpectedResponse.doOnNext(HttpResponseDecoder.HttpDecodedResponse::close).then();
210210
} else {
211211
// ProxyMethod ReturnType: Mono<? extends RestResponseBase<?, ?>>
212212
result = asyncExpectedResponse.flatMap(response ->
@@ -218,7 +218,7 @@ private Object handleRestReturnType(final Mono<HttpResponseDecoder.HttpDecodedRe
218218
} else if (TypeUtil.isTypeOrSubTypeOf(returnType, void.class) || TypeUtil.isTypeOrSubTypeOf(returnType,
219219
Void.class)) {
220220
// ProxyMethod ReturnType: Void
221-
asyncExpectedResponse.block();
221+
asyncExpectedResponse.doOnNext(HttpResponseDecoder.HttpDecodedResponse::close).block();
222222
result = null;
223223
} else {
224224
// ProxyMethod ReturnType: T where T != async (Mono, Flux) or sync Void

sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/rest/SyncRestProxy.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ private Object handleRestReturnType(final HttpResponseDecoder.HttpDecodedRespons
210210
if (TypeUtil.isTypeOrSubTypeOf(returnType, void.class) || TypeUtil.isTypeOrSubTypeOf(returnType,
211211
Void.class)) {
212212
// ProxyMethod ReturnType: Void
213-
result = expectedResponse;
213+
expectedResponse.close();
214+
result = null;
214215
} else {
215216
// ProxyMethod ReturnType: T where T != async (Mono, Flux) or sync Void
216217
// Block the deserialization until a value T is received

sdk/core/azure-core/src/test/java/com/azure/core/http/rest/RestProxyTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ Mono<Response<Void>> testMethod(
7070
@HeaderParam("Content-Length") Long contentLength
7171
);
7272

73+
@Get("my/url/path")
74+
@ExpectedResponses({200})
75+
Mono<Void> testMethodReturnsMonoVoid();
76+
77+
@Get("my/url/path")
78+
@ExpectedResponses({200})
79+
void testVoidMethod();
80+
7381
@Get("my/url/path")
7482
@ExpectedResponses({200})
7583
StreamResponse testDownload();
@@ -179,6 +187,35 @@ public void doesNotChangeBinaryDataContentType(BinaryData data, long contentLeng
179187
assertEquals(expectedContentClazz, actualContentClazz);
180188
}
181189

190+
@Test
191+
public void monoVoidReturningApiClosesResponse() {
192+
LocalHttpClient client = new LocalHttpClient();
193+
HttpPipeline pipeline = new HttpPipelineBuilder()
194+
.httpClient(client)
195+
.build();
196+
197+
TestInterface testInterface = RestProxy.create(TestInterface.class, pipeline);
198+
StepVerifier.create(
199+
testInterface.testMethodReturnsMonoVoid())
200+
.verifyComplete();
201+
202+
Mockito.verify(client.lastResponseSpy).close();
203+
}
204+
205+
@Test
206+
public void voidReturningApiClosesResponse() {
207+
LocalHttpClient client = new LocalHttpClient();
208+
HttpPipeline pipeline = new HttpPipelineBuilder()
209+
.httpClient(client)
210+
.build();
211+
212+
TestInterface testInterface = RestProxy.create(TestInterface.class, pipeline);
213+
214+
testInterface.testVoidMethod();
215+
216+
Mockito.verify(client.lastResponseSpy).close();
217+
}
218+
182219
private static Stream<Arguments> doesNotChangeBinaryDataContentTypeDataProvider() throws Exception {
183220
String string = "hello";
184221
byte[] bytes = string.getBytes();

sdk/core/azure-core/src/test/java/com/azure/core/implementation/http/rest/SyncRestProxyTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,25 @@ Response<Void> testMethod(
6262
@Get("my/url/path")
6363
@ExpectedResponses({200})
6464
StreamResponse testDownload(Context context);
65+
66+
@Get("my/url/path")
67+
@ExpectedResponses({200})
68+
void testVoidMethod(Context context);
69+
}
70+
71+
@Test
72+
public void voidReturningApiClosesResponse() {
73+
LocalHttpClient client = new LocalHttpClient();
74+
HttpPipeline pipeline = new HttpPipelineBuilder()
75+
.httpClient(client)
76+
.build();
77+
78+
TestInterface testInterface = RestProxy.create(TestInterface.class, pipeline);
79+
80+
Context context = new Context(HTTP_REST_PROXY_SYNC_PROXY_ENABLE, true);
81+
testInterface.testVoidMethod(context);
82+
83+
Mockito.verify(client.lastResponseSpy).close();
6584
}
6685

6786
@Test

0 commit comments

Comments
 (0)