From 1ceee045294a6059093851645968f5a7e00a58f3 Mon Sep 17 00:00:00 2001 From: Ghanan Gowripalan Date: Fri, 24 Apr 2020 12:45:33 -0700 Subject: Do not copy tcpip.CancellableTimer A CancellableTimer's AfterFunc timer instance creates a closure over the CancellableTimer's address. This closure makes a CancellableTimer unsafe to copy. No behaviour change, existing tests pass. PiperOrigin-RevId: 308306664 --- pkg/tcpip/stack/ndp.go | 31 +++++++++++++++++++++---------- pkg/tcpip/timer.go | 25 ++++++++++++++++++++++--- pkg/tcpip/timer_test.go | 16 ++++++++-------- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/pkg/tcpip/stack/ndp.go b/pkg/tcpip/stack/ndp.go index 193a9dfde..c11d62f97 100644 --- a/pkg/tcpip/stack/ndp.go +++ b/pkg/tcpip/stack/ndp.go @@ -412,20 +412,33 @@ type dadState struct { // defaultRouterState holds data associated with a default router discovered by // a Router Advertisement (RA). type defaultRouterState struct { - invalidationTimer tcpip.CancellableTimer + // Timer to invalidate the default router. + // + // May not be nil. + invalidationTimer *tcpip.CancellableTimer } // onLinkPrefixState holds data associated with an on-link prefix discovered by // a Router Advertisement's Prefix Information option (PI) when the NDP // configurations was configured to do so. type onLinkPrefixState struct { - invalidationTimer tcpip.CancellableTimer + // Timer to invalidate the on-link prefix. + // + // May not be nil. + invalidationTimer *tcpip.CancellableTimer } // slaacPrefixState holds state associated with a SLAAC prefix. type slaacPrefixState struct { - deprecationTimer tcpip.CancellableTimer - invalidationTimer tcpip.CancellableTimer + // Timer to deprecate the prefix. + // + // May not be nil. + deprecationTimer *tcpip.CancellableTimer + + // Timer to invalidate the prefix. + // + // May not be nil. + invalidationTimer *tcpip.CancellableTimer // Nonzero only when the address is not valid forever. validUntil time.Time @@ -775,7 +788,6 @@ func (ndp *ndpState) invalidateDefaultRouter(ip tcpip.Address) { } rtr.invalidationTimer.StopLocked() - delete(ndp.defaultRouters, ip) // Let the integrator know a discovered default router is invalidated. @@ -804,7 +816,7 @@ func (ndp *ndpState) rememberDefaultRouter(ip tcpip.Address, rl time.Duration) { } state := defaultRouterState{ - invalidationTimer: tcpip.MakeCancellableTimer(&ndp.nic.mu, func() { + invalidationTimer: tcpip.NewCancellableTimer(&ndp.nic.mu, func() { ndp.invalidateDefaultRouter(ip) }), } @@ -834,7 +846,7 @@ func (ndp *ndpState) rememberOnLinkPrefix(prefix tcpip.Subnet, l time.Duration) } state := onLinkPrefixState{ - invalidationTimer: tcpip.MakeCancellableTimer(&ndp.nic.mu, func() { + invalidationTimer: tcpip.NewCancellableTimer(&ndp.nic.mu, func() { ndp.invalidateOnLinkPrefix(prefix) }), } @@ -859,7 +871,6 @@ func (ndp *ndpState) invalidateOnLinkPrefix(prefix tcpip.Subnet) { } s.invalidationTimer.StopLocked() - delete(ndp.onLinkPrefixes, prefix) // Let the integrator know a discovered on-link prefix is invalidated. @@ -979,7 +990,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) { } state := slaacPrefixState{ - deprecationTimer: tcpip.MakeCancellableTimer(&ndp.nic.mu, func() { + deprecationTimer: tcpip.NewCancellableTimer(&ndp.nic.mu, func() { state, ok := ndp.slaacPrefixes[prefix] if !ok { panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the deprecated SLAAC prefix %s", prefix)) @@ -987,7 +998,7 @@ func (ndp *ndpState) doSLAAC(prefix tcpip.Subnet, pl, vl time.Duration) { ndp.deprecateSLAACAddress(state.ref) }), - invalidationTimer: tcpip.MakeCancellableTimer(&ndp.nic.mu, func() { + invalidationTimer: tcpip.NewCancellableTimer(&ndp.nic.mu, func() { state, ok := ndp.slaacPrefixes[prefix] if !ok { panic(fmt.Sprintf("ndp: must have a slaacPrefixes entry for the invalidated SLAAC prefix %s", prefix)) diff --git a/pkg/tcpip/timer.go b/pkg/tcpip/timer.go index 67f66fc72..59f3b391f 100644 --- a/pkg/tcpip/timer.go +++ b/pkg/tcpip/timer.go @@ -88,6 +88,9 @@ func (t *cancellableTimerInstance) stop() { // // The term "related work" is defined as some work that needs to be done while // holding some lock that the timer must also hold while doing some work. +// +// Note, it is not safe to copy a CancellableTimer as its timer instance creates +// a closure over the address of the CancellableTimer. type CancellableTimer struct { // The active instance of a cancellable timer. instance cancellableTimerInstance @@ -154,12 +157,28 @@ func (t *CancellableTimer) Reset(d time.Duration) { } } -// MakeCancellableTimer returns an unscheduled CancellableTimer with the given +// Lock is a no-op used by the copylocks checker from go vet. +// +// See CancellableTimer for details about why it shouldn't be copied. +// +// See https://github.com/golang/go/issues/8005#issuecomment-190753527 for more +// details about the copylocks checker. +func (*CancellableTimer) Lock() {} + +// Unlock is a no-op used by the copylocks checker from go vet. +// +// See CancellableTimer for details about why it shouldn't be copied. +// +// See https://github.com/golang/go/issues/8005#issuecomment-190753527 for more +// details about the copylocks checker. +func (*CancellableTimer) Unlock() {} + +// NewCancellableTimer returns an unscheduled CancellableTimer with the given // locker and fn. // // fn MUST NOT attempt to lock locker. // // Callers must call Reset to schedule the timer to fire. -func MakeCancellableTimer(locker sync.Locker, fn func()) CancellableTimer { - return CancellableTimer{locker: locker, fn: fn} +func NewCancellableTimer(locker sync.Locker, fn func()) *CancellableTimer { + return &CancellableTimer{locker: locker, fn: fn} } diff --git a/pkg/tcpip/timer_test.go b/pkg/tcpip/timer_test.go index 730134906..b4940e397 100644 --- a/pkg/tcpip/timer_test.go +++ b/pkg/tcpip/timer_test.go @@ -43,7 +43,7 @@ func TestCancellableTimerReassignment(t *testing.T) { // that has an active timer (even if it has been stopped as a stopped // timer may be blocked on a lock before it can check if it has been // stopped while another goroutine holds the same lock). - timer = tcpip.MakeCancellableTimer(&lock, func() { + timer = *tcpip.NewCancellableTimer(&lock, func() { wg.Done() }) timer.Reset(shortDuration) @@ -59,7 +59,7 @@ func TestCancellableTimerFire(t *testing.T) { ch := make(chan struct{}) var lock sync.Mutex - timer := tcpip.MakeCancellableTimer(&lock, func() { + timer := tcpip.NewCancellableTimer(&lock, func() { ch <- struct{}{} }) timer.Reset(shortDuration) @@ -85,7 +85,7 @@ func TestCancellableTimerResetFromLongDuration(t *testing.T) { ch := make(chan struct{}) var lock sync.Mutex - timer := tcpip.MakeCancellableTimer(&lock, func() { ch <- struct{}{} }) + timer := tcpip.NewCancellableTimer(&lock, func() { ch <- struct{}{} }) timer.Reset(middleDuration) lock.Lock() @@ -116,7 +116,7 @@ func TestCancellableTimerResetFromShortDuration(t *testing.T) { var lock sync.Mutex lock.Lock() - timer := tcpip.MakeCancellableTimer(&lock, func() { ch <- struct{}{} }) + timer := tcpip.NewCancellableTimer(&lock, func() { ch <- struct{}{} }) timer.Reset(shortDuration) timer.StopLocked() lock.Unlock() @@ -153,7 +153,7 @@ func TestCancellableTimerImmediatelyStop(t *testing.T) { for i := 0; i < 1000; i++ { lock.Lock() - timer := tcpip.MakeCancellableTimer(&lock, func() { ch <- struct{}{} }) + timer := tcpip.NewCancellableTimer(&lock, func() { ch <- struct{}{} }) timer.Reset(shortDuration) timer.StopLocked() lock.Unlock() @@ -174,7 +174,7 @@ func TestCancellableTimerStoppedResetWithoutLock(t *testing.T) { var lock sync.Mutex lock.Lock() - timer := tcpip.MakeCancellableTimer(&lock, func() { ch <- struct{}{} }) + timer := tcpip.NewCancellableTimer(&lock, func() { ch <- struct{}{} }) timer.Reset(shortDuration) timer.StopLocked() lock.Unlock() @@ -205,7 +205,7 @@ func TestManyCancellableTimerResetAfterBlockedOnLock(t *testing.T) { var lock sync.Mutex lock.Lock() - timer := tcpip.MakeCancellableTimer(&lock, func() { ch <- struct{}{} }) + timer := tcpip.NewCancellableTimer(&lock, func() { ch <- struct{}{} }) timer.Reset(shortDuration) for i := 0; i < 10; i++ { // Sleep until the timer fires and gets blocked trying to take the lock. @@ -237,7 +237,7 @@ func TestManyCancellableTimerResetUnderLock(t *testing.T) { var lock sync.Mutex lock.Lock() - timer := tcpip.MakeCancellableTimer(&lock, func() { ch <- struct{}{} }) + timer := tcpip.NewCancellableTimer(&lock, func() { ch <- struct{}{} }) timer.Reset(shortDuration) for i := 0; i < 10; i++ { timer.StopLocked() -- cgit v1.2.3