Skip to content

Commit c86880b

Browse files
miaoyipufacebook-github-bot
authored andcommitted
Rewrite removeRecursively to fast remove unloaded children if possible (try #3)
Reviewed By: chadaustin Differential Revision: D37157670 fbshipit-source-id: 3fa90023b22e66fb7b7e3fa3fc3ee6a1dff68fb7
1 parent 57c8c28 commit c86880b

File tree

9 files changed

+229
-106
lines changed

9 files changed

+229
-106
lines changed

eden/fs/inodes/DirEntry.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ TreeInodePtr DirEntry::asTreePtrOrNull() const {
4949
return TreeInodePtr{};
5050
}
5151

52+
TreeInode* FOLLY_NULLABLE DirEntry::asTreeOrNull() const {
53+
if (hasInodePointer_) {
54+
if (auto tree = dynamic_cast<TreeInode*>(inode_)) {
55+
return tree;
56+
}
57+
}
58+
return nullptr;
59+
}
60+
5261
void DirEntry::setInode(InodeBase* inode) {
5362
XDCHECK(!hasInodePointer_);
5463
XDCHECK(inode);

eden/fs/inodes/DirEntry.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ class DirEntry {
153153
*/
154154
TreeInodePtr asTreePtrOrNull() const;
155155

156+
/**
157+
* Similar to asTreePtrOrNull() except it returns TreeInode* to avoid
158+
* dereferring TreeInodePtr that could potentially deadlock.
159+
*/
160+
TreeInode* FOLLY_NULLABLE asTreeOrNull() const;
161+
156162
/**
157163
* Associates a loaded inode pointer with this entry. Does not take ownership.
158164
*/

eden/fs/inodes/EdenMount.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,7 @@ ImmediateFuture<SetPathObjectIdResultAndTimes> EdenMount::setPathObjectId(
668668
*/
669669
auto oldParent = getWorkingCopyParent();
670670
XLOG(DBG3) << "adding " << rootId << " to Eden mount " << this->getPath()
671-
<< " at path" << path << " on top of " << oldParent;
671+
<< " at path " << path << " on top of " << oldParent;
672672

673673
auto ctx = std::make_shared<CheckoutContext>(
674674
this,

eden/fs/inodes/Overlay.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,10 @@ void Overlay::removeChild(
622622
}
623623
}
624624

625+
void Overlay::removeChildren(InodeNumber parent, const DirContents& content) {
626+
saveOverlayDir(parent, content);
627+
}
628+
625629
void Overlay::renameChild(
626630
InodeNumber src,
627631
InodeNumber dst,

eden/fs/inodes/Overlay.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ class Overlay : public std::enable_shared_from_this<Overlay> {
242242
InodeNumber parent,
243243
PathComponentPiece childName,
244244
const DirContents& content);
245+
void removeChildren(InodeNumber parent, const DirContents& content);
245246

246247
void renameChild(
247248
InodeNumber src,

eden/fs/inodes/PrjfsDispatcherImpl.cpp

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -372,22 +372,20 @@ ImmediateFuture<folly::Unit> handleNotPresentFileNotification(
372372
return createDirInode(mount, dirname.copy(), context)
373373
.thenValue([basename = basename.copy(),
374374
&context](const TreeInodePtr treeInode) {
375-
return treeInode
376-
->removeRecursively(
377-
basename, InvalidationRequired::No, context)
378-
.thenTry([](folly::Try<folly::Unit> try_) {
379-
if (auto* exc =
380-
try_.tryGetExceptionObject<std::system_error>()) {
381-
if (isEnoent(*exc)) {
382-
// ProjectedFS can sometimes send multiple deletion
383-
// notification for the same file, in which case a
384-
// previous deletion will have removed the file already.
385-
// We can safely ignore the error here.
386-
return folly::Try{folly::unit};
387-
}
388-
}
389-
return try_;
390-
});
375+
return treeInode->removeRecursively(
376+
basename, InvalidationRequired::No, context);
377+
})
378+
.thenTry([](folly::Try<folly::Unit> try_) {
379+
if (auto* exc = try_.tryGetExceptionObject<std::system_error>()) {
380+
if (isEnoent(*exc)) {
381+
// ProjectedFS can sometimes send multiple deletion
382+
// notification for the same file, in which case a
383+
// previous deletion will have removed the file already.
384+
// We can safely ignore the error here.
385+
return folly::Try{folly::unit};
386+
}
387+
}
388+
return try_;
391389
});
392390
})
393391
.ensure([path = std::move(path)] {});

eden/fs/inodes/TreeInode.cpp

Lines changed: 152 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,47 +1207,158 @@ ImmediateFuture<folly::Unit> TreeInode::rmdir(
12071207
});
12081208
}
12091209

1210-
ImmediateFuture<folly::Unit> TreeInode::removeRecursively(
1210+
void TreeInode::removeAllChildrenRecursively(
1211+
InvalidationRequired invalidate,
1212+
ObjectFetchContext& context,
1213+
const RenameLock& renameLock) {
1214+
// TODO: Unconditional materialization is slightly conservative. If the
1215+
// BackingStore Tree is empty, then this function can return without
1216+
// materializing.
1217+
materialize(&renameLock);
1218+
#ifndef _WIN32
1219+
if (getNodeId() == getMount()->getDotEdenInodeNumber()) {
1220+
throw InodeError(EPERM, inodePtrFromThis());
1221+
}
1222+
#endif
1223+
1224+
std::vector<TreeInodePtr> loadedTreeNodes;
1225+
// Step 1, collect children nodes who are tree and loaded
1226+
{
1227+
auto contents = contents_.rlock();
1228+
for (auto& entry : contents->entries) {
1229+
if (auto asTreePtr = entry.second.asTreePtrOrNull()) {
1230+
loadedTreeNodes.push_back(std::move(asTreePtr));
1231+
}
1232+
}
1233+
}
1234+
1235+
// Step 2, Clear contents in the child folders
1236+
for (auto& treeNode : loadedTreeNodes) {
1237+
treeNode->removeAllChildrenRecursively(invalidate, context, renameLock);
1238+
}
1239+
1240+
loadedTreeNodes.clear();
1241+
1242+
// Step 3, Now all child nodes are removable, unless one of the directories
1243+
// had a new entry added while the contents lock was not held.
1244+
auto contents = contents_.wlock();
1245+
auto it = contents->entries.begin();
1246+
while (it != contents->entries.end()) {
1247+
auto inodeNum = it->second.getInodeNumber();
1248+
bool isDir = it->second.isDirectory();
1249+
if (it->second.getInode()) {
1250+
// If a treeInode is not empty, i.e. files were added to the tree
1251+
// between step2 and step3, an exception will be thrown.
1252+
1253+
// TODO: There's a race here: checkPreRemove acquires the child's
1254+
// contents lock but then releases it after the check. Thus, there's a
1255+
// window where the child can gain an entry being unlinked, which breaks
1256+
// EdenFS's internal data model. This code should acquire the child's
1257+
// contents lock and hold it across the unlink operation.
1258+
//
1259+
// TODO: Have checkPreRemove take a DirContents& to ensure the contents
1260+
// lock is acquired by the parent, and encourage holding it across the
1261+
// unlink operation.
1262+
//
1263+
// Be careful, here we obtains TreeInode* instead of TreeIndePtr to avoid
1264+
// deference of TreeInodePtr, otherwise there could be a deadlock on
1265+
// getting the parent location info when deference.
1266+
if (TreeInode* asTreePtr = it->second.asTreeOrNull()) {
1267+
int checkResult = checkPreRemove(*asTreePtr);
1268+
if (checkResult != 0) {
1269+
throw InodeError(checkResult, InodePtr::newPtrLocked(asTreePtr));
1270+
}
1271+
}
1272+
1273+
auto inode = it->second.getInode();
1274+
inode->markUnlinked(this, it->first, renameLock);
1275+
}
1276+
// Erase from contents must happen right after markUnlink
1277+
it = contents->entries.erase(it);
1278+
1279+
if (isDir) {
1280+
getOverlay()->recursivelyRemoveOverlayData(inodeNum);
1281+
} else {
1282+
getOverlay()->removeOverlayData(inodeNum);
1283+
}
1284+
}
1285+
1286+
if (InvalidationRequired::Yes == invalidate) {
1287+
invalidateChannelDirCache(*contents).get();
1288+
}
1289+
updateMtimeAndCtimeLocked(contents->entries, getNow());
1290+
getOverlay()->removeChildren(getNodeId(), contents->entries);
1291+
}
1292+
1293+
InodePtr TreeInode::tryRemoveUnloadedChild(
1294+
PathComponentPiece name,
1295+
InvalidationRequired invalidate) {
1296+
#ifndef _WIN32
1297+
if (getNodeId() == getMount()->getDotEdenInodeNumber()) {
1298+
throw InodeError(EPERM, inodePtrFromThis());
1299+
}
1300+
#endif
1301+
auto contents = contents_.wlock();
1302+
1303+
auto it = contents->entries.find(name);
1304+
if (it == contents->entries.end()) {
1305+
throw InodeError(ENOENT, inodePtrFromThis(), name);
1306+
}
1307+
1308+
auto inodeName = copyCanonicalInodeName(it);
1309+
auto inodeNumber = it->second.getInodeNumber();
1310+
1311+
if (auto node = it->second.getInodePtr()) {
1312+
// The child has a loaded! Fall back to the slow path.
1313+
return node;
1314+
}
1315+
1316+
contents->entries.erase(it);
1317+
if (InvalidationRequired::Yes == invalidate) {
1318+
invalidateChannelEntryCache(*contents, inodeName, inodeNumber)
1319+
.throwUnlessValue();
1320+
invalidateChannelDirCache(*contents).get();
1321+
}
1322+
1323+
updateMtimeAndCtimeLocked(contents->entries, getNow());
1324+
if (it->second.isDirectory()) {
1325+
getOverlay()->recursivelyRemoveOverlayData(inodeNumber);
1326+
} else {
1327+
getOverlay()->removeOverlayData(inodeNumber);
1328+
}
1329+
getOverlay()->removeChild(getNodeId(), name, contents->entries);
1330+
return nullptr;
1331+
}
1332+
1333+
ImmediateFuture<folly::Unit> TreeInode::removeRecursivelyNoFlushInvalidation(
12111334
PathComponentPiece name,
12121335
InvalidationRequired invalidate,
12131336
ObjectFetchContext& context) {
1214-
return getOrLoadChild(name, context)
1215-
.thenValue([self = inodePtrFromThis(),
1216-
name = name.copy(),
1217-
invalidate,
1218-
&context](InodePtr child) mutable {
1219-
auto asFileInode = child.asSubclassPtrOrNull<FileInodePtr>();
1220-
if (asFileInode) {
1221-
return self->removeImpl<FileInodePtr>(
1222-
std::move(name), std::move(child), invalidate, 1, context);
1223-
} else {
1224-
auto tree = child.asTreePtr();
1337+
// Fast return if the node is unloaded and removed
1338+
auto child = tryRemoveUnloadedChild(name, invalidate);
1339+
if (!child) {
1340+
return folly::unit;
1341+
}
12251342

1226-
std::vector<PathComponent> names;
1227-
{
1228-
auto contents = tree->contents_.rlock();
1229-
for (const auto& entry : contents->entries) {
1230-
names.emplace_back(entry.first);
1231-
}
1232-
}
1343+
if (child.asFilePtrOrNull()) {
1344+
return inodePtrFromThis()->removeImpl<FileInodePtr>(
1345+
PathComponent{name}, std::move(child), invalidate, 1, context);
1346+
} else {
1347+
{
1348+
auto renameLock = inodePtrFromThis()->getMount()->acquireRenameLock();
1349+
child.asTreePtr()->removeAllChildrenRecursively(
1350+
invalidate, context, renameLock);
1351+
}
1352+
return inodePtrFromThis()->removeImpl<TreeInodePtr>(
1353+
PathComponent{name}, std::move(child), invalidate, 1, context);
1354+
}
1355+
}
12331356

1234-
std::vector<ImmediateFuture<folly::Unit>> childRemovalFutures;
1235-
childRemovalFutures.reserve(names.size());
1236-
for (const auto& name : names) {
1237-
childRemovalFutures.push_back(
1238-
tree->removeRecursively(name, invalidate, context));
1239-
}
1240-
return collectAllSafe(std::move(childRemovalFutures))
1241-
.thenValue([self,
1242-
name = std::move(name),
1243-
invalidate,
1244-
child = std::move(child),
1245-
&context](std::vector<folly::Unit>&&) mutable {
1246-
return self->removeImpl<TreeInodePtr>(
1247-
std::move(name), std::move(child), invalidate, 1, context);
1248-
});
1249-
}
1250-
})
1357+
ImmediateFuture<folly::Unit> TreeInode::removeRecursively(
1358+
PathComponentPiece name,
1359+
InvalidationRequired invalidate,
1360+
ObjectFetchContext& context) {
1361+
return this->removeRecursivelyNoFlushInvalidation(name, invalidate, context)
12511362
.thenValue([self = inodePtrFromThis(), invalidate](folly::Unit&&) {
12521363
if (invalidate == InvalidationRequired::Yes) {
12531364
return self->getMount()->flushInvalidations();
@@ -1271,7 +1382,7 @@ ImmediateFuture<folly::Unit> TreeInode::removeImpl(
12711382
}
12721383

12731384
// Verify that we can remove the child before we materialize ourself
1274-
int checkResult = checkPreRemove(child);
1385+
int checkResult = checkPreRemove(*child);
12751386
if (checkResult != 0) {
12761387
return ImmediateFuture<Unit>{
12771388
folly::Try<Unit>{InodeError{checkResult, child}}};
@@ -1398,7 +1509,7 @@ int TreeInode::tryRemoveChild(
13981509
}
13991510

14001511
// Verify that the child is still in a good state to remove
1401-
auto checkError = checkPreRemove(child);
1512+
auto checkError = checkPreRemove(*child);
14021513
if (checkError != 0) {
14031514
return checkError;
14041515
}
@@ -1436,16 +1547,16 @@ int TreeInode::tryRemoveChild(
14361547
return 0;
14371548
}
14381549

1439-
int TreeInode::checkPreRemove(const TreeInodePtr& child) {
1550+
int TreeInode::checkPreRemove(const TreeInode& child) {
14401551
// Lock the child contents, and make sure they are empty
1441-
auto childContents = child->contents_.rlock();
1552+
auto childContents = child.contents_.rlock();
14421553
if (!childContents->entries.empty()) {
14431554
return ENOTEMPTY;
14441555
}
14451556
return 0;
14461557
}
14471558

1448-
int TreeInode::checkPreRemove(const FileInodePtr& /* child */) {
1559+
int TreeInode::checkPreRemove(const FileInode& /* child */) {
14491560
// Nothing to do
14501561
return 0;
14511562
}

eden/fs/inodes/TreeInode.h

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "eden/fs/inodes/DirEntry.h"
1616
#include "eden/fs/inodes/InodeBase.h"
1717
#include "eden/fs/inodes/InodeOrTreeOrEntry.h"
18+
#include "eden/fs/utils/PathFuncs.h"
1819

1920
namespace facebook::eden {
2021

@@ -239,6 +240,37 @@ class TreeInode final : public InodeBaseMetadata<DirContents> {
239240
InvalidationRequired invalidate,
240241
ObjectFetchContext& context);
241242

243+
/**
244+
* Internal method intended for removeRecursively to use. This method does not
245+
* flush invalidation so the caller won't see the up-to-date content after
246+
* return. Call EdenMount::flushInvalidations to ensure any changes to the
247+
* inode will be visible after it returns.
248+
*/
249+
ImmediateFuture<folly::Unit> removeRecursivelyNoFlushInvalidation(
250+
PathComponentPiece name,
251+
InvalidationRequired invalidate,
252+
ObjectFetchContext& context);
253+
254+
/**
255+
* Attempts to remove and unlink children of this inode. Under concurrent
256+
* modification, it is not guaranteed that TreeInode is empty after this
257+
* function returns.
258+
*/
259+
void removeAllChildrenRecursively(
260+
InvalidationRequired invalidate,
261+
ObjectFetchContext& context,
262+
const RenameLock& renameLock);
263+
264+
/**
265+
* For unloaded nodes, the removal should be simpler: remove the node
266+
* from entries and update the overlay.
267+
* If the return value is valid, the entry was not removed, and the child's
268+
* loaded inode was returned.
269+
*/
270+
InodePtr tryRemoveUnloadedChild(
271+
PathComponentPiece name,
272+
InvalidationRequired invalidate);
273+
242274
/**
243275
* Create a filesystem node.
244276
* Only unix domain sockets and regular files are supported; attempting to
@@ -635,8 +667,8 @@ class TreeInode final : public InodeBaseMetadata<DirContents> {
635667
* checkPreRemove() is called by tryRemoveChild() for file or directory
636668
* specific checks before unlinking an entry. Returns an errno value or 0.
637669
*/
638-
FOLLY_NODISCARD static int checkPreRemove(const TreeInodePtr& child);
639-
FOLLY_NODISCARD static int checkPreRemove(const FileInodePtr& child);
670+
FOLLY_NODISCARD static int checkPreRemove(const TreeInode& child);
671+
FOLLY_NODISCARD static int checkPreRemove(const FileInode& child);
640672

641673
/**
642674
* This helper function starts loading a currently unloaded child inode.

0 commit comments

Comments
 (0)