mirror of
https://github.com/hanzoai/go-fuse.git
synced 2026-10-30 14:46:18 +00:00
144 lines
3.6 KiB
Go
144 lines
3.6 KiB
Go
// Copyright 2019 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 test
|
|
|
|
// verify that several lookup requests can be served in parallel without deadlock.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"testing"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"github.com/hanzoai/go-fuse/v2/fuse"
|
|
"github.com/hanzoai/go-fuse/v2/fuse/nodefs"
|
|
"github.com/hanzoai/go-fuse/v2/internal/testutil"
|
|
)
|
|
|
|
// tRoot implements simple root node which Lookups children in predefined .nodes.
|
|
// The Lookup is synchronized with main test driver on .lookupq and .lookupGo.
|
|
type tRoot struct {
|
|
nodefs.Node
|
|
nodes map[string]nodefs.Node // name -> Node
|
|
|
|
lookupq chan string // main <- fssrv: lookup(name) request received
|
|
lookupGo chan struct{} // main -> fssrv: ok to further process lookup requests
|
|
}
|
|
|
|
func (r *tRoot) Lookup(out *fuse.Attr, name string, fctx *fuse.Context) (*nodefs.Inode, fuse.Status) {
|
|
node, ok := r.nodes[name]
|
|
if !ok {
|
|
// e.g. it can be lookup for .Trash automatically issued by volume manager
|
|
return nil, fuse.ENOENT
|
|
}
|
|
|
|
r.lookupq <- name // tell main driver that we received lookup(name)
|
|
<-r.lookupGo // wait for main to allow us to continue
|
|
|
|
st := node.GetAttr(out, nil, fctx)
|
|
return node.Inode(), st
|
|
}
|
|
|
|
// verifyFileRead verifies that file @path has content == dataOK.
|
|
func verifyFileRead(path string, dataOK string) error {
|
|
v, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if string(v) != dataOK {
|
|
return fmt.Errorf("%s: file read: got %q ; want %q", path, v, dataOK)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestNodeParallelLookup(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
root := &tRoot{
|
|
Node: nodefs.NewDefaultNode(),
|
|
nodes: make(map[string]nodefs.Node),
|
|
lookupq: make(chan string),
|
|
lookupGo: make(chan struct{}),
|
|
}
|
|
|
|
opts := nodefs.NewOptions()
|
|
opts.LookupKnownChildren = true
|
|
opts.Debug = testutil.VerboseTest()
|
|
srv, _, err := nodefs.MountRoot(dir, root, opts)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
root.nodes["hello"] = NewDataNode([]byte("abc"))
|
|
root.nodes["world"] = NewDataNode([]byte("def"))
|
|
root.Inode().NewChild("hello", false, root.nodes["hello"])
|
|
root.Inode().NewChild("world", false, root.nodes["world"])
|
|
|
|
go srv.Serve()
|
|
if err := srv.WaitMount(); err != nil {
|
|
t.Fatal("WaitMount", err)
|
|
}
|
|
defer func() {
|
|
err := srv.Unmount()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}()
|
|
|
|
// the test will deadlock if the client cannot issue several lookups simultaneously
|
|
if srv.KernelSettings().Flags&fuse.CAP_PARALLEL_DIROPS == 0 {
|
|
t.Skip("Kernel serializes dir lookups")
|
|
}
|
|
|
|
// spawn 2 threads to access the files in parallel
|
|
// this will deadlock if nodefs does not allow simultaneous Lookups to be handled.
|
|
// see https://github.com/hanwen/go-fuse/commit/d0fca860 for context.
|
|
ctx0, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
wg, ctx := errgroup.WithContext(ctx0)
|
|
wg.Go(func() error {
|
|
return verifyFileRead(dir+"/hello", "abc")
|
|
})
|
|
wg.Go(func() error {
|
|
return verifyFileRead(dir+"/world", "def")
|
|
})
|
|
|
|
// wait till both threads queue into Lookup
|
|
expect := map[string]struct{}{ // set of expected lookups
|
|
"hello": {},
|
|
"world": {},
|
|
}
|
|
loop:
|
|
for len(expect) > 0 {
|
|
var lookup string
|
|
select {
|
|
case <-ctx.Done():
|
|
break loop // wg.Wait will return the error
|
|
case lookup = <-root.lookupq:
|
|
// ok
|
|
}
|
|
|
|
if testutil.VerboseTest() {
|
|
log.Printf("I: <- lookup %q", lookup)
|
|
}
|
|
_, ok := expect[lookup]
|
|
if !ok {
|
|
t.Fatalf("unexpected lookup: %q ; expect: %q", lookup, expect)
|
|
}
|
|
delete(expect, lookup)
|
|
}
|
|
|
|
// let both lookups continue
|
|
close(root.lookupGo)
|
|
|
|
err = wg.Wait()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|