mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 06:47:41 +03:00
inodes: update SNAPSHOT at the beginning of checkout
Summary: During checkout, the overlay is updated from the leaf up to the root, and thus halfway through the operation, the overlay contains hashes from both the old commit and the new commit. When the operation finishes, this discrepency no longer exist as the overlay has been fully updated to the destination commit. However, if EdenFS either crashes, or is being killed during the checkout operation, the overlay will be left in this half updated state. Restarting EdenFS and running a status or checkout operation will walk the inodes hierarchy and construct them from the overlay, forcing a large amount of blobs and manifests to be fetched from the network. This can be disruptive and lead to updates/status taking a very long time due to potentially having to fetch the entirety of the repository. Thankfully, EdenFS can recognize this situation by simply updating its SNAPSHOT file at the beginning of the checkout operation. If EdenFS crashes or is killed, Mercurial will send EdenFS its own parent commit, which EdenFS can compare and disagree about, failing the operation instead of fetching large parts of the repository. Reviewed By: chadaustin Differential Revision: D33591210 fbshipit-source-id: b724429bb848425d2b1684b160ad8e1bdfee252a
This commit is contained in:
parent
572b6d9a8f
commit
9b3486480b
@ -35,31 +35,47 @@ CheckoutContext::CheckoutContext(
|
||||
|
||||
CheckoutContext::~CheckoutContext() {}
|
||||
|
||||
void CheckoutContext::start(RenameLock&& renameLock) {
|
||||
renameLock_ = std::move(renameLock);
|
||||
}
|
||||
|
||||
Future<vector<CheckoutConflict>> CheckoutContext::finish(
|
||||
void CheckoutContext::start(
|
||||
RenameLock&& renameLock,
|
||||
EdenMount::ParentLock::LockedPtr&& parentLock,
|
||||
RootId newSnapshot) {
|
||||
renameLock_ = std::move(renameLock);
|
||||
|
||||
// Only update the parent if it is not a dry run.
|
||||
if (!isDryRun()) {
|
||||
std::optional<RootId> oldParent;
|
||||
if (parentLock) {
|
||||
XCHECK(parentLock->checkoutInProgress);
|
||||
oldParent = parentLock->commitHash;
|
||||
// Update the in-memory snapshot ID
|
||||
parentLock->commitHash = newSnapshot;
|
||||
}
|
||||
|
||||
auto config = mount_->getCheckoutConfig();
|
||||
|
||||
// Save the new snapshot hash to the config
|
||||
config->setParentCommit(std::move(newSnapshot));
|
||||
if (!oldParent.has_value()) {
|
||||
config->setParentCommit(std::move(newSnapshot));
|
||||
} else {
|
||||
config->setCheckoutInProgress(oldParent.value(), newSnapshot);
|
||||
}
|
||||
XLOG(DBG1) << "updated snapshot for " << config->getMountPath() << " from "
|
||||
<< (oldParent.has_value() ? oldParent->value() : "<none>")
|
||||
<< " to " << newSnapshot;
|
||||
}
|
||||
}
|
||||
|
||||
parentLock.unlock();
|
||||
Future<vector<CheckoutConflict>> CheckoutContext::finish(RootId newSnapshot) {
|
||||
auto config = mount_->getCheckoutConfig();
|
||||
|
||||
auto parentCommit = config->getParentCommit();
|
||||
if (parentCommit.isCheckoutInProgress()) {
|
||||
XCHECK_EQ(
|
||||
parentCommit.getCurrentRootId(ParentCommit::RootIdPreference::To)
|
||||
.value(),
|
||||
newSnapshot);
|
||||
config->setParentCommit(newSnapshot);
|
||||
}
|
||||
|
||||
// Release the rename lock.
|
||||
// This allows any filesystem unlink() or rename() operations to proceed.
|
||||
|
@ -73,8 +73,15 @@ class CheckoutContext {
|
||||
|
||||
/**
|
||||
* Start the checkout operation.
|
||||
*
|
||||
* As a side effect, this updates the SNAPSHOT file on disk, in the case
|
||||
* where EdenFS is killed or crashes during checkout, this allows EdenFS to
|
||||
* detect that Mercurial is out of date.
|
||||
*/
|
||||
void start(RenameLock&& renameLock);
|
||||
void start(
|
||||
RenameLock&& renameLock,
|
||||
EdenMount::ParentLock::LockedPtr&& parentLock,
|
||||
RootId newSnapshot);
|
||||
|
||||
/**
|
||||
* Complete the checkout operation
|
||||
@ -82,9 +89,7 @@ class CheckoutContext {
|
||||
* Returns the list of conflicts and errors that were encountered during the
|
||||
* operation.
|
||||
*/
|
||||
folly::Future<std::vector<CheckoutConflict>> finish(
|
||||
EdenMount::ParentLock::LockedPtr&& parentLock,
|
||||
RootId newSnapshot);
|
||||
folly::Future<std::vector<CheckoutConflict>> finish(RootId newSnapshot);
|
||||
|
||||
void addConflict(ConflictType type, RelativePathPiece path);
|
||||
void
|
||||
|
@ -693,14 +693,13 @@ folly::Future<SetPathObjectIdResultAndTimes> EdenMount::setPathObjectId(
|
||||
auto [targetTreeInode, incomingTree] = results;
|
||||
targetTreeInode->unloadChildrenUnreferencedByFs();
|
||||
// TODO(@yipu): Remove rename lock
|
||||
ctx->start(this->acquireRenameLock());
|
||||
ctx->start(this->acquireRenameLock(), {}, rootId);
|
||||
setPathObjectIdTime->didAcquireRenameLock = stopWatch.elapsed();
|
||||
return targetTreeInode->checkout(ctx.get(), nullptr, incomingTree);
|
||||
})
|
||||
.thenValue([this, ctx, setPathObjectIdTime, stopWatch, rootId](auto&&) {
|
||||
.thenValue([ctx, setPathObjectIdTime, stopWatch, rootId](auto&&) {
|
||||
setPathObjectIdTime->didCheckout = stopWatch.elapsed();
|
||||
// Complete and save the new snapshot
|
||||
return ctx->finish(parentState_.wlock(), rootId);
|
||||
return ctx->finish(rootId);
|
||||
})
|
||||
.thenValue([ctx, setPathObjectIdTime, stopWatch](
|
||||
std::vector<CheckoutConflict>&& conflicts) {
|
||||
@ -1351,8 +1350,10 @@ folly::Future<CheckoutResult> EdenMount::checkout(
|
||||
checkoutTimes->didDiff = stopWatch.elapsed();
|
||||
|
||||
// Perform the requested checkout operation after the journal diff
|
||||
// completes.
|
||||
ctx->start(this->acquireRenameLock());
|
||||
// completes. This also updates the SNAPSHOT file to make sure that an
|
||||
// interrupted checkout can be properly detected.
|
||||
auto renameLock = this->acquireRenameLock();
|
||||
ctx->start(std::move(renameLock), parentState_.wlock(), snapshotHash);
|
||||
|
||||
checkoutTimes->didAcquireRenameLock = stopWatch.elapsed();
|
||||
|
||||
@ -1390,11 +1391,11 @@ folly::Future<CheckoutResult> EdenMount::checkout(
|
||||
return rootInode->checkout(ctx.get(), fromTree, toTree);
|
||||
});
|
||||
})
|
||||
.thenValue([this, ctx, checkoutTimes, stopWatch, snapshotHash](auto&&) {
|
||||
.thenValue([ctx, checkoutTimes, stopWatch, snapshotHash](auto&&) {
|
||||
checkoutTimes->didCheckout = stopWatch.elapsed();
|
||||
|
||||
// Complete the checkout
|
||||
return ctx->finish(parentState_.wlock(), snapshotHash);
|
||||
return ctx->finish(snapshotHash);
|
||||
})
|
||||
.ensure([this]() {
|
||||
// Checkout completed, make sure to always reset the checkoutInProgress
|
||||
|
Loading…
Reference in New Issue
Block a user