Skip to content

Commit d896be2

Browse files
committed
Use atomic ints to set the time for lastSent and lastReceived, and pingOutstanding.
This resolves the issue of data races as previously coded, it changes the internal struct for the options but leaves the Options setting API the same.
1 parent e020008 commit d896be2

7 files changed

+30
-27
lines changed

client.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ type client struct {
7777
stop chan struct{}
7878
persist Store
7979
options ClientOptions
80-
lastSent time.Time
81-
lastReceived time.Time
82-
pingOutstanding bool
80+
lastSent int64
81+
lastReceived int64
82+
pingOutstanding int32
8383
status uint32
8484
workers sync.WaitGroup
8585
}
@@ -239,8 +239,8 @@ func (c *client) Connect() Token {
239239
c.stop = make(chan struct{})
240240

241241
if c.options.KeepAlive != 0 {
242-
c.lastReceived = time.Now()
243-
c.lastSent = time.Now()
242+
atomic.StoreInt64(&c.lastReceived, time.Now().Unix())
243+
atomic.StoreInt64(&c.lastSent, time.Now().Unix())
244244
c.workers.Add(1)
245245
go keepalive(c)
246246
}
@@ -342,9 +342,9 @@ func (c *client) reconnect() {
342342
}
343343

344344
if c.options.KeepAlive != 0 {
345-
c.pingOutstanding = false
346-
c.lastReceived = time.Now()
347-
c.lastSent = time.Now()
345+
atomic.StoreInt32(&c.pingOutstanding, 0)
346+
atomic.StoreInt64(&c.lastReceived, time.Now().Unix())
347+
atomic.StoreInt64(&c.lastSent, time.Now().Unix())
348348
c.workers.Add(1)
349349
go keepalive(c)
350350
}

message.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func newConnectMsgFromOptions(options *ClientOptions) *packets.ConnectPacket {
9898
}
9999
}
100100

101-
m.Keepalive = uint16(options.KeepAlive.Seconds())
101+
m.Keepalive = uint16(options.KeepAlive)
102102

103103
return m
104104
}

net.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"net/url"
2323
"os"
2424
"reflect"
25+
"sync/atomic"
2526
"time"
2627

