Skip to content

Commit 4ffba76

Browse files
committed
crypto/tls: don't block in Conn.Close if Writes are in-flight
Conn.Close sends an encrypted "close notify" to signal secure EOF. But writing that involves acquiring mutexes (handshake mutex + the c.out mutex) and writing to the network. But if the reason we're calling Conn.Close is because the network is already being problematic, then Close might block, waiting for one of those mutexes. Instead of blocking, and instead of introducing new API (at least for now), distinguish between a normal Close (one that sends a secure EOF) and a resource-releasing destructor-style Close based on whether there are existing Write calls in-flight. Because io.Writer and io.Closer aren't defined with respect to concurrent usage, a Close with active Writes is already undefined, and should only be used during teardown after failures (e.g. deadlines or cancelations by HTTP users). A normal user will do a Write then serially do a Close, and things are unchanged for that case. This should fix the leaked goroutines and hung net/http.Transport requests when there are network errors while making TLS requests. Change-Id: If3f8c69d6fdcebf8c70227f41ad042ccc3f20ac9 Reviewed-on: https://go-review.googlesource.com/18572 Reviewed-by: Adam Langley <[email protected]> Reviewed-by: Russ Cox <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent e779bfa commit 4ffba76

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

src/crypto/tls/conn.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"io"
1717
"net"
1818
"sync"
19+
"sync/atomic"
1920
"time"
2021
)
2122

@@ -56,6 +57,11 @@ type Conn struct {
5657
input *block // application data waiting to be read
5758
hand bytes.Buffer // handshake data waiting to be read
5859

60+
// activeCall is an atomic int32; the low bit is whether Close has
61+
// been called. the rest of the bits are the number of goroutines
62+
// in Conn.Write.
63+
activeCall int32
64+
5965
tmp [16]byte
6066
}
6167

@@ -855,8 +861,22 @@ func (c *Conn) readHandshake() (interface{}, error) {
855861
return m, nil
856862
}
857863

864+
var errClosed = errors.New("crypto/tls: use of closed connection")
865+
858866
// Write writes data to the connection.
859867
func (c *Conn) Write(b []byte) (int, error) {
868+
// interlock with Close below
869+
for {
870+
x := atomic.LoadInt32(&c.activeCall)
871+
if x&1 != 0 {
872+
return 0, errClosed
873+
}
874+
if atomic.CompareAndSwapInt32(&c.activeCall, x, x+2) {
875+
defer atomic.AddInt32(&c.activeCall, -2)
876+
break
877+
}
878+
}
879+
860880
if err := c.Handshake(); err != nil {
861881
return 0, err
862882
}
@@ -960,6 +980,27 @@ func (c *Conn) Read(b []byte) (n int, err error) {
960980

961981
// Close closes the connection.
962982
func (c *Conn) Close() error {
983+
// Interlock with Conn.Write above.
984+
var x int32
985+
for {
986+
x = atomic.LoadInt32(&c.activeCall)
987+
if x&1 != 0 {
988+
return errClosed
989+
}
990+
if atomic.CompareAndSwapInt32(&c.activeCall, x, x|1) {
991+
break
992+
}
993+
}
994+
if x != 0 {
995+
// io.Writer and io.Closer should not be used concurrently.
996+
// If Close is called while a Write is currently in-flight,
997+
// interpret that as a sign that this Close is really just
998+
// being used to break the Write and/or clean up resources and
999+
// avoid sending the alertCloseNotify, which may block
1000+
// waiting on handshakeMutex or the c.out mutex.
1001+
return c.conn.Close()
1002+
}
1003+
9631004
var alertErr error
9641005

9651006
c.handshakeMutex.Lock()

src/crypto/tls/tls_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package tls
66

77
import (
88
"bytes"
9+
"errors"
910
"fmt"
1011
"internal/testenv"
1112
"io"
@@ -364,3 +365,104 @@ func TestVerifyHostnameResumed(t *testing.T) {
364365
c.Close()
365366
}
366367
}
368+
369+
func TestConnCloseBreakingWrite(t *testing.T) {
370+
ln := newLocalListener(t)
371+
defer ln.Close()
372+
373+
srvCh := make(chan *Conn, 1)
374+
var serr error
375+
var sconn net.Conn
376+
go func() {
377+
var err error
378+
sconn, err = ln.Accept()
379+
if err != nil {
380+
serr = err
381+
srvCh <- nil
382+
return
383+
}
384+
serverConfig := *testConfig
385+
srv := Server(sconn, &serverConfig)
386+
if err := srv.Handshake(); err != nil {
387+
serr = fmt.Errorf("handshake: %v", err)
388+
srvCh <- nil
389+
return
390+
}
391+
srvCh <- srv
392+
}()
393+
394+
cconn, err := net.Dial("tcp", ln.Addr().String())
395+
if err != nil {
396+
t.Fatal(err)
397+
}
398+
defer cconn.Close()
399+
400+
conn := &changeImplConn{
401+
Conn: cconn,
402+
}
403+
404+
clientConfig := *testConfig
405+
tconn := Client(conn, &clientConfig)
406+
if err := tconn.Handshake(); err != nil {
407+
t.Fatal(err)
408+
}
409+
410+
srv := <-srvCh
411+
if srv == nil {
412+
t.Fatal(serr)
413+
}
414+
defer sconn.Close()
415+
416+
connClosed := make(chan struct{})
417+
conn.closeFunc = func() error {
418+
close(connClosed)
419+
return nil
420+
}
421+
422+
inWrite := make(chan bool, 1)
423+
var errConnClosed = errors.New("conn closed for test")
424+
conn.writeFunc = func(p []byte) (n int, err error) {
425+
inWrite <- true
426+
<-connClosed
427+
return 0, errConnClosed
428+
}
429+
430+
closeReturned := make(chan bool, 1)
431+
go func() {
432+
<-inWrite
433+
tconn.Close() // test that this doesn't block forever.
434+
closeReturned <- true
435+
}()
436+
437+
_, err = tconn.Write([]byte("foo"))
438+
if err != errConnClosed {
439+
t.Errorf("Write error = %v; want errConnClosed", err)
440+
}
441+
442+
<-closeReturned
443+
if err := tconn.Close(); err != errClosed {
444+
t.Errorf("Close error = %v; want errClosed", err)
445+
}
446+
}
447+
448+
// changeImplConn is a net.Conn which can change its Write and Close
449+
// methods.
450+
type changeImplConn struct {
451+
net.Conn
452+
writeFunc func([]byte) (int, error)
453+
closeFunc func() error
454+
}
455+
456+
func (w *changeImplConn) Write(p []byte) (n int, err error) {
457+
if w.writeFunc != nil {
458+
return w.writeFunc(p)
459+
}
460+
return w.Conn.Write(p)
461+
}
462+
463+
func (w *changeImplConn) Close() error {
464+
if w.closeFunc != nil {
465+
return w.closeFunc()
466+
}
467+
return w.Conn.Close()
468+
}

0 commit comments

Comments
 (0)