Skip to content

Commit b5b8683

Browse files
committed
Drain inbound traffic before closing to avoid TCP RST data loss
Half-close with CloseWrite() and drain with io.Copy before conn.Close() so late peer packets don't trigger RST. Also fix a pre-existing data race in Ping() where s.pingId was read outside pingLock. Signed-off-by: Andreas Erz <andreas.erz@sap.com>
1 parent d887ecc commit b5b8683

1 file changed

Lines changed: 14 additions & 1 deletion

File tree

connection.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ func NewConnectionWithOptions(conn net.Conn, server bool, opts ...spdy.FramerOpt
280280
// Ping sends a ping frame across the connection and
281281
// returns the response time
282282
func (s *Connection) Ping() (time.Duration, error) {
283-
pid := s.pingId
284283
s.pingLock.Lock()
284+
pid := s.pingId
285285
if s.pingId > 0x7ffffffe {
286286
s.pingId = s.pingId - 0x7ffffffe
287287
} else {
@@ -740,6 +740,19 @@ func (s *Connection) shutdown(closeTimeout time.Duration) {
740740
var err error
741741
select {
742742
case <-streamsClosed:
743+
// Drain inbound traffic before closing so the kernel sees FIN, not RST.
744+
// If a peer packet (e.g. a SPDY PING) arrives at our socket after Close(),
745+
// the kernel responds with RST and discards anything still queued in OUR
746+
// send buffer. i.e. bytes the application already wrote and counted as sent
747+
// never reach the wire. Half-closing first lets the peer drain and stop
748+
// sending; io.Copy consumes anything already in flight.
749+
if cw, ok := s.conn.(interface{ CloseWrite() error }); ok {
750+
if err := cw.CloseWrite(); err == nil {
751+
_ = s.conn.SetReadDeadline(time.Now().Add(10 * time.Second))
752+
n, _ := io.Copy(io.Discard, s.conn)
753+
debugMessage("(%p) drained %d bytes during shutdown", s, n)
754+
}
755+
}
743756
// No active streams, close should be safe
744757
err = s.conn.Close()
745758
case <-timeout:

0 commit comments

Comments
 (0)