Add support for loading dynamic libraries into the RocksDB environment (#5281)

Summary:
This change adds a Dynamic Library class to the RocksDB Env.  Dynamic libraries are populated via the  Env::LoadLibrary method.

The addition of dynamic library support allows for a few different features to be developed:
1.  The compression code can be changed to use dynamic library support.  This would allow RocksDB to determine at run-time what compression packages were installed.  This change would eliminate the need to make sure the build-time and run-time environment had the same library set.  It would also simplify some of the Java build issues (where it attempts to build and include various packages inside the RocksDB jars).

2.  Along with other features (to be provided in a subsequent PR), this change would allow code/configurations to be added to RocksDB at run-time.  For example, the build system includes code for building an "rados" environment and adding "Cassandra" features.  Instead of these extensions being built into the base RocksDB code, these extensions could be loaded at run-time as required/appropriate, either by configuration or explicitly.

We intend to push out other changes in support of the extending RocksDB at run-time via configurations.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/5281

Differential Revision: D15447613

Pulled By: riversand963

fbshipit-source-id: 452cd4f54511c0bceee18f6d9d919aae9fd25fef
This commit is contained in:
Mark Rambacher 2019-06-03 22:59:54 -07:00 committed by Facebook Github Bot
parent 5d6e8df1cf
commit c8267120d8
7 changed files with 203 additions and 0 deletions

1
.gitignore vendored
View File

@ -47,6 +47,7 @@ rocksdb_undump
db_test2 db_test2
trace_analyzer trace_analyzer
trace_analyzer_test trace_analyzer_test
.DS_Store
java/out java/out
java/target java/target

View File

@ -30,6 +30,7 @@ ROCKSDB_COMPILER_FLAGS = [
"-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX", "-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
"-DROCKSDB_BACKTRACE", "-DROCKSDB_BACKTRACE",
"-Wnarrowing", "-Wnarrowing",
"-DROCKSDB_NO_DYNAMIC_EXTENSION",
] ]
ROCKSDB_EXTERNAL_DEPS = [ ROCKSDB_EXTERNAL_DEPS = [

View File

@ -35,6 +35,7 @@ ROCKSDB_COMPILER_FLAGS = [
"-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX", "-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
"-DROCKSDB_BACKTRACE", "-DROCKSDB_BACKTRACE",
"-Wnarrowing", "-Wnarrowing",
"-DROCKSDB_NO_DYNAMIC_EXTENSION",
] ]
ROCKSDB_EXTERNAL_DEPS = [ ROCKSDB_EXTERNAL_DEPS = [

View File

@ -602,6 +602,19 @@ EOF
fi fi
fi fi
if [ "$FBCODE_BUILD" != "true" -a "$PLATFORM" = OS_LINUX ]; then
$CXX $COMMON_FLAGS $PLATFORM_SHARED_CFLAGS -x c++ -c - -o test_dl.o 2>/dev/null <<EOF
void dummy_func() {}
EOF
if [ "$?" = 0 ]; then
$CXX $COMMON_FLAGS $PLATFORM_SHARED_LDFLAGS test_dl.o -o /dev/null 2>/dev/null
if [ "$?" = 0 ]; then
EXEC_LDFLAGS+="-ldl"
rm -f test_dl.o
fi
fi
fi
PLATFORM_CCFLAGS="$PLATFORM_CCFLAGS $COMMON_FLAGS" PLATFORM_CCFLAGS="$PLATFORM_CCFLAGS $COMMON_FLAGS"
PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS $COMMON_FLAGS" PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS $COMMON_FLAGS"

99
env/env_posix.cc vendored
View File

@ -7,8 +7,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// 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 <dirent.h> #include <dirent.h>
#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
#include <dlfcn.h>
#endif
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#if defined(OS_LINUX) #if defined(OS_LINUX)
#include <linux/fs.h> #include <linux/fs.h>
#endif #endif
@ -69,6 +73,17 @@
#endif #endif
namespace rocksdb { namespace rocksdb {
#if defined(OS_WIN)
static const std::string kSharedLibExt = ".dll";
static const char kPathSeparator = ';';
#else
static const char kPathSeparator = ':';
#if defined(OS_MACOSX)
static const std::string kSharedLibExt = ".dylib";
#else
static const std::string kSharedLibExt = ".so";
#endif
#endif
namespace { namespace {
@ -115,6 +130,32 @@ int cloexec_flags(int flags, const EnvOptions* options) {
return flags; return flags;
} }
#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
class PosixDynamicLibrary : public DynamicLibrary {
public:
PosixDynamicLibrary(const std::string& name, void* handle)
: name_(name), handle_(handle) {}
~PosixDynamicLibrary() override { dlclose(handle_); }
Status LoadSymbol(const std::string& sym_name, FunctionPtr* func) override {
char* err = dlerror(); // Clear any old error
*func = (FunctionPtr)dlsym(handle_, sym_name.c_str());
if (*func != nullptr) {
return Status::OK();
} else {
err = dlerror();
return Status::NotFound("Error finding symbol: " + sym_name, err);
}
}
const char* Name() const override { return name_.c_str(); }
private:
std::string name_;
void* handle_;
};
#endif // !ROCKSDB_NO_DYNAMIC_EXTENSION
class PosixEnv : public Env { class PosixEnv : public Env {
public: public:
PosixEnv(); PosixEnv();
@ -729,6 +770,64 @@ class PosixEnv : public Env {
return result; return result;
} }
#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
/**
* Loads the named library into the result.
* If the input name is empty, the current executable is loaded
* On *nix systems, a "lib" prefix is added to the name if one is not supplied
* Comparably, the appropriate shared library extension is added to the name
* if not supplied. If search_path is not specified, the shared library will
* be loaded using the default path (LD_LIBRARY_PATH) If search_path is
* specified, the shared library will be searched for in the directories
* provided by the search path
*/
Status LoadLibrary(const std::string& name, const std::string& path,
std::shared_ptr<DynamicLibrary>* result) override {
Status status;
assert(result != nullptr);
if (name.empty()) {
void* hndl = dlopen(NULL, RTLD_NOW);
if (hndl != nullptr) {
result->reset(new PosixDynamicLibrary(name, hndl));
return Status::OK();
}
} else {
std::string library_name = name;
if (library_name.find(kSharedLibExt) == std::string::npos) {
library_name = library_name + kSharedLibExt;
}
#if !defined(OS_WIN)
if (library_name.find('/') == std::string::npos &&
library_name.compare(0, 3, "lib") != 0) {
library_name = "lib" + library_name;
}
#endif
if (path.empty()) {
void* hndl = dlopen(library_name.c_str(), RTLD_NOW);
if (hndl != nullptr) {
result->reset(new PosixDynamicLibrary(library_name, hndl));
return Status::OK();
}
} else {
std::string local_path;
std::stringstream ss(path);
while (getline(ss, local_path, kPathSeparator)) {
if (!path.empty()) {
std::string full_name = local_path + "/" + library_name;
void* hndl = dlopen(full_name.c_str(), RTLD_NOW);
if (hndl != nullptr) {
result->reset(new PosixDynamicLibrary(full_name, hndl));
return Status::OK();
}
}
}
}
}
return Status::IOError(
IOErrorMsg("Failed to open shared library: xs", name), dlerror());
}
#endif // !ROCKSDB_NO_DYNAMIC_EXTENSION
void Schedule(void (*function)(void* arg1), void* arg, Priority pri = LOW, void Schedule(void (*function)(void* arg1), void* arg, Priority pri = LOW,
void* tag = nullptr, void* tag = nullptr,
void (*unschedFunction)(void* arg) = nullptr) override; void (*unschedFunction)(void* arg) = nullptr) override;

46
env/env_test.cc vendored
View File

@ -247,6 +247,52 @@ TEST_F(EnvPosixTest, MemoryMappedFileBuffer) {
ASSERT_EQ(expected_data, actual_data); ASSERT_EQ(expected_data, actual_data);
} }
#ifndef ROCKSDB_NO_DYNAMIC_EXTENSION
TEST_F(EnvPosixTest, LoadRocksDBLibrary) {
std::shared_ptr<DynamicLibrary> library;
std::function<void*(void*, const char*)> function;
Status status = env_->LoadLibrary("no-such-library", "", &library);
ASSERT_NOK(status);
ASSERT_EQ(nullptr, library.get());
status = env_->LoadLibrary("rocksdb", "", &library);
if (status.ok()) { // If we have can find a rocksdb shared library
ASSERT_NE(nullptr, library.get());
ASSERT_OK(library->LoadFunction("rocksdb_create_default_env",
&function)); // from C definition
ASSERT_NE(nullptr, function);
ASSERT_NOK(library->LoadFunction("no-such-method", &function));
ASSERT_EQ(nullptr, function);
ASSERT_OK(env_->LoadLibrary(library->Name(), "", &library));
} else {
ASSERT_EQ(nullptr, library.get());
}
}
#endif // !ROCKSDB_NO_DYNAMIC_EXTENSION
#if !defined(OS_WIN) && !defined(ROCKSDB_NO_DYNAMIC_EXTENSION)
TEST_F(EnvPosixTest, LoadRocksDBLibraryWithSearchPath) {
std::shared_ptr<DynamicLibrary> library;
std::function<void*(void*, const char*)> function;
ASSERT_NOK(env_->LoadLibrary("no-such-library", "/tmp", &library));
ASSERT_EQ(nullptr, library.get());
ASSERT_NOK(env_->LoadLibrary("dl", "/tmp", &library));
ASSERT_EQ(nullptr, library.get());
Status status = env_->LoadLibrary("rocksdb", "/tmp:./", &library);
if (status.ok()) {
ASSERT_NE(nullptr, library.get());
ASSERT_OK(env_->LoadLibrary(library->Name(), "", &library));
}
char buff[1024];
std::string cwd = getcwd(buff, sizeof(buff));
status = env_->LoadLibrary("rocksdb", "/tmp:" + cwd, &library);
if (status.ok()) {
ASSERT_NE(nullptr, library.get());
ASSERT_OK(env_->LoadLibrary(library->Name(), "", &library));
}
}
#endif // !OS_WIN && !ROCKSDB_NO_DYNAMIC_EXTENSION
TEST_P(EnvPosixTestWithParam, UnSchedule) { TEST_P(EnvPosixTestWithParam, UnSchedule) {
std::atomic<bool> called(false); std::atomic<bool> called(false);
env_->SetBackgroundThreads(1, Env::LOW); env_->SetBackgroundThreads(1, Env::LOW);

View File

@ -41,6 +41,7 @@
namespace rocksdb { namespace rocksdb {
class DynamicLibrary;
class FileLock; class FileLock;
class Logger; class Logger;
class RandomAccessFile; class RandomAccessFile;
@ -338,6 +339,18 @@ class Env {
// REQUIRES: lock has not already been unlocked. // REQUIRES: lock has not already been unlocked.
virtual Status UnlockFile(FileLock* lock) = 0; virtual Status UnlockFile(FileLock* lock) = 0;
// Opens `lib_name` as a dynamic library.
// If the 'search_path' is specified, breaks the path into its components
// based on the appropriate platform separator (";" or ";") and looks for the
// library in those directories. If 'search path is not specified, uses the
// default library path search mechanism (such as LD_LIBRARY_PATH). On
// success, stores a dynamic library in `*result`.
virtual Status LoadLibrary(const std::string& /*lib_name*/,
const std::string& /*search_path */,
std::shared_ptr<DynamicLibrary>* /*result*/) {
return Status::NotSupported("LoadLibrary is not implemented in this Env");
}
// Priority for scheduling job in thread pool // Priority for scheduling job in thread pool
enum Priority { BOTTOM, LOW, HIGH, USER, TOTAL }; enum Priority { BOTTOM, LOW, HIGH, USER, TOTAL };
@ -978,6 +991,29 @@ class FileLock {
void operator=(const FileLock&); void operator=(const FileLock&);
}; };
class DynamicLibrary {
public:
typedef void* (*FunctionPtr)();
virtual ~DynamicLibrary() {}
/** Returns the name of the dynamic library */
virtual const char* Name() const = 0;
/**
* Loads the symbol for sym_name from the library and updates the input
* function. Returns the loaded symbol
*/
template <typename T>
Status LoadFunction(const std::string& sym_name, std::function<T>* function) {
FunctionPtr ptr;
Status s = LoadSymbol(sym_name, &ptr);
*function = reinterpret_cast<T*>(ptr);
return s;
}
/** Loads and returns the symbol for sym_name from the library */
virtual Status LoadSymbol(const std::string& sym_name, FunctionPtr* func) = 0;
};
extern void LogFlush(const std::shared_ptr<Logger>& info_log); extern void LogFlush(const std::shared_ptr<Logger>& info_log);
extern void Log(const InfoLogLevel log_level, extern void Log(const InfoLogLevel log_level,
@ -1168,6 +1204,12 @@ class EnvWrapper : public Env {
Status UnlockFile(FileLock* l) override { return target_->UnlockFile(l); } Status UnlockFile(FileLock* l) override { return target_->UnlockFile(l); }
Status LoadLibrary(const std::string& lib_name,
const std::string& search_path,
std::shared_ptr<DynamicLibrary>* result) override {
return target_->LoadLibrary(lib_name, search_path, result);
}
void Schedule(void (*f)(void* arg), void* a, Priority pri, void Schedule(void (*f)(void* arg), void* a, Priority pri,
void* tag = nullptr, void (*u)(void* arg) = nullptr) override { void* tag = nullptr, void (*u)(void* arg) = nullptr) override {
return target_->Schedule(f, a, pri, tag, u); return target_->Schedule(f, a, pri, tag, u);