github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/storage/shard_foreachentry_prop_test.go (about) 1 // +build big 2 // 3 // Copyright (c) 2018 Uber Technologies, Inc. 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 // THE SOFTWARE. 22 23 package storage 24 25 import ( 26 "fmt" 27 "math/rand" 28 "os" 29 "sync" 30 "testing" 31 "time" 32 33 "github.com/m3db/m3/src/dbnode/namespace" 34 "github.com/m3db/m3/src/x/context" 35 "github.com/m3db/m3/src/x/ident" 36 xtime "github.com/m3db/m3/src/x/time" 37 38 "github.com/leanovate/gopter" 39 "github.com/leanovate/gopter/gen" 40 "github.com/leanovate/gopter/prop" 41 "github.com/stretchr/testify/require" 42 ) 43 44 func TestShardConcurrentForEaches(t *testing.T) { 45 parameters := gopter.DefaultTestParameters() 46 seed := time.Now().UnixNano() 47 parameters.MinSuccessfulTests = 200 48 parameters.MaxSize = 40 49 parameters.MaxDiscardRatio = 20 50 parameters.Rng = rand.New(rand.NewSource(seed)) 51 properties := gopter.NewProperties(parameters) 52 53 properties.Property(`ForEachShardEntry does not change the shard entries ref count`, prop.ForAll( 54 func(entries []shardEntryState, workFns []dbShardEntryBatchWorkFn) (bool, error) { 55 result := testShardConcurrentForEach(t, entries, workFns) 56 if result.Status == gopter.PropTrue { 57 return true, nil 58 } 59 return false, result.Error 60 }, 61 genShardEntryStates(), 62 genBatchWorkFns(), 63 )) 64 reporter := gopter.NewFormatedReporter(true, 160, os.Stdout) 65 if !properties.Run(reporter) { 66 t.Errorf("failed with initial seed: %d", seed) 67 } 68 } 69 70 func TestShardConcurrentForEachesAndTick(t *testing.T) { 71 parameters := gopter.DefaultTestParameters() 72 seed := time.Now().UnixNano() 73 parameters.MinSuccessfulTests = 200 74 parameters.MaxSize = 40 75 parameters.MaxDiscardRatio = 20 76 parameters.Rng = rand.New(rand.NewSource(seed)) 77 properties := gopter.NewProperties(parameters) 78 79 properties.Property(`Concurrent ForEachShardEntry and Tick does not panic`, prop.ForAll( 80 func(entries []shardEntryState, workFns []dbShardEntryBatchWorkFn) bool { 81 testShardConcurrentForEachTick(t, entries, workFns) 82 return true 83 }, 84 genShardEntryStates(), 85 genBatchWorkFns(), 86 )) 87 reporter := gopter.NewFormatedReporter(true, 160, os.Stdout) 88 if !properties.Run(reporter) { 89 t.Errorf("failed with initial seed: %d", seed) 90 } 91 } 92 93 func testShardConcurrentForEachTick( 94 t *testing.T, 95 entries []shardEntryState, 96 workFns []dbShardEntryBatchWorkFn, 97 ) { 98 shard, opts := propTestDatabaseShard(t, 10) 99 defer func() { 100 shard.Close() 101 opts.RuntimeOptionsManager().Close() 102 }() 103 104 for _, entry := range entries { 105 addTestSeriesWithCount(shard, entry.id, entry.refCount) 106 } 107 108 require.NoError(t, shardEntriesAreEqual(shard, entries)) 109 110 var ( 111 numRoutines = len(workFns) + 1 112 barrier = make(chan struct{}, numRoutines) 113 wg sync.WaitGroup 114 ) 115 116 wg.Add(numRoutines) 117 118 for _, fn := range workFns { 119 fn := fn 120 go func() { 121 <-barrier 122 shard.forEachShardEntryBatch(fn) 123 wg.Done() 124 }() 125 } 126 127 go func() { 128 <-barrier 129 _, err := shard.Tick(context.NewNoOpCanncellable(), xtime.Now(), namespace.Context{}) 130 require.NoError(t, err) 131 wg.Done() 132 }() 133 134 for i := 0; i < numRoutines; i++ { 135 barrier <- struct{}{} 136 } 137 138 wg.Wait() 139 } 140 141 func testShardConcurrentForEach( 142 t *testing.T, 143 entries []shardEntryState, 144 workFns []dbShardEntryBatchWorkFn, 145 ) gopter.PropResult { 146 shard, opts := propTestDatabaseShard(t, 10) 147 defer func() { 148 shard.Close() 149 opts.RuntimeOptionsManager().Close() 150 }() 151 152 for _, entry := range entries { 153 addTestSeriesWithCount(shard, entry.id, entry.refCount) 154 } 155 156 require.NoError(t, shardEntriesAreEqual(shard, entries)) 157 158 var ( 159 numRoutines = len(workFns) 160 barrier = make(chan struct{}, numRoutines) 161 wg sync.WaitGroup 162 ) 163 164 wg.Add(numRoutines) 165 166 for _, fn := range workFns { 167 fn := fn 168 go func() { 169 <-barrier 170 shard.forEachShardEntryBatch(fn) 171 wg.Done() 172 }() 173 } 174 175 for i := 0; i < numRoutines; i++ { 176 barrier <- struct{}{} 177 } 178 179 wg.Wait() 180 181 if err := shardEntriesAreEqual(shard, entries); err != nil { 182 return gopter.PropResult{ 183 Status: gopter.PropError, 184 Error: err, 185 } 186 } 187 188 return gopter.PropResult{ 189 Status: gopter.PropTrue, 190 } 191 } 192 193 func shardEntriesAreEqual(shard *dbShard, expectedEntries []shardEntryState) error { 194 shard.Lock() 195 defer shard.Unlock() 196 197 if len(expectedEntries) == 0 && shard.list.Front() == nil { 198 return nil 199 } 200 201 elem := shard.list.Front() 202 for idx, expectedEntry := range expectedEntries { 203 if elem == nil { 204 return fmt.Errorf("expected to have %d idx, but did not see anything", idx) 205 } 206 nextElem := elem.Next() 207 entry := elem.Value.(*Entry) 208 if !entry.Series.ID().Equal(expectedEntry.id) { 209 return fmt.Errorf("expected id: %s at %d, observed: %s", 210 expectedEntry.id.String(), idx, entry.Series.ID().String()) 211 } 212 if entry.ReaderWriterCount() != expectedEntry.refCount { 213 return fmt.Errorf("expected id: %s at %d to have ref count %d, observed: %d", 214 entry.Series.ID().String(), idx, expectedEntry.refCount, entry.ReaderWriterCount()) 215 } 216 elem = nextElem 217 } 218 219 if elem != nil { 220 return fmt.Errorf("expected to have see %d entries, but observed more", len(expectedEntries)) 221 } 222 223 return nil 224 } 225 226 type shardEntryState struct { 227 id ident.ID 228 refCount int32 229 } 230 231 func genShardEntryStates() gopter.Gen { 232 return gen.SliceOf(genShardEntryState()) 233 } 234 235 func genShardEntryState() gopter.Gen { 236 return gopter.CombineGens(gen.UInt(), gen.UInt16()). 237 Map( 238 func(values []interface{}) shardEntryState { 239 return shardEntryState{ 240 id: ident.StringID(fmt.Sprintf("foo%d", values[0].(uint))), 241 refCount: int32(values[1].(uint16)), 242 } 243 }, 244 ) 245 } 246 247 func genBatchWorkFns() gopter.Gen { 248 return gen.SliceOf(genBatchWorkFn()) 249 } 250 251 func genBatchWorkFn() gopter.Gen { 252 return gen.UInt8(). 253 Map(func(n uint8) dbShardEntryBatchWorkFn { 254 i := uint8(0) 255 return func([]*Entry) bool { 256 i++ 257 return i < n 258 } 259 }) 260 }