github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/error_test.go (about)

     1  // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package pebble
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"math"
    11  	"strings"
    12  	"sync/atomic"
    13  	"testing"
    14  
    15  	"github.com/cockroachdb/errors"
    16  	"github.com/cockroachdb/pebble/vfs"
    17  	"github.com/cockroachdb/pebble/vfs/errorfs"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  type panicLogger struct{}
    22  
    23  func (l panicLogger) Infof(format string, args ...interface{})  {}
    24  func (l panicLogger) Errorf(format string, args ...interface{}) {}
    25  
    26  func (l panicLogger) Fatalf(format string, args ...interface{}) {
    27  	panic(errors.Errorf("fatal: "+format, args...))
    28  }
    29  
    30  // corruptFS injects a corruption in the `index`th byte read.
    31  type corruptFS struct {
    32  	vfs.FS
    33  	// index is the index of the byte which we will corrupt.
    34  	index     atomic.Int32
    35  	bytesRead atomic.Int32
    36  }
    37  
    38  func (fs *corruptFS) maybeCorrupt(n int32, p []byte) {
    39  	newBytesRead := fs.bytesRead.Add(n)
    40  	pIdx := newBytesRead - 1 - fs.index.Load()
    41  	if pIdx >= 0 && pIdx < n {
    42  		p[pIdx]++
    43  	}
    44  }
    45  
    46  func (fs *corruptFS) maybeCorruptAt(n int32, p []byte, offset int64) {
    47  	pIdx := fs.index.Load() - int32(offset)
    48  	if pIdx >= 0 && pIdx < n {
    49  		p[pIdx]++
    50  	}
    51  }
    52  
    53  func (fs *corruptFS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
    54  	f, err := fs.FS.Open(name)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	cf := corruptFile{f, fs}
    59  	for _, opt := range opts {
    60  		opt.Apply(cf)
    61  	}
    62  	return cf, nil
    63  }
    64  
    65  type corruptFile struct {
    66  	vfs.File
    67  	fs *corruptFS
    68  }
    69  
    70  func (f corruptFile) Read(p []byte) (int, error) {
    71  	n, err := f.File.Read(p)
    72  	f.fs.maybeCorrupt(int32(n), p)
    73  	return n, err
    74  }
    75  
    76  func (f corruptFile) ReadAt(p []byte, off int64) (int, error) {
    77  	n, err := f.File.ReadAt(p, off)
    78  	f.fs.maybeCorruptAt(int32(n), p, off)
    79  	return n, err
    80  }
    81  
    82  func expectLSM(expected string, d *DB, t *testing.T) {
    83  	t.Helper()
    84  	expected = strings.TrimSpace(expected)
    85  	d.mu.Lock()
    86  	actual := d.mu.versions.currentVersion().String()
    87  	d.mu.Unlock()
    88  	actual = strings.TrimSpace(actual)
    89  	if expected != actual {
    90  		t.Fatalf("expected\n%s\nbut found\n%s", expected, actual)
    91  	}
    92  }
    93  
    94  // TestErrors repeatedly runs a short sequence of operations, injecting FS
    95  // errors at different points, until success is achieved.
    96  func TestErrors(t *testing.T) {
    97  	run := func(fs *errorfs.FS) (err error) {
    98  		defer func() {
    99  			if r := recover(); r != nil {
   100  				if e, ok := r.(error); ok {
   101  					err = e
   102  				} else {
   103  					t.Fatal(r)
   104  				}
   105  			}
   106  		}()
   107  
   108  		d, err := Open("", &Options{
   109  			FS:     fs,
   110  			Logger: panicLogger{},
   111  		})
   112  		if err != nil {
   113  			return err
   114  		}
   115  
   116  		key := []byte("a")
   117  		value := []byte("b")
   118  		if err := d.Set(key, value, nil); err != nil {
   119  			return err
   120  		}
   121  		if err := d.Flush(); err != nil {
   122  			return err
   123  		}
   124  		if err := d.Compact(nil, []byte("\xff"), false); err != nil {
   125  			return err
   126  		}
   127  
   128  		iter, _ := d.NewIter(nil)
   129  		for valid := iter.First(); valid; valid = iter.Next() {
   130  		}
   131  		if err := iter.Close(); err != nil {
   132  			return err
   133  		}
   134  		return d.Close()
   135  	}
   136  
   137  	errorCounts := make(map[string]int)
   138  	for i := int32(0); ; i++ {
   139  		fs := errorfs.Wrap(vfs.NewMem(), errorfs.ErrInjected.If(errorfs.OnIndex(i)))
   140  		err := run(fs)
   141  		if err == nil {
   142  			t.Logf("success %d\n", i)
   143  			break
   144  		}
   145  		errorCounts[err.Error()]++
   146  	}
   147  
   148  	expectedErrors := []string{
   149  		"fatal: MANIFEST flush failed: injected error",
   150  		"fatal: MANIFEST sync failed: injected error",
   151  		"fatal: MANIFEST set current failed: injected error",
   152  		"fatal: MANIFEST dirsync failed: injected error",
   153  	}
   154  	for _, expected := range expectedErrors {
   155  		if errorCounts[expected] == 0 {
   156  			t.Errorf("expected error %q did not occur", expected)
   157  		}
   158  	}
   159  }
   160  
   161  // TestRequireReadError injects FS errors into read operations at successively later
   162  // points until all operations can complete. It requires an operation fails any time
   163  // an error was injected. This differs from the TestErrors case above as that one
   164  // cannot require operations fail since it involves flush/compaction, which retry
   165  // internally and succeed following an injected error.
   166  func TestRequireReadError(t *testing.T) {
   167  	run := func(formatVersion FormatMajorVersion, index int32) (err error) {
   168  		// Perform setup with error injection disabled as it involves writes/background ops.
   169  		ii := errorfs.OnIndex(-1)
   170  		fs := errorfs.Wrap(vfs.NewMem(), errorfs.ErrInjected.If(ii))
   171  		opts := &Options{
   172  			FS:                 fs,
   173  			Logger:             panicLogger{},
   174  			FormatMajorVersion: formatVersion,
   175  		}
   176  		opts.private.disableTableStats = true
   177  		d, err := Open("", opts)
   178  		require.NoError(t, err)
   179  
   180  		defer func() {
   181  			if d != nil {
   182  				require.NoError(t, d.Close())
   183  			}
   184  		}()
   185  
   186  		key1 := []byte("a1")
   187  		key2 := []byte("a2")
   188  		value := []byte("b")
   189  		require.NoError(t, d.Set(key1, value, nil))
   190  		require.NoError(t, d.Set(key2, value, nil))
   191  		require.NoError(t, d.Flush())
   192  		require.NoError(t, d.Compact(key1, key2, false))
   193  		require.NoError(t, d.DeleteRange(key1, key2, nil))
   194  		require.NoError(t, d.Set(key1, value, nil))
   195  		require.NoError(t, d.Flush())
   196  		if formatVersion < FormatSetWithDelete {
   197  			expectLSM(`
   198  0.0:
   199    000007:[a1#13,SET-a2#inf,RANGEDEL]
   200  6:
   201    000005:[a1#10,SET-a2#11,SET]
   202  `, d, t)
   203  		} else {
   204  			expectLSM(`
   205  0.0:
   206    000007:[a1#13,SETWITHDEL-a2#inf,RANGEDEL]
   207  6:
   208    000005:[a1#10,SET-a2#11,SET]
   209  `, d, t)
   210  		}
   211  
   212  		// Now perform foreground ops with error injection enabled.
   213  		ii.Store(index)
   214  		iter, _ := d.NewIter(nil)
   215  		if err := iter.Error(); err != nil {
   216  			return err
   217  		}
   218  		numFound := 0
   219  		expectedKeys := [][]byte{key1, key2}
   220  		for valid := iter.First(); valid; valid = iter.Next() {
   221  			if !bytes.Equal(iter.Key(), expectedKeys[numFound]) {
   222  				t.Fatalf("expected key %v; found %v", expectedKeys[numFound], iter.Key())
   223  			}
   224  			if !bytes.Equal(iter.Value(), value) {
   225  				t.Fatalf("expected value %v; found %v", value, iter.Value())
   226  			}
   227  			numFound++
   228  		}
   229  		if err := iter.Close(); err != nil {
   230  			return err
   231  		}
   232  		if err := d.Close(); err != nil {
   233  			d = nil
   234  			return err
   235  		}
   236  		d = nil
   237  		// Reaching here implies all read operations succeeded. This
   238  		// should only happen when we reached a large enough index at
   239  		// which `errorfs.FS` did not return any error.
   240  		if i := ii.Load(); i < 0 {
   241  			t.Errorf("FS error injected %d ops ago went unreported", -i)
   242  		}
   243  		if numFound != 2 {
   244  			t.Fatalf("expected 2 values; found %d", numFound)
   245  		}
   246  		return nil
   247  	}
   248  
   249  	versions := []FormatMajorVersion{FormatMostCompatible, FormatSetWithDelete}
   250  	for _, version := range versions {
   251  		t.Run(fmt.Sprintf("version-%s", version), func(t *testing.T) {
   252  			for i := int32(0); ; i++ {
   253  				err := run(version, i)
   254  				if err == nil {
   255  					t.Logf("no failures reported at index %d", i)
   256  					break
   257  				}
   258  			}
   259  		})
   260  	}
   261  }
   262  
   263  // TestCorruptReadError verifies that reads to a corrupted file detect the
   264  // corruption and return an error. In this case the filesystem reads return
   265  // successful status but the data they return is corrupt.
   266  func TestCorruptReadError(t *testing.T) {
   267  	run := func(formatVersion FormatMajorVersion, index int32) (err error) {
   268  		// Perform setup with corruption injection disabled as it involves writes/background ops.
   269  		fs := &corruptFS{
   270  			FS: vfs.NewMem(),
   271  		}
   272  		fs.index.Store(-1)
   273  		opts := &Options{
   274  			FS:                 fs,
   275  			Logger:             panicLogger{},
   276  			FormatMajorVersion: formatVersion,
   277  		}
   278  		opts.private.disableTableStats = true
   279  		d, err := Open("", opts)
   280  		if err != nil {
   281  			t.Fatalf("%v", err)
   282  		}
   283  		defer func() {
   284  			if d != nil {
   285  				require.NoError(t, d.Close())
   286  			}
   287  		}()
   288  
   289  		key1 := []byte("a1")
   290  		key2 := []byte("a2")
   291  		value := []byte("b")
   292  		require.NoError(t, d.Set(key1, value, nil))
   293  		require.NoError(t, d.Set(key2, value, nil))
   294  		require.NoError(t, d.Flush())
   295  		require.NoError(t, d.Compact(key1, key2, false))
   296  		require.NoError(t, d.DeleteRange(key1, key2, nil))
   297  		require.NoError(t, d.Set(key1, value, nil))
   298  		require.NoError(t, d.Flush())
   299  		if formatVersion < FormatSetWithDelete {
   300  			expectLSM(`
   301  0.0:
   302    000007:[a1#13,SET-a2#inf,RANGEDEL]
   303  6:
   304    000005:[a1#10,SET-a2#11,SET]
   305  `, d, t)
   306  
   307  		} else {
   308  			expectLSM(`
   309  0.0:
   310    000007:[a1#13,SETWITHDEL-a2#inf,RANGEDEL]
   311  6:
   312    000005:[a1#10,SET-a2#11,SET]
   313  `, d, t)
   314  		}
   315  
   316  		// Now perform foreground ops with corruption injection enabled.
   317  		fs.index.Store(index)
   318  		iter, _ := d.NewIter(nil)
   319  		if err := iter.Error(); err != nil {
   320  			return err
   321  		}
   322  
   323  		numFound := 0
   324  		expectedKeys := [][]byte{key1, key2}
   325  		for valid := iter.First(); valid; valid = iter.Next() {
   326  			if !bytes.Equal(iter.Key(), expectedKeys[numFound]) {
   327  				t.Fatalf("expected key %v; found %v", expectedKeys[numFound], iter.Key())
   328  			}
   329  			if !bytes.Equal(iter.Value(), value) {
   330  				t.Fatalf("expected value %v; found %v", value, iter.Value())
   331  			}
   332  			numFound++
   333  		}
   334  		if err := iter.Close(); err != nil {
   335  			return err
   336  		}
   337  		if err := d.Close(); err != nil {
   338  			return err
   339  		}
   340  		d = nil
   341  		// Reaching here implies all read operations succeeded. This
   342  		// should only happen when we reached a large enough index at
   343  		// which `corruptFS` did not inject any corruption.
   344  		if bytesRead := fs.bytesRead.Load(); bytesRead > index {
   345  			t.Errorf("corruption error injected at index %d went unreported", index)
   346  		}
   347  		if numFound != 2 {
   348  			t.Fatalf("expected 2 values; found %d", numFound)
   349  		}
   350  		return nil
   351  	}
   352  	versions := []FormatMajorVersion{FormatMostCompatible, FormatSetWithDelete}
   353  	for _, version := range versions {
   354  		t.Run(fmt.Sprintf("version-%s", version), func(t *testing.T) {
   355  			for i := int32(0); ; i++ {
   356  				err := run(version, i)
   357  				if err == nil {
   358  					t.Logf("no failures reported at index %d", i)
   359  					break
   360  				}
   361  			}
   362  		})
   363  	}
   364  }
   365  
   366  func TestDBWALRotationCrash(t *testing.T) {
   367  	memfs := vfs.NewStrictMem()
   368  
   369  	var index atomic.Int32
   370  	inj := errorfs.InjectorFunc(func(op errorfs.Op) error {
   371  		if op.Kind.ReadOrWrite() == errorfs.OpIsWrite && index.Add(-1) == -1 {
   372  			memfs.SetIgnoreSyncs(true)
   373  		}
   374  		return nil
   375  	})
   376  	triggered := func() bool { return index.Load() < 0 }
   377  
   378  	run := func(fs *errorfs.FS, k int32) (err error) {
   379  		opts := &Options{
   380  			FS:           fs,
   381  			Logger:       panicLogger{},
   382  			MemTableSize: 2048,
   383  		}
   384  		opts.private.disableTableStats = true
   385  		d, err := Open("", opts)
   386  		if err != nil || triggered() {
   387  			return err
   388  		}
   389  
   390  		// Write keys with the FS set up to simulate a crash by ignoring
   391  		// syncs on the k-th write operation.
   392  		index.Store(k)
   393  		key := []byte("test")
   394  		for i := 0; i < 10; i++ {
   395  			v := []byte(strings.Repeat("b", i))
   396  			err = d.Set(key, v, nil)
   397  			if err != nil || triggered() {
   398  				break
   399  			}
   400  		}
   401  		err = firstError(err, d.Close())
   402  		return err
   403  	}
   404  
   405  	fs := errorfs.Wrap(memfs, inj)
   406  	for k := int32(0); ; k++ {
   407  		// Run, simulating a crash by ignoring syncs after the k-th write
   408  		// operation after Open.
   409  		index.Store(math.MaxInt32)
   410  		err := run(fs, k)
   411  		if !triggered() {
   412  			// Stop when we reach a value of k greater than the number of
   413  			// write operations performed during `run`.
   414  			t.Logf("No crash at write operation %d\n", k)
   415  			if err != nil {
   416  				t.Fatalf("Filesystem did not 'crash', but error returned: %s", err)
   417  			}
   418  			break
   419  		}
   420  		t.Logf("Crashed at write operation % 2d, error: %v\n", k, err)
   421  
   422  		// Reset the filesystem to its state right before the simulated
   423  		// "crash", restore syncs, and run again without crashing.
   424  		memfs.ResetToSyncedState()
   425  		memfs.SetIgnoreSyncs(false)
   426  		index.Store(math.MaxInt32)
   427  		require.NoError(t, run(fs, k))
   428  	}
   429  }