Resource leak when Controller returns a ResponseEntity<InputStreamResource>

See original GitHub issue

Affects: 5.1.5

I return a stream with BodyBuilder.body(new InputStreamResource(stream)) from a @Controller:

@SpringBootApplication
@Controller
public class Issue22626 {

	@Autowired(required = false)
	private Supplier<InputStream> source = () -> new NullInputStream(10000);

	@GetMapping("/")
	public ResponseEntity<InputStreamResource> download() {
		var resource = new InputStreamResource(source.get());
		return ok().eTag("Issue22626").lastModified(22626).body(resource);
	}

	public static void main(String[] args) {
		run(Issue22626.class, args);
	}
}

That stream is a proxy which helps me to detect resource leaks. The proxy contains the stack trace of the last read operation and a scheduled task to close the resource and log an error after 10 minutes of inactivity.

In the region 5 out of 10000 streams, I see a resource leak. That is, the log shows the “Returning resource” message, then shows that the thread is already serving others requests and then after 10 minutes I see a message from my stream proxy that the resource is leaking. There was no read operation at all called on that stream. Which means the framework left the resource untouched and never reaches ResourceHttpMessageConverter.writeContent() (which would then close the stream). I tried to dig into the framework, but it is for me not possible to understand what is going on at all.

~Note, this is an production issue which happens in the region 5 out of 10000, I cannot provide something to reproduce this memory leak. I will however try my best to give some more follow up information.~ I will provide a minimal application to reproduce the issue.

All I can see is that the framework assumes that ResourceHttpMessageConverter.writeContent() would close the resource. I think that is too late. Obviously many things could happen before reaching that place (and something silently does… ~my suspicion goes into a unfortunately timed client sided connection closing~) which leaves the resource unclosed. Could you please revise the framework code in regard of serving a Controller which returns an InputStream and make sure that the very first thing into which this Controller returns wraps the stream into a try-with-resource block?

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:1
  • Comments:14 (6 by maintainers)

github_iconTop GitHub Comments

2reactions
mixcloud-downloadercommented, Apr 15, 2019

Wait, from a user point of view I don’t even notice that. As a user I just write a controller which returns a ResponseEntity<InputStreamResource>. I was totally unaware of the framework magic which happens afterwards and leads to the memory leak. If you decide that’s an OK behavior for the framework, don’t you at least think it need some bit of documentation that InputStreamResource is broken by design?

Edit: Also just not opening the resource in the controller because the framework decides somewhere later not to read it is not trivial at all. In my case I need to open the resource beforehand to know which Headers I would want to send. From that moment on the resource is open and now I give up control to the framework. Would the framework please be so kind and if it decides not to read the resource please just close it. It is a very unintuitive design that the framework sometimes closes the resource and sometimes not.

Edit2: Also here a short excerpt from InputStreamResource documentation:

this is a descriptor for an already opened resource - therefore returning {@code true} from {@link #isOpen()}. Do not use an {@code InputStreamResource} if you need to keep the resource descriptor somewhere, or if you need to read from a stream multiple times.

I find it very misleading that your framework is designed to accept an already opened resource but not closing it. Please fix it or at the very least make your design decision very clear in the documentation.

1reaction
rstoyanchevcommented, Apr 19, 2019

Also if we were to accept responsibility for dealing with open resources in the return value from a controller method in one case, we would have to assume all such cases which would include return asynchronous return values like DeferredResult<Resource> or DeferredResult<HttpEntity<Resource>> and the like, which would more or less be not possible since it involves an async dispatch through the servlet container that cannot be wrapped with try-finally.

Read more comments on GitHub >

github_iconTop Results From Across the Web

InputStream is closed in spring controller - java - Stack Overflow
However, I am not sure whether the resource will be closed correctly to prevent application from resource leak. response = ResponseEntity.ok() .
Read more >
Spring MVC: save memory with lazy streams in RESTful services
The application writer is mostly concerned with providing controller ... is its return value; in other words, the view will pull data from...
Read more >
Streaming file content efficiently without risking resource leaks
The application had a file handle leak in the endpoint that serves binary data. The reason for this was that a InputStream was...
Read more >
Returning Image/Media Data with Spring MVC - Baeldung
In this tutorial, we'll illustrate how to return images and other media using the Spring MVC framework. We will discuss several approaches, ...
Read more >
6. Resources - Spring
Will be false for all usual resource implementations, with the exception of InputStreamResource . getDescription() : returns a description for this resource, to ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found