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  }