github.com/bhojpur/cache@v0.0.4/pkg/memory/simulation_test.go (about) 1 package memory_test 2 3 // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved. 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 import ( 24 "bytes" 25 "fmt" 26 "math/rand" 27 "sync" 28 "sync/atomic" 29 "testing" 30 31 memcache "github.com/bhojpur/cache/pkg/memory" 32 ) 33 34 func TestSimulate_1op_1p(t *testing.T) { testSimulate(t, nil, 1, 1, 1) } 35 func TestSimulate_10op_1p(t *testing.T) { testSimulate(t, nil, 1, 10, 1) } 36 func TestSimulate_100op_1p(t *testing.T) { testSimulate(t, nil, 1, 100, 1) } 37 func TestSimulate_1000op_1p(t *testing.T) { testSimulate(t, nil, 1, 1000, 1) } 38 func TestSimulate_10000op_1p(t *testing.T) { testSimulate(t, nil, 1, 10000, 1) } 39 40 func TestSimulate_10op_10p(t *testing.T) { testSimulate(t, nil, 1, 10, 10) } 41 func TestSimulate_100op_10p(t *testing.T) { testSimulate(t, nil, 1, 100, 10) } 42 func TestSimulate_1000op_10p(t *testing.T) { testSimulate(t, nil, 1, 1000, 10) } 43 func TestSimulate_10000op_10p(t *testing.T) { testSimulate(t, nil, 1, 10000, 10) } 44 45 func TestSimulate_100op_100p(t *testing.T) { testSimulate(t, nil, 1, 100, 100) } 46 func TestSimulate_1000op_100p(t *testing.T) { testSimulate(t, nil, 1, 1000, 100) } 47 func TestSimulate_10000op_100p(t *testing.T) { testSimulate(t, nil, 1, 10000, 100) } 48 49 func TestSimulate_10000op_1000p(t *testing.T) { testSimulate(t, nil, 1, 10000, 1000) } 50 51 // Randomly generate operations on a given database with multiple clients to ensure consistency and thread safety. 52 func testSimulate(t *testing.T, openOption *memcache.Options, round, threadCount, parallelism int) { 53 if testing.Short() { 54 t.Skip("skipping test in short mode.") 55 } 56 57 rand.Seed(int64(qseed)) 58 59 // A list of operations that readers and writers can perform. 60 var readerHandlers = []simulateHandler{simulateGetHandler} 61 var writerHandlers = []simulateHandler{simulateGetHandler, simulatePutHandler} 62 63 var versions = make(map[int]*QuickDB) 64 versions[1] = NewQuickDB() 65 66 db := MustOpenWithOption(openOption) 67 defer db.MustClose() 68 69 var mutex sync.Mutex 70 71 for n := 0; n < round; n++ { 72 // Run n threads in parallel, each with their own operation. 73 var threads = make(chan bool, parallelism) 74 var wg sync.WaitGroup 75 76 // counter for how many goroutines were fired 77 var opCount int64 78 79 // counter for ignored operations 80 var igCount int64 81 82 var errCh = make(chan error, threadCount) 83 84 var i int 85 for { 86 // this buffered channel will keep accepting booleans 87 // until it hits the limit defined by the parallelism 88 // argument to testSimulate() 89 threads <- true 90 91 // this wait group can only be marked "done" from inside 92 // the subsequent goroutine 93 wg.Add(1) 94 writable := ((rand.Int() % 100) < 20) // 20% writers 95 96 // Choose an operation to execute. 97 var handler simulateHandler 98 if writable { 99 handler = writerHandlers[rand.Intn(len(writerHandlers))] 100 } else { 101 handler = readerHandlers[rand.Intn(len(readerHandlers))] 102 } 103 104 // Execute a thread for the given operation. 105 go func(writable bool, handler simulateHandler) { 106 defer wg.Done() 107 atomic.AddInt64(&opCount, 1) 108 // Start transaction. 109 tx, err := db.Begin(writable) 110 if err != nil { 111 errCh <- fmt.Errorf("error tx begin: %v", err) 112 return 113 } 114 115 // Obtain current state of the dataset. 116 mutex.Lock() 117 var qdb = versions[tx.ID()] 118 if writable { 119 qdb = versions[tx.ID()-1].Copy() 120 } 121 mutex.Unlock() 122 123 // Make sure we commit/rollback the tx at the end and update the state. 124 if writable { 125 defer func() { 126 mutex.Lock() 127 versions[tx.ID()] = qdb 128 mutex.Unlock() 129 130 if err := tx.Commit(); err != nil { 131 errCh <- err 132 return 133 } 134 }() 135 } else { 136 defer func() { _ = tx.Rollback() }() 137 } 138 139 // Ignore operation if we don't have data yet. 140 if qdb == nil { 141 atomic.AddInt64(&igCount, 1) 142 return 143 } 144 145 // Execute handler. 146 handler(tx, qdb) 147 148 // Release a thread back to the scheduling loop. 149 <-threads 150 }(writable, handler) 151 152 i++ 153 if i >= threadCount { 154 break 155 } 156 } 157 158 // Wait until all threads are done. 159 wg.Wait() 160 t.Logf("transactions:%d ignored:%d", opCount, igCount) 161 close(errCh) 162 for err := range errCh { 163 if err != nil { 164 t.Fatalf("error from inside goroutine: %v", err) 165 } 166 } 167 168 db.MustClose() 169 db.MustReopen() 170 } 171 172 } 173 174 type simulateHandler func(tx *memcache.Tx, qdb *QuickDB) 175 176 // Retrieves a key from the database and verifies that it is what is expected. 177 func simulateGetHandler(tx *memcache.Tx, qdb *QuickDB) { 178 // Randomly retrieve an existing exist. 179 keys := qdb.Rand() 180 if len(keys) == 0 { 181 return 182 } 183 184 // Retrieve root bucket. 185 b := tx.Bucket(keys[0]) 186 if b == nil { 187 panic(fmt.Sprintf("bucket[0] expected: %08x\n", trunc(keys[0], 4))) 188 } 189 190 // Drill into nested buckets. 191 for _, key := range keys[1 : len(keys)-1] { 192 b = b.Bucket(key) 193 if b == nil { 194 panic(fmt.Sprintf("bucket[n] expected: %v -> %v\n", keys, key)) 195 } 196 } 197 198 // Verify key/value on the final bucket. 199 expected := qdb.Get(keys) 200 actual := b.Get(keys[len(keys)-1]) 201 if !bytes.Equal(actual, expected) { 202 fmt.Println("=== EXPECTED ===") 203 fmt.Println(expected) 204 fmt.Println("=== ACTUAL ===") 205 fmt.Println(actual) 206 fmt.Println("=== END ===") 207 panic("value mismatch") 208 } 209 } 210 211 // Inserts a key into the database. 212 func simulatePutHandler(tx *memcache.Tx, qdb *QuickDB) { 213 var err error 214 keys, value := randKeys(), randValue() 215 216 // Retrieve root bucket. 217 b := tx.Bucket(keys[0]) 218 if b == nil { 219 b, err = tx.CreateBucket(keys[0]) 220 if err != nil { 221 panic("create bucket: " + err.Error()) 222 } 223 } 224 225 // Create nested buckets, if necessary. 226 for _, key := range keys[1 : len(keys)-1] { 227 child := b.Bucket(key) 228 if child != nil { 229 b = child 230 } else { 231 b, err = b.CreateBucket(key) 232 if err != nil { 233 panic("create bucket: " + err.Error()) 234 } 235 } 236 } 237 238 // Insert into database. 239 if err := b.Put(keys[len(keys)-1], value); err != nil { 240 panic("put: " + err.Error()) 241 } 242 243 // Insert into in-memory database. 244 qdb.Put(keys, value) 245 } 246 247 // QuickDB is an in-memory database that replicates the functionality of the 248 // Bhojpur Cache in-memory DB type except that it is entirely in-memory. It 249 // is meant for testing that the In-Memory database is consistent. 250 type QuickDB struct { 251 sync.RWMutex 252 m map[string]interface{} 253 } 254 255 // NewQuickDB returns an instance of QuickDB. 256 func NewQuickDB() *QuickDB { 257 return &QuickDB{m: make(map[string]interface{})} 258 } 259 260 // Get retrieves the value at a key path. 261 func (db *QuickDB) Get(keys [][]byte) []byte { 262 db.RLock() 263 defer db.RUnlock() 264 265 m := db.m 266 for _, key := range keys[:len(keys)-1] { 267 value := m[string(key)] 268 if value == nil { 269 return nil 270 } 271 switch value := value.(type) { 272 case map[string]interface{}: 273 m = value 274 case []byte: 275 return nil 276 } 277 } 278 279 // Only return if it's a simple value. 280 if value, ok := m[string(keys[len(keys)-1])].([]byte); ok { 281 return value 282 } 283 return nil 284 } 285 286 // Put inserts a value into a key path. 287 func (db *QuickDB) Put(keys [][]byte, value []byte) { 288 db.Lock() 289 defer db.Unlock() 290 291 // Build buckets all the way down the key path. 292 m := db.m 293 for _, key := range keys[:len(keys)-1] { 294 if _, ok := m[string(key)].([]byte); ok { 295 return // Keypath intersects with a simple value. Do nothing. 296 } 297 298 if m[string(key)] == nil { 299 m[string(key)] = make(map[string]interface{}) 300 } 301 m = m[string(key)].(map[string]interface{}) 302 } 303 304 // Insert value into the last key. 305 m[string(keys[len(keys)-1])] = value 306 } 307 308 // Rand returns a random key path that points to a simple value. 309 func (db *QuickDB) Rand() [][]byte { 310 db.RLock() 311 defer db.RUnlock() 312 if len(db.m) == 0 { 313 return nil 314 } 315 var keys [][]byte 316 db.rand(db.m, &keys) 317 return keys 318 } 319 320 func (db *QuickDB) rand(m map[string]interface{}, keys *[][]byte) { 321 i, index := 0, rand.Intn(len(m)) 322 for k, v := range m { 323 if i == index { 324 *keys = append(*keys, []byte(k)) 325 if v, ok := v.(map[string]interface{}); ok { 326 db.rand(v, keys) 327 } 328 return 329 } 330 i++ 331 } 332 panic("quickdb rand: out-of-range") 333 } 334 335 // Copy copies the entire database. 336 func (db *QuickDB) Copy() *QuickDB { 337 db.RLock() 338 defer db.RUnlock() 339 return &QuickDB{m: db.copy(db.m)} 340 } 341 342 func (db *QuickDB) copy(m map[string]interface{}) map[string]interface{} { 343 clone := make(map[string]interface{}, len(m)) 344 for k, v := range m { 345 switch v := v.(type) { 346 case map[string]interface{}: 347 clone[k] = db.copy(v) 348 default: 349 clone[k] = v 350 } 351 } 352 return clone 353 } 354 355 func randKey() []byte { 356 var min, max = 1, 1024 357 n := rand.Intn(max-min) + min 358 b := make([]byte, n) 359 for i := 0; i < n; i++ { 360 b[i] = byte(rand.Intn(255)) 361 } 362 return b 363 } 364 365 func randKeys() [][]byte { 366 var keys [][]byte 367 var count = rand.Intn(2) + 2 368 for i := 0; i < count; i++ { 369 keys = append(keys, randKey()) 370 } 371 return keys 372 } 373 374 func randValue() []byte { 375 n := rand.Intn(8192) 376 b := make([]byte, n) 377 for i := 0; i < n; i++ { 378 b[i] = byte(rand.Intn(255)) 379 } 380 return b 381 }