github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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 }