Resource leak when Controller returns a ResponseEntity<InputStreamResource>
See original GitHub issueAffects: 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:
- Created 5 years ago
- Reactions:1
- Comments:14 (6 by maintainers)
Top Related StackOverflow Question
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 thatInputStreamResourceis 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
InputStreamResourcedocumentation: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.
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>orDeferredResult<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.