storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/metacache-manager.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2020 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "context" 21 "fmt" 22 "runtime/debug" 23 "sync" 24 "time" 25 26 "storj.io/minio/cmd/logger" 27 ) 28 29 // localMetacacheMgr is the *local* manager for this peer. 30 // It should never be used directly since buckets are 31 // distributed deterministically. 32 // Therefore no cluster locks are required. 33 var localMetacacheMgr = &metacacheManager{ 34 buckets: make(map[string]*bucketMetacache), 35 trash: make(map[string]metacache), 36 } 37 38 type metacacheManager struct { 39 mu sync.RWMutex 40 init sync.Once 41 buckets map[string]*bucketMetacache 42 trash map[string]metacache // Recently deleted lists. 43 } 44 45 const metacacheManagerTransientBucket = "**transient**" 46 const metacacheMaxEntries = 5000 47 48 // initManager will start async saving the cache. 49 func (m *metacacheManager) initManager() { 50 // Add a transient bucket. 51 tb := newBucketMetacache(metacacheManagerTransientBucket, false) 52 tb.transient = true 53 m.buckets[metacacheManagerTransientBucket] = tb 54 55 // Start saver when object layer is ready. 56 go func() { 57 objAPI := newObjectLayerFn() 58 for objAPI == nil { 59 time.Sleep(time.Second) 60 objAPI = newObjectLayerFn() 61 } 62 if !globalIsErasure { 63 logger.Info("metacacheManager was initialized in non-erasure mode, skipping save") 64 return 65 } 66 67 t := time.NewTicker(time.Minute) 68 defer t.Stop() 69 70 var exit bool 71 bg := context.Background() 72 for !exit { 73 select { 74 case <-t.C: 75 case <-GlobalContext.Done(): 76 exit = true 77 } 78 m.mu.RLock() 79 for _, v := range m.buckets { 80 if !exit { 81 v.cleanup() 82 } 83 logger.LogIf(bg, v.save(bg)) 84 } 85 m.mu.RUnlock() 86 m.mu.Lock() 87 for k, v := range m.trash { 88 if time.Since(v.lastUpdate) > metacacheMaxRunningAge { 89 v.delete(context.Background()) 90 delete(m.trash, k) 91 } 92 } 93 m.mu.Unlock() 94 } 95 }() 96 } 97 98 // findCache will get a metacache. 99 func (m *metacacheManager) findCache(ctx context.Context, o listPathOptions) metacache { 100 if o.Transient || isReservedOrInvalidBucket(o.Bucket, false) { 101 return m.getTransient().findCache(o) 102 } 103 m.mu.RLock() 104 b, ok := m.buckets[o.Bucket] 105 if ok { 106 m.mu.RUnlock() 107 return b.findCache(o) 108 } 109 if meta, ok := m.trash[o.ID]; ok { 110 m.mu.RUnlock() 111 return meta 112 } 113 m.mu.RUnlock() 114 return m.getBucket(ctx, o.Bucket).findCache(o) 115 } 116 117 // updateCacheEntry will update non-transient state. 118 func (m *metacacheManager) updateCacheEntry(update metacache) (metacache, error) { 119 m.mu.RLock() 120 if meta, ok := m.trash[update.id]; ok { 121 m.mu.RUnlock() 122 return meta, nil 123 } 124 125 b, ok := m.buckets[update.bucket] 126 m.mu.RUnlock() 127 if ok { 128 return b.updateCacheEntry(update) 129 } 130 131 // We should have either a trashed bucket or this 132 return metacache{}, errVolumeNotFound 133 } 134 135 // getBucket will get a bucket metacache or load it from disk if needed. 136 func (m *metacacheManager) getBucket(ctx context.Context, bucket string) *bucketMetacache { 137 m.init.Do(m.initManager) 138 139 // Return a transient bucket for invalid or system buckets. 140 if isReservedOrInvalidBucket(bucket, false) { 141 return m.getTransient() 142 } 143 m.mu.RLock() 144 b, ok := m.buckets[bucket] 145 m.mu.RUnlock() 146 if ok { 147 if b.bucket != bucket { 148 logger.Info("getBucket: cached bucket %s does not match this bucket %s", b.bucket, bucket) 149 debug.PrintStack() 150 } 151 return b 152 } 153 154 m.mu.Lock() 155 // See if someone else fetched it while we waited for the lock. 156 b, ok = m.buckets[bucket] 157 if ok { 158 m.mu.Unlock() 159 if b.bucket != bucket { 160 logger.Info("getBucket: newly cached bucket %s does not match this bucket %s", b.bucket, bucket) 161 debug.PrintStack() 162 } 163 return b 164 } 165 166 // Load bucket. If we fail return the transient bucket. 167 b, err := loadBucketMetaCache(ctx, bucket) 168 if err != nil { 169 m.mu.Unlock() 170 logger.LogIf(ctx, err) 171 return m.getTransient() 172 } 173 if b.bucket != bucket { 174 logger.LogIf(ctx, fmt.Errorf("getBucket: loaded bucket %s does not match this bucket %s", b.bucket, bucket)) 175 } 176 m.buckets[bucket] = b 177 m.mu.Unlock() 178 return b 179 } 180 181 // deleteBucketCache will delete the bucket cache if it exists. 182 func (m *metacacheManager) deleteBucketCache(bucket string) { 183 m.init.Do(m.initManager) 184 m.mu.Lock() 185 b, ok := m.buckets[bucket] 186 if !ok { 187 m.mu.Unlock() 188 return 189 } 190 delete(m.buckets, bucket) 191 m.mu.Unlock() 192 193 // Since deletes may take some time we try to do it without 194 // holding lock to m all the time. 195 b.mu.Lock() 196 defer b.mu.Unlock() 197 for k, v := range b.caches { 198 if time.Since(v.lastUpdate) > metacacheMaxRunningAge { 199 v.delete(context.Background()) 200 continue 201 } 202 v.error = "Bucket deleted" 203 v.status = scanStateError 204 m.mu.Lock() 205 m.trash[k] = v 206 m.mu.Unlock() 207 } 208 } 209 210 // deleteAll will delete all caches. 211 func (m *metacacheManager) deleteAll() { 212 m.init.Do(m.initManager) 213 m.mu.Lock() 214 defer m.mu.Unlock() 215 for bucket, b := range m.buckets { 216 b.deleteAll() 217 if !b.transient { 218 delete(m.buckets, bucket) 219 } 220 } 221 } 222 223 // getTransient will return a transient bucket. 224 func (m *metacacheManager) getTransient() *bucketMetacache { 225 m.init.Do(m.initManager) 226 m.mu.RLock() 227 bmc := m.buckets[metacacheManagerTransientBucket] 228 m.mu.RUnlock() 229 return bmc 230 } 231 232 // checkMetacacheState should be used if data is not updating. 233 // Should only be called if a failure occurred. 234 func (o listPathOptions) checkMetacacheState(ctx context.Context, rpc *peerRESTClient) error { 235 // We operate on a copy... 236 o.Create = false 237 var cache metacache 238 if rpc == nil || o.Transient { 239 cache = localMetacacheMgr.findCache(ctx, o) 240 } else { 241 c, err := rpc.GetMetacacheListing(ctx, o) 242 if err != nil { 243 return err 244 } 245 cache = *c 246 } 247 248 if cache.status == scanStateNone || cache.fileNotFound { 249 return errFileNotFound 250 } 251 if cache.status == scanStateSuccess || cache.status == scanStateStarted { 252 if time.Since(cache.lastUpdate) > metacacheMaxRunningAge { 253 // We got a stale entry, mark error on handling server. 254 err := fmt.Errorf("timeout: list %s not updated", cache.id) 255 cache.error = err.Error() 256 cache.status = scanStateError 257 if rpc == nil || o.Transient { 258 localMetacacheMgr.updateCacheEntry(cache) 259 } else { 260 rpc.UpdateMetacacheListing(ctx, cache) 261 } 262 return err 263 } 264 return nil 265 } 266 if cache.error != "" { 267 return fmt.Errorf("async cache listing failed with: %s", cache.error) 268 } 269 return nil 270 }