github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libkbfs/disk_block_cache_wrapped.go (about)

     1  // Copyright 2017 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libkbfs
     6  
     7  import (
     8  	"path/filepath"
     9  	"sync"
    10  
    11  	"github.com/keybase/client/go/kbfs/data"
    12  	"github.com/keybase/client/go/kbfs/kbfsblock"
    13  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    14  	"github.com/keybase/client/go/kbfs/kbfsmd"
    15  	"github.com/keybase/client/go/kbfs/kbfssync"
    16  	"github.com/keybase/client/go/kbfs/tlf"
    17  	"github.com/pkg/errors"
    18  	ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
    19  	"golang.org/x/net/context"
    20  )
    21  
    22  const (
    23  	workingSetCacheFolderName = "kbfs_block_cache"
    24  	syncCacheFolderName       = "kbfs_sync_cache"
    25  )
    26  
    27  // diskBlockCacheConfig specifies the interfaces that a DiskBlockCacheStandard
    28  // needs to perform its functions. This adheres to the standard libkbfs Config
    29  // API.
    30  type diskBlockCacheConfig interface {
    31  	codecGetter
    32  	logMaker
    33  	clockGetter
    34  	diskLimiterGetter
    35  	initModeGetter
    36  	blockCacher
    37  }
    38  
    39  type diskBlockCacheWrapped struct {
    40  	config      diskBlockCacheConfig
    41  	storageRoot string
    42  	// Protects the caches
    43  	mtx             sync.RWMutex
    44  	workingSetCache *DiskBlockCacheLocal
    45  	syncCache       *DiskBlockCacheLocal
    46  	deleteGroup     kbfssync.RepeatedWaitGroup
    47  }
    48  
    49  var _ DiskBlockCache = (*diskBlockCacheWrapped)(nil)
    50  
    51  func (cache *diskBlockCacheWrapped) enableCache(
    52  	typ diskLimitTrackerType, cacheFolder string, mode InitMode) (err error) {
    53  	cache.mtx.Lock()
    54  	defer cache.mtx.Unlock()
    55  	var cachePtr **DiskBlockCacheLocal
    56  	switch typ {
    57  	case syncCacheLimitTrackerType:
    58  		cachePtr = &cache.syncCache
    59  	case workingSetCacheLimitTrackerType:
    60  		cachePtr = &cache.workingSetCache
    61  	default:
    62  		return errors.New("invalid disk cache type")
    63  	}
    64  	if *cachePtr != nil {
    65  		// We already have a cache of the desired type. Thus, this method is
    66  		// idempotent.
    67  		return nil
    68  	}
    69  	if mode.IsTestMode() {
    70  		*cachePtr, err = newDiskBlockCacheLocalForTest(
    71  			cache.config, typ)
    72  	} else {
    73  		cacheStorageRoot := filepath.Join(cache.storageRoot, cacheFolder)
    74  		*cachePtr, err = newDiskBlockCacheLocal(
    75  			cache.config, typ, cacheStorageRoot, mode)
    76  	}
    77  	return err
    78  }
    79  
    80  func newDiskBlockCacheWrapped(
    81  	config diskBlockCacheConfig, storageRoot string, mode InitMode) (
    82  	cache *diskBlockCacheWrapped, err error) {
    83  	cache = &diskBlockCacheWrapped{
    84  		config:      config,
    85  		storageRoot: storageRoot,
    86  	}
    87  	err = cache.enableCache(
    88  		workingSetCacheLimitTrackerType, workingSetCacheFolderName, mode)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	syncCacheErr := cache.enableCache(
    93  		syncCacheLimitTrackerType, syncCacheFolderName, mode)
    94  	if syncCacheErr != nil {
    95  		log := config.MakeLogger("DBC")
    96  		log.Warning("Could not initialize sync block cache.")
    97  		// We still return success because the working set cache successfully
    98  		// initialized.
    99  	}
   100  	return cache, nil
   101  }
   102  
   103  func (cache *diskBlockCacheWrapped) getCacheLocked(
   104  	cacheType DiskBlockCacheType) (*DiskBlockCacheLocal, error) {
   105  	if cacheType == DiskBlockSyncCache {
   106  		if cache.syncCache == nil {
   107  			return nil, errors.New("Sync cache not enabled")
   108  		}
   109  		return cache.syncCache, nil
   110  	}
   111  	return cache.workingSetCache, nil
   112  }
   113  
   114  // DoesCacheHaveSpace implements the DiskBlockCache interface for
   115  // diskBlockCacheWrapped.
   116  func (cache *diskBlockCacheWrapped) DoesCacheHaveSpace(
   117  	ctx context.Context, cacheType DiskBlockCacheType) (bool, int64, error) {
   118  	cache.mtx.RLock()
   119  	defer cache.mtx.RUnlock()
   120  	c, err := cache.getCacheLocked(cacheType)
   121  	if err != nil {
   122  		return false, 0, err
   123  	}
   124  	return c.DoesCacheHaveSpace(ctx)
   125  }
   126  
   127  // IsSyncCacheEnabled returns true if the sync cache is enabled.
   128  func (cache *diskBlockCacheWrapped) IsSyncCacheEnabled() bool {
   129  	return cache.syncCache != nil
   130  }
   131  
   132  func (cache *diskBlockCacheWrapped) rankCachesLocked(
   133  	preferredCacheType DiskBlockCacheType) (
   134  	primaryCache, secondaryCache *DiskBlockCacheLocal) {
   135  	if preferredCacheType != DiskBlockWorkingSetCache {
   136  		if cache.syncCache == nil {
   137  			log := cache.config.MakeLogger("DBC")
   138  			log.Warning("Sync cache is preferred, but there is no sync cache")
   139  			return cache.workingSetCache, nil
   140  		}
   141  		return cache.syncCache, cache.workingSetCache
   142  	}
   143  	return cache.workingSetCache, cache.syncCache
   144  }
   145  
   146  func (cache *diskBlockCacheWrapped) moveBetweenCachesWithBlockLocked(
   147  	ctx context.Context, tlfID tlf.ID, blockID kbfsblock.ID, buf []byte,
   148  	serverHalf kbfscrypto.BlockCryptKeyServerHalf,
   149  	prefetchStatus PrefetchStatus, newCacheType DiskBlockCacheType) {
   150  	primaryCache, secondaryCache := cache.rankCachesLocked(newCacheType)
   151  	// Move the block into its preferred cache.
   152  	err := primaryCache.Put(ctx, tlfID, blockID, buf, serverHalf)
   153  	if err != nil {
   154  		// The cache will log the non-fatal error, so just return.
   155  		return
   156  	}
   157  
   158  	if prefetchStatus == FinishedPrefetch {
   159  		// Don't propagate a finished status to the primary
   160  		// cache, since the status needs to be with respect to
   161  		// that particular cache (i.e., if the primary cache
   162  		// is the sync cache, all the child blocks must be in
   163  		// the sync cache, for this block to be considered
   164  		// synced, and we can't verify that here).
   165  		prefetchStatus = TriggeredPrefetch
   166  	}
   167  	if prefetchStatus != NoPrefetch {
   168  		_ = primaryCache.UpdateMetadata(ctx, blockID, prefetchStatus)
   169  	}
   170  
   171  	// Remove the block from the non-preferred cache (which is
   172  	// set to be the secondary cache at this point).
   173  	cache.deleteGroup.Add(1)
   174  	go func() {
   175  		defer cache.deleteGroup.Done()
   176  		// Don't catch the errors -- this is just best effort.
   177  		_, _, _ = secondaryCache.Delete(ctx, []kbfsblock.ID{blockID})
   178  	}()
   179  }
   180  
   181  // Get implements the DiskBlockCache interface for diskBlockCacheWrapped.
   182  func (cache *diskBlockCacheWrapped) Get(
   183  	ctx context.Context, tlfID tlf.ID, blockID kbfsblock.ID,
   184  	preferredCacheType DiskBlockCacheType) (
   185  	buf []byte, serverHalf kbfscrypto.BlockCryptKeyServerHalf,
   186  	prefetchStatus PrefetchStatus, err error) {
   187  	cache.mtx.RLock()
   188  	defer cache.mtx.RUnlock()
   189  	primaryCache, secondaryCache := cache.rankCachesLocked(preferredCacheType)
   190  	// Check both caches if the primary cache doesn't have the block.
   191  	buf, serverHalf, prefetchStatus, err = primaryCache.Get(ctx, tlfID, blockID)
   192  	if _, isNoSuchBlockError := errors.Cause(err).(data.NoSuchBlockError); isNoSuchBlockError &&
   193  		secondaryCache != nil {
   194  		buf, serverHalf, prefetchStatus, err = secondaryCache.Get(
   195  			ctx, tlfID, blockID)
   196  		if err != nil {
   197  			return nil, kbfscrypto.BlockCryptKeyServerHalf{}, NoPrefetch, err
   198  		}
   199  		if preferredCacheType != DiskBlockAnyCache {
   200  			cache.moveBetweenCachesWithBlockLocked(
   201  				ctx, tlfID, blockID, buf, serverHalf, prefetchStatus,
   202  				preferredCacheType)
   203  		}
   204  	}
   205  	return buf, serverHalf, prefetchStatus, err
   206  }
   207  
   208  // GetMetadata implements the DiskBlockCache interface for
   209  // diskBlockCacheWrapped.
   210  func (cache *diskBlockCacheWrapped) GetMetadata(ctx context.Context,
   211  	blockID kbfsblock.ID) (metadata DiskBlockCacheMetadata, err error) {
   212  	cache.mtx.RLock()
   213  	defer cache.mtx.RUnlock()
   214  	if cache.syncCache != nil {
   215  		md, err := cache.syncCache.GetMetadata(ctx, blockID)
   216  		switch errors.Cause(err) {
   217  		case nil:
   218  			return md, nil
   219  		case ldberrors.ErrNotFound:
   220  		default:
   221  			return md, err
   222  		}
   223  	}
   224  	return cache.workingSetCache.GetMetadata(ctx, blockID)
   225  }
   226  
   227  func (cache *diskBlockCacheWrapped) moveBetweenCachesLocked(
   228  	ctx context.Context, tlfID tlf.ID, blockID kbfsblock.ID,
   229  	newCacheType DiskBlockCacheType) (moved bool) {
   230  	_, secondaryCache := cache.rankCachesLocked(newCacheType)
   231  	buf, serverHalf, prefetchStatus, err := secondaryCache.Get(
   232  		ctx, tlfID, blockID)
   233  	if err != nil {
   234  		// The block isn't in the secondary cache, so there's nothing to move.
   235  		return false
   236  	}
   237  	cache.moveBetweenCachesWithBlockLocked(
   238  		ctx, tlfID, blockID, buf, serverHalf, prefetchStatus, newCacheType)
   239  	return true
   240  }
   241  
   242  // GetPefetchStatus implements the DiskBlockCache interface for
   243  // diskBlockCacheWrapped.
   244  func (cache *diskBlockCacheWrapped) GetPrefetchStatus(
   245  	ctx context.Context, tlfID tlf.ID, blockID kbfsblock.ID,
   246  	cacheType DiskBlockCacheType) (prefetchStatus PrefetchStatus, err error) {
   247  	cache.mtx.RLock()
   248  	defer cache.mtx.RUnlock()
   249  
   250  	// Try the sync cache first unless working set cache is required.
   251  	if cacheType != DiskBlockWorkingSetCache {
   252  		if cache.syncCache == nil {
   253  			return NoPrefetch, errors.New("Sync cache not enabled")
   254  		}
   255  
   256  		md, err := cache.syncCache.GetMetadata(ctx, blockID)
   257  		switch errors.Cause(err) {
   258  		case nil:
   259  			return md.PrefetchStatus(), nil
   260  		case ldberrors.ErrNotFound:
   261  			if cacheType == DiskBlockSyncCache {
   262  				// Try moving the block and getting it again.
   263  				moved := cache.moveBetweenCachesLocked(
   264  					ctx, tlfID, blockID, cacheType)
   265  				if moved {
   266  					md, err := cache.syncCache.GetMetadata(ctx, blockID)
   267  					if err != nil {
   268  						return NoPrefetch, err
   269  					}
   270  					return md.PrefetchStatus(), nil
   271  				}
   272  				return NoPrefetch, err
   273  			}
   274  			// Otherwise try the working set cache below.
   275  		default:
   276  			return NoPrefetch, err
   277  		}
   278  	}
   279  
   280  	md, err := cache.workingSetCache.GetMetadata(ctx, blockID)
   281  	if err != nil {
   282  		return NoPrefetch, err
   283  	}
   284  	return md.PrefetchStatus(), nil
   285  }
   286  
   287  // Put implements the DiskBlockCache interface for diskBlockCacheWrapped.
   288  func (cache *diskBlockCacheWrapped) Put(ctx context.Context, tlfID tlf.ID,
   289  	blockID kbfsblock.ID, buf []byte,
   290  	serverHalf kbfscrypto.BlockCryptKeyServerHalf,
   291  	cacheType DiskBlockCacheType) error {
   292  	// This is a write operation but we are only reading the pointers to the
   293  	// caches. So we use a read lock.
   294  	cache.mtx.RLock()
   295  	defer cache.mtx.RUnlock()
   296  	if cacheType == DiskBlockSyncCache && cache.syncCache != nil {
   297  		workingSetCache := cache.workingSetCache
   298  		err := cache.syncCache.Put(ctx, tlfID, blockID, buf, serverHalf)
   299  		if err == nil {
   300  			cache.deleteGroup.Add(1)
   301  			go func() {
   302  				defer cache.deleteGroup.Done()
   303  				// Don't catch the errors -- this is just best effort.
   304  				_, _, _ = workingSetCache.Delete(ctx, []kbfsblock.ID{blockID})
   305  			}()
   306  			return nil
   307  		}
   308  		// Otherwise drop through and put it into the working set cache.
   309  	}
   310  	// No need to put it in the working cache if it's already in the
   311  	// sync cache.
   312  	if cache.syncCache != nil {
   313  		_, _, _, err := cache.syncCache.Get(ctx, tlfID, blockID)
   314  		if err == nil {
   315  			return nil
   316  		}
   317  	}
   318  	return cache.workingSetCache.Put(ctx, tlfID, blockID, buf, serverHalf)
   319  }
   320  
   321  // Delete implements the DiskBlockCache interface for diskBlockCacheWrapped.
   322  func (cache *diskBlockCacheWrapped) Delete(ctx context.Context,
   323  	blockIDs []kbfsblock.ID, cacheType DiskBlockCacheType) (
   324  	numRemoved int, sizeRemoved int64, err error) {
   325  	// This is a write operation but we are only reading the pointers to the
   326  	// caches. So we use a read lock.
   327  	cache.mtx.RLock()
   328  	defer cache.mtx.RUnlock()
   329  	if cache.syncCache == nil && cacheType == DiskBlockSyncCache {
   330  		return 0, 0, errors.New("Sync cache not enabled")
   331  	} else if cache.syncCache != nil &&
   332  		(cacheType == DiskBlockAnyCache || cacheType == DiskBlockSyncCache) {
   333  		numRemoved, sizeRemoved, err = cache.syncCache.Delete(ctx, blockIDs)
   334  		if err != nil {
   335  			return 0, 0, err
   336  		}
   337  		if cacheType == DiskBlockSyncCache {
   338  			return numRemoved, sizeRemoved, err
   339  		}
   340  	}
   341  
   342  	wsNumRemoved, wsSizeRemoved, err := cache.workingSetCache.Delete(
   343  		ctx, blockIDs)
   344  	if err != nil {
   345  		return 0, 0, err
   346  	}
   347  	return wsNumRemoved + numRemoved, wsSizeRemoved + sizeRemoved, nil
   348  }
   349  
   350  // UpdateMetadata implements the DiskBlockCache interface for
   351  // diskBlockCacheWrapped.
   352  func (cache *diskBlockCacheWrapped) UpdateMetadata(
   353  	ctx context.Context, tlfID tlf.ID, blockID kbfsblock.ID,
   354  	prefetchStatus PrefetchStatus, cacheType DiskBlockCacheType) error {
   355  	// This is a write operation but we are only reading the pointers to the
   356  	// caches. So we use a read lock.
   357  	cache.mtx.RLock()
   358  	defer cache.mtx.RUnlock()
   359  	primaryCache, secondaryCache := cache.rankCachesLocked(cacheType)
   360  
   361  	err := primaryCache.UpdateMetadata(ctx, blockID, prefetchStatus)
   362  	_, isNoSuchBlockError := errors.Cause(err).(data.NoSuchBlockError)
   363  	if !isNoSuchBlockError {
   364  		return err
   365  	}
   366  	if cacheType == DiskBlockSyncCache {
   367  		// Try moving the block and getting it again.
   368  		moved := cache.moveBetweenCachesLocked(
   369  			ctx, tlfID, blockID, cacheType)
   370  		if moved {
   371  			err = primaryCache.UpdateMetadata(ctx, blockID, prefetchStatus)
   372  		}
   373  		return err
   374  	}
   375  	err = secondaryCache.UpdateMetadata(ctx, blockID, prefetchStatus)
   376  	_, isNoSuchBlockError = errors.Cause(err).(data.NoSuchBlockError)
   377  	if !isNoSuchBlockError {
   378  		return err
   379  	}
   380  	// Try one last time in the primary cache, in case of this
   381  	// sequence of events:
   382  	// 0) Block exists in secondary.
   383  	// 1) UpdateMetadata checks primary, gets NoSuchBlockError.
   384  	// 2) Other goroutine writes block to primary.
   385  	// 3) Other goroutine deletes block from primary.
   386  	// 4) UpdateMetadata checks secondary, gets NoSuchBlockError.
   387  	return primaryCache.UpdateMetadata(ctx, blockID, prefetchStatus)
   388  }
   389  
   390  // ClearAllTlfBlocks implements the DiskBlockCache interface for
   391  // diskBlockCacheWrapper.
   392  func (cache *diskBlockCacheWrapped) ClearAllTlfBlocks(
   393  	ctx context.Context, tlfID tlf.ID, cacheType DiskBlockCacheType) error {
   394  	cache.mtx.RLock()
   395  	defer cache.mtx.RUnlock()
   396  	c, err := cache.getCacheLocked(cacheType)
   397  	if err != nil {
   398  		return err
   399  	}
   400  	return c.ClearAllTlfBlocks(ctx, tlfID)
   401  }
   402  
   403  // GetLastUnrefRev implements the DiskBlockCache interface for
   404  // diskBlockCacheWrapped.
   405  func (cache *diskBlockCacheWrapped) GetLastUnrefRev(
   406  	ctx context.Context, tlfID tlf.ID, cacheType DiskBlockCacheType) (
   407  	kbfsmd.Revision, error) {
   408  	cache.mtx.RLock()
   409  	defer cache.mtx.RUnlock()
   410  	c, err := cache.getCacheLocked(cacheType)
   411  	if err != nil {
   412  		return kbfsmd.RevisionUninitialized, err
   413  	}
   414  	return c.GetLastUnrefRev(ctx, tlfID)
   415  }
   416  
   417  // PutLastUnrefRev implements the DiskBlockCache interface for
   418  // diskBlockCacheWrapped.
   419  func (cache *diskBlockCacheWrapped) PutLastUnrefRev(
   420  	ctx context.Context, tlfID tlf.ID, rev kbfsmd.Revision,
   421  	cacheType DiskBlockCacheType) error {
   422  	cache.mtx.RLock()
   423  	defer cache.mtx.RUnlock()
   424  	c, err := cache.getCacheLocked(cacheType)
   425  	if err != nil {
   426  		return err
   427  	}
   428  	return c.PutLastUnrefRev(ctx, tlfID, rev)
   429  }
   430  
   431  // Status implements the DiskBlockCache interface for diskBlockCacheWrapped.
   432  func (cache *diskBlockCacheWrapped) Status(
   433  	ctx context.Context) map[string]DiskBlockCacheStatus {
   434  	// This is a write operation but we are only reading the pointers to the
   435  	// caches. So we use a read lock.
   436  	cache.mtx.RLock()
   437  	defer cache.mtx.RUnlock()
   438  	statuses := make(map[string]DiskBlockCacheStatus, 2)
   439  	if cache.workingSetCache != nil {
   440  		for name, status := range cache.workingSetCache.Status(ctx) {
   441  			statuses[name] = status
   442  		}
   443  	}
   444  	if cache.syncCache == nil {
   445  		return statuses
   446  	}
   447  	for name, status := range cache.syncCache.Status(ctx) {
   448  		statuses[name] = status
   449  	}
   450  	return statuses
   451  }
   452  
   453  // Mark implements the DiskBlockCache interface for diskBlockCacheWrapped.
   454  func (cache *diskBlockCacheWrapped) Mark(
   455  	ctx context.Context, blockID kbfsblock.ID, tag string,
   456  	cacheType DiskBlockCacheType) error {
   457  	cache.mtx.RLock()
   458  	defer cache.mtx.RUnlock()
   459  	c, err := cache.getCacheLocked(cacheType)
   460  	if err != nil {
   461  		return err
   462  	}
   463  	return c.Mark(ctx, blockID, tag)
   464  }
   465  
   466  // DeleteUnmarked implements the DiskBlockCache interface for
   467  // diskBlockCacheWrapped.
   468  func (cache *diskBlockCacheWrapped) DeleteUnmarked(
   469  	ctx context.Context, tlfID tlf.ID, tag string,
   470  	cacheType DiskBlockCacheType) error {
   471  	cache.mtx.RLock()
   472  	defer cache.mtx.RUnlock()
   473  	c, err := cache.getCacheLocked(cacheType)
   474  	if err != nil {
   475  		return err
   476  	}
   477  	return c.DeleteUnmarked(ctx, tlfID, tag)
   478  }
   479  
   480  func (cache *diskBlockCacheWrapped) waitForDeletes(ctx context.Context) error {
   481  	return cache.deleteGroup.Wait(ctx)
   482  }
   483  
   484  // AddHomeTLF implements the DiskBlockCache interface for diskBlockCacheWrapped.
   485  func (cache *diskBlockCacheWrapped) AddHomeTLF(ctx context.Context,
   486  	tlfID tlf.ID) error {
   487  	cache.mtx.RLock()
   488  	defer cache.mtx.RUnlock()
   489  	if cache.syncCache == nil {
   490  		return errors.New("Sync cache not enabled")
   491  	}
   492  	return cache.syncCache.AddHomeTLF(ctx, tlfID)
   493  }
   494  
   495  // ClearHomeTLFs implements the DiskBlockCache interface for
   496  // diskBlockCacheWrapped.
   497  func (cache *diskBlockCacheWrapped) ClearHomeTLFs(ctx context.Context) error {
   498  	cache.mtx.RLock()
   499  	defer cache.mtx.RUnlock()
   500  	if cache.syncCache == nil {
   501  		return errors.New("Sync cache not enabled")
   502  	}
   503  	return cache.syncCache.ClearHomeTLFs(ctx)
   504  }
   505  
   506  // GetTlfSize implements the DiskBlockCache interface for
   507  // diskBlockCacheWrapped.
   508  func (cache *diskBlockCacheWrapped) GetTlfSize(
   509  	ctx context.Context, tlfID tlf.ID, cacheType DiskBlockCacheType) (
   510  	size uint64, err error) {
   511  	cache.mtx.RLock()
   512  	defer cache.mtx.RUnlock()
   513  
   514  	if cacheType != DiskBlockWorkingSetCache && cache.syncCache != nil {
   515  		// Either sync cache only, or both.
   516  		syncSize, err := cache.syncCache.GetTlfSize(ctx, tlfID)
   517  		if err != nil {
   518  			return 0, err
   519  		}
   520  		size += syncSize
   521  	}
   522  
   523  	if cacheType != DiskBlockSyncCache {
   524  		// Either working set cache only, or both.
   525  		workingSetSize, err := cache.workingSetCache.GetTlfSize(ctx, tlfID)
   526  		if err != nil {
   527  			return 0, err
   528  		}
   529  		size += workingSetSize
   530  	}
   531  
   532  	return size, nil
   533  }
   534  
   535  // GetTlfSize implements the DiskBlockCache interface for
   536  // diskBlockCacheWrapped.
   537  func (cache *diskBlockCacheWrapped) GetTlfIDs(
   538  	ctx context.Context, cacheType DiskBlockCacheType) (
   539  	tlfIDs []tlf.ID, err error) {
   540  	cache.mtx.RLock()
   541  	defer cache.mtx.RUnlock()
   542  
   543  	if cacheType != DiskBlockWorkingSetCache && cache.syncCache != nil {
   544  		// Either sync cache only, or both.
   545  		tlfIDs, err = cache.syncCache.GetTlfIDs(ctx)
   546  		if err != nil {
   547  			return nil, err
   548  		}
   549  	}
   550  
   551  	if cacheType != DiskBlockSyncCache {
   552  		// Either working set cache only, or both.
   553  		wsTlfIDs, err := cache.workingSetCache.GetTlfIDs(ctx)
   554  		if err != nil {
   555  			return nil, err
   556  		}
   557  
   558  		// Uniquify them if needed.
   559  		if len(tlfIDs) == 0 {
   560  			tlfIDs = wsTlfIDs
   561  		} else {
   562  			s := make(map[tlf.ID]bool, len(tlfIDs)+len(wsTlfIDs))
   563  			for _, id := range tlfIDs {
   564  				s[id] = true
   565  			}
   566  			for _, id := range wsTlfIDs {
   567  				s[id] = true
   568  			}
   569  			tlfIDs = make([]tlf.ID, 0, len(s))
   570  			for id := range s {
   571  				tlfIDs = append(tlfIDs, id)
   572  			}
   573  		}
   574  	}
   575  
   576  	return tlfIDs, nil
   577  }
   578  
   579  // WaitUntilStarted implements the DiskBlockCache interface for
   580  // diskBlockCacheWrapped.
   581  func (cache *diskBlockCacheWrapped) WaitUntilStarted(
   582  	cacheType DiskBlockCacheType) (err error) {
   583  	cache.mtx.RLock()
   584  	defer cache.mtx.RUnlock()
   585  
   586  	if cacheType != DiskBlockWorkingSetCache && cache.syncCache != nil {
   587  		err = cache.syncCache.WaitUntilStarted()
   588  		if err != nil {
   589  			return err
   590  		}
   591  	}
   592  
   593  	if cacheType != DiskBlockSyncCache {
   594  		err = cache.workingSetCache.WaitUntilStarted()
   595  		if err != nil {
   596  			return err
   597  		}
   598  	}
   599  
   600  	return nil
   601  }
   602  
   603  // Shutdown implements the DiskBlockCache interface for diskBlockCacheWrapped.
   604  func (cache *diskBlockCacheWrapped) Shutdown(ctx context.Context) <-chan struct{} {
   605  	cache.mtx.Lock()
   606  	defer cache.mtx.Unlock()
   607  	wsCh := cache.workingSetCache.Shutdown(ctx)
   608  	var syncCh <-chan struct{}
   609  	if cache.syncCache != nil {
   610  		syncCh = cache.syncCache.Shutdown(ctx)
   611  	} else {
   612  		ch := make(chan struct{})
   613  		close(ch)
   614  		syncCh = ch
   615  	}
   616  	retCh := make(chan struct{})
   617  	go func() {
   618  		<-wsCh
   619  		<-syncCh
   620  		close(retCh)
   621  	}()
   622  	return retCh
   623  }