Prevent concurrent multiple opens of leveldb database.
Summary: The fcntl call cannot detect lock conflicts when invoked multiple times from the same thread. Use a static lockedFile Set to record the paths that are locked. A lockfile request checks to see if htis filename already exists in lockedFiles, if so, then it triggers an error. Otherwise, it inserts the filename in the lockedFiles Set. A unlock file request verifies that the filename is in the lockedFiles set and removes it from lockedFiles set. Test Plan: unit test attached Reviewers: heyongqiang Reviewed By: heyongqiang Differential Revision: https://reviews.facebook.net/D4755
This commit is contained in:
parent
deb1a1fa9b
commit
e56b2c5a31
7
Makefile
7
Makefile
@ -52,7 +52,8 @@ TESTS = \
|
|||||||
table_test \
|
table_test \
|
||||||
version_edit_test \
|
version_edit_test \
|
||||||
version_set_test \
|
version_set_test \
|
||||||
write_batch_test
|
write_batch_test \
|
||||||
|
filelock_test
|
||||||
|
|
||||||
TOOLS = \
|
TOOLS = \
|
||||||
manifest_dump
|
manifest_dump
|
||||||
@ -174,6 +175,10 @@ leveldb_server_test: thrift/test/simpletest.o $(LIBRARY)
|
|||||||
manifest_dump: tools/manifest_dump.o $(LIBOBJECTS)
|
manifest_dump: tools/manifest_dump.o $(LIBOBJECTS)
|
||||||
$(CXX) tools/manifest_dump.o $(LIBOBJECTS) -o $@ $(LDFLAGS)
|
$(CXX) tools/manifest_dump.o $(LIBOBJECTS) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
filelock_test: util/filelock_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||||
|
$(CXX) util/filelock_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
|
||||||
ifeq ($(PLATFORM), IOS)
|
ifeq ($(PLATFORM), IOS)
|
||||||
# For iOS, create universal object files to be used on both the simulator and
|
# For iOS, create universal object files to be used on both the simulator and
|
||||||
# a device.
|
# a device.
|
||||||
|
@ -213,6 +213,10 @@ class DBTest {
|
|||||||
ASSERT_OK(TryReopen(options));
|
ASSERT_OK(TryReopen(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status PureReopen(Options* options, DB** db) {
|
||||||
|
return DB::Open(*options, dbname_, db);
|
||||||
|
}
|
||||||
|
|
||||||
Status TryReopen(Options* options) {
|
Status TryReopen(Options* options) {
|
||||||
delete db_;
|
delete db_;
|
||||||
db_ = NULL;
|
db_ = NULL;
|
||||||
@ -828,6 +832,13 @@ TEST(DBTest, WAL) {
|
|||||||
ASSERT_EQ("v3", Get("foo"));
|
ASSERT_EQ("v3", Get("foo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(DBTest, CheckLock) {
|
||||||
|
DB* localdb;
|
||||||
|
Options options = CurrentOptions();
|
||||||
|
ASSERT_TRUE(TryReopen(&options).ok());
|
||||||
|
ASSERT_TRUE(!(PureReopen(&options, &localdb).ok())); // second open should fail
|
||||||
|
}
|
||||||
|
|
||||||
TEST(DBTest, FLUSH) {
|
TEST(DBTest, FLUSH) {
|
||||||
Options options = CurrentOptions();
|
Options options = CurrentOptions();
|
||||||
WriteOptions writeOpt = WriteOptions();
|
WriteOptions writeOpt = WriteOptions();
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <set>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@ -31,6 +32,10 @@ namespace leveldb {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
// list of pathnames that are locked
|
||||||
|
static std::set<std::string> lockedFiles;
|
||||||
|
static port::Mutex mutex_lockedFiles;
|
||||||
|
|
||||||
static Status IOError(const std::string& context, int err_number) {
|
static Status IOError(const std::string& context, int err_number) {
|
||||||
return Status::IOError(context, strerror(err_number));
|
return Status::IOError(context, strerror(err_number));
|
||||||
}
|
}
|
||||||
@ -291,7 +296,28 @@ class PosixMmapFile : public WritableFile {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static int LockOrUnlock(int fd, bool lock) {
|
static int LockOrUnlock(const std::string& fname, int fd, bool lock) {
|
||||||
|
mutex_lockedFiles.Lock();
|
||||||
|
if (lock) {
|
||||||
|
// If it already exists in the lockedFiles set, then it is already locked,
|
||||||
|
// and fail this lock attempt. Otherwise, insert it into lockedFiles.
|
||||||
|
// This check is needed because fcntl() does not detect lock conflict
|
||||||
|
// if the fcntl is issued by the same thread that earlier acquired
|
||||||
|
// this lock.
|
||||||
|
if (lockedFiles.insert(fname).second == false) {
|
||||||
|
mutex_lockedFiles.Unlock();
|
||||||
|
errno = ENOLCK;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we are unlocking, then verify that we had locked it earlier,
|
||||||
|
// it should already exist in lockedFiles. Remove it from lockedFiles.
|
||||||
|
if (lockedFiles.erase(fname) != 1) {
|
||||||
|
mutex_lockedFiles.Unlock();
|
||||||
|
errno = ENOLCK;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
errno = 0;
|
errno = 0;
|
||||||
struct flock f;
|
struct flock f;
|
||||||
memset(&f, 0, sizeof(f));
|
memset(&f, 0, sizeof(f));
|
||||||
@ -299,12 +325,19 @@ static int LockOrUnlock(int fd, bool lock) {
|
|||||||
f.l_whence = SEEK_SET;
|
f.l_whence = SEEK_SET;
|
||||||
f.l_start = 0;
|
f.l_start = 0;
|
||||||
f.l_len = 0; // Lock/unlock entire file
|
f.l_len = 0; // Lock/unlock entire file
|
||||||
return fcntl(fd, F_SETLK, &f);
|
int value = fcntl(fd, F_SETLK, &f);
|
||||||
|
if (value == -1 && lock) {
|
||||||
|
// if there is an error in locking, then remove the pathname from lockedfiles
|
||||||
|
lockedFiles.erase(fname);
|
||||||
|
}
|
||||||
|
mutex_lockedFiles.Unlock();
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PosixFileLock : public FileLock {
|
class PosixFileLock : public FileLock {
|
||||||
public:
|
public:
|
||||||
int fd_;
|
int fd_;
|
||||||
|
std::string filename;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PosixEnv : public Env {
|
class PosixEnv : public Env {
|
||||||
@ -435,12 +468,13 @@ class PosixEnv : public Env {
|
|||||||
int fd = open(fname.c_str(), O_RDWR | O_CREAT, 0644);
|
int fd = open(fname.c_str(), O_RDWR | O_CREAT, 0644);
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
result = IOError(fname, errno);
|
result = IOError(fname, errno);
|
||||||
} else if (LockOrUnlock(fd, true) == -1) {
|
} else if (LockOrUnlock(fname, fd, true) == -1) {
|
||||||
result = IOError("lock " + fname, errno);
|
result = IOError("lock " + fname, errno);
|
||||||
close(fd);
|
close(fd);
|
||||||
} else {
|
} else {
|
||||||
PosixFileLock* my_lock = new PosixFileLock;
|
PosixFileLock* my_lock = new PosixFileLock;
|
||||||
my_lock->fd_ = fd;
|
my_lock->fd_ = fd;
|
||||||
|
my_lock->filename = fname;
|
||||||
*lock = my_lock;
|
*lock = my_lock;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -449,7 +483,7 @@ class PosixEnv : public Env {
|
|||||||
virtual Status UnlockFile(FileLock* lock) {
|
virtual Status UnlockFile(FileLock* lock) {
|
||||||
PosixFileLock* my_lock = reinterpret_cast<PosixFileLock*>(lock);
|
PosixFileLock* my_lock = reinterpret_cast<PosixFileLock*>(lock);
|
||||||
Status result;
|
Status result;
|
||||||
if (LockOrUnlock(my_lock->fd_, false) == -1) {
|
if (LockOrUnlock(my_lock->filename, my_lock->fd_, false) == -1) {
|
||||||
result = IOError("unlock", errno);
|
result = IOError("unlock", errno);
|
||||||
}
|
}
|
||||||
close(my_lock->fd_);
|
close(my_lock->fd_);
|
||||||
|
57
util/filelock_test.cc
Normal file
57
util/filelock_test.cc
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2012 Facebook. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "leveldb/status.h"
|
||||||
|
#include "leveldb/env.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "util/coding.h"
|
||||||
|
#include "util/testharness.h"
|
||||||
|
|
||||||
|
namespace leveldb {
|
||||||
|
|
||||||
|
class LockTest {
|
||||||
|
public:
|
||||||
|
static LockTest* current_;
|
||||||
|
std::string file_;
|
||||||
|
leveldb::Env* env_;
|
||||||
|
|
||||||
|
LockTest() : file_(test::TmpDir() + "/db_testlock_file"),
|
||||||
|
env_(leveldb::Env::Default()) {
|
||||||
|
current_ = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~LockTest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Status LockFile(FileLock** db_lock) {
|
||||||
|
return env_->LockFile(file_, db_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status UnlockFile(FileLock* db_lock) {
|
||||||
|
return env_->UnlockFile(db_lock);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
LockTest* LockTest::current_;
|
||||||
|
|
||||||
|
TEST(LockTest, LockBySameThread) {
|
||||||
|
FileLock* lock1;
|
||||||
|
FileLock* lock2;
|
||||||
|
|
||||||
|
// acquire a lock on a file
|
||||||
|
ASSERT_OK(LockFile(&lock1));
|
||||||
|
|
||||||
|
// re-acquire the lock on the same file. This should fail.
|
||||||
|
ASSERT_TRUE(LockFile(&lock2).IsIOError());
|
||||||
|
|
||||||
|
// release the lock
|
||||||
|
ASSERT_OK(UnlockFile(lock1));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace leveldb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
return leveldb::test::RunAllTests();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user