Repository: cockroachdb/pebble
Pull request #619 (2020-04-10 15:00:01)

vfs: match RocksDB WAL preallocation semantics
RocksDB always uses `FALLOC_FL_KEEP_SIZE` when doing WAL
preallocation. Failure to use `FALLOC_FL_KEEP_SIZE` was causing RocksDB
to sometimes fail to open a Pebble created DB if it found a fragmented
log record where a middle fragment was zeroed. This can occur if the
Pebble DB crashed after writing the first fragment of a record and the
WAL had been preallocated. Ideally, RocksDB would be changed to treat
such zeroed records the same as recycled records, but for compatibility
we want to deal with the versions of RocksDB already used by CockroachDB
in the wild.

Fixes cockroachdb/cockroach#47255

petermattis: 

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.png" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/cockroachdb/pebble/619)
<!-- Reviewable:end -->


Patch:
diff --git a/db.go b/db.go
index 873e70b8..bfffa486 100644
--- a/db.go
+++ b/db.go
@@ -992,11 +992,11 @@ func (d *DB) AsyncFlush() (<-chan struct{}, error) {
 	}
 
 	d.commit.mu.Lock()
-	defer d.commit.mu.Unlock()
 	d.mu.Lock()
-	defer d.mu.Unlock()
 	flushed := d.mu.mem.queue[len(d.mu.mem.queue)-1].flushed
 	err := d.makeRoomForWrite(nil)
+	d.mu.Unlock()
+	d.commit.mu.Unlock()
 	if err != nil {
 		return nil, err
 	}
diff --git a/ingest.go b/ingest.go
index 49e8559d..9843f5d4 100644
--- a/ingest.go
+++ b/ingest.go
@@ -86,9 +86,6 @@ func ingestLoad1(
 			meta.Smallest = key.Clone()
 			smallestSet = true
 		}
