...

Source file src/go.etcd.io/bbolt/tests/failpoint/db_failpoint_test.go

Documentation: go.etcd.io/bbolt/tests/failpoint

     1  package failpoint
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/require"
    10  
    11  	bolt "go.etcd.io/bbolt"
    12  	"go.etcd.io/bbolt/internal/btesting"
    13  	gofail "go.etcd.io/gofail/runtime"
    14  )
    15  
    16  func TestFailpoint_MapFail(t *testing.T) {
    17  	err := gofail.Enable("mapError", `return("map somehow failed")`)
    18  	require.NoError(t, err)
    19  	defer func() {
    20  		err = gofail.Disable("mapError")
    21  		require.NoError(t, err)
    22  	}()
    23  
    24  	f := filepath.Join(t.TempDir(), "db")
    25  	_, err = bolt.Open(f, 0666, nil)
    26  	require.Error(t, err)
    27  	require.ErrorContains(t, err, "map somehow failed")
    28  }
    29  
    30  // ensures when munmap fails, the flock is unlocked
    31  func TestFailpoint_UnmapFail_DbClose(t *testing.T) {
    32  	//unmap error on db close
    33  	//we need to open the db first, and then enable the error.
    34  	//otherwise the db cannot be opened.
    35  	f := filepath.Join(t.TempDir(), "db")
    36  
    37  	err := gofail.Enable("unmapError", `return("unmap somehow failed")`)
    38  	require.NoError(t, err)
    39  	_, err = bolt.Open(f, 0666, nil)
    40  	require.Error(t, err)
    41  	require.ErrorContains(t, err, "unmap somehow failed")
    42  	//disable the error, and try to reopen the db
    43  	err = gofail.Disable("unmapError")
    44  	require.NoError(t, err)
    45  
    46  	db, err := bolt.Open(f, 0666, &bolt.Options{Timeout: 30 * time.Second})
    47  	require.NoError(t, err)
    48  	err = db.Close()
    49  	require.NoError(t, err)
    50  }
    51  
    52  func TestFailpoint_mLockFail(t *testing.T) {
    53  	err := gofail.Enable("mlockError", `return("mlock somehow failed")`)
    54  	require.NoError(t, err)
    55  
    56  	f := filepath.Join(t.TempDir(), "db")
    57  	_, err = bolt.Open(f, 0666, &bolt.Options{Mlock: true})
    58  	require.Error(t, err)
    59  	require.ErrorContains(t, err, "mlock somehow failed")
    60  
    61  	// It should work after disabling the failpoint.
    62  	err = gofail.Disable("mlockError")
    63  	require.NoError(t, err)
    64  
    65  	_, err = bolt.Open(f, 0666, &bolt.Options{Mlock: true})
    66  	require.NoError(t, err)
    67  }
    68  
    69  func TestFailpoint_mLockFail_When_remap(t *testing.T) {
    70  	db := btesting.MustCreateDB(t)
    71  	db.Mlock = true
    72  
    73  	err := gofail.Enable("mlockError", `return("mlock somehow failed in allocate")`)
    74  	require.NoError(t, err)
    75  
    76  	err = db.Fill([]byte("data"), 1, 10000,
    77  		func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
    78  		func(tx int, k int) []byte { return make([]byte, 100) },
    79  	)
    80  
    81  	require.Error(t, err)
    82  	require.ErrorContains(t, err, "mlock somehow failed in allocate")
    83  
    84  	// It should work after disabling the failpoint.
    85  	err = gofail.Disable("mlockError")
    86  	require.NoError(t, err)
    87  	db.MustClose()
    88  	db.MustReopen()
    89  
    90  	err = db.Fill([]byte("data"), 1, 10000,
    91  		func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
    92  		func(tx int, k int) []byte { return make([]byte, 100) },
    93  	)
    94  
    95  	require.NoError(t, err)
    96  }
    97  
    98  // TestIssue72 reproduces issue 72.
    99  //
   100  // When bbolt is processing a `Put` invocation, the key might be concurrently
   101  // updated by the application which calls the `Put` API (although it shouldn't).
   102  // It might lead to a situation that bbolt use an old key to find a proper
   103  // position to insert the key/value pair, but actually inserts a new key.
   104  // Eventually it might break the rule that all keys should be sorted. In a
   105  // worse case, it might cause page elements to point to already freed pages.
   106  //
   107  // REF: https://github.com/etcd-io/bbolt/issues/72
   108  func TestIssue72(t *testing.T) {
   109  	db := btesting.MustCreateDBWithOption(t, &bolt.Options{PageSize: 4096})
   110  
   111  	bucketName := []byte(t.Name())
   112  	err := db.Update(func(tx *bolt.Tx) error {
   113  		_, txerr := tx.CreateBucket(bucketName)
   114  		return txerr
   115  	})
   116  	require.NoError(t, err)
   117  
   118  	// The layout is like:
   119  	//
   120  	//         +--+--+--+
   121  	//  +------+1 |3 |10+---+
   122  	//  |      +-++--+--+   |
   123  	//  |         |         |
   124  	//  |         |         |
   125  	// +v-+--+   +v-+--+  +-v+--+--+
   126  	// |1 |2 |   |3 |4 |  |10|11|12|
   127  	// +--+--+   +--+--+  +--+--+--+
   128  	//
   129  	err = db.Update(func(tx *bolt.Tx) error {
   130  		bk := tx.Bucket(bucketName)
   131  
   132  		for _, id := range []int{1, 2, 3, 4, 10, 11, 12} {
   133  			if txerr := bk.Put(idToBytes(id), make([]byte, 1000)); txerr != nil {
   134  				return txerr
   135  			}
   136  		}
   137  		return nil
   138  	})
   139  	require.NoError(t, err)
   140  
   141  	require.NoError(t, gofail.Enable("beforeBucketPut", `sleep(5000)`))
   142  
   143  	//         +--+--+--+
   144  	//  +------+1 |3 |1 +---+
   145  	//  |      +-++--+--+   |
   146  	//  |         |         |
   147  	//  |         |         |
   148  	// +v-+--+   +v-+--+  +-v+--+--+--+
   149  	// |1 |2 |   |3 |4 |  |1 |10|11|12|
   150  	// +--+--+   +--+--+  +--+--+--+--+
   151  	//
   152  	key := idToBytes(13)
   153  	updatedKey := idToBytes(1)
   154  	err = db.Update(func(tx *bolt.Tx) error {
   155  		bk := tx.Bucket(bucketName)
   156  
   157  		go func() {
   158  			time.Sleep(3 * time.Second)
   159  			copy(key, updatedKey)
   160  		}()
   161  		return bk.Put(key, make([]byte, 100))
   162  	})
   163  	require.NoError(t, err)
   164  
   165  	require.NoError(t, gofail.Disable("beforeBucketPut"))
   166  
   167  	// bbolt inserts 100 into last branch page. Since there are two `1`
   168  	// keys in branch, spill operation will update first `1` pointer and
   169  	// then last one won't be updated and continues to point to freed page.
   170  	//
   171  	//
   172  	//                  +--+--+--+
   173  	//  +---------------+1 |3 |1 +---------+
   174  	//  |               +--++-+--+         |
   175  	//  |                   |              |
   176  	//  |                   |              |
   177  	//  |        +--+--+   +v-+--+   +-----v-----+
   178  	//  |        |1 |2 |   |3 |4 |   |freed page |
   179  	//  |        +--+--+   +--+--+   +-----------+
   180  	//  |
   181  	// +v-+--+--+--+---+
   182  	// |1 |10|11|12|100|
   183  	// +--+--+--+--+---+
   184  	err = db.Update(func(tx *bolt.Tx) error {
   185  		return tx.Bucket(bucketName).Put(idToBytes(100), make([]byte, 100))
   186  	})
   187  	require.NoError(t, err)
   188  
   189  	defer func() {
   190  		if r := recover(); r != nil {
   191  			t.Logf("panic info:\n %v", r)
   192  		}
   193  	}()
   194  
   195  	// Add more keys to ensure branch node to spill.
   196  	err = db.Update(func(tx *bolt.Tx) error {
   197  		bk := tx.Bucket(bucketName)
   198  
   199  		for _, id := range []int{101, 102, 103, 104, 105} {
   200  			if txerr := bk.Put(idToBytes(id), make([]byte, 1000)); txerr != nil {
   201  				return txerr
   202  			}
   203  		}
   204  		return nil
   205  	})
   206  	require.NoError(t, err)
   207  }
   208  
   209  func idToBytes(id int) []byte {
   210  	return []byte(fmt.Sprintf("%010d", id))
   211  }
   212  

View as plain text