Commit 36b35911 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

fuse: fix deadlock with parallel mounts

Commit 1aa7b7b2 ("fs: document known deadlocks") describes a deadlock
scenario where inheriting file descriptors triggers a spurious close
during subprocess setup, leading to deadlock.

This exact scenario actually happens when setting up the FUSE mount
using fusermount: the fusermount process inherits one half of a socket
pair, which is used to pass back the opened /dev/fuse file.  After the
mount is successful, we open a file in the FUSE mount for the poll
hack. This means that in parallel scenarios, we may use fd 3 as the
poll hack, while also calling fusermount with inherited file
descriptors.

Solve this by grabbing fd 3 during initialization.

This is not completely foolproof, as FD 0, 1 and 2 could be closed
(and then reused) after initialization finished, but this should be
uncommon as 0, 1 and 2 are standard input/output/error.

Doing it during the init phase means that we prevent deadlock for all
users that inherit single file descriptors.

Change-Id: If5ac7c941f0ee2e13ca657c31d056a676eed3fde
parent e9e7c22a
...@@ -680,3 +680,44 @@ func TestStaleHardlinks(t *testing.T) { ...@@ -680,3 +680,44 @@ func TestStaleHardlinks(t *testing.T) {
func init() { func init() {
syscall.Umask(0) syscall.Umask(0)
} }
func testMountDir(dir string) error {
opts := &Options{}
opts.Debug = testutil.VerboseTest()
server, err := Mount(dir, &Inode{}, opts)
if err != nil {
return err
}
server.Unmount()
server.Wait()
return nil
}
func TestParallelMount(t *testing.T) {
before := runtime.GOMAXPROCS(1)
defer runtime.GOMAXPROCS(before)
N := 1000
todo := make(chan string, N)
result := make(chan error, N)
for i := 0; i < N; i++ {
todo <- t.TempDir()
}
close(todo)
P := 2
for i := 0; i < P; i++ {
go func() {
for d := range todo {
result <- testMountDir(d)
}
}()
}
for i := 0; i < N; i++ {
e := <-result
if e != nil {
t.Error(e)
}
}
}
...@@ -7,6 +7,29 @@ import ( ...@@ -7,6 +7,29 @@ import (
"syscall" "syscall"
) )
var reservedFDs []*os.File
func init() {
// Both Darwin and Linux invoke a subprocess with one
// inherited file descriptor to create the mount. To protect
// against deadlock, we must ensure that file descriptor 3
// never points to a FUSE filesystem. We do this by simply
// grabbing fd 3 and never releasing it. (This is not
// completely foolproof: a preceding init routine could grab fd 3,
// and then release it later.)
for {
f, err := os.Open(os.DevNull)
if err != nil {
panic(fmt.Sprintf("open(%q): %v", os.DevNull, err))
}
if f.Fd() > 3 {
f.Close()
break
}
reservedFDs = append(reservedFDs, f)
}
}
func getConnection(local *os.File) (int, error) { func getConnection(local *os.File) (int, error) {
conn, err := net.FileConn(local) conn, err := net.FileConn(local)
if err != nil { if err != nil {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment