From 9dcbd18f301f6d8601d479e37204e282c3a7535c Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 10 Sep 2024 11:35:16 +0200 Subject: [PATCH] fix: avoid race conditions These race conditions may seem harmless, and maybe they are unlikely to cause real trouble, but: 1. There is a theoretical chance they do. A partially written time value in one goroutine may result in an invalid (and very wrong) time in another, leading to misbehaving code. 2. They introduce noise, and make it more difficult to find&fix other race conditions. --- ap/ap.go | 10 ++++++++-- dealer/dealer.go | 12 ++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ap/ap.go b/ap/ap.go index 2c872b3..87668fd 100644 --- a/ap/ap.go +++ b/ap/ap.go @@ -48,6 +48,7 @@ type Accesspoint struct { recvChans map[PacketType][]chan Packet recvChansLock sync.RWMutex lastPongAck time.Time + lastPongAckLock sync.Mutex // connMu is held for writing when performing reconnection and for reading mainly when accessing welcome // or sending packets. If it's not held, a valid connection (and APWelcome) is available. Be careful not to deadlock @@ -274,7 +275,9 @@ loop: } case PacketTypePongAck: log.Tracef("received accesspoint pong ack") + ap.lastPongAckLock.Lock() ap.lastPongAck = time.Now() + ap.lastPongAckLock.Unlock() continue default: ap.recvChansLock.RLock() @@ -334,8 +337,11 @@ loop: case <-ap.pongAckTickerStop: break loop case <-ticker.C: - if time.Since(ap.lastPongAck) > pongAckInterval { - log.Errorf("did not receive last pong ack from accesspoint, %.0fs passed", time.Since(ap.lastPongAck).Seconds()) + ap.lastPongAckLock.Lock() + timePassed := time.Since(ap.lastPongAck) + ap.lastPongAckLock.Unlock() + if timePassed > pongAckInterval { + log.Errorf("did not receive last pong ack from accesspoint, %.0fs passed", timePassed.Seconds()) // closing the connection should make the read on the "recvLoop" fail, // continue hoping for a new connection diff --git a/dealer/dealer.go b/dealer/dealer.go index bf179d6..f3d3125 100644 --- a/dealer/dealer.go +++ b/dealer/dealer.go @@ -31,6 +31,7 @@ type Dealer struct { recvLoopStop chan struct{} recvLoopOnce sync.Once lastPong time.Time + lastPongLock sync.Mutex // connMu is held for writing when performing reconnection and for reading when accessing the conn. // If it's not held, a valid connection is available. Be careful not to deadlock anything with this. @@ -126,8 +127,11 @@ loop: case <-d.pingTickerStop: break loop case <-ticker.C: - if time.Since(d.lastPong) > pingInterval+timeout { - log.Errorf("did not receive last pong from dealer, %.0fs passed", time.Since(d.lastPong).Seconds()) + d.lastPongLock.Lock() + timePassed := time.Since(d.lastPong) + d.lastPongLock.Unlock() + if timePassed > pingInterval+timeout { + log.Errorf("did not receive last pong from dealer, %.0fs passed", timePassed.Seconds()) // closing the connection should make the read on the "recvLoop" fail, // continue hoping for a new connection @@ -200,7 +204,9 @@ loop: // we never receive ping messages break case "pong": + d.lastPongLock.Lock() d.lastPong = time.Now() + d.lastPongLock.Unlock() log.Tracef("received dealer pong") break default: @@ -267,7 +273,9 @@ func (d *Dealer) reconnect() error { return err } + d.lastPongLock.Lock() d.lastPong = time.Now() + d.lastPongLock.Unlock() // restart the recv loop go d.recvLoop()