http2: set up the timer of closing idle connection after the initialization

Fixes golang/go#66763

Change-Id: I046028b6072a6da77e7f1b4d1f2e6b14f8edb042
Reviewed-on: https://go-review.googlesource.com/c/net/+/578115
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
diff --git a/http2/transport.go b/http2/transport.go
index 2fa4949..714ef18 100644
--- a/http2/transport.go
+++ b/http2/transport.go
@@ -826,10 +826,6 @@
 		hooks.newclientconn(cc)
 		c = cc.tconn
 	}
-	if d := t.idleConnTimeout(); d != 0 {
-		cc.idleTimeout = d
-		cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
-	}
 	if VerboseLogs {
 		t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
 	}
@@ -893,6 +889,12 @@
 		return nil, cc.werr
 	}
 
+	// Start the idle timer after the connection is fully initialized.
+	if d := t.idleConnTimeout(); d != 0 {
+		cc.idleTimeout = d
+		cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
+	}
+
 	cc.goRun(cc.readLoop)
 	return cc, nil
 }
diff --git a/http2/transport_test.go b/http2/transport_test.go
index 3e4297f..2aa0c3e 100644
--- a/http2/transport_test.go
+++ b/http2/transport_test.go
@@ -5426,3 +5426,28 @@
 	}
 	tc.wantFrameType(FrameRSTStream)
 }
+
+func TestIssue66763Race(t *testing.T) {
+	tr := &Transport{
+		IdleConnTimeout: 1 * time.Nanosecond,
+		AllowHTTP:       true, // issue 66763 only occurs when AllowHTTP is true
+	}
+	defer tr.CloseIdleConnections()
+
+	cli, srv := net.Pipe()
+	donec := make(chan struct{})
+	go func() {
+		// Creating the client conn may succeed or fail,
+		// depending on when the idle timeout happens.
+		// Either way, the idle timeout will close the net.Conn.
+		tr.NewClientConn(cli)
+		close(donec)
+	}()
+
+	// The client sends its preface and SETTINGS frame,
+	// and then closes its conn after the idle timeout.
+	io.ReadAll(srv)
+	srv.Close()
+
+	<-donec
+}