add new InodePtr classes

Summary:
This defines our own custom smart pointer type for Inode objects.  This will
provide us with more control over Inode lifetime, allowing us to decide if we
want to unload them immediately when they become unreferenced, or keep them
around for a while.

This will also allow us to fix some memory management issues around EdenMount
destruction.  Currently we destroy the EdenMount immediately when it is
unmounted.  This can cause issues if other parts of the code are still holding
references to Inode objects from this EdenMount.  Our custom InodePtr class
will also allow us to delay destroying the EdenMount until all of its Inodes
have been destroyed.

This diff adds the new pointer types and updates the code to use them, but does
not actually implement destroying unreferenced inodes yet.  The logic for that
has proven to be slightly subtle; I will split it out into its own separate
diff.

Reviewed By: wez

Differential Revision: D4351072

fbshipit-source-id: 7a9d81cbd226c9662a79a2f2ceda82fe2651f312
This commit is contained in:
Adam Simpkins 2017-01-17 15:01:37 -08:00 committed by Facebook Github Bot
parent 26c2526080
commit 3a73253057
18 changed files with 1052 additions and 119 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -626,7 +626,7 @@ WorkingCopyStatus getPathStatus(
try {
// Use getInodeBase() as a test of whether the path exists.
auto inodeBase = mount->getInodeBase(path);
if (std::dynamic_pointer_cast<FileInode>(inodeBase) != nullptr) {
if (inodeBase.asFilePtrOrNull() != nullptr) {
return WorkingCopyStatus::File;
} else {
return WorkingCopyStatus::Directory;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -10,6 +10,7 @@
#pragma once
#include <folly/Synchronized.h>
#include "eden/fs/inodes/DirstatePersistence.h"
#include "eden/fs/inodes/InodePtrFwd.h"
#include "eden/fs/inodes/gen-cpp2/overlay_types.h"
#include "eden/fs/model/Tree.h"
#include "eden/fs/service/gen-cpp2/EdenService.h"
@ -29,8 +30,6 @@ class ObjectStore;
class Tree;
class TreeInode;
using InodePtr = std::shared_ptr<InodeBase>;
namespace fusell {
class InodeBase;
class MountPoint;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -69,8 +69,7 @@ EdenMount::EdenMount(
auto snapshotID = config_->getSnapshotID();
TreeInodePtr rootInode;
if (rootOverlayDir) {
rootInode =
std::make_shared<TreeInode>(this, std::move(rootOverlayDir.value()));
rootInode = TreeInodePtr::makeNew(this, std::move(rootOverlayDir.value()));
} else {
// Note: We immediately wait on the Future returned by
// getTreeForCommit().
@ -79,7 +78,7 @@ EdenMount::EdenMount(
// the code slightly so that this is done in a helper function, before the
// EdenMount constructor is called.
auto rootTree = objectStore_->getTreeForCommit(snapshotID).get();
rootInode = std::make_shared<TreeInode>(this, std::move(rootTree));
rootInode = TreeInodePtr::makeNew(this, std::move(rootTree));
}
inodeMap_->setRootInode(rootInode);
@ -144,23 +143,11 @@ InodePtr EdenMount::getInodeBase(RelativePathPiece path) const {
}
TreeInodePtr EdenMount::getTreeInode(RelativePathPiece path) const {
auto inodeBase = getInodeBase(path);
auto treeInode = std::dynamic_pointer_cast<TreeInode>(inodeBase);
if (treeInode) {
return treeInode;
} else {
throw InodeError(ENOTDIR, inodeBase);
}
return getInodeBase(path).asTreePtr();
}
FileInodePtr EdenMount::getFileInode(RelativePathPiece path) const {
auto inodeBase = getInodeBase(path);
auto fileInode = std::dynamic_pointer_cast<FileInode>(inodeBase);
if (fileInode) {
return fileInode;
} else {
throw InodeError(EISDIR, inodeBase);
}
return getInodeBase(path).asFilePtr();
}
}
} // facebook::eden

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -11,6 +11,7 @@
#include <folly/Synchronized.h>
#include <memory>
#include "eden/fs/inodes/InodePtrFwd.h"
#include "eden/fs/journal/JournalDelta.h"
#include "eden/utils/PathFuncs.h"
@ -24,18 +25,11 @@ class BindMount;
class ClientConfig;
class Dirstate;
class EdenDispatcher;
class FileInode;
class InodeBase;
class InodeMap;
class ObjectStore;
class Overlay;
class Journal;
class Tree;
class TreeInode;
using InodePtr = std::shared_ptr<InodeBase>;
using TreeInodePtr = std::shared_ptr<TreeInode>;
using FileInodePtr = std::shared_ptr<FileInode>;
/**
* EdenMount contains all of the data about a specific eden mount point.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -165,8 +165,7 @@ folly::Future<std::shared_ptr<fusell::FileHandle>> FileInode::open(
data->materializeForRead(fi.flags, getPathBuggy(), overlay);
}
return std::make_shared<FileHandle>(
std::static_pointer_cast<FileInode>(shared_from_this()), data, fi.flags);
return std::make_shared<FileHandle>(inodePtrFromThis(), data, fi.flags);
}
std::shared_ptr<FileHandle> FileInode::finishCreate() {
@ -177,8 +176,7 @@ std::shared_ptr<FileHandle> FileInode::finishCreate() {
};
data->materializeForWrite(0, getPathBuggy(), getMount()->getOverlay());
return std::make_shared<FileHandle>(
std::static_pointer_cast<FileInode>(shared_from_this()), data, 0);
return std::make_shared<FileHandle>(inodePtrFromThis(), data, 0);
}
Future<vector<string>> FileInode::listxattr() {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -64,8 +64,17 @@ class FileInode : public InodeBase {
std::shared_ptr<FileData> getOrLoadData();
private:
/**
* Get a FileInodePtr to ourself.
*
* This uses FileInodePtr::newPtrFromExisting() internally.
*
* This should only be called in contexts where we know an external caller
* already has an existing reference to us. (Which is most places--a caller
* has to have a reference to us in order to call any of our APIs.)
*/
FileInodePtr inodePtrFromThis() {
return std::static_pointer_cast<FileInode>(shared_from_this());
return FileInodePtr::newPtrFromExisting(this);
}
/// Called as part of shutting down an open handle.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -24,7 +24,7 @@ namespace eden {
class EdenMount;
class TreeInode;
class InodeBase : public std::enable_shared_from_this<InodeBase> {
class InodeBase {
public:
/**
* Constructor for the root TreeInode of an EdenMount.
@ -172,6 +172,10 @@ class InodeBase : public std::enable_shared_from_this<InodeBase> {
}
private:
template <typename InodeType>
friend class InodePtrImpl;
friend class InodePtrTestHelper;
struct LocationInfo {
LocationInfo(TreeInodePtr p, PathComponentPiece n)
: parent(std::move(p)), name(n) {}
@ -196,6 +200,29 @@ class InodeBase : public std::enable_shared_from_this<InodeBase> {
bool getPathHelper(std::vector<PathComponent>& names, bool stopOnUnlinked)
const;
// incrementPtrRef() is called by InodePtr whenever an InodePtr is copied.
void incrementPtrRef() const {
auto prevValue = ptrRefcount_.fetch_add(1, std::memory_order_acq_rel);
// Calls to incrementPtrRef() are not allowed to increment the reference
// count from 0 to 1.
//
// The refcount is only allowed to go from 0 to 1 when holding the InodeMap
// lock or our parent TreeInode's contents lock. Those two situations call
// newInodeRefConstructed() instead
DCHECK_NE(0, prevValue);
}
// newInodeRefConstructed() is called any time we construct a brand new
// InodePtr in response to a request to access or load an Inode. The only
// APIs that hand out new InodePtrs are InodeMap::lookupInode() and
// TreeInode::getOrLoadChild().
void newInodeRefConstructed() const {
ptrRefcount_.fetch_add(1, std::memory_order_acq_rel);
}
void decrementPtrRef() const {
ptrRefcount_.fetch_sub(1, std::memory_order_acq_rel);
}
fuse_ino_t const ino_;
/**
@ -217,6 +244,58 @@ class InodeBase : public std::enable_shared_from_this<InodeBase> {
*/
std::atomic<uint32_t> numFuseReferences_{0};
/**
* A reference count used by InodePtr.
*
* A few notes about the refcount management:
*
* - Inode objects are not necessarily destroyed immediately when the
* refcount goes to 0. They may remain in memory for a while in case they
* get used again relatively soon. When necessary we can sweep the loaded
* inode objects and unload inodes whose refcount is 0 and who have not
* been accessed recently.
*
* - When copying or deleting InodePtr objects this reference count is
* updated atomically with acquire/release barriers. No other locks need
* to be held during these operations. The current thread is guaranteed to
* already hold a reference to the Inode in question since it already has
* an InodePtr. These operations can increment a refcount from 1 or more
* to a higher value, but they can never increment a refcount from 0 to 1.
* They can also decrement a refcount from 1 to 0.
*
* - Either the InodeMap lock or the parent TreeInode's contents lock is
* always held when incrementing the refcount from 0 to 1.
*
* Only two operations can inrement the refcount from 0 to 1:
* - InodeMap::lookupInode().
* This acquires the InodeMap lock
* - TreeInode::getOrLoadChild()
* This acquires the parent's TreeInode lock
*
* When checking to see if we can unload an inode, we acquire both it's
* parent TreeInode's contents lock and the InodeMap lock (in that order).
* We are therefore guaranteed that if the refcount is 0 when we check it,
* no other thread can increment it to 1 before we delete the object.
*
* Notes about owning vs non-owning pointers:
* - An Inode always holds an owning TreeInodePtr to its parent. This
* ensures the parent cannot be unloaded as long as it has any unloaded
* children.
*
* - The InodeMap stores raw (non-owning) pointers to the inodes. When an
* Inode is unloaded we explicitly inform the InodeMap of the change.
*
* - Each TreeInode holds raw (non-owning) pointers to its children. When an
* Inode is unloaded we explicitly reset its parent pointer to this object.
*
* - The numFuseReferences_ variable tracks the number of users that know
* about this inode by its inode number. However, this does not prevent us
* from destroying the Inode object. We can unload the Inode object itself
* in this case, and InodeMap will retain enough information to be able to
* re-create the Inode object later if this inode is looked up again.
*/
mutable std::atomic<uint32_t> ptrRefcount_{0};
/**
* Information about this Inode's location in the file system path.
* Eden does not support hard links, so each Inode has exactly one location.
@ -237,3 +316,5 @@ class InodeBase : public std::enable_shared_from_this<InodeBase> {
};
}
}
#include "eden/fs/inodes/InodePtr-defs.h"

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -14,16 +14,12 @@
#include <folly/Synchronized.h>
#include <memory>
#include <system_error>
#include "eden/fs/inodes/InodePtr.h"
#include "eden/utils/PathFuncs.h"
namespace facebook {
namespace eden {
class InodeBase;
class TreeInode;
using InodePtr = std::shared_ptr<InodeBase>;
using TreeInodePtr = std::shared_ptr<TreeInode>;
/**
* A subclass of std::system_error referring to a specific inode.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -45,7 +45,7 @@ void InodeMap::setRootInode(TreeInodePtr root) {
auto data = data_.wlock();
CHECK(!root_);
root_ = root;
auto ret = data->loadedInodes_.emplace(FUSE_ROOT_ID, root);
auto ret = data->loadedInodes_.emplace(FUSE_ROOT_ID, root.get());
CHECK(ret.second);
}
@ -63,9 +63,9 @@ Future<InodePtr> InodeMap::lookupInode(fuse_ino_t number) {
//
// This code path should be quite common, so it's better to perform
// makeFuture()'s memory allocation without the lock held.
InodePtr result = loadedIter->second;
auto result = InodePtr::newPtrLocked(loadedIter->second);
data.unlock();
return folly::makeFuture(result);
return folly::makeFuture<InodePtr>(result);
}
// Look up the data in the unloadedInodes_ map.
@ -112,7 +112,7 @@ Future<InodePtr> InodeMap::lookupInode(fuse_ino_t number) {
// We found a loaded parent.
// Grab copies of the arguments we need for startChildLookup(),
// with the lock still held.
InodePtr firstLoadedParent = loadedIter->second;
InodePtr firstLoadedParent = InodePtr::newPtrLocked(loadedIter->second);
PathComponent requiredChildName = unloadedData->name;
// Unlock the data before starting the child lookup
data.unlock();
@ -175,7 +175,7 @@ void InodeMap::startChildLookup(
const InodePtr& parent,
PathComponentPiece childName,
fuse_ino_t childInodeNumber) {
auto treeInode = std::dynamic_pointer_cast<TreeInode>(parent);
auto treeInode = parent.asTreePtrOrNull();
if (!treeInode) {
auto bug = EDEN_BUG() << "parent inode " << parent->getNodeId() << " of ("
<< childName << ", " << childInodeNumber
@ -218,7 +218,7 @@ void InodeMap::inodeLoadComplete(const InodePtr& inode) {
<< "load of inode " << number;
// Insert the entry into loadedInodes_, and remove it from unloadedInodes_
data->loadedInodes_.emplace(number, inode);
data->loadedInodes_.emplace(number, inode.get());
data->unloadedInodesReverse_.erase(reverseIter);
data->unloadedInodes_.erase(it);
} catch (const std::exception& ex) {
@ -262,23 +262,13 @@ InodeMap::PromiseVector InodeMap::extractPendingPromises(fuse_ino_t number) {
}
Future<TreeInodePtr> InodeMap::lookupTreeInode(fuse_ino_t number) {
return lookupInode(number).then([](const InodePtr& inode) {
auto tree = std::dynamic_pointer_cast<TreeInode>(inode);
if (!tree) {
throwSystemErrorExplicit(ENOTDIR);
}
return tree;
});
return lookupInode(number).then(
[](const InodePtr& inode) { return inode.asTreePtr(); });
}
Future<FileInodePtr> InodeMap::lookupFileInode(fuse_ino_t number) {
return lookupInode(number).then([](const InodePtr& inode) {
auto tree = std::dynamic_pointer_cast<FileInode>(inode);
if (!tree) {
throwSystemErrorExplicit(EISDIR);
}
return tree;
});
return lookupInode(number).then(
[](const InodePtr& inode) { return inode.asFilePtr(); });
}
InodePtr InodeMap::lookupLoadedInode(fuse_ino_t number) {
@ -287,7 +277,7 @@ InodePtr InodeMap::lookupLoadedInode(fuse_ino_t number) {
if (it == data->loadedInodes_.end()) {
return nullptr;
}
return it->second;
return InodePtr::newPtrLocked(it->second);
}
TreeInodePtr InodeMap::lookupLoadedTree(fuse_ino_t number) {
@ -295,11 +285,7 @@ TreeInodePtr InodeMap::lookupLoadedTree(fuse_ino_t number) {
if (!inode) {
return nullptr;
}
auto tree = std::dynamic_pointer_cast<TreeInode>(inode);
if (!tree) {
throwSystemErrorExplicit(ENOTDIR);
}
return tree;
return inode.asTreePtr();
}
FileInodePtr InodeMap::lookupLoadedFile(fuse_ino_t number) {
@ -307,11 +293,7 @@ FileInodePtr InodeMap::lookupLoadedFile(fuse_ino_t number) {
if (!inode) {
return nullptr;
}
auto file = std::dynamic_pointer_cast<FileInode>(inode);
if (!file) {
throwSystemErrorExplicit(EISDIR);
}
return file;
return inode.asFilePtr();
}
UnloadedInodeData InodeMap::lookupUnloadedInode(fuse_ino_t number) {
@ -413,7 +395,7 @@ void InodeMap::inodeCreated(const InodePtr& inode) {
VLOG(4) << "created new inode " << inode->getNodeId() << ": "
<< inode->getLogPath();
auto data = data_.wlock();
data->loadedInodes_.emplace(inode->getNodeId(), inode);
data->loadedInodes_.emplace(inode->getNodeId(), inode.get());
}
fuse_ino_t InodeMap::getOrAllocateUnloadedInodeNumber(

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -365,12 +365,12 @@ class InodeMap {
/**
* The map of loaded inodes
*
* TODO: When we switch to our own custom InodePtr implementation,
* this map should eventually store raw pointers, and not hold a reference
* to the inodes. The InodeMap itself should not force Inode objects to
* remain in existence forever.
* This map stores raw pointers rather than InodePtr objects. The InodeMap
* itself does not hold a reference to the Inode objects. When an Inode is
* looked up the InodeMap will wrap the Inode in an InodePtr so that the
* caller acquires a reference.
*/
std::unordered_map<fuse_ino_t, InodePtr> loadedInodes_;
std::unordered_map<fuse_ino_t, InodeBase*> loadedInodes_;
/**
* The map of currently unloaded inodes

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2017, 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.
*
*/
#pragma once
/*
* This file contains definitions of a few simple and commonly called
* InodePtrImpl methods. It is useful for callers to see the definition of
* these methods so they can be inlined.
*
* This file is included automatically by InodeBase.h
*/
#include "InodePtr.h"
#include "InodeBase.h"
namespace facebook {
namespace eden {
template <typename InodeType>
InodePtrImpl<InodeType>::InodePtrImpl(
InodeType* value,
NormalIncrementEnum) noexcept
: value_(value) {
if (value_) {
value_->incrementPtrRef();
}
}
template <typename InodeType>
InodePtrImpl<InodeType>::InodePtrImpl(
InodeType* value,
LockedIncrementEnum) noexcept
: value_(value) {
// We don't check for value_ == nullptr here.
// The caller should always ensure the argument is non-null for this call.
value_->newInodeRefConstructed();
}
template <typename InodeType>
void InodePtrImpl<InodeType>::incref() {
if (value_) {
value_->incrementPtrRef();
}
}
template <typename InodeType>
void InodePtrImpl<InodeType>::decref() {
if (value_) {
value_->decrementPtrRef();
}
}
}
}

162
eden/fs/inodes/InodePtr.cpp Normal file
View File

@ -0,0 +1,162 @@
/*
* Copyright (c) 2017, 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/InodePtr.h"
#include "eden/fs/inodes/InodePtr-defs.h"
#include "eden/fs/inodes/FileInode.h"
#include "eden/fs/inodes/InodeBase.h"
#include "eden/fs/inodes/InodeError.h"
#include "eden/fs/inodes/TreeInode.h"
namespace facebook {
namespace eden {
template <typename InodeType>
template <typename SubclassRawPtrType>
SubclassRawPtrType InodeBasePtrImpl<InodeType>::asSubclass(
int errnoValue) const {
if (this->value_ == nullptr) {
return nullptr;
}
auto* subclassPtr = dynamic_cast<SubclassRawPtrType>(this->value_);
if (subclassPtr == nullptr) {
throw InodeError(errnoValue, *this);
}
return subclassPtr;
}
template <typename InodeType>
template <typename SubclassPtrType>
SubclassPtrType InodeBasePtrImpl<InodeType>::asSubclassPtr(
int errnoValue) const {
if (this->value_ == nullptr) {
return SubclassPtrType{};
}
auto* subclassPtr =
dynamic_cast<typename SubclassPtrType::InodeType*>(this->value_);
if (subclassPtr == nullptr) {
throw InodeError(errnoValue, *this);
}
return SubclassPtrType{subclassPtr, SubclassPtrType::NORMAL_INCREMENT};
}
template <typename InodeType>
template <typename SubclassPtrType>
SubclassPtrType InodeBasePtrImpl<InodeType>::extractSubclassPtr(
int errnoValue) {
if (this->value_ == nullptr) {
return SubclassPtrType{};
}
auto* subclassPtr =
dynamic_cast<typename SubclassPtrType::InodeType*>(this->value_);
if (subclassPtr == nullptr) {
throw InodeError(errnoValue, *this);
}
this->value_ = nullptr;
return SubclassPtrType{subclassPtr, SubclassPtrType::NO_INCREMENT};
}
template <typename InodeType>
template <typename SubclassPtrType>
SubclassPtrType InodeBasePtrImpl<InodeType>::extractSubclassPtrOrNull() {
if (this->value_ == nullptr) {
return SubclassPtrType{};
}
auto* subclassPtr =
dynamic_cast<typename SubclassPtrType::InodeType*>(this->value_);
if (subclassPtr == nullptr) {
return SubclassPtrType{};
}
this->value_ = nullptr;
return SubclassPtrType{subclassPtr, SubclassPtrType::NO_INCREMENT};
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::FileInode*
InodeBasePtrImpl<InodeType>::asFile() const {
return asSubclass<FileInodeRawPtr>(EISDIR);
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::FileInodePtr
InodeBasePtrImpl<InodeType>::asFilePtr() const& {
return asSubclassPtr<FileInodePtr>(EISDIR);
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::FileInodePtr
InodeBasePtrImpl<InodeType>::asFilePtr()&& {
return extractSubclassPtr<FileInodePtr>(EISDIR);
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::FileInode*
InodeBasePtrImpl<InodeType>::asFileOrNull() const {
return dynamic_cast<FileInodeRawPtr>(this->value_);
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::FileInodePtr
InodeBasePtrImpl<InodeType>::asFilePtrOrNull() const& {
return FileInodePtr{dynamic_cast<FileInodeRawPtr>(this->value_),
FileInodePtr::NORMAL_INCREMENT};
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::FileInodePtr
InodeBasePtrImpl<InodeType>::asFilePtrOrNull()&& {
return extractSubclassPtrOrNull<FileInodePtr>();
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::TreeInode*
InodeBasePtrImpl<InodeType>::asTree() const {
return asSubclass<TreeInodeRawPtr>(ENOTDIR);
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::TreeInodePtr
InodeBasePtrImpl<InodeType>::asTreePtr() const& {
return asSubclassPtr<TreeInodePtr>(ENOTDIR);
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::TreeInodePtr
InodeBasePtrImpl<InodeType>::asTreePtr()&& {
return extractSubclassPtr<TreeInodePtr>(ENOTDIR);
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::TreeInode*
InodeBasePtrImpl<InodeType>::asTreeOrNull() const {
return dynamic_cast<TreeInodeRawPtr>(this->value_);
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::TreeInodePtr
InodeBasePtrImpl<InodeType>::asTreePtrOrNull() const& {
return TreeInodePtr{dynamic_cast<TreeInodeRawPtr>(this->value_),
TreeInodePtr::NORMAL_INCREMENT};
}
template <typename InodeType>
typename detail::InodePtrTraits<InodeType>::TreeInodePtr
InodeBasePtrImpl<InodeType>::asTreePtrOrNull()&& {
return extractSubclassPtrOrNull<TreeInodePtr>();
}
// Explicitly instantiate InodePtrImpl for all inode class types
template class InodeBasePtrImpl<InodeBase>;
template class InodePtrImpl<FileInode>;
template class InodePtrImpl<TreeInode>;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -9,23 +9,349 @@
*/
#pragma once
#include <memory>
#include "InodePtrFwd.h"
#include <glog/logging.h>
#include <cstddef>
#include <utility>
namespace facebook {
namespace eden {
class InodeBase;
class TreeInode;
class FileInode;
/**
* A custom smart pointer class for pointing to Inode objects.
*
* This maintains a reference count similar to std::shared_ptr. However, we do
* slightly more custom management of Inode ownership. Inodes are not
* immediately destroyed when their reference count drops to 0. Instead they
* simply become available to unload
*/
template <typename InodeTypeParam>
class InodePtrImpl {
public:
using InodeType = InodeTypeParam;
constexpr InodePtrImpl() noexcept {}
constexpr /* implicit */ InodePtrImpl(std::nullptr_t) noexcept {}
~InodePtrImpl() {
decref();
}
/*
* Copy/move constructors and assignment operators
*/
InodePtrImpl(const InodePtrImpl& other) noexcept : value_(other.value_) {
incref();
}
InodePtrImpl(InodePtrImpl&& other) noexcept {
value_ = other.value_;
other.value_ = nullptr;
}
InodePtrImpl& operator=(const InodePtrImpl& other) noexcept {
// Only update reference counts if we are not already pointing at the
// desired Inode.
//
// This handles self assignment, and is necessary to ensure that we do not
// decrement our Inode's reference count to 0 in the middle of self
// assignment.
if (value_ != other.value_) {
decref();
value_ = other.value_;
incref();
}
return *this;
}
InodePtrImpl& operator=(InodePtrImpl&& other) noexcept {
// The C++ standard says that move self-assignment is undefined.
// http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#1204
//
// Make sure our callers never try to move assign an InodePtr from itself.
DCHECK_NE(this, &other);
decref();
value_ = other.value_;
other.value_ = nullptr;
return *this;
}
/*
* Templated constructors and assignment operators.
*
* These support:
* - construction of InodePtr<InodeBase> from inode subclasses like
* InodePtr<FileInode>
* - construction of InodePtr<InodeType> from InodePtr<const InodeType>
*/
template <typename Inode>
/* implicit */ InodePtrImpl(const InodePtrImpl<Inode>& other) noexcept
: value_(other.value_) {
incref();
}
template <typename Inode>
/* implicit */ InodePtrImpl(InodePtrImpl<Inode>&& other) noexcept {
value_ = other.value_;
other.value_ = nullptr;
}
template <typename Inode>
InodePtrImpl& operator=(const InodePtrImpl<Inode>& other) noexcept {
if (value_ != other.value_) {
decref();
value_ = other.value_;
incref();
}
return *this;
}
template <typename Inode>
InodePtrImpl& operator=(InodePtrImpl<Inode>&& other) noexcept {
DCHECK_NE(this, &other);
decref();
value_ = other.value_;
other.value_ = nullptr;
return *this;
}
/**
* Explicit boolean conversion.
*
* Returns !isNull()
*/
explicit operator bool() const {
return value_ != nullptr;
}
InodeType* get() const {
return value_;
}
InodeType* operator->() const {
return value_;
}
InodeType& operator*() const {
return *value_;
}
void reset() {
decref();
value_ = nullptr;
}
/**
* An API for InodeMap::lookupInode() and TreeInode::getOrLoadChild() to use
* when constructing new InodePtr objects to return to callers.
*
* This API should only be used by these two call sites. All other callers
* should use one of those two APIs (or their related helper functions) to
* obtain InodePtrs
*
* This API should only be called when holding the InodeMap lock, or the
* parent TreeInode's contents lock.
*/
static InodePtrImpl newPtrLocked(InodeType* value) noexcept {
return InodePtrImpl{value, LOCKED_INCREMENT};
}
/**
* An API for TreeInode to use to construct an InodePtr from itself in order
* to give to new children inodes that it creates.
*
* It should always be the case that the caller (the one asking the TreeInode
* for its child) already has an existing reference to the TreeInode.
* Therefore the TreeInode's refcount should already be at least 1, and this
* API should never cause a refcount transition from 0 to 1.
*/
static InodePtrImpl newPtrFromExisting(InodeType* value) noexcept {
return InodePtrImpl{value, NORMAL_INCREMENT};
}
template <typename... Args>
static InodePtrImpl makeNew(Args&&... args) {
auto* inode = new InodeType(std::forward<Args>(args)...);
return InodePtrImpl{inode, LOCKED_INCREMENT};
}
protected:
template <typename OtherInodeType>
friend class InodePtrImpl;
template <typename OtherInodeType>
friend class InodeBasePtrImpl;
enum NoIncrementEnum { NO_INCREMENT };
enum NormalIncrementEnum { NORMAL_INCREMENT };
enum LockedIncrementEnum { LOCKED_INCREMENT };
// Protected constructors for internal use.
InodePtrImpl(InodeType* value, NormalIncrementEnum) noexcept;
InodePtrImpl(InodeType* value, LockedIncrementEnum) noexcept;
InodePtrImpl(InodeType* value, NoIncrementEnum) noexcept : value_(value) {}
void incref();
void decref();
InodeType* value_{nullptr};
};
namespace detail {
// Helper class so that InodePtr and InodeBasePtr can return
// FileInodePtr vs ConstFileInodePtr appropriately, and similarly for TreeInode
// pointers.
template <typename BaseType>
struct InodePtrTraits;
template <>
struct InodePtrTraits<InodeBase> {
using FileInode = ::facebook::eden::FileInode;
using FileInodePtr = ::facebook::eden::FileInodePtr;
using TreeInode = ::facebook::eden::TreeInode;
using TreeInodePtr = ::facebook::eden::TreeInodePtr;
};
template <>
struct InodePtrTraits<const InodeBase> {
using FileInode = const ::facebook::eden::FileInode;
using FileInodePtr = ConstFileInodePtr;
using TreeInode = const ::facebook::eden::TreeInode;
using TreeInodePtr = ConstTreeInodePtr;
};
}
/**
* An InodePtr pointing to InodeBase.
*
* This derives from the generic InodePtrImpl class, and adds a few methods for
* converting to specific Inode subclasses.
*/
template <typename InodeTypeParam>
class InodeBasePtrImpl : public InodePtrImpl<InodeTypeParam> {
public:
using InodeType = InodeTypeParam;
using FileInodeRawPtr =
typename detail::InodePtrTraits<InodeTypeParam>::FileInode*;
using FileInodePtr =
typename detail::InodePtrTraits<InodeTypeParam>::FileInodePtr;
using TreeInodeRawPtr =
typename detail::InodePtrTraits<InodeTypeParam>::TreeInode*;
using TreeInodePtr =
typename detail::InodePtrTraits<InodeTypeParam>::TreeInodePtr;
/* Inherit all of our parent class's constructors */
using InodePtrImpl<InodeType>::InodePtrImpl;
/**
* Convert this InodePtr to a FileInodePtr.
*
* Throws EISDIR if this points to a TreeInode instead of a FileInode.
* Returns a null FileInodePtr if this pointer is null.
*/
FileInodeRawPtr asFile() const;
FileInodePtr asFilePtr() const&;
/**
* Extract the pointer from this InodePtr and put it in a FileInodePtr
* object. This InodePtr is reset to null.
*
* Throws EISDIR if this points to a TreeInode instead of a FileInode.
* On error this InodePtr object will be left unchanged (it is not reset to
* null).
*/
FileInodePtr asFilePtr() &&;
/**
* Convert this InodePtr to a FileInodePtr.
*
* Returns a null pointer if this points to a TreeInode instead of a
* FileInode.
*/
FileInodeRawPtr asFileOrNull() const;
FileInodePtr asFilePtrOrNull() const&;
/**
* Extract the pointer from this InodePtr and put it in a FileInodePtr
* object. This InodePtr is reset to null.
*
* Returns null if this points to a TreeInode instead of a FileInode.
* On error this InodePtr object will be left unchanged (it is not reset to
* null).
*/
FileInodePtr asFilePtrOrNull() &&;
/**
* Convert this InodePtr to a TreeInodePtr.
*
* Throws ENOTDIR if this points to a FileInode instead of a TreeInode.
* Returns a null TreeInodePtr if this pointer is null.
*/
TreeInodeRawPtr asTree() const;
TreeInodePtr asTreePtr() const&;
TreeInodePtr asTreePtr() &&;
/**
* Convert this InodePtr to a TreeInodePtr.
*
* Returns a null pointer if this points to a FileInode instead of a
* TreeInode.
*/
TreeInodeRawPtr asTreeOrNull() const;
TreeInodePtr asTreePtrOrNull() const&;
TreeInodePtr asTreePtrOrNull() &&;
private:
template <typename SubclassRawPtrType>
SubclassRawPtrType asSubclass(int errnoValue) const;
template <typename SubclassPtrType>
SubclassPtrType asSubclassPtr(int errnoValue) const;
template <typename SubclassPtrType>
SubclassPtrType extractSubclassPtr(int errnoValue);
template <typename SubclassPtrType>
SubclassPtrType extractSubclassPtrOrNull();
};
/*
* Pointer aliases.
* Eventually we will likely need to replace these with a custom time that
* gives us finer-grained control over reference counting and object
* destruction.
* Operators to compare InodePtr types
*/
using InodePtr = std::shared_ptr<InodeBase>;
using TreeInodePtr = std::shared_ptr<TreeInode>;
using FileInodePtr = std::shared_ptr<FileInode>;
template <typename T, typename U>
bool operator==(const InodePtrImpl<T>& a, const InodePtrImpl<U>& b) {
return a.get() == b.get();
}
template <typename T, typename U>
bool operator!=(const InodePtrImpl<T>& a, const InodePtrImpl<U>& b) {
return a.get() != b.get();
}
template <typename T, typename U>
bool operator<(const InodePtrImpl<T>& a, const InodePtrImpl<U>& b) {
return a.get() < b.get();
}
template <typename T, typename U>
bool operator<=(const InodePtrImpl<T>& a, const InodePtrImpl<U>& b) {
return a.get() <= b.get();
}
template <typename T, typename U>
bool operator>(const InodePtrImpl<T>& a, const InodePtrImpl<U>& b) {
return a.get() > b.get();
}
template <typename T, typename U>
bool operator>=(const InodePtrImpl<T>& a, const InodePtrImpl<U>& b) {
return a.get() >= b.get();
}
/*
* Operators to compare InodePtrImpl with nullptr
*/
template <typename InodeTypeParam>
bool operator==(const InodePtrImpl<InodeTypeParam>& ptr, std::nullptr_t) {
return !bool(ptr);
}
template <typename InodeTypeParam>
bool operator!=(const InodePtrImpl<InodeTypeParam>& ptr, std::nullptr_t) {
return bool(ptr);
}
template <typename InodeTypeParam>
bool operator==(std::nullptr_t, const InodePtrImpl<InodeTypeParam>& ptr) {
return !bool(ptr);
}
template <typename InodeTypeParam>
bool operator!=(std::nullptr_t, const InodePtrImpl<InodeTypeParam>& ptr) {
return bool(ptr);
}
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2017, 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.
*
*/
#pragma once
/*
* This file contains forward declarations of InodePtr and related types
*/
namespace facebook {
namespace eden {
class FileInode;
class InodeBase;
class TreeInode;
template <typename InodeType>
class InodePtrImpl;
template <typename InodeType>
class InodeBasePtrImpl;
/*
* Friendly names for the various InodePtr classes.
*/
using FileInodePtr = InodePtrImpl<FileInode>;
using TreeInodePtr = InodePtrImpl<TreeInode>;
using InodePtr = InodeBasePtrImpl<InodeBase>;
using ConstFileInodePtr = InodePtrImpl<const FileInode>;
using ConstTreeInodePtr = InodePtrImpl<const TreeInode>;
using ConstInodePtr = InodeBasePtrImpl<const InodeBase>;
}
}

View File

@ -105,7 +105,7 @@ Future<InodePtr> TreeInode::getOrLoadChild(PathComponentPiece name) {
// Check to see if the entry is already loaded
auto& entryPtr = iter->second;
if (entryPtr->inode) {
return makeFuture(entryPtr->inode->shared_from_this());
return makeFuture<InodePtr>(InodePtr::newPtrLocked(entryPtr->inode));
}
// The entry is not loaded yet. Ask the InodeMap about the entry.
@ -141,7 +141,7 @@ Future<InodePtr> TreeInode::getOrLoadChild(PathComponentPiece name) {
Future<TreeInodePtr> TreeInode::getOrLoadChildTree(PathComponentPiece name) {
return getOrLoadChild(name).then([](InodePtr child) {
auto treeInode = std::dynamic_pointer_cast<TreeInode>(child);
auto treeInode = child.asTreePtrOrNull();
if (!treeInode) {
return makeFuture<TreeInodePtr>(InodeError(ENOTDIR, child));
}
@ -270,11 +270,7 @@ Future<InodePtr> TreeInode::startLoadingInode(
// Eventually we may want to go ahead start loading some of the blob data
// now, but we don't have to wait for it to be ready before marking the
// inode loaded.
auto inode = std::make_shared<FileInode>(
number,
std::static_pointer_cast<TreeInode>(shared_from_this()),
name,
entry);
auto inode = FileInodePtr::makeNew(number, inodePtrFromThis(), name, entry);
return makeFuture<InodePtr>(inode);
}
@ -289,12 +285,12 @@ Future<InodePtr> TreeInode::startLoadingInode(
if (!entry->materialized && entry->hash) {
return getStore()->getTreeFuture(entry->hash.value()).then([
self = std::static_pointer_cast<TreeInode>(shared_from_this()),
self = inodePtrFromThis(),
childName = PathComponent{name},
entry,
number
](std::unique_ptr<Tree> tree)->InodePtr {
return std::make_shared<TreeInode>(
return TreeInodePtr::makeNew(
number, self, childName, entry, std::move(tree));
});
}
@ -308,7 +304,7 @@ Future<InodePtr> TreeInode::startLoadingInode(
auto targetName = getPathBuggy() + name;
auto overlayDir = getOverlay()->loadOverlayDir(targetName);
DCHECK(overlayDir) << "missing overlay for " << targetName;
return std::make_shared<TreeInode>(
return TreeInodePtr::makeNew(
number, inodePtrFromThis(), name, entry, std::move(overlayDir.value()));
}
@ -442,7 +438,7 @@ TreeInode::create(PathComponentPiece name, mode_t mode, int flags) {
auto childNumber = inodeMap->allocateInodeNumber();
// build a corresponding FileInode
inode = std::make_shared<FileInode>(
inode = FileInodePtr::makeNew(
childNumber,
this->inodePtrFromThis(),
name,
@ -520,7 +516,7 @@ TreeInodePtr TreeInode::mkdir(PathComponentPiece name, mode_t mode) {
// Create the TreeInode
auto* inodeMap = this->getInodeMap();
auto childNumber = inodeMap->allocateInodeNumber();
newChild = std::make_shared<TreeInode>(
newChild = TreeInodePtr::makeNew(
childNumber,
this->inodePtrFromThis(),
name,
@ -666,12 +662,12 @@ folly::Future<folly::Unit> TreeInode::rmdirImpl(
return self->rmdirImpl(childName, child, attemptNum + 1);
});
} else {
// Just update to point to the current child
InodePtr childGeneric = ent->inode->shared_from_this();
child = std::dynamic_pointer_cast<TreeInode>(childGeneric);
if (!child) {
throw InodeError(ENOTDIR, childGeneric);
// Just update to point to the current child, if it is still a tree
auto* currentChildTree = dynamic_cast<TreeInode*>(ent->inode);
if (!currentChildTree) {
throw InodeError(ENOTDIR, inodePtrFromThis(), name);
}
child = TreeInodePtr::newPtrLocked(currentChildTree);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -220,8 +220,17 @@ class TreeInode : public InodeBase {
* used to track the directory in the inode */
static Dir buildDirFromTree(const Tree* tree);
/**
* Get a TreeInodePtr to ourself.
*
* This uses TreeInodePtr::newPtrFromExisting() internally.
*
* This should only be called in contexts where we know an external caller
* already has an existing reference to us. (Which is most places--a caller
* has to have a reference to us in order to call any of our APIs.)
*/
TreeInodePtr inodePtrFromThis() {
return std::static_pointer_cast<TreeInode>(shared_from_this());
return TreeInodePtr::newPtrFromExisting(this);
}
folly::Future<folly::Unit>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, Facebook, Inc.
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
@ -32,10 +32,10 @@ TEST(InodeBase, getPath) {
return parent->getChildByName(PathComponentPiece{name}).get();
};
auto childTree = [&getChild](const TreeInodePtr& parent, StringPiece name) {
return dynamic_pointer_cast<TreeInode>(getChild(parent, name));
return getChild(parent, name).asTreePtr();
};
auto childFile = [&getChild](const TreeInodePtr& parent, StringPiece name) {
return dynamic_pointer_cast<FileInode>(getChild(parent, name));
return getChild(parent, name).asFilePtr();
};
auto a = childTree(root, "a");

View File

@ -0,0 +1,295 @@
/*
* Copyright (c) 2017, 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 <gtest/gtest.h>
#include "eden/fs/inodes/FileInode.h"
#include "eden/fs/inodes/InodeBase.h"
#include "eden/fs/inodes/InodePtr.h"
#include "eden/fs/inodes/TreeInode.h"
#include "eden/fs/testharness/TestMount.h"
#include "eden/fs/testharness/TestUtil.h"
#include "eden/utils/test/TestChecks.h"
using namespace facebook::eden;
namespace facebook {
namespace eden {
/*
* InodePtrTestHelper is declared as a friend by InodeBase.
*
* We can use this class to contain any functions that need to access private
* inode state for the purpose of our tests.
*/
class InodePtrTestHelper {
public:
template <typename InodePtrType>
static uint32_t getRefcount(const InodePtrType& inode) {
return inode->ptrRefcount_.load(std::memory_order_acquire);
}
};
}
}
#define EXPECT_REFCOUNT(expected, inodePtr) \
EXPECT_EQ(expected, InodePtrTestHelper::getRefcount(inodePtr))
TEST(InodePtr, constructionAndAssignment) {
TestMountBuilder builder;
auto testMount = builder.build();
// Get the root inode
auto rootPtr = testMount->getEdenMount()->getRootInode();
// The refcount for the root should be 2:
// - The InodeMap keeps 1 reference to the root inode
// - We got a second reference
EXPECT_REFCOUNT(2, rootPtr);
EXPECT_TRUE(rootPtr);
{
// Construction through newPtrFromExisting()
auto ptr2 = TreeInodePtr::newPtrFromExisting(rootPtr.get());
EXPECT_REFCOUNT(3, rootPtr);
EXPECT_EQ(rootPtr.get(), ptr2.get());
// reset()
ptr2.reset();
EXPECT_REFCOUNT(2, rootPtr);
EXPECT_FALSE(ptr2);
}
{
// Copy construction
auto ptr2 = rootPtr;
EXPECT_REFCOUNT(3, rootPtr);
EXPECT_EQ(rootPtr.get(), ptr2.get());
}
// Decrement via destruction
EXPECT_REFCOUNT(2, rootPtr);
{
// Default construction, then copy assignment
TreeInodePtr ptr2;
EXPECT_FALSE(ptr2);
EXPECT_REFCOUNT(2, rootPtr);
ptr2 = rootPtr;
EXPECT_REFCOUNT(3, rootPtr);
EXPECT_TRUE(ptr2);
EXPECT_EQ(rootPtr.get(), ptr2.get());
// Move construction
TreeInodePtr ptr3{std::move(ptr2)};
EXPECT_REFCOUNT(3, rootPtr);
EXPECT_EQ(rootPtr.get(), ptr3.get());
EXPECT_TRUE(ptr3);
EXPECT_TRUE(nullptr == ptr2.get());
EXPECT_FALSE(ptr2);
// Move assignment
ptr2 = std::move(ptr3);
EXPECT_REFCOUNT(3, rootPtr);
EXPECT_EQ(rootPtr.get(), ptr2.get());
EXPECT_TRUE(ptr2);
EXPECT_TRUE(nullptr == ptr3.get());
EXPECT_FALSE(ptr3);
// Try move assigning to the value it already points to
// This effectively decrements the refcount since the right-hand side gets
// reset but the left-hand side of the assignment stays the same.
ptr3 = rootPtr;
EXPECT_REFCOUNT(4, rootPtr);
ptr2 = std::move(ptr3);
EXPECT_REFCOUNT(3, rootPtr);
EXPECT_FALSE(ptr3);
EXPECT_TRUE(ptr2);
EXPECT_EQ(rootPtr.get(), ptr2.get());
}
EXPECT_REFCOUNT(2, rootPtr);
{
// Copy assignment from null
// First set ptr2 to non-null
TreeInodePtr ptr2 = rootPtr;
EXPECT_REFCOUNT(3, rootPtr);
EXPECT_TRUE(ptr2);
TreeInodePtr nullTreePtr;
ptr2 = nullTreePtr;
EXPECT_REFCOUNT(2, rootPtr);
EXPECT_FALSE(ptr2);
EXPECT_FALSE(nullTreePtr);
// Move assignment from null
// First set ptr2 to non-null
ptr2 = rootPtr;
EXPECT_REFCOUNT(3, rootPtr);
EXPECT_TRUE(ptr2);
ptr2 = std::move(nullTreePtr);
EXPECT_REFCOUNT(2, rootPtr);
EXPECT_FALSE(ptr2);
EXPECT_FALSE(nullTreePtr);
// Copy construction from null
TreeInodePtr ptr4{nullTreePtr};
EXPECT_REFCOUNT(2, rootPtr);
EXPECT_FALSE(ptr4);
// Move construction from null
TreeInodePtr ptr5{std::move(nullTreePtr)};
EXPECT_REFCOUNT(2, rootPtr);
EXPECT_FALSE(ptr5);
}
EXPECT_REFCOUNT(2, rootPtr);
}
TEST(InodePtr, baseConstructionAndAssignment) {
TestMountBuilder builder;
auto testMount = builder.build();
auto rootPtr = testMount->getEdenMount()->getRootInode();
EXPECT_REFCOUNT(2, rootPtr);
// Construct an InodePtr from TreeInodePtr
InodePtr basePtr = rootPtr;
EXPECT_REFCOUNT(3, rootPtr);
EXPECT_EQ(rootPtr.get(), basePtr.get());
EXPECT_TRUE(basePtr);
{
// Move construction from TreeInodePtr
TreeInodePtr root2 = rootPtr;
EXPECT_REFCOUNT(4, rootPtr);
EXPECT_EQ(rootPtr.get(), root2.get());
InodePtr basePtr2(std::move(root2));
EXPECT_REFCOUNT(4, rootPtr);
EXPECT_EQ(rootPtr.get(), basePtr2.get());
EXPECT_TRUE(basePtr2);
EXPECT_FALSE(root2);
// Copy assignment from TreeInodePtr
InodePtr basePtr3;
EXPECT_FALSE(basePtr3);
basePtr3 = rootPtr;
EXPECT_TRUE(basePtr3);
EXPECT_TRUE(rootPtr);
EXPECT_EQ(rootPtr.get(), basePtr3.get());
EXPECT_REFCOUNT(5, rootPtr);
// Move assignment from TreeInodePtr
basePtr3.reset();
EXPECT_REFCOUNT(4, rootPtr);
root2 = rootPtr;
EXPECT_REFCOUNT(5, rootPtr);
EXPECT_FALSE(basePtr3);
basePtr3 = std::move(root2);
EXPECT_TRUE(basePtr3);
EXPECT_FALSE(root2);
EXPECT_REFCOUNT(5, rootPtr);
// Try move assigning to the value it already points to
root2 = rootPtr;
EXPECT_REFCOUNT(6, rootPtr);
basePtr3 = std::move(root2);
EXPECT_REFCOUNT(5, rootPtr);
EXPECT_FALSE(root2);
EXPECT_TRUE(basePtr3);
EXPECT_EQ(rootPtr.get(), basePtr3.get());
}
EXPECT_REFCOUNT(3, rootPtr);
}
TEST(InodePtr, baseCasting) {
TestMountBuilder builder;
auto testMount = builder.build();
auto rootPtr = testMount->getEdenMount()->getRootInode();
EXPECT_REFCOUNT(2, rootPtr);
// Construct an InodePtr from TreeInodePtr
InodePtr basePtr = rootPtr;
EXPECT_REFCOUNT(3, rootPtr);
// Test the various asTree* methods
{
// Raw pointer versions
EXPECT_EQ(rootPtr.get(), basePtr.get());
EXPECT_EQ(rootPtr.get(), basePtr.asTree());
EXPECT_EQ(rootPtr.get(), basePtr.asTreeOrNull());
EXPECT_REFCOUNT(3, rootPtr);
}
{
// Copy versions
auto rawPtr = basePtr.asTree();
auto rawPtr2 = basePtr.asTreeOrNull();
EXPECT_REFCOUNT(3, rootPtr);
auto ptr2 = basePtr.asTreePtr();
EXPECT_TRUE(basePtr);
EXPECT_EQ(rootPtr.get(), basePtr.get());
EXPECT_EQ(rootPtr.get(), ptr2.get());
EXPECT_TRUE(ptr2);
EXPECT_REFCOUNT(4, rootPtr);
auto ptr3 = basePtr.asTreePtrOrNull();
EXPECT_REFCOUNT(5, rootPtr);
}
EXPECT_REFCOUNT(3, rootPtr);
{
// Move versions
auto base2 = basePtr;
EXPECT_REFCOUNT(4, rootPtr);
auto ptr2 = std::move(base2).asTreePtr();
EXPECT_REFCOUNT(4, rootPtr);
EXPECT_FALSE(base2);
EXPECT_EQ(rootPtr.get(), ptr2.get());
ptr2.reset();
EXPECT_REFCOUNT(3, rootPtr);
base2 = basePtr;
EXPECT_REFCOUNT(4, rootPtr);
ptr2 = std::move(base2).asTreePtrOrNull();
EXPECT_REFCOUNT(4, rootPtr);
EXPECT_FALSE(base2);
EXPECT_EQ(rootPtr.get(), ptr2.get());
}
EXPECT_REFCOUNT(3, rootPtr);
// Test the various asFile* methods
{
// Raw pointer versions
EXPECT_EQ(rootPtr.get(), basePtr.get());
EXPECT_THROW_ERRNO(basePtr.asFile(), EISDIR);
EXPECT_TRUE(nullptr == basePtr.asFileOrNull());
EXPECT_REFCOUNT(3, rootPtr);
auto rawPtr = basePtr.asFileOrNull();
EXPECT_TRUE(nullptr == rawPtr);
EXPECT_REFCOUNT(3, rootPtr);
}
{
// Copy versions
EXPECT_THROW_ERRNO(basePtr.asFile(), EISDIR);
EXPECT_THROW_ERRNO(basePtr.asFilePtr(), EISDIR);
EXPECT_REFCOUNT(3, rootPtr);
auto filePtr = basePtr.asFilePtrOrNull();
EXPECT_FALSE(filePtr);
EXPECT_REFCOUNT(3, rootPtr);
}
{
// Move versions
auto base2 = basePtr;
EXPECT_REFCOUNT(4, rootPtr);
EXPECT_THROW_ERRNO(std::move(base2).asFile(), EISDIR);
EXPECT_TRUE(base2);
EXPECT_REFCOUNT(4, rootPtr);
EXPECT_THROW_ERRNO(std::move(base2).asFilePtr(), EISDIR);
EXPECT_TRUE(base2);
EXPECT_REFCOUNT(4, rootPtr);
auto filePtr = std::move(base2).asFilePtrOrNull();
EXPECT_FALSE(filePtr);
EXPECT_TRUE(base2);
EXPECT_REFCOUNT(4, rootPtr);
}
EXPECT_REFCOUNT(3, rootPtr);
}