fs/torrentfs.go | 17 ++++++++++++++--- fs/torrentfs_test.go | 47 ++++++++++++++++++++++++++++++++++------------- diff --git a/fs/torrentfs.go b/fs/torrentfs.go index 6fd721b71a4fcefabb45f5870a7e1608cd71a2d4..fd9cb0599a97cb2897bb84a969e11fef02590caa 100644 --- a/fs/torrentfs.go +++ b/fs/torrentfs.go @@ -25,9 +25,11 @@ interruptedReads = expvar.NewInt("interruptedReads") ) type TorrentFS struct { - Client *torrent.Client - destroyed chan struct{} - mu sync.Mutex + Client *torrent.Client + destroyed chan struct{} + mu sync.Mutex + blockedReads int + event sync.Cond } var ( @@ -73,6 +75,10 @@ return "/" + strings.Join(append([]string{n.metadata.Name}, n.path...), "/") } func blockingRead(fs *TorrentFS, t torrent.Torrent, off int64, p []byte, intr fusefs.Intr) (n int, err fuse.Error) { + fs.mu.Lock() + fs.blockedReads++ + fs.event.Broadcast() + fs.mu.Unlock() var ( _n int _err fuse.Error @@ -91,6 +97,10 @@ err = fuse.EIO case <-intr: err = fuse.EINTR } + fs.mu.Lock() + fs.blockedReads-- + fs.event.Broadcast() + fs.mu.Unlock() return } @@ -296,5 +306,6 @@ fs := &TorrentFS{ Client: cl, destroyed: make(chan struct{}), } + fs.event.L = &fs.mu return fs } diff --git a/fs/torrentfs_test.go b/fs/torrentfs_test.go index 2f9c79fb87d0ddd6481403ae340f55b41ddd8bb6..52591aedd1ea396a860d5c7972cae7cadd8cfde3 100644 --- a/fs/torrentfs_test.go +++ b/fs/torrentfs_test.go @@ -77,6 +77,8 @@ log.Printf("%x", tl.Metainfo.Info.Pieces) return } +// Unmount without first killing the FUSE connection while there are FUSE +// operations blocked inside the filesystem code. func TestUnmountWedged(t *testing.T) { layout, err := newGreetingLayout() if err != nil { @@ -96,7 +98,7 @@ NoDefaultBlocklist: true, }) defer client.Stop() - log.Printf("%+v", *layout.Metainfo) + t.Logf("%+v", *layout.Metainfo) client.AddTorrent(layout.Metainfo) fs := New(client) fuseConn, err := fuse.Mount(layout.MountDir) @@ -106,32 +108,50 @@ t.Skip(err) } t.Fatal(err) } + <-fuseConn.Ready + if err := fuseConn.MountError; err != nil { + t.Fatal(err) + } go func() { server := fusefs.Server{ FS: fs, Debug: func(msg interface{}) { - log.Print(msg) + t.Log(msg) }, } server.Serve(fuseConn) }() - <-fuseConn.Ready - if err := fuseConn.MountError; err != nil { - log.Fatal(err) - } + // Read the greeting file, though it will never be available. This should + // "wedge" FUSE, requiring the fs object to be forcibly destroyed. The + // read call will return with a FS error. go func() { - ioutil.ReadFile(filepath.Join(layout.MountDir, layout.Metainfo.Info.Name)) + _, err := ioutil.ReadFile(filepath.Join(layout.MountDir, layout.Metainfo.Info.Name)) + if err == nil { + t.Fatal("expected error reading greeting") + } }() - // time.Sleep(time.Second) + + // Wait until the read has blocked inside the filesystem code. + fs.mu.Lock() + for fs.blockedReads != 1 { + fs.event.Wait() + } + fs.mu.Unlock() + fs.Destroy() - // time.Sleep(time.Second) - err = fuse.Unmount(layout.MountDir) - if err != nil { - log.Print(err) + + for { + err = fuse.Unmount(layout.MountDir) + if err != nil { + t.Logf("error unmounting: %s", err) + } else { + break + } } + err = fuseConn.Close() if err != nil { - t.Log(err) + t.Fatalf("error closing fuse conn: %s", err) } } @@ -140,6 +160,7 @@ layout, err := newGreetingLayout() if err != nil { t.Fatal(err) } + defer layout.Destroy() seeder, err := torrent.NewClient(&torrent.Config{ DataDir: layout.Completed, DisableTrackers: true,