Skip to content

Commit 94c6a30

Browse files
authored
Fix deadlock bug in ipcache (#43)
* Fix deadlock bug in ipcache * Make sure autotools are installed for build
1 parent 1d7bfdc commit 94c6a30

File tree

4 files changed

+47
-76
lines changed

4 files changed

+47
-76
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ RUN chmod -R a+rx /go/bin/traceroute-caller
1111

1212

1313
FROM ubuntu:latest
14-
# Install all the standard packages we need and then remove the ap-get lists.
14+
# Install all the standard packages we need and then remove the apt-get lists.
1515
RUN apt-get update && \
16-
apt-get install -y python python-pip make iproute2 coreutils && \
16+
apt-get install -y python python-pip make iproute2 coreutils autoconf && \
1717
apt-get clean && \
1818
rm -rf /var/lib/apt/lists/*
1919

ipcache/ipcache.go

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,45 @@ type Tracer interface {
2828
type cachedTest struct {
2929
timeStamp time.Time
3030
data string
31-
done chan struct{}
31+
dataReady chan struct{}
3232
}
3333

3434
// RecentIPCache contains a list of all the IP addresses that we have traced to
3535
// recently. We keep this list to ensure that we don't traceroute to the same
3636
// location repeatedly at a high frequency.
3737
type RecentIPCache struct {
3838
cache map[string]*cachedTest
39-
mu sync.RWMutex
39+
mu sync.Mutex
4040

4141
tracer Tracer
4242
}
4343

44-
// Trace performs a trace and adds it to the cache.
45-
func (rc *RecentIPCache) Trace(conn connection.Connection) {
46-
ip := conn.RemoteIP
44+
func (rc *RecentIPCache) getEntry(ip string) (*cachedTest, bool) {
4745
rc.mu.Lock()
48-
_, ok := rc.cache[ip]
49-
if !ok {
50-
nc := &cachedTest{
46+
defer rc.mu.Unlock()
47+
_, existed := rc.cache[ip]
48+
if !existed {
49+
rc.cache[ip] = &cachedTest{
5150
timeStamp: time.Now(),
52-
done: make(chan struct{}),
51+
dataReady: make(chan struct{}),
5352
}
54-
rc.cache[ip] = nc
55-
rc.mu.Unlock()
53+
}
54+
return rc.cache[ip], existed
55+
}
5656

57-
nc.data = rc.tracer.Trace(conn, nc.timeStamp)
58-
close(nc.done)
59-
return
57+
// Trace performs a trace and adds it to the cache. It calls the methods of the
58+
// tracer, so if those create files on disk, then files on disk will be created
59+
// as a side effect.
60+
func (rc *RecentIPCache) Trace(conn connection.Connection) string {
61+
c, cached := rc.getEntry(conn.RemoteIP)
62+
if cached {
63+
<-c.dataReady
64+
rc.tracer.CreateCacheTest(conn, time.Now(), c.data)
65+
return c.data
6066
}
61-
rc.mu.Unlock()
62-
rc.tracer.CreateCacheTest(conn, time.Now(), rc.GetData(ip))
67+
c.data = rc.tracer.Trace(conn, c.timeStamp)
68+
close(c.dataReady)
69+
return c.data
6370
}
6471

6572
// GetCacheLength returns the number of items currently in the cache. The
@@ -69,19 +76,6 @@ func (rc *RecentIPCache) GetCacheLength() int {
6976
return len(rc.cache)
7077
}
7178

72-
// GetData will wait till the test content available if there is an entry
73-
// in cache.
74-
func (rc *RecentIPCache) GetData(ip string) string {
75-
rc.mu.RLock()
76-
defer rc.mu.RUnlock()
77-
c, ok := rc.cache[ip]
78-
if ok {
79-
<-c.done
80-
return c.data
81-
}
82-
return ""
83-
}
84-
8579
// New creates and returns a RecentIPCache. It also starts up a background
8680
// goroutine that scrubs the cache.
8781
func New(ctx context.Context, tracer Tracer, ipCacheTimeout, ipCacheUpdatePeriod time.Duration) *RecentIPCache {
@@ -96,13 +90,14 @@ func New(ctx context.Context, tracer Tracer, ipCacheTimeout, ipCacheUpdatePeriod
9690
if ctx.Err() != nil {
9791
return
9892
}
93+
// Must hold lock while performing GC.
94+
m.mu.Lock()
9995
for k, v := range m.cache {
10096
if now.Sub(v.timeStamp) > ipCacheTimeout {
101-
m.mu.Lock()
10297
delete(m.cache, k)
103-
m.mu.Unlock()
10498
}
10599
}
100+
m.mu.Unlock()
106101
}
107102

108103
}()

ipcache/ipcache_test.go

Lines changed: 19 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package ipcache_test
22

33
import (
44
"context"
5-
"log"
65
"testing"
76
"time"
87

@@ -12,45 +11,19 @@ import (
1211

1312
type testTracer struct {
1413
calls int
14+
cctest int
1515
answers []map[connection.Connection]struct{}
1616
}
1717

1818
func (tf *testTracer) Trace(conn connection.Connection, t time.Time) string {
19-
log.Println("Create Trace Test")
20-
return "Fake Trace test " + conn.RemoteIP
19+
return "Fake trace test " + conn.RemoteIP
2120
}
2221

2322
func (tf *testTracer) CreateCacheTest(conn connection.Connection, t time.Time, cachedTest string) {
24-
log.Println("Create cached Test " + conn.RemoteIP)
23+
tf.cctest++
2524
return
2625
}
2726

28-
func TestGetData(t *testing.T) {
29-
ctx, cancel := context.WithCancel(context.Background())
30-
defer cancel()
31-
var tt testTracer
32-
testCache := ipcache.New(ctx, &tt, 100*time.Second, time.Second)
33-
34-
conn1 := connection.Connection{
35-
RemoteIP: "1.1.1.2",
36-
RemotePort: 5034,
37-
LocalIP: "1.1.1.3",
38-
LocalPort: 58790,
39-
Cookie: "10f3d"}
40-
41-
testCache.Trace(conn1)
42-
time.Sleep(200 * time.Millisecond)
43-
tmp := testCache.GetData("1.1.1.1")
44-
if tmp != "" {
45-
t.Error("GetData not working correctly ")
46-
}
47-
48-
tmp = testCache.GetData("1.1.1.2")
49-
if tmp != "Fake Trace test 1.1.1.2" {
50-
t.Error("GetData not working correctly ")
51-
}
52-
}
53-
5427
func TestTrace(t *testing.T) {
5528
ctx, cancel := context.WithCancel(context.Background())
5629
defer cancel()
@@ -64,25 +37,29 @@ func TestTrace(t *testing.T) {
6437
LocalPort: 58790,
6538
Cookie: "10f3d"}
6639

67-
testCache.Trace(conn1)
68-
time.Sleep(200 * time.Millisecond)
69-
tmp := testCache.GetData("1.1.1.2")
70-
if tmp != "Fake Trace test 1.1.1.2" {
40+
tmp := testCache.Trace(conn1)
41+
if tmp != "Fake trace test 1.1.1.2" {
7142
t.Error("cache not trace correctly ")
7243
}
7344

7445
conn2 := connection.Connection{
7546
RemoteIP: "1.1.1.5",
7647
RemotePort: 5034,
77-
LocalIP: "1.1.1.7",
48+
LocalIP: "1.1.1.3",
7849
LocalPort: 58790,
7950
Cookie: "aaaa"}
8051

81-
testCache.Trace(conn2)
82-
testCache.Trace(conn1)
83-
84-
time.Sleep(200 * time.Millisecond)
85-
52+
t2 := testCache.Trace(conn2)
53+
if t2 != "Fake trace test 1.1.1.5" {
54+
t.Error("cache did not trace")
55+
}
56+
if tt.cctest != 0 {
57+
t.Errorf("Should have had zero calls to CreateCachedTest, not %d", tt.cctest)
58+
}
59+
testCache.Trace(conn1) // This should be cached
60+
if tt.cctest != 1 {
61+
t.Errorf("Should have had one call to CreateCachedTest, not %d", tt.cctest)
62+
}
8663
if testCache.GetCacheLength() != 2 {
8764
t.Error("cache not working correctly ")
8865
}
@@ -101,12 +78,12 @@ func TestRecentIPCache(t *testing.T) {
10178
Cookie: "11",
10279
})
10380
if tmp.GetCacheLength() != 1 {
104-
t.Error("cache not working correctly")
81+
t.Error("Did not put an entry into the cache")
10582
}
10683

10784
time.Sleep(300 * time.Millisecond)
10885
if tmp.GetCacheLength() != 0 {
109-
t.Error("cache not working correctly")
86+
t.Error("Cache GC failed to collect garbage")
11087
}
11188
cancel()
11289
time.Sleep(200 * time.Millisecond)

scamper/scamper.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,3 @@ func (d *Daemon) trace(conn connection.Connection, t time.Time) string {
227227
}
228228
return string(buff.Bytes())
229229
}
230-

0 commit comments

Comments
 (0)