From cba06f2a50f09b9f35d8dd748be48b5e8d53fe03 Mon Sep 17 00:00:00 2001
From: Ian Lance Taylor <iant@golang.org>
Date: Thu, 28 Mar 2024 17:25:23 +0800
Subject: [PATCH 1/2] [1.15 backport]runtime: decrement netpollWaiters in
 netpollunblock

We used to decrement it in netpollgoready, but that missed
the common case of a descriptor becoming ready due to I/O.
All calls to netpollgoready go through netpollunblock,
so this shouldn't miss any decrements we missed before.

Fixes #60782

Change-Id: Ideefefa1ac96ca38e09fe2dd5d595c5dd7883237
Reviewed-on: https://go-review.googlesource.com/c/go/+/503923
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
---
 src/runtime/crash_test.go                   |  9 +++
 src/runtime/netpoll.go                      |  4 +-
 src/runtime/testdata/testprognet/waiters.go | 69 +++++++++++++++++++++
 3 files changed, 81 insertions(+), 1 deletion(-)
 create mode 100644 src/runtime/testdata/testprognet/waiters.go

diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go
index 34f30c9..263bd54 100644
--- a/src/runtime/crash_test.go
+++ b/src/runtime/crash_test.go
@@ -781,3 +781,12 @@ func TestDoublePanic(t *testing.T) {
 		}
 	}
 }
+
+func TestNetpollWaiters(t *testing.T) {
+	t.Parallel()
+	output := runTestProg(t, "testprognet", "NetpollWaiters")
+	want := "OK\n"
+	if output != want {
+		t.Fatalf("output is not %q\n%s", want, output)
+	}
+}
diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go
index f296b0a..3611995 100644
--- a/src/runtime/netpoll.go
+++ b/src/runtime/netpoll.go
@@ -470,13 +470,15 @@ func netpollunblock(pd *pollDesc, mode int32, ioready bool) *g {
 			// will check for timeout/cancel before waiting.
 			return nil
 		}
-		var new uintptr
+		new := pdNil
 		if ioready {
 			new = pdReady
 		}
 		if atomic.Casuintptr(gpp, old, new) {
 			if old == pdWait {
 				old = 0
+			} else if old != 0 {
+				netpollWaiters.Add(-1)
 			}
 			return (*g)(unsafe.Pointer(old))
 		}
diff --git a/src/runtime/testdata/testprognet/waiters.go b/src/runtime/testdata/testprognet/waiters.go
new file mode 100644
index 0000000..480e872
--- /dev/null
+++ b/src/runtime/testdata/testprognet/waiters.go
@@ -0,0 +1,69 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"fmt"
+	"io"
+	"log"
+	"net"
+	"runtime/internal/atomic"
+	"sync"
+	"time"
+	_ "unsafe" // for go:linkname
+)
+
+// The bug is that netpollWaiters increases monotonically.
+// This doesn't cause a problem until it overflows.
+// Use linkname to see the value.
+//go:linkname netpollWaiters runtime.netpollWaiters
+var netpollWaiters uint32
+
+func init() {
+	register("NetpollWaiters", NetpollWaiters)
+}
+
+func NetpollWaiters() {
+	listener, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		conn, err := listener.Accept()
+		if err != nil {
+			log.Fatal(err)
+		}
+		defer conn.Close()
+		if _, err := io.Copy(io.Discard, conn); err != nil {
+			log.Fatal(err)
+		}
+	}()
+
+	wg.Add(1)
+	go func() {
+		defer wg.Done()
+		conn, err := net.Dial("tcp", listener.Addr().String())
+		if err != nil {
+			log.Fatal(err)
+		}
+		defer conn.Close()
+		for i := 0; i < 10; i++ {
+			fmt.Fprintf(conn, "%d\n", i)
+			time.Sleep(time.Millisecond)
+		}
+	}()
+
+	wg.Wait()
+	if v := atomic.Load(&netpollWaiters); v != 0 {
+		log.Fatalf("current waiters %v", v)
+	}
+
+	fmt.Println("OK")
+}
+
-- 
2.27.0

