2016-06-09 04:59:51 +03:00
|
|
|
/*
|
2017-01-21 09:02:33 +03:00
|
|
|
* Copyright (c) 2016-present, Facebook, Inc.
|
2016-06-09 04:59:51 +03:00
|
|
|
* 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 "ObjectStore.h"
|
|
|
|
|
2016-06-09 04:59:56 +03:00
|
|
|
#include <folly/Conv.h>
|
2016-12-13 04:48:49 +03:00
|
|
|
#include <folly/Optional.h>
|
2016-12-14 05:11:05 +03:00
|
|
|
#include <folly/futures/Future.h>
|
2016-06-09 04:59:51 +03:00
|
|
|
#include <folly/io/IOBuf.h>
|
2018-05-01 07:20:51 +03:00
|
|
|
#include <folly/logging/xlog.h>
|
2016-06-09 04:59:56 +03:00
|
|
|
#include <stdexcept>
|
2017-06-22 23:39:57 +03:00
|
|
|
|
2016-06-09 04:59:56 +03:00
|
|
|
#include "eden/fs/model/Blob.h"
|
|
|
|
#include "eden/fs/model/Tree.h"
|
2017-06-22 23:39:57 +03:00
|
|
|
#include "eden/fs/store/BackingStore.h"
|
|
|
|
#include "eden/fs/store/LocalStore.h"
|
2016-06-09 04:59:51 +03:00
|
|
|
|
2016-12-14 05:11:05 +03:00
|
|
|
using folly::Future;
|
2016-06-09 04:59:51 +03:00
|
|
|
using folly::IOBuf;
|
2016-12-14 05:11:05 +03:00
|
|
|
using folly::makeFuture;
|
2016-06-09 04:59:51 +03:00
|
|
|
using std::shared_ptr;
|
2016-06-09 04:59:56 +03:00
|
|
|
using std::string;
|
2016-06-09 04:59:51 +03:00
|
|
|
using std::unique_ptr;
|
|
|
|
|
2018-08-24 00:12:10 +03:00
|
|
|
DEFINE_bool(
|
|
|
|
reverify_empty_files,
|
|
|
|
true,
|
|
|
|
"Perform extra verification of file contents for empty files.");
|
|
|
|
|
2016-06-09 04:59:51 +03:00
|
|
|
namespace facebook {
|
|
|
|
namespace eden {
|
|
|
|
|
2016-06-14 01:15:31 +03:00
|
|
|
ObjectStore::ObjectStore(
|
|
|
|
shared_ptr<LocalStore> localStore,
|
|
|
|
shared_ptr<BackingStore> backingStore)
|
|
|
|
: localStore_(std::move(localStore)),
|
|
|
|
backingStore_(std::move(backingStore)) {}
|
2016-06-09 04:59:51 +03:00
|
|
|
|
|
|
|
ObjectStore::~ObjectStore() {}
|
|
|
|
|
2017-09-30 02:43:01 +03:00
|
|
|
Future<shared_ptr<const Tree>> ObjectStore::getTree(const Hash& id) const {
|
2016-06-14 01:15:31 +03:00
|
|
|
// Check in the LocalStore first
|
2018-09-15 02:57:46 +03:00
|
|
|
return localStore_->getTree(id).thenValue(
|
2018-06-01 19:23:33 +03:00
|
|
|
[id, backingStore = backingStore_](shared_ptr<const Tree> tree) {
|
|
|
|
if (tree) {
|
|
|
|
XLOG(DBG4) << "tree " << id << " found in local store";
|
|
|
|
return makeFuture(std::move(tree));
|
|
|
|
}
|
2018-05-10 04:41:24 +03:00
|
|
|
|
2018-06-01 19:23:33 +03:00
|
|
|
// Note: We don't currently have logic here to avoid duplicate work if
|
|
|
|
// multiple callers request the same tree at once. We could store a map
|
|
|
|
// of pending lookups as (Hash --> std::list<Promise<unique_ptr<Tree>>),
|
|
|
|
// and just add a new Promise to the list if this Hash already exists in
|
|
|
|
// the pending list.
|
|
|
|
//
|
|
|
|
// However, de-duplication of object loads will already be done at the
|
|
|
|
// Inode layer. Therefore we currently don't bother de-duping loads at
|
|
|
|
// this layer.
|
|
|
|
|
|
|
|
// Load the tree from the BackingStore.
|
2018-09-07 21:02:45 +03:00
|
|
|
return backingStore->getTree(id).thenValue(
|
2018-06-01 19:23:33 +03:00
|
|
|
[id](unique_ptr<const Tree> loadedTree) {
|
|
|
|
if (!loadedTree) {
|
|
|
|
// TODO: Perhaps we should do some short-term negative caching?
|
|
|
|
XLOG(DBG2) << "unable to find tree " << id;
|
|
|
|
throw std::domain_error(
|
|
|
|
folly::to<string>("tree ", id.toString(), " not found"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: For now, the BackingStore objects actually end up already
|
|
|
|
// saving the Tree object in the LocalStore, so we don't do
|
|
|
|
// anything here.
|
|
|
|
//
|
|
|
|
// localStore_->putTree(loadedTree.get());
|
|
|
|
XLOG(DBG3) << "tree " << id << " retrieved from backing store";
|
|
|
|
return shared_ptr<const Tree>(std::move(loadedTree));
|
|
|
|
});
|
|
|
|
});
|
2016-06-09 04:59:51 +03:00
|
|
|
}
|
|
|
|
|
2017-09-30 02:43:01 +03:00
|
|
|
Future<shared_ptr<const Blob>> ObjectStore::getBlob(const Hash& id) const {
|
2018-10-02 20:01:06 +03:00
|
|
|
return localStore_->getBlob(id).thenValue(
|
|
|
|
[id, localStore = localStore_, backingStore = backingStore_](
|
|
|
|
shared_ptr<const Blob> blob) {
|
|
|
|
if (blob) {
|
|
|
|
if (FLAGS_reverify_empty_files && blob->getContents().empty()) {
|
|
|
|
return backingStore->verifyEmptyBlob(id).thenValue(
|
|
|
|
[id, localStore, origBlob = std::move(blob)](
|
|
|
|
std::unique_ptr<Blob> updatedBlob) mutable {
|
|
|
|
if (!updatedBlob) {
|
|
|
|
return shared_ptr<const Blob>(std::move(origBlob));
|
|
|
|
}
|
|
|
|
localStore->putBlob(id, updatedBlob.get());
|
|
|
|
return shared_ptr<const Blob>(std::move(updatedBlob));
|
|
|
|
});
|
2018-06-01 06:40:09 +03:00
|
|
|
}
|
2018-10-02 20:01:06 +03:00
|
|
|
XLOG(DBG4) << "blob " << id << " found in local store";
|
|
|
|
return makeFuture(shared_ptr<const Blob>(std::move(blob)));
|
|
|
|
}
|
2018-05-10 04:41:24 +03:00
|
|
|
|
2018-10-02 20:01:06 +03:00
|
|
|
// Look in the BackingStore
|
|
|
|
return backingStore->getBlob(id).thenValue(
|
|
|
|
[localStore, id](unique_ptr<const Blob> loadedBlob) {
|
|
|
|
if (!loadedBlob) {
|
|
|
|
XLOG(DBG2) << "unable to find blob " << id;
|
|
|
|
// TODO: Perhaps we should do some short-term negative caching?
|
|
|
|
throw std::domain_error(
|
|
|
|
folly::to<string>("blob ", id.toString(), " not found"));
|
|
|
|
}
|
|
|
|
|
|
|
|
XLOG(DBG3) << "blob " << id << " retrieved from backing store";
|
|
|
|
localStore->putBlob(id, loadedBlob.get());
|
|
|
|
return shared_ptr<const Blob>(std::move(loadedBlob));
|
|
|
|
});
|
|
|
|
});
|
2016-06-09 04:59:51 +03:00
|
|
|
}
|
|
|
|
|
2018-05-25 23:47:49 +03:00
|
|
|
folly::Future<folly::Unit> ObjectStore::prefetchBlobs(
|
|
|
|
const std::vector<Hash>& ids) const {
|
|
|
|
// In theory we could/should ask the localStore_ to filter the list
|
|
|
|
// of ids down to just the set that we need to load, but there is no
|
|
|
|
// bulk key existence check in rocksdb, so we would need to cause it
|
|
|
|
// to load all the blocks of those keys into memory.
|
|
|
|
// So for the moment we are committing a layering violation in the
|
|
|
|
// interest of making things faster in practice by just asking the
|
|
|
|
// mercurial backing store to ensure that its local hgcache storage
|
|
|
|
// has entries for all of the requested keys.
|
|
|
|
if (ids.empty()) {
|
|
|
|
return folly::unit;
|
|
|
|
}
|
|
|
|
return backingStore_->prefetchBlobs(ids);
|
|
|
|
}
|
|
|
|
|
2017-09-30 02:43:01 +03:00
|
|
|
Future<shared_ptr<const Tree>> ObjectStore::getTreeForCommit(
|
2016-12-14 05:11:05 +03:00
|
|
|
const Hash& commitID) const {
|
2017-06-22 23:39:57 +03:00
|
|
|
XLOG(DBG3) << "getTreeForCommit(" << commitID << ")";
|
2016-06-16 00:23:25 +03:00
|
|
|
|
2018-09-07 21:02:45 +03:00
|
|
|
return backingStore_->getTreeForCommit(commitID).thenValue(
|
2017-09-30 02:43:01 +03:00
|
|
|
[commitID](std::shared_ptr<const Tree> tree) {
|
2016-12-14 05:11:05 +03:00
|
|
|
if (!tree) {
|
|
|
|
throw std::domain_error(folly::to<string>(
|
|
|
|
"unable to import commit ", commitID.toString()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// For now we assume that the BackingStore will insert the Tree into the
|
|
|
|
// LocalStore on its own, so we don't have to update the LocalStore
|
|
|
|
// ourselves here.
|
|
|
|
return tree;
|
|
|
|
});
|
2016-06-14 01:15:32 +03:00
|
|
|
}
|
|
|
|
|
2016-12-14 05:11:05 +03:00
|
|
|
Future<BlobMetadata> ObjectStore::getBlobMetadata(const Hash& id) const {
|
2018-09-15 02:57:46 +03:00
|
|
|
return localStore_->getBlobMetadata(id).thenValue(
|
2018-06-01 19:23:33 +03:00
|
|
|
[id, localStore = localStore_, backingStore = backingStore_](
|
|
|
|
folly::Optional<BlobMetadata>&& localData) {
|
|
|
|
if (localData.hasValue()) {
|
2018-08-24 00:12:10 +03:00
|
|
|
if (FLAGS_reverify_empty_files && localData.value().size == 0) {
|
|
|
|
return backingStore->verifyEmptyBlob(id).thenValue(
|
|
|
|
[id, metadata = localData.value(), localStore](
|
|
|
|
std::unique_ptr<Blob> blob) {
|
|
|
|
if (!blob) {
|
|
|
|
return metadata;
|
|
|
|
}
|
|
|
|
return localStore->putBlob(id, blob.get());
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return makeFuture(localData.value());
|
|
|
|
}
|
2016-12-14 05:11:05 +03:00
|
|
|
}
|
|
|
|
|
2018-06-01 19:23:33 +03:00
|
|
|
// Load the blob from the BackingStore.
|
|
|
|
//
|
|
|
|
// TODO: It would be nice to add a smarter API to the BackingStore so
|
|
|
|
// that we can query it just for the blob metadata if it supports
|
|
|
|
// getting that without retrieving the full blob data.
|
2018-09-15 02:57:46 +03:00
|
|
|
return backingStore->getBlob(id).thenValue(
|
2018-06-01 19:23:33 +03:00
|
|
|
[localStore, id](std::unique_ptr<Blob> blob) {
|
|
|
|
if (!blob) {
|
|
|
|
// TODO: Perhaps we should do some short-term negative caching?
|
|
|
|
throw std::domain_error(
|
|
|
|
folly::to<string>("blob ", id.toString(), " not found"));
|
|
|
|
}
|
|
|
|
|
|
|
|
return localStore->putBlob(id, blob.get());
|
|
|
|
});
|
2016-12-14 05:11:05 +03:00
|
|
|
});
|
2016-06-09 04:59:51 +03:00
|
|
|
}
|
2017-11-04 01:58:04 +03:00
|
|
|
} // namespace eden
|
|
|
|
} // namespace facebook
|