Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug Fix Windows: Remove lmdb files when delete_library is called #1437

Merged
merged 3 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cpp/arcticdb/storage/library.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ class Library {
return storages_->fast_delete();
}

void close() {
storages_->close();
}

bool key_exists(const VariantKey& key) {
return storages_->key_exists(key);
}
Expand Down
5 changes: 4 additions & 1 deletion cpp/arcticdb/storage/library_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ std::shared_ptr<Library> LibraryManager::get_library(const LibraryPath& path, co

void LibraryManager::close_library_if_open(const LibraryPath &path) {
std::lock_guard<std::mutex> lock{open_libraries_mutex_};
open_libraries_.erase(path);
if (auto it = open_libraries_.find(path); it != open_libraries_.end()) {
it->second->close();
poodlewars marked this conversation as resolved.
Show resolved Hide resolved
open_libraries_.erase(it);
}
}

std::vector<LibraryPath> LibraryManager::get_library_paths() const {
Expand Down
37 changes: 37 additions & 0 deletions cpp/arcticdb/storage/lmdb/lmdb_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,43 @@ void LmdbStorage::do_iterate_type(KeyType key_type, const IterateTypeVisitor& vi
}
}

void remove_db_files(const fs::path& lib_path) {
std::vector<std::string> files = {"lock.mdb", "data.mdb"};

for (const auto& file : files) {
fs::path file_path = lib_path / file;
try {
if (fs::exists(file_path)) {
fs::remove(file_path);
}
} catch (const std::filesystem::filesystem_error& e) {
raise<ErrorCode::E_UNEXPECTED_LMDB_ERROR>(
fmt::format("Unexpected LMDB Error: Failed to remove LMDB file at path: {} error: {}",
file_path.string(), e.what()));
}
}

if (fs::exists(lib_path)) {
if (!fs::is_empty(lib_path)) {
log::storage().warn(fmt::format("Skipping deletion of directory holding LMDB library during "
"library deletion as it contains files unrelated to LMDB"));
} else {
try {
fs::remove_all(lib_path);
} catch (const fs::filesystem_error& e) {
raise<ErrorCode::E_UNEXPECTED_LMDB_ERROR>(
fmt::format("Unexpected LMDB Error: Failed to remove directory: {} error: {}",
lib_path.string(), e.what()));
}
}
}
}

void LmdbStorage::close() {
env_.reset();
remove_db_files(lib_dir_);
}


namespace {
template<class T>
Expand Down
9 changes: 8 additions & 1 deletion cpp/arcticdb/storage/lmdb/lmdb_storage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,18 @@ class LmdbStorage final : public Storage {

inline bool do_fast_delete() final;

void close() override;

void do_iterate_type(KeyType key_type, const IterateTypeVisitor& visitor, const std::string &prefix) final;

bool do_key_exists(const VariantKey & key) final;

::lmdb::env& env() { return *env_; }
::lmdb::env& env() {
if (!env_) {
raise<ErrorCode::E_UNEXPECTED_LMDB_ERROR>("Unexpected LMDB Error: Invalid operation: LMDB environment has been removed");
poodlewars marked this conversation as resolved.
Show resolved Hide resolved
}
return *env_;
}

std::string do_key_path(const VariantKey&) const final { return {}; };

Expand Down
2 changes: 2 additions & 0 deletions cpp/arcticdb/storage/storage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ class Storage {
return do_fast_delete();
}

virtual void close() { }

inline bool key_exists(const VariantKey &key) {
return do_key_exists(key);
}
Expand Down
4 changes: 4 additions & 0 deletions cpp/arcticdb/storage/storages.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ class Storages {
return primary().fast_delete();
}

void close() {
primary().close();
}

bool key_exists(const VariantKey& key) {
return primary().key_exists(key);
}
Expand Down
49 changes: 44 additions & 5 deletions cpp/arcticdb/storage/test/test_storage_exceptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,30 @@ class LMDBStorageFactory : public StorageFactory {
private:
uint64_t map_size;
bool use_mock;
fs::path db_path;
std::string lib_name;
public:
explicit LMDBStorageFactory(bool use_mock = false) : map_size(128ULL * (1ULL << 20) /* 128MB */), use_mock(use_mock) { }
explicit LMDBStorageFactory(uint64_t map_size, bool use_mock = false) : map_size(map_size), use_mock(use_mock), db_path(TEST_DATABASES_PATH / "test_lmdb"), lib_name("test_lib") { }

explicit LMDBStorageFactory(uint64_t map_size, bool use_mock = false) : map_size(map_size), use_mock(use_mock) { }
explicit LMDBStorageFactory(bool use_mock = false) : LMDBStorageFactory(128ULL * (1ULL << 20), use_mock) { }
poodlewars marked this conversation as resolved.
Show resolved Hide resolved

std::unique_ptr<arcticdb::storage::Storage> create() override {
arcticdb::proto::lmdb_storage::Config cfg;

fs::path db_name = "test_lmdb";
cfg.set_path((TEST_DATABASES_PATH / db_name).generic_string());
cfg.set_path((db_path).generic_string());
cfg.set_map_size(map_size);
cfg.set_recreate_if_exists(true);
cfg.set_use_mock_storage_for_testing(use_mock);

arcticdb::storage::LibraryPath library_path{"a", "b"};
arcticdb::storage::LibraryPath library_path(lib_name, '/');

return std::make_unique<arcticdb::storage::lmdb::LmdbStorage>(library_path, arcticdb::storage::OpenMode::DELETE, cfg);
}

fs::path get_lib_path() const {
return db_path / lib_name;
}

void setup() override {
if (!fs::exists(TEST_DATABASES_PATH)) {
fs::create_directories(TEST_DATABASES_PATH);
Expand Down Expand Up @@ -311,6 +316,40 @@ TEST_F(LMDBStorageTestBase, MockUnexpectedLMDBErrorException) {
ASSERT_EQ(list_in_store(*storage), symbols);
}

TEST_F(LMDBStorageTestBase, RemoveLibPath) {
LMDBStorageFactory factory;
auto storage = factory.create();
auto path = factory.get_lib_path();

storage->close();
ASSERT_FALSE(fs::exists(path));
// Once we call close, any other operations should throw UnexpectedLMDBErrorException as lmdb env is closed
ASSERT_THROW({
write_in_store(*storage, "sym1");
}, UnexpectedLMDBErrorException);

ASSERT_THROW({
update_in_store(*storage, "sym1");
}, UnexpectedLMDBErrorException);

ASSERT_THROW({
remove_in_store(*storage, {"sym1"});
}, UnexpectedLMDBErrorException);

ASSERT_THROW({
read_in_store(*storage, "sym1");
}, UnexpectedLMDBErrorException);

ASSERT_THROW({
exists_in_store(*storage, "sym1");
}, UnexpectedLMDBErrorException);

ASSERT_THROW({
list_in_store(*storage);
}, UnexpectedLMDBErrorException);

}

// S3 error handling with mock client
// Note: Exception handling is different for S3 as compared to other storages.
// S3 does not return an error if you rewrite an existing key. It overwrites it.
Expand Down
19 changes: 1 addition & 18 deletions python/arcticdb/adapters/lmdb_library_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,24 +168,7 @@ def get_library_config(self, name, library_options: LibraryOptions):
)

def cleanup_library(self, library_name: str):
poodlewars marked this conversation as resolved.
Show resolved Hide resolved
lmdb_files_removed = True
for file in ("lock.mdb", "data.mdb"):
path = os.path.join(self._path, library_name, file)
try:
os.remove(path)
except Exception as e:
lmdb_files_removed = False
_rm_errorhandler(None, path, e)
dir_path = os.path.join(self._path, library_name)
if os.path.exists(dir_path):
if os.listdir(dir_path):
log.warn(
"Skipping deletion of directory holding LMDB library during library deletion as it contains "
f"files unrelated to LMDB. LMDB files {'have' if lmdb_files_removed else 'have not'} been "
f"removed. directory=[{dir_path}]"
)
else:
shutil.rmtree(os.path.join(self._path, library_name), onerror=_rm_errorhandler)
pass

def get_storage_override(self) -> StorageOverride:
lmdb_override = LmdbOverride()
Expand Down
1 change: 0 additions & 1 deletion python/arcticdb/arctic.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,6 @@ def delete_library(self, name: str) -> None:
except LibraryNotFound:
return
lib._nvs.version_store.clear()
del lib
lib_mgr_name = self._library_adapter.get_name_for_library_manager(name)
self._library_manager.close_library_if_open(lib_mgr_name)
try:
Expand Down
6 changes: 6 additions & 0 deletions python/tests/integration/arcticdb/test_lmdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ def test_map_size_bad_input(options):

assert "Incorrect format for map_size" in str(e.value)

def test_delete_library(lmdb_storage):
ac = lmdb_storage.create_arctic()
lib = ac.create_library("library")
ac.delete_library("library")
with pytest.raises(StorageException) as e:
lib.write("sym1", pd.DataFrame())

@pytest.mark.parametrize("options", ["MAP_SIZE=1GB", "atlas_shape=1GB"])
def test_lmdb_options_unknown_option(options):
Expand Down
Loading