-
Notifications
You must be signed in to change notification settings - Fork 38.5k
Memory Leak with SseEmitter #33340
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
Comments
Here is the code in question from ResponseBodyEmitter:
|
Thanks for the report @randeepbydesign. It looks like you are still investigating, but this caught my eye:
It would be great if could you clarify how the edit: please also tell us the latest exact version that you used in which you encounter the issue 🙏 |
In addition to the version information which is crucial, have you registered an The Spring Framework version in the SO report is very old 5.0.7, and fails here while flushing when the client had gone away, and the handler initialization, a few lines below, is never reached. This was improved in db3d537 for Spring Framework 5.2.10. Now |
Hi, thank you for the reply and my apology for not being clear with these details at the outset. A common theme in our error logs is a Broken pipe exception when attempting to send an SSE event:
We do have handlers defined for |
Just to follow up, our |
You can see the handling here. It either invokes |
I think I may have started this with a red herring. As part of the initial investigation I reported that:
After looking at some added logs it looks like we are handling these errors properly on a send operation:
The client is properly deregistered at that point. We are now pivoting towards a look at the We are setting up some reflection interceptors to monitor this in the cloud. One active question we have that could help if you know the answer: how is handler populated and how might it go wrong? Looking at the code it happens in a method: |
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed. |
It's either Is it possible that there is a large number of messages being sent immediately during that brief period when the emitter is buffering messages before it has been initialized? If there are enough requests in parallel, then the buffering for each could add up to a point that where the process to runs out of memory. At the moment we don't have anything on |
I think the idea of a burst of messages being sent immediately is unlikely as the service runs for several days, heap size grows during that time, and it eventually OOMs. We have some more tooling in place to collect data and I'll be sure to report back here. |
The logic for initializing This is why I'm raising the possibility that the issue could be a result of a natural accumulation of early messages, and that could peak at any point, even after several days. |
We isolated the root cause. There may be some blame on both sides but... it's probably mostly on my side :0) The issue occurs because we create an When the validations fail, the SSEEmitter remains in our Set and get requests to send messages back to a recipient that already got a 400 response. "Ok cool," you might say, "but when the 400 was generated shouldn't the Because of this, |
It sounds a lot like a lifecycle issue in your application controller, then.
Is there anything we can do to prevent this situation? |
If I understand correctly, the controller method never returns? The If this is not correct, feel free to clarify. However, either way it sounds like you need to perform validations first before returning the SseEmitter or caching it. |
@rstoyanchev, I think your assessment is mostly correct. The controller method returns, but returns a json object instead of the SseEmitter. It makes sense that, if the SseEmitter is never returned that it cannot be intercepted with the current pattern. And it was clearly a mistake on our part. However, I have a feeling that the nature of SSE means that this could occur for other people. By "nature" I simply mean that to work with SSE, applications will need to keep these references in memory. Once the object is created, one needs to consider exception handling at all points in the workflow after the SSE is created and stored. Failing noisily, such as in a memory leak! might be the best way to get eyes on this. Perhaps:
|
@randeepbydesign to this point I don't have a good understanding of your scenario. I am trying to picture your code, but I have no idea what "validations on the request to open the SSE connection" really is, when and where an exception is thrown, whether the controller method returns or not, what does it return, and many more questions that are left open. The description has come in bits and pieces without any substantive detail. I have understood enough to know that the exception occurs in your code, and therefore your code should be aware of the exception, but I really cannot engage any more more given this level of detail. |
Affects: webmvc 6.x
We have a new application that uses the SseEmitter to send events back to clients as they arrive in our system via a Kafka Consumer. Recently this application started crashing every few days with Heap OOM errors. After some analysis, it looks like what is happening is that the connection gets terminated, but the
.onError
handler is never called. As a result we treat the connection as still available and keep publishing events to it.As part of the error, it looks like the underlying
handler
object is set to null. As a result, the message is queued in theearlySendAttempts
LinkedHashSet. It continues to accumulate messages there and accumulate until a Heap OOM occurs.Someone else reported the issue on Stackoverflow here.
This looks like it was introduced in the following commit from @bclozel.
Reproducibility is tough but I will continue to work for it
The text was updated successfully, but these errors were encountered: