diff --git a/cmd/main/main.go b/cmd/main/main.go deleted file mode 100644 index f94db511..00000000 --- a/cmd/main/main.go +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Atree - Scalable Arrays and Ordered Maps - * - * Copyright 2021 Dapper Labs, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package main - -import ( - "flag" - "fmt" - - "github.com/onflow/atree" - - "github.com/fxamacker/cbor/v2" -) - -const cborTagUInt64Value = 164 - -type Uint64Value uint64 - -var _ atree.Value = Uint64Value(0) -var _ atree.Storable = Uint64Value(0) - -func (v Uint64Value) ChildStorables() []atree.Storable { - return nil -} - -func (v Uint64Value) StoredValue(_ atree.SlabStorage) (atree.Value, error) { - return v, nil -} - -func (v Uint64Value) Storable(_ atree.SlabStorage, _ atree.Address, _ uint64) (atree.Storable, error) { - return v, nil -} - -// Encode encodes UInt64Value as -// -// cbor.Tag{ -// Number: cborTagUInt64Value, -// Content: uint64(v), -// } -func (v Uint64Value) Encode(enc *atree.Encoder) error { - err := enc.CBOR.EncodeRawBytes([]byte{ - // tag number - 0xd8, cborTagUInt64Value, - }) - if err != nil { - return err - } - return enc.CBOR.EncodeUint64(uint64(v)) -} - -// TODO: cache size -func (v Uint64Value) ByteSize() uint32 { - // tag number (2 bytes) + encoded content - return 2 + atree.GetUintCBORSize(uint64(v)) -} - -func (v Uint64Value) String() string { - return fmt.Sprintf("%d", uint64(v)) -} - -type testTypeInfo struct { - value uint64 -} - -var _ atree.TypeInfo = testTypeInfo{} - -func (i testTypeInfo) Copy() atree.TypeInfo { - return i -} - -func (testTypeInfo) IsComposite() bool { - return false -} - -func (i testTypeInfo) ID() string { - return fmt.Sprintf("uint64(%d)", i.value) -} - -func (i testTypeInfo) Encode(e *cbor.StreamEncoder) error { - return e.EncodeUint64(i.value) -} - -func (i testTypeInfo) Equal(other atree.TypeInfo) bool { - otherTestTypeInfo, ok := other.(testTypeInfo) - return ok && i.value == otherTestTypeInfo.value -} - -func decodeStorable(dec *cbor.StreamDecoder, _ atree.SlabID, _ []atree.ExtraData) (atree.Storable, error) { - tagNumber, err := dec.DecodeTagNumber() - if err != nil { - return nil, err - } - - switch tagNumber { - case atree.CBORTagSlabID: - return atree.DecodeSlabIDStorable(dec) - - case cborTagUInt64Value: - n, err := dec.DecodeUint64() - if err != nil { - return nil, err - } - return Uint64Value(n), nil - - default: - return nil, fmt.Errorf("invalid tag number %d", tagNumber) - } -} - -// TODO: implement different slab size for metadata slab and data slab. -func main() { - var slabSize uint64 - var numElements uint64 - var verbose bool - - flag.Uint64Var(&slabSize, "size", 1024, "slab size in bytes") - flag.Uint64Var(&numElements, "count", 500, "number of elements in array") - flag.BoolVar(&verbose, "verbose", false, "verbose output") - - flag.Parse() - - minThreshold, maxThreshold, _, _ := atree.SetThreshold(slabSize) - - fmt.Printf( - "Inserting %d elements (uint64) into array with slab size %d, min size %d, and max size %d ...\n", - numElements, - slabSize, - minThreshold, - maxThreshold, - ) - - encMode, err := cbor.EncOptions{}.EncMode() - if err != nil { - fmt.Println(err) - return - } - - decMode, err := cbor.DecOptions{}.DecMode() - if err != nil { - fmt.Println(err) - return - } - - storage := atree.NewBasicSlabStorage(encMode, decMode, decodeStorable, decodeTypeInfo) - - typeInfo := testTypeInfo{} - - address := atree.Address{1, 2, 3, 4, 5, 6, 7, 8} - - array, err := atree.NewArray(storage, address, typeInfo) - - if err != nil { - fmt.Println(err) - return - } - - for i := uint64(0); i < numElements; i++ { - err := array.Append(Uint64Value(i)) - if err != nil { - fmt.Println(err) - return - } - } - - stats, err := atree.GetArrayStats(array) - if err != nil { - fmt.Println(err) - return - } - - fmt.Printf("%+v\n", stats) - - if verbose { - fmt.Printf("\n\n=========== array layout ===========\n") - atree.PrintArray(array) - } -} - -func decodeTypeInfo(_ *cbor.StreamDecoder) (atree.TypeInfo, error) { - return testTypeInfo{}, nil -} diff --git a/cmd/stress/array.go b/cmd/stress/array.go index 699bf60a..1a8e94d3 100644 --- a/cmd/stress/array.go +++ b/cmd/stress/array.go @@ -21,6 +21,7 @@ package main import ( "fmt" "os" + "reflect" "runtime" "sync" "time" @@ -28,11 +29,17 @@ import ( "github.com/onflow/atree" ) +type arrayOpType int + const ( - arrayAppendOp = iota + arrayAppendOp arrayOpType = iota arrayInsertOp arraySetOp arrayRemoveOp + arrayMutateChildContainerAfterGet + arrayMutateChildContainerAfterAppend + arrayMutateChildContainerAfterInsert + arrayMutateChildContainerAfterSet maxArrayOp ) @@ -43,10 +50,14 @@ type arrayStatus struct { count uint64 // number of elements in array - appendOps uint64 - insertOps uint64 - setOps uint64 - removeOps uint64 + appendOps uint64 + insertOps uint64 + setOps uint64 + removeOps uint64 + mutateChildContainerAfterGetOps uint64 + mutateChildContainerAfterAppendOps uint64 + mutateChildContainerAfterInsertOps uint64 + mutateChildContainerAfterSetOps uint64 } var _ Status = &arrayStatus{} @@ -64,7 +75,7 @@ func (status *arrayStatus) String() string { var m runtime.MemStats runtime.ReadMemStats(&m) - return fmt.Sprintf("duration %s, heapAlloc %d MiB, %d elements, %d appends, %d sets, %d inserts, %d removes", + return fmt.Sprintf("duration %s, heapAlloc %d MiB, %d elements, %d appends, %d sets, %d inserts, %d removes, %d Get mutations, %d Append mutations, %d Insert mutations, %d Set mutations", duration.Truncate(time.Second).String(), m.Alloc/1024/1024, status.count, @@ -72,38 +83,44 @@ func (status *arrayStatus) String() string { status.setOps, status.insertOps, status.removeOps, + status.mutateChildContainerAfterGetOps, + status.mutateChildContainerAfterAppendOps, + status.mutateChildContainerAfterInsertOps, + status.mutateChildContainerAfterSetOps, ) } -func (status *arrayStatus) incAppend() { +func (status *arrayStatus) incOp(op arrayOpType, newTotalCount uint64) { status.lock.Lock() defer status.lock.Unlock() - status.appendOps++ - status.count++ -} + switch op { + case arrayAppendOp: + status.appendOps++ -func (status *arrayStatus) incSet() { - status.lock.Lock() - defer status.lock.Unlock() + case arrayInsertOp: + status.insertOps++ - status.setOps++ -} + case arraySetOp: + status.setOps++ -func (status *arrayStatus) incInsert() { - status.lock.Lock() - defer status.lock.Unlock() + case arrayRemoveOp: + status.removeOps++ - status.insertOps++ - status.count++ -} + case arrayMutateChildContainerAfterGet: + status.mutateChildContainerAfterGetOps++ -func (status *arrayStatus) incRemove() { - status.lock.Lock() - defer status.lock.Unlock() + case arrayMutateChildContainerAfterAppend: + status.mutateChildContainerAfterAppendOps++ + + case arrayMutateChildContainerAfterInsert: + status.mutateChildContainerAfterInsertOps++ + + case arrayMutateChildContainerAfterSet: + status.mutateChildContainerAfterSetOps++ + } - status.removeOps++ - status.count-- + status.count = newTotalCount } func (status *arrayStatus) Write() { @@ -113,13 +130,11 @@ func (status *arrayStatus) Write() { func testArray( storage *atree.PersistentSlabStorage, address atree.Address, - typeInfo atree.TypeInfo, - maxLength uint64, status *arrayStatus, - minHeapAllocMiB uint64, - maxHeapAllocMiB uint64, ) { + typeInfo := newArrayTypeInfo() + // Create new array array, err := atree.NewArray(storage, address, typeInfo) if err != nil { @@ -127,12 +142,12 @@ func testArray( return } - // values contains array elements in the same order. It is used to check data loss. - values := make([]atree.Value, 0, maxLength) + // expectedValues contains array elements in the same order. It is used to check data loss. + expectedValues := make(arrayValue, 0, flagMaxLength) reduceHeapAllocs := false - opCount := uint64(0) + opCountForStorageHealthCheck := uint64(0) var m runtime.MemStats @@ -140,10 +155,10 @@ func testArray( runtime.ReadMemStats(&m) allocMiB := m.Alloc / 1024 / 1024 - if !reduceHeapAllocs && allocMiB > maxHeapAllocMiB { + if !reduceHeapAllocs && allocMiB > flagMaxHeapAllocMiB { fmt.Printf("\nHeapAlloc is %d MiB, removing elements to reduce allocs...\n", allocMiB) reduceHeapAllocs = true - } else if reduceHeapAllocs && allocMiB < minHeapAllocMiB { + } else if reduceHeapAllocs && allocMiB < flagMinHeapAllocMiB { fmt.Printf("\nHeapAlloc is %d MiB, resuming random operation...\n", allocMiB) reduceHeapAllocs = false } @@ -178,245 +193,404 @@ func testArray( fmt.Printf("\nHeapAlloc is %d MiB after cleanup and forced gc\n", allocMiB) // Prevent infinite loop that doesn't do useful work. - if allocMiB > maxHeapAllocMiB { + if allocMiB > flagMaxHeapAllocMiB { // This shouldn't happen unless there's a memory leak. fmt.Fprintf( os.Stderr, "Exiting because allocMiB %d > maxMapHeapAlloMiB %d with empty map\n", allocMiB, - maxHeapAllocMiB) + flagMaxHeapAllocMiB) return } } - nextOp := r.Intn(maxArrayOp) + var forceRemove bool + if array.Count() == flagMaxLength || reduceHeapAllocs { + forceRemove = true + } - if array.Count() == maxLength || reduceHeapAllocs { - nextOp = arrayRemoveOp + var prevOp arrayOpType + expectedValues, prevOp, err = modifyArray(expectedValues, array, maxNestedLevels, forceRemove) + if err != nil { + fmt.Fprint(os.Stderr, err.Error()) + return } - switch nextOp { + opCountForStorageHealthCheck++ - case arrayAppendOp: - opCount++ + // Update status + status.incOp(prevOp, array.Count()) - nestedLevels := r.Intn(maxNestedLevels) - v, err := randomValue(storage, address, nestedLevels) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to generate random value %s: %s", v, err) + // Check array elements against values after every op + err = checkArrayDataLoss(expectedValues, array) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + + if opCountForStorageHealthCheck >= flagMinOpsForStorageHealthCheck { + opCountForStorageHealthCheck = 0 + + if !checkStorageHealth(storage, array.SlabID()) { return } - copiedValue, err := copyValue(storage, atree.Address{}, v) + // Commit slabs to storage so slabs are encoded and then decoded at next op. + err = storage.FastCommit(runtime.NumCPU()) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to copy random value %s: %s", v, err) + fmt.Fprintf(os.Stderr, "Failed to commit to storage: %s", err) return } - // Append to values - values = append(values, copiedValue) + // Drop cache after commit to force slab decoding at next op. + storage.DropCache() + } + } +} + +func nextArrayOp( + expectedValues arrayValue, + array *atree.Array, + nestedLevels int, + forceRemove bool, +) (arrayOpType, error) { - // Append to array - err = array.Append(v) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to append %s: %s", v, err) - return - } + if forceRemove { + if array.Count() == 0 { + return 0, fmt.Errorf("failed to force remove array elements because array has no elements") + } + return arrayRemoveOp, nil + } + + if array.Count() == 0 { + return arrayAppendOp, nil + } - // Update status - status.incAppend() + for { + nextOp := arrayOpType(r.Intn(int(maxArrayOp))) - case arraySetOp: - opCount++ + switch nextOp { + case arrayMutateChildContainerAfterAppend, + arrayMutateChildContainerAfterInsert, + arrayMutateChildContainerAfterSet: - if array.Count() == 0 { - continue + if nestedLevels-1 > 0 { + return nextOp, nil } - k := r.Intn(int(array.Count())) + // New child container can't be created because next nestedLevels is 0. + // Try another array operation. - nestedLevels := r.Intn(maxNestedLevels) - v, err := randomValue(storage, address, nestedLevels) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to generate random value %s: %s", v, err) - return + case arrayMutateChildContainerAfterGet: + if hasChildContainerInArray(expectedValues) { + return nextOp, nil } - copiedValue, err := copyValue(storage, atree.Address{}, v) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to copy random value %s: %s", v, err) - return - } + // Array doesn't have child container, try another array operation. - oldV := values[k] + default: + return nextOp, nil + } + } +} - // Update values - values[k] = copiedValue +func modifyArray( + expectedValues arrayValue, + array *atree.Array, + nestedLevels int, + forceRemove bool, +) (arrayValue, arrayOpType, error) { - // Update array - existingStorable, err := array.Set(uint64(k), v) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to set %s at index %d: %s", v, k, err) - return - } + storage := array.Storage + address := array.Address() - // Compare overwritten value from array with overwritten value from values - existingValue, err := existingStorable.StoredValue(storage) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to convert %s to value: %s", existingStorable, err) - return - } + nextOp, err := nextArrayOp(expectedValues, array, nestedLevels, forceRemove) + if err != nil { + return nil, 0, err + } - err = valueEqual(oldV, existingValue) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to compare %s and %s: %s", existingValue, oldV, err) - return - } + switch nextOp { + case arrayAppendOp, arrayMutateChildContainerAfterAppend: + + var nextNestedLevels int + + switch nextOp { + case arrayAppendOp: + nextNestedLevels = r.Intn(nestedLevels) + case arrayMutateChildContainerAfterAppend: + nextNestedLevels = nestedLevels - 1 + default: + panic("not reachable") + } - // Delete overwritten element from storage - err = removeStorable(storage, existingStorable) + // Create new chid child + expectedChildValue, child, err := randomValue(storage, address, nextNestedLevels) + if err != nil { + return nil, 0, fmt.Errorf("failed to generate random value %s: %s", child, err) + } + + // Update expectedValues + expectedValues = append(expectedValues, expectedChildValue) + + // Update array + err = array.Append(child) + if err != nil { + return nil, 0, fmt.Errorf("failed to append %s: %s", child, err) + } + + if nextOp == arrayMutateChildContainerAfterAppend { + index := len(expectedValues) - 1 + + expectedValues[index], err = modifyContainer(expectedValues[index], child, nextNestedLevels) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove storable %s: %s", existingStorable, err) - return + return nil, 0, fmt.Errorf("failed to modify child container at index %d: %w", index, err) } + } + + case arraySetOp, arrayMutateChildContainerAfterSet: + + var nextNestedLevels int + + switch nextOp { + case arraySetOp: + nextNestedLevels = r.Intn(nestedLevels) + case arrayMutateChildContainerAfterSet: + nextNestedLevels = nestedLevels - 1 + default: + panic("not reachable") + } + + // Create new child child + expectedChildValue, child, err := randomValue(storage, address, nextNestedLevels) + if err != nil { + return nil, 0, fmt.Errorf("failed to generate random value %s: %s", child, err) + } + + index := r.Intn(int(array.Count())) + + oldExpectedValue := expectedValues[index] + + // Update expectedValues + expectedValues[index] = expectedChildValue + + // Update array + existingStorable, err := array.Set(uint64(index), child) + if err != nil { + return nil, 0, fmt.Errorf("failed to set %s at index %d: %s", child, index, err) + } + + // Compare overwritten value from array with overwritten value from expectedValues + existingValue, err := existingStorable.StoredValue(storage) + if err != nil { + return nil, 0, fmt.Errorf("failed to convert %s to value: %s", existingStorable, err) + } + + err = valueEqual(oldExpectedValue, existingValue) + if err != nil { + return nil, 0, fmt.Errorf("failed to compare %s and %s: %s", existingValue, oldExpectedValue, err) + } + + // Delete overwritten element from storage + err = removeStorable(storage, existingStorable) + if err != nil { + return nil, 0, fmt.Errorf("failed to remove storable %s: %s", existingStorable, err) + } - err = removeValue(storage, oldV) + if nextOp == arrayMutateChildContainerAfterSet { + expectedValues[index], err = modifyContainer(expectedValues[index], child, nextNestedLevels) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove copied overwritten value %s: %s", oldV, err) - return + return nil, 0, fmt.Errorf("failed to modify child container at index %d: %w", index, err) } + } + + case arrayInsertOp, arrayMutateChildContainerAfterInsert: - // Update status - status.incSet() + var nextNestedLevels int + switch nextOp { case arrayInsertOp: - opCount++ + nextNestedLevels = r.Intn(nestedLevels) + case arrayMutateChildContainerAfterInsert: + nextNestedLevels = nestedLevels - 1 + default: + panic("not reachable") + } - k := r.Intn(int(array.Count() + 1)) + // Create new child child + expectedChildValue, child, err := randomValue(storage, address, nextNestedLevels) + if err != nil { + return nil, 0, fmt.Errorf("failed to generate random value %s: %s", child, err) + } - nestedLevels := r.Intn(maxNestedLevels) - v, err := randomValue(storage, address, nestedLevels) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to generate random value %s: %s", v, err) - return - } + index := r.Intn(int(array.Count() + 1)) - copiedValue, err := copyValue(storage, atree.Address{}, v) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to copy random value %s: %s", v, err) - return - } + // Update expectedValues + if index == int(array.Count()) { + expectedValues = append(expectedValues, expectedChildValue) + } else { + expectedValues = append(expectedValues, nil) + copy(expectedValues[index+1:], expectedValues[index:]) + expectedValues[index] = expectedChildValue + } - // Update values - if k == int(array.Count()) { - values = append(values, copiedValue) - } else { - values = append(values, nil) - copy(values[k+1:], values[k:]) - values[k] = copiedValue - } + // Update array + err = array.Insert(uint64(index), child) + if err != nil { + return nil, 0, fmt.Errorf("failed to insert %s into index %d: %s", child, index, err) + } - // Update array - err = array.Insert(uint64(k), v) + if nextOp == arrayMutateChildContainerAfterInsert { + expectedValues[index], err = modifyContainer(expectedValues[index], child, nextNestedLevels) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to insert %s into index %d: %s", v, k, err) - return + return nil, 0, fmt.Errorf("failed to modify child container at index %d: %w", index, err) } + } - // Update status - status.incInsert() + case arrayRemoveOp: + index := r.Intn(int(array.Count())) - case arrayRemoveOp: - if array.Count() == 0 { - continue - } + oldExpectedValue := expectedValues[index] - opCount++ + // Update expectedValues + copy(expectedValues[index:], expectedValues[index+1:]) + expectedValues[len(expectedValues)-1] = nil + expectedValues = expectedValues[:len(expectedValues)-1] - k := r.Intn(int(array.Count())) + // Update array + existingStorable, err := array.Remove(uint64(index)) + if err != nil { + return nil, 0, fmt.Errorf("failed to remove element at index %d: %s", index, err) + } - oldV := values[k] + // Compare removed value from array with removed value from values + existingValue, err := existingStorable.StoredValue(storage) + if err != nil { + return nil, 0, fmt.Errorf("failed to convert %s to value: %s", existingStorable, err) + } - // Update values - copy(values[k:], values[k+1:]) - values[len(values)-1] = nil - values = values[:len(values)-1] + err = valueEqual(oldExpectedValue, existingValue) + if err != nil { + return nil, 0, fmt.Errorf("failed to compare %s and %s: %s", existingValue, oldExpectedValue, err) + } - // Update array - existingStorable, err := array.Remove(uint64(k)) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove element at index %d: %s", k, err) - return - } + // Delete removed element from storage + err = removeStorable(storage, existingStorable) + if err != nil { + return nil, 0, fmt.Errorf("failed to remove element %s: %s", existingStorable, err) + } - // Compare removed value from array with removed value from values - existingValue, err := existingStorable.StoredValue(storage) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to convert %s to value: %s", existingStorable, err) - return - } + case arrayMutateChildContainerAfterGet: + index, found := getRandomChildContainerIndexInArray(expectedValues) + if !found { + // arrayMutateChildContainerAfterGet op can't be performed because there isn't any child container in this array. + // Try another array operation. + return modifyArray(expectedValues, array, nestedLevels, forceRemove) + } - err = valueEqual(oldV, existingValue) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to compare %s and %s: %s", existingValue, oldV, err) - return - } + child, err := array.Get(uint64(index)) + if err != nil { + return nil, 0, fmt.Errorf("failed to get element from array at index %d: %s", index, err) + } - // Delete removed element from storage - err = removeStorable(storage, existingStorable) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove element %s: %s", existingStorable, err) - return - } + expectedValues[index], err = modifyContainer(expectedValues[index], child, nestedLevels-1) + if err != nil { + return nil, 0, fmt.Errorf("failed to modify child container at index %d: %w", index, err) + } + } - err = removeValue(storage, oldV) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove copied removed value %s: %s", oldV, err) - return - } + return expectedValues, nextOp, nil +} + +func modifyContainer(expectedValue atree.Value, value atree.Value, nestedLevels int) (expected atree.Value, err error) { - // Update status - status.incRemove() + switch value := value.(type) { + case *atree.Array: + expectedArrayValue, ok := expectedValue.(arrayValue) + if !ok { + return nil, fmt.Errorf("failed to get expected value of type arrayValue: got %T", expectedValue) } - // Check array elements against values after every op - err = checkArrayDataLoss(array, values) + expectedValue, _, err = modifyArray(expectedArrayValue, value, nestedLevels, false) if err != nil { - fmt.Fprintln(os.Stderr, err) - return + return nil, err } - if opCount >= 100 { - opCount = 0 - rootIDs, err := atree.CheckStorageHealth(storage, -1) - if err != nil { - fmt.Fprintln(os.Stderr, err) - return - } - ids := make([]atree.SlabID, 0, len(rootIDs)) - for id := range rootIDs { - // filter out root ids with empty address - if !id.HasTempAddress() { - ids = append(ids, id) - } - } - if len(ids) != 1 || ids[0] != array.SlabID() { - fmt.Fprintf(os.Stderr, "root slab ids %v in storage, want %s\n", ids, array.SlabID()) - return - } + case *atree.OrderedMap: + expectedMapValue, ok := expectedValue.(mapValue) + if !ok { + return nil, fmt.Errorf("failed to get expected value of type mapValue: got %T", expectedValue) + } + + expectedValue, _, err = modifyMap(expectedMapValue, value, nestedLevels, false) + if err != nil { + return nil, err } + + default: + return nil, fmt.Errorf("failed to get container: got %T", value) } + + return expectedValue, nil } -func checkArrayDataLoss(array *atree.Array, values []atree.Value) error { +func hasChildContainerInArray(expectedValues arrayValue) bool { + for _, v := range expectedValues { + switch v.(type) { + case arrayValue, mapValue: + return true + } + } + return false +} + +func getRandomChildContainerIndexInArray(expectedValues arrayValue) (index int, found bool) { + indexes := make([]int, 0, len(expectedValues)) + for i, v := range expectedValues { + switch v.(type) { + case arrayValue, mapValue: + indexes = append(indexes, i) + } + } + if len(indexes) == 0 { + return 0, false + } + return indexes[r.Intn(len(indexes))], true +} + +func checkStorageHealth(storage *atree.PersistentSlabStorage, rootSlabID atree.SlabID) bool { + rootIDs, err := atree.CheckStorageHealth(storage, -1) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return false + } + + // Filter out slabs with temp address because + // child array/map values used for data loss check is stored with temp address. + ids := make([]atree.SlabID, 0, len(rootIDs)) + for id := range rootIDs { + // filter out root ids with empty address + if !id.HasTempAddress() { + ids = append(ids, id) + } + } + + if len(ids) != 1 || ids[0] != rootSlabID { + fmt.Fprintf(os.Stderr, "root slab ids %v in storage, want %s\n", ids, rootSlabID) + return false + } + + return true +} + +func checkArrayDataLoss(expectedValues arrayValue, array *atree.Array) error { // Check array has the same number of elements as values - if array.Count() != uint64(len(values)) { - return fmt.Errorf("Count() %d != len(values) %d", array.Count(), len(values)) + if array.Count() != uint64(len(expectedValues)) { + return fmt.Errorf("Count() %d != len(values) %d", array.Count(), len(expectedValues)) } // Check every element - for i, v := range values { + for i, v := range expectedValues { convertedValue, err := array.Get(uint64(i)) if err != nil { return fmt.Errorf("failed to get element at %d: %w", i, err) @@ -427,5 +601,30 @@ func checkArrayDataLoss(array *atree.Array, values []atree.Value) error { } } + if flagCheckSlabEnabled { + err := checkArraySlab(array) + if err != nil { + return err + } + } + return nil } + +func checkArraySlab(array *atree.Array) error { + err := atree.VerifyArray(array, array.Address(), array.Type(), typeInfoComparator, hashInputProvider, true) + if err != nil { + return err + } + + return atree.VerifyArraySerialization( + array, + cborDecMode, + cborEncMode, + decodeStorable, + decodeTypeInfo, + func(a, b atree.Storable) bool { + return reflect.DeepEqual(a, b) + }, + ) +} diff --git a/cmd/stress/main.go b/cmd/stress/main.go index c74e677a..e93364c6 100644 --- a/cmd/stress/main.go +++ b/cmd/stress/main.go @@ -70,25 +70,47 @@ func updateStatus(sigc <-chan os.Signal, status Status) { } } -func main() { +var cborEncMode = func() cbor.EncMode { + encMode, err := cbor.EncOptions{}.EncMode() + if err != nil { + panic(fmt.Sprintf("Failed to create CBOR encoding mode: %s", err)) + } + return encMode +}() + +var cborDecMode = func() cbor.DecMode { + decMode, err := cbor.DecOptions{}.DecMode() + if err != nil { + panic(fmt.Sprintf("Failed to create CBOR decoding mode: %s\n", err)) + } + return decMode +}() + +var ( + flagType string + flagCheckSlabEnabled bool + flagMaxLength uint64 + flagSeedHex string + flagMinHeapAllocMiB, flagMaxHeapAllocMiB uint64 + flagMinOpsForStorageHealthCheck uint64 +) - var typ string - var maxLength uint64 - var seedHex string - var minHeapAllocMiB, maxHeapAllocMiB uint64 +func main() { - flag.StringVar(&typ, "type", "array", "array or map") - flag.Uint64Var(&maxLength, "maxlen", 10_000, "max number of elements") - flag.StringVar(&seedHex, "seed", "", "seed for prng in hex (default is Unix time)") - flag.Uint64Var(&minHeapAllocMiB, "minheap", 1000, "min HeapAlloc in MiB to stop extra removal of elements") - flag.Uint64Var(&maxHeapAllocMiB, "maxheap", 2000, "max HeapAlloc in MiB to trigger extra removal of elements") + flag.StringVar(&flagType, "type", "array", "array or map") + flag.BoolVar(&flagCheckSlabEnabled, "slabcheck", false, "in memory and serialized slab check") + flag.Uint64Var(&flagMinOpsForStorageHealthCheck, "minOpsForStorageHealthCheck", 100, "number of operations for storage health check") + flag.Uint64Var(&flagMaxLength, "maxlen", 10_000, "max number of elements") + flag.StringVar(&flagSeedHex, "seed", "", "seed for prng in hex (default is Unix time)") + flag.Uint64Var(&flagMinHeapAllocMiB, "minheap", 1000, "min HeapAlloc in MiB to stop extra removal of elements") + flag.Uint64Var(&flagMaxHeapAllocMiB, "maxheap", 2000, "max HeapAlloc in MiB to trigger extra removal of elements") flag.Parse() var seed int64 - if len(seedHex) != 0 { + if len(flagSeedHex) != 0 { var err error - seed, err = strconv.ParseInt(strings.ReplaceAll(seedHex, "0x", ""), 16, 64) + seed, err = strconv.ParseInt(strings.ReplaceAll(flagSeedHex, "0x", ""), 16, 64) if err != nil { panic("Failed to parse seed flag (hex string)") } @@ -96,9 +118,9 @@ func main() { r = newRand(seed) - typ = strings.ToLower(typ) + flagType = strings.ToLower(flagType) - if typ != "array" && typ != "map" { + if flagType != "array" && flagType != "map" { fmt.Fprintf(os.Stderr, "Please specify type as either \"array\" or \"map\"") return } @@ -106,52 +128,49 @@ func main() { sigc := make(chan os.Signal, 1) signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) - // Create storage - encMode, err := cbor.EncOptions{}.EncMode() - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to create CBOR encoding mode: %s\n", err) - return - } - - decMode, err := cbor.DecOptions{}.DecMode() - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to create CBOR decoding mode: %s\n", err) - return - } - baseStorage := NewInMemBaseStorage() storage := atree.NewPersistentSlabStorage( baseStorage, - encMode, - decMode, + cborEncMode, + cborDecMode, decodeStorable, decodeTypeInfo, ) - typeInfo := testTypeInfo{value: 123} - address := atree.Address{1, 2, 3, 4, 5, 6, 7, 8} - switch typ { + switch flagType { case "array": - fmt.Printf("Starting array stress test, minMapHeapAlloc = %d MiB, maxMapHeapAlloc = %d MiB\n", minHeapAllocMiB, maxHeapAllocMiB) + var msg string + if flagCheckSlabEnabled { + msg = fmt.Sprintf("Starting array stress test with slab check, minMapHeapAlloc = %d MiB, maxMapHeapAlloc = %d MiB", flagMinHeapAllocMiB, flagMaxHeapAllocMiB) + } else { + msg = fmt.Sprintf("Starting array stress test, minMapHeapAlloc = %d MiB, maxMapHeapAlloc = %d MiB", flagMinHeapAllocMiB, flagMaxHeapAllocMiB) + } + fmt.Println(msg) status := newArrayStatus() go updateStatus(sigc, status) - testArray(storage, address, typeInfo, maxLength, status, minHeapAllocMiB, maxHeapAllocMiB) + testArray(storage, address, status) case "map": - fmt.Printf("Starting map stress test, minMapHeapAlloc = %d MiB, maxMapHeapAlloc = %d MiB\n", minHeapAllocMiB, maxHeapAllocMiB) + var msg string + if flagCheckSlabEnabled { + msg = fmt.Sprintf("Starting map stress test with slab check, minMapHeapAlloc = %d MiB, maxMapHeapAlloc = %d MiB", flagMinHeapAllocMiB, flagMaxHeapAllocMiB) + } else { + msg = fmt.Sprintf("Starting map stress test, minMapHeapAlloc = %d MiB, maxMapHeapAlloc = %d MiB", flagMinHeapAllocMiB, flagMaxHeapAllocMiB) + } + fmt.Println(msg) status := newMapStatus() go updateStatus(sigc, status) - testMap(storage, address, typeInfo, maxLength, status, minHeapAllocMiB, maxHeapAllocMiB) + testMap(storage, address, status) } } diff --git a/cmd/stress/map.go b/cmd/stress/map.go index 13f222c2..c560bb5a 100644 --- a/cmd/stress/map.go +++ b/cmd/stress/map.go @@ -21,6 +21,7 @@ package main import ( "fmt" "os" + "reflect" "runtime" "sync" "time" @@ -28,11 +29,15 @@ import ( "github.com/onflow/atree" ) +type mapOpType int + const ( - mapSetOp1 = iota + mapSetOp1 mapOpType = iota mapSetOp2 mapSetOp3 mapRemoveOp + mapMutateChildContainerAfterGet + mapMutateChildContainerAfterSet maxMapOp ) @@ -43,8 +48,10 @@ type mapStatus struct { count uint64 // number of elements in map - setOps uint64 - removeOps uint64 + setOps uint64 + removeOps uint64 + mutateChildContainerAfterGetOps uint64 + mutateChildContainerAfterSetOps uint64 } var _ Status = &mapStatus{} @@ -62,32 +69,36 @@ func (status *mapStatus) String() string { var m runtime.MemStats runtime.ReadMemStats(&m) - return fmt.Sprintf("duration %s, heapAlloc %d MiB, %d elements, %d sets, %d removes", + return fmt.Sprintf("duration %s, heapAlloc %d MiB, %d elements, %d sets, %d removes, %d Get mutations, %d Set mutations", duration.Truncate(time.Second).String(), m.Alloc/1024/1024, status.count, status.setOps, status.removeOps, + status.mutateChildContainerAfterGetOps, + status.mutateChildContainerAfterSetOps, ) } -func (status *mapStatus) incSet(newValue bool) { +func (status *mapStatus) incOp(op mapOpType, newTotalCount uint64) { status.lock.Lock() defer status.lock.Unlock() - status.setOps++ + switch op { + case mapSetOp1, mapSetOp2, mapSetOp3: + status.setOps++ - if newValue { - status.count++ - } -} + case mapRemoveOp: + status.removeOps++ -func (status *mapStatus) incRemove() { - status.lock.Lock() - defer status.lock.Unlock() + case mapMutateChildContainerAfterGet: + status.mutateChildContainerAfterGetOps++ - status.removeOps++ - status.count-- + case mapMutateChildContainerAfterSet: + status.mutateChildContainerAfterSetOps++ + } + + status.count = newTotalCount } func (status *mapStatus) Write() { @@ -97,12 +108,9 @@ func (status *mapStatus) Write() { func testMap( storage *atree.PersistentSlabStorage, address atree.Address, - typeInfo atree.TypeInfo, - maxLength uint64, status *mapStatus, - minHeapAllocMiB uint64, - maxHeapAllocMiB uint64, ) { + typeInfo := newMapTypeInfo() m, err := atree.NewMap(storage, address, atree.NewDefaultDigesterBuilder(), typeInfo) if err != nil { @@ -110,15 +118,12 @@ func testMap( return } - // elements contains generated keys and values. It is used to check data loss. - elements := make(map[atree.Value]atree.Value, maxLength) - - // keys contains generated keys. It is used to select random keys for removal. - keys := make([]atree.Value, 0, maxLength) + // expectedValues contains generated keys and values. It is used to check data loss. + expectedValues := make(mapValue, flagMaxLength) reduceHeapAllocs := false - opCount := uint64(0) + opCountForStorageHealthCheck := uint64(0) var ms runtime.MemStats @@ -126,10 +131,10 @@ func testMap( runtime.ReadMemStats(&ms) allocMiB := ms.Alloc / 1024 / 1024 - if !reduceHeapAllocs && allocMiB > maxHeapAllocMiB { + if !reduceHeapAllocs && allocMiB > flagMaxHeapAllocMiB { fmt.Printf("\nHeapAlloc is %d MiB, removing elements to reduce allocs...\n", allocMiB) reduceHeapAllocs = true - } else if reduceHeapAllocs && allocMiB < minHeapAllocMiB { + } else if reduceHeapAllocs && allocMiB < flagMinHeapAllocMiB { fmt.Printf("\nHeapAlloc is %d MiB, resuming random operation...\n", allocMiB) reduceHeapAllocs = false } @@ -148,7 +153,7 @@ func testMap( storage.DropDeltas() storage.DropCache() - elements = make(map[atree.Value]atree.Value, maxLength) + expectedValues = make(map[atree.Value]atree.Value, flagMaxLength) // Load root slab from storage and cache it in read cache rootID := m.SlabID() @@ -166,230 +171,292 @@ func testMap( fmt.Printf("\nHeapAlloc is %d MiB after cleanup and forced gc\n", allocMiB) // Prevent infinite loop that doesn't do useful work. - if allocMiB > maxHeapAllocMiB { + if allocMiB > flagMaxHeapAllocMiB { // This shouldn't happen unless there's a memory leak. fmt.Fprintf( os.Stderr, "Exiting because allocMiB %d > maxMapHeapAlloMiB %d with empty map\n", allocMiB, - maxHeapAllocMiB) + flagMaxHeapAllocMiB) return } } - nextOp := r.Intn(maxMapOp) + var forceRemove bool + if m.Count() == flagMaxLength || reduceHeapAllocs { + forceRemove = true + } - if m.Count() == maxLength || reduceHeapAllocs { - nextOp = mapRemoveOp + var prevOp mapOpType + expectedValues, prevOp, err = modifyMap(expectedValues, m, maxNestedLevels, forceRemove) + if err != nil { + fmt.Fprint(os.Stderr, err.Error()) + return } - switch nextOp { + opCountForStorageHealthCheck++ - case mapSetOp1, mapSetOp2, mapSetOp3: - opCount++ + // Update status + status.incOp(prevOp, m.Count()) - k, err := randomKey() - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to generate random key %s: %s", k, err) - return - } + // Check map elements against elements after every op + err = checkMapDataLoss(expectedValues, m) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } - nestedLevels := r.Intn(maxNestedLevels) - v, err := randomValue(storage, address, nestedLevels) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to generate random value %s: %s", v, err) - return - } + if opCountForStorageHealthCheck >= flagMinOpsForStorageHealthCheck { + opCountForStorageHealthCheck = 0 - copiedKey, err := copyValue(storage, atree.Address{}, k) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to copy random key %s: %s", k, err) + if !checkStorageHealth(storage, m.SlabID()) { return } - copiedValue, err := copyValue(storage, atree.Address{}, v) + // Commit slabs to storage so slabs are encoded and then decoded at next op. + err = storage.FastCommit(runtime.NumCPU()) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to copy random value %s: %s", k, err) + fmt.Fprintf(os.Stderr, "Failed to commit to storage: %s", err) return } - oldV := elements[copiedKey] + // Drop cache after commit to force slab decoding at next op. + storage.DropCache() + } + } +} - // Update keys - if oldV == nil { - keys = append(keys, copiedKey) - } +func nextMapOp( + expectedValues mapValue, + m *atree.OrderedMap, + nestedLevels int, + forceRemove bool, +) (mapOpType, error) { - // Update elements - elements[copiedKey] = copiedValue + if forceRemove { + if m.Count() == 0 { + return 0, fmt.Errorf("failed to force remove map elements because map has no elements") + } + return mapRemoveOp, nil + } - // Update map - existingStorable, err := m.Set(compare, hashInputProvider, k, v) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to set %s at index %d: %s", v, k, err) - return - } + if m.Count() == 0 { + return mapSetOp1, nil + } - // Compare old value from map with old value from elements - if (oldV == nil) && (existingStorable != nil) { - fmt.Fprintf(os.Stderr, "Set returned storable %s, want nil", existingStorable) - return - } + for { + nextOp := mapOpType(r.Intn(int(maxMapOp))) - if (oldV != nil) && (existingStorable == nil) { - fmt.Fprintf(os.Stderr, "Set returned nil, want %s", oldV) - return + switch nextOp { + case mapMutateChildContainerAfterSet: + if nestedLevels-1 > 0 { + return nextOp, nil } - if existingStorable != nil { - - existingValue, err := existingStorable.StoredValue(storage) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to convert %s to value: %s", existingStorable, err) - return - } - - err = valueEqual(oldV, existingValue) - if err != nil { - fmt.Fprintf(os.Stderr, "Set() returned wrong existing value %s, want %s", existingValue, oldV) - return - } - - err = removeStorable(storage, existingStorable) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove map storable element %s: %s", existingStorable, err) - return - } - - err = removeValue(storage, oldV) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove copied overwritten value %s: %s", existingValue, err) - return - } + // New child container can't be created because next nestedLevels is 0. + // Try another map operation. + + case mapMutateChildContainerAfterGet: + if hasChildContainerInMap(expectedValues) { + return nextOp, nil } - // Update status - status.incSet(oldV == nil) + // Map doesn't have child container, try another map operation. - case mapRemoveOp: - if m.Count() == 0 { - continue - } + default: + return nextOp, nil + } + } +} - opCount++ +func modifyMap( + expectedValues mapValue, + m *atree.OrderedMap, + nestedLevels int, + forceRemove bool, +) (mapValue, mapOpType, error) { - index := r.Intn(len(keys)) - k := keys[index] + storage := m.Storage + address := m.Address() - oldV := elements[k] + nextOp, err := nextMapOp(expectedValues, m, nestedLevels, forceRemove) + if err != nil { + return nil, 0, err + } - // Update elements - delete(elements, k) + switch nextOp { + case mapSetOp1, mapSetOp2, mapSetOp3, mapMutateChildContainerAfterSet: - // Update keys - copy(keys[index:], keys[index+1:]) - keys[len(keys)-1] = nil - keys = keys[:len(keys)-1] + var nextNestedLevels int - // Update map - existingKeyStorable, existingValueStorable, err := m.Remove(compare, hashInputProvider, k) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove element with key %s: %s", k, err) - return - } + switch nextOp { + case mapSetOp1, mapSetOp2, mapSetOp3: + nextNestedLevels = r.Intn(nestedLevels) + case mapMutateChildContainerAfterSet: + nextNestedLevels = nestedLevels - 1 + default: + panic("not reachable") + } - // Compare removed key from map with removed key from elements - existingKeyValue, err := existingKeyStorable.StoredValue(storage) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to convert %s to value: %s", existingKeyStorable, err) - return - } + expectedKey, key, err := randomKey() + if err != nil { + return nil, 0, fmt.Errorf("failed to generate random key %s: %s", key, err) + } - err = valueEqual(k, existingKeyValue) - if err != nil { - fmt.Fprintf(os.Stderr, "Remove() returned wrong existing key %s, want %s", existingKeyStorable, k) - return - } + expectedChildValue, child, err := randomValue(storage, address, nextNestedLevels) + if err != nil { + return nil, 0, fmt.Errorf("failed to generate random value %s: %s", child, err) + } - // Compare removed value from map with removed value from elements - existingValue, err := existingValueStorable.StoredValue(storage) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to convert %s to value: %s", existingValueStorable, err) - return - } + oldExpectedValue := expectedValues[expectedKey] - err = valueEqual(oldV, existingValue) - if err != nil { - fmt.Fprintf(os.Stderr, "Remove() returned wrong existing value %s, want %s", existingValueStorable, oldV) - return - } + // Update expectedValues + expectedValues[expectedKey] = expectedChildValue + + // Update map + existingStorable, err := m.Set(compare, hashInputProvider, key, child) + if err != nil { + return nil, 0, fmt.Errorf("failed to set %s at index %d: %s", child, key, err) + } - err = removeStorable(storage, existingKeyStorable) + // Compare old value from map with old value from elements + if (oldExpectedValue == nil) != (existingStorable == nil) { + return nil, 0, fmt.Errorf("Set returned storable %s != expected %s", existingStorable, oldExpectedValue) + } + + if existingStorable != nil { + + existingValue, err := existingStorable.StoredValue(storage) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove key %s: %s", existingKeyStorable, err) - return + return nil, 0, fmt.Errorf("failed to convert %s to value: %s", existingStorable, err) } - err = removeStorable(storage, existingValueStorable) + err = valueEqual(oldExpectedValue, existingValue) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove value %s: %s", existingValueStorable, err) - return + return nil, 0, fmt.Errorf("Set() returned wrong existing value %s, want %s", existingValue, oldExpectedValue) } - err = removeValue(storage, k) + // Delete removed element from storage + err = removeStorable(storage, existingStorable) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove copied key %s: %s", k, err) - return + return nil, 0, fmt.Errorf("failed to remove map storable element %s: %s", existingStorable, err) } + } - err = removeValue(storage, oldV) + if nextOp == mapMutateChildContainerAfterSet { + expectedValues[expectedKey], err = modifyContainer(expectedValues[expectedKey], child, nextNestedLevels) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to remove copied value %s: %s", existingValue, err) - return + return nil, 0, fmt.Errorf("failed to modify child container at key %s: %w", expectedKey, err) } + } - // Update status - status.incRemove() + case mapRemoveOp: + // Use for-range on Go map to get random key + var key atree.Value + for k := range expectedValues { + key = k + break } - // Check map elements against elements after every op - err = checkMapDataLoss(m, elements) + oldExpectedValue := expectedValues[key] + + // Update expectedValues + delete(expectedValues, key) + + // Update map + existingKeyStorable, existingValueStorable, err := m.Remove(compare, hashInputProvider, key) if err != nil { - fmt.Fprintln(os.Stderr, err) - return + return nil, 0, fmt.Errorf("failed to remove element with key %s: %s", key, err) } - if opCount >= 100 { - opCount = 0 - rootIDs, err := atree.CheckStorageHealth(storage, -1) - if err != nil { - fmt.Fprintln(os.Stderr, err) - return - } - ids := make([]atree.SlabID, 0, len(rootIDs)) - for id := range rootIDs { - // filter out root ids with empty address - if !id.HasTempAddress() { - ids = append(ids, id) - } - } - if len(ids) != 1 || ids[0] != m.SlabID() { - fmt.Fprintf(os.Stderr, "root slab ids %v in storage, want %s\n", ids, m.SlabID()) - return - } + // Compare removed key from map with removed key from elements + existingKeyValue, err := existingKeyStorable.StoredValue(storage) + if err != nil { + return nil, 0, fmt.Errorf("failed to convert %s to value: %s", existingKeyStorable, err) + } + + err = valueEqual(key, existingKeyValue) + if err != nil { + return nil, 0, fmt.Errorf("Remove() returned wrong existing key %s, want %s", existingKeyStorable, key) + } + + // Compare removed value from map with removed value from elements + existingValue, err := existingValueStorable.StoredValue(storage) + if err != nil { + return nil, 0, fmt.Errorf("failed to convert %s to value: %s", existingValueStorable, err) + } + + err = valueEqual(oldExpectedValue, existingValue) + if err != nil { + return nil, 0, fmt.Errorf("Remove() returned wrong existing value %s, want %s", existingValueStorable, oldExpectedValue) + } + + // Delete removed element from storage + err = removeStorable(storage, existingKeyStorable) + if err != nil { + return nil, 0, fmt.Errorf("failed to remove key %s: %s", existingKeyStorable, err) + } + + err = removeStorable(storage, existingValueStorable) + if err != nil { + return nil, 0, fmt.Errorf("failed to remove value %s: %s", existingValueStorable, err) + } + + case mapMutateChildContainerAfterGet: + key, found := getRandomChildContainerKeyInMap(expectedValues) + if !found { + // mapMutateChildContainerAfterGet op can't be performed because there isn't any child container in this map. + // Try another map operation. + return modifyMap(expectedValues, m, nestedLevels, forceRemove) + } + + child, err := m.Get(compare, hashInputProvider, key) + if err != nil { + return nil, 0, fmt.Errorf("failed to get element from map at key %s: %s", key, err) + } + + expectedValues[key], err = modifyContainer(expectedValues[key], child, nestedLevels-1) + if err != nil { + return nil, 0, fmt.Errorf("failed to modify child container at key %s: %w", key, err) + } + } + + return expectedValues, nextOp, nil +} + +func hasChildContainerInMap(expectedValues mapValue) bool { + for _, v := range expectedValues { + switch v.(type) { + case arrayValue, mapValue: + return true } } + return false } -func checkMapDataLoss(m *atree.OrderedMap, elements map[atree.Value]atree.Value) error { +func getRandomChildContainerKeyInMap(expectedValues mapValue) (key atree.Value, found bool) { + keys := make([]atree.Value, 0, len(expectedValues)) + for k, v := range expectedValues { + switch v.(type) { + case arrayValue, mapValue: + keys = append(keys, k) + } + } + if len(keys) == 0 { + return nil, false + } + return keys[r.Intn(len(keys))], true +} + +func checkMapDataLoss(expectedValues mapValue, m *atree.OrderedMap) error { // Check map has the same number of elements as elements - if m.Count() != uint64(len(elements)) { - return fmt.Errorf("Count() %d != len(values) %d", m.Count(), len(elements)) + if m.Count() != uint64(len(expectedValues)) { + return fmt.Errorf("Count() %d != len(values) %d", m.Count(), len(expectedValues)) } // Check every element - for k, v := range elements { + for k, v := range expectedValues { convertedValue, err := m.Get(compare, hashInputProvider, k) if err != nil { return fmt.Errorf("failed to get element with key %s: %w", k, err) @@ -400,5 +467,30 @@ func checkMapDataLoss(m *atree.OrderedMap, elements map[atree.Value]atree.Value) } } + if flagCheckSlabEnabled { + err := checkMapSlab(m) + if err != nil { + return err + } + } + return nil } + +func checkMapSlab(m *atree.OrderedMap) error { + err := atree.VerifyMap(m, m.Address(), m.Type(), typeInfoComparator, hashInputProvider, true) + if err != nil { + return err + } + + return atree.VerifyMapSerialization( + m, + cborDecMode, + cborEncMode, + decodeStorable, + decodeTypeInfo, + func(a, b atree.Storable) bool { + return reflect.DeepEqual(a, b) + }, + ) +} diff --git a/cmd/stress/storable.go b/cmd/stress/storable.go index a2bdf1da..0aaf1aa4 100644 --- a/cmd/stress/storable.go +++ b/cmd/stress/storable.go @@ -413,7 +413,7 @@ func (v StringValue) String() string { return v.str } -func decodeStorable(dec *cbor.StreamDecoder, _ atree.SlabID, _ []atree.ExtraData) (atree.Storable, error) { +func decodeStorable(dec *cbor.StreamDecoder, id atree.SlabID, inlinedExtraData []atree.ExtraData) (atree.Storable, error) { t, err := dec.NextType() if err != nil { return nil, err @@ -435,6 +435,15 @@ func decodeStorable(dec *cbor.StreamDecoder, _ atree.SlabID, _ []atree.ExtraData switch tagNumber { + case atree.CBORTagInlinedArray: + return atree.DecodeInlinedArrayStorable(dec, decodeStorable, id, inlinedExtraData) + + case atree.CBORTagInlinedMap: + return atree.DecodeInlinedMapStorable(dec, decodeStorable, id, inlinedExtraData) + + case atree.CBORTagInlinedCompactMap: + return atree.DecodeInlinedCompactMapStorable(dec, decodeStorable, id, inlinedExtraData) + case atree.CBORTagSlabID: return atree.DecodeSlabIDStorable(dec) diff --git a/cmd/stress/typeinfo.go b/cmd/stress/typeinfo.go index ec78239f..6a1fafb1 100644 --- a/cmd/stress/typeinfo.go +++ b/cmd/stress/typeinfo.go @@ -26,38 +26,107 @@ import ( "github.com/fxamacker/cbor/v2" ) -type testTypeInfo struct { - value uint64 +const ( + maxArrayTypeValue = 10 + maxMapTypeValue = 10 + + arrayTypeTagNum = 246 + mapTypeTagNum = 245 +) + +type arrayTypeInfo struct { + value int +} + +func newArrayTypeInfo() arrayTypeInfo { + return arrayTypeInfo{value: r.Intn(maxArrayTypeValue)} +} + +var _ atree.TypeInfo = arrayTypeInfo{} + +func (i arrayTypeInfo) Copy() atree.TypeInfo { + return i +} + +func (i arrayTypeInfo) IsComposite() bool { + return false +} + +func (i arrayTypeInfo) ID() string { + return fmt.Sprintf("array(%d)", i) +} + +func (i arrayTypeInfo) Encode(e *cbor.StreamEncoder) error { + err := e.EncodeTagHead(arrayTypeTagNum) + if err != nil { + return err + } + return e.EncodeInt64(int64(i.value)) +} + +func (i arrayTypeInfo) Equal(other atree.TypeInfo) bool { + otherArrayTypeInfo, ok := other.(arrayTypeInfo) + return ok && i.value == otherArrayTypeInfo.value +} + +type mapTypeInfo struct { + value int } -var _ atree.TypeInfo = testTypeInfo{} +var _ atree.TypeInfo = mapTypeInfo{} -func (i testTypeInfo) Copy() atree.TypeInfo { +func newMapTypeInfo() mapTypeInfo { + return mapTypeInfo{value: r.Intn(maxMapTypeValue)} +} + +func (i mapTypeInfo) Copy() atree.TypeInfo { return i } -func (i testTypeInfo) IsComposite() bool { +func (i mapTypeInfo) IsComposite() bool { return false } -func (i testTypeInfo) ID() string { - return fmt.Sprintf("uint64(%d)", i) +func (i mapTypeInfo) ID() string { + return fmt.Sprintf("map(%d)", i) } -func (i testTypeInfo) Encode(e *cbor.StreamEncoder) error { - return e.EncodeUint64(i.value) +func (i mapTypeInfo) Encode(e *cbor.StreamEncoder) error { + err := e.EncodeTagHead(mapTypeTagNum) + if err != nil { + return err + } + return e.EncodeInt64(int64(i.value)) } -func (i testTypeInfo) Equal(other atree.TypeInfo) bool { - otherTestTypeInfo, ok := other.(testTypeInfo) - return ok && i.value == otherTestTypeInfo.value +func (i mapTypeInfo) Equal(other atree.TypeInfo) bool { + otherMapTypeInfo, ok := other.(mapTypeInfo) + return ok && i.value == otherMapTypeInfo.value } func decodeTypeInfo(dec *cbor.StreamDecoder) (atree.TypeInfo, error) { - value, err := dec.DecodeUint64() + num, err := dec.DecodeTagNumber() if err != nil { return nil, err } + switch num { + case arrayTypeTagNum: + value, err := dec.DecodeInt64() + if err != nil { + return nil, err + } + + return arrayTypeInfo{value: int(value)}, nil - return testTypeInfo{value: value}, nil + case mapTypeTagNum: + value, err := dec.DecodeInt64() + if err != nil { + return nil, err + } + + return mapTypeInfo{value: int(value)}, nil + + default: + return nil, fmt.Errorf("failed to decode type info with tag number %d", num) + } } diff --git a/cmd/stress/utils.go b/cmd/stress/utils.go index 96f72584..cd143bd4 100644 --- a/cmd/stress/utils.go +++ b/cmd/stress/utils.go @@ -20,6 +20,7 @@ package main import ( "fmt" + "math" "math/rand" "reflect" "time" @@ -40,9 +41,13 @@ const ( uint64Type smallStringType largeStringType - arrayType + maxSimpleValueType +) + +const ( + arrayType int = iota mapType - maxValueType + maxContainerValueType ) var ( @@ -68,136 +73,81 @@ func randStr(n int) string { return string(runes) } -func generateValue(storage *atree.PersistentSlabStorage, address atree.Address, valueType int, nestedLevels int) (atree.Value, error) { +func generateSimpleValue( + valueType int, +) (expected atree.Value, actual atree.Value, err error) { switch valueType { case uint8Type: - return Uint8Value(r.Intn(255)), nil + v := Uint8Value(r.Intn(math.MaxUint8)) // 255 + return v, v, nil + case uint16Type: - return Uint16Value(r.Intn(6535)), nil + v := Uint16Value(r.Intn(math.MaxUint16)) // 65535 + return v, v, nil + case uint32Type: - return Uint32Value(r.Intn(4294967295)), nil + v := Uint32Value(r.Intn(math.MaxUint32)) // 4294967295 + return v, v, nil + case uint64Type: - return Uint64Value(r.Intn(1844674407370955161)), nil + v := Uint64Value(r.Intn(math.MaxInt)) // 9_223_372_036_854_775_807 + return v, v, nil + case smallStringType: slen := r.Intn(125) - return NewStringValue(randStr(slen)), nil + v := NewStringValue(randStr(slen)) + return v, v, nil + case largeStringType: - slen := r.Intn(125) + 1024 - return NewStringValue(randStr(slen)), nil + slen := r.Intn(125) + 1024/2 + v := NewStringValue(randStr(slen)) + return v, v, nil + + default: + return nil, nil, fmt.Errorf("unexpected randome simple value type %d", valueType) + } +} + +func generateContainerValue( + valueType int, + storage atree.SlabStorage, + address atree.Address, + nestedLevels int, +) (expected atree.Value, actual atree.Value, err error) { + switch valueType { case arrayType: length := r.Intn(maxNestedArraySize) return newArray(storage, address, length, nestedLevels) + case mapType: length := r.Intn(maxNestedMapSize) return newMap(storage, address, length, nestedLevels) - default: - return Uint8Value(r.Intn(255)), nil - } -} - -func randomKey() (atree.Value, error) { - t := r.Intn(largeStringType + 1) - return generateValue(nil, atree.Address{}, t, 0) -} - -func randomValue(storage *atree.PersistentSlabStorage, address atree.Address, nestedLevels int) (atree.Value, error) { - var t int - if nestedLevels <= 0 { - t = r.Intn(largeStringType + 1) - } else { - t = r.Intn(maxValueType) - } - return generateValue(storage, address, t, nestedLevels) -} -func copyValue(storage *atree.PersistentSlabStorage, address atree.Address, value atree.Value) (atree.Value, error) { - switch v := value.(type) { - case Uint8Value: - return Uint8Value(uint8(v)), nil - case Uint16Value: - return Uint16Value(uint16(v)), nil - case Uint32Value: - return Uint32Value(uint32(v)), nil - case Uint64Value: - return Uint64Value(uint64(v)), nil - case StringValue: - return NewStringValue(v.str), nil - case *atree.Array: - return copyArray(storage, address, v) - case *atree.OrderedMap: - return copyMap(storage, address, v) default: - return nil, fmt.Errorf("failed to copy value: value type %T isn't supported", v) + return nil, nil, fmt.Errorf("unexpected randome container value type %d", valueType) } } -func copyArray(storage *atree.PersistentSlabStorage, address atree.Address, array *atree.Array) (*atree.Array, error) { - iterator, err := array.ReadOnlyIterator() - if err != nil { - return nil, err - } - return atree.NewArrayFromBatchData(storage, address, array.Type(), func() (atree.Value, error) { - v, err := iterator.Next() - if err != nil { - return nil, err - } - if v == nil { - return nil, nil - } - return copyValue(storage, address, v) - }) +func randomKey() (atree.Value, atree.Value, error) { + t := r.Intn(maxSimpleValueType) + return generateSimpleValue(t) } -func copyMap(storage *atree.PersistentSlabStorage, address atree.Address, m *atree.OrderedMap) (*atree.OrderedMap, error) { - iterator, err := m.ReadOnlyIterator() - if err != nil { - return nil, err +func randomValue( + storage atree.SlabStorage, + address atree.Address, + nestedLevels int, +) (expected atree.Value, actual atree.Value, err error) { + if nestedLevels <= 0 { + t := r.Intn(maxSimpleValueType) + return generateSimpleValue(t) } - return atree.NewMapFromBatchData( - storage, - address, - atree.NewDefaultDigesterBuilder(), - m.Type(), - compare, - hashInputProvider, - m.Seed(), - func() (atree.Value, atree.Value, error) { - k, v, err := iterator.Next() - if err != nil { - return nil, nil, err - } - if k == nil { - return nil, nil, nil - } - copiedKey, err := copyValue(storage, address, k) - if err != nil { - return nil, nil, err - } - copiedValue, err := copyValue(storage, address, v) - if err != nil { - return nil, nil, err - } - return copiedKey, copiedValue, nil - }) -} -func removeValue(storage *atree.PersistentSlabStorage, value atree.Value) error { - switch v := value.(type) { - case *atree.Array: - return removeStorable(storage, atree.SlabIDStorable(v.SlabID())) - case *atree.OrderedMap: - return removeStorable(storage, atree.SlabIDStorable(v.SlabID())) - } - return nil + t := r.Intn(maxContainerValueType) + return generateContainerValue(t, storage, address, nestedLevels) } -func removeStorable(storage *atree.PersistentSlabStorage, storable atree.Storable) error { - sid, ok := storable.(atree.SlabIDStorable) - if !ok { - return nil - } - - id := atree.SlabID(sid) +func removeStorable(storage atree.SlabStorage, storable atree.Storable) error { value, err := storable.StoredValue(storage) if err != nil { @@ -205,8 +155,6 @@ func removeStorable(storage *atree.PersistentSlabStorage, storable atree.Storabl } switch v := value.(type) { - case StringValue: - return storage.Remove(id) case *atree.Array: err := v.PopIterate(func(storable atree.Storable) { _ = removeStorable(storage, storable) @@ -214,7 +162,6 @@ func removeStorable(storage *atree.PersistentSlabStorage, storable atree.Storabl if err != nil { return err } - return storage.Remove(id) case *atree.OrderedMap: err := v.PopIterate(func(keyStorable atree.Storable, valueStorable atree.Storable) { @@ -224,237 +171,218 @@ func removeStorable(storage *atree.PersistentSlabStorage, storable atree.Storabl if err != nil { return err } - return storage.Remove(id) + } - default: - return fmt.Errorf("failed to remove storable: storable type %T isn't supported", v) + if sid, ok := storable.(atree.SlabIDStorable); ok { + return storage.Remove(atree.SlabID(sid)) } + + return nil } -func valueEqual(a atree.Value, b atree.Value) error { - switch a.(type) { +func valueEqual(expected atree.Value, actual atree.Value) error { + switch expected := expected.(type) { + case arrayValue: + actual, ok := actual.(*atree.Array) + if !ok { + return fmt.Errorf("failed to convert actual value to *Array, got %T", actual) + } + + return arrayEqual(expected, actual) + case *atree.Array: - return arrayEqual(a, b) + return fmt.Errorf("expected value shouldn't be *Array") + + case mapValue: + actual, ok := actual.(*atree.OrderedMap) + if !ok { + return fmt.Errorf("failed to convert actual value to *OrderedMap, got %T", actual) + } + + return mapEqual(expected, actual) + case *atree.OrderedMap: - return mapEqual(a, b) + return fmt.Errorf("expected value shouldn't be *OrderedMap") + default: - if !reflect.DeepEqual(a, b) { - return fmt.Errorf("value %s (%T) != value %s (%T)", a, a, b, b) + if !reflect.DeepEqual(expected, actual) { + return fmt.Errorf("expected value %v (%T) != actual value %v (%T)", expected, expected, actual, actual) } } + return nil } -func arrayEqual(a atree.Value, b atree.Value) error { - array1, ok := a.(*atree.Array) - if !ok { - return fmt.Errorf("value %s type is %T, want *atree.Array", a, a) +func arrayEqual(expected arrayValue, actual *atree.Array) error { + if uint64(len(expected)) != actual.Count() { + return fmt.Errorf("array count %d != expected count %d", actual.Count(), len(expected)) } - array2, ok := b.(*atree.Array) - if !ok { - return fmt.Errorf("value %s type is %T, want *atree.Array", b, b) - } - - if array1.Count() != array2.Count() { - return fmt.Errorf("array %s count %d != array %s count %d", array1, array1.Count(), array2, array2.Count()) - } - - iterator1, err := array1.ReadOnlyIterator() + iterator, err := actual.ReadOnlyIterator() if err != nil { - return fmt.Errorf("failed to get array1 iterator: %w", err) - } - - iterator2, err := array2.ReadOnlyIterator() - if err != nil { - return fmt.Errorf("failed to get array2 iterator: %w", err) + return fmt.Errorf("failed to get array iterator: %w", err) } + i := 0 for { - value1, err := iterator1.Next() + actualValue, err := iterator.Next() if err != nil { - return fmt.Errorf("iterator1.Next() error: %w", err) + return fmt.Errorf("iterator.Next() error: %w", err) } - value2, err := iterator2.Next() - if err != nil { - return fmt.Errorf("iterator2.Next() error: %w", err) + if actualValue == nil { + break + } + + if i >= len(expected) { + return fmt.Errorf("more elements from array iterator than expected") } - err = valueEqual(value1, value2) + err = valueEqual(expected[i], actualValue) if err != nil { return fmt.Errorf("array elements are different: %w", err) } - if value1 == nil || value2 == nil { - break - } + i++ } - return nil -} - -func mapEqual(a atree.Value, b atree.Value) error { - m1, ok := a.(*atree.OrderedMap) - if !ok { - return fmt.Errorf("value %s type is %T, want *atree.OrderedMap", a, a) + if i != len(expected) { + return fmt.Errorf("got %d iterated array elements, expect %d values", i, len(expected)) } - m2, ok := b.(*atree.OrderedMap) - if !ok { - return fmt.Errorf("value %s type is %T, want *atree.OrderedMap", b, b) - } - - if m1.Count() != m2.Count() { - return fmt.Errorf("map %s count %d != map %s count %d", m1, m1.Count(), m2, m2.Count()) - } + return nil +} - iterator1, err := m1.ReadOnlyIterator() - if err != nil { - return fmt.Errorf("failed to get m1 iterator: %w", err) +func mapEqual(expected mapValue, actual *atree.OrderedMap) error { + if uint64(len(expected)) != actual.Count() { + return fmt.Errorf("map count %d != expected count %d", actual.Count(), len(expected)) } - iterator2, err := m2.ReadOnlyIterator() + iterator, err := actual.ReadOnlyIterator() if err != nil { - return fmt.Errorf("failed to get m2 iterator: %w", err) + return fmt.Errorf("failed to get map iterator: %w", err) } + i := 0 for { - key1, value1, err := iterator1.Next() + actualKey, actualValue, err := iterator.Next() if err != nil { - return fmt.Errorf("iterator1.Next() error: %w", err) + return fmt.Errorf("iterator.Next() error: %w", err) } - key2, value2, err := iterator2.Next() - if err != nil { - return fmt.Errorf("iterator2.Next() error: %w", err) + if actualKey == nil { + break } - err = valueEqual(key1, key2) - if err != nil { - return fmt.Errorf("map keys are different: %w", err) + expectedValue, exist := expected[actualKey] + if !exist { + return fmt.Errorf("failed to find key %v in expected values", actualKey) } - err = valueEqual(value1, value2) + err = valueEqual(expectedValue, actualValue) if err != nil { return fmt.Errorf("map values are different: %w", err) } - if key1 == nil || key2 == nil { - break - } + i++ + } + + if i != len(expected) { + return fmt.Errorf("got %d iterated map elements, expect %d values", i, len(expected)) } return nil } // newArray creates atree.Array with random elements of specified size and nested level -func newArray(storage *atree.PersistentSlabStorage, address atree.Address, length int, nestedLevel int) (*atree.Array, error) { - typeInfo := testTypeInfo{value: 123} +func newArray( + storage atree.SlabStorage, + address atree.Address, + length int, + nestedLevel int, +) (arrayValue, *atree.Array, error) { + + typeInfo := newArrayTypeInfo() array, err := atree.NewArray(storage, address, typeInfo) if err != nil { - return nil, fmt.Errorf("failed to create new array: %w", err) + return nil, nil, fmt.Errorf("failed to create new array: %w", err) } - values := make([]atree.Value, length) + expectedValues := make(arrayValue, length) for i := 0; i < length; i++ { - value, err := randomValue(storage, address, nestedLevel-1) - if err != nil { - return nil, err - } - copedValue, err := copyValue(storage, atree.Address{}, value) + expectedValue, value, err := randomValue(storage, address, nestedLevel-1) if err != nil { - return nil, err + return nil, nil, err } - values[i] = copedValue + + expectedValues[i] = expectedValue + err = array.Append(value) if err != nil { - return nil, err + return nil, nil, err } } - err = checkArrayDataLoss(array, values) + err = checkArrayDataLoss(expectedValues, array) if err != nil { - return nil, err - } - - for _, v := range values { - err := removeValue(storage, v) - if err != nil { - return nil, err - } + return nil, nil, err } - return array, nil + return expectedValues, array, nil } // newMap creates atree.OrderedMap with random elements of specified size and nested level -func newMap(storage *atree.PersistentSlabStorage, address atree.Address, length int, nestedLevel int) (*atree.OrderedMap, error) { - typeInfo := testTypeInfo{value: 123} +func newMap( + storage atree.SlabStorage, + address atree.Address, + length int, + nestedLevel int, +) (mapValue, *atree.OrderedMap, error) { + + typeInfo := newMapTypeInfo() m, err := atree.NewMap(storage, address, atree.NewDefaultDigesterBuilder(), typeInfo) if err != nil { - return nil, fmt.Errorf("failed to create new map: %w", err) + return nil, nil, fmt.Errorf("failed to create new map: %w", err) } - elements := make(map[atree.Value]atree.Value, length) + expectedValues := make(mapValue, length) - for i := 0; i < length; i++ { - k, err := randomKey() - if err != nil { - return nil, err - } - - copiedKey, err := copyValue(storage, atree.Address{}, k) + for m.Count() < uint64(length) { + expectedKey, key, err := randomKey() if err != nil { - return nil, err + return nil, nil, err } - v, err := randomValue(storage, address, nestedLevel-1) + expectedValue, value, err := randomValue(storage, address, nestedLevel-1) if err != nil { - return nil, err + return nil, nil, err } - copiedValue, err := copyValue(storage, atree.Address{}, v) - if err != nil { - return nil, err - } + expectedValues[expectedKey] = expectedValue - elements[copiedKey] = copiedValue - - existingStorable, err := m.Set(compare, hashInputProvider, k, v) + existingStorable, err := m.Set(compare, hashInputProvider, key, value) if err != nil { - return nil, err + return nil, nil, err } if existingStorable != nil { // Delete overwritten element err = removeStorable(storage, existingStorable) if err != nil { - return nil, fmt.Errorf("failed to remove storable element %s: %w", existingStorable, err) + return nil, nil, fmt.Errorf("failed to remove storable element %s: %w", existingStorable, err) } } } - err = checkMapDataLoss(m, elements) + err = checkMapDataLoss(expectedValues, m) if err != nil { - return nil, err + return nil, nil, err } - for k, v := range elements { - err := removeValue(storage, k) - if err != nil { - return nil, err - } - err = removeValue(storage, v) - if err != nil { - return nil, err - } - } - - return m, nil + return expectedValues, m, nil } type InMemBaseStorage struct { @@ -542,3 +470,27 @@ func (s *InMemBaseStorage) SegmentsTouched() int { func (s *InMemBaseStorage) ResetReporter() { // not needed } + +// arrayValue is an atree.Value that represents an array of atree.Value. +// It's used to test elements of atree.Array. +type arrayValue []atree.Value + +var _ atree.Value = &arrayValue{} + +func (v arrayValue) Storable(atree.SlabStorage, atree.Address, uint64) (atree.Storable, error) { + panic("not reachable") +} + +// mapValue is an atree.Value that represents a map of atree.Value. +// It's used to test elements of atree.OrderedMap. +type mapValue map[atree.Value]atree.Value + +var _ atree.Value = &mapValue{} + +func (v mapValue) Storable(atree.SlabStorage, atree.Address, uint64) (atree.Storable, error) { + panic("not reachable") +} + +var typeInfoComparator = func(a atree.TypeInfo, b atree.TypeInfo) bool { + return a.ID() == b.ID() +}