...

Source file src/go.etcd.io/bbolt/db_test.go

Documentation: go.etcd.io/bbolt

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

View as plain text