2018-11-22 06:41:56 +03:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include "BlobCache.h"
|
|
|
|
#include <folly/MapUtil.h>
|
|
|
|
#include <folly/logging/xlog.h>
|
|
|
|
#include "eden/fs/model/Blob.h"
|
2018-12-11 06:28:14 +03:00
|
|
|
#include "eden/fs/utils/IDGen.h"
|
2018-11-22 06:41:56 +03:00
|
|
|
|
|
|
|
namespace facebook {
|
|
|
|
namespace eden {
|
|
|
|
|
|
|
|
BlobInterestHandle::BlobInterestHandle(
|
|
|
|
std::weak_ptr<BlobCache> blobCache,
|
|
|
|
const Hash& hash,
|
2018-12-11 06:28:14 +03:00
|
|
|
std::weak_ptr<const Blob> blob,
|
|
|
|
uint64_t generation) noexcept
|
|
|
|
: blobCache_{std::move(blobCache)},
|
|
|
|
hash_{hash},
|
|
|
|
blob_{std::move(blob)},
|
|
|
|
cacheItemGeneration_{generation} {}
|
2018-11-22 06:41:56 +03:00
|
|
|
|
|
|
|
void BlobInterestHandle::reset() noexcept {
|
|
|
|
if (auto blobCache = blobCache_.lock()) {
|
2018-12-11 06:28:14 +03:00
|
|
|
blobCache->dropInterestHandle(hash_, cacheItemGeneration_);
|
2018-11-22 06:41:56 +03:00
|
|
|
}
|
|
|
|
blobCache_.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<const Blob> BlobInterestHandle::getBlob() const {
|
|
|
|
auto blobCache = blobCache_.lock();
|
|
|
|
if (blobCache) {
|
|
|
|
// UnlikelyNeededAgain because there's no need to create a new interest
|
|
|
|
// handle nor bump the refcount.
|
|
|
|
auto blob =
|
|
|
|
blobCache->get(hash_, BlobCache::Interest::UnlikelyNeededAgain).blob;
|
|
|
|
if (blob) {
|
|
|
|
return blob;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the blob is no longer in cache, at least see if it's still in memory.
|
|
|
|
return blob_.lock();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<BlobCache> BlobCache::create(
|
|
|
|
size_t maximumCacheSizeBytes,
|
|
|
|
size_t minimumEntryCount) {
|
|
|
|
// Allow make_shared with private constructor.
|
|
|
|
struct BC : BlobCache {
|
|
|
|
BC(size_t x, size_t y) : BlobCache{x, y} {}
|
|
|
|
};
|
|
|
|
return std::make_shared<BC>(maximumCacheSizeBytes, minimumEntryCount);
|
|
|
|
}
|
|
|
|
|
|
|
|
BlobCache::BlobCache(size_t maximumCacheSizeBytes, size_t minimumEntryCount)
|
|
|
|
: maximumCacheSizeBytes_{maximumCacheSizeBytes},
|
|
|
|
minimumEntryCount_{minimumEntryCount} {}
|
|
|
|
|
|
|
|
BlobCache::~BlobCache() {}
|
|
|
|
|
|
|
|
BlobCache::GetResult BlobCache::get(const Hash& hash, Interest interest) {
|
2018-12-11 06:28:14 +03:00
|
|
|
XLOG(DBG6) << "BlobCache::get " << hash;
|
|
|
|
|
2018-11-22 06:41:56 +03:00
|
|
|
// Acquires BlobCache's lock upon destruction by calling dropInterestHandle,
|
|
|
|
// so ensure that, if an exception is thrown below, the ~BlobInterestHandle
|
|
|
|
// runs after the lock is released.
|
|
|
|
BlobInterestHandle interestHandle;
|
|
|
|
|
|
|
|
auto state = state_.wlock();
|
|
|
|
|
|
|
|
auto* item = folly::get_ptr(state->items, hash);
|
|
|
|
if (!item) {
|
2018-12-11 06:28:14 +03:00
|
|
|
XLOG(DBG6) << "BlobCache::get missed";
|
2018-12-07 06:25:38 +03:00
|
|
|
++state->missCount;
|
2018-11-22 06:41:56 +03:00
|
|
|
return GetResult{};
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (interest) {
|
|
|
|
case Interest::UnlikelyNeededAgain:
|
|
|
|
interestHandle.blob_ = item->blob;
|
|
|
|
break;
|
|
|
|
case Interest::WantHandle:
|
2018-12-11 06:28:14 +03:00
|
|
|
interestHandle = BlobInterestHandle{
|
|
|
|
shared_from_this(), hash, item->blob, item->generation};
|
2018-11-22 06:41:56 +03:00
|
|
|
++item->referenceCount;
|
|
|
|
break;
|
|
|
|
case Interest::LikelyNeededAgain:
|
|
|
|
interestHandle.blob_ = item->blob;
|
|
|
|
// Bump the reference count without allocating an interest handle - this
|
|
|
|
// will cause the reference count to never reach zero, avoiding early
|
|
|
|
// eviction.
|
|
|
|
//
|
|
|
|
// TODO: One possible optimization here is to set a bit (reference count
|
|
|
|
// to UINT64_MAX) after which new interest handles never need to be
|
|
|
|
// created.
|
|
|
|
++item->referenceCount;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-12-11 06:28:14 +03:00
|
|
|
XLOG(DBG6) << "BlobCache::get hit";
|
|
|
|
|
2018-11-22 06:41:56 +03:00
|
|
|
// TODO: Should we avoid promoting if interest is UnlikelyNeededAgain?
|
|
|
|
// For now, we'll try not to be too clever.
|
|
|
|
state->evictionQueue.splice(
|
|
|
|
state->evictionQueue.end(), state->evictionQueue, item->index);
|
2018-12-07 06:25:38 +03:00
|
|
|
++state->hitCount;
|
2018-11-22 06:41:56 +03:00
|
|
|
return GetResult{item->blob, std::move(interestHandle)};
|
|
|
|
}
|
|
|
|
|
|
|
|
BlobInterestHandle BlobCache::insert(
|
|
|
|
std::shared_ptr<const Blob> blob,
|
|
|
|
Interest interest) {
|
2018-12-11 06:28:14 +03:00
|
|
|
XLOG(DBG6) << "BlobCache::insert " << blob->getHash();
|
|
|
|
|
2018-11-22 06:41:56 +03:00
|
|
|
// Acquires BlobCache's lock upon destruction by calling dropInterestHandle,
|
|
|
|
// so ensure that, if an exception is thrown below, the ~BlobInterestHandle
|
|
|
|
// runs after the lock is released.
|
|
|
|
BlobInterestHandle interestHandle;
|
|
|
|
|
|
|
|
auto hash = blob->getHash();
|
|
|
|
auto size = blob->getSize();
|
|
|
|
|
2018-12-11 06:28:14 +03:00
|
|
|
auto cacheItemGeneration = generateUniqueID();
|
|
|
|
|
2018-11-22 06:41:56 +03:00
|
|
|
if (interest == Interest::WantHandle) {
|
|
|
|
// This can throw, so do it before inserting into items.
|
2018-12-11 06:28:14 +03:00
|
|
|
interestHandle =
|
|
|
|
BlobInterestHandle{shared_from_this(), hash, blob, cacheItemGeneration};
|
2018-11-22 06:41:56 +03:00
|
|
|
} else {
|
|
|
|
interestHandle.blob_ = blob;
|
|
|
|
}
|
|
|
|
|
2018-12-11 06:28:14 +03:00
|
|
|
XLOG(DBG6) << " creating entry with generation=" << cacheItemGeneration;
|
|
|
|
|
2018-11-22 06:41:56 +03:00
|
|
|
auto state = state_.wlock();
|
2018-12-11 06:28:14 +03:00
|
|
|
auto [iter, inserted] =
|
|
|
|
state->items.try_emplace(hash, std::move(blob), cacheItemGeneration);
|
2018-11-22 06:41:56 +03:00
|
|
|
// noexcept from here until `try`
|
|
|
|
switch (interest) {
|
|
|
|
case Interest::UnlikelyNeededAgain:
|
|
|
|
break;
|
|
|
|
case Interest::WantHandle:
|
|
|
|
case Interest::LikelyNeededAgain:
|
|
|
|
++iter->second.referenceCount;
|
|
|
|
break;
|
|
|
|
}
|
2018-12-11 06:28:14 +03:00
|
|
|
auto* itemPtr = &iter->second;
|
2018-11-22 06:41:56 +03:00
|
|
|
if (inserted) {
|
|
|
|
try {
|
|
|
|
state->evictionQueue.push_back(itemPtr);
|
|
|
|
} catch (std::exception&) {
|
|
|
|
state->items.erase(iter);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
iter->second.index = std::prev(state->evictionQueue.end());
|
|
|
|
state->totalSize += size;
|
|
|
|
evictUntilFits(*state);
|
|
|
|
} else {
|
2018-12-11 06:28:14 +03:00
|
|
|
XLOG(DBG6) << " duplicate entry, using generation " << itemPtr->generation;
|
|
|
|
// Inserting duplicate entry - use its generation.
|
|
|
|
interestHandle.cacheItemGeneration_ = itemPtr->generation;
|
2018-11-22 06:41:56 +03:00
|
|
|
state->evictionQueue.splice(
|
2018-12-11 06:28:14 +03:00
|
|
|
state->evictionQueue.end(), state->evictionQueue, itemPtr->index);
|
2018-11-22 06:41:56 +03:00
|
|
|
}
|
|
|
|
return interestHandle;
|
|
|
|
}
|
|
|
|
|
2018-12-06 22:33:06 +03:00
|
|
|
bool BlobCache::contains(const Hash& hash) const {
|
|
|
|
auto state = state_.rlock();
|
|
|
|
return 1 == state->items.count(hash);
|
|
|
|
}
|
|
|
|
|
2018-12-11 06:28:14 +03:00
|
|
|
void BlobCache::clear() {
|
|
|
|
XLOG(DBG6) << "BlobCache::clear";
|
|
|
|
auto state = state_.wlock();
|
|
|
|
state->totalSize = 0;
|
|
|
|
state->items.clear();
|
|
|
|
state->evictionQueue.clear();
|
|
|
|
}
|
|
|
|
|
2018-12-07 06:25:38 +03:00
|
|
|
BlobCache::Stats BlobCache::getStats() const {
|
|
|
|
auto state = state_.rlock();
|
|
|
|
Stats stats;
|
|
|
|
stats.blobCount = state->items.size();
|
|
|
|
stats.totalSizeInBytes = state->totalSize;
|
|
|
|
stats.hitCount = state->hitCount;
|
|
|
|
stats.missCount = state->missCount;
|
|
|
|
stats.evictionCount = state->evictionCount;
|
|
|
|
stats.dropCount = state->dropCount;
|
|
|
|
return stats;
|
2018-11-22 06:41:56 +03:00
|
|
|
}
|
|
|
|
|
2018-12-11 06:28:14 +03:00
|
|
|
void BlobCache::dropInterestHandle(
|
|
|
|
const Hash& hash,
|
|
|
|
uint64_t generation) noexcept {
|
|
|
|
XLOG(DBG6) << "dropInterestHandle " << hash << " generation=" << generation;
|
2018-11-22 06:41:56 +03:00
|
|
|
auto state = state_.wlock();
|
|
|
|
|
|
|
|
auto* item = folly::get_ptr(state->items, hash);
|
|
|
|
if (!item) {
|
|
|
|
// Cached item already evicted.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-12-11 06:28:14 +03:00
|
|
|
if (generation != item->generation) {
|
|
|
|
// Item was evicted and re-added between creating and dropping the interest
|
|
|
|
// handle.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-22 06:41:56 +03:00
|
|
|
if (item->referenceCount == 0) {
|
|
|
|
XLOG(WARN)
|
|
|
|
<< "Reference count on item for " << hash
|
|
|
|
<< " was already zero: an exception must have been thrown during get()";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (--item->referenceCount == 0) {
|
|
|
|
state->evictionQueue.erase(item->index);
|
2018-12-07 06:25:38 +03:00
|
|
|
++state->dropCount;
|
2018-11-22 06:41:56 +03:00
|
|
|
evictItem(*state, item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlobCache::evictUntilFits(State& state) noexcept {
|
2018-12-11 06:28:14 +03:00
|
|
|
XLOG(DBG6) << "state.totalSize=" << state.totalSize
|
|
|
|
<< ", maximumCacheSizeBytes_=" << maximumCacheSizeBytes_
|
|
|
|
<< ", evictionQueue.size()=" << state.evictionQueue.size()
|
|
|
|
<< ", minimumEntryCount_=" << minimumEntryCount_;
|
2018-11-22 06:41:56 +03:00
|
|
|
while (state.totalSize > maximumCacheSizeBytes_ &&
|
|
|
|
state.evictionQueue.size() > minimumEntryCount_) {
|
|
|
|
evictOne(state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlobCache::evictOne(State& state) noexcept {
|
|
|
|
CacheItem* front = state.evictionQueue.front();
|
|
|
|
state.evictionQueue.pop_front();
|
2018-12-07 06:25:38 +03:00
|
|
|
++state.evictionCount;
|
2018-11-22 06:41:56 +03:00
|
|
|
evictItem(state, front);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlobCache::evictItem(State& state, CacheItem* item) noexcept {
|
2018-12-11 06:28:14 +03:00
|
|
|
XLOG(DBG6) << "evicting " << item->blob->getHash()
|
|
|
|
<< " generation=" << item->generation;
|
2018-11-22 06:41:56 +03:00
|
|
|
auto size = item->blob->getSize();
|
|
|
|
// TODO: Releasing this BlobPtr here can run arbitrary deleters which could,
|
|
|
|
// in theory, try to reacquire the BlobCache's lock. The blob could be
|
|
|
|
// scheduled for deletion in a deletion queue but then it's hard to ensure
|
|
|
|
// that scheduling is noexcept. Instead, BlobPtr should be replaced with an
|
|
|
|
// refcounted pointer that doesn't allow running custom deleters.
|
|
|
|
state.items.erase(item->blob->getHash());
|
|
|
|
state.totalSize -= size;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace eden
|
|
|
|
} // namespace facebook
|