From 13ce18eb241c07ea31cdba0c5b5669bd0101556d Mon Sep 17 00:00:00 2001 From: Kohei Tokunaga Date: Thu, 26 Sep 2024 02:30:56 +0900 Subject: [PATCH] store: use OnForget API for checking if a node is reusable Signed-off-by: Kohei Tokunaga --- cmd/go.mod | 2 +- cmd/go.sum | 6 +- go.mod | 2 +- go.sum | 8 +- store/fs.go | 284 ++++++++++++++++++++++++---------------------------- 5 files changed, 137 insertions(+), 165 deletions(-) diff --git a/cmd/go.mod b/cmd/go.mod index 28fff89f0..ecc08cd12 100644 --- a/cmd/go.mod +++ b/cmd/go.mod @@ -72,7 +72,7 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/hanwen/go-fuse/v2 v2.5.1 // indirect + github.com/hanwen/go-fuse/v2 v2.6.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect diff --git a/cmd/go.sum b/cmd/go.sum index 00e4bb79e..f84bfdb20 100644 --- a/cmd/go.sum +++ b/cmd/go.sum @@ -892,8 +892,8 @@ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= -github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ= -github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs= +github.com/hanwen/go-fuse/v2 v2.6.1 h1:F3RUMbAuRhVTi3fvgf8HjMPvOm9xEv5wjuy/AXJtEwI= +github.com/hanwen/go-fuse/v2 v2.6.1/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mmketmoef48= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -950,7 +950,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= @@ -981,7 +980,6 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= diff --git a/go.mod b/go.mod index 4ddc8b529..2c9283b66 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/docker/cli v27.3.1+incompatible github.com/docker/go-metrics v0.0.1 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da - github.com/hanwen/go-fuse/v2 v2.5.1 + github.com/hanwen/go-fuse/v2 v2.6.1 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.7 github.com/klauspost/compress v1.17.10 diff --git a/go.sum b/go.sum index 22a8bb65b..50213be82 100644 --- a/go.sum +++ b/go.sum @@ -136,8 +136,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ= -github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs= +github.com/hanwen/go-fuse/v2 v2.6.1 h1:F3RUMbAuRhVTi3fvgf8HjMPvOm9xEv5wjuy/AXJtEwI= +github.com/hanwen/go-fuse/v2 v2.6.1/go.mod h1:ugNaD/iv5JYyS1Rcvi57Wz7/vrLQJo10mmketmoef48= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -171,7 +171,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -184,7 +183,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= @@ -343,7 +341,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -355,7 +352,6 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= diff --git a/store/fs.go b/store/fs.go index 7d6fdaf98..c724c7ce0 100644 --- a/store/fs.go +++ b/store/fs.go @@ -37,7 +37,6 @@ import ( fusefs "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" digest "github.com/opencontainers/go-digest" - "golang.org/x/sync/singleflight" ) const ( @@ -100,59 +99,15 @@ type fs struct { // inodes numbers of noeds inside each `diff` directory are prefixed by an unique uint32 // so that they don't conflict with nodes outside `diff` directories. layerMap *idMap - - knownNode map[string]map[string]*layerReleasable - knownNodeMu sync.Mutex -} - -type layerReleasable struct { - n fusefs.InodeEmbedder - released bool - mu sync.Mutex -} - -func (lh *layerReleasable) releasable() bool { - lh.mu.Lock() - released := lh.released - lh.mu.Unlock() - return released && isForgotten(lh.n.EmbeddedInode()) -} - -func (lh *layerReleasable) release() { - lh.mu.Lock() - lh.released = true - lh.mu.Unlock() -} - -func isForgotten(n *fusefs.Inode) bool { - if !n.Forgotten() { - return false - } - for _, cn := range n.Children() { - if !isForgotten(cn) { - return false - } - } - return true -} - -type inoReleasable struct { - n fusefs.InodeEmbedder -} - -func (r *inoReleasable) releasable() bool { - return r.n.EmbeddedInode().Forgotten() } func (fs *fs) newInodeWithID(ctx context.Context, p func(uint32) fusefs.InodeEmbedder) (*fusefs.Inode, syscall.Errno) { - var ino fusefs.InodeEmbedder - if err := fs.nodeMap.add(func(id uint32) (releasable, error) { - ino = p(id) - return &inoReleasable{ino}, nil - }); err != nil || ino == nil { + id, err := fs.nodeMap.get() + if err != nil { log.G(ctx).WithError(err).Debug("cannot generate ID") return nil, syscall.EIO } + ino := p(id) return ino.EmbeddedInode(), 0 } @@ -172,8 +127,8 @@ func (n *rootnode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) // lookup on memory nodes if cn := n.GetChild(name); cn != nil { switch tn := cn.Operations().(type) { - case *fusefs.MemSymlink: - copyAttr(&out.Attr, &tn.Attr) + case *MemSymlinkOnForget: + copyAttr(&out.Attr, tn.attr) case *refnode: copyAttr(&out.Attr, &tn.attr) default: @@ -186,13 +141,15 @@ func (n *rootnode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) switch name { case poolLink: sAttr := defaultLinkAttr(&out.Attr) - cn := &fusefs.MemSymlink{Data: []byte(n.fs.layerManager.refPool.root())} - copyAttr(&cn.Attr, &out.Attr) + fattr := &out.Attr + cn := &MemSymlinkOnForget{fusefs.MemSymlink{Data: []byte(n.fs.layerManager.refPool.root())}, n.fs, fattr} + copyAttr(cn.attr, &out.Attr) return n.fs.newInodeWithID(ctx, func(ino uint32) fusefs.InodeEmbedder { out.Attr.Ino = uint64(ino) - cn.Attr.Ino = uint64(ino) + cn.attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) - return n.NewInode(ctx, cn, sAttr) + fattr.Ino = uint64(ino) + return n.NewPersistentInode(ctx, cn, sAttr) }) } refBytes, err := base64.StdEncoding.DecodeString(name) @@ -216,7 +173,7 @@ func (n *rootnode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) out.Attr.Ino = uint64(ino) cn.attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) - return n.NewInode(ctx, cn, sAttr) + return n.NewPersistentInode(ctx, cn, sAttr) }) } @@ -231,6 +188,12 @@ type refnode struct { var _ = (fusefs.InodeEmbedder)((*refnode)(nil)) +var _ = (fusefs.NodeOnForgetter)((*refnode)(nil)) + +func (n *refnode) OnForget() { + n.fs.nodeMap.remove(uint32(n.attr.Ino)) +} + var _ = (fusefs.NodeLookuper)((*refnode)(nil)) // Lookup returns layernode of the specified name @@ -263,7 +226,7 @@ func (n *refnode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) ( out.Attr.Ino = uint64(ino) cn.attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) - return n.NewInode(ctx, cn, sAttr) + return n.NewPersistentInode(ctx, cn, sAttr) }) } @@ -284,19 +247,13 @@ func (n *refnode) Rmdir(ctx context.Context, name string) syscall.Errno { return syscall.EIO } if current == 0 { - n.fs.knownNodeMu.Lock() - lh, ok := n.fs.knownNode[n.ref.String()][targetDigest.String()] - if !ok { - n.fs.knownNodeMu.Unlock() - log.G(ctx).WithError(err).Warnf("node of layer %v/%v is not registered", n.ref, targetDigest) - return syscall.EIO + if cn := n.GetChild(name); cn != nil { + cn.RmAllChildren() + n.RmChild(name) } - lh.release() - delete(n.fs.knownNode[n.ref.String()], targetDigest.String()) - if len(n.fs.knownNode[n.ref.String()]) == 0 { - delete(n.fs.knownNode, n.ref.String()) + if len(n.Children()) == 0 { + n.EmbeddedInode().RmAllChildren() } - n.fs.knownNodeMu.Unlock() } log.G(ctx).WithField("refcounter", current).Infof("layer %v/%v is marked as RELEASE", n.ref, targetDigest) return syscall.ENOENT @@ -314,6 +271,12 @@ type layernode struct { var _ = (fusefs.InodeEmbedder)((*layernode)(nil)) +var _ = (fusefs.NodeOnForgetter)((*layernode)(nil)) + +func (n *layernode) OnForget() { + n.fs.nodeMap.remove(uint32(n.attr.Ino)) +} + var _ = (fusefs.NodeCreater)((*layernode)(nil)) // Create marks this layer as "using". @@ -330,6 +293,37 @@ var _ = (fusefs.NodeLookuper)((*layernode)(nil)) // Lookup routes to the target file stored in the pool, based on the specified file name. func (n *layernode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fusefs.Inode, syscall.Errno) { + if cn := n.GetChild(name); cn != nil { + found := false + switch tn := cn.Operations().(type) { + case *MemSymlinkOnForget: + copyAttr(&out.Attr, tn.attr) + found = true + case *MemRegularFileOnForget: + copyAttr(&out.Attr, tn.attr) + found = true + case *blobnode: + copyAttr(&out.Attr, &tn.attr) + found = true + default: + log.G(ctx).Debug("layernode.Lookup: uknown node type detected; trying NodeGetattrer provided by layer root node") + if na, ok := cn.Operations().(fusefs.NodeGetattrer); ok { + var ao fuse.AttrOut + errno := na.Getattr(ctx, nil, &ao) + if errno != 0 { + return nil, errno + } + copyAttr(&out.Attr, &ao.Attr) + found = true + } + } + if !found { + log.G(ctx).Warn("layernode.Lookup: uknown node type detected") + return nil, syscall.EIO + } + out.Attr.Ino = cn.StableAttr().Ino + return cn, 0 + } switch name { case layerInfoLink: info, err := n.fs.layerManager.getLayerInfo(ctx, n.refnode.ref, n.digest) @@ -344,34 +338,18 @@ func (n *layernode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) } infoData := buf.Bytes() sAttr := defaultFileAttr(uint64(len(infoData)), &out.Attr) - cn := &fusefs.MemRegularFile{Data: infoData} - copyAttr(&cn.Attr, &out.Attr) + fattr := out.Attr + cn := &MemRegularFileOnForget{fusefs.MemRegularFile{Data: infoData}, n.fs, &fattr} + copyAttr(cn.attr, &out.Attr) return n.fs.newInodeWithID(ctx, func(ino uint32) fusefs.InodeEmbedder { out.Attr.Ino = uint64(ino) - cn.Attr.Ino = uint64(ino) + cn.attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) - return n.NewInode(ctx, cn, sAttr) + fattr.Ino = uint64(ino) + return n.NewPersistentInode(ctx, cn, sAttr) }) case layerLink, blobLink: - // Check if layer is already known - if name == layerLink { - n.fs.knownNodeMu.Lock() - if lh, ok := n.fs.knownNode[n.refnode.ref.String()][n.digest.String()]; ok { - var ao fuse.AttrOut - if errno := lh.n.(fusefs.NodeGetattrer).Getattr(ctx, nil, &ao); errno != 0 { - return nil, errno - } - copyAttr(&out.Attr, &ao.Attr) - n.fs.knownNodeMu.Unlock() - return n.NewInode(ctx, lh.n, fusefs.StableAttr{ - Mode: out.Attr.Mode, - Ino: out.Attr.Ino, - }), 0 - } - n.fs.knownNodeMu.Unlock() - } - // Resolve layer l, err := n.fs.layerManager.getLayer(ctx, n.refnode.ref, n.digest) if err != nil { @@ -400,48 +378,41 @@ func (n *layernode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) } if name == blobLink { sAttr := layerToAttr(l, &out.Attr) - cn := &blobnode{l: l} + cn := &blobnode{l: l, fs: n.fs} copyAttr(&cn.attr, &out.Attr) return n.fs.newInodeWithID(ctx, func(ino uint32) fusefs.InodeEmbedder { out.Attr.Ino = uint64(ino) cn.attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) - return n.NewInode(ctx, cn, sAttr) + return n.NewPersistentInode(ctx, cn, sAttr) }) } var cn *fusefs.Inode var errno syscall.Errno - err = n.fs.layerMap.add(func(id uint32) (releasable, error) { + err = func() error { + id, err := n.fs.layerMap.get() + if err != nil { + return err + } root, err := l.RootNode(id) if err != nil { - return nil, err + return err } var ao fuse.AttrOut errno = root.(fusefs.NodeGetattrer).Getattr(ctx, nil, &ao) if errno != 0 { - return nil, fmt.Errorf("failed to get root node: %v", errno) + return fmt.Errorf("failed to get root node: %v", errno) } copyAttr(&out.Attr, &ao.Attr) - cn = n.NewInode(ctx, root, fusefs.StableAttr{ + cn = n.NewPersistentInode(ctx, root, fusefs.StableAttr{ Mode: out.Attr.Mode, Ino: out.Attr.Ino, }) - - rr := &layerReleasable{n: root} - n.fs.knownNodeMu.Lock() - if n.fs.knownNode == nil { - n.fs.knownNode = make(map[string]map[string]*layerReleasable) - } - if n.fs.knownNode[n.refnode.ref.String()] == nil { - n.fs.knownNode[n.refnode.ref.String()] = make(map[string]*layerReleasable) - } - n.fs.knownNode[n.refnode.ref.String()][n.digest.String()] = rr - n.fs.knownNodeMu.Unlock() - return rr, nil - }) + return nil + }() if err != nil || errno != 0 { log.G(ctx).WithField(remoteSnapshotLogKey, prepareFailed). WithField("layerdigest", n.digest). @@ -468,10 +439,17 @@ type blobnode struct { fusefs.Inode l layer.Layer attr fuse.Attr + fs *fs } var _ = (fusefs.InodeEmbedder)((*blobnode)(nil)) +var _ = (fusefs.NodeOnForgetter)((*blobnode)(nil)) + +func (n *blobnode) OnForget() { + n.fs.nodeMap.remove(uint32(n.attr.Ino)) +} + var _ = (fusefs.NodeOpener)((*blobnode)(nil)) func (n *blobnode) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) { @@ -598,61 +576,61 @@ func defaultLinkAttr(out *fuse.Attr) fusefs.StableAttr { } } -// idMap manages uint32 IDs with automatic GC for releasable objects. type idMap struct { - m map[uint32]releasable - max uint32 - mu sync.Mutex - cleanupG singleflight.Group + m map[uint32]struct{} + mu sync.Mutex } -type releasable interface { - releasable() bool -} - -// add reserves an unique uint32 object for the provided releasable object. -// when that object become releasable, that ID will be reused for other objects. -func (m *idMap) add(p func(uint32) (releasable, error)) error { - m.cleanupG.Do("cleanup", func() (interface{}, error) { - m.mu.Lock() - defer m.mu.Unlock() - max := uint32(0) - for i := uint32(0); i <= m.max; i++ { - if e, ok := m.m[i]; ok { - if e.releasable() { - delete(m.m, i) - } else { - max = i - } - } - } - m.max = max - return nil, nil - }) - +func (m *idMap) get() (uint32, error) { m.mu.Lock() defer m.mu.Unlock() - - if m.m == nil { - m.m = make(map[uint32]releasable) - } - for i := uint32(0); i <= ^uint32(0); i++ { if i == 0 { continue } - e, ok := m.m[i] - if !ok || e.releasable() { - r, err := p(i) - if err != nil { - return err - } - if m.max < i { - m.max = i + if _, ok := m.m[i]; !ok { + if m.m == nil { + m.m = make(map[uint32]struct{}) } - m.m[i] = r - return nil + m.m[i] = struct{}{} + return i, nil } } - return fmt.Errorf("no ID is usable") + return 0, fmt.Errorf("no ID is usable") +} + +func (m *idMap) remove(id uint32) { + m.mu.Lock() + defer m.mu.Unlock() + if m.m != nil { + delete(m.m, id) + } +} + +type MemRegularFileOnForget struct { + fusefs.MemRegularFile + fs *fs + attr *fuse.Attr +} + +var _ = (fusefs.InodeEmbedder)((*MemRegularFileOnForget)(nil)) + +var _ = (fusefs.NodeOnForgetter)((*MemRegularFileOnForget)(nil)) + +func (n *MemRegularFileOnForget) OnForget() { + n.fs.nodeMap.remove(uint32(n.attr.Ino)) +} + +type MemSymlinkOnForget struct { + fusefs.MemSymlink + fs *fs + attr *fuse.Attr +} + +var _ = (fusefs.InodeEmbedder)((*MemSymlinkOnForget)(nil)) + +var _ = (fusefs.NodeOnForgetter)((*MemSymlinkOnForget)(nil)) + +func (n *MemSymlinkOnForget) OnForget() { + n.fs.nodeMap.remove(uint32(n.attr.Ino)) }