-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Adding Close selector to prevent FD/FIFO leaks #3707
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
Conversation
following File Descriptor leak issue spring-projects#3705, we added slector.close() and this leak stopped. Closing/destroying ChannelOutputStream object does not close the selector therefore it retains redundant pipes/FD that cen be seen using lsof command or ls /proc/
...ion-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioConnection.java
Outdated
Show resolved
Hide resolved
set selector to null after closing it
@@ -662,6 +662,8 @@ protected synchronized void doWrite(ByteBuffer buffer) throws IOException { | |||
TcpNioConnection.this.socketChannel.write(buffer); | |||
remaining = buffer.remaining(); | |||
} | |||
selector.close(); | |||
selector = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And why do we need to close selector after each write?
You said yourself in the PR description:
Closing/destroying
ChannelOutputStream
object does not close the selector
I don't see that you close a ChannelOutputStream
in this write operation, so I believe it is done somehow externally. Therefore that ChannelOutputStream.close()
is called from there.
So, it still looks suspicious to always close a selector in the end of write, but not once when ChannelOutputStream
is closed.
What do I miss, please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I see your point it is better to move selector.close()
to ChannelOutputStream.close()
And I would like to ask you if you feel comfortable with:
ChannelOutputStream.close()
eventually calls doClose()
that eventually calls super.close()
that has the following code:
for (TcpSender sender : this.senders) { sender.removeDeadConnection(this);
This removeDeadConnection
does nothing it has empty implementation, I feel (maybe wrong) that it may cause Out Of Memory in one of the maps in my case in TcpSendingMessageHandler
in map connections
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that is not related to what we are talking here about.
I guess @garyrussell is looking into that via #3701 .
Let's concentrate here on the proper selector closing!
Make sure selector is closed to prevent FD leaks.
Close ChannelOutputStream and its selector to prevent leaks
Hi Artem, I added to |
} catch (IOException e) { | ||
// do nothing | ||
} | ||
} | ||
doClose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think such an implementation is OK.
This doClose()
is exactly that one in the outer class, so this close()
is going to be called again.
And so on in recursive manner.
I'm not sure what was an original intention with such a delegation, but we need to be sure that we don't do a recursion.
Could you, please, double check the solution?
Also the catch
block must be on a new line.
Be sure run \gradlew clean :spring-integration-ip:check
before pushing the change to the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure what's going on, but GitHub is not rendering the changes from all 5 commits; I can only see this change.
It looks like a bug that we never closed the output stream; we would have hit the stack overflow problem if we did; day one issue, I think.
I believe we can just remove that doClose()
call here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and since the output close()
is never called from the outside, it is safe to have it call it from connection close.
And yes: still keep a selector.close()
😄
Make sure selector is closed to prevent FD/pipes leak
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current code is probably OK so far in terms of context responsibility.
But I still wonder how and when this ChannelOutputStream.close()
is called?
Your previous version was very close to the truth. We only needed to figure out what to do with the recursion.
I've just ran the whole test suite for ip
module and no one calls this ChannelOutputStream.close()
. Therefore your current fix does not bring us a desired final solution for original OOM issue...
/CC @garyrussell
Full diff now:
Not sure why GH is messed up. |
} catch (IOException e) { | ||
// do nothing | ||
} | ||
} | ||
doClose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and since the output close()
is never called from the outside, it is safe to have it call it from connection close.
And yes: still keep a selector.close()
😄
Make sure OutputStream is closed to avoid FD leak
See last commit. I made sure no recursive call would take place. |
I will try tomorrow to add unit test for |
try { | ||
// in order to prevent endless recursive calls from channelOutputStream.close() to doClose() that would call channelOutputStream.close() etc... | ||
if (this.channelOutputStream != null) { | ||
((OutputStream) this.channelOutputStream).close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand how does your code help?
The this.channelOutputStream
is never null
according TcpNioConnection
and you cannot set it to null
(and you don't) because it is final
.
And it is pointless to cast it to OutputStream
since it is a super of the ChannelOutputStream
.
Our idea was really to call this close here, but remove that doClose()
from the ChannelOutputStream
.
At the moment I'm very confused since the solution does not reflect your comments and does not address our discussion.
Please, elaborate what am I still missing?
Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I took a look again in the code the source of the trouble is that doClose()
does not call ChannelOutputStream.close()
, and that the ChannelOutputStream.close()
itself is erronous:
that is:
@Override public void close() { doClose(); }
doClose
is not part of class ChannelOutputStream
and what it does has nothing to do with ChannelOutputStream
, it nor handle the selector
that is private member of ChannelOutputStream
neither closes the OutputStream itself.
So I think that the best approach would be to rewrite the ChannelOutputStream.close()
to:
@Override public void close() { if (this.selector != null) { try { this.selector.close(); } catch (IOException e) { // do nothing } } super.close(); }
This is more similar to ChannelInputStream.close()
In addition I added ChannelOutputStream.close()
to doClose()
.
In addition I took the liberty to write real impelmentation of ChannelOutputStream.flush()`.
Attached diff from the base source.
10x,
diff.txt
David
following File Descriptor leak issue spring-projects#3705, we added slector.close() and this leak stopped. Closing/destroying ChannelOutputStream object does not close the selector therefore it retains redundant pipes/FD that cen be seen using lsof command or ls /proc/
prevent FD leak by closing ChannelOutputStream and its selector. Rewriting doClose, ChannelOutputStream.close() and ChannelOutputStream.flush()
style correction
} | ||
} | ||
try { | ||
super.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason to call super
: it is just an empty method.
That's why we didn't delegate to it originally anyway.
} | ||
|
||
@Override | ||
public void flush() { | ||
try { | ||
super.flush(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No reason to call super
: it is just an empty method.
I think we can just remove this method altogether.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no problem will not override flush at all
try { | ||
this.selector.close(); | ||
} | ||
catch (@SuppressWarnings(UNUSED) Exception e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it is this method responsibility to suppress an exception.
Let it be done in the TcpNioConnection.close()
instead!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that we agreed in the previous discussion that this catch
is going to be removed...
@@ -663,7 +684,6 @@ protected synchronized void doWrite(ByteBuffer buffer) throws IOException { | |||
remaining = buffer.remaining(); | |||
} | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong: every member (even last method) of the class has to be surrounded with blank lines.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure where this empty line is missing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just bring it back and push!
It is not a problem though: we can clean it up on merge.
Just letting you to know what is our code style: https://github.com/spring-projects/spring-integration/wiki/Spring-Integration-Framework-Code-Style !
diff.txt
Outdated
@@ -0,0 +1,51 @@ | |||
diff --git a/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioConnection.java b/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/connection/TcpNioConnection.java |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file must not be here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how can i remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It must be somehow in your local project copy.
So, just remove it and push commit.
Removing flush, ChannelOutputStream.close() throws exception
try { | ||
this.selector.close(); | ||
} | ||
catch (@SuppressWarnings(UNUSED) Exception e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that we agreed in the previous discussion that this catch
is going to be removed...
Quoting Artem, "I thought that we agreed in the previous discussion that this catch is going to be removed... " |
Remove redundant catch clause and redundant file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is OK with me so far.
Would you mind to add your name to the @author
list of the affected class?
Also: any chances to cover this functionality with with some simple unit test?
I believe something like mocking with verification that ChannelOutputStream.close()
is called when we call TcpNioConnection.close()
should be enough.
Plus, confirm, please, that this change really fixes OOM problem for your project.
Would you mind to add your name to the @author list of the affected class?
Also: any chances to cover this functionality with with some simple unit test? Plus, confirm, please, that this change really fixes OOM problem for your project. |
Just add a new line Yes, that Probably something like |
… failed Make sure internal output stream would close even if selector.close() failed by putting the outputStream.close(0 in finally clause
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's it.
Taking this locally for final review and clean up.
Will try to add some testing as well...
10x |
Merged as and cherry-picked to Sorry I couldn't figured out a simple test for this @shvo123 , thank you for contribution; looking forward for more! |
Hi Artem,
What you should do is to write a big buffer of 260K, in this situation
`ChannelOutputStream.write()` would open selector and now without closing it
you would have FD/PIPEs leak.
After writing you can call:
```ChannelOutputStream cos = connection.getChannelOutputStream();```
```TcpNioConnection.close();```
Now you need to check whether selector is still open, you can add isSelectorOpen method to ChannelOutputStream class, and call it.
10x,
David
|
Hi @shvo123 !
Yes, I tried to do that, but it blocks when it tries to be selected. If you can come up with some unit-test on the matter, I would appreciate your contribution. Thanks |
Hi Artem, Can you tell me when formal release would be released so we can download it from maven repository instead of using our customized Spring integration. |
5.5.8 (and the other supported branches) were released this week https://github.com/spring-projects/spring-integration/releases |
10x
…On Thu, Jan 20, 2022 at 4:29 PM Gary Russell ***@***.***> wrote:
5.5.8 (and the other supported branches) were released this week
https://github.com/spring-projects/spring-integration/releases
—
Reply to this email directly, view it on GitHub
<#3707 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACOO3JESMJDC6ZJMDHGT6V3UXAL3BANCNFSM5LEFDYRA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you were mentioned.Message ID:
***@***.***>
--
Rgds,
David Herschler Shvo
|
following File Descriptor leak issue #3705, we added slector.close() and this leak stopped. Closing/destroying ChannelOutputStream object does not close the selector therefore it retains redundant pipes/FD that cen be seen using lsof command or ls /proc/