package container import ( "sync" utilSync "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sync" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" ) type Info struct { Indexed bool Removed bool } type infoValue struct { info Info err error } type InfoProvider interface { Info(id cid.ID) (Info, error) } type infoProvider struct { mtx *sync.RWMutex cache map[cid.ID]infoValue kl *utilSync.KeyLocker[cid.ID] source Source sourceErr error sourceOnce *sync.Once sourceFactory func() (Source, error) } func NewInfoProvider(sourceFactory func() (Source, error)) InfoProvider { return &infoProvider{ mtx: &sync.RWMutex{}, cache: make(map[cid.ID]infoValue), sourceOnce: &sync.Once{}, kl: utilSync.NewKeyLocker[cid.ID](), sourceFactory: sourceFactory, } } func (r *infoProvider) Info(id cid.ID) (Info, error) { v, found := r.tryGetFromCache(id) if found { return v.info, v.err } return r.getFromSource(id) } func (r *infoProvider) tryGetFromCache(id cid.ID) (infoValue, bool) { r.mtx.RLock() defer r.mtx.RUnlock() value, found := r.cache[id] return value, found } func (r *infoProvider) getFromSource(id cid.ID) (Info, error) { r.kl.Lock(id) defer r.kl.Unlock(id) if v, ok := r.tryGetFromCache(id); ok { return v.info, v.err } r.sourceOnce.Do(func() { r.source, r.sourceErr = r.sourceFactory() }) if r.sourceErr != nil { return Info{}, r.sourceErr } cnr, err := r.source.Get(id) var civ infoValue if err != nil { if client.IsErrContainerNotFound(err) { removed, err := WasRemoved(r.source, id) if err != nil { civ.err = err } else { civ.info.Removed = removed } } else { civ.err = err } } else { civ.info.Indexed = IsIndexedContainer(cnr.Value) } r.putToCache(id, civ) return civ.info, civ.err } func (r *infoProvider) putToCache(id cid.ID, ct infoValue) { r.mtx.Lock() defer r.mtx.Unlock() r.cache[id] = ct }