Add remove and list functions to digest set

Add mutex protection around set access

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
Derek McGowan 2015-09-29 10:04:15 -07:00
parent ec87e9b697
commit 68b48789cc
2 changed files with 147 additions and 2 deletions

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"sort" "sort"
"strings" "strings"
"sync"
) )
var ( var (
@ -27,6 +28,7 @@ var (
// the complete set of digests. To mitigate collisions, an // the complete set of digests. To mitigate collisions, an
// appropriately long short code should be used. // appropriately long short code should be used.
type Set struct { type Set struct {
mutex sync.RWMutex
entries digestEntries entries digestEntries
} }
@ -63,6 +65,8 @@ func checkShortMatch(alg Algorithm, hex, shortAlg, shortHex string) bool {
// with an empty digest value. If multiple matches are found // with an empty digest value. If multiple matches are found
// ErrDigestAmbiguous will be returned with an empty digest value. // ErrDigestAmbiguous will be returned with an empty digest value.
func (dst *Set) Lookup(d string) (Digest, error) { func (dst *Set) Lookup(d string) (Digest, error) {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
if len(dst.entries) == 0 { if len(dst.entries) == 0 {
return "", ErrDigestNotFound return "", ErrDigestNotFound
} }
@ -101,13 +105,15 @@ func (dst *Set) Lookup(d string) (Digest, error) {
return dst.entries[idx].digest, nil return dst.entries[idx].digest, nil
} }
// Add adds the given digests to the set. An error will be returned // Add adds the given digest to the set. An error will be returned
// if the given digest is invalid. If the digest already exists in the // if the given digest is invalid. If the digest already exists in the
// table, this operation will be a no-op. // set, this operation will be a no-op.
func (dst *Set) Add(d Digest) error { func (dst *Set) Add(d Digest) error {
if err := d.Validate(); err != nil { if err := d.Validate(); err != nil {
return err return err
} }
dst.mutex.Lock()
defer dst.mutex.Unlock()
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d} entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
searchFunc := func(i int) bool { searchFunc := func(i int) bool {
if dst.entries[i].val == entry.val { if dst.entries[i].val == entry.val {
@ -130,12 +136,56 @@ func (dst *Set) Add(d Digest) error {
return nil return nil
} }
// Remove removes the given digest from the set. An err will be
// returned if the given digest is invalid. If the digest does
// not exist in the set, this operation will be a no-op.
func (dst *Set) Remove(d Digest) error {
if err := d.Validate(); err != nil {
return err
}
dst.mutex.Lock()
defer dst.mutex.Unlock()
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
searchFunc := func(i int) bool {
if dst.entries[i].val == entry.val {
return dst.entries[i].alg >= entry.alg
}
return dst.entries[i].val >= entry.val
}
idx := sort.Search(len(dst.entries), searchFunc)
// Not found if idx is after or value at idx is not digest
if idx == len(dst.entries) || dst.entries[idx].digest != d {
return nil
}
entries := dst.entries
copy(entries[idx:], entries[idx+1:])
entries = entries[:len(entries)-1]
dst.entries = entries
return nil
}
// All returns all the digests in the set
func (dst *Set) All() []Digest {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
retValues := make([]Digest, len(dst.entries))
for i := range dst.entries {
retValues[i] = dst.entries[i].digest
}
return retValues
}
// ShortCodeTable returns a map of Digest to unique short codes. The // ShortCodeTable returns a map of Digest to unique short codes. The
// length represents the minimum value, the maximum length may be the // length represents the minimum value, the maximum length may be the
// entire value of digest if uniqueness cannot be achieved without the // entire value of digest if uniqueness cannot be achieved without the
// full value. This function will attempt to make short codes as short // full value. This function will attempt to make short codes as short
// as possible to be unique. // as possible to be unique.
func ShortCodeTable(dst *Set, length int) map[Digest]string { func ShortCodeTable(dst *Set, length int) map[Digest]string {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
m := make(map[Digest]string, len(dst.entries)) m := make(map[Digest]string, len(dst.entries))
l := length l := length
resetIdx := 0 resetIdx := 0

View file

@ -125,6 +125,66 @@ func TestAddDuplication(t *testing.T) {
} }
} }
func TestRemove(t *testing.T) {
digests, err := createDigests(10)
if err != nil {
t.Fatal(err)
}
dset := NewSet()
for i := range digests {
if err := dset.Add(digests[i]); err != nil {
t.Fatal(err)
}
}
dgst, err := dset.Lookup(digests[0].String())
if err != nil {
t.Fatal(err)
}
if dgst != digests[0] {
t.Fatalf("Unexpected digest value:\n\tExpected: %s\n\tActual: %s", digests[0], dgst)
}
if err := dset.Remove(digests[0]); err != nil {
t.Fatal(err)
}
if _, err := dset.Lookup(digests[0].String()); err != ErrDigestNotFound {
t.Fatalf("Expected error %v when looking up removed digest, got %v", ErrDigestNotFound, err)
}
}
func TestAll(t *testing.T) {
digests, err := createDigests(100)
if err != nil {
t.Fatal(err)
}
dset := NewSet()
for i := range digests {
if err := dset.Add(digests[i]); err != nil {
t.Fatal(err)
}
}
all := map[Digest]struct{}{}
for _, dgst := range dset.All() {
all[dgst] = struct{}{}
}
if len(all) != len(digests) {
t.Fatalf("Unexpected number of unique digests found:\n\tExpected: %d\n\tActual: %d", len(digests), len(all))
}
for i, dgst := range digests {
if _, ok := all[dgst]; !ok {
t.Fatalf("Missing element at position %d: %s", i, dgst)
}
}
}
func assertEqualShort(t *testing.T, actual, expected string) { func assertEqualShort(t *testing.T, actual, expected string) {
if actual != expected { if actual != expected {
t.Fatalf("Unexpected short value:\n\tExpected: %s\n\tActual: %s", expected, actual) t.Fatalf("Unexpected short value:\n\tExpected: %s\n\tActual: %s", expected, actual)
@ -219,6 +279,29 @@ func benchLookupNTable(b *testing.B, n int, shortLen int) {
} }
} }
func benchRemoveNTable(b *testing.B, n int) {
digests, err := createDigests(n)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
b.StopTimer()
for j := range digests {
if err = dset.Add(digests[j]); err != nil {
b.Fatal(err)
}
}
b.StartTimer()
for j := range digests {
if err = dset.Remove(digests[j]); err != nil {
b.Fatal(err)
}
}
}
}
func benchShortCodeNTable(b *testing.B, n int, shortLen int) { func benchShortCodeNTable(b *testing.B, n int, shortLen int) {
digests, err := createDigests(n) digests, err := createDigests(n)
if err != nil { if err != nil {
@ -249,6 +332,18 @@ func BenchmarkAdd1000(b *testing.B) {
benchAddNTable(b, 1000) benchAddNTable(b, 1000)
} }
func BenchmarkRemove10(b *testing.B) {
benchRemoveNTable(b, 10)
}
func BenchmarkRemove100(b *testing.B) {
benchRemoveNTable(b, 100)
}
func BenchmarkRemove1000(b *testing.B) {
benchRemoveNTable(b, 1000)
}
func BenchmarkLookup10(b *testing.B) { func BenchmarkLookup10(b *testing.B) {
benchLookupNTable(b, 10, 12) benchLookupNTable(b, 10, 12)
} }