-		if err := iter.Error(); err != nil {
-			return nil, err
-		}
 		if key, _ := iter.Last(); key != nil {
 			if err := ingestValidateKey(opts, key); err != nil {
 				return nil, err
@@ -118,9 +115,6 @@ func ingestLoad1(
 				meta.Smallest = key.Clone()
 			}
 		}
-		if err := iter.Error(); err != nil {
-			return nil, err
-		}
 		if key, val := iter.Last(); key != nil {
 			if err := ingestValidateKey(opts, key); err != nil {
 				return nil, err
@@ -630,8 +624,6 @@ func (d *DB) ingestApply(jobID int, meta []*fileMetadata) (*versionEdit, error)
 	// determine the target level. This prevents two concurrent ingestion jobs
 	// from using the same version to determine the target level, and also
 	// provides serialization with concurrent compaction and flush jobs.
-	// logAndApply unconditionally releases the manifest lock, but any earlier
-	// returns must unlock the manifest.
 	d.mu.versions.logLock()
 	current := d.mu.versions.currentVersion()
 	baseLevel := d.mu.versions.picker.getBaseLevel()
@@ -644,7 +636,6 @@ func (d *DB) ingestApply(jobID int, meta []*fileMetadata) (*versionEdit, error)
 		var err error
 		f.Level, err = ingestTargetLevel(d.newIters, iterOps, d.cmp, current, baseLevel, d.mu.compact.inProgress, m)
 		if err != nil {
-			d.mu.versions.logUnlock()
 			return nil, err
 		}
 		f.Meta = m
diff --git a/internal/errorfs/errorfs.go b/internal/errorfs/errorfs.go
index fea3374b..1e8c70b1 100644
--- a/internal/errorfs/errorfs.go
+++ b/internal/errorfs/errorfs.go
@@ -6,11 +6,8 @@ package errorfs
 
 import (
 	"io"
-	"math/rand"
 	"os"
-	"sync"
 	"sync/atomic"
-	"time"
 
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/vfs"
@@ -19,16 +16,6 @@ import (
 // ErrInjected is an error artifically injected for testing fs error paths.
 var ErrInjected = errors.New("injected error")
 
-// Op is an enum describing the type of operation performed.
-type Op int
-
-const (
-	// OpRead describes read operations.
-	OpRead Op = iota
-	// OpWrite describes write operations.
-	OpWrite
-)
-
 // OnIndex constructs an injector that returns an error on
 // the (n+1)-th invocation of its MaybeError function. It
 // may be passed to Wrap to inject an error into an FS.
@@ -48,37 +35,16 @@ func (ii *InjectIndex) Index() int32 { return atomic.LoadInt32(&ii.index) }
 func (ii *InjectIndex) SetIndex(v int32) { atomic.StoreInt32(&ii.index, v) }
 
 // MaybeError implements the Injector interface.
-func (ii *InjectIndex) MaybeError(op Op) error {
+func (ii *InjectIndex) MaybeError() error {
 	if atomic.AddInt32(&ii.index, -1) == -1 {
-		return errors.WithStack(ErrInjected)
+		return ErrInjected
 	}
 	return nil
 }
 
-// WithProbability returns a function that returns an error with the provided
-// probability when passed op. It may be passed to Wrap to inject an error
-// into an ErrFS with the provided probability. p should be within the range
-// [0.0,1.0].
-func WithProbability(op Op, p float64) Injector {
-	mu := new(sync.Mutex)
-	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
-	return injectorFunc(func(currOp Op) error {
-		mu.Lock()
-		defer mu.Unlock()
-		if currOp == op && rnd.Float64() < p {
-			return errors.WithStack(ErrInjected)
-		}
-		return nil
-	})
-}
-
-type injectorFunc func(Op) error
-
-func (f injectorFunc) MaybeError(op Op) error { return f(op) }
-
 // Injector injects errors into FS operations.
 type Injector interface {
-	MaybeError(Op) error
+	MaybeError() error
 }
 
 // FS implements vfs.FS, injecting errors into
@@ -105,30 +71,24 @@ func Wrap(fs vfs.FS, inj Injector) *FS {
 // deciding when to inject errors. If an error is injected, the file
 // propagates the error instead of shadowing the operation.
 func WrapFile(f vfs.File, inj Injector) vfs.File {
-	return &errorFile{file: f, inj: inj}
-}
-
-// Unwrap returns the FS implementation underlying fs.
-// See pebble/vfs.Root.
-func (fs *FS) Unwrap() vfs.FS {
-	return fs.fs
+	return errorFile{file: f, inj: inj}
 }
 
 // Create implements FS.Create.
 func (fs *FS) Create(name string) (vfs.File, error) {
-	if err := fs.inj.MaybeError(OpWrite); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return nil, err
 	}
 	f, err := fs.fs.Create(name)
 	if err != nil {
 		return nil, err
 	}
-	return &errorFile{f, fs.inj}, nil
+	return errorFile{f, fs.inj}, nil
 }
 
 // Link implements FS.Link.
 func (fs *FS) Link(oldname, newname string) error {
-	if err := fs.inj.MaybeError(OpWrite); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return err
 	}
 	return fs.fs.Link(oldname, newname)
@@ -136,14 +96,14 @@ func (fs *FS) Link(oldname, newname string) error {
 
 // Open implements FS.Open.
 func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
-	if err := fs.inj.MaybeError(OpRead); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return nil, err
 	}
 	f, err := fs.fs.Open(name)
 	if err != nil {
 		return nil, err
 	}
-	ef := &errorFile{f, fs.inj}
+	ef := errorFile{f, fs.inj}
 	for _, opt := range opts {
 		opt.Apply(ef)
 	}
@@ -152,14 +112,14 @@ func (fs *FS) Open(name string, opts ...vfs.OpenOption) (vfs.File, error) {
 
 // OpenDir implements FS.OpenDir.
 func (fs *FS) OpenDir(name string) (vfs.File, error) {
-	if err := fs.inj.MaybeError(OpRead); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return nil, err
 	}
 	f, err := fs.fs.OpenDir(name)
 	if err != nil {
 		return nil, err
 	}
-	return &errorFile{f, fs.inj}, nil
+	return errorFile{f, fs.inj}, nil
 }
 
 // PathBase implements FS.PathBase.
@@ -183,7 +143,7 @@ func (fs *FS) Remove(name string) error {
 		return nil
 	}
 
-	if err := fs.inj.MaybeError(OpWrite); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return err
 	}
 	return fs.fs.Remove(name)
@@ -191,7 +151,7 @@ func (fs *FS) Remove(name string) error {
 
 // RemoveAll implements FS.RemoveAll.
 func (fs *FS) RemoveAll(fullname string) error {
-	if err := fs.inj.MaybeError(OpWrite); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return err
 	}
 	return fs.fs.RemoveAll(fullname)
@@ -199,7 +159,7 @@ func (fs *FS) RemoveAll(fullname string) error {
 
 // Rename implements FS.Rename.
 func (fs *FS) Rename(oldname, newname string) error {
-	if err := fs.inj.MaybeError(OpWrite); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return err
 	}
 	return fs.fs.Rename(oldname, newname)
@@ -207,7 +167,7 @@ func (fs *FS) Rename(oldname, newname string) error {
 
 // ReuseForWrite implements FS.ReuseForWrite.
 func (fs *FS) ReuseForWrite(oldname, newname string) (vfs.File, error) {
-	if err := fs.inj.MaybeError(OpWrite); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return nil, err
 	}
 	return fs.fs.ReuseForWrite(oldname, newname)
@@ -215,7 +175,7 @@ func (fs *FS) ReuseForWrite(oldname, newname string) (vfs.File, error) {
 
 // MkdirAll implements FS.MkdirAll.
 func (fs *FS) MkdirAll(dir string, perm os.FileMode) error {
-	if err := fs.inj.MaybeError(OpWrite); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return err
 	}
 	return fs.fs.MkdirAll(dir, perm)
@@ -223,7 +183,7 @@ func (fs *FS) MkdirAll(dir string, perm os.FileMode) error {
 
 // Lock implements FS.Lock.
 func (fs *FS) Lock(name string) (io.Closer, error) {
-	if err := fs.inj.MaybeError(OpWrite); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return nil, err
 	}
 	return fs.fs.Lock(name)
@@ -231,7 +191,7 @@ func (fs *FS) Lock(name string) (io.Closer, error) {
 
 // List implements FS.List.
 func (fs *FS) List(dir string) ([]string, error) {
-	if err := fs.inj.MaybeError(OpRead); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return nil, err
 	}
 	return fs.fs.List(dir)
@@ -239,55 +199,53 @@ func (fs *FS) List(dir string) ([]string, error) {
 
 // Stat implements FS.Stat.
 func (fs *FS) Stat(name string) (os.FileInfo, error) {
-	if err := fs.inj.MaybeError(OpRead); err != nil {
+	if err := fs.inj.MaybeError(); err != nil {
 		return nil, err
 	}
 	return fs.fs.Stat(name)
 }
 
-// errorFile implements vfs.File. The interface is implemented on the pointer
-// type to allow pointer equality comparisons.
 type errorFile struct {
 	file vfs.File
 	inj  Injector
 }
 
-func (f *errorFile) Close() error {
+func (f errorFile) Close() error {
 	// We don't inject errors during close as those calls should never fail in
 	// practice.
 	return f.file.Close()
 }
 
-func (f *errorFile) Read(p []byte) (int, error) {
-	if err := f.inj.MaybeError(OpRead); err != nil {
+func (f errorFile) Read(p []byte) (int, error) {
+	if err := f.inj.MaybeError(); err != nil {
 		return 0, err
 	}
 	return f.file.Read(p)
 }
 
-func (f *errorFile) ReadAt(p []byte, off int64) (int, error) {
-	if err := f.inj.MaybeError(OpRead); err != nil {
+func (f errorFile) ReadAt(p []byte, off int64) (int, error) {
+	if err := f.inj.MaybeError(); err != nil {
 		return 0, err
 	}
 	return f.file.ReadAt(p, off)
 }
 
-func (f *errorFile) Write(p []byte) (int, error) {
-	if err := f.inj.MaybeError(OpWrite); err != nil {
+func (f errorFile) Write(p []byte) (int, error) {
+	if err := f.inj.MaybeError(); err != nil {
 		return 0, err
 	}
 	return f.file.Write(p)
 }
 
-func (f *errorFile) Stat() (os.FileInfo, error) {
-	if err := f.inj.MaybeError(OpRead); err != nil {
+func (f errorFile) Stat() (os.FileInfo, error) {
+	if err := f.inj.MaybeError(); err != nil {
 		return nil, err
 	}
 	return f.file.Stat()
 }
 
-func (f *errorFile) Sync() error {
-	if err := f.inj.MaybeError(OpWrite); err != nil {
+func (f errorFile) Sync() error {
+	if err := f.inj.MaybeError(); err != nil {
 		return err
 	}
 	return f.file.Sync()
diff --git a/internal/metamorphic/ops.go b/internal/metamorphic/ops.go
index 63f213af..477be541 100644
--- a/internal/metamorphic/ops.go
+++ b/internal/metamorphic/ops.go
@@ -6,16 +6,13 @@ package metamorphic
 
 import (
 	"fmt"
-	"io"
 	"os"
 	"strings"
 
 	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble"
-	"github.com/cockroachdb/pebble/internal/errorfs"
 	"github.com/cockroachdb/pebble/internal/private"
 	"github.com/cockroachdb/pebble/sstable"
-	"github.com/cockroachdb/pebble/vfs"
 )
 
 // op defines the interface for a single operation, such as creating a batch,
@@ -34,7 +31,7 @@ type initOp struct {
 
 func (o *initOp) run(t *test, h *history) {
 	t.batches = make([]*pebble.Batch, o.batchSlots)
-	t.iters = make([]*retryableIter, o.iterSlots)
+	t.iters = make([]*pebble.Iterator, o.iterSlots)
 	t.snapshots = make([]*pebble.Snapshot, o.snapshotSlots)
 	h.Recordf("%s", o)
 }
@@ -232,17 +229,14 @@ func (o *ingestOp) run(t *test, h *history) {
 		err = firstError(err, b.Close())
 	}
 
-	err = firstError(err, withRetries(func() error {
-		return t.db.Ingest(paths)
-	}))
+	err = firstError(err, t.db.Ingest(paths))
 
 	h.Recordf("%s // %v", o, err)
 }
 
 func (o *ingestOp) build(t *test, h *history, b *pebble.Batch, i int) (string, error) {
-	rootFS := vfs.Root(t.opts.FS)
-	path := rootFS.PathJoin(t.tmpDir, fmt.Sprintf("ext%d", i))
-	f, err := rootFS.Create(path)
+	path := t.opts.FS.PathJoin(t.tmpDir, fmt.Sprintf("ext%d", i))
+	f, err := t.opts.FS.Create(path)
 	if err != nil {
 		return "", err
 	}
@@ -413,12 +407,7 @@ type getOp struct {
 
 func (o *getOp) run(t *test, h *history) {
 	r := t.getReader(o.readerID)
-	var val []byte
-	var closer io.Closer
-	err := withRetries(func() (err error) {
-		val, closer, err = r.Get(o.key)
-		return err
-	})
+	val, closer, err := r.Get(o.key)
 	h.Recordf("%s // [%q] %v", o, val, err)
 	if closer != nil {
 		closer.Close()
@@ -439,20 +428,12 @@ type newIterOp struct {
 
 func (o *newIterOp) run(t *test, h *history) {
 	r := t.getReader(o.readerID)
-	var i *pebble.Iterator
-	for {
-		i = r.NewIter(&pebble.IterOptions{
-			LowerBound: o.lower,
-			UpperBound: o.upper,
-		})
-		if err := i.Error(); !errors.Is(err, errorfs.ErrInjected) {
-			break
-		}
-		// close this iter and retry NewIter
-		_ = i.Close()
-	}
+	i := r.NewIter(&pebble.IterOptions{
+		LowerBound: o.lower,
+		UpperBound: o.upper,
+	})
 	t.setIter(o.iterID, i)
-	h.Recordf("%s // %v", o, i.Error())
+	h.Recordf("%s", o)
 }
 
 func (o *newIterOp) String() string {
diff --git a/internal/metamorphic/retryable.go b/internal/metamorphic/retryable.go
deleted file mode 100644
index ecbd0e20..00000000
--- a/internal/metamorphic/retryable.go
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use
-// of this source code is governed by a BSD-style license that can be found in
-// the LICENSE file.
-
-package metamorphic
-
-import (
-	"github.com/cockroachdb/errors"
-	"github.com/cockroachdb/pebble"
-	"github.com/cockroachdb/pebble/internal/errorfs"
-)
-
-// withRetries executes fn, retrying it whenever an errorfs.ErrInjected error
-// is returned.  It returns the first nil or non-errorfs.ErrInjected error
-// returned by fn.
-func withRetries(fn func() error) error {
-	for {
-		if err := fn(); !errors.Is(err, errorfs.ErrInjected) {
-			return err
-		}
-	}
-}
-
-// retryableIter holds an iterator and the state necessary to reset it to its
-// state after the last successful operation. This allows us to retry failed
-// iterator operations by running them again on a non-error iterator with the
-// same pre-operation state.
-type retryableIter struct {
-	iter    *pebble.Iterator
-	lastKey []byte
-}
-
-func (i *retryableIter) needRetry() bool {
-	return errors.Is(i.iter.Error(), errorfs.ErrInjected)
-}
-
-func (i *retryableIter) withRetry(fn func()) {
-	for {
-		fn()
-		if !i.needRetry() {
-			break
-		}
-		for i.needRetry() {
-			i.iter.SeekGE(i.lastKey)
-		}
-	}
-
-	i.lastKey = i.lastKey[:0]
-	if i.iter.Valid() {
-		i.lastKey = append(i.lastKey, i.iter.Key()...)
-	}
-}
-
-func (i *retryableIter) Close() error {
-	return i.iter.Close()
-}
-
-func (i *retryableIter) Error() error {
-	return i.iter.Error()
-}
-
-func (i *retryableIter) First() bool {
-	var valid bool
-	i.withRetry(func() { valid = i.iter.First() })
-	return valid
-}
-
-func (i *retryableIter) Key() []byte {
-	return i.iter.Key()
-}
-
-func (i *retryableIter) Last() bool {
-	var valid bool
-	i.withRetry(func() { valid = i.iter.Last() })
-	return valid
-}
-
-func (i *retryableIter) Next() bool {
-	var valid bool
-	i.withRetry(func() { valid = i.iter.Next() })
-	return valid
-}
-
-func (i *retryableIter) Prev() bool {
-	var valid bool
-	i.withRetry(func() { valid = i.iter.Prev() })
-	return valid
-}
-
-func (i *retryableIter) SeekGE(key []byte) bool {
-	var valid bool
-	i.withRetry(func() { valid = i.iter.SeekGE(key) })
-	return valid
-}
-
-func (i *retryableIter) SeekLT(key []byte) bool {
-	var valid bool
-	i.withRetry(func() { valid = i.iter.SeekLT(key) })
-	return valid
-}
-
-func (i *retryableIter) SeekPrefixGE(key []byte) bool {
-	var valid bool
-	i.withRetry(func() { valid = i.iter.SeekPrefixGE(key) })
-	return valid
-}
-
-func (i *retryableIter) SetBounds(lower, upper []byte) {
-	i.iter.SetBounds(lower, upper)
-}
-
-func (i *retryableIter) Valid() bool {
-	return i.iter.Valid()
-}
-
-func (i *retryableIter) Value() []byte {
-	return i.iter.Value()
-}
diff --git a/internal/record/log_writer.go b/internal/record/log_writer.go
index 63dcba19..b87fbff1 100644
--- a/internal/record/log_writer.go
+++ b/internal/record/log_writer.go
@@ -523,10 +523,8 @@ func (w *LogWriter) Close() error {
 	}
 
 	if w.c != nil {
-		cerr := w.c.Close()
-		w.c = nil
-		if cerr != nil {
-			return cerr
+		if err := w.c.Close(); err != nil {
+			return err
 		}
 	}
 	w.err = errors.New("pebble/record: closed LogWriter")
diff --git a/sstable/reader.go b/sstable/reader.go
index 804bfbd9..8e7d060f 100644
--- a/sstable/reader.go
+++ b/sstable/reader.go
@@ -1515,7 +1515,6 @@ func (r *Reader) EstimateDiskUsage(start, end []byte) (uint64, error) {
 		if err != nil {
 			return 0, err
 		}
-		defer startIdxBlock.Release()
 		startIdxIter, err = newBlockIter(r.Compare, startIdxBlock.Get())
 		if err != nil {
 			return 0, err
@@ -1535,7 +1534,6 @@ func (r *Reader) EstimateDiskUsage(start, end []byte) (uint64, error) {
 			if err != nil {
 				return 0, err
 			}
-			defer endIdxBlock.Release()
 			endIdxIter, err = newBlockIter(r.Compare, endIdxBlock.Get())
 			if err != nil {
 				return 0, err
diff --git a/vfs/preallocate_darwin.go b/vfs/preallocate_darwin.go
deleted file mode 100644
index cdaee8d7..00000000
--- a/vfs/preallocate_darwin.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2016 The etcd Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// +build darwin
-
-package vfs
-
-import (
-	"syscall"
-	"unsafe"
-)
-
-func preallocExtend(fd uintptr, offset, length int64) error {
-	if err := preallocFixed(fd, offset, length); err != nil {
-		return err
-	}
-	return syscall.Ftruncate(int(fd), offset+length)
-}
-
-func preallocFixed(fd uintptr, offset, length int64) error {
-	// allocate all requested space or no space at all
-	// TODO: allocate contiguous space on disk with F_ALLOCATECONTIG flag
-	fstore := &syscall.Fstore_t{
-		Flags:   syscall.F_ALLOCATEALL,
-		Posmode: syscall.F_PEOFPOSMODE,
-		Length:  length}
-	p := unsafe.Pointer(fstore)
-	_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, uintptr(syscall.F_PREALLOCATE), uintptr(p))
-	if errno == 0 || errno == syscall.ENOTSUP {
-		return nil
-	}
-
-	// wrong argument to fallocate syscall
-	if errno == syscall.EINVAL {
-		// filesystem "st_blocks" are allocated in the units of
-		// "Allocation Block Size" (run "diskutil info /" command)
-		var stat syscall.Stat_t
-		syscall.Fstat(int(fd), &stat)
-
-		// syscall.Statfs_t.Bsize is "optimal transfer block size"
-		// and contains matching 4096 value when latest OS X kernel
-		// supports 4,096 KB filesystem block size
-		var statfs syscall.Statfs_t
-		syscall.Fstatfs(int(fd), &statfs)
-		blockSize := int64(statfs.Bsize)
-
-		if stat.Blocks*blockSize >= offset+length {
-			// enough blocks are already allocated
-			return nil
-		}
-	}
-	return errno
-}
diff --git a/vfs/preallocate_generic.go b/vfs/preallocate_generic.go
index e55fd3f6..a7073921 100644
--- a/vfs/preallocate_generic.go
+++ b/vfs/preallocate_generic.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build !darwin,!linux
+// +build !linux
 
 package vfs
 
diff --git a/vfs/preallocate_linux.go b/vfs/preallocate_linux.go
index f83c2b4d..d7484d4d 100644
--- a/vfs/preallocate_linux.go
+++ b/vfs/preallocate_linux.go
@@ -18,17 +18,10 @@ package vfs
 
 import (
 	"syscall"
+
+	"golang.org/x/sys/unix"
 )
 
 func preallocExtend(fd uintptr, offset, length int64) error {
-	err := syscall.Fallocate(int(fd), 0 /* mode */, offset, length)
-	if err != nil {
-		errno, ok := err.(syscall.Errno)
-		// not supported; fallback
-		// fallocate EINTRs frequently in some environments; fallback
-		if ok && (errno == syscall.ENOTSUP || errno == syscall.EINTR) {
-			return syscall.Ftruncate(int(fd), offset+length)
-		}
-	}
-	return err
+	return syscall.Fallocate(int(fd), unix.FALLOC_FL_KEEP_SIZE, offset, length)
 }
diff --git a/vfs/vfs.go b/vfs/vfs.go
index a1c0b569..52856391 100644
--- a/vfs/vfs.go
+++ b/vfs/vfs.go
@@ -263,20 +263,3 @@ func LinkOrCopy(fs FS, oldname, newname string) error {
 	}
 	return Copy(fs, oldname, newname)
 }
-
-// Root returns the base FS implementation, unwrapping all nested FSs that
-// expose an Unwrap method.
-func Root(fs FS) FS {
-	type unwrapper interface {
-		Unwrap() FS
-	}
-
-	for {
-		u, ok := fs.(unwrapper)
-		if !ok {
-			break
-		}
-		fs = u.Unwrap()
-	}
-	return fs
-}


Test patch:
diff --git a/ingest_test.go b/ingest_test.go
index 74bdd727..f5a7b84b 100644
--- a/ingest_test.go
+++ b/ingest_test.go
@@ -16,7 +16,6 @@ import (
 	"testing"
 	"time"
 
-	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble/internal/base"
 	"github.com/cockroachdb/pebble/internal/datadriven"
 	"github.com/cockroachdb/pebble/internal/errorfs"
@@ -551,69 +550,6 @@ func TestIngest(t *testing.T) {
 	})
 }
 
-func TestIngestError(t *testing.T) {
-	for i := int32(0); ; i++ {
-		mem := vfs.NewMem()
-
-		f0, err := mem.Create("ext0")
-		require.NoError(t, err)
-		w := sstable.NewWriter(f0, sstable.WriterOptions{})
-		require.NoError(t, w.Set([]byte("d"), nil))
-		require.NoError(t, w.Close())
-		f1, err := mem.Create("ext1")
-		require.NoError(t, err)
-		w = sstable.NewWriter(f1, sstable.WriterOptions{})
-		require.NoError(t, w.Set([]byte("d"), nil))
-		require.NoError(t, w.Close())
-
-		inj := errorfs.OnIndex(-1)
-		d, err := Open("", &Options{
-			FS:     errorfs.Wrap(mem, inj),
-			Logger: panicLogger{},
-		})
-		require.NoError(t, err)
-		// Force the creation of an L0 sstable that overlaps with the tables
-		// we'll attempt to ingest. This ensures that we exercise filesystem
-		// codepaths when determining the ingest target level.
-		require.NoError(t, d.Set([]byte("a"), nil, nil))
-		require.NoError(t, d.Set([]byte("d"), nil, nil))
-		require.NoError(t, d.Flush())
-
-		t.Run(fmt.Sprintf("index-%d", i), func(t *testing.T) {
-			defer func() {
-				if r := recover(); r != nil {
-					if e, ok := r.(error); ok && errors.Is(e, errorfs.ErrInjected) {
-						return
-					}
-					// d.opts.Logger.Fatalf won't propagate ErrInjected
-					// itself, but should contain the error message.
-					if strings.HasSuffix(fmt.Sprint(r), errorfs.ErrInjected.Error()) {
-						return
-					}
-					t.Fatal(r)
-				}
-			}()
-
-			inj.SetIndex(i)
-			err1 := d.Ingest([]string{"ext0"})
-			err2 := d.Ingest([]string{"ext1"})
-			err := firstError(err1, err2)
-			if err != nil && !errors.Is(err, errorfs.ErrInjected) {
-				t.Fatal(err)
-			}
-		})
-
-		// d.Close may error if we failed to flush the manifest.
-		_ = d.Close()
-
-		// If the injector's index is non-negative, the i-th filesystem
-		// operation was never executed.
-		if inj.Index() >= 0 {
-			break
-		}
-	}
-}
-
 func TestIngestCompact(t *testing.T) {
 	var buf syncedBuffer
 	mem := vfs.NewMem()
diff --git a/internal/metamorphic/meta_test.go b/internal/metamorphic/meta_test.go
index 2b2d64f0..3ea0c28a 100644
--- a/internal/metamorphic/meta_test.go
+++ b/internal/metamorphic/meta_test.go
@@ -19,7 +19,6 @@ import (
 
 	"github.com/cockroachdb/pebble"
 	"github.com/cockroachdb/pebble/internal/base"
-	"github.com/cockroachdb/pebble/internal/errorfs"
 	"github.com/cockroachdb/pebble/internal/randvar"
 	"github.com/cockroachdb/pebble/vfs"
 	"github.com/pmezard/go-difflib/difflib"
@@ -42,13 +41,8 @@ var (
 		"the directory storing test state")
 	disk = flag.Bool("disk", false,
 		"whether to use an in-mem DB or on-disk (in-mem is significantly faster)")
-	// TODO: default error rate to a non-zero value
-	errorRate = flag.Float64("error-rate", 0.0,
-		"rate of errors injected into filesystem operations (0 ≤ r < 1)")
 	failRE = flag.String("fail", "",
 		"fail the test if the supplied regular expression matches the output")
-	traceFile = flag.String("trace-file", "",
-		"write an execution trace to `<run-dir>/file`")
 	keep = flag.Bool("keep", false,
 		"keep the DB directory even on successful runs")
 	ops    = randvar.NewFlag("uniform:5000-10000")
@@ -95,10 +89,6 @@ func testMetaRun(t *testing.T, runDir string) {
 			opts.FS = vfs.NewMem()
 		}
 	}
-	// Wrap the filesystem with one that will inject errors into read
-	// operations with *errorRate probability.
-	opts.FS = errorfs.Wrap(opts.FS, errorfs.WithProbability(errorfs.OpRead, *errorRate))
-
 	if opts.WALDir != "" {
 		opts.WALDir = opts.FS.PathJoin(runDir, opts.WALDir)
 	}
@@ -186,17 +176,11 @@ func TestMeta(t *testing.T) {
 		optionsStr := optionsToString(opts)
 		require.NoError(t, ioutil.WriteFile(optionsPath, []byte(optionsStr), 0644))
 
-		args := []string{
-			"-disk=" + fmt.Sprint(*disk),
-			"-keep=" + fmt.Sprint(*keep),
-			"-run-dir=" + runDir,
-			"-test.run=" + rootName + "$",
-		}
-		if *traceFile != "" {
-			args = append(args, "-test.trace="+filepath.Join(runDir, *traceFile))
-		}
-
-		cmd := exec.Command(os.Args[0], args...)
+		cmd := exec.Command(os.Args[0],
+			"-disk="+fmt.Sprint(*disk),
+			"-keep="+fmt.Sprint(*keep),
+			"-run-dir="+runDir,
+			"-test.run="+rootName+"$")
 		out, err := cmd.CombinedOutput()
 		if err != nil {
 			t.Fatalf(`
diff --git a/internal/metamorphic/test.go b/internal/metamorphic/test.go
index 26d2769b..4adfcb99 100644
--- a/internal/metamorphic/test.go
+++ b/internal/metamorphic/test.go
@@ -10,9 +10,7 @@ import (
 	"os"
 	"strings"
 
-	"github.com/cockroachdb/errors"
 	"github.com/cockroachdb/pebble"
-	"github.com/cockroachdb/pebble/internal/errorfs"
 	"github.com/cockroachdb/pebble/vfs"
 )
 
@@ -31,7 +29,7 @@ type test struct {
 	// The slots for the batches, iterators, and snapshots. These are read and
 	// written by the ops to pass state from one op to another.
 	batches   []*pebble.Batch
-	iters     []*retryableIter
+	iters     []*pebble.Iterator
 	snapshots []*pebble.Snapshot
 }
 
@@ -51,13 +49,7 @@ func (t *test) init(h *history, dir string, testOpts *testOptions) error {
 	t.opts = testOpts.opts.EnsureDefaults()
 	t.opts.Logger = h.Logger()
 	t.opts.EventListener = pebble.MakeLoggingEventListener(t.opts.Logger)
-	t.opts.DebugCheck = func(db *pebble.DB) error {
-		// Wrap the ordinary DebugCheckLevels with retrying
-		// of injected errors.
-		return withRetries(func() error {
-			return pebble.DebugCheckLevels(db)
-		})
-	}
+	t.opts.DebugCheck = pebble.DebugCheckLevels
 
 	defer t.opts.Cache.Unref()
 
@@ -67,7 +59,7 @@ func (t *test) init(h *history, dir string, testOpts *testOptions) error {
 	// difference between in-memory and on-disk which causes different code paths
 	// and timings to be exercised.
 	maybeExit := func(err error) {
-		if err == nil || errors.Is(err, errorfs.ErrInjected) {
+		if err == nil {
 			return
 		}
 		t.maybeSaveData()
@@ -114,12 +106,7 @@ func (t *test) init(h *history, dir string, testOpts *testOptions) error {
 		maybeExit(info.Err)
 	}
 
-	var db *pebble.DB
-	var err error
-	err = withRetries(func() error {
-		db, err = pebble.Open(dir, t.opts)
-		return err
-	})
+	db, err := pebble.Open(dir, t.opts)
 	if err != nil {
 		return err
 	}
@@ -166,29 +153,26 @@ func (t *test) restartDB() error {
 		return nil
 	}
 	t.opts.Cache.Ref()
-	fs := vfs.Root(t.opts.FS).(*vfs.MemFS)
+	fs := t.opts.FS.(*vfs.MemFS)
 	fs.SetIgnoreSyncs(true)
 	if err := t.db.Close(); err != nil {
 		return err
 	}
 	fs.ResetToSyncedState()
 	fs.SetIgnoreSyncs(false)
-	err := withRetries(func() (err error) {
-		t.db, err = pebble.Open(t.dir, t.opts)
-		return err
-	})
+	var err error
+	t.db, err = pebble.Open(t.dir, t.opts)
 	t.opts.Cache.Unref()
 	return err
 }
 
 // If an in-memory FS is being used, save the contents to disk.
 func (t *test) maybeSaveData() {
-	rootFS := vfs.Root(t.opts.FS)
-	if rootFS == vfs.Default {
+	if t.opts.FS == vfs.Default {
 		return
 	}
 	_ = os.RemoveAll(t.dir)
-	if _, err := vfs.Clone(rootFS, vfs.Default, t.dir, t.dir); err != nil {
+	if _, err := vfs.Clone(t.opts.FS, vfs.Default, t.dir, t.dir); err != nil {
 		t.opts.Logger.Infof("unable to clone: %s: %v", t.dir, err)
 	}
 }
@@ -213,7 +197,7 @@ func (t *test) setIter(id objID, i *pebble.Iterator) {
 	if id.tag() != iterTag {
 		panic(fmt.Sprintf("invalid iter ID: %s", id))
 	}
-	t.iters[id.slot()] = &retryableIter{iter: i, lastKey: nil}
+	t.iters[id.slot()] = i
 }
 
 func (t *test) setSnapshot(id objID, s *pebble.Snapshot) {
@@ -257,7 +241,7 @@ func (t *test) getCloser(id objID) io.Closer {
 	panic(fmt.Sprintf("cannot close ID: %s", id))
 }
 
-func (t *test) getIter(id objID) *retryableIter {
+func (t *test) getIter(id objID) *pebble.Iterator {
 	if id.tag() != iterTag {
 		panic(fmt.Sprintf("invalid iter ID: %s", id))
 	}
diff --git a/sstable/reader_test.go b/sstable/reader_test.go
index 1597c4e6..e0b51866 100644
--- a/sstable/reader_test.go
+++ b/sstable/reader_test.go
@@ -198,12 +198,6 @@ func TestInjectedErrors(t *testing.T) {
 				return firstError(err, f.Close())
 			}
 			defer func() { reterr = firstError(reterr, r.Close()) }()
-
-			_, err = r.EstimateDiskUsage([]byte("borrower"), []byte("lender"))
-			if err != nil {
-				return err
-			}
-
 			iter, err := r.NewIter(nil, nil)
 			if err != nil {
 				return err