@@ -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}
0 commit comments