github.com/cockroachdb/pebble@v1.1.2/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 } 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.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 inj := errorfs.OnIndex(-1) 170 fs := errorfs.Wrap(vfs.NewMem(), inj) 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 inj.SetIndex(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 := inj.Index(); 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, _ string) error { 371 if op.OpKind() == errorfs.OpKindWrite && 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 }