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

Use of RequestPart with String leads to a file descriptor leak #27773

Closed
samudra77 opened this issue Dec 6, 2021 · 11 comments
Closed

Use of RequestPart with String leads to a file descriptor leak #27773

samudra77 opened this issue Dec 6, 2021 · 11 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@samudra77
Copy link

samudra77 commented Dec 6, 2021

StringHttpMessageConvertor is reading the InputStream but it's not closing it.
This keeps the file handle open and leaks file handles.
I am using @RequestPart in rest controller with string parameter.
the request is a multipart form data. Other Jackson converters are closing the streams.
The stream is pointing to a temporary file created by servlet api on tomcat.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 6, 2021
@jhoeller jhoeller added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Dec 7, 2021
@jhoeller jhoeller added this to the Triage Queue milestone Dec 7, 2021
@symphony-youri
Copy link

I'm observing this issue too with 2.2.10.RELEASE and more recent versions (up to 2.6.1)

@rstoyanchev
Copy link
Contributor

The behavior goes back to 6661788, and looking at the description in #14728 and #14869, it was related to a server writing with multipart data. However, it was also applied on the reading side too, and it's not clear why, just from reading the linked issues.

One option would be to re-introduce FileCopyUtils on the reading side. Another, to close the InputStream on the server side after reading the body, e.g. in AbstractMessageConverterMethodArgumentResolver. There is a PR along those lines #25897.

@symphony-youri
Copy link

symphony-youri commented Dec 15, 2021

As a workaround, moving from @RequestPart to @RequestParam for strings parameters fixes the issue (as the converter won't be used).

@rstoyanchev rstoyanchev added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Dec 15, 2021
@samudra77
Copy link
Author

Yes I have changed the code to use request param for string arguments. But wondering if it's also server specific? Noticed this happening with Tomcat after recent patches applied on but couldn't figure out who is the culprit and should be responsible for closing the stream. Someone has to close it. Application code unfortunately has no handle to it.

@symphony-youri
Copy link

Interesting, I'm wondering why this issue has been unnoticed in our application. And it has been detected by one user after we (force) upgraded the dependency to tomcat core to 9.0.54 (was 9.0.38 before).

@symphony-youri
Copy link

Actually changing to @RequestParam broke some of our API clients that were sending a part with a filename set even though it was a string content. So we had to revert this change.

We are trying another workaround based on a RequestBodyAdvice:

@ControllerAdvice
@Component
public class CloseStringRequestPartAdvice extends RequestBodyAdviceAdapter {
  @Override
  public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    // being extra careful to only apply it for @RequestPart String parameters
    return targetType != null
        && String.class.getTypeName().equals(targetType.getTypeName())
        && converterType.equals(StringHttpMessageConverter.class)
        && methodParameter.getParameterAnnotations() != null
        && methodParameter.getParameterAnnotations().length >= 1
        && RequestPart.class.equals(methodParameter.getParameterAnnotations()[0].annotationType());
  }

  @Override
  public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
    // extra check, to really be sure we try to close it when needed
    if (supports(parameter, targetType, converterType)) {
      try {
        inputMessage.getBody().close();
      } catch (Exception e) {
        // we rather silently fail than prevent the request from working and eventually leaking a file descriptor
        LOG.debug("Failed to close form data string", e);
      }
    }
    return body;
  }
}

There are extra (and specific to our use case probably) checks done to avoid closing streams inadvertently.

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Jan 10, 2022

@samudra77 and @symphony-youri, can you confirm what kind of part this is for, is it a file or just plain text?

I see a potentially related issue in Tomcat https://bz.apache.org/bugzilla/show_bug.cgi?id=65710 that suggests a JVM issue, which can be confirmed by trying with Java 11. There was also a fix implemented in Tomcat 9.0.56 to make up for the issue, so you could try upgrading to 9.0.56 too and see if it makes a difference?

@symphony-youri
Copy link

It was for plain text (that ends up serialized as a temp file).
I'll try to update to see if it makes a difference. Thanks for spotting the Tomcat issue.

@samudra77
Copy link
Author

samudra77 commented Jan 10, 2022

We are also passing string data which is leaking. File data requests are not leaking. We are on Tomcat 8.5 and I have read the Tomcat issue and looked at the Tomcat class which doesn't seem to have the problematic line mentioned in the Tomcat issue thread

new FileInputStream(dfos.getFile());
to:
Files.newInputStream(dfos.getFile().toPath());

But regardless of that. I am thinking if StringHttpMessageConverter is doing the right thing or not. Should it close the stream it's reading after converting to string.

@symphony-youri
Copy link

I tried with Tomcat 9.0.56 (by upgrading to Spring Boot 2.6.2) and the issue is still there.
I also tried with Java 11 and it still happens.

@rstoyanchev rstoyanchev modified the milestones: 6.0.0-M2, 5.3.15 Jan 11, 2022
@rstoyanchev rstoyanchev changed the title StringHttpMessageConverter not closing inputstream Use of RequestPart with String leads to a file descriptor leak Jan 11, 2022
@rstoyanchev rstoyanchev assigned jhoeller and unassigned rstoyanchev Jan 11, 2022
@rstoyanchev
Copy link
Contributor

rstoyanchev commented Jan 11, 2022

Thanks for trying. I think the auto-cleanup happens with garbage collection through Tomcat's DiskFileItem#finalize method, and/or similar mechanisms in the file I/O of the JDK are also potentially at play. Keep in mind the reporter on the Tomcat issue is trying with a while loop sending requests, so it's possible that it does make a difference still depending on how you're checking for the leak.

That said we don't want to rely on any of these implicit mechanism and we will ensure that the resolver for @RequestPart closes the InputStream as it should.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

5 participants