Fix POSIX LockFile after failure to create file (#8747)

Summary:
Failure to create the lock file (e.g. out of space) could
prevent future LockFile attempts in the same process on the same file
from succeeding.

Also added DEBUG code to fail assertion if PosixFileLock is destroyed
without using UnlockFile (which is a risk because FileLock is in the
public API with virtual destructor).

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

Test Plan: test added

Reviewed By: ajkr

Differential Revision: D30732543

Pulled By: pdillinger

fbshipit-source-id: 4c30a959566d91f778d6fad3fbbd5f3941b097c1
This commit is contained in:
Peter Dillinger 2021-09-07 22:40:37 -07:00 committed by Facebook GitHub Bot
parent cb5b851ff8
commit e40b04e9fa
4 changed files with 45 additions and 5 deletions

View File

@ -7,6 +7,7 @@
* Fix a race in DumpStats() with column family destruction due to not taking a Ref on each entry while iterating the ColumnFamilySet. * Fix a race in DumpStats() with column family destruction due to not taking a Ref on each entry while iterating the ColumnFamilySet.
* Fix a race in item ref counting in LRUCache when promoting an item from the SecondaryCache. * Fix a race in item ref counting in LRUCache when promoting an item from the SecondaryCache.
* Fix a race in BackupEngine if RateLimiter is reconfigured during concurrent Restore operations. * Fix a race in BackupEngine if RateLimiter is reconfigured during concurrent Restore operations.
* Fix a bug on POSIX in which failure to create a lock file (e.g. out of space) can prevent future LockFile attempts in the same process on the same file from succeeding.
### New Features ### New Features
* RemoteCompaction's interface now includes `db_name`, `db_id`, `session_id`, which could help the user uniquely identify compaction job between db instances and sessions. * RemoteCompaction's interface now includes `db_name`, `db_id`, `session_id`, which could help the user uniquely identify compaction job between db instances and sessions.

23
env/env_test.cc vendored
View File

@ -45,6 +45,7 @@
#include "rocksdb/convenience.h" #include "rocksdb/convenience.h"
#include "rocksdb/env.h" #include "rocksdb/env.h"
#include "rocksdb/env_encryption.h" #include "rocksdb/env_encryption.h"
#include "rocksdb/file_system.h"
#include "rocksdb/system_clock.h" #include "rocksdb/system_clock.h"
#include "test_util/sync_point.h" #include "test_util/sync_point.h"
#include "test_util/testharness.h" #include "test_util/testharness.h"
@ -2644,6 +2645,28 @@ TEST_F(EnvTest, GenerateRawUniqueIdTrackRandomDeviceOnly) {
t.Run(); t.Run();
} }
TEST_F(EnvTest, FailureToCreateLockFile) {
auto env = Env::Default();
auto fs = env->GetFileSystem();
std::string dir = test::PerThreadDBPath(env, "lockdir");
std::string file = dir + "/lockfile";
// Ensure directory doesn't exist
ASSERT_OK(DestroyDir(env, dir));
// Make sure that we can acquire a file lock after the first attempt fails
FileLock* lock = nullptr;
ASSERT_NOK(fs->LockFile(file, IOOptions(), &lock, /*dbg*/ nullptr));
ASSERT_FALSE(lock);
ASSERT_OK(fs->CreateDir(dir, IOOptions(), /*dbg*/ nullptr));
ASSERT_OK(fs->LockFile(file, IOOptions(), &lock, /*dbg*/ nullptr));
ASSERT_OK(fs->UnlockFile(lock, IOOptions(), /*dbg*/ nullptr));
// Clean up
ASSERT_OK(DestroyDir(env, dir));
}
} // namespace ROCKSDB_NAMESPACE } // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) { int main(int argc, char** argv) {

22
env/fs_posix.cc vendored
View File

@ -107,8 +107,18 @@ static int LockOrUnlock(int fd, bool lock) {
class PosixFileLock : public FileLock { class PosixFileLock : public FileLock {
public: public:
int fd_; int fd_ = /*invalid*/ -1;
std::string filename; std::string filename;
void Clear() {
fd_ = -1;
filename.clear();
}
virtual ~PosixFileLock() override {
// Check for destruction without UnlockFile
assert(fd_ == -1);
}
}; };
int cloexec_flags(int flags, const EnvOptions* options) { int cloexec_flags(int flags, const EnvOptions* options) {
@ -825,9 +835,6 @@ class PosixFileSystem : public FileSystem {
if (fd < 0) { if (fd < 0) {
result = IOError("while open a file for lock", fname, errno); result = IOError("while open a file for lock", fname, errno);
} else if (LockOrUnlock(fd, true) == -1) { } else if (LockOrUnlock(fd, true) == -1) {
// if there is an error in locking, then remove the pathname from
// lockedfiles
locked_files.erase(fname);
result = IOError("While lock file", fname, errno); result = IOError("While lock file", fname, errno);
close(fd); close(fd);
} else { } else {
@ -837,6 +844,12 @@ class PosixFileSystem : public FileSystem {
my_lock->filename = fname; my_lock->filename = fname;
*lock = my_lock; *lock = my_lock;
} }
if (!result.ok()) {
// If there is an error in locking, then remove the pathname from
// locked_files. (If we got this far, it did not exist in locked_files
// before this call.)
locked_files.erase(fname);
}
mutex_locked_files.Unlock(); mutex_locked_files.Unlock();
return result; return result;
@ -856,6 +869,7 @@ class PosixFileSystem : public FileSystem {
result = IOError("unlock", my_lock->filename, errno); result = IOError("unlock", my_lock->filename, errno);
} }
close(my_lock->fd_); close(my_lock->fd_);
my_lock->Clear();
delete my_lock; delete my_lock;
mutex_locked_files.Unlock(); mutex_locked_files.Unlock();
return result; return result;

View File

@ -1209,7 +1209,9 @@ class Logger {
InfoLogLevel log_level_; InfoLogLevel log_level_;
}; };
// Identifies a locked file. // Identifies a locked file. Except in custom Env/Filesystem implementations,
// the lifetime of a FileLock object should be managed only by LockFile() and
// UnlockFile().
class FileLock { class FileLock {
public: public:
FileLock() {} FileLock() {}