-
Notifications
You must be signed in to change notification settings - Fork 337
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce the new struct inodeParents that wraps a map and one special slot for the most recent parent. Unit tests included. Because the map is lazily initialized, we should save some memory on the common single-parent case (= file with no hard links) compared to before. Benchmarking with gocryptfs shows no discernible change in performance. fsstress testing with gocryptfs shows no issues. TestStaleHardlinks the previous commit passes now. Change-Id: I8d69093abc906addde751a9e70dbd78a3a61371a
- Loading branch information
Showing
3 changed files
with
180 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// Copyright 2021 the Go-FUSE 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 fs | ||
|
||
// inodeParents stores zero or more parents of an Inode, | ||
// remembering which one is the most recent. | ||
// | ||
// No internal locking: the caller is responsible for preventing | ||
// concurrent access. | ||
type inodeParents struct { | ||
// newest is the most-recently add()'ed parent. | ||
// nil when we don't have any parents. | ||
newest *parentData | ||
// other are parents in addition to the newest. | ||
// nil or empty when we have <= 1 parents. | ||
other map[parentData]struct{} | ||
} | ||
|
||
// add adds a parent to the store. | ||
func (p *inodeParents) add(n parentData) { | ||
// one and only parent | ||
if p.newest == nil { | ||
p.newest = &n | ||
} | ||
// already known as `newest` | ||
if *p.newest == n { | ||
return | ||
} | ||
// old `newest` gets displaced into `other` | ||
if p.other == nil { | ||
p.other = make(map[parentData]struct{}) | ||
} | ||
p.other[*p.newest] = struct{}{} | ||
// new parent becomes `newest` (possibly moving up from `other`) | ||
delete(p.other, n) | ||
p.newest = &n | ||
} | ||
|
||
// get returns the most recent parent | ||
// or nil if there is no parent at all. | ||
func (p *inodeParents) get() *parentData { | ||
return p.newest | ||
} | ||
|
||
// all returns all known parents | ||
// or nil if there is no parent at all. | ||
func (p *inodeParents) all() []parentData { | ||
count := p.count() | ||
if count == 0 { | ||
return nil | ||
} | ||
out := make([]parentData, 0, count) | ||
out = append(out, *p.newest) | ||
for i := range p.other { | ||
out = append(out, i) | ||
} | ||
return out | ||
} | ||
|
||
func (p *inodeParents) delete(n parentData) { | ||
// We have zero parents, so we can't delete any. | ||
if p.newest == nil { | ||
return | ||
} | ||
// If it's not the `newest` it must be in `other` (or nowhere). | ||
if *p.newest != n { | ||
delete(p.other, n) | ||
return | ||
} | ||
// We want to delete `newest`, but there is no other to replace it. | ||
if len(p.other) == 0 { | ||
p.newest = nil | ||
return | ||
} | ||
// Move random entry from `other` over `newest`. | ||
var i parentData | ||
for i = range p.other { | ||
p.newest = &i | ||
break | ||
} | ||
delete(p.other, i) | ||
} | ||
|
||
func (p *inodeParents) clear() { | ||
p.newest = nil | ||
p.other = nil | ||
} | ||
|
||
func (p *inodeParents) count() int { | ||
if p.newest == nil { | ||
return 0 | ||
} | ||
return 1 + len(p.other) | ||
} | ||
|
||
type parentData struct { | ||
name string | ||
parent *Inode | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package fs | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestInodeParents(t *testing.T) { | ||
var p inodeParents | ||
var ino1, ino2, ino3 Inode | ||
|
||
// empty store should be empty without panicing | ||
if count := p.count(); count != 0 { | ||
t.Error(count) | ||
} | ||
if p.all() != nil { | ||
t.Error("empty store should return nil but did not") | ||
} | ||
|
||
// non-dupes should be stored | ||
all := []parentData{ | ||
parentData{"foo", &ino1}, | ||
parentData{"foo2", &ino1}, | ||
parentData{"foo3", &ino1}, | ||
parentData{"foo", &ino2}, | ||
parentData{"foo", &ino3}, | ||
} | ||
for i, v := range all { | ||
p.add(v) | ||
if count := p.count(); count != i+1 { | ||
t.Errorf("want=%d have=%d", i+1, count) | ||
} | ||
last := p.get() | ||
if *last != v { | ||
t.Error("get did not give us last-known parent") | ||
} | ||
} | ||
|
||
// adding dupes should not cause the count to increase, but | ||
// must cause get() to return the most recently added dupe. | ||
for _, v := range all { | ||
p.add(v) | ||
if count := p.count(); count != len(all) { | ||
t.Errorf("want=%d have=%d", len(all), count) | ||
} | ||
last := p.get() | ||
if *last != v { | ||
t.Error("get did not give us last-known parent") | ||
} | ||
} | ||
|
||
all2 := p.all() | ||
if len(all) != len(all2) { | ||
t.Errorf("want=%d have=%d", len(all), len(all2)) | ||
} | ||
} |