Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vfs: make Unwrap part of FS, fix metamorphic restart op #3887

Merged
merged 1 commit into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions metamorphic/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,15 +267,28 @@ func (t *Test) minFMV() pebble.FormatMajorVersion {

func (t *Test) restartDB(dbID objID) error {
db := t.getDB(dbID)
// If strictFS is not used, we use pebble.NoSync for writeOpts, so we can't
// restart the database (even if we don't revert to synced data).
if !t.testOpts.strictFS {
return nil
}
t.opts.Cache.Ref()
// The fs isn't necessarily a MemFS.
fs, ok := vfs.Root(t.opts.FS).(*vfs.MemFS)
if ok {
fs.SetIgnoreSyncs(true)
if t.testOpts.sharedStorageEnabled {
// We simulate a crash by essentially ignoring writes to disk after a
// certain point. However, we cannot prevent the process (which didn't
// actually crash) from deleting an external object before we call Close().
// TODO(radu): perhaps we want all syncs to fail after the "crash" point?
return nil
}
// We can't do this if we have more than one database since they share the
// same FS (and we only close/reopen one of them).
// TODO(radu): have each database use its own MemFS.
if len(t.dbs) > 1 {
return nil
}
t.opts.Cache.Ref()

memFS := vfs.Root(t.opts.FS).(*vfs.MemFS)
memFS.SetIgnoreSyncs(true)
if err := db.Close(); err != nil {
return err
}
Expand All @@ -287,10 +300,9 @@ func (t *Test) restartDB(dbID objID) error {
return err
}
}
if ok {
fs.ResetToSyncedState()
fs.SetIgnoreSyncs(false)
}

memFS.ResetToSyncedState()
memFS.SetIgnoreSyncs(false)

// TODO(jackson): Audit errorRate and ensure custom options' hooks semantics
// are well defined within the context of retries.
Expand Down
5 changes: 5 additions & 0 deletions vfs/disk_health.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,11 @@ func WithDiskHealthChecks(
return fs, fs
}

// Unwrap is part of the FS interface.
func (d *diskHealthCheckingFS) Unwrap() FS {
return d.fs
}

// timeFilesystemOp executes the provided closure, which should perform a
// singular filesystem operation of a type matching opType on the named file. It
// records the provided start time such that the long-lived disk-health checking
Expand Down
2 changes: 2 additions & 0 deletions vfs/disk_health_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ func (m mockFS) GetDiskUsage(path string) (DiskUsage, error) {
return m.getDiskUsage(path)
}

func (m mockFS) Unwrap() FS { return nil }

var _ FS = &mockFS{}

func TestDiskHealthChecking_WriteStatsCollector(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions vfs/mem_fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,9 @@ func (*MemFS) GetDiskUsage(string) (DiskUsage, error) {
return DiskUsage{}, ErrUnsupported
}

// Unwrap implements FS.Unwrap.
func (*MemFS) Unwrap() FS { return nil }

// memNode holds a file's data or a directory's children.
type memNode struct {
isDir bool
Expand Down Expand Up @@ -721,6 +724,9 @@ func (f *memFile) Write(p []byte) (int, error) {
panic("stuff")
}
} else {
if grow := f.pos - len(f.n.mu.data); grow > 0 {
f.n.mu.data = append(f.n.mu.data, make([]byte, grow)...)
}
f.n.mu.data = append(f.n.mu.data[:f.pos], p...)
}
f.pos += len(p)
Expand Down
22 changes: 13 additions & 9 deletions vfs/vfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ type FS interface {
// GetDiskUsage returns disk space statistics for the filesystem where
// path is any file or directory within that filesystem.
GetDiskUsage(path string) (DiskUsage, error)

// Unwrap is implemented by "wrapping" filesystems (those that add some
// functionality on top of an underlying FS); it returns the wrapped FS.
// It is used by vfs.Root.
//
// Returns nil if this is not a wrapping filesystem.
Unwrap() FS
}

// A DeviceID uniquely identifies a block device on which filesystem data is
Expand Down Expand Up @@ -344,6 +351,8 @@ func (defaultFS) PathDir(path string) string {
return filepath.Dir(path)
}

func (defaultFS) Unwrap() FS { return nil }

type randomReadsOption struct{}

// RandomReadsOption is an OpenOption that optimizes opened file handle for
Expand Down Expand Up @@ -444,18 +453,13 @@ func LinkOrCopy(fs FS, oldname, newname string) error {
// 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
n := fs.Unwrap()
if n == nil {
return fs
}
fs = u.Unwrap()
fs = n
}
return fs
}

// ErrUnsupported may be returned a FS when it does not support an operation.
Expand Down
2 changes: 2 additions & 0 deletions vfs/vfstest/open_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ func (fs *openFilesFS) GetDiskUsage(path string) (vfs.DiskUsage, error) {
return fs.inner.GetDiskUsage(path)
}

func (fs *openFilesFS) Unwrap() vfs.FS { return fs.inner }

func (fs *openFilesFS) wrapOpenFile(f vfs.File, err error) (vfs.File, error) {
if f == nil || err != nil {
return f, err
Expand Down
Loading