Skip to content

Commit ef262b1

Browse files
authored
ifname: avoid unpredictable conditions in getMap test (influxdata#7848)
1 parent 38c01b4 commit ef262b1

File tree

2 files changed

+28
-45
lines changed

2 files changed

+28
-45
lines changed

plugins/processors/ifname/ifname.go

+8-12
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,7 @@ type IfName struct {
111111

112112
gsBase snmp.GosnmpWrapper `toml:"-"`
113113

114-
sigs sigMap `toml:"-"`
115-
sigsLock sync.Mutex `toml:"-"`
114+
sigs sigMap `toml:"-"`
116115
}
117116

118117
const minRetry time.Duration = 5 * time.Minute
@@ -251,14 +250,14 @@ func (d *IfName) getMap(agent string) (entry nameMap, age time.Duration, err err
251250
}
252251

253252
// Is this the first request for this agent?
254-
d.sigsLock.Lock()
253+
d.rwLock.Lock()
255254
sig, found := d.sigs[agent]
256255
if !found {
257256
s := make(chan struct{})
258257
d.sigs[agent] = s
259258
sig = s
260259
}
261-
d.sigsLock.Unlock()
260+
d.rwLock.Unlock()
262261

263262
if found {
264263
// This is not the first request. Wait for first to finish.
@@ -281,24 +280,21 @@ func (d *IfName) getMap(agent string) (entry nameMap, age time.Duration, err err
281280
m, err = d.getMapRemote(agent)
282281
if err != nil {
283282
//failure. signal without saving to cache
284-
d.sigsLock.Lock()
283+
d.rwLock.Lock()
285284
close(sig)
286285
delete(d.sigs, agent)
287-
d.sigsLock.Unlock()
286+
d.rwLock.Unlock()
288287

289288
return nil, 0, fmt.Errorf("getting remote table: %w", err)
290289
}
291290

292-
// Cache it
291+
// Cache it, then signal any other waiting requests for this agent
292+
// and clean up
293293
d.rwLock.Lock()
294294
d.cache.Put(agent, m)
295-
d.rwLock.Unlock()
296-
297-
// Signal any other waiting requests for this agent and clean up
298-
d.sigsLock.Lock()
299295
close(sig)
300296
delete(d.sigs, agent)
301-
d.sigsLock.Unlock()
297+
d.rwLock.Unlock()
302298

303299
return m, 0, nil
304300
}

plugins/processors/ifname/ifname_test.go

+20-33
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func TestTable(t *testing.T) {
2626

2727
config := snmp.ClientConfig{
2828
Version: 2,
29-
Timeout: internal.Duration{Duration: 5 * time.Second}, // doesn't work with 0 timeout
29+
Timeout: internal.Duration{Duration: 5 * time.Second}, // Doesn't work with 0 timeout
3030
}
3131
gs, err := snmp.NewWrapper(config)
3232
require.NoError(t, err)
@@ -36,7 +36,7 @@ func TestTable(t *testing.T) {
3636
err = gs.Connect()
3737
require.NoError(t, err)
3838

39-
//could use ifIndex but oid index is always the same
39+
// Could use ifIndex but oid index is always the same
4040
m, err := buildMap(gs, tab, "ifDescr")
4141
require.NoError(t, err)
4242
require.NotEmpty(t, m)
@@ -53,7 +53,7 @@ func TestIfName(t *testing.T) {
5353
CacheSize: 1000,
5454
ClientConfig: snmp.ClientConfig{
5555
Version: 2,
56-
Timeout: internal.Duration{Duration: 5 * time.Second}, // doesn't work with 0 timeout
56+
Timeout: internal.Duration{Duration: 5 * time.Second}, // Doesn't work with 0 timeout
5757
},
5858
}
5959
err := d.Init()
@@ -93,65 +93,52 @@ func TestIfName(t *testing.T) {
9393

9494
func TestGetMap(t *testing.T) {
9595
d := IfName{
96-
SourceTag: "ifIndex",
97-
DestTag: "ifName",
98-
AgentTag: "agent",
9996
CacheSize: 1000,
100-
ClientConfig: snmp.ClientConfig{
101-
Version: 2,
102-
Timeout: internal.Duration{Duration: 5 * time.Second}, // doesn't work with 0 timeout
103-
},
104-
CacheTTL: config.Duration(10 * time.Second),
97+
CacheTTL: config.Duration(10 * time.Second),
10598
}
10699

107-
// This test mocks the snmp transaction so don't run net-snmp
108-
// commands to look up table names.
100+
// Don't run net-snmp commands to look up table names.
109101
d.makeTable = func(agent string) (*si.Table, error) {
110102
return &si.Table{}, nil
111103
}
112104
err := d.Init()
113105
require.NoError(t, err)
114106

115-
// Request the same agent multiple times in goroutines. The first
116-
// request should make the mocked remote call and the others
117-
// should block until the response is cached, then return the
118-
// cached response.
119-
120107
expected := nameMap{
121108
1: "ifname1",
122109
2: "ifname2",
123110
}
124111

125-
var wgRemote sync.WaitGroup
126112
var remoteCalls int32
127113

128-
wgRemote.Add(1)
114+
// Mock the snmp transaction
129115
d.getMapRemote = func(agent string) (nameMap, error) {
130116
atomic.AddInt32(&remoteCalls, 1)
131-
wgRemote.Wait() //don't return until all requests are made
132117
return expected, nil
133118
}
119+
m, age, err := d.getMap("agent")
120+
require.NoError(t, err)
121+
require.Zero(t, age) // Age is zero when map comes from getMapRemote
122+
require.Equal(t, expected, m)
134123

135-
const thMax = 3
136-
var wgReq sync.WaitGroup
124+
// Remote call should happen the first time getMap runs
125+
require.Equal(t, int32(1), remoteCalls)
137126

127+
var wg sync.WaitGroup
128+
const thMax = 3
138129
for th := 0; th < thMax; th++ {
139-
wgReq.Add(1)
130+
wg.Add(1)
140131
go func() {
141-
defer wgReq.Done()
142-
m, _, err := d.getMap("agent")
132+
defer wg.Done()
133+
m, age, err := d.getMap("agent")
143134
require.NoError(t, err)
135+
require.NotZero(t, age) // Age is nonzero when map comes from cache
144136
require.Equal(t, expected, m)
145137
}()
146138
}
147139

148-
//signal mocked remote call to finish
149-
wgRemote.Done()
150-
151-
//wait for requests to finish
152-
wgReq.Wait()
140+
wg.Wait()
153141

154-
//remote call should only happen once
142+
// Remote call should not happen subsequent times getMap runs
155143
require.Equal(t, int32(1), remoteCalls)
156-
157144
}

0 commit comments

Comments
 (0)