Skip to content

Commit

Permalink
arc: first cut of the blobStore
Browse files Browse the repository at this point in the history
  • Loading branch information
toru committed Nov 20, 2024
1 parent d07b1ac commit cf5f7ca
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 0 deletions.
74 changes: 74 additions & 0 deletions blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright Chrono Technologies LLC
// SPDX-License-Identifier: MIT

package arc

import "crypto/sha256"

const (
// Length of the blobID in bytes.
blobIDLen = 32
)

// blobID is a 32-byte fixed-length byte array representing the SHA-256 hash of
// a blob value. It is an array and not a slice for map key compatibility.
type blobID [blobIDLen]byte

// Slice returns the given blobID as a byte slice.
func (id blobID) Slice() []byte {
return id[:]
}

// blob represents the blob value and its reference count.
type blob struct {
value []byte
refCount int
}

// blobStore maps blobIDs to their corresponding blobs. It is used to store
// values that exceed the 32-byte value length threshold.
type blobStore map[blobID]*blob

// get returns the blob that matches the blobID.
func (bs blobStore) get(id blobID) []byte {
b, found := bs[id]

if !found {
return nil
}

// Create a copy of the value since returning a pointer to the underlying
// value can have serious implications, such as breaking data integrity.
ret := make([]byte, len(b.value))
copy(ret, b.value)

return ret
}

// put either creates a new blob and inserts it to the blobStore or increments
// the refCount of an existing blob. It returns a blobID on success.
func (bs blobStore) put(value []byte) blobID {
k := blobID(sha256.Sum256(value))

if b, found := bs[k]; found {
b.refCount++
} else {
bs[k] = &blob{value: value, refCount: 1}
}

return k
}

// release decrements the refCount of a blob if it exists for the given blobID.
// When the refCount reaches zero, the blob is removed from the blobStore.
func (bs blobStore) release(id blobID) {
if b, found := bs[id]; found {
if b.refCount > 0 {
b.refCount--
}

if b.refCount == 0 {
delete(bs, id)
}
}
}
77 changes: 77 additions & 0 deletions blob_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright Chrono Technologies LLC
// SPDX-License-Identifier: MIT

package arc

import (
"bytes"
"crypto/sha256"
"testing"
)

func TestBlobStorePut(t *testing.T) {
store := blobStore{}

tests := []struct {
value []byte
expectedBlobID blobID
expectedRefCount int
}{
{[]byte("apple"), sha256.Sum256([]byte("apple")), 1},
{[]byte("apple"), sha256.Sum256([]byte("apple")), 2},
{[]byte("apple"), sha256.Sum256([]byte("apple")), 3},
}

for _, test := range tests {
blobID := store.put(test.value)

if !bytes.Equal(blobID.Slice(), test.expectedBlobID.Slice()) {
t.Errorf("unexpected blobID: got:%q, want:%q", blobID, test.expectedBlobID)
}

value := store.get(blobID)

if !bytes.Equal(value, test.value) {
t.Errorf("unexpected blob: got:%q, want:%q", value, test.value)
}

if got := store[blobID].refCount; got != test.expectedRefCount {
t.Errorf("unexpected refCount: got:%d, want:%d", got, test.expectedRefCount)
}
}
}

func TestBlobStoreRelease(t *testing.T) {
store := blobStore{}
value := []byte("pineapple")
refCount := 20

var blobID blobID

for i := 0; i < refCount; i++ {
blobID = store.put(value)
}

for i := refCount; i > 0; i-- {
store.release(blobID)

expectedRefCount := i - 1

if expectedRefCount == 0 {
if _, found := store[blobID]; found {
t.Error("expected blob to be removed")
}
} else {
if store[blobID].refCount != expectedRefCount {
t.Errorf("unexpected refCount: got:%d, want:%d", store[blobID].refCount, expectedRefCount)
}
}
}

// Test that the store does not panic with an unknown key.
store.release(sha256.Sum256([]byte("bogus")))

if len(store) != 0 {
t.Error("store should be empty")
}
}
3 changes: 3 additions & 0 deletions debug.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright Chrono Technologies LLC
// SPDX-License-Identifier: MIT

package arc

import "fmt"
Expand Down

0 comments on commit cf5f7ca

Please sign in to comment.