diff --git a/alloc/test.go b/alloc/test.go index d2cf2c8..97353b9 100644 --- a/alloc/test.go +++ b/alloc/test.go @@ -10,7 +10,6 @@ import ( func NewTestAllocator(parentAllocator types.Allocator) *TestAllocator { return &TestAllocator{ parentAllocator: parentAllocator, - nodesAccessed: map[types.NodeAddress]struct{}{}, nodesAllocated: map[types.NodeAddress]struct{}{}, nodesDeallocated: map[types.NodeAddress]struct{}{}, } @@ -20,14 +19,12 @@ func NewTestAllocator(parentAllocator types.Allocator) *TestAllocator { type TestAllocator struct { parentAllocator types.Allocator - nodesAccessed map[types.NodeAddress]struct{} nodesAllocated map[types.NodeAddress]struct{} nodesDeallocated map[types.NodeAddress]struct{} } // Node returns node bytes. func (a *TestAllocator) Node(nodeAddress types.NodeAddress) []byte { - a.nodesAccessed[nodeAddress] = struct{}{} return a.parentAllocator.Node(nodeAddress) } @@ -54,11 +51,10 @@ func (a *TestAllocator) NodeSize() uint64 { // Nodes returns touched nodes. func (a *TestAllocator) Nodes() ( - accessed []types.NodeAddress, allocated []types.NodeAddress, deallocated []types.NodeAddress, ) { - return mapToSlice(a.nodesAccessed), mapToSlice(a.nodesAllocated), mapToSlice(a.nodesDeallocated) + return mapToSlice(a.nodesAllocated), mapToSlice(a.nodesDeallocated) } func mapToSlice(m map[types.NodeAddress]struct{}) []types.NodeAddress { diff --git a/db.go b/db.go index ddd2e1b..cd2d946 100644 --- a/db.go +++ b/db.go @@ -155,23 +155,17 @@ func (db *DB) DeleteSnapshot(snapshotID types.SnapshotID) error { } } - // FIXME (wojciech): Iterate over space instead - for sID := db.nextSnapshot.SingularityNode.FirstSnapshotID; sID < snapshotID; sID++ { - listNodeAddress, exists := deallocationLists.Get(sID) - if !exists { - continue - } - - nextListNodeAddress, _ := nextDeallocationLists.Get(sID) + for snapshotItem := range deallocationLists.Iterator() { + nextListNodeAddress, _ := nextDeallocationLists.Get(snapshotItem.Key) newNextListNodeAddress := nextListNodeAddress nextList := list.New(list.Config{ Item: &newNextListNodeAddress, }) - if err := nextList.Attach(listNodeAddress); err != nil { + if err := nextList.Attach(snapshotItem.Value); err != nil { return err } if newNextListNodeAddress != nextListNodeAddress { - if err := nextDeallocationLists.Set(sID, newNextListNodeAddress); err != nil { + if err := nextDeallocationLists.Set(snapshotItem.Key, newNextListNodeAddress); err != nil { return err } } diff --git a/go.mod b/go.mod index 58bf0ac..58c8d20 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/samber/lo v1.47.0 github.com/stretchr/testify v1.9.0 + golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 ) require ( diff --git a/go.sum b/go.sum index df40b26..1fe8d98 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/list/list.go b/list/list.go index 42706fa..bcd2e8d 100644 --- a/list/list.go +++ b/list/list.go @@ -1,6 +1,10 @@ package list -import "github.com/outofforest/quantum/types" +import ( + "sort" + + "github.com/outofforest/quantum/types" +) // Config stores list configuration. type Config struct { @@ -134,3 +138,58 @@ func (l *List) Deallocate(allocator types.Allocator) { allocator.Deallocate(n) } } + +// Iterator iterates over items in the list. +func (l *List) Iterator() func(func(types.NodeAddress) bool) { + return func(yield func(types.NodeAddress) bool) { + if *l.config.Item == 0 { + return + } + + stack := []types.NodeAddress{*l.config.Item} + for { + if len(stack) == 0 { + return + } + + n := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + _, listNode := l.config.NodeAllocator.Get(n) + for i := range listNode.Header.NumOfItems { + if !yield(listNode.Items[i]) { + return + } + } + + stack = append(stack, listNode.Items[uint64(len(listNode.Items))-listNode.Header.NumOfSideLists:]...) + } + } +} + +// Nodes returns list of nodes used by the list. +func (l *List) Nodes() []types.NodeAddress { + if *l.config.Item == 0 { + return nil + } + + nodes := []types.NodeAddress{} + stack := []types.NodeAddress{*l.config.Item} + + for { + if len(stack) == 0 { + sort.Slice(nodes, func(i, j int) bool { + return nodes[i] < nodes[j] + }) + + return nodes + } + + n := stack[len(stack)-1] + stack = stack[:len(stack)-1] + nodes = append(nodes, n) + + _, listNode := l.config.NodeAllocator.Get(n) + stack = append(stack, listNode.Items[uint64(len(listNode.Items))-listNode.Header.NumOfSideLists:]...) + } +} diff --git a/list/list_test.go b/list/list_test.go new file mode 100644 index 0000000..5489985 --- /dev/null +++ b/list/list_test.go @@ -0,0 +1,58 @@ +package list_test + +import ( + "github.com/samber/lo" + "github.com/stretchr/testify/require" + + "github.com/outofforest/quantum/alloc" + "github.com/outofforest/quantum/list" + "github.com/outofforest/quantum/space" + "github.com/outofforest/quantum/types" +) + +//nolint:unused +func newEnv(requireT *require.Assertions) *env { + allocator := alloc.NewTestAllocator(alloc.NewAllocator(alloc.Config{ + TotalSize: 1024 * 1024, + NodeSize: 512, + })) + + nodeAllocator, err := list.NewNodeAllocator(allocator) + requireT.NoError(err) + + return &env{ + Allocator: allocator, + Item: lo.ToPtr[types.NodeAddress](0), + nodeAllocator: nodeAllocator, + } +} + +//nolint:unused +type env struct { + Allocator *alloc.TestAllocator + Item *types.NodeAddress + + snapshotID types.SnapshotID + nodeAllocator list.NodeAllocator +} + +//nolint:unused +func (e *env) NextSnapshot() *list.List { + snapshotID := e.snapshotID + e.snapshotID++ + + itemCopy := *e.Item + e.Item = &itemCopy + + return list.New(list.Config{ + SnapshotID: snapshotID, + Item: e.Item, + NodeAllocator: e.nodeAllocator, + Allocator: alloc.NewSnapshotAllocator( + snapshotID, + e.Allocator, + &space.Space[types.SnapshotID, types.NodeAddress]{}, + e.nodeAllocator, + ), + }) +} diff --git a/space/space.go b/space/space.go index de9e0a0..af2768c 100644 --- a/space/space.go +++ b/space/space.go @@ -1,6 +1,8 @@ package space import ( + "sort" + "github.com/cespare/xxhash" "github.com/outofforest/photon" @@ -191,6 +193,45 @@ func (s *Space[K, V]) Iterator() func(func(types.DataItem[K, V]) bool) { } } +// Nodes returns list of nodes used by the space. +func (s *Space[K, V]) Nodes() []types.NodeAddress { + switch *s.config.SpaceRoot.State { + case types.StateFree: + return nil + case types.StateData: + return []types.NodeAddress{*s.config.SpaceRoot.Item} + case types.StatePointer: + } + + nodes := []types.NodeAddress{} + stack := []types.NodeAddress{*s.config.SpaceRoot.Item} + + for { + if len(stack) == 0 { + sort.Slice(nodes, func(i, j int) bool { + return nodes[i] < nodes[j] + }) + + return nodes + } + + n := stack[len(stack)-1] + stack = stack[:len(stack)-1] + nodes = append(nodes, n) + + _, pointerNode := s.config.PointerNodeAllocator.Get(n) + for i, state := range pointerNode.States { + switch state { + case types.StateFree: + case types.StateData: + nodes = append(nodes, pointerNode.Items[i]) + case types.StatePointer: + stack = append(stack, pointerNode.Items[i]) + } + } + } +} + func (s *Space[K, V]) set(pInfo types.ParentInfo, item types.DataItem[K, V]) error { for { switch *pInfo.State { diff --git a/space/space_test.go b/space/space_test.go index 08e0ce8..4f0a4b6 100644 --- a/space/space_test.go +++ b/space/space_test.go @@ -6,6 +6,7 @@ import ( "github.com/samber/lo" "github.com/stretchr/testify/require" + "golang.org/x/exp/constraints" "github.com/outofforest/quantum/alloc" "github.com/outofforest/quantum/list" @@ -20,13 +21,14 @@ func TestSetOneItem(t *testing.T) { s := e.NextSnapshot() requireT.NoError(s.Set(0, 0)) - nodesAccessed, nodesAllocated, nodesDeallocated := e.Allocator.Nodes() + nodesAllocated, nodesDeallocated := e.Allocator.Nodes() - requireT.Empty(nodesAccessed) requireT.Equal([]types.NodeAddress{0x01}, nodesAllocated) requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x01}, s.Nodes()) + requireT.Empty(e.DeallocationLists.Nodes()) - requireT.Equal([]int{0}, collect(s)) + requireT.Equal([]int{0}, collectSpaceValues(s)) } func TestSetTwoItems(t *testing.T) { @@ -37,13 +39,14 @@ func TestSetTwoItems(t *testing.T) { requireT.NoError(s.Set(0, 0)) requireT.NoError(s.Set(1, 1)) - nodesAccessed, nodesAllocated, nodesDeallocated := e.Allocator.Nodes() + nodesAllocated, nodesDeallocated := e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x01}, nodesAccessed) requireT.Equal([]types.NodeAddress{0x01}, nodesAllocated) requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x01}, s.Nodes()) + requireT.Empty(e.DeallocationLists.Nodes()) - requireT.Equal([]int{0, 1}, collect(s)) + requireT.Equal([]int{0, 1}, collectSpaceValues(s)) } func TestSetWithPointerNode(t *testing.T) { @@ -54,57 +57,61 @@ func TestSetWithPointerNode(t *testing.T) { // Insert 0 requireT.NoError(s.Set(0, 0)) - nodesAccessed, nodesAllocated, nodesDeallocated := e.Allocator.Nodes() + nodesAllocated, nodesDeallocated := e.Allocator.Nodes() - requireT.Empty(nodesAccessed) requireT.Equal([]types.NodeAddress{0x01}, nodesAllocated) requireT.Empty(nodesDeallocated) + requireT.Empty(e.DeallocationLists.Nodes()) - requireT.Equal([]int{0}, collect(s)) + requireT.Equal([]int{0}, collectSpaceValues(s)) // Insert 1 requireT.NoError(s.Set(1, 1)) - nodesAccessed, nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x01}, nodesAccessed) requireT.Empty(nodesAllocated) requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x01}, s.Nodes()) + requireT.Empty(e.DeallocationLists.Nodes()) - requireT.Equal([]int{0, 1}, collect(s)) + requireT.Equal([]int{0, 1}, collectSpaceValues(s)) // Insert 2 requireT.NoError(s.Set(2, 2)) - nodesAccessed, nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x01}, nodesAccessed) requireT.Empty(nodesAllocated) requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x01}, s.Nodes()) + requireT.Empty(e.DeallocationLists.Nodes()) - requireT.Equal([]int{0, 1, 2}, collect(s)) + requireT.Equal([]int{0, 1, 2}, collectSpaceValues(s)) // Insert 3 requireT.NoError(s.Set(3, 3)) - nodesAccessed, nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x01}, nodesAccessed) requireT.Empty(nodesAllocated) requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x01}, s.Nodes()) + requireT.Empty(e.DeallocationLists.Nodes()) - requireT.Equal([]int{0, 1, 2, 3}, collect(s)) + requireT.Equal([]int{0, 1, 2, 3}, collectSpaceValues(s)) // Insert 4 requireT.NoError(s.Set(4, 4)) - nodesAccessed, nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x01, 0x02}, nodesAccessed) requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, nodesAllocated) requireT.Equal([]types.NodeAddress{0x01}, nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, s.Nodes()) + requireT.Empty(e.DeallocationLists.Nodes()) - requireT.Equal([]int{0, 1, 2, 3, 4}, collect(s)) + requireT.Equal([]int{0, 1, 2, 3, 4}, collectSpaceValues(s)) } func TestSet(t *testing.T) { @@ -118,7 +125,7 @@ func TestSet(t *testing.T) { requireT.NoError(s.Set(i, i)) } - requireT.Equal(items, collect(s)) + requireT.Equal(items, collectSpaceValues(s)) } func TestGet(t *testing.T) { @@ -189,8 +196,8 @@ func TestSetOnNext(t *testing.T) { requireT.NoError(s2.Set(i, i+10)) } - requireT.Equal([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, collect(s1)) - requireT.Equal([]int{5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, collect(s2)) + requireT.Equal([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, collectSpaceValues(s1)) + requireT.Equal([]int{5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, collectSpaceValues(s2)) } func TestReplace(t *testing.T) { @@ -232,70 +239,187 @@ func TestCopyOnSet(t *testing.T) { requireT := require.New(t) e := newEnv(requireT) + s0 := e.NextSnapshot() + + for i := range 5 { + requireT.NoError(s0.Set(i, i)) + } + + nodesAllocated, nodesDeallocated := e.Allocator.Nodes() + requireT.Equal([]types.NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, nodesAllocated) + requireT.Equal([]types.NodeAddress{0x01}, nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, s0.Nodes()) + requireT.Empty(e.DeallocationLists.Nodes()) + s1 := e.NextSnapshot() for i := range 5 { - requireT.NoError(s1.Set(i, i)) + requireT.NoError(s1.Set(i, i+10)) } - nodesAccessed1, nodesAllocated1, nodesDeallocated1 := e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x01, 0x02}, nodesAccessed1) - requireT.Equal([]types.NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, nodesAllocated1) - requireT.Equal([]types.NodeAddress{0x01}, nodesDeallocated1) + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + requireT.Equal([]types.NodeAddress{0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, nodesAllocated) + requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, s0.Nodes()) + requireT.Equal([]types.NodeAddress{0x08, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, s1.Nodes()) + + requireT.Equal([]types.SnapshotID{0x00}, collectSpaceKeys(e.DeallocationLists)) + requireT.Equal([]types.NodeAddress{0x0a}, e.DeallocationLists.Nodes()) + + dList0 := e.DeallocationList(0x00) + requireT.Equal([]types.NodeAddress{0x09}, dList0.Nodes()) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, collectListItems(dList0)) s2 := e.NextSnapshot() for i := range 5 { - requireT.NoError(s2.Set(i, i+10)) + requireT.NoError(s2.Set(i, i+20)) } - nodesAccessed2, nodesAllocated2, nodesDeallocated2 := e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}, nodesAccessed2) - requireT.Equal([]types.NodeAddress{0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, nodesAllocated2) - requireT.Empty(nodesDeallocated2) + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + requireT.Equal([]types.NodeAddress{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}, nodesAllocated) + requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, s0.Nodes()) + requireT.Equal([]types.NodeAddress{0x08, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, s1.Nodes()) + requireT.Equal([]types.NodeAddress{0x10, 0x13, 0x14, 0x15, 0x16, 0x17}, s2.Nodes()) + + requireT.Equal([]types.SnapshotID{0x01}, collectSpaceKeys(e.DeallocationLists)) + requireT.Equal([]types.NodeAddress{0x12}, e.DeallocationLists.Nodes()) + + dList1 := e.DeallocationList(0x01) + requireT.Equal([]types.NodeAddress{0x11}, dList1.Nodes()) + requireT.Equal([]types.NodeAddress{0x08, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, collectListItems(dList1)) + + // Partial copy s3 := e.NextSnapshot() + for i := range 2 { + requireT.NoError(s3.Set(i, i+30)) + } + + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + requireT.Equal([]types.NodeAddress{0x18, 0x19, 0x1a, 0x1b, 0x1c}, nodesAllocated) + requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, s0.Nodes()) + requireT.Equal([]types.NodeAddress{0x08, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, s1.Nodes()) + requireT.Equal([]types.NodeAddress{0x10, 0x13, 0x14, 0x15, 0x16, 0x17}, s2.Nodes()) + requireT.Equal([]types.NodeAddress{0x15, 0x16, 0x17, 0x18, 0x1b, 0x1c}, s3.Nodes()) + + requireT.Equal([]types.SnapshotID{0x02}, collectSpaceKeys(e.DeallocationLists)) + requireT.Equal([]types.NodeAddress{0x1a}, e.DeallocationLists.Nodes()) + + dList2 := e.DeallocationList(0x02) + requireT.Equal([]types.NodeAddress{0x19}, dList2.Nodes()) + requireT.Equal([]types.NodeAddress{0x10, 0x13, 0x14}, collectListItems(dList2)) + + // Overwrite everything to create two deallocation lists. + + s4 := e.NextSnapshot() + for i := range 5 { - requireT.NoError(s3.Set(i, i+20)) + requireT.NoError(s4.Set(i, i+40)) } - nodesAccessed3, nodesAllocated3, nodesDeallocated3 := e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x08, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12}, nodesAccessed3) - requireT.Equal([]types.NodeAddress{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}, nodesAllocated3) - requireT.Empty(nodesDeallocated3) + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + requireT.Equal([]types.NodeAddress{0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25}, nodesAllocated) + requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, s0.Nodes()) + requireT.Equal([]types.NodeAddress{0x08, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, s1.Nodes()) + requireT.Equal([]types.NodeAddress{0x10, 0x13, 0x14, 0x15, 0x16, 0x17}, s2.Nodes()) + requireT.Equal([]types.NodeAddress{0x15, 0x16, 0x17, 0x18, 0x1b, 0x1c}, s3.Nodes()) + requireT.Equal([]types.NodeAddress{0x1d, 0x20, 0x21, 0x22, 0x24, 0x25}, s4.Nodes()) + + requireT.Equal([]types.SnapshotID{0x02, 0x03}, collectSpaceKeys(e.DeallocationLists)) + requireT.Equal([]types.NodeAddress{0x1f}, e.DeallocationLists.Nodes()) + + dList2 = e.DeallocationList(0x02) + requireT.Equal([]types.NodeAddress{0x23}, dList2.Nodes()) + requireT.Equal([]types.NodeAddress{0x15, 0x16, 0x17}, collectListItems(dList2)) + + dList3 := e.DeallocationList(0x03) + requireT.Equal([]types.NodeAddress{0x1e}, dList3.Nodes()) + requireT.Equal([]types.NodeAddress{0x18, 0x1b, 0x1c}, collectListItems(dList3)) + + // Check all the values again + + requireT.Equal([]int{0, 1, 2, 3, 4}, collectSpaceValues(s0)) + requireT.Equal([]int{10, 11, 12, 13, 14}, collectSpaceValues(s1)) + requireT.Equal([]int{20, 21, 22, 23, 24}, collectSpaceValues(s2)) + requireT.Equal([]int{22, 23, 24, 30, 31}, collectSpaceValues(s3)) + requireT.Equal([]int{40, 41, 42, 43, 44}, collectSpaceValues(s4)) } func TestCopyOnDelete(t *testing.T) { requireT := require.New(t) e := newEnv(requireT) - s1 := e.NextSnapshot() + s0 := e.NextSnapshot() for i := range 5 { - requireT.NoError(s1.Set(i, i)) + requireT.NoError(s0.Set(i, i)) } - nodesAccessed1, nodesAllocated1, nodesDeallocated1 := e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x01, 0x02}, nodesAccessed1) - requireT.Equal([]types.NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, nodesAllocated1) - requireT.Equal([]types.NodeAddress{0x01}, nodesDeallocated1) + nodesAllocated, nodesDeallocated := e.Allocator.Nodes() + requireT.Equal([]types.NodeAddress{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, nodesAllocated) + requireT.Equal([]types.NodeAddress{0x01}, nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, s0.Nodes()) + requireT.Empty(e.DeallocationLists.Nodes()) + + s1 := e.NextSnapshot() + + requireT.NoError(s1.Delete(2)) + + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + requireT.Equal([]types.NodeAddress{0x08, 0x09, 0x0a, 0x0b}, nodesAllocated) + requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, s0.Nodes()) + requireT.Equal([]types.NodeAddress{0x04, 0x05, 0x06, 0x07, 0x08, 0x0b}, s1.Nodes()) + + requireT.Equal([]types.SnapshotID{0x00}, collectSpaceKeys(e.DeallocationLists)) + requireT.Equal([]types.NodeAddress{0x0a}, e.DeallocationLists.Nodes()) + + dList0 := e.DeallocationList(0x00) + requireT.Equal([]types.NodeAddress{0x09}, dList0.Nodes()) + requireT.Equal([]types.NodeAddress{0x02, 0x03}, collectListItems(dList0)) + + v, exists := s1.Get(2) + requireT.False(exists) + requireT.Equal(0, v) + requireT.Equal([]int{0, 1, 3, 4}, collectSpaceValues(s1)) s2 := e.NextSnapshot() - requireT.NoError(s2.Delete(2)) + requireT.NoError(s2.Delete(4)) - nodesAccessed2, nodesAllocated2, nodesDeallocated2 := e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x09, 0x0a}, nodesAccessed2) - requireT.Equal([]types.NodeAddress{0x08, 0x09, 0x0a, 0x0b}, nodesAllocated2) - requireT.Empty(nodesDeallocated2) + nodesAllocated, nodesDeallocated = e.Allocator.Nodes() + requireT.Equal([]types.NodeAddress{0x0c, 0x0d, 0x0e, 0x0f, 0x10}, nodesAllocated) + requireT.Empty(nodesDeallocated) + requireT.Equal([]types.NodeAddress{0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, s0.Nodes()) + requireT.Equal([]types.NodeAddress{0x04, 0x05, 0x06, 0x07, 0x08, 0x0b}, s1.Nodes()) + requireT.Equal([]types.NodeAddress{0x04, 0x05, 0x06, 0x0b, 0x0c, 0x0f}, s2.Nodes()) - s3 := e.NextSnapshot() - requireT.NoError(s3.Delete(4)) + requireT.Equal([]types.SnapshotID{0x00, 0x01}, collectSpaceKeys(e.DeallocationLists)) + requireT.Equal([]types.NodeAddress{0x0e}, e.DeallocationLists.Nodes()) + + dList0 = e.DeallocationList(0x00) + requireT.Equal([]types.NodeAddress{0x10}, dList0.Nodes()) + requireT.Equal([]types.NodeAddress{0x07}, collectListItems(dList0)) + + dList1 := e.DeallocationList(0x01) + requireT.Equal([]types.NodeAddress{0x0d}, dList1.Nodes()) + requireT.Equal([]types.NodeAddress{0x08}, collectListItems(dList1)) + + v, exists = s2.Get(2) + requireT.False(exists) + requireT.Equal(0, v) - nodesAccessed3, nodesAllocated3, nodesDeallocated3 := e.Allocator.Nodes() - requireT.Equal([]types.NodeAddress{0x07, 0x08, 0x0e}, nodesAccessed3) - requireT.Equal([]types.NodeAddress{0x0c, 0x0d, 0x0e, 0x0f, 0x10}, nodesAllocated3) - requireT.Empty(nodesDeallocated3) + v, exists = s2.Get(4) + requireT.False(exists) + requireT.Equal(0, v) + + requireT.Equal([]int{0, 1, 2, 3, 4}, collectSpaceValues(s0)) + requireT.Equal([]int{0, 1, 3, 4}, collectSpaceValues(s1)) + requireT.Equal([]int{0, 1, 3}, collectSpaceValues(s2)) } func TestSetCollisions(t *testing.T) { @@ -314,7 +438,7 @@ func TestSetCollisions(t *testing.T) { sort.Ints(allValues) - requireT.Equal(allValues, collect(s)) + requireT.Equal(allValues, collectSpaceValues(s)) } func TestGetCollisions(t *testing.T) { @@ -368,15 +492,15 @@ func newEnv(requireT *require.Assertions) *env { return &env{ Allocator: allocator, - SpaceRoot: types.ParentInfo{ + spaceRoot: types.ParentInfo{ State: lo.ToPtr(types.StateFree), Item: lo.ToPtr[types.NodeAddress](0), }, - DeallocationRoot: types.ParentInfo{ + deallocationRoot: types.ParentInfo{ State: lo.ToPtr(types.StateFree), Item: lo.ToPtr[types.NodeAddress](0), }, - hashMod: lo.ToPtr[uint64](0), + spaceHashMod: lo.ToPtr[uint64](0), pointerNodeAllocator: pointerNodeAllocator, dataNodeAllocator: dataNodeAllocator, snapshotToNodeNodeAllocator: snapshotToNodeNodeAllocator, @@ -385,12 +509,14 @@ func newEnv(requireT *require.Assertions) *env { } type env struct { - Allocator *alloc.TestAllocator - SpaceRoot types.ParentInfo - DeallocationRoot types.ParentInfo + Allocator *alloc.TestAllocator + DeallocationLists *space.Space[types.SnapshotID, types.NodeAddress] snapshotID types.SnapshotID - hashMod *uint64 + snapshotAllocator alloc.SnapshotAllocator + spaceRoot types.ParentInfo + deallocationRoot types.ParentInfo + spaceHashMod *uint64 pointerNodeAllocator space.NodeAllocator[types.NodeAddress] dataNodeAllocator space.NodeAllocator[types.DataItem[int, int]] snapshotToNodeNodeAllocator space.NodeAllocator[types.DataItem[types.SnapshotID, types.NodeAddress]] @@ -401,55 +527,97 @@ func (e *env) NextSnapshot() *space.Space[int, int] { snapshotID := e.snapshotID e.snapshotID++ - stateCopy := *e.SpaceRoot.State - itemCopy := *e.SpaceRoot.Item + stateCopy := *e.spaceRoot.State + itemCopy := *e.spaceRoot.Item - e.SpaceRoot = types.ParentInfo{ + e.spaceRoot = types.ParentInfo{ State: &stateCopy, Item: &itemCopy, } - e.DeallocationRoot = types.ParentInfo{ + e.deallocationRoot = types.ParentInfo{ State: lo.ToPtr(types.StateFree), Item: lo.ToPtr[types.NodeAddress](0), } - deallocationLists := &space.Space[types.SnapshotID, types.NodeAddress]{} - snapshotAllocator := alloc.NewSnapshotAllocator( + e.DeallocationLists = &space.Space[types.SnapshotID, types.NodeAddress]{} + e.snapshotAllocator = alloc.NewSnapshotAllocator( snapshotID, e.Allocator, - deallocationLists, + e.DeallocationLists, e.listNodeAllocator, ) - *deallocationLists = *space.New[types.SnapshotID, types.NodeAddress](space.Config[types.SnapshotID, types.NodeAddress]{ - SnapshotID: snapshotID, - HashMod: lo.ToPtr[uint64](0), - SpaceRoot: e.DeallocationRoot, - PointerNodeAllocator: e.pointerNodeAllocator, - DataNodeAllocator: e.snapshotToNodeNodeAllocator, - Allocator: snapshotAllocator, - }) + *e.DeallocationLists = *space.New[types.SnapshotID, types.NodeAddress]( + space.Config[types.SnapshotID, types.NodeAddress]{ + SnapshotID: snapshotID, + HashMod: lo.ToPtr[uint64](0), + SpaceRoot: e.deallocationRoot, + PointerNodeAllocator: e.pointerNodeAllocator, + DataNodeAllocator: e.snapshotToNodeNodeAllocator, + Allocator: e.snapshotAllocator, + }, + ) return space.New[int, int](space.Config[int, int]{ SnapshotID: snapshotID, - HashMod: e.hashMod, - SpaceRoot: e.SpaceRoot, + HashMod: e.spaceHashMod, + SpaceRoot: e.spaceRoot, PointerNodeAllocator: e.pointerNodeAllocator, DataNodeAllocator: e.dataNodeAllocator, - Allocator: snapshotAllocator, + Allocator: e.snapshotAllocator, }) } -func collect(s *space.Space[int, int]) []int { - values := []int{} +func (e *env) DeallocationList(snapshotID types.SnapshotID) *list.List { + nodeAddress, exists := e.DeallocationLists.Get(snapshotID) + if !exists { + return nil + } + + return list.New(list.Config{ + SnapshotID: snapshotID, + Item: &nodeAddress, + NodeAllocator: e.listNodeAllocator, + Allocator: e.snapshotAllocator, + }) +} + +func collectSpaceValues[K comparable, V constraints.Ordered](s *space.Space[K, V]) []V { + values := []V{} for item := range s.Iterator() { values = append(values, item.Value) } - sort.Ints(values) + sort.Slice(values, func(i, j int) bool { + return values[i] < values[j] + }) return values } +func collectSpaceKeys[K constraints.Ordered, V comparable](s *space.Space[K, V]) []K { + keys := []K{} + for item := range s.Iterator() { + keys = append(keys, item.Key) + } + + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + return keys +} + +func collectListItems(l *list.List) []types.NodeAddress { + items := []types.NodeAddress{} + for item := range l.Iterator() { + items = append(items, item) + } + + sort.Slice(items, func(i, j int) bool { + return items[i] < items[j] + }) + return items +} + var collisions = [][]int{ {15691551, 62234586, 76498628, 79645586}, {6417226, 8828927, 78061179, 87384387},