github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/block_retrieval_queue.go (about)

     1  // Copyright 2016 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  	"container/heap"
     9  	"io"
    10  	"reflect"
    11  	"sync"
    12  	"time"
    13  
    14  	lru "github.com/hashicorp/golang-lru"
    15  
    16  	"github.com/eapache/channels"
    17  	"github.com/keybase/client/go/kbfs/data"
    18  	"github.com/keybase/client/go/kbfs/env"
    19  	"github.com/keybase/client/go/kbfs/kbfsblock"
    20  	"github.com/keybase/client/go/kbfs/libkey"
    21  	"github.com/keybase/client/go/kbfs/tlf"
    22  	"github.com/keybase/client/go/libkb"
    23  	"github.com/keybase/client/go/logger"
    24  	"github.com/pkg/errors"
    25  	"golang.org/x/net/context"
    26  )
    27  
    28  const (
    29  	defaultBlockRetrievalWorkerQueueSize int = 100
    30  	defaultPrefetchWorkerQueueSize       int = 2
    31  	testBlockRetrievalWorkerQueueSize    int = 5
    32  	testPrefetchWorkerQueueSize          int = 1
    33  	defaultOnDemandRequestPriority       int = 1 << 30
    34  	throttleRequestPriority              int = 1 << 15
    35  
    36  	defaultThrottledPrefetchPeriod = 1 * time.Second
    37  )
    38  
    39  type blockRetrievalPartialConfig interface {
    40  	data.Versioner
    41  	logMaker
    42  	blockCacher
    43  	blockServerGetter
    44  	diskBlockCacheGetter
    45  	syncedTlfGetterSetter
    46  	initModeGetter
    47  	clockGetter
    48  	reporterGetter
    49  	settingsDBGetter
    50  	subscriptionManagerGetter
    51  	subscriptionManagerPublisherGetter
    52  }
    53  
    54  type blockRetrievalConfig interface {
    55  	blockRetrievalPartialConfig
    56  	blockGetter() blockGetter
    57  }
    58  
    59  type realBlockRetrievalConfig struct {
    60  	blockRetrievalPartialConfig
    61  	bg blockGetter
    62  }
    63  
    64  func (c *realBlockRetrievalConfig) blockGetter() blockGetter {
    65  	return c.bg
    66  }
    67  
    68  // blockRetrievalRequest represents one consumer's request for a block.
    69  type blockRetrievalRequest struct {
    70  	block  data.Block
    71  	doneCh chan error
    72  }
    73  
    74  // blockRetrieval contains the metadata for a given block retrieval. May
    75  // represent many requests, all of which will be handled at once.
    76  type blockRetrieval struct {
    77  	//// Retrieval Metadata
    78  	// the block pointer to retrieve
    79  	blockPtr data.BlockPointer
    80  	// the key metadata for the request
    81  	kmd libkey.KeyMetadata
    82  	// the context encapsulating all request contexts
    83  	ctx *CoalescingContext
    84  	// cancel function for the context
    85  	cancelFunc context.CancelFunc
    86  
    87  	// protects requests, cacheLifetime, the prefetch channels, and action
    88  	reqMtx sync.RWMutex
    89  	// the individual requests for this block pointer: they must be notified
    90  	// once the block is returned
    91  	requests []*blockRetrievalRequest
    92  	// the cache lifetime for the retrieval
    93  	cacheLifetime data.BlockCacheLifetime
    94  	// the follow-on action to take once the block is fetched
    95  	action BlockRequestAction
    96  
    97  	//// Queueing Metadata
    98  	// the index of the retrieval in the heap
    99  	index int
   100  	// the priority of the retrieval: larger priorities are processed first
   101  	priority int
   102  	// state of global request counter when this retrieval was created;
   103  	// maintains FIFO
   104  	insertionOrder uint64
   105  }
   106  
   107  // blockPtrLookup is used to uniquely identify block retrieval requests. The
   108  // reflect.Type is needed because sometimes a request is placed concurrently
   109  // for a specific block type and a generic block type. The requests will both
   110  // cause a retrieval, but branching on type allows us to avoid special casing
   111  // the code.
   112  type blockPtrLookup struct {
   113  	bp data.BlockPointer
   114  	t  reflect.Type
   115  }
   116  
   117  // blockRetrievalQueue manages block retrieval requests. Higher priority
   118  // requests are executed first. Requests are executed in FIFO order within a
   119  // given priority level.
   120  type blockRetrievalQueue struct {
   121  	config blockRetrievalConfig
   122  	log    logger.Logger
   123  	vlog   *libkb.VDebugLog
   124  	// protects ptrs, insertionCount, and the heap
   125  	mtx sync.RWMutex
   126  	// queued or in progress retrievals
   127  	ptrs map[blockPtrLookup]*blockRetrieval
   128  	// global counter of insertions to queue
   129  	// capacity: ~584 years at 1 billion requests/sec
   130  	insertionCount  uint64
   131  	heap            *blockRetrievalHeap
   132  	appStateUpdater env.AppStateUpdater
   133  
   134  	// These are notification channels to maximize the time that each request
   135  	// is in the heap, allowing preemption as long as possible. This way, a
   136  	// request only exits the heap once a worker is ready.
   137  	workerCh         channels.Channel
   138  	prefetchWorkerCh channels.Channel
   139  	throttledWorkCh  channels.Channel
   140  
   141  	// slices to store the workers so we can terminate them when we're done
   142  	workers []*blockRetrievalWorker
   143  
   144  	// channel to be closed when we're done accepting requests
   145  	doneLock sync.RWMutex
   146  	doneCh   chan struct{}
   147  
   148  	shutdownCompleteCh chan struct{}
   149  
   150  	// protects prefetcher
   151  	prefetchMtx sync.RWMutex
   152  	// prefetcher for handling prefetching scenarios
   153  	prefetcher Prefetcher
   154  
   155  	prefetchStatusLock           sync.Mutex
   156  	prefetchStatusForNoDiskCache *lru.Cache
   157  }
   158  
   159  var _ BlockRetriever = (*blockRetrievalQueue)(nil)
   160  
   161  // newBlockRetrievalQueue creates a new block retrieval queue. The numWorkers
   162  // parameter determines how many workers can concurrently call Work (more than
   163  // numWorkers will block).
   164  func newBlockRetrievalQueue(
   165  	numWorkers int, numPrefetchWorkers int,
   166  	throttledPrefetchPeriod time.Duration,
   167  	config blockRetrievalConfig,
   168  	appStateUpdater env.AppStateUpdater) *blockRetrievalQueue {
   169  	var throttledWorkCh channels.Channel
   170  	if numPrefetchWorkers > 0 {
   171  		throttledWorkCh = NewInfiniteChannelWrapper()
   172  	}
   173  
   174  	log := config.MakeLogger("")
   175  	q := &blockRetrievalQueue{
   176  		config:             config,
   177  		log:                log,
   178  		vlog:               config.MakeVLogger(log),
   179  		ptrs:               make(map[blockPtrLookup]*blockRetrieval),
   180  		heap:               &blockRetrievalHeap{},
   181  		appStateUpdater:    appStateUpdater,
   182  		workerCh:           NewInfiniteChannelWrapper(),
   183  		prefetchWorkerCh:   NewInfiniteChannelWrapper(),
   184  		throttledWorkCh:    throttledWorkCh,
   185  		doneCh:             make(chan struct{}),
   186  		shutdownCompleteCh: make(chan struct{}),
   187  		workers: make([]*blockRetrievalWorker, 0,
   188  			numWorkers+numPrefetchWorkers),
   189  	}
   190  	q.prefetcher = newBlockPrefetcher(q, config, nil, nil, appStateUpdater)
   191  	for i := 0; i < numWorkers; i++ {
   192  		q.workers = append(q.workers, newBlockRetrievalWorker(
   193  			config.blockGetter(), q, q.workerCh))
   194  	}
   195  	for i := 0; i < numPrefetchWorkers; i++ {
   196  		q.workers = append(q.workers, newBlockRetrievalWorker(
   197  			config.blockGetter(), q, q.prefetchWorkerCh))
   198  	}
   199  	if numPrefetchWorkers > 0 {
   200  		go q.throttleReleaseLoop(
   201  			throttledPrefetchPeriod / time.Duration(numPrefetchWorkers))
   202  	}
   203  	return q
   204  }
   205  
   206  func (brq *blockRetrievalQueue) sendWork(workerCh channels.Channel) {
   207  	select {
   208  	case <-brq.doneCh:
   209  		_ = brq.shutdownRetrievalLocked()
   210  	// Notify the next queued worker.
   211  	case workerCh.In() <- struct{}{}:
   212  	}
   213  }
   214  
   215  func (brq *blockRetrievalQueue) throttleReleaseLoop(
   216  	period time.Duration) {
   217  	var tickerCh <-chan time.Time
   218  	if period > 0 {
   219  		t := time.NewTicker(period)
   220  		defer t.Stop()
   221  		tickerCh = t.C
   222  	} else {
   223  		fullTickerCh := make(chan time.Time)
   224  		close(fullTickerCh)
   225  		tickerCh = fullTickerCh
   226  	}
   227  	for {
   228  		select {
   229  		case <-brq.doneCh:
   230  			return
   231  		case <-tickerCh:
   232  		}
   233  
   234  		select {
   235  		case <-brq.throttledWorkCh.Out():
   236  			brq.mtx.Lock()
   237  			brq.sendWork(brq.prefetchWorkerCh)
   238  			brq.mtx.Unlock()
   239  		case <-brq.doneCh:
   240  			return
   241  		}
   242  	}
   243  }
   244  
   245  func (brq *blockRetrievalQueue) popIfNotEmptyLocked() *blockRetrieval {
   246  	if brq.heap.Len() > 0 {
   247  		return heap.Pop(brq.heap).(*blockRetrieval)
   248  	}
   249  	return nil
   250  }
   251  
   252  func (brq *blockRetrievalQueue) popIfNotEmpty() *blockRetrieval {
   253  	brq.mtx.Lock()
   254  	defer brq.mtx.Unlock()
   255  	return brq.popIfNotEmptyLocked()
   256  }
   257  
   258  func (brq *blockRetrievalQueue) shutdownRetrievalLocked() bool {
   259  	retrieval := brq.popIfNotEmptyLocked()
   260  	if retrieval == nil {
   261  		return false
   262  	}
   263  
   264  	// TODO: try to infer the block type from the requests in the retrieval?
   265  	bpLookup := blockPtrLookup{retrieval.blockPtr, reflect.TypeOf(nil)}
   266  	delete(brq.ptrs, bpLookup)
   267  	brq.finalizeRequestAfterPtrDeletion(
   268  		retrieval, nil, DiskBlockAnyCache, io.EOF)
   269  	return true
   270  }
   271  
   272  // notifyWorker notifies workers that there is a new request for processing.
   273  func (brq *blockRetrievalQueue) notifyWorker(priority int) {
   274  	// On-demand workers and prefetch workers share the priority queue. This
   275  	// allows maximum time for requests to jump the queue, at least until the
   276  	// worker actually begins working on it.
   277  	//
   278  	// Note that the worker being notified won't necessarily work on the exact
   279  	// request that caused the notification. It's just a counter. That means
   280  	// that sometimes on-demand workers will work on prefetch requests, and
   281  	// vice versa. But the numbers should match.
   282  	//
   283  	// However, there are some pathological scenarios where if all the workers
   284  	// of one type are making progress but the other type are not (which is
   285  	// highly improbable), requests of one type could starve the other. By
   286  	// design, on-demand requests _should_ starve prefetch requests, so this is
   287  	// a problem only if prefetch requests can starve on-demand workers. But
   288  	// because there are far more on-demand workers than prefetch workers, this
   289  	// should never actually happen.
   290  	workerCh := brq.workerCh
   291  	if priority <= throttleRequestPriority && brq.throttledWorkCh != nil {
   292  		workerCh = brq.throttledWorkCh
   293  	} else if priority < defaultOnDemandRequestPriority {
   294  		workerCh = brq.prefetchWorkerCh
   295  	}
   296  	brq.sendWork(workerCh)
   297  }
   298  
   299  func (brq *blockRetrievalQueue) initPrefetchStatusCacheLocked() error {
   300  	if !brq.config.IsTestMode() && brq.config.Mode().Type() != InitSingleOp {
   301  		// If the disk block cache directory can't be accessed due to
   302  		// permission errors (happens sometimes on iOS for some
   303  		// reason), we might need to rely on this in-memory map.
   304  		brq.log.Warning("No disk block cache is initialized when not testing")
   305  	}
   306  	brq.log.CDebugf(context.TODO(), "Using a local cache for prefetch status")
   307  	var err error
   308  	cache, err := lru.New(10000)
   309  	if err == nil {
   310  		brq.prefetchStatusForNoDiskCache = cache
   311  	}
   312  	return err
   313  }
   314  
   315  func (brq *blockRetrievalQueue) getPrefetchStatus(
   316  	id kbfsblock.ID) (PrefetchStatus, error) {
   317  	brq.prefetchStatusLock.Lock()
   318  	defer brq.prefetchStatusLock.Unlock()
   319  	if brq.prefetchStatusForNoDiskCache == nil {
   320  		err := brq.initPrefetchStatusCacheLocked()
   321  		if err != nil {
   322  			return NoPrefetch, err
   323  		}
   324  	}
   325  	status, ok := brq.prefetchStatusForNoDiskCache.Get(id)
   326  	if !ok {
   327  		return NoPrefetch, nil
   328  	}
   329  	return status.(PrefetchStatus), nil
   330  }
   331  
   332  func (brq *blockRetrievalQueue) setPrefetchStatus(
   333  	id kbfsblock.ID, prefetchStatus PrefetchStatus) error {
   334  	brq.prefetchStatusLock.Lock()
   335  	defer brq.prefetchStatusLock.Unlock()
   336  	if brq.prefetchStatusForNoDiskCache == nil {
   337  		err := brq.initPrefetchStatusCacheLocked()
   338  		if err != nil {
   339  			return err
   340  		}
   341  	}
   342  	if brq.prefetchStatusForNoDiskCache == nil {
   343  		panic("nil???")
   344  	}
   345  	status, ok := brq.prefetchStatusForNoDiskCache.Get(id)
   346  	if !ok || prefetchStatus > status.(PrefetchStatus) {
   347  		brq.prefetchStatusForNoDiskCache.Add(id, prefetchStatus)
   348  	}
   349  	return nil
   350  }
   351  
   352  func (brq *blockRetrievalQueue) cacheHashBehavior(
   353  	tlfID tlf.ID) data.BlockCacheHashBehavior {
   354  	return cacheHashBehavior(brq.config, brq.config, tlfID)
   355  }
   356  
   357  // PutInCaches implements the BlockRetriever interface for
   358  // BlockRetrievalQueue.
   359  func (brq *blockRetrievalQueue) PutInCaches(ctx context.Context,
   360  	ptr data.BlockPointer, tlfID tlf.ID, block data.Block, lifetime data.BlockCacheLifetime,
   361  	prefetchStatus PrefetchStatus, cacheType DiskBlockCacheType) (err error) {
   362  	err = brq.config.BlockCache().Put(
   363  		ptr, tlfID, block, lifetime, brq.cacheHashBehavior(tlfID))
   364  	switch err.(type) {
   365  	case nil:
   366  	case data.CachePutCacheFullError:
   367  		// Ignore cache full errors and send to the disk cache anyway.
   368  	default:
   369  		return err
   370  	}
   371  	dbc := brq.config.DiskBlockCache()
   372  	if dbc == nil {
   373  		return brq.setPrefetchStatus(ptr.ID, prefetchStatus)
   374  	}
   375  	err = dbc.UpdateMetadata(ctx, tlfID, ptr.ID, prefetchStatus, cacheType)
   376  	switch errors.Cause(err).(type) {
   377  	case nil:
   378  	case data.NoSuchBlockError:
   379  		// TODO: Add the block to the DBC. This is complicated because we
   380  		// need the serverHalf.
   381  		brq.vlog.CLogf(ctx, libkb.VLog2,
   382  			"Block %s missing for disk block cache metadata update", ptr.ID)
   383  	default:
   384  		brq.vlog.CLogf(ctx, libkb.VLog2, "Error updating metadata: %+v", err)
   385  	}
   386  	// All disk cache errors are fatal
   387  	return err
   388  }
   389  
   390  // checkCaches copies a block into `block` if it's in one of our caches.
   391  func (brq *blockRetrievalQueue) checkCaches(ctx context.Context,
   392  	kmd libkey.KeyMetadata, ptr data.BlockPointer, block data.Block,
   393  	action BlockRequestAction) (PrefetchStatus, error) {
   394  	dbc := brq.config.DiskBlockCache()
   395  	preferredCacheType := action.CacheType()
   396  
   397  	cachedBlock, err := brq.config.BlockCache().Get(ptr)
   398  	if err == nil {
   399  		if dbc == nil {
   400  			block.Set(cachedBlock)
   401  			return brq.getPrefetchStatus(ptr.ID)
   402  		}
   403  
   404  		prefetchStatus, err := dbc.GetPrefetchStatus(
   405  			ctx, kmd.TlfID(), ptr.ID, preferredCacheType)
   406  		if err == nil {
   407  			block.Set(cachedBlock)
   408  			return prefetchStatus, nil
   409  		}
   410  		// If the prefetch status wasn't in the preferred cache, do a
   411  		// full `Get()` below in an attempt to move the full block
   412  		// into the preferred cache.
   413  	} else if dbc == nil || action.DelayCacheCheck() {
   414  		return NoPrefetch, err
   415  	}
   416  
   417  	blockBuf, serverHalf, prefetchStatus, err := dbc.Get(
   418  		ctx, kmd.TlfID(), ptr.ID, preferredCacheType)
   419  	if err != nil {
   420  		return NoPrefetch, err
   421  	}
   422  	if len(blockBuf) == 0 {
   423  		return NoPrefetch, data.NoSuchBlockError{ID: ptr.ID}
   424  	}
   425  
   426  	// Assemble the block from the encrypted block buffer.
   427  	err = brq.config.blockGetter().assembleBlockLocal(
   428  		ctx, kmd, ptr, block, blockBuf, serverHalf)
   429  	if err == nil {
   430  		// Cache the block in memory.
   431  		_ = brq.config.BlockCache().Put(
   432  			ptr, kmd.TlfID(), block, data.TransientEntry,
   433  			brq.cacheHashBehavior(kmd.TlfID()))
   434  	}
   435  	return prefetchStatus, err
   436  }
   437  
   438  // request retrieves blocks asynchronously.
   439  func (brq *blockRetrievalQueue) request(ctx context.Context,
   440  	priority int, kmd libkey.KeyMetadata, ptr data.BlockPointer, block data.Block,
   441  	lifetime data.BlockCacheLifetime, action BlockRequestAction) <-chan error {
   442  	brq.vlog.CLogf(ctx, libkb.VLog2,
   443  		"Request of %v, action=%s, priority=%d", ptr, action, priority)
   444  
   445  	// Only continue if we haven't been shut down
   446  	brq.doneLock.RLock()
   447  	defer brq.doneLock.RUnlock()
   448  
   449  	ch := make(chan error, 1)
   450  	select {
   451  	case <-brq.doneCh:
   452  		ch <- io.EOF
   453  		if action.PrefetchTracked() {
   454  			brq.Prefetcher().CancelPrefetch(ptr)
   455  		}
   456  		return ch
   457  	case <-ctx.Done():
   458  		ch <- ctx.Err()
   459  		return ch
   460  	default:
   461  	}
   462  	if block == nil {
   463  		ch <- errors.New("nil block passed to blockRetrievalQueue.Request")
   464  		if action.PrefetchTracked() {
   465  			brq.Prefetcher().CancelPrefetch(ptr)
   466  		}
   467  		return ch
   468  	}
   469  
   470  	// Check caches before locking the mutex.
   471  	prefetchStatus, err := brq.checkCaches(ctx, kmd, ptr, block, action)
   472  	if err == nil {
   473  		if action != BlockRequestSolo {
   474  			brq.vlog.CLogf(
   475  				ctx, libkb.VLog2, "Found %v in caches: %s", ptr, prefetchStatus)
   476  		}
   477  		if action.PrefetchTracked() {
   478  			brq.Prefetcher().ProcessBlockForPrefetch(ctx, ptr, block, kmd,
   479  				priority, lifetime, prefetchStatus, action)
   480  		}
   481  		ch <- nil
   482  		return ch
   483  	}
   484  	err = checkDataVersion(brq.config, data.Path{}, ptr)
   485  	if err != nil {
   486  		if action.PrefetchTracked() {
   487  			brq.Prefetcher().CancelPrefetch(ptr)
   488  		}
   489  		ch <- err
   490  		return ch
   491  	}
   492  
   493  	bpLookup := blockPtrLookup{ptr, reflect.TypeOf(block)}
   494  
   495  	brq.mtx.Lock()
   496  	defer brq.mtx.Unlock()
   497  	// We might have to retry if the context has been canceled.  This loop will
   498  	// iterate a maximum of 2 times. It either hits the `break` statement at
   499  	// the bottom on the first iteration, or the `continue` statement first
   500  	// which causes it to `break` on the next iteration.
   501  	var br *blockRetrieval
   502  	for {
   503  		var exists bool
   504  		br, exists = brq.ptrs[bpLookup]
   505  		if !exists {
   506  			// Add to the heap
   507  			br = &blockRetrieval{
   508  				blockPtr:       ptr,
   509  				kmd:            kmd,
   510  				index:          -1,
   511  				priority:       priority,
   512  				insertionOrder: brq.insertionCount,
   513  				cacheLifetime:  lifetime,
   514  				action:         action,
   515  			}
   516  			br.ctx, br.cancelFunc = NewCoalescingContext(ctx)
   517  			brq.insertionCount++
   518  			brq.ptrs[bpLookup] = br
   519  			heap.Push(brq.heap, br)
   520  			brq.notifyWorker(priority)
   521  		} else {
   522  			err := br.ctx.AddContext(ctx)
   523  			if err == context.Canceled {
   524  				// We need to delete the request pointer, but we'll still let
   525  				// the existing request be processed by a worker.
   526  				delete(brq.ptrs, bpLookup)
   527  				continue
   528  			}
   529  		}
   530  		break
   531  	}
   532  	if br.index == -1 {
   533  		// Log newly-scheduled requests via the normal logger, so we
   534  		// can understand why the bserver fetches certain blocks, and
   535  		// be able to time the request from start to finish.
   536  		brq.log.CDebugf(ctx,
   537  			"Scheduling request of %v, action=%s, priority=%d",
   538  			ptr, action, priority)
   539  	}
   540  	br.reqMtx.Lock()
   541  	defer br.reqMtx.Unlock()
   542  	br.requests = append(br.requests, &blockRetrievalRequest{
   543  		block:  block,
   544  		doneCh: ch,
   545  	})
   546  	if lifetime > br.cacheLifetime {
   547  		br.cacheLifetime = lifetime
   548  	}
   549  	oldPriority := br.priority
   550  	if priority > oldPriority {
   551  		br.priority = priority
   552  		// If the new request priority is higher, elevate the retrieval in the
   553  		// queue.  Skip this if the request is no longer in the queue (which
   554  		// means it's actively being processed).
   555  		if br.index != -1 {
   556  			heap.Fix(brq.heap, br.index)
   557  			if (oldPriority < defaultOnDemandRequestPriority &&
   558  				priority >= defaultOnDemandRequestPriority) ||
   559  				(oldPriority <= throttleRequestPriority &&
   560  					priority > throttleRequestPriority) {
   561  				// We've crossed the priority threshold for a given
   562  				// worker type, so we now need a worker for the new
   563  				// priority level to pick up the request.  This means
   564  				// that we might have up to two workers "activated"
   565  				// per request. However, they won't leak because if a
   566  				// worker sees an empty queue, it continues merrily
   567  				// along.
   568  				brq.notifyWorker(priority)
   569  			}
   570  		}
   571  	}
   572  	// Update the action if needed.
   573  	brq.vlog.CLogf(
   574  		ctx, libkb.VLog2, "Combining actions %s and %s", action, br.action)
   575  	br.action = action.Combine(br.action)
   576  	brq.vlog.CLogf(ctx, libkb.VLog2, "Got action %s", br.action)
   577  	return ch
   578  }
   579  
   580  // Request implements the BlockRetriever interface for blockRetrievalQueue.
   581  func (brq *blockRetrievalQueue) Request(ctx context.Context,
   582  	priority int, kmd libkey.KeyMetadata, ptr data.BlockPointer, block data.Block,
   583  	lifetime data.BlockCacheLifetime, action BlockRequestAction) <-chan error {
   584  	if !action.NonMasterBranch() && brq.config.IsSyncedTlf(kmd.TlfID()) {
   585  		action = action.AddSync()
   586  	}
   587  	return brq.request(ctx, priority, kmd, ptr, block, lifetime, action)
   588  }
   589  
   590  func (brq *blockRetrievalQueue) finalizeRequestAfterPtrDeletion(
   591  	retrieval *blockRetrieval, block data.Block, cacheType DiskBlockCacheType,
   592  	retrievalErr error) {
   593  	defer retrieval.cancelFunc()
   594  
   595  	// This is a lock that exists for the race detector, since there
   596  	// shouldn't be any other goroutines accessing the retrieval at this
   597  	// point. In `Request`, the requests slice can be modified while locked
   598  	// by `brq.mtx`. But once we delete `bpLookup` from `brq.ptrs` here
   599  	// (while locked by `brq.mtx`), there is no longer a way for anyone else
   600  	// to write `retrieval.requests`. However, the race detector still
   601  	// notices that we're reading `retrieval.requests` without a lock, where
   602  	// it was written by a different goroutine in `Request`. So, we lock it
   603  	// with its own mutex in both places.
   604  	retrieval.reqMtx.RLock()
   605  	defer retrieval.reqMtx.RUnlock()
   606  
   607  	dbc := brq.config.DiskBlockCache()
   608  	if dbc != nil && cacheType != retrieval.action.CacheType() {
   609  		brq.log.CDebugf(retrieval.ctx,
   610  			"Cache type changed from %s to %s since we made the request for %s",
   611  			cacheType, retrieval.action.CacheType(),
   612  			retrieval.blockPtr)
   613  		_, _, _, err := dbc.Get(
   614  			retrieval.ctx, retrieval.kmd.TlfID(), retrieval.blockPtr.ID,
   615  			retrieval.action.CacheType())
   616  		if err != nil {
   617  			brq.log.CDebugf(retrieval.ctx,
   618  				"Couldn't move block to preferred cache: %+v", err)
   619  		}
   620  	}
   621  
   622  	// Cache the block and trigger prefetches if there is no error.
   623  	if retrieval.action.PrefetchTracked() {
   624  		if retrievalErr == nil {
   625  			// We treat this request as not having been prefetched, because the
   626  			// only way to get here is if the request wasn't already cached.
   627  			// Need to call with context.Background() because the retrieval's
   628  			// context will be canceled as soon as this method returns.
   629  			brq.Prefetcher().ProcessBlockForPrefetch(context.Background(),
   630  				retrieval.blockPtr, block, retrieval.kmd, retrieval.priority,
   631  				retrieval.cacheLifetime, NoPrefetch, retrieval.action)
   632  		} else {
   633  			brq.log.CDebugf(
   634  				retrieval.ctx, "Couldn't get block %s: %+v", retrieval.blockPtr, retrievalErr)
   635  			brq.Prefetcher().CancelPrefetch(retrieval.blockPtr)
   636  		}
   637  	} else if retrievalErr == nil {
   638  		// Even if the block is not being tracked by the prefetcher,
   639  		// we still want to put it in the block caches.
   640  		err := brq.PutInCaches(
   641  			retrieval.ctx, retrieval.blockPtr, retrieval.kmd.TlfID(), block,
   642  			retrieval.cacheLifetime, NoPrefetch, retrieval.action.CacheType())
   643  		if err != nil {
   644  			brq.log.CDebugf(
   645  				retrieval.ctx, "Couldn't put block in cache: %+v", err)
   646  			// swallow the error if we were unable to put the block into caches.
   647  		}
   648  	}
   649  
   650  	for _, r := range retrieval.requests {
   651  		req := r
   652  		if block != nil {
   653  			// Copy the decrypted block to the caller
   654  			req.block.Set(block)
   655  		}
   656  		// Since we created this channel with a buffer size of 1, this won't
   657  		// block.
   658  		req.doneCh <- retrievalErr
   659  	}
   660  	// Clearing references to the requested blocks seems to plug a
   661  	// leak, but not sure why yet.
   662  	// TODO: strib fixed this earlier. Should be safe to remove here, but
   663  	// follow up in PR.
   664  	retrieval.requests = nil
   665  }
   666  
   667  // FinalizeRequest is the last step of a retrieval request once a block has
   668  // been obtained. It removes the request from the blockRetrievalQueue,
   669  // preventing more requests from mutating the retrieval, then notifies all
   670  // subscribed requests.
   671  func (brq *blockRetrievalQueue) FinalizeRequest(
   672  	retrieval *blockRetrieval, block data.Block, cacheType DiskBlockCacheType,
   673  	retrievalErr error) {
   674  	brq.mtx.Lock()
   675  	// This might have already been removed if the context has been canceled.
   676  	// That's okay, because this will then be a no-op.
   677  	bpLookup := blockPtrLookup{retrieval.blockPtr, reflect.TypeOf(block)}
   678  	delete(brq.ptrs, bpLookup)
   679  	brq.mtx.Unlock()
   680  	brq.finalizeRequestAfterPtrDeletion(
   681  		retrieval, block, cacheType, retrievalErr)
   682  }
   683  
   684  func channelToWaitGroup(wg *sync.WaitGroup, ch <-chan struct{}) {
   685  	wg.Add(1)
   686  	go func() {
   687  		<-ch
   688  		wg.Done()
   689  	}()
   690  }
   691  
   692  func (brq *blockRetrievalQueue) finalizeAllRequests() {
   693  	brq.mtx.Lock()
   694  	defer brq.mtx.Unlock()
   695  	for brq.shutdownRetrievalLocked() {
   696  	}
   697  }
   698  
   699  // Shutdown is called when we are no longer accepting requests.
   700  func (brq *blockRetrievalQueue) Shutdown() <-chan struct{} {
   701  	brq.doneLock.Lock()
   702  	defer brq.doneLock.Unlock()
   703  
   704  	select {
   705  	case <-brq.doneCh:
   706  		return brq.shutdownCompleteCh
   707  	default:
   708  	}
   709  
   710  	var shutdownWaitGroup sync.WaitGroup
   711  	// We close `doneCh` first so that new requests coming in get
   712  	// finalized immediately rather than racing with dying workers.
   713  	close(brq.doneCh)
   714  	for _, w := range brq.workers {
   715  		channelToWaitGroup(&shutdownWaitGroup, w.Shutdown())
   716  	}
   717  	brq.finalizeAllRequests()
   718  
   719  	brq.prefetchMtx.Lock()
   720  	defer brq.prefetchMtx.Unlock()
   721  	channelToWaitGroup(&shutdownWaitGroup, brq.prefetcher.Shutdown())
   722  
   723  	brq.workerCh.Close()
   724  	brq.prefetchWorkerCh.Close()
   725  	if brq.throttledWorkCh != nil {
   726  		brq.throttledWorkCh.Close()
   727  	}
   728  
   729  	go func() {
   730  		shutdownWaitGroup.Wait()
   731  		close(brq.shutdownCompleteCh)
   732  	}()
   733  	return brq.shutdownCompleteCh
   734  }
   735  
   736  // TogglePrefetcher allows upstream components to turn the prefetcher on or
   737  // off. If an error is returned due to a context cancelation, the prefetcher is
   738  // never re-enabled.
   739  func (brq *blockRetrievalQueue) TogglePrefetcher(enable bool,
   740  	testSyncCh <-chan struct{}, testDoneCh chan<- struct{}) <-chan struct{} {
   741  	// We must hold this lock for the whole function so that multiple calls to
   742  	// this function doesn't leak prefetchers.
   743  	brq.prefetchMtx.Lock()
   744  	defer brq.prefetchMtx.Unlock()
   745  	// Allow the caller to block on the current shutdown.
   746  	ch := brq.prefetcher.Shutdown()
   747  	if enable {
   748  		brq.prefetcher = newBlockPrefetcher(
   749  			brq, brq.config, testSyncCh, testDoneCh, brq.appStateUpdater)
   750  	}
   751  	return ch
   752  }
   753  
   754  // Prefetcher allows us to retrieve the prefetcher.
   755  func (brq *blockRetrievalQueue) Prefetcher() Prefetcher {
   756  	brq.prefetchMtx.RLock()
   757  	defer brq.prefetchMtx.RUnlock()
   758  	return brq.prefetcher
   759  }