internal/poll: add mutex to prevent SetDeadline race in Plan 9

There are data races on fd.[rw]aio and fd.[rw]timedout when Read/Write
is called on a polled fd concurrently with SetDeadline (see #38769).
Adding a mutex around accesses to each pair (read and write) prevents
the race, which was causing deadlocks in net/http tests on the builders.

Updates #38769.

Change-Id: I31719b3c9a664e81a775cda583cff31c0da946c4
Reviewed-on: https://go-review.googlesource.com/c/go/+/235820
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/internal/poll/fd_plan9.go b/src/internal/poll/fd_plan9.go
index e57e041..0b5b937 100644
--- a/src/internal/poll/fd_plan9.go
+++ b/src/internal/poll/fd_plan9.go
@@ -7,6 +7,7 @@
 import (
 	"errors"
 	"io"
+	"sync"
 	"sync/atomic"
 	"time"
 )
@@ -24,6 +25,8 @@
 	Destroy func()
 
 	// deadlines
+	rmu       sync.Mutex
+	wmu       sync.Mutex
 	raio      *asyncIO
 	waio      *asyncIO
 	rtimer    *time.Timer
@@ -59,9 +62,6 @@
 
 // Read implements io.Reader.
 func (fd *FD) Read(fn func([]byte) (int, error), b []byte) (int, error) {
-	if fd.rtimedout.isSet() {
-		return 0, ErrDeadlineExceeded
-	}
 	if err := fd.readLock(); err != nil {
 		return 0, err
 	}
@@ -69,7 +69,13 @@
 	if len(b) == 0 {
 		return 0, nil
 	}
+	fd.rmu.Lock()
+	if fd.rtimedout.isSet() {
+		fd.rmu.Unlock()
+		return 0, ErrDeadlineExceeded
+	}
 	fd.raio = newAsyncIO(fn, b)
+	fd.rmu.Unlock()
 	n, err := fd.raio.Wait()
 	fd.raio = nil
 	if isHangup(err) {
@@ -83,14 +89,17 @@
 
 // Write implements io.Writer.
 func (fd *FD) Write(fn func([]byte) (int, error), b []byte) (int, error) {
-	if fd.wtimedout.isSet() {
-		return 0, ErrDeadlineExceeded
-	}
 	if err := fd.writeLock(); err != nil {
 		return 0, err
 	}
 	defer fd.writeUnlock()
+	fd.wmu.Lock()
+	if fd.wtimedout.isSet() {
+		fd.wmu.Unlock()
+		return 0, ErrDeadlineExceeded
+	}
 	fd.waio = newAsyncIO(fn, b)
+	fd.wmu.Unlock()
 	n, err := fd.waio.Wait()
 	fd.waio = nil
 	if isInterrupted(err) {
@@ -117,9 +126,13 @@
 func setDeadlineImpl(fd *FD, t time.Time, mode int) error {
 	d := t.Sub(time.Now())
 	if mode == 'r' || mode == 'r'+'w' {
+		fd.rmu.Lock()
+		defer fd.rmu.Unlock()
 		fd.rtimedout.setFalse()
 	}
 	if mode == 'w' || mode == 'r'+'w' {
+		fd.wmu.Lock()
+		defer fd.wmu.Unlock()
 		fd.wtimedout.setFalse()
 	}
 	if t.IsZero() || d < 0 {
@@ -140,18 +153,22 @@
 		// Interrupt I/O operation once timer has expired
 		if mode == 'r' || mode == 'r'+'w' {
 			fd.rtimer = time.AfterFunc(d, func() {
+				fd.rmu.Lock()
 				fd.rtimedout.setTrue()
 				if fd.raio != nil {
 					fd.raio.Cancel()
 				}
+				fd.rmu.Unlock()
 			})
 		}
 		if mode == 'w' || mode == 'r'+'w' {
 			fd.wtimer = time.AfterFunc(d, func() {
+				fd.wmu.Lock()
 				fd.wtimedout.setTrue()
 				if fd.waio != nil {
 					fd.waio.Cancel()
 				}
+				fd.wmu.Unlock()
 			})
 		}
 	}