| 704 | } |
| 705 | |
| 706 | bool AsyncDataCache::makeSpace( |
| 707 | MachinePageCount numPages, |
| 708 | std::function<bool(memory::Allocation& allocation)> allocate) { |
| 709 | // Try to allocate and if failed, evict the desired amount and |
| 710 | // retry. This is without synchronization, so that other threads may |
| 711 | // get what one thread evicted but this will usually work in a |
| 712 | // couple of iterations. If this does not settle within 8 tries, we |
| 713 | // start counting the contending threads and doing random backoff to |
| 714 | // serialize the evicts and allocates. If a new thread enters when |
| 715 | // thread counting and backoff are in effect, it gets a rank at the |
| 716 | // end of the queue. The larger the rank, the larger the backoff, so |
| 717 | // that first comer is likelier to get the memory. We cannot |
| 718 | // serialize with a mutex because memory arbitration must not be |
| 719 | // called from inside a global mutex. |
| 720 | |
| 721 | constexpr int32_t kMaxAttempts = kNumShards * 4; |
| 722 | // Evict at least 1MB even for small allocations to avoid constantly hitting |
| 723 | // the mutex protected evict loop. |
| 724 | constexpr int32_t kMinEvictPages = 256; |
| 725 | // If requesting less than kSmallSizePages try up to 4x more if |
| 726 | // first try failed. |
| 727 | constexpr int32_t kSmallSizePages = 2048; // 8MB |
| 728 | float sizeMultiplier = 1.2; |
| 729 | // True if this thread is counted in 'numThreadsInAllocate_'. |
| 730 | bool isCounted = false; |
| 731 | // If more than half the allowed retries are needed, this is the rank in |
| 732 | // arrival order of this. |
| 733 | int32_t rank = 0; |
| 734 | // Allocation into which evicted pages are moved. |
| 735 | memory::Allocation acquired; |
| 736 | // 'acquired' is not managed by a pool. Make sure it is freed on throw. |
| 737 | // Destruct without pool and non-empty kills the process. |
| 738 | auto guard = folly::makeGuard([&]() { |
| 739 | try { |
| 740 | allocator_->freeNonContiguous(acquired); |
| 741 | } catch (std::exception& e) { |
| 742 | LOG(ERROR) << "Exception from freeNonContiguous(): " << e.what(); |
| 743 | } |
| 744 | if (isCounted) { |
| 745 | --numThreadsInAllocate_; |
| 746 | } |
| 747 | }); |
| 748 | BOLT_CHECK( |
| 749 | numThreadsInAllocate_ >= 0 && numThreadsInAllocate_ < 10000, |
| 750 | "Leak in numThreadsInAllocate_: {}", |
| 751 | numThreadsInAllocate_); |
| 752 | if (numThreadsInAllocate_) { |
| 753 | rank = ++numThreadsInAllocate_; |
| 754 | isCounted = true; |
| 755 | } |
| 756 | for (auto nthAttempt = 0; nthAttempt < kMaxAttempts; ++nthAttempt) { |
| 757 | if (canTryAllocate(numPages, acquired)) { |
| 758 | if (allocate(acquired)) { |
| 759 | return true; |
| 760 | } |
| 761 | } |
| 762 | |
| 763 | if (nthAttempt > 2 && ssdCache_ && ssdCache_->writeInProgress()) { |
nothing calls this directly
no test coverage detected