From 8538477d58b97ad7f5c91c9c5b7007404f2a6dac Mon Sep 17 00:00:00 2001 From: miller Date: Wed, 22 Feb 2023 14:15:08 +0000 Subject: [PATCH] internal/poll: avoid race between SetDeadline and timer expiry in Plan 9 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 TryBot-Result: Gopher Robot Reviewed-by: David du Colombier <0intro@gmail.com> Reviewed-by: Bryan Mills Reviewed-by: Ian Lance Taylor --- src/internal/poll/fd_plan9.go | 44 ++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/internal/poll/fd_plan9.go b/src/internal/poll/fd_plan9.go index 0fdf4f6d80..55a07956d8 100644 --- a/src/internal/poll/fd_plan9.go +++ b/src/internal/poll/fd_plan9.go @@ -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 { -- 2.50.0