Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http-utils: fix leak in AbstractTimeoutHttpFilter #3038

Merged
merged 7 commits into from
Aug 13, 2024

Conversation

bryce-anderson
Copy link
Contributor

Motivation:

The Single.timeout(..) family of operations are part of a class of operations will short-circuit the response based on the cancellation pathway, specifically they will return a TimeoutException down the error path. Because cancellation and the result are intrinsically racy there is currently the possibility that the result will be generated but we cannot return it to the subscriber having already given them an Exception in the error pathway. For values that are stateful this can result in a resource leak. One such leak occurs in the AbstractTimeoutHttpFilter.

Modifications:

Modify the AbstractTimeoutHttpFilter to keep a handle on the resource and make sure we close it if we receive a cancellation. This is a localized fix but appears to be effective.

Result:

One less leak.

Motivation:

The Single.timeout(..) family of operations are part of a class of operations
will short-circuit the response based on the cancellation pathway, specifically
they will return a TimeoutException down the error path. Because cancellation
and the result are intrinsically racy there is currently the possibility that
the result will be generated but we cannot return it to the subscriber having
already given them an Exception in the error pathway. For values that are
stateful this can result in a resource leak. One such leak occurs in the
AbstractTimeoutHttpFilter.

Modifications:

Modify the AbstractTimeoutHttpFilter to keep a handle on the resource and make
sure we close it if we receive a cancellation. This is a localized fix but
appears to be effective.

Result:

One less leak.
@@ -117,7 +122,9 @@ final Single<StreamingHttpResponse> withTimeout(final StreamingHttpRequest reque
final Duration timeout = timeoutForRequest.apply(request, useForTimeout);
Single<StreamingHttpResponse> response = responseFunction.apply(request);
if (null != timeout) {
final Single<StreamingHttpResponse> timeoutResponse = response.timeout(timeout, useForTimeout);
final Single<StreamingHttpResponse> timeoutResponse = response
.<StreamingHttpResponse>liftSync(CleanupSubscriber::new)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feels like we need something similar inside JavaNetSoTimeoutHttpConnectionFilter as well. In that case, if timeout delivers onError, amb operator will do the cancel for the response and then we are in the same situation of response racing with cancel.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about in a followup?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me

Copy link
Member

@idelpivnitskiy idelpivnitskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, let's just give some time for others to review

try {
Object current = stateUpdater.getAndSet(this, COMPLETE);
if (current instanceof StreamingHttpResponse) {
clean((StreamingHttpResponse) current);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we've propagated the StreamingHttpResponse via delegate.onSuccess() isn't it possible this will generate a duplicate subscribe? Can you add comments to clarify why this is OK? I see some comments in the test but not clear this covers all scenarios (someone may call cancel() after Subscriber.onSuccess() despite RS#2.3 and this maybe racy).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TL;DR is that if we get a cancel it passed through the timeout operator and 'won', therefore the message will never be propagated. I added clarifying comments and pointed to the test that ensures the behavior.

@bryce-anderson bryce-anderson merged commit d09421a into apple:main Aug 13, 2024
11 checks passed
@bryce-anderson bryce-anderson deleted the bl_anderson/timeout-leak branch August 13, 2024 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants