Explicitly closing all directory file descriptors (#10049)

Summary:
Currently, the DB directory file descriptor is left open until the deconstruction process (`DB::Close()` does not close the file descriptor). To verify this, comment out the lines between `db_ = nullptr` and `db_->Close()` (line 512, 513, 514, 515 in ldb_cmd.cc) to leak the ``db_'' object, build `ldb` tool and run
```
strace --trace=open,openat,close ./ldb --db=$TEST_TMPDIR --ignore_unknown_options put K1 V1 --create_if_missing
```
There is one directory file descriptor that is not closed in the strace log.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/10049

Test Plan: Add a new unit test DBBasicTest.DBCloseAllDirectoryFDs: Open a database with different WAL directory and three different data directories, and all directory file descriptors should be closed after calling Close(). Explicitly call Close() after a directory file descriptor is not used so that the counter of directory open and close should be equivalent.

Reviewed By: ajkr, hx235

Differential Revision: D36722135

Pulled By: littlepig2013

fbshipit-source-id: 07bdc2abc417c6b30997b9bbef1f79aa757b21ff
This commit is contained in:
Zichen Zhu 2022-06-01 18:03:34 -07:00 committed by Facebook GitHub Bot
parent b4d0e041d0
commit 65893ad959
24 changed files with 218 additions and 10 deletions

View File

@ -675,6 +675,19 @@ ColumnFamilyData::~ColumnFamilyData() {
id_, name_.c_str());
}
}
if (data_dirs_.size()) { // Explicitly close data directories
Status s = Status::OK();
for (auto& data_dir_ptr : data_dirs_) {
if (data_dir_ptr) {
s = data_dir_ptr->Close(IOOptions(), nullptr);
if (!s.ok()) {
ROCKS_LOG_WARN(ioptions_.logger, "Ignoring error %s",
s.ToString().c_str());
}
}
}
}
}
bool ColumnFamilyData::UnrefAndTryDelete() {

View File

@ -25,6 +25,7 @@
#endif
#include "util/file_checksum_helper.h"
#include "util/random.h"
#include "utilities/counted_fs.h"
#include "utilities/fault_injection_env.h"
#include "utilities/merge_operators.h"
#include "utilities/merge_operators/string_append/stringappend.h"
@ -1153,6 +1154,39 @@ TEST_F(DBBasicTest, DBClose) {
ASSERT_EQ(env->GetCloseCount(), 3);
}
TEST_F(DBBasicTest, DBCloseAllDirectoryFDs) {
Options options = GetDefaultOptions();
std::string dbname = test::PerThreadDBPath("db_close_all_dir_fds_test");
// Configure a specific WAL directory
options.wal_dir = dbname + "_wal_dir";
// Configure 3 different data directories
options.db_paths.emplace_back(dbname + "_1", 512 * 1024);
options.db_paths.emplace_back(dbname + "_2", 4 * 1024 * 1024);
options.db_paths.emplace_back(dbname + "_3", 1024 * 1024 * 1024);
ASSERT_OK(DestroyDB(dbname, options));
DB* db = nullptr;
std::unique_ptr<Env> env = NewCompositeEnv(
std::make_shared<CountedFileSystem>(FileSystem::Default()));
options.create_if_missing = true;
options.env = env.get();
Status s = DB::Open(options, dbname, &db);
ASSERT_OK(s);
ASSERT_TRUE(db != nullptr);
// Explicitly close the database to ensure the open and close counter for
// directories are equivalent
s = db->Close();
auto* counted_fs =
options.env->GetFileSystem()->CheckedCast<CountedFileSystem>();
assert(counted_fs);
ASSERT_TRUE(counted_fs->counters()->dir_opens ==
counted_fs->counters()->dir_closes);
ASSERT_OK(s);
delete db;
}
TEST_F(DBBasicTest, DBCloseFlushError) {
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
new FaultInjectionTestEnv(env_));

View File

@ -714,12 +714,17 @@ Status DBImpl::CloseHelper() {
write_buffer_manager_->RemoveDBFromQueue(wbm_stall_.get());
}
IOStatus io_s = directories_.Close(IOOptions(), nullptr /* dbg */);
if (!io_s.ok()) {
ret = io_s;
}
if (ret.IsAborted()) {
// Reserve IsAborted() error for those where users didn't release
// certain resource and they can release them and come back and
// retry. In this case, we wrap this exception to something else.
return Status::Incomplete(ret.ToString());
}
return ret;
}
@ -4382,6 +4387,9 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
s = dir_obj->FsyncWithDirOptions(IOOptions(), nullptr,
DirFsyncOptions(options_file_name));
}
if (s.ok()) {
s = dir_obj->Close(IOOptions(), nullptr);
}
}
if (s.ok()) {
InstrumentedMutexLock l(&mutex_);

View File

@ -113,6 +113,39 @@ class Directories {
FSDirectory* GetDbDir() { return db_dir_.get(); }
IOStatus Close(const IOOptions& options, IODebugContext* dbg) {
// close all directories for all database paths
IOStatus s = IOStatus::OK();
if (db_dir_) {
s = db_dir_->Close(options, dbg);
}
if (!s.ok()) {
return s;
}
if (wal_dir_) {
s = wal_dir_->Close(options, dbg);
}
if (!s.ok()) {
return s;
}
if (data_dirs_.size() > 0 && s.ok()) {
for (auto& data_dir_ptr : data_dirs_) {
if (data_dir_ptr) {
s = data_dir_ptr->Close(options, dbg);
if (!s.ok()) {
return s;
}
}
}
}
return s;
}
private:
std::unique_ptr<FSDirectory> db_dir_;
std::vector<std::unique_ptr<FSDirectory>> data_dirs_;

View File

@ -588,6 +588,7 @@ class SpecialEnv : public EnvWrapper {
~NoopDirectory() {}
Status Fsync() override { return Status::OK(); }
Status Close() override { return Status::OK(); }
};
result->reset(new NoopDirectory());

View File

@ -276,6 +276,13 @@ class CompositeDirectoryWrapper : public Directory {
IODebugContext dbg;
return target_->FsyncWithDirOptions(io_opts, &dbg, DirFsyncOptions());
}
Status Close() override {
IOOptions io_opts;
IODebugContext dbg;
return target_->Close(io_opts, &dbg);
}
size_t GetUniqueId(char* id, size_t max_size) const override {
return target_->GetUniqueId(id, max_size);
}

4
env/env.cc vendored
View File

@ -350,6 +350,10 @@ class LegacyDirectoryWrapper : public FSDirectory {
IODebugContext* /*dbg*/) override {
return status_to_io_status(target_->Fsync());
}
IOStatus Close(const IOOptions& /*options*/,
IODebugContext* /*dbg*/) override {
return status_to_io_status(target_->Close());
}
size_t GetUniqueId(char* id, size_t max_size) const override {
return target_->GetUniqueId(id, max_size);
}

2
env/fs_posix.cc vendored
View File

@ -550,7 +550,7 @@ class PosixFileSystem : public FileSystem {
if (fd < 0) {
return IOError("While open directory", name, errno);
} else {
result->reset(new PosixDirectory(fd));
result->reset(new PosixDirectory(fd, name));
}
return IOStatus::OK();
}

29
env/io_posix.cc vendored
View File

@ -1639,7 +1639,8 @@ PosixMemoryMappedFileBuffer::~PosixMemoryMappedFileBuffer() {
// The magic number for BTRFS is fixed, if it's not defined, define it here
#define BTRFS_SUPER_MAGIC 0x9123683E
#endif
PosixDirectory::PosixDirectory(int fd) : fd_(fd) {
PosixDirectory::PosixDirectory(int fd, const std::string& directory_name)
: fd_(fd), directory_name_(directory_name) {
is_btrfs_ = false;
#ifdef OS_LINUX
struct statfs buf;
@ -1649,12 +1650,28 @@ PosixDirectory::PosixDirectory(int fd) : fd_(fd) {
#endif
}
PosixDirectory::~PosixDirectory() { close(fd_); }
PosixDirectory::~PosixDirectory() {
if (fd_ >= 0) {
IOStatus s = PosixDirectory::Close(IOOptions(), nullptr);
s.PermitUncheckedError();
}
}
IOStatus PosixDirectory::Fsync(const IOOptions& opts, IODebugContext* dbg) {
return FsyncWithDirOptions(opts, dbg, DirFsyncOptions());
}
IOStatus PosixDirectory::Close(const IOOptions& /*opts*/,
IODebugContext* /*dbg*/) {
IOStatus s = IOStatus::OK();
if (close(fd_) < 0) {
s = IOError("While closing directory ", directory_name_, errno);
} else {
fd_ = -1;
}
return s;
}
IOStatus PosixDirectory::FsyncWithDirOptions(
const IOOptions& /*opts*/, IODebugContext* /*dbg*/,
const DirFsyncOptions& dir_fsync_options) {
@ -1686,15 +1703,19 @@ IOStatus PosixDirectory::FsyncWithDirOptions(
}
// fallback to dir-fsync for kDefault, kDirRenamed and kFileDeleted
}
// skip fsync/fcntl when fd_ == -1 since this file descriptor has been closed
// in either the de-construction or the close function, data must have been
// fsync-ed before de-construction and close is called
#ifdef HAVE_FULLFSYNC
// btrfs is a Linux file system, while currently F_FULLFSYNC is available on
// Mac OS.
assert(!is_btrfs_);
if (::fcntl(fd_, F_FULLFSYNC) < 0) {
if (fd_ != -1 && ::fcntl(fd_, F_FULLFSYNC) < 0) {
return IOError("while fcntl(F_FULLFSYNC)", "a directory", errno);
}
#else // HAVE_FULLFSYNC
if (fsync(fd_) == -1) {
if (fd_ != -1 && fsync(fd_) == -1) {
s = IOError("While fsync", "a directory", errno);
}
#endif // HAVE_FULLFSYNC

5
env/io_posix.h vendored
View File

@ -450,10 +450,12 @@ struct PosixMemoryMappedFileBuffer : public MemoryMappedFileBuffer {
class PosixDirectory : public FSDirectory {
public:
explicit PosixDirectory(int fd);
explicit PosixDirectory(int fd, const std::string& directory_name);
~PosixDirectory();
virtual IOStatus Fsync(const IOOptions& opts, IODebugContext* dbg) override;
virtual IOStatus Close(const IOOptions& opts, IODebugContext* dbg) override;
virtual IOStatus FsyncWithDirOptions(
const IOOptions&, IODebugContext*,
const DirFsyncOptions& dir_fsync_options) override;
@ -461,6 +463,7 @@ class PosixDirectory : public FSDirectory {
private:
int fd_;
bool is_btrfs_;
const std::string directory_name_;
};
} // namespace ROCKSDB_NAMESPACE

5
env/mock_env.cc vendored
View File

@ -447,6 +447,11 @@ class MockEnvDirectory : public FSDirectory {
IODebugContext* /*dbg*/) override {
return IOStatus::OK();
}
IOStatus Close(const IOOptions& /*options*/,
IODebugContext* /*dbg*/) override {
return IOStatus::OK();
}
};
class MockEnvFileLock : public FileLock {

View File

@ -443,6 +443,9 @@ Status SetIdentityFile(Env* env, const std::string& dbname,
s = dir_obj->FsyncWithDirOptions(IOOptions(), nullptr,
DirFsyncOptions(identify_file_name));
}
if (s.ok()) {
s = dir_obj->Close(IOOptions(), nullptr);
}
if (!s.ok()) {
env->DeleteFile(tmp).PermitUncheckedError();
}

View File

@ -1148,6 +1148,8 @@ class Directory {
virtual ~Directory() {}
// Fsync directory. Can be called concurrently from multiple threads.
virtual Status Fsync() = 0;
// Close directory.
virtual Status Close() = 0;
virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const {
return 0;
@ -1810,6 +1812,7 @@ class DirectoryWrapper : public Directory {
explicit DirectoryWrapper(Directory* target) : target_(target) {}
Status Fsync() override { return target_->Fsync(); }
Status Close() override { return target_->Close(); }
size_t GetUniqueId(char* id, size_t max_size) const override {
return target_->GetUniqueId(id, max_size);
}

View File

@ -1267,6 +1267,9 @@ class FSDirectory {
return Fsync(options, dbg);
}
// Close directory
virtual IOStatus Close(const IOOptions& options, IODebugContext* dbg) = 0;
virtual size_t GetUniqueId(char* /*id*/, size_t /*max_size*/) const {
return 0;
}
@ -1811,6 +1814,10 @@ class FSDirectoryWrapper : public FSDirectory {
return target_->FsyncWithDirOptions(options, dbg, dir_fsync_options);
}
IOStatus Close(const IOOptions& options, IODebugContext* dbg) override {
return target_->Close(options, dbg);
}
size_t GetUniqueId(char* id, size_t max_size) const override {
return target_->GetUniqueId(id, max_size);
}

View File

@ -601,7 +601,7 @@ IOStatus WinFileSystem::NewDirectory(const std::string& name,
return s;
}
result->reset(new WinDirectory(handle));
result->reset(new WinDirectory(name, handle));
return s;
}

View File

@ -1064,6 +1064,22 @@ IOStatus WinDirectory::Fsync(const IOOptions& /*options*/,
return IOStatus::OK();
}
IOStatus WinDirectory::Close(const IOOptions& /*options*/,
IODebugContext* /*dbg*/) {
IOStatus s = IOStatus::OK();
BOOL ret __attribute__((__unused__));
if (handle_ != INVALID_HANDLE_VALUE) {
ret = ::CloseHandle(handle_);
if (!ret) {
auto lastError = GetLastError();
s = IOErrorFromWindowsError("Directory closes failed for : " + GetName(),
lastError);
}
handle_ = NULL;
}
return s;
}
size_t WinDirectory::GetUniqueId(char* id, size_t max_size) const {
return GetUniqueIdFromFile(handle_, id, max_size);
}

View File

@ -472,14 +472,23 @@ class WinMemoryMappedBuffer : public MemoryMappedFileBuffer {
};
class WinDirectory : public FSDirectory {
const std::string filename_;
HANDLE handle_;
public:
explicit WinDirectory(HANDLE h) noexcept : handle_(h) {
explicit WinDirectory(const std::string& filename, HANDLE h) noexcept
: filename_(filename), handle_(h) {
assert(handle_ != INVALID_HANDLE_VALUE);
}
~WinDirectory() { ::CloseHandle(handle_); }
~WinDirectory() {
if (handle_ != NULL) {
IOStatus s = WinDirectory::Close(IOOptions(), nullptr);
s.PermitUncheckedError();
}
}
const std::string& GetName() const { return filename_; }
IOStatus Fsync(const IOOptions& options, IODebugContext* dbg) override;
IOStatus Close(const IOOptions& options, IODebugContext* dbg) override;
size_t GetUniqueId(char* id, size_t max_size) const override;
};

View File

@ -509,6 +509,8 @@ void LDBCommand::CloseDB() {
for (auto& pair : cf_handles_) {
delete pair.second;
}
Status s = db_->Close();
s.PermitUncheckedError();
delete db_;
db_ = nullptr;
}

View File

@ -224,6 +224,15 @@ class CountedDirectory : public FSDirectoryWrapper {
return rv;
}
IOStatus Close(const IOOptions& options, IODebugContext* dbg) override {
IOStatus rv = FSDirectoryWrapper::Close(options, dbg);
if (rv.ok()) {
fs_->counters()->closes++;
fs_->counters()->dir_closes++;
}
return rv;
}
IOStatus FsyncWithDirOptions(const IOOptions& options, IODebugContext* dbg,
const DirFsyncOptions& dir_options) override {
IOStatus rv =
@ -250,6 +259,10 @@ std::string FileOpCounters::PrintCounters() const {
ss << "Num Dir Fsync(): " << dsyncs.load(std::memory_order_relaxed)
<< std::endl;
ss << "Num Close(): " << closes.load(std::memory_order_relaxed) << std::endl;
ss << "Num Dir Open(): " << dir_opens.load(std::memory_order_relaxed)
<< std::endl;
ss << "Num Dir Close(): " << dir_closes.load(std::memory_order_relaxed)
<< std::endl;
ss << "Num Read(): " << reads.ops.load(std::memory_order_relaxed)
<< std::endl;
ss << "Num Append(): " << writes.ops.load(std::memory_order_relaxed)
@ -347,6 +360,7 @@ IOStatus CountedFileSystem::NewDirectory(const std::string& name,
IOStatus s = target()->NewDirectory(name, options, &base, dbg);
if (s.ok()) {
counters_.opens++;
counters_.dir_opens++;
result->reset(new CountedDirectory(std::move(base), this));
}
return s;

View File

@ -46,6 +46,8 @@ struct FileOpCounters {
std::atomic<int> syncs;
std::atomic<int> dsyncs;
std::atomic<int> fsyncs;
std::atomic<int> dir_opens;
std::atomic<int> dir_closes;
OpCounter reads;
OpCounter writes;
@ -57,7 +59,9 @@ struct FileOpCounters {
flushes(0),
syncs(0),
dsyncs(0),
fsyncs(0) {}
fsyncs(0),
dir_opens(0),
dir_closes(0) {}
void Reset() {
opens = 0;
@ -68,6 +72,8 @@ struct FileOpCounters {
syncs = 0;
dsyncs = 0;
fsyncs = 0;
dir_opens = 0;
dir_closes = 0;
reads.Reset();
writes.Reset();
}

View File

@ -107,6 +107,13 @@ Status TestDirectory::Fsync() {
return dir_->Fsync();
}
Status TestDirectory::Close() {
if (!env_->IsFilesystemActive()) {
return env_->GetError();
}
return dir_->Close();
}
TestRandomAccessFile::TestRandomAccessFile(
std::unique_ptr<RandomAccessFile>&& target, FaultInjectionTestEnv* env)
: target_(std::move(target)), env_(env) {

View File

@ -138,6 +138,7 @@ class TestDirectory : public Directory {
~TestDirectory() {}
virtual Status Fsync() override;
virtual Status Close() override;
private:
FaultInjectionTestEnv* env_;

View File

@ -109,6 +109,14 @@ IOStatus TestFSDirectory::Fsync(const IOOptions& options, IODebugContext* dbg) {
return s;
}
IOStatus TestFSDirectory::Close(const IOOptions& options, IODebugContext* dbg) {
if (!fs_->IsFilesystemActive()) {
return fs_->GetError();
}
IOStatus s = dir_->Close(options, dbg);
return s;
}
IOStatus TestFSDirectory::FsyncWithDirOptions(
const IOOptions& options, IODebugContext* dbg,
const DirFsyncOptions& dir_fsync_options) {

View File

@ -180,6 +180,9 @@ class TestFSDirectory : public FSDirectory {
virtual IOStatus Fsync(const IOOptions& options,
IODebugContext* dbg) override;
virtual IOStatus Close(const IOOptions& options,
IODebugContext* dbg) override;
virtual IOStatus FsyncWithDirOptions(
const IOOptions& options, IODebugContext* dbg,
const DirFsyncOptions& dir_fsync_options) override;