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  }