From e654a9659cad29e3c206a04f6543292efbc7ea13 Mon Sep 17 00:00:00 2001 From: Matthieu Rakotojaona Date: Sun, 19 Jul 2015 15:14:47 +0200 Subject: [PATCH] Protect the snapshots cache with a RWMutex --- cmd/restic/cmd_mount.go | 71 ++++++++++++++++------- cmd/restic/fuse/snapshot.go | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 21 deletions(-) create mode 100644 cmd/restic/fuse/snapshot.go diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 906f450ed..d26634ad4 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "fmt" "os" + "sync" "time" "golang.org/x/net/context" @@ -79,7 +80,7 @@ func (cmd CmdMount) Execute(args []string) error { root := fs.Tree{} root.Add("snapshots", &snapshots{ repo: repo, - knownSnapshots: make(map[string]*restic.Snapshot), + knownSnapshots: make(map[string]snapshotWithId), }) cmd.global.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint) @@ -96,6 +97,11 @@ func (cmd CmdMount) Execute(args []string) error { return c.MountError } +type snapshotWithId struct { + *restic.Snapshot + backend.ID +} + // These lines statically ensure that a *snapshots implement the given // interfaces; a misplaced refactoring of the implementation that breaks // the interface will be catched by the compiler @@ -105,8 +111,9 @@ var _ = fs.NodeStringLookuper(&snapshots{}) type snapshots struct { repo *repository.Repository - // snapshot timestamp -> snapshot - knownSnapshots map[string]*restic.Snapshot + // knownSnapshots maps snapshot timestamp to the snapshot + sync.RWMutex + knownSnapshots map[string]snapshotWithId } func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error { @@ -115,16 +122,39 @@ func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error { return nil } -func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { - ret := make([]fuse.Dirent, 0) +func (sn *snapshots) updateCache(ctx context.Context) error { + sn.Lock() + defer sn.Unlock() + for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { snapshot, err := restic.LoadSnapshot(sn.repo, id) if err != nil { - return nil, err + return err } - sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = snapshot + sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = snapshotWithId{snapshot, id} + } + return nil +} +func (sn *snapshots) get(name string) (snapshot snapshotWithId, ok bool) { + sn.Lock() + snapshot, ok = sn.knownSnapshots[name] + sn.Unlock() + return snapshot, ok +} + +func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { + err := sn.updateCache(ctx) + if err != nil { + return nil, err + } + + sn.RLock() + defer sn.RUnlock() + + ret := make([]fuse.Dirent, 0) + for _, snapshot := range sn.knownSnapshots { ret = append(ret, fuse.Dirent{ - Inode: binary.BigEndian.Uint64(id[:8]), + Inode: binary.BigEndian.Uint64(snapshot.ID[:8]), Type: fuse.DT_Dir, Name: snapshot.Time.Format(time.RFC3339), }) @@ -134,21 +164,21 @@ func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { } func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { - if _, ok := sn.knownSnapshots[name]; !ok { - // At least this snapshot is not cached. We use this opportunity to - // load all missing snapshots - for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { - snapshot, err := restic.LoadSnapshot(sn.repo, id) - if err != nil { - return nil, err - } - snapshotName := snapshot.Time.Format(time.RFC3339) - sn.knownSnapshots[snapshotName] = snapshot - break + snapshot, ok := sn.get(name) + + if !ok { + // We don't know about it, update the cache + err := sn.updateCache(ctx) + if err != nil { + return nil, err + } + snapshot, ok = sn.get(name) + if !ok { + // We still don't know about it, this time it really doesn't exist + return nil, fuse.ENOENT } } - snapshot := sn.knownSnapshots[name] tree, err := restic.LoadTree(sn.repo, snapshot.Tree) if err != nil { return nil, err @@ -157,7 +187,6 @@ func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { repo: sn.repo, tree: tree, }, nil - return nil, fuse.ENOENT } // Statically ensure that *dir implement those interface diff --git a/cmd/restic/fuse/snapshot.go b/cmd/restic/fuse/snapshot.go new file mode 100644 index 000000000..f341073a4 --- /dev/null +++ b/cmd/restic/fuse/snapshot.go @@ -0,0 +1,109 @@ +package fuse + +import ( + "encoding/binary" + "os" + "sync" + "time" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + + "github.com/restic/restic" + "github.com/restic/restic/backend" + "github.com/restic/restic/repository" + + "golang.org/x/net/context" +) + +type SnapshotWithId struct { + *restic.Snapshot + backend.ID +} + +// These lines statically ensure that a *SnapshotsDir implement the given +// interfaces; a misplaced refactoring of the implementation that breaks +// the interface will be catched by the compiler +var _ = fs.HandleReadDirAller(&SnapshotsDir{}) +var _ = fs.NodeStringLookuper(&SnapshotsDir{}) + +type SnapshotsDir struct { + repo *repository.Repository + + // knownSnapshots maps snapshot timestamp to the snapshot + sync.RWMutex + knownSnapshots map[string]SnapshotWithId +} + +func NewSnapshotsDir(repo *repository.Repository) *SnapshotsDir { + return &SnapshotsDir{ + repo: repo, + knownSnapshots: make(map[string]SnapshotWithId), + } +} + +func (sn *SnapshotsDir) Attr(ctx context.Context, attr *fuse.Attr) error { + attr.Inode = 0 + attr.Mode = os.ModeDir | 0555 + return nil +} + +func (sn *SnapshotsDir) updateCache(ctx context.Context) error { + sn.Lock() + defer sn.Unlock() + + for id := range sn.repo.List(backend.Snapshot, ctx.Done()) { + snapshot, err := restic.LoadSnapshot(sn.repo, id) + if err != nil { + return err + } + sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = SnapshotWithId{snapshot, id} + } + return nil +} +func (sn *SnapshotsDir) get(name string) (snapshot SnapshotWithId, ok bool) { + sn.RLock() + snapshot, ok = sn.knownSnapshots[name] + sn.RUnlock() + return snapshot, ok +} + +func (sn *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { + err := sn.updateCache(ctx) + if err != nil { + return nil, err + } + + sn.RLock() + defer sn.RUnlock() + + ret := make([]fuse.Dirent, 0) + for _, snapshot := range sn.knownSnapshots { + ret = append(ret, fuse.Dirent{ + Inode: binary.BigEndian.Uint64(snapshot.ID[:8]), + Type: fuse.DT_Dir, + Name: snapshot.Time.Format(time.RFC3339), + }) + } + + return ret, nil +} + +func (sn *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) { + snapshot, ok := sn.get(name) + + if !ok { + // We don't know about it, update the cache + err := sn.updateCache(ctx) + if err != nil { + return nil, err + } + snapshot, ok = sn.get(name) + if !ok { + // We still don't know about it, this time it really doesn't exist + return nil, fuse.ENOENT + } + } + + return newDirFromSnapshot(sn.repo, snapshot) +}