]> Cypherpunks repositories - gostls13.git/commitdiff
internal/poll: avoid race between SetDeadline and timer expiry in Plan 9
authormiller <millerresearch@gmail.com>
Wed, 22 Feb 2023 14:15:08 +0000 (14:15 +0000)
committerBryan Mills <bcmills@google.com>
Mon, 27 Feb 2023 18:39:30 +0000 (18:39 +0000)
The mutexes added by CL 235820 aren't sufficient to prevent a race when
an i/o deadline timer fires just as the deadline is being reset to zero.

Consider this possible sequence when goroutine S is clearing the
deadline and goroutine T has been started by the timer:

1. S locks the mutex
2. T blocks on the mutex
3. S sets the timedout flag to false
4. S calls Stop on the timer (and fails, because the timer has fired)
5. S unlocks the mutex
6. T locks the mutex
7. T sets the timedout flag to true

Now all subsequent I/O will timeout, although the deadline has been
cleared.

The fix is for the timeout goroutine to skip setting the timedout
flag if the timer pointer has been cleared, or reassigned by
another SetDeadline operation.

Fixes #57114

Change-Id: I4a45d19c3b4b66cdf151dcc3f70536deaa8216a7
Reviewed-on: https://go-review.googlesource.com/c/go/+/470215
Run-TryBot: Bryan Mills <bcmills@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: David du Colombier <0intro@gmail.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
src/internal/poll/fd_plan9.go

index 0fdf4f6d80f6a878f1adb02ebf961d3e38540047..55a07956d836d5e50228713c90cf639ed277e220 100644 (file)
@@ -122,48 +122,54 @@ func setDeadlineImpl(fd *FD, t time.Time, mode int) error {
        if mode == 'r' || mode == 'r'+'w' {
                fd.rmu.Lock()
                defer fd.rmu.Unlock()
+               if fd.rtimer != nil {
+                       fd.rtimer.Stop()
+                       fd.rtimer = nil
+               }
                fd.rtimedout.Store(false)
        }
        if mode == 'w' || mode == 'r'+'w' {
                fd.wmu.Lock()
                defer fd.wmu.Unlock()
-               fd.wtimedout.Store(false)
-       }
-       if t.IsZero() || d < 0 {
-               // Stop timer
-               if mode == 'r' || mode == 'r'+'w' {
-                       if fd.rtimer != nil {
-                               fd.rtimer.Stop()
-                       }
-                       fd.rtimer = nil
-               }
-               if mode == 'w' || mode == 'r'+'w' {
-                       if fd.wtimer != nil {
-                               fd.wtimer.Stop()
-                       }
+               if fd.wtimer != nil {
+                       fd.wtimer.Stop()
                        fd.wtimer = nil
                }
-       } else {
+               fd.wtimedout.Store(false)
+       }
+       if !t.IsZero() && d > 0 {
                // Interrupt I/O operation once timer has expired
                if mode == 'r' || mode == 'r'+'w' {
-                       fd.rtimer = time.AfterFunc(d, func() {
+                       var timer *time.Timer
+                       timer = time.AfterFunc(d, func() {
                                fd.rmu.Lock()
+                               defer fd.rmu.Unlock()
+                               if fd.rtimer != timer {
+                                       // deadline was changed
+                                       return
+                               }
                                fd.rtimedout.Store(true)
                                if fd.raio != nil {
                                        fd.raio.Cancel()
                                }
-                               fd.rmu.Unlock()
                        })
+                       fd.rtimer = timer
                }
                if mode == 'w' || mode == 'r'+'w' {
-                       fd.wtimer = time.AfterFunc(d, func() {
+                       var timer *time.Timer
+                       timer = time.AfterFunc(d, func() {
                                fd.wmu.Lock()
+                               defer fd.wmu.Unlock()
+                               if fd.wtimer != timer {
+                                       // deadline was changed
+                                       return
+                               }
                                fd.wtimedout.Store(true)
                                if fd.waio != nil {
                                        fd.waio.Cancel()
                                }
-                               fd.wmu.Unlock()
                        })
+                       fd.wtimer = timer
                }
        }
        if !t.IsZero() && d < 0 {