sapling/eden/fs/inodes/CheckoutContext.cpp
Adam Simpkins 086fc9ac30 send FUSE invalidation requests in a separate thread
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
2018-03-27 11:23:42 -07:00

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