diff --git a/allocator.go b/allocator.go index cbf880e..82641d8 100644 --- a/allocator.go +++ b/allocator.go @@ -37,11 +37,14 @@ func (a *Allocator) Node(nodeAddress NodeAddress) []byte { } // Allocate allocates node and copies data into it. -func (a *Allocator) Allocate(copyFrom []byte) (NodeAddress, []byte) { +func (a *Allocator) Allocate(copyFrom []byte) (NodeAddress, []byte, error) { a.lastAllocatedNode++ + if a.lastAllocatedNode >= NodeAddress(len(a.data)) { + return 0, nil, errors.New("out of space") + } node := a.Node(a.lastAllocatedNode) copy(node, copyFrom) - return a.lastAllocatedNode, node + return a.lastAllocatedNode, node, nil } // Deallocate deallocates node. @@ -76,24 +79,32 @@ type SnapshotAllocator struct { } // Allocate allocates new node. -func (sa SnapshotAllocator) Allocate() (NodeAddress, []byte) { - nodeAddress, node := sa.allocator.Allocate(sa.allocator.zeroNode) +func (sa SnapshotAllocator) Allocate() (NodeAddress, []byte, error) { + nodeAddress, node, err := sa.allocator.Allocate(sa.allocator.zeroNode) + if err != nil { + return 0, nil, err + } + sa.dirtyNodes[nodeAddress] = struct{}{} - return nodeAddress, node + return nodeAddress, node, nil } // Copy allocates new node and copies content from existing one. -func (sa SnapshotAllocator) Copy(data []byte) (NodeAddress, []byte) { - nodeAddress, node := sa.allocator.Allocate(data) +func (sa SnapshotAllocator) Copy(data []byte) (NodeAddress, []byte, error) { + nodeAddress, node, err := sa.allocator.Allocate(data) + if err != nil { + return 0, nil, err + } + sa.dirtyNodes[nodeAddress] = struct{}{} - return nodeAddress, node + return nodeAddress, node, nil } // Deallocate marks node for deallocation. -func (sa SnapshotAllocator) Deallocate(nodeAddress NodeAddress, srcSnapshotID SnapshotID) { +func (sa SnapshotAllocator) Deallocate(nodeAddress NodeAddress, srcSnapshotID SnapshotID) error { if srcSnapshotID == sa.snapshotID { sa.DeallocateImmediately(nodeAddress) - return + return nil } listNodeAddress, _ := sa.deallocationLists.Get(srcSnapshotID) @@ -103,10 +114,16 @@ func (sa SnapshotAllocator) Deallocate(nodeAddress NodeAddress, srcSnapshotID Sn NodeAllocator: sa.listNodeAllocator, Allocator: sa, }) - list.Add(nodeAddress) + if err := list.Add(nodeAddress); err != nil { + return err + } if list.config.Item != listNodeAddress { - sa.deallocationLists.Set(srcSnapshotID, list.config.Item) + if err := sa.deallocationLists.Set(srcSnapshotID, list.config.Item); err != nil { + return err + } } + + return nil } // DeallocateImmediately dealocates node immediately. @@ -171,15 +188,21 @@ func (na SpaceNodeAllocator[T]) Get(nodeAddress NodeAddress) ([]byte, SpaceNode[ } // Allocate allocates new object. -func (na SpaceNodeAllocator[T]) Allocate(allocator SnapshotAllocator) (NodeAddress, SpaceNode[T]) { - n, node := allocator.Allocate() - return n, na.project(node) +func (na SpaceNodeAllocator[T]) Allocate(allocator SnapshotAllocator) (NodeAddress, SpaceNode[T], error) { + n, node, err := allocator.Allocate() + if err != nil { + return 0, SpaceNode[T]{}, err + } + return n, na.project(node), nil } // Copy allocates copy of existing object. -func (na SpaceNodeAllocator[T]) Copy(allocator SnapshotAllocator, data []byte) (NodeAddress, SpaceNode[T]) { - n, node := allocator.Copy(data) - return n, na.project(node) +func (na SpaceNodeAllocator[T]) Copy(allocator SnapshotAllocator, data []byte) (NodeAddress, SpaceNode[T], error) { + n, node, err := allocator.Copy(data) + if err != nil { + return 0, SpaceNode[T]{}, err + } + return n, na.project(node), nil } // Index returns element index based on hash. @@ -237,15 +260,21 @@ func (na ListNodeAllocator) Get(nodeAddress NodeAddress) ([]byte, ListNode) { } // Allocate allocates new object. -func (na ListNodeAllocator) Allocate(allocator SnapshotAllocator) (NodeAddress, ListNode) { - n, node := allocator.Allocate() - return n, na.project(node) +func (na ListNodeAllocator) Allocate(allocator SnapshotAllocator) (NodeAddress, ListNode, error) { + n, node, err := allocator.Allocate() + if err != nil { + return 0, ListNode{}, err + } + return n, na.project(node), nil } // Copy allocates copy of existing object. -func (na ListNodeAllocator) Copy(allocator SnapshotAllocator, data []byte) (NodeAddress, ListNode) { - n, node := allocator.Copy(data) - return n, na.project(node) +func (na ListNodeAllocator) Copy(allocator SnapshotAllocator, data []byte) (NodeAddress, ListNode, error) { + n, node, err := allocator.Copy(data) + if err != nil { + return 0, ListNode{}, err + } + return n, na.project(node), nil } func (na ListNodeAllocator) project(node []byte) ListNode { diff --git a/db.go b/db.go index 515bd14..f1997da 100644 --- a/db.go +++ b/db.go @@ -140,7 +140,9 @@ func (db *DB) DeleteSnapshot(snapshotID SnapshotID) error { NodeAllocator: db.listNodeAllocator, }) list.Deallocate(db.config.Allocator) - nextDeallocationLists.Delete(sID) + if err := nextDeallocationLists.Delete(sID); err != nil { + return err + } } // FIXME (wojciech): Iterate over space instead @@ -154,14 +156,20 @@ func (db *DB) DeleteSnapshot(snapshotID SnapshotID) error { nextList := NewList(ListConfig{ Item: nextListNodeAddress, }) - nextList.Attach(listNodeAddress) + if err := nextList.Attach(listNodeAddress); err != nil { + return err + } if nextList.config.Item != nextListNodeAddress { - nextDeallocationLists.Set(sID, nextList.config.Item) + if err := nextDeallocationLists.Set(sID, nextList.config.Item); err != nil { + return err + } } } nextSnapshotInfo.PreviousSnapshotID = snapshotInfo.PreviousSnapshotID - db.nextSnapshot.Snapshots.Set(snapshotInfo.NextSnapshotID, nextSnapshotInfo) + if err := db.nextSnapshot.Snapshots.Set(snapshotInfo.NextSnapshotID, nextSnapshotInfo); err != nil { + return err + } } if snapshotInfo.PreviousSnapshotID > db.nextSnapshot.SingularityNode.FirstSnapshotID { @@ -170,7 +178,9 @@ func (db *DB) DeleteSnapshot(snapshotID SnapshotID) error { return errors.Errorf("snapshot %d does not exist", snapshotID) } previousSnapshotInfo.NextSnapshotID = snapshotInfo.NextSnapshotID - db.nextSnapshot.Snapshots.Set(snapshotInfo.PreviousSnapshotID, previousSnapshotInfo) + if err := db.nextSnapshot.Snapshots.Set(snapshotInfo.PreviousSnapshotID, previousSnapshotInfo); err != nil { + return err + } } if snapshotID == db.nextSnapshot.SingularityNode.FirstSnapshotID { @@ -195,14 +205,18 @@ func (db *DB) Commit() error { if *spaceToCommit.PInfo.State == stateFree || *spaceToCommit.PInfo.Item == spaceToCommit.OriginalItem { continue } - db.nextSnapshot.Spaces.Set(spaceID, SpaceInfo{ + if err := db.nextSnapshot.Spaces.Set(spaceID, SpaceInfo{ HashMod: *spaceToCommit.HashMod, State: *spaceToCommit.PInfo.State, Node: *spaceToCommit.PInfo.Item, - }) + }); err != nil { + return err + } } - db.nextSnapshot.Snapshots.Set(db.nextSnapshot.SnapshotID, *db.nextSnapshot.SnapshotInfo) + if err := db.nextSnapshot.Snapshots.Set(db.nextSnapshot.SnapshotID, *db.nextSnapshot.SnapshotInfo); err != nil { + return err + } *photon.FromBytes[SingularityNode](db.config.Allocator.Node(0)) = *db.nextSnapshot.SingularityNode diff --git a/db_test.go b/db_test.go index ab798fe..c34b472 100644 --- a/db_test.go +++ b/db_test.go @@ -24,13 +24,15 @@ var collisions = [][]int{ } func newDB(t *testing.T) *DB { + requireT := require.New(t) + db, err := New(Config{ Allocator: NewAllocator(AllocatorConfig{ TotalSize: 10 * 1024 * 1024, NodeSize: 512, }), }) - require.NoError(t, err) + requireT.NoError(err) return db } @@ -47,43 +49,49 @@ func TestCollisions(t *testing.T) { } func TestSet(t *testing.T) { + requireT := require.New(t) + db := newDB(t) space, err := GetSpace[int, int](spaceID, db) - require.NoError(t, err) + requireT.NoError(err) for i := range 10 { - space.Set(i, i) + requireT.NoError(space.Set(i, i)) } - require.Equal(t, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, collect(space)) + requireT.Equal([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, collect(space)) } func TestSetCollisions(t *testing.T) { + requireT := require.New(t) + db := newDB(t) space, err := GetSpace[int, int](spaceID, db) - require.NoError(t, err) + requireT.NoError(err) allValues := make([]int, 0, len(collisions)*len(collisions[0])) for _, set := range collisions { for _, i := range set { allValues = append(allValues, i) - space.Set(i, i) + requireT.NoError(space.Set(i, i)) } } sort.Ints(allValues) - require.Equal(t, allValues, collect(space)) + requireT.Equal(allValues, collect(space)) } func TestGetCollisions(t *testing.T) { + requireT := require.New(t) + db := newDB(t) space, err := GetSpace[int, int](spaceID, db) - require.NoError(t, err) + requireT.NoError(err) inserted := make([]int, 0, len(collisions)*len(collisions[0])) read := make([]int, 0, len(collisions)*len(collisions[0])) @@ -91,7 +99,7 @@ func TestGetCollisions(t *testing.T) { for _, set := range collisions { for _, i := range set { inserted = append(inserted, i) - space.Set(i, i) + requireT.NoError(space.Set(i, i)) } } @@ -106,7 +114,7 @@ func TestGetCollisions(t *testing.T) { sort.Ints(inserted) sort.Ints(read) - require.Equal(t, inserted, read) + requireT.Equal(inserted, read) } func TestSetOnNext(t *testing.T) { @@ -118,7 +126,7 @@ func TestSetOnNext(t *testing.T) { requireT.NoError(err) for i := range 10 { - space1.Set(i, i) + requireT.NoError(space1.Set(i, i)) } requireT.NoError(db.Commit()) @@ -127,7 +135,7 @@ func TestSetOnNext(t *testing.T) { requireT.NoError(err) for i := range 5 { - space2.Set(i, i+10) + requireT.NoError(space2.Set(i, i+10)) } requireT.Equal([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, collect(space1)) @@ -143,7 +151,7 @@ func TestGet(t *testing.T) { requireT.NoError(err) for i := range 10 { - space.Set(i, i) + requireT.NoError(space.Set(i, i)) } for i := range 10 { v, exists := space.Get(i) @@ -161,7 +169,7 @@ func TestReplace(t *testing.T) { requireT.NoError(err) for i := range 10 { - space1.Set(i, i) + requireT.NoError(space1.Set(i, i)) } requireT.NoError(db.Commit()) @@ -170,7 +178,7 @@ func TestReplace(t *testing.T) { requireT.NoError(err) for i, j := 0, 10; i < 5; i, j = i+1, j+1 { - space2.Set(i, j) + requireT.NoError(space2.Set(i, j)) } for i := range 10 { diff --git a/list.go b/list.go index dbc088a..bd85eef 100644 --- a/list.go +++ b/list.go @@ -21,20 +21,26 @@ type List struct { } // Add adds address to the list. -func (l *List) Add(nodeAddress NodeAddress) { +func (l *List) Add(nodeAddress NodeAddress) error { if l.config.Item == 0 { - newNodeAddress, newNode := l.config.NodeAllocator.Allocate(l.config.Allocator) + newNodeAddress, newNode, err := l.config.NodeAllocator.Allocate(l.config.Allocator) + if err != nil { + return err + } newNode.Items[0] = nodeAddress newNode.Header.SnapshotID = l.config.SnapshotID newNode.Header.NumOfItems = 1 l.config.Item = newNodeAddress - return + return nil } listNodeData, listNode := l.config.NodeAllocator.Get(l.config.Item) if listNode.Header.NumOfItems+listNode.Header.NumOfSideLists < uint64(len(listNode.Items)) { if listNode.Header.SnapshotID < l.config.SnapshotID { - newNodeAddress, newNode := l.config.NodeAllocator.Copy(l.config.Allocator, listNodeData) + newNodeAddress, newNode, err := l.config.NodeAllocator.Copy(l.config.Allocator, listNodeData) + if err != nil { + return err + } newNode.Header.SnapshotID = l.config.SnapshotID oldNodeAddress := l.config.Item l.config.Item = newNodeAddress @@ -45,28 +51,36 @@ func (l *List) Add(nodeAddress NodeAddress) { listNode.Items[listNode.Header.NumOfItems] = nodeAddress listNode.Header.NumOfItems++ - return + return nil } - newNodeAddress, newNode := l.config.NodeAllocator.Allocate(l.config.Allocator) + newNodeAddress, newNode, err := l.config.NodeAllocator.Allocate(l.config.Allocator) + if err != nil { + return err + } newNode.Items[0] = nodeAddress newNode.Items[len(newNode.Items)-1] = l.config.Item newNode.Header.SnapshotID = l.config.SnapshotID newNode.Header.NumOfItems = 1 newNode.Header.NumOfSideLists = 1 l.config.Item = newNodeAddress + + return nil } // Attach attaches another list. -func (l *List) Attach(nodeAddress NodeAddress) { +func (l *List) Attach(nodeAddress NodeAddress) error { if l.config.Item == 0 { l.config.Item = nodeAddress - return + return nil } listNodeData, listNode := l.config.NodeAllocator.Get(l.config.Item) if listNode.Header.NumOfItems+listNode.Header.NumOfSideLists < uint64(len(listNode.Items)) { if listNode.Header.SnapshotID < l.config.SnapshotID { - newNodeAddress, newNode := l.config.NodeAllocator.Copy(l.config.Allocator, listNodeData) + newNodeAddress, newNode, err := l.config.NodeAllocator.Copy(l.config.Allocator, listNodeData) + if err != nil { + return err + } newNode.Header.SnapshotID = l.config.SnapshotID oldNodeAddress := l.config.Item l.config.Item = newNodeAddress @@ -77,15 +91,20 @@ func (l *List) Attach(nodeAddress NodeAddress) { listNode.Items[uint64(len(listNode.Items))-listNode.Header.NumOfSideLists-1] = nodeAddress listNode.Header.NumOfSideLists++ - return + return nil } - newNodeAddress, newNode := l.config.NodeAllocator.Allocate(l.config.Allocator) + newNodeAddress, newNode, err := l.config.NodeAllocator.Allocate(l.config.Allocator) + if err != nil { + return err + } newNode.Items[uint64(len(listNode.Items))-1] = l.config.Item newNode.Items[uint64(len(listNode.Items))-2] = nodeAddress newNode.Header.SnapshotID = l.config.SnapshotID newNode.Header.NumOfSideLists = 2 l.config.Item = newNodeAddress + + return nil } // Deallocate deallocates nodes referenced by the list. diff --git a/space.go b/space.go index 7ec171a..2e45859 100644 --- a/space.go +++ b/space.go @@ -73,8 +73,8 @@ func (s *Space[K, V]) Get(key K) (V, bool) { } // Set sets the value for the key. -func (s *Space[K, V]) Set(key K, value V) { - s.set(s.config.SpaceRoot, DataItem[K, V]{ +func (s *Space[K, V]) Set(key K, value V) error { + return s.set(s.config.SpaceRoot, DataItem[K, V]{ Hash: hashKey(key, 0), Key: key, Value: value, @@ -82,24 +82,29 @@ func (s *Space[K, V]) Set(key K, value V) { } // Delete deletes the key from space. -func (s *Space[K, V]) Delete(key K) { +func (s *Space[K, V]) Delete(key K) error { h := hashKey(key, 0) pInfo := s.config.SpaceRoot for { switch *pInfo.State { case stateFree: - return + return nil case stateData: // FIXME (wojciech): Don't copy the node if split is required. dataNodeData, dataNode := s.config.DataNodeAllocator.Get(*pInfo.Item) if dataNode.Header.SnapshotID < s.config.SnapshotID { - newNodeAddress, newNode := s.config.DataNodeAllocator.Copy(s.config.Allocator, dataNodeData) + newNodeAddress, newNode, err := s.config.DataNodeAllocator.Copy(s.config.Allocator, dataNodeData) + if err != nil { + return err + } newNode.Header.SnapshotID = s.config.SnapshotID oldNodeAddress := *pInfo.Item *pInfo.Item = newNodeAddress - s.config.Allocator.Deallocate(oldNodeAddress, dataNode.Header.SnapshotID) + if err := s.config.Allocator.Deallocate(oldNodeAddress, dataNode.Header.SnapshotID); err != nil { + return err + } dataNode = newNode } if dataNode.Header.HashMod > 0 { @@ -111,15 +116,20 @@ func (s *Space[K, V]) Delete(key K) { dataNode.States[index] = stateFree } - return + return nil default: pointerNodeData, pointerNode := s.config.PointerNodeAllocator.Get(*pInfo.Item) if pointerNode.Header.SnapshotID < s.config.SnapshotID { - newNodeAddress, newNode := s.config.PointerNodeAllocator.Copy(s.config.Allocator, pointerNodeData) + newNodeAddress, newNode, err := s.config.PointerNodeAllocator.Copy(s.config.Allocator, pointerNodeData) + if err != nil { + return err + } newNode.Header.SnapshotID = s.config.SnapshotID oldNodeAddress := *pInfo.Item *pInfo.Item = newNodeAddress - s.config.Allocator.Deallocate(oldNodeAddress, pointerNode.Header.SnapshotID) + if err := s.config.Allocator.Deallocate(oldNodeAddress, pointerNode.Header.SnapshotID); err != nil { + return err + } pointerNode = newNode } if pointerNode.Header.HashMod > 0 { @@ -137,11 +147,14 @@ func (s *Space[K, V]) Delete(key K) { } } -func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) { +func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) error { for { switch *pInfo.State { case stateFree: - dataNodeAddress, dataNode := s.config.DataNodeAllocator.Allocate(s.config.Allocator) + dataNodeAddress, dataNode, err := s.config.DataNodeAllocator.Allocate(s.config.Allocator) + if err != nil { + return err + } dataNode.Header.SnapshotID = s.config.SnapshotID *pInfo.State = stateData @@ -151,17 +164,22 @@ func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) { dataNode.States[index] = stateData dataNode.Items[index] = item - return + return nil case stateData: // FIXME (wojciech): Don't copy the node if split is required. dataNodeData, dataNode := s.config.DataNodeAllocator.Get(*pInfo.Item) if dataNode.Header.SnapshotID < s.config.SnapshotID { - newNodeAddress, newNode := s.config.DataNodeAllocator.Copy(s.config.Allocator, dataNodeData) + newNodeAddress, newNode, err := s.config.DataNodeAllocator.Copy(s.config.Allocator, dataNodeData) + if err != nil { + return err + } newNode.Header.SnapshotID = s.config.SnapshotID oldNodeAddress := *pInfo.Item *pInfo.Item = newNodeAddress - s.config.Allocator.Deallocate(oldNodeAddress, dataNode.Header.SnapshotID) + if err := s.config.Allocator.Deallocate(oldNodeAddress, dataNode.Header.SnapshotID); err != nil { + return err + } dataNode = newNode } if dataNode.Header.HashMod > 0 { @@ -173,14 +191,14 @@ func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) { dataNode.States[index] = stateData dataNode.Items[index] = item - return + return nil } if item.Hash == dataNode.Items[index].Hash { if item.Key == dataNode.Items[index].Key { dataNode.Items[index] = item - return + return nil } // hash conflict @@ -188,18 +206,23 @@ func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) { dataNode.Header.HashMod = *s.config.HashMod } - s.redistributeNode(pInfo) - s.set(pInfo, item) - - return + if err := s.redistributeNode(pInfo); err != nil { + return err + } + return s.set(pInfo, item) default: pointerNodeData, pointerNode := s.config.PointerNodeAllocator.Get(*pInfo.Item) if pointerNode.Header.SnapshotID < s.config.SnapshotID { - newNodeAddress, newNode := s.config.PointerNodeAllocator.Copy(s.config.Allocator, pointerNodeData) + newNodeAddress, newNode, err := s.config.PointerNodeAllocator.Copy(s.config.Allocator, pointerNodeData) + if err != nil { + return err + } newNode.Header.SnapshotID = s.config.SnapshotID oldNodeAddress := *pInfo.Item *pInfo.Item = newNodeAddress - s.config.Allocator.Deallocate(oldNodeAddress, pointerNode.Header.SnapshotID) + if err := s.config.Allocator.Deallocate(oldNodeAddress, pointerNode.Header.SnapshotID); err != nil { + return err + } pointerNode = newNode } if pointerNode.Header.HashMod > 0 { @@ -217,11 +240,14 @@ func (s *Space[K, V]) set(pInfo ParentInfo, item DataItem[K, V]) { } } -func (s *Space[K, V]) redistributeNode(pInfo ParentInfo) { +func (s *Space[K, V]) redistributeNode(pInfo ParentInfo) error { dataNodeAddress := *pInfo.Item _, dataNode := s.config.DataNodeAllocator.Get(dataNodeAddress) - pointerNodeAddress, pointerNode := s.config.PointerNodeAllocator.Allocate(s.config.Allocator) + pointerNodeAddress, pointerNode, err := s.config.PointerNodeAllocator.Allocate(s.config.Allocator) + if err != nil { + return err + } *pointerNode.Header = SpaceNodeHeader{ SnapshotID: s.config.SnapshotID, HashMod: dataNode.Header.HashMod, @@ -235,10 +261,12 @@ func (s *Space[K, V]) redistributeNode(pInfo ParentInfo) { continue } - s.set(pInfo, dataNode.Items[i]) + if err := s.set(pInfo, dataNode.Items[i]); err != nil { + return err + } } - s.config.Allocator.Deallocate(dataNodeAddress, dataNode.Header.SnapshotID) + return s.config.Allocator.Deallocate(dataNodeAddress, dataNode.Header.SnapshotID) } func hashKey[K comparable](key K, hashMod uint64) Hash {