tcp: transition to FIN_WAIT1 when FIN is queued#13401
Conversation
9d98248 to
75d607d
Compare
Fix #13393 gVisor performs the close-state transition (ESTABLISHED -> FIN_WAIT1, CLOSE_WAIT -> LAST_ACK) in the sender, at the moment the FIN segment is actually transmitted. Because the FIN is queued as a separate segment behind any pending data, a full send window keeps it from ever being sent: the transition never happens and the endpoint stays in ESTABLISHED forever, so a peer that actively closes the connection (e.g. a slow consumer hit by a write deadline) is never observed as closed. Linux instead transitions at enqueue time: tcp_close_state() moves the socket to FIN_WAIT1 before tcp_send_fin(), and the FIN may remain queued until the window opens. Move the transition out of the sender and into shutdownLocked(), performed the moment the FIN is enqueued. The queued FIN still flushes normally once the window opens (no data loss), and the connection advances through the closing state machine even when the FIN cannot be sent yet. FUTURE_COPYBARA_INTEGRATE_REVIEW=#13401 from tanyifeng:fin_state_trans 75d607d PiperOrigin-RevId: 929909275
Fix #13393 gVisor performs the close-state transition (ESTABLISHED -> FIN_WAIT1, CLOSE_WAIT -> LAST_ACK) in the sender, at the moment the FIN segment is actually transmitted. Because the FIN is queued as a separate segment behind any pending data, a full send window keeps it from ever being sent: the transition never happens and the endpoint stays in ESTABLISHED forever, so a peer that actively closes the connection (e.g. a slow consumer hit by a write deadline) is never observed as closed. Linux instead transitions at enqueue time: tcp_close_state() moves the socket to FIN_WAIT1 before tcp_send_fin(), and the FIN may remain queued until the window opens. Move the transition out of the sender and into shutdownLocked(), performed the moment the FIN is enqueued. The queued FIN still flushes normally once the window opens (no data loss), and the connection advances through the closing state machine even when the FIN cannot be sent yet. FUTURE_COPYBARA_INTEGRATE_REVIEW=#13401 from tanyifeng:fin_state_trans 75d607d PiperOrigin-RevId: 929909275
Fix google#13393 gVisor performs the close-state transition (ESTABLISHED -> FIN_WAIT1, CLOSE_WAIT -> LAST_ACK) in the sender, at the moment the FIN segment is actually transmitted. Because the FIN is queued as a separate segment behind any pending data, a full send window keeps it from ever being sent: the transition never happens and the endpoint stays in ESTABLISHED forever, so a peer that actively closes the connection (e.g. a slow consumer hit by a write deadline) is never observed as closed. Linux instead transitions at enqueue time: tcp_close_state() moves the socket to FIN_WAIT1 before tcp_send_fin(), and the FIN may remain queued until the window opens. Move the transition out of the sender and into shutdownLocked(), performed the moment the FIN is enqueued. The queued FIN still flushes normally once the window opens (no data loss), and the connection advances through the closing state machine even when the FIN cannot be sent yet. Signed-off-by: Tan Yifeng <yiftan@tencent.com>
75d607d to
bcb259a
Compare
|
I noticed two CI failures on Turns out the state-transition move broke an assumption in the receiver. That triggers two things:
The fix is small: track whether the FIN was actually transmitted with a @nybidari Could you please take another look and confirm the approach looks reasonable? Thanks! |
Fix #13393 gVisor performs the close-state transition (ESTABLISHED -> FIN_WAIT1, CLOSE_WAIT -> LAST_ACK) in the sender, at the moment the FIN segment is actually transmitted. Because the FIN is queued as a separate segment behind any pending data, a full send window keeps it from ever being sent: the transition never happens and the endpoint stays in ESTABLISHED forever, so a peer that actively closes the connection (e.g. a slow consumer hit by a write deadline) is never observed as closed. Linux instead transitions at enqueue time: tcp_close_state() moves the socket to FIN_WAIT1 before tcp_send_fin(), and the FIN may remain queued until the window opens. Move the transition out of the sender and into shutdownLocked(), performed the moment the FIN is enqueued. The queued FIN still flushes normally once the window opens (no data loss), and the connection advances through the closing state machine even when the FIN cannot be sent yet. FUTURE_COPYBARA_INTEGRATE_REVIEW=#13401 from tanyifeng:fin_state_trans bcb259a PiperOrigin-RevId: 929909275
|
Thanks for the due diligence @tanyifeng! I have kicked a new run with your latest changes in https://buildkite.com/gvisor/pipeline/builds/43252. |
Fix #13393 gVisor performs the close-state transition (ESTABLISHED -> FIN_WAIT1, CLOSE_WAIT -> LAST_ACK) in the sender, at the moment the FIN segment is actually transmitted. Because the FIN is queued as a separate segment behind any pending data, a full send window keeps it from ever being sent: the transition never happens and the endpoint stays in ESTABLISHED forever, so a peer that actively closes the connection (e.g. a slow consumer hit by a write deadline) is never observed as closed. Linux instead transitions at enqueue time: tcp_close_state() moves the socket to FIN_WAIT1 before tcp_send_fin(), and the FIN may remain queued until the window opens. Move the transition out of the sender and into shutdownLocked(), performed the moment the FIN is enqueued. The queued FIN still flushes normally once the window opens (no data loss), and the connection advances through the closing state machine even when the FIN cannot be sent yet. FUTURE_COPYBARA_INTEGRATE_REVIEW=#13401 from tanyifeng:fin_state_trans bcb259a PiperOrigin-RevId: 929909275
Fix #13393 gVisor performs the close-state transition (ESTABLISHED -> FIN_WAIT1, CLOSE_WAIT -> LAST_ACK) in the sender, at the moment the FIN segment is actually transmitted. Because the FIN is queued as a separate segment behind any pending data, a full send window keeps it from ever being sent: the transition never happens and the endpoint stays in ESTABLISHED forever, so a peer that actively closes the connection (e.g. a slow consumer hit by a write deadline) is never observed as closed. Linux instead transitions at enqueue time: tcp_close_state() moves the socket to FIN_WAIT1 before tcp_send_fin(), and the FIN may remain queued until the window opens. Move the transition out of the sender and into shutdownLocked(), performed the moment the FIN is enqueued. The queued FIN still flushes normally once the window opens (no data loss), and the connection advances through the closing state machine even when the FIN cannot be sent yet. FUTURE_COPYBARA_INTEGRATE_REVIEW=#13401 from tanyifeng:fin_state_trans bcb259a PiperOrigin-RevId: 929909275
Fix #13393 gVisor performs the close-state transition (ESTABLISHED -> FIN_WAIT1, CLOSE_WAIT -> LAST_ACK) in the sender, at the moment the FIN segment is actually transmitted. Because the FIN is queued as a separate segment behind any pending data, a full send window keeps it from ever being sent: the transition never happens and the endpoint stays in ESTABLISHED forever, so a peer that actively closes the connection (e.g. a slow consumer hit by a write deadline) is never observed as closed. Linux instead transitions at enqueue time: tcp_close_state() moves the socket to FIN_WAIT1 before tcp_send_fin(), and the FIN may remain queued until the window opens. Move the transition out of the sender and into shutdownLocked(), performed the moment the FIN is enqueued. The queued FIN still flushes normally once the window opens (no data loss), and the connection advances through the closing state machine even when the FIN cannot be sent yet. FUTURE_COPYBARA_INTEGRATE_REVIEW=#13401 from tanyifeng:fin_state_trans bcb259a PiperOrigin-RevId: 929909275
Fix #13393
gVisor performs the close-state transition (ESTABLISHED -> FIN_WAIT1, CLOSE_WAIT -> LAST_ACK) in the sender, at the moment the FIN segment is actually transmitted. Because the FIN is queued as a separate segment behind any pending data, a full send window keeps it from ever being sent: the transition never happens and the endpoint stays in ESTABLISHED forever, so a peer that actively closes the connection (e.g. a slow consumer hit by a write deadline) is never observed as closed.
Linux instead transitions at enqueue time: tcp_close_state() moves the socket to FIN_WAIT1 before tcp_send_fin(), and the FIN may remain queued until the window opens.
Move the transition out of the sender and into shutdownLocked(), performed the moment the FIN is enqueued. The queued FIN still flushes normally once the window opens (no data loss), and the connection advances through the closing state machine even when the FIN cannot be sent yet.