Skip to content

Commit c8d2a3d

Browse files
committed
mpt: Export PersistedVersion
This resolves #83.
1 parent f5aef32 commit c8d2a3d

5 files changed

Lines changed: 185 additions & 5 deletions

File tree

mpt/disk.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,12 @@ type diskTree struct {
9393
pmem *pmem.Mem
9494
mem []byte // cache of pmem.Data()
9595

96-
file1 File
97-
file2 File
98-
leaf File
99-
closed bool
100-
err error // sticky error
96+
file1 File
97+
file2 File
98+
leaf File
99+
persistedVersion int64
100+
closed bool
101+
err error // sticky error
101102
}
102103

103104
// broken marks the tree broken with err as the reason.
@@ -211,6 +212,7 @@ func memOpen(file1, file2, disk File, op string) (_ Tree, err error) {
211212
}
212213

213214
t.mem = t.pmem.Data()
215+
t.persistedVersion = t.hdr().version()
214216

215217
return t, nil
216218
}
@@ -233,6 +235,7 @@ func (t *diskTree) Sync() error {
233235
if err := t.pmem.Sync(); err != nil {
234236
return t.broken(err)
235237
}
238+
t.persistedVersion = t.hdr().version()
236239
return nil
237240
}
238241

mpt/disk_test.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ func (tt *tester) reopen(minVer int64, minExact bool, format string, args ...any
171171
if version < minVer || minExact != exact {
172172
tt.t.Fatalf("reopen: %s: version = %d,%v, want ≥ %d,%v", kind, version, exact, minVer, minExact)
173173
}
174+
if tree.PersistedVersion() != version {
175+
tt.t.Fatalf("reopen: %s: PersistedVersion = %d, want %d", kind, tree.PersistedVersion(), version)
176+
}
174177
if !exact {
175178
f1.readOnly = false
176179
f2.readOnly = false
@@ -437,3 +440,158 @@ func TestSetOverwriteDiskSize(t *testing.T) {
437440
}
438441
})
439442
}
443+
444+
func TestPersistedVersion(t *testing.T) {
445+
type step struct {
446+
action string // "check", "set", "snap", "sync", "reopen"
447+
key string
448+
val string
449+
version int64
450+
wantVersion int64
451+
wantPersisted int64 // expected PersistedVersion on diskTree (on memTree, equals wantVersion)
452+
}
453+
454+
tests := []struct {
455+
name string
456+
diskOnly bool
457+
steps []step
458+
}{
459+
{
460+
name: "initial_empty",
461+
steps: []step{
462+
{action: "check", wantVersion: 0, wantPersisted: 0},
463+
},
464+
},
465+
{
466+
name: "snap_without_sync",
467+
steps: []step{
468+
{action: "set", key: "k1", val: "v1", wantVersion: 0, wantPersisted: 0},
469+
{action: "snap", version: 10, wantVersion: 10, wantPersisted: 0},
470+
},
471+
},
472+
{
473+
name: "snap_then_sync",
474+
steps: []step{
475+
{action: "set", key: "k1", val: "v1", wantVersion: 0, wantPersisted: 0},
476+
{action: "snap", version: 10, wantVersion: 10, wantPersisted: 0},
477+
{action: "sync", wantVersion: 10, wantPersisted: 10},
478+
},
479+
},
480+
{
481+
name: "unflushed_crash_recovery",
482+
diskOnly: true,
483+
steps: []step{
484+
{action: "set", key: "k1", val: "v1", wantVersion: 0, wantPersisted: 0},
485+
{action: "snap", version: 10, wantVersion: 10, wantPersisted: 0},
486+
{action: "sync", wantVersion: 10, wantPersisted: 10},
487+
{action: "set", key: "k2", val: "v2", wantVersion: 10, wantPersisted: 10},
488+
{action: "snap", version: 20, wantVersion: 20, wantPersisted: 10},
489+
{action: "reopen", wantVersion: 10, wantPersisted: 10},
490+
},
491+
},
492+
{
493+
name: "negative_version_snap",
494+
steps: []step{
495+
{action: "set", key: "k1", val: "v1", wantVersion: 0, wantPersisted: 0},
496+
{action: "snap", version: 10, wantVersion: 10, wantPersisted: 0},
497+
{action: "sync", wantVersion: 10, wantPersisted: 10},
498+
{action: "set", key: "k2", val: "v2", wantVersion: 10, wantPersisted: 10},
499+
{action: "snap", version: -1, wantVersion: 10, wantPersisted: 10},
500+
{action: "sync", wantVersion: 10, wantPersisted: 10},
501+
},
502+
},
503+
{
504+
name: "multiple_sync_cycles",
505+
steps: []step{
506+
{action: "set", key: "k1", val: "v1", wantVersion: 0, wantPersisted: 0},
507+
{action: "snap", version: 10, wantVersion: 10, wantPersisted: 0},
508+
{action: "sync", wantVersion: 10, wantPersisted: 10},
509+
{action: "set", key: "k2", val: "v2", wantVersion: 10, wantPersisted: 10},
510+
{action: "snap", version: 20, wantVersion: 20, wantPersisted: 10},
511+
{action: "sync", wantVersion: 20, wantPersisted: 20},
512+
{action: "set", key: "k3", val: "v3", wantVersion: 20, wantPersisted: 20},
513+
{action: "snap", version: 30, wantVersion: 30, wantPersisted: 20},
514+
{action: "sync", wantVersion: 30, wantPersisted: 30},
515+
},
516+
},
517+
}
518+
519+
t.Run("diskTree", func(t *testing.T) {
520+
for _, tc := range tests {
521+
t.Run(tc.name, func(t *testing.T) {
522+
f1 := new(memFile)
523+
f2 := new(memFile)
524+
f3 := new(memFile)
525+
526+
tree, err := New(f1, f2, f3)
527+
check(t, err)
528+
defer tree.Close()
529+
530+
for i, s := range tc.steps {
531+
switch s.action {
532+
case "set":
533+
check(t, tree.Set(Key(s.key), Val(s.val)))
534+
case "snap":
535+
_, err := tree.Snap(s.version)
536+
check(t, err)
537+
case "sync":
538+
check(t, tree.Sync())
539+
case "reopen":
540+
f1Copy := &memFile{data: slices.Clone(f1.data)}
541+
f2Copy := &memFile{data: slices.Clone(f2.data)}
542+
f3Copy := &memFile{data: slices.Clone(f3.data)}
543+
check(t, tree.Close())
544+
tree, err = New(f1Copy, f2Copy, f3Copy)
545+
check(t, err)
546+
case "check":
547+
// Just assert versions.
548+
default:
549+
t.Fatalf("unknown action %q", s.action)
550+
}
551+
552+
if v, _ := tree.Version(); v != s.wantVersion {
553+
t.Fatalf("step %d (%s): Version() = %d, want %d", i, s.action, v, s.wantVersion)
554+
}
555+
if got := tree.PersistedVersion(); got != s.wantPersisted {
556+
t.Fatalf("step %d (%s): PersistedVersion() = %d, want %d", i, s.action, got, s.wantPersisted)
557+
}
558+
}
559+
})
560+
}
561+
})
562+
563+
t.Run("memTree", func(t *testing.T) {
564+
for _, tc := range tests {
565+
if tc.diskOnly {
566+
continue
567+
}
568+
t.Run(tc.name, func(t *testing.T) {
569+
tree := NewMemTree()
570+
defer tree.Close()
571+
572+
for i, s := range tc.steps {
573+
switch s.action {
574+
case "set":
575+
check(t, tree.Set(Key(s.key), Val(s.val)))
576+
case "snap":
577+
_, err := tree.Snap(s.version)
578+
check(t, err)
579+
case "sync":
580+
check(t, tree.Sync())
581+
case "check":
582+
// Just assert versions.
583+
default:
584+
t.Fatalf("unknown action %q", s.action)
585+
}
586+
587+
if v, _ := tree.Version(); v != s.wantVersion {
588+
t.Fatalf("step %d (%s): Version() = %d, want %d", i, s.action, v, s.wantVersion)
589+
}
590+
if got := tree.PersistedVersion(); got != s.wantVersion {
591+
t.Fatalf("step %d (%s): PersistedVersion() = %d, want %d", i, s.action, got, s.wantVersion)
592+
}
593+
}
594+
})
595+
}
596+
})
597+
}

