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

pebble: add global sync to MemFS #3110

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 62 additions & 12 deletions vfs/mem_fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,46 @@ func (y *MemFS) String() string {
return s.String()
}

// Sync makes the entire filesystem persistent. This is equivalent to calling
// Sync() on every file and directory.
//
// This method can be used in conjunction with others to emulate a restart after
// a process crash, as follows:
//
// fs.Sync() // capture/snapshot the filesystem
// fs.SetIgnoreSyncs(true) // prevent changes from taking effect
// db.Close() // shutdown, with no writes taking effect
// fs.ResetToSyncedState() // rollback to the snapshot
// strictFS.SetIgnoreSyncs(false) // allow changes again
// db = Open(..., &Options{FS: fs}) // reopen on top of the reverted data
//
// Without the fs.Sync() step above, the sequence emulates a power outage / OS
// crash: everything that was not synced will be lost. With the fs.Sync(), only
// data that was not flushed to the FS will be lost, but things that were
// flushed and not synced will survive the process restart.
//
// For non-strict MemFS, Sync() is effectively a no-op because the latter anyway
// behaves as if every operation is synced immediately.
//
// On the contrary, a strict MemFS does not sync at all by default. A
// coarse-grained "global" Sync() allows bridging the gap between the two
// behaviours. If a more fine-grained behaviour is needed, a test should
// selectively sync individual files/directories.
func (y *MemFS) Sync() {
if !y.strict {
return
}
y.mu.Lock()
defer y.mu.Unlock()
if !y.ignoreSyncs {
y.root.traverse(func(n *memNode) { n.sync() })
// TODO(pavelkalinnikov): add an option of setting ignoreSyncs=true here, to
// make Sync()+SetIgnoreSyncs(true) atomic. Alternatively, add this option
// to SetIgnoreSyncs, or make a more general "batch" API for making multiple
// operations atomically.
}
}

// SetIgnoreSyncs sets the MemFS.ignoreSyncs field. See the usage comment with NewStrictMem() for
// details.
func (y *MemFS) SetIgnoreSyncs(ignoreSyncs bool) {
Expand Down Expand Up @@ -611,6 +651,26 @@ func (f *memNode) Sys() interface{} {
return nil
}

func (f *memNode) traverse(visit func(*memNode)) {
visit(f)
for _, node := range f.children {
node.traverse(visit)
}
}

func (f *memNode) sync() {
if f.isDir {
f.syncedChildren = make(map[string]*memNode)
for k, v := range f.children {
f.syncedChildren[k] = v
}
} else {
f.mu.Lock()
f.mu.syncedData = slices.Clone(f.mu.data)
f.mu.Unlock()
}
}

func (f *memNode) dump(w *bytes.Buffer, level int) {
if f.isDir {
w.WriteString(" ")
Expand Down Expand Up @@ -778,18 +838,8 @@ func (f *memFile) Sync() error {
}
f.fs.mu.Lock()
defer f.fs.mu.Unlock()
if f.fs.ignoreSyncs {
return nil
}
if f.n.isDir {
f.n.syncedChildren = make(map[string]*memNode)
for k, v := range f.n.children {
f.n.syncedChildren[k] = v
}
} else {
f.n.mu.Lock()
f.n.mu.syncedData = slices.Clone(f.n.mu.data)
f.n.mu.Unlock()
if !f.fs.ignoreSyncs {
f.n.sync()
}
return nil
}
Expand Down
20 changes: 20 additions & 0 deletions vfs/mem_fs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ func runTestCases(t *testing.T, testCases []string, fs *MemFS) {
err = fs.Rename(s[1], s[2])
case "reuseForWrite":
g, err = fs.ReuseForWrite(s[1], s[2])
case "sync":
fs.Sync()
case "resetToSynced":
fs.ResetToSyncedState()
case "ignoreSyncs":
Expand Down Expand Up @@ -392,6 +394,24 @@ func TestStrictFS(t *testing.T) {
"7j: f.read 1 = a",
"7k: f.read 1 fails",
"7l: f.close",

// Global sync.
"8a: mkdirall /aba/c/aba",
"8b: create /aba/c/aba/x",
"8c: mkdirall /aba/d",
"8d: create /aba/d/y",
"8e: create /aba/d/z",
"8f: sync",
"8g: ignoreSyncs",
"8h: create /aba/c/aba/gone",
"8i: resetToSynced",
"8j: stopIgnoringSyncs",
"8k: openDir /aba/c/aba",
"8l: open /aba/c/aba/x",
"8m: open /aba/c/aba/gone fails",
"8n: open /aba/d/y",
"8o: open /aba/d/z",
"8p: open /aba/d/none fails",
}
runTestCases(t, testCases, fs)
}
Expand Down