2728
"github.com/eclipse/paho.mqtt.golang/packets"
@@ -125,7 +126,7 @@ func incoming(c *client) {
125126
case c.ibound <- cp:
126127
// Notify keepalive logic that we recently received a packet
127128
if c.options.KeepAlive != 0 {
128-
c.lastReceived = time.Now()
129+
atomic.StoreInt64(&c.lastReceived, time.Now().Unix())
129130
}
130131
case <-c.stop:
131132
// This avoids a deadlock should a message arrive while shutting down.
@@ -205,7 +206,7 @@ func outgoing(c *client) {
205206
}
206207
// Reset ping timer after sending control packet.
207208
if c.options.KeepAlive != 0 {
208-
c.lastSent = time.Now()
209+
atomic.StoreInt64(&c.lastSent, time.Now().Unix())
209210
}
210211
}
211212
}
@@ -228,7 +229,7 @@ func alllogic(c *client) {
228229
switch m := msg.(type) {
229230
case *packets.PingrespPacket:
230231
DEBUG.Println(NET, "received pingresp")
231-
c.pingOutstanding = false
232+
atomic.StoreInt32(&c.pingOutstanding, 0)
232233
case *packets.SubackPacket:
233234
DEBUG.Println(NET, "received suback, id:", m.MessageID)
234235
token := c.getToken(m.MessageID)

options.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ type ClientOptions struct {
5252
ProtocolVersion uint
5353
protocolVersionExplicit bool
5454
TLSConfig tls.Config
55-
KeepAlive time.Duration
55+
KeepAlive int64
5656
PingTimeout time.Duration
5757
ConnectTimeout time.Duration
5858
MaxReconnectInterval time.Duration
@@ -90,7 +90,7 @@ func NewClientOptions() *ClientOptions {
9090
ProtocolVersion: 0,
9191
protocolVersionExplicit: false,
9292
TLSConfig: tls.Config{},
93-
KeepAlive: 30 * time.Second,
93+
KeepAlive: 30,
9494
PingTimeout: 10 * time.Second,
9595
ConnectTimeout: 30 * time.Second,
9696
MaxReconnectInterval: 10 * time.Minute,
@@ -182,7 +182,7 @@ func (o *ClientOptions) SetStore(s Store) *ClientOptions {
182182
// allow the client to know that a connection has not been lost with the
183183
// server.
184184
func (o *ClientOptions) SetKeepAlive(k time.Duration) *ClientOptions {
185-
o.KeepAlive = k
185+
o.KeepAlive = int64(k / time.Second)
186186
return o
187187
}
188188

options_reader.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func (r *ClientOptionsReader) TLSConfig() tls.Config {
102102
}
103103

104104
func (r *ClientOptionsReader) KeepAlive() time.Duration {
105-
s := r.options.KeepAlive
105+
s := time.Duration(r.options.KeepAlive * int64(time.Second))
106106
return s
107107
}
108108

ping.go

+11-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package mqtt
1616

1717
import (
1818
"errors"
19+
"sync/atomic"
1920
"time"
2021

2122
"github.com/eclipse/paho.mqtt.golang/packets"
@@ -24,16 +25,16 @@ import (
2425
func keepalive(c *client) {
2526
defer c.workers.Done()
2627
DEBUG.Println(PNG, "keepalive starting")
27-
var checkInterval time.Duration
28+
var checkInterval int64
2829
var pingSent time.Time
2930

30-
if c.options.KeepAlive > 10*time.Second {
31-
checkInterval = 5 * time.Second
31+
if c.options.KeepAlive > 10 {
32+
checkInterval = 5
3233
} else {
3334
checkInterval = c.options.KeepAlive / 2
3435
}
3536

36-
intervalTicker := time.NewTicker(checkInterval)
37+
intervalTicker := time.NewTicker(time.Duration(checkInterval * int64(time.Second)))
3738
defer intervalTicker.Stop()
3839

3940
for {
@@ -42,19 +43,20 @@ func keepalive(c *client) {
4243
DEBUG.Println(PNG, "keepalive stopped")
4344
return
4445
case <-intervalTicker.C:
45-
if time.Now().Sub(c.lastSent) >= c.options.KeepAlive || time.Now().Sub(c.lastReceived) >= c.options.KeepAlive {
46-
if !c.pingOutstanding {
46+
DEBUG.Println(PNG, "ping check", time.Now().Unix()-atomic.LoadInt64(&c.lastSent))
47+
if time.Now().Unix()-atomic.LoadInt64(&c.lastSent) >= c.options.KeepAlive || time.Now().Unix()-atomic.LoadInt64(&c.lastReceived) >= c.options.KeepAlive {
48+
if atomic.LoadInt32(&c.pingOutstanding) == 0 {
4749
DEBUG.Println(PNG, "keepalive sending ping")
4850
ping := packets.NewControlPacket(packets.Pingreq).(*packets.PingreqPacket)
4951
//We don't want to wait behind large messages being sent, the Write call
5052
//will block until it it able to send the packet.
51-
c.pingOutstanding = true
53+
atomic.StoreInt32(&c.pingOutstanding, 1)
5254
ping.Write(c.conn)
53-
c.lastSent = time.Now()
55+
atomic.StoreInt64(&c.lastSent, time.Now().Unix())
5456
pingSent = time.Now()
5557
}
5658
}
57-
if c.pingOutstanding && time.Now().Sub(pingSent) >= c.options.PingTimeout {
59+
if atomic.LoadInt32(&c.pingOutstanding) > 0 && time.Now().Sub(pingSent) >= c.options.PingTimeout {
5860
CRITICAL.Println(PNG, "pingresp not received, disconnecting")
5961
c.errors <- errors.New("pingresp not received, disconnecting")
6062
return

unit_options_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func Test_NewClientOptions_default(t *testing.T) {
3636
t.Fatalf("bad default password")
3737
}
3838

39-
if o.KeepAlive != 30*time.Second {
39+
if o.KeepAlive != 30 {
4040
t.Fatalf("bad default timeout")
4141
}
4242
}
@@ -69,7 +69,7 @@ func Test_NewClientOptions_mix(t *testing.T) {
6969
t.Fatalf("bad set password")
7070
}
7171

72-
if o.KeepAlive != 88000000000 {
72+
if o.KeepAlive != 88 {
7373
t.Fatalf("bad set timeout: %d", o.KeepAlive)
7474
}
7575
}

0 commit comments

Comments
 (0)