mpt/dmem.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ func (t *diskTree) Version() (version int64, exact bool) {
126126
return hdr.version(), hdr.exact()
127127
}
128128

129+
// PersistedVersion returns the latest version number that has been
130+
// completely written and synced to disk.
131+
func (t *diskTree) PersistedVersion() int64 {
132+
t.mmu.RLock()
133+
defer t.mmu.RUnlock()
134+
135+
return t.persistedVersion
136+
}
137+
129138
// Set sets the value associated with key to val.
130139
func (t *diskTree) Set(key Key, val Val) error {
131140
t.mmu.RLock()

mpt/mem.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ func (t *memTree) Version() (version int64, exact bool) {
9696
return t.version, t.exact
9797
}
9898

99+
// PersistedVersion returns the latest version number of the tree.
100+
// For an in-memory tree, this is the same as Version.
101+
func (t *memTree) PersistedVersion() int64 {
102+
return t.version
103+
}
104+
99105
// Snap returns a snapshot of t.
100106
func (t *memTree) Snap(version int64) (Snapshot, error) {
101107
if t.err != nil {

mpt/tree.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,10 @@ type Tree interface {
236236
// expected to replay all Set calls up to the next version.
237237
Version() (version int64, exact bool)
238238

239+
// PersistedVersion returns the latest version number that has been
240+
// completely written and synced to disk.
241+
PersistedVersion() int64
242+
239243
// Close calls Sync and then closes the underlying files.
240244
Close() error
241245
}

0 commit comments

Comments
 (0)