Skip to content

Commit 869e576

Browse files
Paul Marksbradfitz
Paul Marks
authored andcommitted
net: wait for cancelation goroutine before returning from connect.
This fixes a race which made it possible to cancel a connection after returning from net.Dial. Fixes #15035 Fixes #15078 Change-Id: Iec6215009538362f7ad9f408a33549f3e94d1606 Reviewed-on: https://go-review.googlesource.com/21497 Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent bbbd572 commit 869e576

File tree

3 files changed

+93
-3
lines changed

3 files changed

+93
-3
lines changed

src/net/dial_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package net
66

77
import (
8+
"bufio"
89
"internal/testenv"
910
"io"
1011
"net/internal/socktest"
@@ -871,3 +872,84 @@ func TestDialCancel(t *testing.T) {
871872
}
872873
}
873874
}
875+
876+
func TestCancelAfterDial(t *testing.T) {
877+
if testing.Short() {
878+
t.Skip("avoiding time.Sleep")
879+
}
880+
881+
ln, err := newLocalListener("tcp")
882+
if err != nil {
883+
t.Fatal(err)
884+
}
885+
886+
var wg sync.WaitGroup
887+
wg.Add(1)
888+
defer func() {
889+
ln.Close()
890+
wg.Wait()
891+
}()
892+
893+
// Echo back the first line of each incoming connection.
894+
go func() {
895+
for {
896+
c, err := ln.Accept()
897+
if err != nil {
898+
break
899+
}
900+
rb := bufio.NewReader(c)
901+
line, err := rb.ReadString('\n')
902+
if err != nil {
903+
t.Error(err)
904+
c.Close()
905+
continue
906+
}
907+
if _, err := c.Write([]byte(line)); err != nil {
908+
t.Error(err)
909+
}
910+
c.Close()
911+
}
912+
wg.Done()
913+
}()
914+
915+
try := func() {
916+
cancel := make(chan struct{})
917+
d := &Dialer{Cancel: cancel}
918+
c, err := d.Dial("tcp", ln.Addr().String())
919+
920+
// Immediately after dialing, request cancelation and sleep.
921+
// Before Issue 15078 was fixed, this would cause subsequent operations
922+
// to fail with an i/o timeout roughly 50% of the time.
923+
close(cancel)
924+
time.Sleep(10 * time.Millisecond)
925+
926+
if err != nil {
927+
t.Fatal(err)
928+
}
929+
defer c.Close()
930+
931+
// Send some data to confirm that the connection is still alive.
932+
const message = "echo!\n"
933+
if _, err := c.Write([]byte(message)); err != nil {
934+
t.Fatal(err)
935+
}
936+
937+
// The server should echo the line, and close the connection.
938+
rb := bufio.NewReader(c)
939+
line, err := rb.ReadString('\n')
940+
if err != nil {
941+
t.Fatal(err)
942+
}
943+
if line != message {
944+
t.Errorf("got %q; want %q", line, message)
945+
}
946+
if _, err := rb.ReadByte(); err != io.EOF {
947+
t.Errorf("got %v; want %v", err, io.EOF)
948+
}
949+
}
950+
951+
// This bug manifested about 50% of the time, so try it a few times.
952+
for i := 0; i < 10; i++ {
953+
try()
954+
}
955+
}

src/net/fd_unix.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,17 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time, cancel <-c
104104
}
105105
if cancel != nil {
106106
done := make(chan bool)
107-
defer close(done)
107+
defer func() {
108+
// This is unbuffered; wait for the goroutine before returning.
109+
done <- true
110+
}()
108111
go func() {
109112
select {
110113
case <-cancel:
111114
// Force the runtime's poller to immediately give
112115
// up waiting for writability.
113116
fd.setWriteDeadline(aLongTimeAgo)
117+
<-done
114118
case <-done:
115119
}
116120
}()

src/net/fd_windows.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,14 +352,18 @@ func (fd *netFD) connect(la, ra syscall.Sockaddr, deadline time.Time, cancel <-c
352352
o := &fd.wop
353353
o.sa = ra
354354
if cancel != nil {
355-
done := make(chan struct{})
356-
defer close(done)
355+
done := make(chan bool)
356+
defer func() {
357+
// This is unbuffered; wait for the goroutine before returning.
358+
done <- true
359+
}()
357360
go func() {
358361
select {
359362
case <-cancel:
360363
// Force the runtime's poller to immediately give
361364
// up waiting for writability.
362365
fd.setWriteDeadline(aLongTimeAgo)
366+
<-done
363367
case <-done:
364368
}
365369
}()

0 commit comments

Comments
 (0)