mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
This is very similar to p9 in design and helps address all the discussed security concerns around the old lisafs design. In p9, the path tree grows unbounded. Once a path is walked, those path nodes exist for the lifetime of the server. That hurts memory performance and usage. Additionally, nodes are allocated separately from FidRefs which are allocated separately from Files. Each path node also has 3 hashmaps tracking various things. This leads to a LOT of allocations. In lisafs, we cut down almost all the additional allocations. Nodes have a bounded lifetime. Once all refs on node are dropped, the node is removed from the filesystem tree. In the overwhelming common case, where the client is not compromised and is behaving correctly (gofer client in sentry), the lifecycle of the ControlFD and Node are equivalent. So lisafs allocates them together. lisafs also only uses 1 hashmap in each node to track children. This too has been optimized. Experimentation showed that majority of directories have at most 3 children to track. So we only allocate the hashmap once we hit 4 children. Before that, we statically track 3 children using hardcoded pointers. Tested: All 221 syscall tests pass with lisafs. PiperOrigin-RevId: 424933787
87 lines
2.4 KiB
Go
87 lines
2.4 KiB
Go
// Copyright 2022 The gVisor 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.
|
|
|
|
package lisafs
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
func TestLookup(t *testing.T) {
|
|
for _, numChildren := range []int{
|
|
numStaticChildren, // For static children.
|
|
2 * numStaticChildren, // For dynamic children.
|
|
} {
|
|
t.Run(fmt.Sprintf("%dChildren", numChildren), func(t *testing.T) {
|
|
var root Node
|
|
root.InitLocked("", nil)
|
|
root.childrenMu.Lock()
|
|
defer root.childrenMu.Unlock()
|
|
|
|
// truth is the source of truth.
|
|
truth := make(map[string]*Node)
|
|
|
|
// Fill node with children.
|
|
for i := 0; i < numChildren; i++ {
|
|
name := fmt.Sprintf("%d", i)
|
|
var child Node
|
|
child.InitLocked(name, &root)
|
|
truth[name] = &child
|
|
}
|
|
|
|
// Test that lookup finds child correctly.
|
|
for i := 0; i < numChildren; i++ {
|
|
name := fmt.Sprintf("%d", i)
|
|
if got, want := root.LookupChildLocked(name), truth[name]; got != want {
|
|
t.Errorf("incorrect child returned by root: want %p, got %p", want, got)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
for _, numChildren := range []int{
|
|
numStaticChildren, // For static children.
|
|
2 * numStaticChildren, // For dynamic children.
|
|
} {
|
|
t.Run(fmt.Sprintf("%dChildren", numChildren), func(t *testing.T) {
|
|
var root Node
|
|
root.InitLocked("", nil)
|
|
root.childrenMu.Lock()
|
|
defer root.childrenMu.Unlock()
|
|
|
|
// truth is the source of truth.
|
|
truth := make(map[string]*Node)
|
|
|
|
// Fill node with children.
|
|
for i := 0; i < numChildren; i++ {
|
|
name := fmt.Sprintf("%d", i)
|
|
var child Node
|
|
child.InitLocked(name, &root)
|
|
truth[name] = &child
|
|
}
|
|
|
|
// Now remove them and check if correct node is removed.
|
|
for i := 0; i < numChildren; i++ {
|
|
name := fmt.Sprintf("%d", i)
|
|
if got, want := root.removeChildLocked(name), truth[name]; got != want {
|
|
t.Errorf("root deleted incorrect ndoe: want %p, got %p", want, got)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|