mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 08:47:12 +03:00
086fc9ac30
Summary: Update FuseChannel to send all invalidation requests in a separate thread. This eliminates a deadlock that could previously occur during checkout operations. The invalidation requests would block until they could acquire the kernel's inode lock on the inode in question. However, the inode lock may already be held by another thread attempting to perform an unlink() or rename() call. These FUSE unlink or rename operations would be blocked waiting on Eden's mount point rename lock, which was acquired by the checkout operation. Checkout operations now let the invalidations complete asynchronously, but we wait for all invalidation operations to complete before indicating to our caller that the checkout has succeeded. Reviewed By: chadaustin, wez Differential Revision: D7404971 fbshipit-source-id: 6fa20c00d054e210eb0258d247d083010557f210
127 lines
4.1 KiB
C++
127 lines
4.1 KiB
C++
/*
|
|
* Copyright (c) 2004-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 "eden/fs/inodes/CheckoutContext.h"
|
|
|
|
#include <folly/experimental/logging/xlog.h>
|
|
|
|
#include "eden/fs/inodes/EdenMount.h"
|
|
#include "eden/fs/inodes/InodePtr.h"
|
|
#include "eden/fs/inodes/TreeInode.h"
|
|
|
|
using folly::Future;
|
|
using folly::Unit;
|
|
using std::vector;
|
|
|
|
namespace facebook {
|
|
namespace eden {
|
|
|
|
CheckoutContext::CheckoutContext(
|
|
EdenMount* mount,
|
|
folly::Synchronized<EdenMount::ParentInfo>::LockedPtr&& parentsLock,
|
|
CheckoutMode checkoutMode)
|
|
: checkoutMode_{checkoutMode},
|
|
mount_{mount},
|
|
parentsLock_(std::move(parentsLock)) {}
|
|
|
|
CheckoutContext::~CheckoutContext() {}
|
|
|
|
void CheckoutContext::start(RenameLock&& renameLock) {
|
|
renameLock_ = std::move(renameLock);
|
|
}
|
|
|
|
Future<vector<CheckoutConflict>> CheckoutContext::finish(Hash newSnapshot) {
|
|
// Only update the parents if it is not a dry run.
|
|
if (!isDryRun()) {
|
|
// Update the in-memory snapshot ID
|
|
parentsLock_->parents.setParents(newSnapshot);
|
|
}
|
|
|
|
// Release the rename lock.
|
|
// This allows any filesystem unlink() or rename() operations to proceed.
|
|
renameLock_.unlock();
|
|
|
|
// If we have a FUSE channel, flush all invalidations we sent to the kernel
|
|
// as part of the checkout operation. This will ensure that other processes
|
|
// will see up-to-date data once we return.
|
|
//
|
|
// We do this after releasing the rename lock since some of the invalidation
|
|
// operations may be blocked waiting on FUSE unlink() and rename() operations
|
|
// complete.
|
|
auto* fuseChannel = mount_->getFuseChannel();
|
|
if (!isDryRun() && fuseChannel) {
|
|
XLOG(DBG4) << "waiting for inode invalidations to complete";
|
|
return fuseChannel->flushInvalidations().then([this] {
|
|
XLOG(DBG4) << "finished processing inode invalidations";
|
|
parentsLock_.unlock();
|
|
return std::move(*conflicts_.wlock());
|
|
});
|
|
}
|
|
|
|
// Release the parentsLock_.
|
|
// Once this is released other checkout operations may proceed.
|
|
parentsLock_.unlock();
|
|
|
|
// Return conflicts_ via a move operation. We don't need them any more, and
|
|
// can give ownership directly to our caller.
|
|
return std::move(*conflicts_.wlock());
|
|
}
|
|
|
|
void CheckoutContext::addConflict(ConflictType type, RelativePathPiece path) {
|
|
// Errors should be added using addError()
|
|
CHECK(type != ConflictType::ERROR)
|
|
<< "attempted to add error using addConflict(): " << path;
|
|
|
|
CheckoutConflict conflict;
|
|
conflict.path = path.value().str();
|
|
conflict.type = type;
|
|
conflicts_.wlock()->push_back(std::move(conflict));
|
|
}
|
|
|
|
void CheckoutContext::addConflict(
|
|
ConflictType type,
|
|
TreeInode* parent,
|
|
PathComponentPiece name) {
|
|
// addConflict() should never be called with an unlinked TreeInode.
|
|
//
|
|
// We are holding the RenameLock for the duration of the checkout operation,
|
|
// and we only operate on TreeInode's that still exist in the file system
|
|
// namespace. Therefore parent->getPath() must always return non-none value
|
|
// here.
|
|
auto parentPath = parent->getPath();
|
|
CHECK(parentPath.hasValue());
|
|
|
|
addConflict(type, parentPath.value() + name);
|
|
}
|
|
|
|
void CheckoutContext::addConflict(ConflictType type, InodeBase* inode) {
|
|
// As above, the inode in question must have a path here.
|
|
auto path = inode->getPath();
|
|
CHECK(path.hasValue());
|
|
addConflict(type, path.value());
|
|
}
|
|
|
|
void CheckoutContext::addError(
|
|
TreeInode* parent,
|
|
PathComponentPiece name,
|
|
const folly::exception_wrapper& ew) {
|
|
// As above in addConflict(), the parent tree must have a valid path here.
|
|
auto parentPath = parent->getPath();
|
|
CHECK(parentPath.hasValue());
|
|
|
|
auto path = parentPath.value() + name;
|
|
CheckoutConflict conflict;
|
|
conflict.path = path.value();
|
|
conflict.type = ConflictType::ERROR;
|
|
conflict.message = folly::exceptionStr(ew).toStdString();
|
|
conflicts_.wlock()->push_back(std::move(conflict));
|
|
}
|
|
} // namespace eden
|
|
} // namespace facebook
|