reliable gracefulClose - #617
Conversation
There was a problem hiding this comment.
Thanks for working on this. I have one main concern with the forkIO change
(my thoughts, but the below prose framing the concern is partly AI-assisted)
gracefulClose becomes fire-and-forget, with no limit on pending closes.
Before this PR, gracefulClose blocked the calling thread. That gave a
natural limit: a server with N handler threads could have at most N closes
in progress.
With forkIO, that limit is gone. Each call starts a thread that may hold
the socket FD for the full timeout. Nothing bounds how many of these exist
at once.
Example: a server closing 1000 connections/sec with a 5-second timeout can
hold ~5000 extra FDs, invisible to the application. A malicious peer can
force the worst case cheaply: connect, trigger a close, and never send FIN.
Each such peer pins one FD for the whole timeout. Eventually accept()
fails with EMFILE for everyone.
There are also smaller costs of the detached thread: the caller can no
longer see exceptions (including the annotated ones), cannot cancel a stuck
close, and cannot know when the FD is really released.
Suggestions, in order of preference:
- Keep
gracefulClosesynchronous. A caller who wants async behavior can
writeforkIO (gracefulClose s t)themselves, and then owns the thread. - If async behavior is the goal, return the
ThreadId(or anAsync) so
the caller can wait or cancel. - If the fork stays internal, bound the number of pending closes.
When the limit is reached, abort the oldest pending close with a
plaincloseand admit the new one. The oldest close has already had
the most time to finish, so canceling it loses the least. This also
means stuck peers (that never send FIN) get recycled first instead of
occupying slots forever. Graceful close is a courtesy; under pressure,
shedding to abortive close is the right behavior, and peers must
handle RST anyway.
The test change also hints at the problem: the added threadDelay after
gracefulClose is needed because the caller no longer knows when the close
finishes.
[ P.S. I have an additional material concern about the gracefulClose implementation, but it predates this PR, and applies equally to the prior state. I think it is likely best to focus on one such topic at a time. ]
|
I agree. Despite both calls eventually closing a socket,
When you |
|
Thank you for your comment. I agree with the observation that users can simply use |
|
Thanks for removing the One bug in the simplification: a lost millisecond/microsecond conversion. The docstring says the second argument of I think this is also why the test needed changing: with the old code on Suggested fix: and the test value can go back to 3000. A test that would catch this |
edd28ca to
3e4c658
Compare
|
It's an embarrassing bug. The commit list had become unnecessarily complex, so I squashed the commits. |
vdukhovni
left a comment
There was a problem hiding this comment.
LGTM. The millisecond conversion is fixed.
As mentioned earlier, I have one more concern about gracefulClose, but it predates this PR and is unchanged by it, so it should not block landing this first.
I will open a separate issue for discussion, with a possible follow-on PR. Short version: the current code returns after the first readable event, not the peer's FIN. If our write side still has undelivered data, the early close aborts the connection and that data may be lost. The fix is to keep reading until EOF (with limits). Details in the upcoming separate issue, which merits a longer discussion.
|
Merged. |
Background: https://kazu-yamamoto.hatenablog.jp/entry/2019/09/20/165939
Since
closeis non-blocking,gracefulCloseshould also return immediately. To achieve this, the QUIC library offloads the termination logic to a separate thread usingforkIO. Experience has shown this approach to be effective. Since there is no harm in the offloaded thread waiting for a long time, a simple timeout suffices—avoiding the complexity of having theIOManagerandTimerManagercompete. While this increases the number of threads, it ensures immediate responsiveness and reliability.@khibino Would you review this?