Skip to content

Commit af7475f

Browse files
Merge pull request #182 from projectcalico/fix-ro-snap
RM-24272 Fix that read-only snapshots shared state with original.
2 parents e47d01b + fad693e commit af7475f

File tree

2 files changed

+74
-20
lines changed

2 files changed

+74
-20
lines changed

trie/ctrie/ctrie.go

+17-10
Original file line numberDiff line numberDiff line change
@@ -306,28 +306,35 @@ func (c *Ctrie) Remove(key []byte) (interface{}, bool) {
306306
return c.remove(&Entry{Key: key, hash: c.hash(key)})
307307
}
308308

309-
// Snapshot returns a stable, point-in-time snapshot of the Ctrie.
309+
// Snapshot returns a stable, point-in-time snapshot of the Ctrie. If the Ctrie
310+
// is read-only, the returned Ctrie will also be read-only.
310311
func (c *Ctrie) Snapshot() *Ctrie {
311-
for {
312-
root := c.readRoot()
313-
main := gcasRead(root, c)
314-
if c.rdcssRoot(root, main, root.copyToGen(&generation{}, c)) {
315-
return newCtrie(c.readRoot().copyToGen(&generation{}, c), c.hashFactory, c.readOnly)
316-
}
317-
}
312+
return c.snapshot(c.readOnly)
318313
}
319314

320315
// ReadOnlySnapshot returns a stable, point-in-time snapshot of the Ctrie which
321316
// is read-only. Write operations on a read-only snapshot will panic.
322317
func (c *Ctrie) ReadOnlySnapshot() *Ctrie {
323-
if c.readOnly {
318+
return c.snapshot(true)
319+
}
320+
321+
// snapshot wraps up the CAS logic to make a snapshot or a read-only snapshot.
322+
func (c *Ctrie) snapshot(readOnly bool) *Ctrie {
323+
if readOnly && c.readOnly {
324324
return c
325325
}
326326
for {
327327
root := c.readRoot()
328328
main := gcasRead(root, c)
329329
if c.rdcssRoot(root, main, root.copyToGen(&generation{}, c)) {
330-
return newCtrie(c.readRoot(), c.hashFactory, true)
330+
if readOnly {
331+
// For a read-only snapshot, we can share the old generation
332+
// root.
333+
return newCtrie(root, c.hashFactory, readOnly)
334+
}
335+
// For a read-write snapshot, we need to take a copy of the root
336+
// in the new generation.
337+
return newCtrie(c.readRoot().copyToGen(&generation{}, c), c.hashFactory, readOnly)
331338
}
332339
}
333340
}

trie/ctrie/ctrie_test.go

+57-10
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ func TestSnapshot(t *testing.T) {
226226
assert.Equal(i, val)
227227
}
228228

229+
// Now remove the values from the original.
229230
for i := 0; i < 100; i++ {
230231
ctrie.Remove([]byte(strconv.Itoa(i)))
231232
}
@@ -237,6 +238,7 @@ func TestSnapshot(t *testing.T) {
237238
assert.Equal(i, val)
238239
}
239240

241+
// New Ctrie and snapshot.
240242
ctrie = New(nil)
241243
for i := 0; i < 100; i++ {
242244
ctrie.Insert([]byte(strconv.Itoa(i)), i)
@@ -266,32 +268,77 @@ func TestSnapshot(t *testing.T) {
266268
_, ok = ctrie.Lookup([]byte("bat"))
267269
assert.False(ok)
268270

269-
snapshot = ctrie.ReadOnlySnapshot()
271+
// Ensure snapshots-of-snapshots work as expected.
272+
snapshot2 := snapshot.Snapshot()
273+
for i := 0; i < 100; i++ {
274+
_, ok := snapshot2.Lookup([]byte(strconv.Itoa(i)))
275+
assert.False(ok)
276+
}
277+
val, ok = snapshot2.Lookup([]byte("bat"))
278+
assert.True(ok)
279+
assert.Equal("man", val)
280+
281+
snapshot2.Remove([]byte("bat"))
282+
_, ok = snapshot2.Lookup([]byte("bat"))
283+
assert.False(ok)
284+
val, ok = snapshot.Lookup([]byte("bat"))
285+
assert.True(ok)
286+
assert.Equal("man", val)
287+
}
288+
289+
func TestReadOnlySnapshot(t *testing.T) {
290+
assert := assert.New(t)
291+
ctrie := New(nil)
292+
for i := 0; i < 100; i++ {
293+
ctrie.Insert([]byte(strconv.Itoa(i)), i)
294+
}
295+
296+
snapshot := ctrie.ReadOnlySnapshot()
297+
298+
// Ensure snapshot contains expected keys.
299+
for i := 0; i < 100; i++ {
300+
val, ok := snapshot.Lookup([]byte(strconv.Itoa(i)))
301+
assert.True(ok)
302+
assert.Equal(i, val)
303+
}
304+
305+
for i := 0; i < 50; i++ {
306+
ctrie.Remove([]byte(strconv.Itoa(i)))
307+
}
308+
309+
// Ensure snapshot was unaffected by removals.
270310
for i := 0; i < 100; i++ {
271311
val, ok := snapshot.Lookup([]byte(strconv.Itoa(i)))
272312
assert.True(ok)
273313
assert.Equal(i, val)
274314
}
275315

276316
// Ensure read-only snapshots panic on writes.
277-
defer func() {
278-
assert.NotNil(recover())
317+
func() {
318+
defer func() {
319+
assert.NotNil(recover())
320+
}()
321+
snapshot.Remove([]byte("blah"))
279322
}()
280-
snapshot.Remove([]byte("blah"))
281323

282324
// Ensure snapshots-of-snapshots work as expected.
283325
snapshot2 := snapshot.Snapshot()
326+
for i := 50; i < 100; i++ {
327+
ctrie.Remove([]byte(strconv.Itoa(i)))
328+
}
284329
for i := 0; i < 100; i++ {
285330
val, ok := snapshot2.Lookup([]byte(strconv.Itoa(i)))
286331
assert.True(ok)
287332
assert.Equal(i, val)
288333
}
289-
snapshot2.Remove([]byte("0"))
290-
_, ok = snapshot2.Lookup([]byte("0"))
291-
assert.False(ok)
292-
val, ok = snapshot.Lookup([]byte("0"))
293-
assert.True(ok)
294-
assert.Equal(0, val)
334+
335+
// Ensure snapshots of read-only snapshots panic on writes.
336+
func() {
337+
defer func() {
338+
assert.NotNil(recover())
339+
}()
340+
snapshot2.Remove([]byte("blah"))
341+
}()
295342
}
296343

297344
func TestIterator(t *testing.T) {

0 commit comments

Comments
 (0)