net: hangup TCP connection after Dial timeout in Plan 9

After Dial timeout, force close the TCP connection by writing "hangup"
to the control file. This unblocks the "connect" command if the
connection is taking too long to establish, and frees up the control
file FD.

Fixes #40118

Change-Id: I1cef8539cd9fe0793e32b49c9d0ef636b4b26e1d
Reviewed-on: https://go-review.googlesource.com/c/go/+/241638
Run-TryBot: David du Colombier <0intro@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: David du Colombier <0intro@gmail.com>
diff --git a/src/net/ipsock_plan9.go b/src/net/ipsock_plan9.go
index 99d3e39..2308236 100644
--- a/src/net/ipsock_plan9.go
+++ b/src/net/ipsock_plan9.go
@@ -206,9 +206,9 @@
 		return nil, err
 	}
 	if la := plan9LocalAddr(laddr); la == "" {
-		_, err = f.WriteString("connect " + dest)
+		err = hangupCtlWrite(ctx, proto, f, "connect "+dest)
 	} else {
-		_, err = f.WriteString("connect " + dest + " " + la)
+		err = hangupCtlWrite(ctx, proto, f, "connect "+dest+" "+la)
 	}
 	if err != nil {
 		f.Close()
@@ -339,3 +339,27 @@
 	}
 	return ip.String() + "!" + itoa(port)
 }
+
+func hangupCtlWrite(ctx context.Context, proto string, ctl *os.File, msg string) error {
+	if proto != "tcp" {
+		_, err := ctl.WriteString(msg)
+		return err
+	}
+	written := make(chan struct{})
+	errc := make(chan error)
+	go func() {
+		select {
+		case <-ctx.Done():
+			ctl.WriteString("hangup")
+			errc <- mapErr(ctx.Err())
+		case <-written:
+			errc <- nil
+		}
+	}()
+	_, err := ctl.WriteString(msg)
+	close(written)
+	if e := <-errc; err == nil && e != nil { // we hung up
+		return e
+	}
+	return err
+}