github.com/bhojpur/cache@v0.0.4/pkg/memory/db_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  	"encoding/binary"
    26  	"errors"
    27  	"flag"
    28  	"fmt"
    29  	"hash/fnv"
    30  	"log"
    31  	"math/rand"
    32  	"os"
    33  	"path/filepath"
    34  	"regexp"
    35  	"sync"
    36  	"testing"
    37  	"time"
    38  	"unsafe"
    39  
    40  	memcache "github.com/bhojpur/cache/pkg/memory"
    41  )
    42  
    43  var statsFlag = flag.Bool("stats", false, "show performance stats")
    44  
    45  // pageSize is the size of one page in the data file.
    46  const pageSize = 4096
    47  
    48  // pageHeaderSize is the size of a page header.
    49  const pageHeaderSize = 16
    50  
    51  // meta represents a simplified version of a database meta page for testing.
    52  type meta struct {
    53  	magic    uint32
    54  	version  uint32
    55  	_        uint32
    56  	_        uint32
    57  	_        [16]byte
    58  	_        uint64
    59  	pgid     uint64
    60  	_        uint64
    61  	checksum uint64
    62  }
    63  
    64  // Ensure that a database can be opened without error.
    65  func TestOpen(t *testing.T) {
    66  	path := tempfile()
    67  	defer os.RemoveAll(path)
    68  
    69  	db, err := memcache.Open(path, 0666, nil)
    70  	if err != nil {
    71  		t.Fatal(err)
    72  	} else if db == nil {
    73  		t.Fatal("expected db")
    74  	}
    75  
    76  	if s := db.Path(); s != path {
    77  		t.Fatalf("unexpected path: %s", s)
    78  	}
    79  
    80  	if err := db.Close(); err != nil {
    81  		t.Fatal(err)
    82  	}
    83  }
    84  
    85  // Tests multiple goroutines simultaneously opening a database.
    86  func TestOpen_MultipleGoroutines(t *testing.T) {
    87  	if testing.Short() {
    88  		t.Skip("skipping test in short mode")
    89  	}
    90  
    91  	const (
    92  		instances  = 30
    93  		iterations = 30
    94  	)
    95  	path := tempfile()
    96  	defer os.RemoveAll(path)
    97  	var wg sync.WaitGroup
    98  	errCh := make(chan error, iterations*instances)
    99  	for iteration := 0; iteration < iterations; iteration++ {
   100  		for instance := 0; instance < instances; instance++ {
   101  			wg.Add(1)
   102  			go func() {
   103  				defer wg.Done()
   104  				db, err := memcache.Open(path, 0600, nil)
   105  				if err != nil {
   106  					errCh <- err
   107  					return
   108  				}
   109  				if err := db.Close(); err != nil {
   110  					errCh <- err
   111  					return
   112  				}
   113  			}()
   114  		}
   115  		wg.Wait()
   116  	}
   117  	close(errCh)
   118  	for err := range errCh {
   119  		if err != nil {
   120  			t.Fatalf("error from inside goroutine: %v", err)
   121  		}
   122  	}
   123  }
   124  
   125  // Ensure that opening a database with a blank path returns an error.
   126  func TestOpen_ErrPathRequired(t *testing.T) {
   127  	_, err := memcache.Open("", 0666, nil)
   128  	if err == nil {
   129  		t.Fatalf("expected error")
   130  	}
   131  }
   132  
   133  // Ensure that opening a database with a bad path returns an error.
   134  func TestOpen_ErrNotExists(t *testing.T) {
   135  	_, err := memcache.Open(filepath.Join(tempfile(), "bad-path"), 0666, nil)
   136  	if err == nil {
   137  		t.Fatal("expected error")
   138  	}
   139  }
   140  
   141  // Ensure that opening a file that is not a Bhojpur Cache database returns ErrInvalid.
   142  func TestOpen_ErrInvalid(t *testing.T) {
   143  	path := tempfile()
   144  	defer os.RemoveAll(path)
   145  
   146  	f, err := os.Create(path)
   147  	if err != nil {
   148  		t.Fatal(err)
   149  	}
   150  	if _, err := fmt.Fprintln(f, "this is not a Bhojpur Cache database"); err != nil {
   151  		t.Fatal(err)
   152  	}
   153  	if err := f.Close(); err != nil {
   154  		t.Fatal(err)
   155  	}
   156  
   157  	if _, err := memcache.Open(path, 0666, nil); err != memcache.ErrInvalid {
   158  		t.Fatalf("unexpected error: %s", err)
   159  	}
   160  }
   161  
   162  // Ensure that opening a file with two invalid versions returns ErrVersionMismatch.
   163  func TestOpen_ErrVersionMismatch(t *testing.T) {
   164  	if pageSize != os.Getpagesize() {
   165  		t.Skip("page size mismatch")
   166  	}
   167  
   168  	// Create empty database.
   169  	db := MustOpenDB()
   170  	path := db.Path()
   171  	defer db.MustClose()
   172  
   173  	// Close database.
   174  	if err := db.DB.Close(); err != nil {
   175  		t.Fatal(err)
   176  	}
   177  
   178  	// Read data file.
   179  	buf, err := os.ReadFile(path)
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  
   184  	// Rewrite meta pages.
   185  	meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize]))
   186  	meta0.version++
   187  	meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize]))
   188  	meta1.version++
   189  	if err := os.WriteFile(path, buf, 0666); err != nil {
   190  		t.Fatal(err)
   191  	}
   192  
   193  	// Reopen data file.
   194  	if _, err := memcache.Open(path, 0666, nil); err != memcache.ErrVersionMismatch {
   195  		t.Fatalf("unexpected error: %s", err)
   196  	}
   197  }
   198  
   199  // Ensure that opening a file with two invalid checksums returns ErrChecksum.
   200  func TestOpen_ErrChecksum(t *testing.T) {
   201  	if pageSize != os.Getpagesize() {
   202  		t.Skip("page size mismatch")
   203  	}
   204  
   205  	// Create empty database.
   206  	db := MustOpenDB()
   207  	path := db.Path()
   208  	defer db.MustClose()
   209  
   210  	// Close database.
   211  	if err := db.DB.Close(); err != nil {
   212  		t.Fatal(err)
   213  	}
   214  
   215  	// Read data file.
   216  	buf, err := os.ReadFile(path)
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  
   221  	// Rewrite meta pages.
   222  	meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize]))
   223  	meta0.pgid++
   224  	meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize]))
   225  	meta1.pgid++
   226  	if err := os.WriteFile(path, buf, 0666); err != nil {
   227  		t.Fatal(err)
   228  	}
   229  
   230  	// Reopen data file.
   231  	if _, err := memcache.Open(path, 0666, nil); err != memcache.ErrChecksum {
   232  		t.Fatalf("unexpected error: %s", err)
   233  	}
   234  }
   235  
   236  // Ensure that opening a database does not increase its size.
   237  func TestOpen_Size(t *testing.T) {
   238  	// Open a data file.
   239  	db := MustOpenDB()
   240  	path := db.Path()
   241  	defer db.MustClose()
   242  
   243  	pagesize := db.Info().PageSize
   244  
   245  	// Insert until we get above the minimum 4MB size.
   246  	if err := db.Update(func(tx *memcache.Tx) error {
   247  		b, _ := tx.CreateBucketIfNotExists([]byte("data"))
   248  		for i := 0; i < 10000; i++ {
   249  			if err := b.Put([]byte(fmt.Sprintf("%04d", i)), make([]byte, 1000)); err != nil {
   250  				t.Fatal(err)
   251  			}
   252  		}
   253  		return nil
   254  	}); err != nil {
   255  		t.Fatal(err)
   256  	}
   257  
   258  	// Close database and grab the size.
   259  	if err := db.DB.Close(); err != nil {
   260  		t.Fatal(err)
   261  	}
   262  	sz := fileSize(path)
   263  	if sz == 0 {
   264  		t.Fatalf("unexpected new file size: %d", sz)
   265  	}
   266  
   267  	// Reopen database, update, and check size again.
   268  	db0, err := memcache.Open(path, 0666, nil)
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  	if err := db0.Update(func(tx *memcache.Tx) error {
   273  		if err := tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}); err != nil {
   274  			t.Fatal(err)
   275  		}
   276  		return nil
   277  	}); err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	if err := db0.Close(); err != nil {
   281  		t.Fatal(err)
   282  	}
   283  	newSz := fileSize(path)
   284  	if newSz == 0 {
   285  		t.Fatalf("unexpected new file size: %d", newSz)
   286  	}
   287  
   288  	// Compare the original size with the new size.
   289  	// db size might increase by a few page sizes due to the new small update.
   290  	if sz < newSz-5*int64(pagesize) {
   291  		t.Fatalf("unexpected file growth: %d => %d", sz, newSz)
   292  	}
   293  }
   294  
   295  // Ensure that opening a database beyond the max step size does not increase its size.
   296  func TestOpen_Size_Large(t *testing.T) {
   297  	if testing.Short() {
   298  		t.Skip("short mode")
   299  	}
   300  
   301  	// Open a data file.
   302  	db := MustOpenDB()
   303  	path := db.Path()
   304  	defer db.MustClose()
   305  
   306  	pagesize := db.Info().PageSize
   307  
   308  	// Insert until we get above the minimum 4MB size.
   309  	var index uint64
   310  	for i := 0; i < 10000; i++ {
   311  		if err := db.Update(func(tx *memcache.Tx) error {
   312  			b, _ := tx.CreateBucketIfNotExists([]byte("data"))
   313  			for j := 0; j < 1000; j++ {
   314  				if err := b.Put(u64tob(index), make([]byte, 50)); err != nil {
   315  					t.Fatal(err)
   316  				}
   317  				index++
   318  			}
   319  			return nil
   320  		}); err != nil {
   321  			t.Fatal(err)
   322  		}
   323  	}
   324  
   325  	// Close database and grab the size.
   326  	if err := db.DB.Close(); err != nil {
   327  		t.Fatal(err)
   328  	}
   329  	sz := fileSize(path)
   330  	if sz == 0 {
   331  		t.Fatalf("unexpected new file size: %d", sz)
   332  	} else if sz < (1 << 30) {
   333  		t.Fatalf("expected larger initial size: %d", sz)
   334  	}
   335  
   336  	// Reopen database, update, and check size again.
   337  	db0, err := memcache.Open(path, 0666, nil)
   338  	if err != nil {
   339  		t.Fatal(err)
   340  	}
   341  	if err := db0.Update(func(tx *memcache.Tx) error {
   342  		return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0})
   343  	}); err != nil {
   344  		t.Fatal(err)
   345  	}
   346  	if err := db0.Close(); err != nil {
   347  		t.Fatal(err)
   348  	}
   349  
   350  	newSz := fileSize(path)
   351  	if newSz == 0 {
   352  		t.Fatalf("unexpected new file size: %d", newSz)
   353  	}
   354  
   355  	// Compare the original size with the new size.
   356  	// db size might increase by a few page sizes due to the new small update.
   357  	if sz < newSz-5*int64(pagesize) {
   358  		t.Fatalf("unexpected file growth: %d => %d", sz, newSz)
   359  	}
   360  }
   361  
   362  // Ensure that a re-opened database is consistent.
   363  func TestOpen_Check(t *testing.T) {
   364  	path := tempfile()
   365  	defer os.RemoveAll(path)
   366  
   367  	db, err := memcache.Open(path, 0666, nil)
   368  	if err != nil {
   369  		t.Fatal(err)
   370  	}
   371  	if err = db.View(func(tx *memcache.Tx) error { return <-tx.Check() }); err != nil {
   372  		t.Fatal(err)
   373  	}
   374  	if err = db.Close(); err != nil {
   375  		t.Fatal(err)
   376  	}
   377  
   378  	db, err = memcache.Open(path, 0666, nil)
   379  	if err != nil {
   380  		t.Fatal(err)
   381  	}
   382  	if err := db.View(func(tx *memcache.Tx) error { return <-tx.Check() }); err != nil {
   383  		t.Fatal(err)
   384  	}
   385  	if err := db.Close(); err != nil {
   386  		t.Fatal(err)
   387  	}
   388  }
   389  
   390  // Ensure that write errors to the meta file handler during initialization are returned.
   391  func TestOpen_MetaInitWriteError(t *testing.T) {
   392  	t.Skip("pending")
   393  }
   394  
   395  // Ensure that a database that is too small returns an error.
   396  func TestOpen_FileTooSmall(t *testing.T) {
   397  	path := tempfile()
   398  	defer os.RemoveAll(path)
   399  
   400  	db, err := memcache.Open(path, 0666, nil)
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  	pageSize := int64(db.Info().PageSize)
   405  	if err = db.Close(); err != nil {
   406  		t.Fatal(err)
   407  	}
   408  
   409  	// corrupt the database
   410  	if err = os.Truncate(path, pageSize); err != nil {
   411  		t.Fatal(err)
   412  	}
   413  
   414  	_, err = memcache.Open(path, 0666, nil)
   415  	if err == nil || err.Error() != "file size too small" {
   416  		t.Fatalf("unexpected error: %s", err)
   417  	}
   418  }
   419  
   420  // TestDB_Open_InitialMmapSize tests if having InitialMmapSize large enough
   421  // to hold data from concurrent write transaction resolves the issue that
   422  // read transaction blocks the write transaction and causes deadlock.
   423  // This is a very hacky test since the mmap size is not exposed.
   424  func TestDB_Open_InitialMmapSize(t *testing.T) {
   425  	path := tempfile()
   426  	defer os.Remove(path)
   427  
   428  	initMmapSize := 1 << 30  // 1GB
   429  	testWriteSize := 1 << 27 // 134MB
   430  
   431  	db, err := memcache.Open(path, 0666, &memcache.Options{InitialMmapSize: initMmapSize})
   432  	if err != nil {
   433  		t.Fatal(err)
   434  	}
   435  
   436  	// create a long-running read transaction
   437  	// that never gets closed while writing
   438  	rtx, err := db.Begin(false)
   439  	if err != nil {
   440  		t.Fatal(err)
   441  	}
   442  
   443  	// create a write transaction
   444  	wtx, err := db.Begin(true)
   445  	if err != nil {
   446  		t.Fatal(err)
   447  	}
   448  
   449  	b, err := wtx.CreateBucket([]byte("test"))
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  
   454  	// and commit a large write
   455  	err = b.Put([]byte("foo"), make([]byte, testWriteSize))
   456  	if err != nil {
   457  		t.Fatal(err)
   458  	}
   459  
   460  	done := make(chan error, 1)
   461  
   462  	go func() {
   463  		err := wtx.Commit()
   464  		done <- err
   465  	}()
   466  
   467  	select {
   468  	case <-time.After(5 * time.Second):
   469  		t.Errorf("unexpected that the reader blocks writer")
   470  	case err := <-done:
   471  		if err != nil {
   472  			t.Fatal(err)
   473  		}
   474  	}
   475  
   476  	if err := rtx.Rollback(); err != nil {
   477  		t.Fatal(err)
   478  	}
   479  }
   480  
   481  // TestDB_Open_ReadOnly checks a database in read only mode can read but not write.
   482  func TestDB_Open_ReadOnly(t *testing.T) {
   483  	// Create a writable db, write k-v and close it.
   484  	db := MustOpenDB()
   485  	defer db.MustClose()
   486  
   487  	if err := db.Update(func(tx *memcache.Tx) error {
   488  		b, err := tx.CreateBucket([]byte("widgets"))
   489  		if err != nil {
   490  			t.Fatal(err)
   491  		}
   492  		if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
   493  			t.Fatal(err)
   494  		}
   495  		return nil
   496  	}); err != nil {
   497  		t.Fatal(err)
   498  	}
   499  	if err := db.DB.Close(); err != nil {
   500  		t.Fatal(err)
   501  	}
   502  
   503  	f := db.f
   504  	o := &memcache.Options{ReadOnly: true}
   505  	readOnlyDB, err := memcache.Open(f, 0666, o)
   506  	if err != nil {
   507  		panic(err)
   508  	}
   509  
   510  	if !readOnlyDB.IsReadOnly() {
   511  		t.Fatal("expect db in read only mode")
   512  	}
   513  
   514  	// Read from a read-only transaction.
   515  	if err := readOnlyDB.View(func(tx *memcache.Tx) error {
   516  		value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
   517  		if !bytes.Equal(value, []byte("bar")) {
   518  			t.Fatal("expect value 'bar', got", value)
   519  		}
   520  		return nil
   521  	}); err != nil {
   522  		t.Fatal(err)
   523  	}
   524  
   525  	// Can't launch read-write transaction.
   526  	if _, err := readOnlyDB.Begin(true); err != memcache.ErrDatabaseReadOnly {
   527  		t.Fatalf("unexpected error: %s", err)
   528  	}
   529  
   530  	if err := readOnlyDB.Close(); err != nil {
   531  		t.Fatal(err)
   532  	}
   533  }
   534  
   535  // TestOpen_BigPage checks the database uses bigger pages when
   536  // changing PageSize.
   537  func TestOpen_BigPage(t *testing.T) {
   538  	pageSize := os.Getpagesize()
   539  
   540  	db1 := MustOpenWithOption(&memcache.Options{PageSize: pageSize * 2})
   541  	defer db1.MustClose()
   542  
   543  	db2 := MustOpenWithOption(&memcache.Options{PageSize: pageSize * 4})
   544  	defer db2.MustClose()
   545  
   546  	if db1sz, db2sz := fileSize(db1.f), fileSize(db2.f); db1sz >= db2sz {
   547  		t.Errorf("expected %d < %d", db1sz, db2sz)
   548  	}
   549  }
   550  
   551  // TestOpen_RecoverFreeList tests opening the DB with free-list
   552  // write-out after no free list sync will recover the free list
   553  // and write it out.
   554  func TestOpen_RecoverFreeList(t *testing.T) {
   555  	db := MustOpenWithOption(&memcache.Options{NoFreelistSync: true})
   556  	defer db.MustClose()
   557  
   558  	// Write some pages.
   559  	tx, err := db.Begin(true)
   560  	if err != nil {
   561  		t.Fatal(err)
   562  	}
   563  	wbuf := make([]byte, 8192)
   564  	for i := 0; i < 100; i++ {
   565  		s := fmt.Sprintf("%d", i)
   566  		b, err := tx.CreateBucket([]byte(s))
   567  		if err != nil {
   568  			t.Fatal(err)
   569  		}
   570  		if err = b.Put([]byte(s), wbuf); err != nil {
   571  			t.Fatal(err)
   572  		}
   573  	}
   574  	if err = tx.Commit(); err != nil {
   575  		t.Fatal(err)
   576  	}
   577  
   578  	// Generate free pages.
   579  	if tx, err = db.Begin(true); err != nil {
   580  		t.Fatal(err)
   581  	}
   582  	for i := 0; i < 50; i++ {
   583  		s := fmt.Sprintf("%d", i)
   584  		b := tx.Bucket([]byte(s))
   585  		if b == nil {
   586  			t.Fatal(err)
   587  		}
   588  		if err := b.Delete([]byte(s)); err != nil {
   589  			t.Fatal(err)
   590  		}
   591  	}
   592  	if err := tx.Commit(); err != nil {
   593  		t.Fatal(err)
   594  	}
   595  	if err := db.DB.Close(); err != nil {
   596  		t.Fatal(err)
   597  	}
   598  
   599  	// Record freelist count from opening with NoFreelistSync.
   600  	db.MustReopen()
   601  	freepages := db.Stats().FreePageN
   602  	if freepages == 0 {
   603  		t.Fatalf("no free pages on NoFreelistSync reopen")
   604  	}
   605  	if err := db.DB.Close(); err != nil {
   606  		t.Fatal(err)
   607  	}
   608  
   609  	// Check free page count is reconstructed when opened with freelist sync.
   610  	db.o = &memcache.Options{}
   611  	db.MustReopen()
   612  	// One less free page for syncing the free list on open.
   613  	freepages--
   614  	if fp := db.Stats().FreePageN; fp < freepages {
   615  		t.Fatalf("closed with %d free pages, opened with %d", freepages, fp)
   616  	}
   617  }
   618  
   619  // Ensure that a database cannot open a transaction when it's not open.
   620  func TestDB_Begin_ErrDatabaseNotOpen(t *testing.T) {
   621  	var db memcache.DB
   622  	if _, err := db.Begin(false); err != memcache.ErrDatabaseNotOpen {
   623  		t.Fatalf("unexpected error: %s", err)
   624  	}
   625  }
   626  
   627  // Ensure that a read-write transaction can be retrieved.
   628  func TestDB_BeginRW(t *testing.T) {
   629  	db := MustOpenDB()
   630  	defer db.MustClose()
   631  
   632  	tx, err := db.Begin(true)
   633  	if err != nil {
   634  		t.Fatal(err)
   635  	} else if tx == nil {
   636  		t.Fatal("expected tx")
   637  	}
   638  
   639  	if tx.DB() != db.DB {
   640  		t.Fatal("unexpected tx database")
   641  	} else if !tx.Writable() {
   642  		t.Fatal("expected writable tx")
   643  	}
   644  
   645  	if err := tx.Commit(); err != nil {
   646  		t.Fatal(err)
   647  	}
   648  }
   649  
   650  // TestDB_Concurrent_WriteTo checks that issuing WriteTo operations concurrently
   651  // with commits does not produce corrupted db files.
   652  func TestDB_Concurrent_WriteTo(t *testing.T) {
   653  	o := &memcache.Options{NoFreelistSync: false}
   654  	db := MustOpenWithOption(o)
   655  	defer db.MustClose()
   656  
   657  	var wg sync.WaitGroup
   658  	wtxs, rtxs := 5, 5
   659  	wg.Add(wtxs * rtxs)
   660  	f := func(tx *memcache.Tx) {
   661  		defer wg.Done()
   662  		f, err := os.CreateTemp("", "bhojpur-cache-")
   663  		if err != nil {
   664  			panic(err)
   665  		}
   666  		time.Sleep(time.Duration(rand.Intn(20)+1) * time.Millisecond)
   667  		tx.WriteTo(f)
   668  		tx.Rollback()
   669  		f.Close()
   670  		snap := &DB{nil, f.Name(), o}
   671  		snap.MustReopen()
   672  		defer snap.MustClose()
   673  		snap.MustCheck()
   674  	}
   675  
   676  	tx1, err := db.Begin(true)
   677  	if err != nil {
   678  		t.Fatal(err)
   679  	}
   680  	if _, err := tx1.CreateBucket([]byte("abc")); err != nil {
   681  		t.Fatal(err)
   682  	}
   683  	if err := tx1.Commit(); err != nil {
   684  		t.Fatal(err)
   685  	}
   686  
   687  	for i := 0; i < wtxs; i++ {
   688  		tx, err := db.Begin(true)
   689  		if err != nil {
   690  			t.Fatal(err)
   691  		}
   692  		if err := tx.Bucket([]byte("abc")).Put([]byte{0}, []byte{0}); err != nil {
   693  			t.Fatal(err)
   694  		}
   695  		for j := 0; j < rtxs; j++ {
   696  			rtx, rerr := db.Begin(false)
   697  			if rerr != nil {
   698  				t.Fatal(rerr)
   699  			}
   700  			go f(rtx)
   701  		}
   702  		if err := tx.Commit(); err != nil {
   703  			t.Fatal(err)
   704  		}
   705  	}
   706  	wg.Wait()
   707  }
   708  
   709  // Ensure that opening a transaction while the DB is closed returns an error.
   710  func TestDB_BeginRW_Closed(t *testing.T) {
   711  	var db memcache.DB
   712  	if _, err := db.Begin(true); err != memcache.ErrDatabaseNotOpen {
   713  		t.Fatalf("unexpected error: %s", err)
   714  	}
   715  }
   716  
   717  func TestDB_Close_PendingTx_RW(t *testing.T) { testDB_Close_PendingTx(t, true) }
   718  func TestDB_Close_PendingTx_RO(t *testing.T) { testDB_Close_PendingTx(t, false) }
   719  
   720  // Ensure that a database cannot close while transactions are open.
   721  func testDB_Close_PendingTx(t *testing.T, writable bool) {
   722  	db := MustOpenDB()
   723  
   724  	// Start transaction.
   725  	tx, err := db.Begin(writable)
   726  	if err != nil {
   727  		t.Fatal(err)
   728  	}
   729  
   730  	// Open update in separate goroutine.
   731  	done := make(chan error, 1)
   732  	go func() {
   733  		err := db.Close()
   734  		done <- err
   735  	}()
   736  
   737  	// Ensure database hasn't closed.
   738  	time.Sleep(100 * time.Millisecond)
   739  	select {
   740  	case err := <-done:
   741  		if err != nil {
   742  			t.Errorf("error from inside goroutine: %v", err)
   743  		}
   744  		t.Fatal("database closed too early")
   745  	default:
   746  	}
   747  
   748  	// Commit/close transaction.
   749  	if writable {
   750  		err = tx.Commit()
   751  	} else {
   752  		err = tx.Rollback()
   753  	}
   754  	if err != nil {
   755  		t.Fatal(err)
   756  	}
   757  
   758  	// Ensure database closed now.
   759  	time.Sleep(100 * time.Millisecond)
   760  	select {
   761  	case err := <-done:
   762  		if err != nil {
   763  			t.Fatalf("error from inside goroutine: %v", err)
   764  		}
   765  	default:
   766  		t.Fatal("database did not close")
   767  	}
   768  }
   769  
   770  // Ensure a database can provide a transactional block.
   771  func TestDB_Update(t *testing.T) {
   772  	db := MustOpenDB()
   773  	defer db.MustClose()
   774  	if err := db.Update(func(tx *memcache.Tx) error {
   775  		b, err := tx.CreateBucket([]byte("widgets"))
   776  		if err != nil {
   777  			t.Fatal(err)
   778  		}
   779  		if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
   780  			t.Fatal(err)
   781  		}
   782  		if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
   783  			t.Fatal(err)
   784  		}
   785  		if err := b.Delete([]byte("foo")); err != nil {
   786  			t.Fatal(err)
   787  		}
   788  		return nil
   789  	}); err != nil {
   790  		t.Fatal(err)
   791  	}
   792  	if err := db.View(func(tx *memcache.Tx) error {
   793  		b := tx.Bucket([]byte("widgets"))
   794  		if v := b.Get([]byte("foo")); v != nil {
   795  			t.Fatalf("expected nil value, got: %v", v)
   796  		}
   797  		if v := b.Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) {
   798  			t.Fatalf("unexpected value: %v", v)
   799  		}
   800  		return nil
   801  	}); err != nil {
   802  		t.Fatal(err)
   803  	}
   804  }
   805  
   806  // Ensure a closed database returns an error while running a transaction block
   807  func TestDB_Update_Closed(t *testing.T) {
   808  	var db memcache.DB
   809  	if err := db.Update(func(tx *memcache.Tx) error {
   810  		if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
   811  			t.Fatal(err)
   812  		}
   813  		return nil
   814  	}); err != memcache.ErrDatabaseNotOpen {
   815  		t.Fatalf("unexpected error: %s", err)
   816  	}
   817  }
   818  
   819  // Ensure a panic occurs while trying to commit a managed transaction.
   820  func TestDB_Update_ManualCommit(t *testing.T) {
   821  	db := MustOpenDB()
   822  	defer db.MustClose()
   823  
   824  	var panicked bool
   825  	if err := db.Update(func(tx *memcache.Tx) error {
   826  		func() {
   827  			defer func() {
   828  				if r := recover(); r != nil {
   829  					panicked = true
   830  				}
   831  			}()
   832  
   833  			if err := tx.Commit(); err != nil {
   834  				t.Fatal(err)
   835  			}
   836  		}()
   837  		return nil
   838  	}); err != nil {
   839  		t.Fatal(err)
   840  	} else if !panicked {
   841  		t.Fatal("expected panic")
   842  	}
   843  }
   844  
   845  // Ensure a panic occurs while trying to rollback a managed transaction.
   846  func TestDB_Update_ManualRollback(t *testing.T) {
   847  	db := MustOpenDB()
   848  	defer db.MustClose()
   849  
   850  	var panicked bool
   851  	if err := db.Update(func(tx *memcache.Tx) error {
   852  		func() {
   853  			defer func() {
   854  				if r := recover(); r != nil {
   855  					panicked = true
   856  				}
   857  			}()
   858  
   859  			if err := tx.Rollback(); err != nil {
   860  				t.Fatal(err)
   861  			}
   862  		}()
   863  		return nil
   864  	}); err != nil {
   865  		t.Fatal(err)
   866  	} else if !panicked {
   867  		t.Fatal("expected panic")
   868  	}
   869  }
   870  
   871  // Ensure a panic occurs while trying to commit a managed transaction.
   872  func TestDB_View_ManualCommit(t *testing.T) {
   873  	db := MustOpenDB()
   874  	defer db.MustClose()
   875  
   876  	var panicked bool
   877  	if err := db.View(func(tx *memcache.Tx) error {
   878  		func() {
   879  			defer func() {
   880  				if r := recover(); r != nil {
   881  					panicked = true
   882  				}
   883  			}()
   884  
   885  			if err := tx.Commit(); err != nil {
   886  				t.Fatal(err)
   887  			}
   888  		}()
   889  		return nil
   890  	}); err != nil {
   891  		t.Fatal(err)
   892  	} else if !panicked {
   893  		t.Fatal("expected panic")
   894  	}
   895  }
   896  
   897  // Ensure a panic occurs while trying to rollback a managed transaction.
   898  func TestDB_View_ManualRollback(t *testing.T) {
   899  	db := MustOpenDB()
   900  	defer db.MustClose()
   901  
   902  	var panicked bool
   903  	if err := db.View(func(tx *memcache.Tx) error {
   904  		func() {
   905  			defer func() {
   906  				if r := recover(); r != nil {
   907  					panicked = true
   908  				}
   909  			}()
   910  
   911  			if err := tx.Rollback(); err != nil {
   912  				t.Fatal(err)
   913  			}
   914  		}()
   915  		return nil
   916  	}); err != nil {
   917  		t.Fatal(err)
   918  	} else if !panicked {
   919  		t.Fatal("expected panic")
   920  	}
   921  }
   922  
   923  // Ensure a write transaction that panics does not hold open locks.
   924  func TestDB_Update_Panic(t *testing.T) {
   925  	db := MustOpenDB()
   926  	defer db.MustClose()
   927  
   928  	// Panic during update but recover.
   929  	func() {
   930  		defer func() {
   931  			if r := recover(); r != nil {
   932  				t.Log("recover: update", r)
   933  			}
   934  		}()
   935  
   936  		if err := db.Update(func(tx *memcache.Tx) error {
   937  			if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
   938  				t.Fatal(err)
   939  			}
   940  			panic("omg")
   941  		}); err != nil {
   942  			t.Fatal(err)
   943  		}
   944  	}()
   945  
   946  	// Verify we can update again.
   947  	if err := db.Update(func(tx *memcache.Tx) error {
   948  		if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
   949  			t.Fatal(err)
   950  		}
   951  		return nil
   952  	}); err != nil {
   953  		t.Fatal(err)
   954  	}
   955  
   956  	// Verify that our change persisted.
   957  	if err := db.Update(func(tx *memcache.Tx) error {
   958  		if tx.Bucket([]byte("widgets")) == nil {
   959  			t.Fatal("expected bucket")
   960  		}
   961  		return nil
   962  	}); err != nil {
   963  		t.Fatal(err)
   964  	}
   965  }
   966  
   967  // Ensure a database can return an error through a read-only transactional block.
   968  func TestDB_View_Error(t *testing.T) {
   969  	db := MustOpenDB()
   970  	defer db.MustClose()
   971  
   972  	if err := db.View(func(tx *memcache.Tx) error {
   973  		return errors.New("xxx")
   974  	}); err == nil || err.Error() != "xxx" {
   975  		t.Fatalf("unexpected error: %s", err)
   976  	}
   977  }
   978  
   979  // Ensure a read transaction that panics does not hold open locks.
   980  func TestDB_View_Panic(t *testing.T) {
   981  	db := MustOpenDB()
   982  	defer db.MustClose()
   983  
   984  	if err := db.Update(func(tx *memcache.Tx) error {
   985  		if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
   986  			t.Fatal(err)
   987  		}
   988  		return nil
   989  	}); err != nil {
   990  		t.Fatal(err)
   991  	}
   992  
   993  	// Panic during view transaction but recover.
   994  	func() {
   995  		defer func() {
   996  			if r := recover(); r != nil {
   997  				t.Log("recover: view", r)
   998  			}
   999  		}()
  1000  
  1001  		if err := db.View(func(tx *memcache.Tx) error {
  1002  			if tx.Bucket([]byte("widgets")) == nil {
  1003  				t.Fatal("expected bucket")
  1004  			}
  1005  			panic("omg")
  1006  		}); err != nil {
  1007  			t.Fatal(err)
  1008  		}
  1009  	}()
  1010  
  1011  	// Verify that we can still use read transactions.
  1012  	if err := db.View(func(tx *memcache.Tx) error {
  1013  		if tx.Bucket([]byte("widgets")) == nil {
  1014  			t.Fatal("expected bucket")
  1015  		}
  1016  		return nil
  1017  	}); err != nil {
  1018  		t.Fatal(err)
  1019  	}
  1020  }
  1021  
  1022  // Ensure that DB stats can be returned.
  1023  func TestDB_Stats(t *testing.T) {
  1024  	db := MustOpenDB()
  1025  	defer db.MustClose()
  1026  	if err := db.Update(func(tx *memcache.Tx) error {
  1027  		_, err := tx.CreateBucket([]byte("widgets"))
  1028  		return err
  1029  	}); err != nil {
  1030  		t.Fatal(err)
  1031  	}
  1032  
  1033  	stats := db.Stats()
  1034  	if stats.TxStats.PageCount != 2 {
  1035  		t.Fatalf("unexpected TxStats.PageCount: %d", stats.TxStats.PageCount)
  1036  	} else if stats.FreePageN != 0 {
  1037  		t.Fatalf("unexpected FreePageN != 0: %d", stats.FreePageN)
  1038  	} else if stats.PendingPageN != 2 {
  1039  		t.Fatalf("unexpected PendingPageN != 2: %d", stats.PendingPageN)
  1040  	}
  1041  }
  1042  
  1043  // Ensure that database pages are in expected order and type.
  1044  func TestDB_Consistency(t *testing.T) {
  1045  	db := MustOpenDB()
  1046  	defer db.MustClose()
  1047  	if err := db.Update(func(tx *memcache.Tx) error {
  1048  		_, err := tx.CreateBucket([]byte("widgets"))
  1049  		return err
  1050  	}); err != nil {
  1051  		t.Fatal(err)
  1052  	}
  1053  
  1054  	for i := 0; i < 10; i++ {
  1055  		if err := db.Update(func(tx *memcache.Tx) error {
  1056  			if err := tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")); err != nil {
  1057  				t.Fatal(err)
  1058  			}
  1059  			return nil
  1060  		}); err != nil {
  1061  			t.Fatal(err)
  1062  		}
  1063  	}
  1064  
  1065  	if err := db.Update(func(tx *memcache.Tx) error {
  1066  		if p, _ := tx.Page(0); p == nil {
  1067  			t.Fatal("expected page")
  1068  		} else if p.Type != "meta" {
  1069  			t.Fatalf("unexpected page type: %s", p.Type)
  1070  		}
  1071  
  1072  		if p, _ := tx.Page(1); p == nil {
  1073  			t.Fatal("expected page")
  1074  		} else if p.Type != "meta" {
  1075  			t.Fatalf("unexpected page type: %s", p.Type)
  1076  		}
  1077  
  1078  		if p, _ := tx.Page(2); p == nil {
  1079  			t.Fatal("expected page")
  1080  		} else if p.Type != "free" {
  1081  			t.Fatalf("unexpected page type: %s", p.Type)
  1082  		}
  1083  
  1084  		if p, _ := tx.Page(3); p == nil {
  1085  			t.Fatal("expected page")
  1086  		} else if p.Type != "free" {
  1087  			t.Fatalf("unexpected page type: %s", p.Type)
  1088  		}
  1089  
  1090  		if p, _ := tx.Page(4); p == nil {
  1091  			t.Fatal("expected page")
  1092  		} else if p.Type != "leaf" {
  1093  			t.Fatalf("unexpected page type: %s", p.Type)
  1094  		}
  1095  
  1096  		if p, _ := tx.Page(5); p == nil {
  1097  			t.Fatal("expected page")
  1098  		} else if p.Type != "freelist" {
  1099  			t.Fatalf("unexpected page type: %s", p.Type)
  1100  		}
  1101  
  1102  		if p, _ := tx.Page(6); p != nil {
  1103  			t.Fatal("unexpected page")
  1104  		}
  1105  		return nil
  1106  	}); err != nil {
  1107  		t.Fatal(err)
  1108  	}
  1109  }
  1110  
  1111  // Ensure that DB stats can be subtracted from one another.
  1112  func TestDBStats_Sub(t *testing.T) {
  1113  	var a, b memcache.Stats
  1114  	a.TxStats.PageCount = 3
  1115  	a.FreePageN = 4
  1116  	b.TxStats.PageCount = 10
  1117  	b.FreePageN = 14
  1118  	diff := b.Sub(&a)
  1119  	if diff.TxStats.PageCount != 7 {
  1120  		t.Fatalf("unexpected TxStats.PageCount: %d", diff.TxStats.PageCount)
  1121  	}
  1122  
  1123  	// free page stats are copied from the receiver and not subtracted
  1124  	if diff.FreePageN != 14 {
  1125  		t.Fatalf("unexpected FreePageN: %d", diff.FreePageN)
  1126  	}
  1127  }
  1128  
  1129  // Ensure two functions can perform updates in a single batch.
  1130  func TestDB_Batch(t *testing.T) {
  1131  	db := MustOpenDB()
  1132  	defer db.MustClose()
  1133  
  1134  	if err := db.Update(func(tx *memcache.Tx) error {
  1135  		if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
  1136  			t.Fatal(err)
  1137  		}
  1138  		return nil
  1139  	}); err != nil {
  1140  		t.Fatal(err)
  1141  	}
  1142  
  1143  	// Iterate over multiple updates in separate goroutines.
  1144  	n := 2
  1145  	ch := make(chan error, n)
  1146  	for i := 0; i < n; i++ {
  1147  		go func(i int) {
  1148  			ch <- db.Batch(func(tx *memcache.Tx) error {
  1149  				return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
  1150  			})
  1151  		}(i)
  1152  	}
  1153  
  1154  	// Check all responses to make sure there's no error.
  1155  	for i := 0; i < n; i++ {
  1156  		if err := <-ch; err != nil {
  1157  			t.Fatal(err)
  1158  		}
  1159  	}
  1160  
  1161  	// Ensure data is correct.
  1162  	if err := db.View(func(tx *memcache.Tx) error {
  1163  		b := tx.Bucket([]byte("widgets"))
  1164  		for i := 0; i < n; i++ {
  1165  			if v := b.Get(u64tob(uint64(i))); v == nil {
  1166  				t.Errorf("key not found: %d", i)
  1167  			}
  1168  		}
  1169  		return nil
  1170  	}); err != nil {
  1171  		t.Fatal(err)
  1172  	}
  1173  }
  1174  
  1175  func TestDB_Batch_Panic(t *testing.T) {
  1176  	db := MustOpenDB()
  1177  	defer db.MustClose()
  1178  
  1179  	var sentinel int
  1180  	var bork = &sentinel
  1181  	var problem interface{}
  1182  	var err error
  1183  
  1184  	// Execute a function inside a batch that panics.
  1185  	func() {
  1186  		defer func() {
  1187  			if p := recover(); p != nil {
  1188  				problem = p
  1189  			}
  1190  		}()
  1191  		err = db.Batch(func(tx *memcache.Tx) error {
  1192  			panic(bork)
  1193  		})
  1194  	}()
  1195  
  1196  	// Verify there is no error.
  1197  	if g, e := err, error(nil); g != e {
  1198  		t.Fatalf("wrong error: %v != %v", g, e)
  1199  	}
  1200  	// Verify the panic was captured.
  1201  	if g, e := problem, bork; g != e {
  1202  		t.Fatalf("wrong error: %v != %v", g, e)
  1203  	}
  1204  }
  1205  
  1206  func TestDB_BatchFull(t *testing.T) {
  1207  	db := MustOpenDB()
  1208  	defer db.MustClose()
  1209  	if err := db.Update(func(tx *memcache.Tx) error {
  1210  		_, err := tx.CreateBucket([]byte("widgets"))
  1211  		return err
  1212  	}); err != nil {
  1213  		t.Fatal(err)
  1214  	}
  1215  
  1216  	const size = 3
  1217  	// buffered so we never leak goroutines
  1218  	ch := make(chan error, size)
  1219  	put := func(i int) {
  1220  		ch <- db.Batch(func(tx *memcache.Tx) error {
  1221  			return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
  1222  		})
  1223  	}
  1224  
  1225  	db.MaxBatchSize = size
  1226  	// high enough to never trigger here
  1227  	db.MaxBatchDelay = 1 * time.Hour
  1228  
  1229  	go put(1)
  1230  	go put(2)
  1231  
  1232  	// Give the batch a chance to exhibit bugs.
  1233  	time.Sleep(10 * time.Millisecond)
  1234  
  1235  	// not triggered yet
  1236  	select {
  1237  	case <-ch:
  1238  		t.Fatalf("batch triggered too early")
  1239  	default:
  1240  	}
  1241  
  1242  	go put(3)
  1243  
  1244  	// Check all responses to make sure there's no error.
  1245  	for i := 0; i < size; i++ {
  1246  		if err := <-ch; err != nil {
  1247  			t.Fatal(err)
  1248  		}
  1249  	}
  1250  
  1251  	// Ensure data is correct.
  1252  	if err := db.View(func(tx *memcache.Tx) error {
  1253  		b := tx.Bucket([]byte("widgets"))
  1254  		for i := 1; i <= size; i++ {
  1255  			if v := b.Get(u64tob(uint64(i))); v == nil {
  1256  				t.Errorf("key not found: %d", i)
  1257  			}
  1258  		}
  1259  		return nil
  1260  	}); err != nil {
  1261  		t.Fatal(err)
  1262  	}
  1263  }
  1264  
  1265  func TestDB_BatchTime(t *testing.T) {
  1266  	db := MustOpenDB()
  1267  	defer db.MustClose()
  1268  	if err := db.Update(func(tx *memcache.Tx) error {
  1269  		_, err := tx.CreateBucket([]byte("widgets"))
  1270  		return err
  1271  	}); err != nil {
  1272  		t.Fatal(err)
  1273  	}
  1274  
  1275  	const size = 1
  1276  	// buffered so we never leak goroutines
  1277  	ch := make(chan error, size)
  1278  	put := func(i int) {
  1279  		ch <- db.Batch(func(tx *memcache.Tx) error {
  1280  			return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
  1281  		})
  1282  	}
  1283  
  1284  	db.MaxBatchSize = 1000
  1285  	db.MaxBatchDelay = 0
  1286  
  1287  	go put(1)
  1288  
  1289  	// Batch must trigger by time alone.
  1290  
  1291  	// Check all responses to make sure there's no error.
  1292  	for i := 0; i < size; i++ {
  1293  		if err := <-ch; err != nil {
  1294  			t.Fatal(err)
  1295  		}
  1296  	}
  1297  
  1298  	// Ensure data is correct.
  1299  	if err := db.View(func(tx *memcache.Tx) error {
  1300  		b := tx.Bucket([]byte("widgets"))
  1301  		for i := 1; i <= size; i++ {
  1302  			if v := b.Get(u64tob(uint64(i))); v == nil {
  1303  				t.Errorf("key not found: %d", i)
  1304  			}
  1305  		}
  1306  		return nil
  1307  	}); err != nil {
  1308  		t.Fatal(err)
  1309  	}
  1310  }
  1311  
  1312  func ExampleDB_Update() {
  1313  	// Open the database.
  1314  	db, err := memcache.Open(tempfile(), 0666, nil)
  1315  	if err != nil {
  1316  		log.Fatal(err)
  1317  	}
  1318  	defer os.Remove(db.Path())
  1319  
  1320  	// Execute several commands within a read-write transaction.
  1321  	if err := db.Update(func(tx *memcache.Tx) error {
  1322  		b, err := tx.CreateBucket([]byte("widgets"))
  1323  		if err != nil {
  1324  			return err
  1325  		}
  1326  		if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
  1327  			return err
  1328  		}
  1329  		return nil
  1330  	}); err != nil {
  1331  		log.Fatal(err)
  1332  	}
  1333  
  1334  	// Read the value back from a separate read-only transaction.
  1335  	if err := db.View(func(tx *memcache.Tx) error {
  1336  		value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
  1337  		fmt.Printf("The value of 'foo' is: %s\n", value)
  1338  		return nil
  1339  	}); err != nil {
  1340  		log.Fatal(err)
  1341  	}
  1342  
  1343  	// Close database to release the file lock.
  1344  	if err := db.Close(); err != nil {
  1345  		log.Fatal(err)
  1346  	}
  1347  
  1348  	// Output:
  1349  	// The value of 'foo' is: bar
  1350  }
  1351  
  1352  func ExampleDB_View() {
  1353  	// Open the database.
  1354  	db, err := memcache.Open(tempfile(), 0666, nil)
  1355  	if err != nil {
  1356  		log.Fatal(err)
  1357  	}
  1358  	defer os.Remove(db.Path())
  1359  
  1360  	// Insert data into a bucket.
  1361  	if err := db.Update(func(tx *memcache.Tx) error {
  1362  		b, err := tx.CreateBucket([]byte("people"))
  1363  		if err != nil {
  1364  			return err
  1365  		}
  1366  		if err := b.Put([]byte("john"), []byte("doe")); err != nil {
  1367  			return err
  1368  		}
  1369  		if err := b.Put([]byte("susy"), []byte("que")); err != nil {
  1370  			return err
  1371  		}
  1372  		return nil
  1373  	}); err != nil {
  1374  		log.Fatal(err)
  1375  	}
  1376  
  1377  	// Access data from within a read-only transactional block.
  1378  	if err := db.View(func(tx *memcache.Tx) error {
  1379  		v := tx.Bucket([]byte("people")).Get([]byte("john"))
  1380  		fmt.Printf("John's last name is %s.\n", v)
  1381  		return nil
  1382  	}); err != nil {
  1383  		log.Fatal(err)
  1384  	}
  1385  
  1386  	// Close database to release the file lock.
  1387  	if err := db.Close(); err != nil {
  1388  		log.Fatal(err)
  1389  	}
  1390  
  1391  	// Output:
  1392  	// John's last name is doe.
  1393  }
  1394  
  1395  func ExampleDB_Begin() {
  1396  	// Open the database.
  1397  	db, err := memcache.Open(tempfile(), 0666, nil)
  1398  	if err != nil {
  1399  		log.Fatal(err)
  1400  	}
  1401  	defer os.Remove(db.Path())
  1402  
  1403  	// Create a bucket using a read-write transaction.
  1404  	if err = db.Update(func(tx *memcache.Tx) error {
  1405  		_, err := tx.CreateBucket([]byte("widgets"))
  1406  		return err
  1407  	}); err != nil {
  1408  		log.Fatal(err)
  1409  	}
  1410  
  1411  	// Create several keys in a transaction.
  1412  	tx, err := db.Begin(true)
  1413  	if err != nil {
  1414  		log.Fatal(err)
  1415  	}
  1416  	b := tx.Bucket([]byte("widgets"))
  1417  	if err = b.Put([]byte("john"), []byte("blue")); err != nil {
  1418  		log.Fatal(err)
  1419  	}
  1420  	if err = b.Put([]byte("abby"), []byte("red")); err != nil {
  1421  		log.Fatal(err)
  1422  	}
  1423  	if err = b.Put([]byte("zephyr"), []byte("purple")); err != nil {
  1424  		log.Fatal(err)
  1425  	}
  1426  	if err = tx.Commit(); err != nil {
  1427  		log.Fatal(err)
  1428  	}
  1429  
  1430  	// Iterate over the values in sorted key order.
  1431  	tx, err = db.Begin(false)
  1432  	if err != nil {
  1433  		log.Fatal(err)
  1434  	}
  1435  	c := tx.Bucket([]byte("widgets")).Cursor()
  1436  	for k, v := c.First(); k != nil; k, v = c.Next() {
  1437  		fmt.Printf("%s likes %s\n", k, v)
  1438  	}
  1439  
  1440  	if err = tx.Rollback(); err != nil {
  1441  		log.Fatal(err)
  1442  	}
  1443  
  1444  	if err = db.Close(); err != nil {
  1445  		log.Fatal(err)
  1446  	}
  1447  
  1448  	// Output:
  1449  	// abby likes red
  1450  	// john likes blue
  1451  	// zephyr likes purple
  1452  }
  1453  
  1454  func BenchmarkDBBatchAutomatic(b *testing.B) {
  1455  	db := MustOpenDB()
  1456  	defer db.MustClose()
  1457  	if err := db.Update(func(tx *memcache.Tx) error {
  1458  		_, err := tx.CreateBucket([]byte("bench"))
  1459  		return err
  1460  	}); err != nil {
  1461  		b.Fatal(err)
  1462  	}
  1463  
  1464  	b.ResetTimer()
  1465  	for i := 0; i < b.N; i++ {
  1466  		start := make(chan struct{})
  1467  		var wg sync.WaitGroup
  1468  
  1469  		for round := 0; round < 1000; round++ {
  1470  			wg.Add(1)
  1471  
  1472  			go func(id uint32) {
  1473  				defer wg.Done()
  1474  				<-start
  1475  
  1476  				h := fnv.New32a()
  1477  				buf := make([]byte, 4)
  1478  				binary.LittleEndian.PutUint32(buf, id)
  1479  				_, _ = h.Write(buf[:])
  1480  				k := h.Sum(nil)
  1481  				insert := func(tx *memcache.Tx) error {
  1482  					b := tx.Bucket([]byte("bench"))
  1483  					return b.Put(k, []byte("filler"))
  1484  				}
  1485  				if err := db.Batch(insert); err != nil {
  1486  					b.Error(err)
  1487  					return
  1488  				}
  1489  			}(uint32(round))
  1490  		}
  1491  		close(start)
  1492  		wg.Wait()
  1493  	}
  1494  
  1495  	b.StopTimer()
  1496  	validateBatchBench(b, db)
  1497  }
  1498  
  1499  func BenchmarkDBBatchSingle(b *testing.B) {
  1500  	db := MustOpenDB()
  1501  	defer db.MustClose()
  1502  	if err := db.Update(func(tx *memcache.Tx) error {
  1503  		_, err := tx.CreateBucket([]byte("bench"))
  1504  		return err
  1505  	}); err != nil {
  1506  		b.Fatal(err)
  1507  	}
  1508  
  1509  	b.ResetTimer()
  1510  	for i := 0; i < b.N; i++ {
  1511  		start := make(chan struct{})
  1512  		var wg sync.WaitGroup
  1513  
  1514  		for round := 0; round < 1000; round++ {
  1515  			wg.Add(1)
  1516  			go func(id uint32) {
  1517  				defer wg.Done()
  1518  				<-start
  1519  
  1520  				h := fnv.New32a()
  1521  				buf := make([]byte, 4)
  1522  				binary.LittleEndian.PutUint32(buf, id)
  1523  				_, _ = h.Write(buf[:])
  1524  				k := h.Sum(nil)
  1525  				insert := func(tx *memcache.Tx) error {
  1526  					b := tx.Bucket([]byte("bench"))
  1527  					return b.Put(k, []byte("filler"))
  1528  				}
  1529  				if err := db.Update(insert); err != nil {
  1530  					b.Error(err)
  1531  					return
  1532  				}
  1533  			}(uint32(round))
  1534  		}
  1535  		close(start)
  1536  		wg.Wait()
  1537  	}
  1538  
  1539  	b.StopTimer()
  1540  	validateBatchBench(b, db)
  1541  }
  1542  
  1543  func BenchmarkDBBatchManual10x100(b *testing.B) {
  1544  	db := MustOpenDB()
  1545  	defer db.MustClose()
  1546  	if err := db.Update(func(tx *memcache.Tx) error {
  1547  		_, err := tx.CreateBucket([]byte("bench"))
  1548  		return err
  1549  	}); err != nil {
  1550  		b.Fatal(err)
  1551  	}
  1552  
  1553  	b.ResetTimer()
  1554  	for i := 0; i < b.N; i++ {
  1555  		start := make(chan struct{})
  1556  		var wg sync.WaitGroup
  1557  		errCh := make(chan error, 10)
  1558  
  1559  		for major := 0; major < 10; major++ {
  1560  			wg.Add(1)
  1561  			go func(id uint32) {
  1562  				defer wg.Done()
  1563  				<-start
  1564  
  1565  				insert100 := func(tx *memcache.Tx) error {
  1566  					h := fnv.New32a()
  1567  					buf := make([]byte, 4)
  1568  					for minor := uint32(0); minor < 100; minor++ {
  1569  						binary.LittleEndian.PutUint32(buf, uint32(id*100+minor))
  1570  						h.Reset()
  1571  						_, _ = h.Write(buf[:])
  1572  						k := h.Sum(nil)
  1573  						b := tx.Bucket([]byte("bench"))
  1574  						if err := b.Put(k, []byte("filler")); err != nil {
  1575  							return err
  1576  						}
  1577  					}
  1578  					return nil
  1579  				}
  1580  				err := db.Update(insert100)
  1581  				errCh <- err
  1582  			}(uint32(major))
  1583  		}
  1584  		close(start)
  1585  		wg.Wait()
  1586  		close(errCh)
  1587  		for err := range errCh {
  1588  			if err != nil {
  1589  				b.Fatal(err)
  1590  			}
  1591  		}
  1592  	}
  1593  
  1594  	b.StopTimer()
  1595  	validateBatchBench(b, db)
  1596  }
  1597  
  1598  func validateBatchBench(b *testing.B, db *DB) {
  1599  	var rollback = errors.New("sentinel error to cause rollback")
  1600  	validate := func(tx *memcache.Tx) error {
  1601  		bucket := tx.Bucket([]byte("bench"))
  1602  		h := fnv.New32a()
  1603  		buf := make([]byte, 4)
  1604  		for id := uint32(0); id < 1000; id++ {
  1605  			binary.LittleEndian.PutUint32(buf, id)
  1606  			h.Reset()
  1607  			_, _ = h.Write(buf[:])
  1608  			k := h.Sum(nil)
  1609  			v := bucket.Get(k)
  1610  			if v == nil {
  1611  				b.Errorf("not found id=%d key=%x", id, k)
  1612  				continue
  1613  			}
  1614  			if g, e := v, []byte("filler"); !bytes.Equal(g, e) {
  1615  				b.Errorf("bad value for id=%d key=%x: %s != %q", id, k, g, e)
  1616  			}
  1617  			if err := bucket.Delete(k); err != nil {
  1618  				return err
  1619  			}
  1620  		}
  1621  		// should be empty now
  1622  		c := bucket.Cursor()
  1623  		for k, v := c.First(); k != nil; k, v = c.Next() {
  1624  			b.Errorf("unexpected key: %x = %q", k, v)
  1625  		}
  1626  		return rollback
  1627  	}
  1628  	if err := db.Update(validate); err != nil && err != rollback {
  1629  		b.Error(err)
  1630  	}
  1631  }
  1632  
  1633  // DB is a test wrapper for memcache.DB.
  1634  type DB struct {
  1635  	*memcache.DB
  1636  	f string
  1637  	o *memcache.Options
  1638  }
  1639  
  1640  // MustOpenDB returns a new, open DB at a temporary location.
  1641  func MustOpenDB() *DB {
  1642  	return MustOpenWithOption(nil)
  1643  }
  1644  
  1645  // MustOpenDBWithOption returns a new, open DB at a temporary location with given options.
  1646  func MustOpenWithOption(o *memcache.Options) *DB {
  1647  	f := tempfile()
  1648  	if o == nil {
  1649  		o = memcache.DefaultOptions
  1650  	}
  1651  
  1652  	freelistType := memcache.FreelistArrayType
  1653  	if env := os.Getenv(memcache.TestFreelistType); env == string(memcache.FreelistMapType) {
  1654  		freelistType = memcache.FreelistMapType
  1655  	}
  1656  	o.FreelistType = freelistType
  1657  
  1658  	db, err := memcache.Open(f, 0666, o)
  1659  	if err != nil {
  1660  		panic(err)
  1661  	}
  1662  	return &DB{
  1663  		DB: db,
  1664  		f:  f,
  1665  		o:  o,
  1666  	}
  1667  }
  1668  
  1669  // Close closes the database and deletes the underlying file.
  1670  func (db *DB) Close() error {
  1671  	// Log statistics.
  1672  	if *statsFlag {
  1673  		db.PrintStats()
  1674  	}
  1675  
  1676  	// Check database consistency after every test.
  1677  	db.MustCheck()
  1678  
  1679  	// Close database and remove file.
  1680  	defer os.Remove(db.Path())
  1681  	return db.DB.Close()
  1682  }
  1683  
  1684  // MustClose closes the database and deletes the underlying file. Panic on error.
  1685  func (db *DB) MustClose() {
  1686  	if err := db.Close(); err != nil {
  1687  		panic(err)
  1688  	}
  1689  }
  1690  
  1691  // MustReopen reopen the database. Panic on error.
  1692  func (db *DB) MustReopen() {
  1693  	indb, err := memcache.Open(db.f, 0666, db.o)
  1694  	if err != nil {
  1695  		panic(err)
  1696  	}
  1697  	db.DB = indb
  1698  }
  1699  
  1700  // PrintStats prints the database stats
  1701  func (db *DB) PrintStats() {
  1702  	var stats = db.Stats()
  1703  	fmt.Printf("[db] %-20s %-20s %-20s\n",
  1704  		fmt.Sprintf("pg(%d/%d)", stats.TxStats.PageCount, stats.TxStats.PageAlloc),
  1705  		fmt.Sprintf("cur(%d)", stats.TxStats.CursorCount),
  1706  		fmt.Sprintf("node(%d/%d)", stats.TxStats.NodeCount, stats.TxStats.NodeDeref),
  1707  	)
  1708  	fmt.Printf("     %-20s %-20s %-20s\n",
  1709  		fmt.Sprintf("rebal(%d/%v)", stats.TxStats.Rebalance, truncDuration(stats.TxStats.RebalanceTime)),
  1710  		fmt.Sprintf("spill(%d/%v)", stats.TxStats.Spill, truncDuration(stats.TxStats.SpillTime)),
  1711  		fmt.Sprintf("w(%d/%v)", stats.TxStats.Write, truncDuration(stats.TxStats.WriteTime)),
  1712  	)
  1713  }
  1714  
  1715  // MustCheck runs a consistency check on the database and panics if any errors are found.
  1716  func (db *DB) MustCheck() {
  1717  	if err := db.Update(func(tx *memcache.Tx) error {
  1718  		// Collect all the errors.
  1719  		var errors []error
  1720  		for err := range tx.Check() {
  1721  			errors = append(errors, err)
  1722  			if len(errors) > 10 {
  1723  				break
  1724  			}
  1725  		}
  1726  
  1727  		// If errors occurred, copy the DB and print the errors.
  1728  		if len(errors) > 0 {
  1729  			var path = tempfile()
  1730  			if err := tx.CopyFile(path, 0600); err != nil {
  1731  				panic(err)
  1732  			}
  1733  
  1734  			// Print errors.
  1735  			fmt.Print("\n\n")
  1736  			fmt.Printf("consistency check failed (%d errors)\n", len(errors))
  1737  			for _, err := range errors {
  1738  				fmt.Println(err)
  1739  			}
  1740  			fmt.Println("")
  1741  			fmt.Println("db saved to:")
  1742  			fmt.Println(path)
  1743  			fmt.Print("\n\n")
  1744  			os.Exit(-1)
  1745  		}
  1746  
  1747  		return nil
  1748  	}); err != nil && err != memcache.ErrDatabaseNotOpen {
  1749  		panic(err)
  1750  	}
  1751  }
  1752  
  1753  // CopyTempFile copies a database to a temporary file.
  1754  func (db *DB) CopyTempFile() {
  1755  	path := tempfile()
  1756  	if err := db.View(func(tx *memcache.Tx) error {
  1757  		return tx.CopyFile(path, 0600)
  1758  	}); err != nil {
  1759  		panic(err)
  1760  	}
  1761  	fmt.Println("db copied to: ", path)
  1762  }
  1763  
  1764  // tempfile returns a temporary file path.
  1765  func tempfile() string {
  1766  	f, err := os.CreateTemp("", "bhojpur-cache-")
  1767  	if err != nil {
  1768  		panic(err)
  1769  	}
  1770  	if err := f.Close(); err != nil {
  1771  		panic(err)
  1772  	}
  1773  	if err := os.Remove(f.Name()); err != nil {
  1774  		panic(err)
  1775  	}
  1776  	return f.Name()
  1777  }
  1778  
  1779  func trunc(b []byte, length int) []byte {
  1780  	if length < len(b) {
  1781  		return b[:length]
  1782  	}
  1783  	return b
  1784  }
  1785  
  1786  func truncDuration(d time.Duration) string {
  1787  	return regexp.MustCompile(`^(\d+)(\.\d+)`).ReplaceAllString(d.String(), "$1")
  1788  }
  1789  
  1790  func fileSize(path string) int64 {
  1791  	fi, err := os.Stat(path)
  1792  	if err != nil {
  1793  		return 0
  1794  	}
  1795  	return fi.Size()
  1796  }
  1797  
  1798  // u64tob converts a uint64 into an 8-byte slice.
  1799  func u64tob(v uint64) []byte {
  1800  	b := make([]byte, 8)
  1801  	binary.BigEndian.PutUint64(b, v)
  1802  	return b
  1803  }