This is an automated email from the ASF dual-hosted git repository.
git-hulk pushed a commit to branch unstable
in repository https://gitbox.apache.org/repos/asf/kvrocks.git
The following commit(s) were added to refs/heads/unstable by this push:
new 6f1c45fdd fix(tests): prevent RocksDB races during test cleanup (#3671)
6f1c45fdd is described below
commit 6f1c45fdd9ca0253ad7f7a5fb96f173be64de611
Author: hulk <[email protected]>
AuthorDate: Wed Oct 14 10:00:59 2026 +0800
fix(tests): prevent RocksDB races during test cleanup (#3671)
RDB, storage, and compaction tests can remove database directories while
RocksDB is still open.
RDBTest::TearDown runs before fixture destruction. Other affected tests
remove directories before their local storage objects are destroyed.
Background purging can delete a file after remove_all discovers its
directory entry, causing intermittent filesystem errors.
The [original CI
job](https://github.com/apache/kvrocks/actions/runs/38224574981/job/114729326234)
failed during RDB teardown:
```text
Value of: clearDBDir(config_.db_dir)
Actual: false
Expected: true
```
The [follow-up CI
job](https://github.com/apache/kvrocks/actions/runs/38361167392/job/115176472110?pr=3671)
failed in Storage.ReplDataManagerFileExistsCRC:
```text
Value of: ec
Actual: true
Expected: false
```
Ensure test readers and contexts are destroyed first, then close RocksDB
and wait for background work before removing the database directories.
Assistant-By GPT-6
---
tests/cppunit/compact_test.cc | 43 +++++++------------------------------------
tests/cppunit/rdb_test.cc | 13 -------------
tests/cppunit/storage_test.cc | 20 +++++++-------------
tests/cppunit/test_base.h | 11 +++++++++++
4 files changed, 25 insertions(+), 62 deletions(-)
diff --git a/tests/cppunit/compact_test.cc b/tests/cppunit/compact_test.cc
index 1d0dc3789..2f2c634c8 100644
--- a/tests/cppunit/compact_test.cc
+++ b/tests/cppunit/compact_test.cc
@@ -31,6 +31,7 @@
#include "storage/compact_filter.h"
#include "storage/redis_metadata.h"
#include "storage/storage.h"
+#include "test_base.h"
#include "time_util.h"
#include "types/redis_hash.h"
#include "types/redis_timeseries.h"
@@ -135,6 +136,7 @@ TEST(Compact, Filter) {
config.db_dir = "compactdb";
config.slot_id_encoded = false;
+ auto cleanup = MakeDBDirCleanup(config.db_dir);
auto storage = std::make_unique<engine::Storage>(&config);
Status s = storage->Open();
assert(s.IsOK());
@@ -229,11 +231,6 @@ TEST(Compact, Filter) {
}
db->ReleaseSnapshot(read_options.snapshot);
- std::error_code ec;
- std::filesystem::remove_all(config.db_dir, ec);
- if (ec) {
- std::cout << "Encounter filesystem error: " << ec << std::endl;
- }
}
TEST_F(CompactHashFieldExpirationTest,
DropsExpiredTTLSubkeyWithoutChangingMetadata) {
@@ -363,6 +360,7 @@ TEST(Compact, KeepsLegacyHashSubkeyWithTTLLikePrefix) {
config.rocks_db.write_buffer_size = 1;
config.rocks_db.block_size = 100;
+ auto cleanup = MakeDBDirCleanup(config.db_dir);
auto storage = std::make_unique<engine::Storage>(&config);
Status s = storage->Open();
ASSERT_TRUE(s.IsOK()) << s.Msg();
@@ -400,13 +398,6 @@ TEST(Compact, KeepsLegacyHashSubkeyWithTTLLikePrefix) {
rs = storage->Get(ctx, ctx.GetReadOptions(), sub_key, &raw_value);
EXPECT_TRUE(rs.ok()) << rs.ToString();
EXPECT_EQ(raw_value, value);
-
- storage.reset();
- std::error_code ec;
- std::filesystem::remove_all(config.db_dir, ec);
- if (ec) {
- std::cout << "Encounter filesystem error: " << ec << std::endl;
- }
}
TEST_F(CompactHashFieldExpirationTest,
KeepsMalformedFieldExpirationSubkeyValue) {
@@ -446,6 +437,7 @@ TEST(Compact, SearchFilter) {
config.db_dir = "compactdb";
config.slot_id_encoded = false;
+ auto cleanup = MakeDBDirCleanup(config.db_dir);
auto storage = std::make_unique<engine::Storage>(&config);
auto s = storage->Open();
assert(s.IsOK());
@@ -503,12 +495,6 @@ TEST(Compact, SearchFilter) {
sg = storage->Get(ctx, rocksdb::ReadOptions(),
storage->GetCFHandle(ColumnFamilyID::Search), num_search_key,
&search_value);
ASSERT_TRUE(sg.IsNotFound());
-
- std::error_code ec;
- std::filesystem::remove_all(config.db_dir, ec);
- if (ec) {
- std::cout << "Encounter filesystem error: " << ec << std::endl;
- }
}
TEST(Compact, IndexFilter) {
@@ -516,6 +502,7 @@ TEST(Compact, IndexFilter) {
config.db_dir = "compactdb";
config.slot_id_encoded = false;
+ auto cleanup = MakeDBDirCleanup(config.db_dir);
auto storage = std::make_unique<engine::Storage>(&config);
auto s = storage->Open();
assert(s.IsOK());
@@ -553,12 +540,6 @@ TEST(Compact, IndexFilter) {
ASSERT_TRUE(timeseries->MGet(ctx, mget_option, false, &mget_result).ok());
ASSERT_EQ(mget_result.size(), 1);
ASSERT_EQ(mget_result[0].name, ts_keep_key);
-
- std::error_code ec;
- std::filesystem::remove_all(config.db_dir, ec);
- if (ec) {
- std::cout << "Encounter filesystem error: " << ec << std::endl;
- }
}
TEST(Compact, TSRetention) {
@@ -566,6 +547,7 @@ TEST(Compact, TSRetention) {
config.db_dir = "compactdb_tsretention";
config.slot_id_encoded = false;
+ auto cleanup = MakeDBDirCleanup(config.db_dir);
auto storage = std::make_unique<engine::Storage>(&config);
auto s = storage->Open();
assert(s.IsOK());
@@ -626,12 +608,6 @@ TEST(Compact, TSRetention) {
ASSERT_EQ(range_result.size(), 2);
ASSERT_EQ(range_result[0].ts, 10);
ASSERT_EQ(range_result[1].ts, 110);
-
- std::error_code ec;
- std::filesystem::remove_all(config.db_dir, ec);
- if (ec) {
- std::cout << "Encounter filesystem error: " << ec << std::endl;
- }
}
TEST(Compact, TSDownstreamSubKey) {
@@ -639,6 +615,7 @@ TEST(Compact, TSDownstreamSubKey) {
config.db_dir = "compactdb_tsdownstream";
config.slot_id_encoded = false;
+ auto cleanup = MakeDBDirCleanup(config.db_dir);
auto storage = std::make_unique<engine::Storage>(&config);
auto s = storage->Open();
assert(s.IsOK());
@@ -690,10 +667,4 @@ TEST(Compact, TSDownstreamSubKey) {
ds_keys = get_all_ds_key();
ASSERT_EQ(ds_keys.size(), 1);
ASSERT_EQ(ds_keys[0], dst_key2);
-
- std::error_code ec;
- std::filesystem::remove_all(config.db_dir, ec);
- if (ec) {
- std::cout << "Encounter filesystem error: " << ec << std::endl;
- }
}
diff --git a/tests/cppunit/rdb_test.cc b/tests/cppunit/rdb_test.cc
index 5c8e6dbd0..74ae3dc67 100644
--- a/tests/cppunit/rdb_test.cc
+++ b/tests/cppunit/rdb_test.cc
@@ -20,7 +20,6 @@
#include "storage/rdb/rdb.h"
#include <cmath>
-#include <filesystem>
#include <map>
#include <memory>
#include <string>
@@ -48,8 +47,6 @@ class RDBTest : public TestBase {
~RDBTest() override = default;
void SetUp() override { crc64_init(); }
- void TearDown() override { ASSERT_TRUE(clearDBDir(config_.db_dir)); }
-
void loadRdb(const std::string &path) {
auto stream_ptr = std::make_unique<RdbFileStream>(path);
auto s = stream_ptr->Open();
@@ -130,16 +127,6 @@ class RDBTest : public TestBase {
std::string ns_;
std::string tmp_rdb_;
-
- private:
- static bool clearDBDir(const std::string &path) {
- try {
- std::filesystem::remove_all(path);
- } catch (std::filesystem::filesystem_error &e) {
- return false;
- }
- return true;
- }
};
void RDBTest::encodingDataCheck() {
diff --git a/tests/cppunit/storage_test.cc b/tests/cppunit/storage_test.cc
index 881f8418b..0add8c185 100644
--- a/tests/cppunit/storage_test.cc
+++ b/tests/cppunit/storage_test.cc
@@ -30,6 +30,8 @@
#include <string>
#include <vector>
+#include "test_base.h"
+
TEST(Storage, CreateBackup) {
std::error_code ec;
@@ -40,6 +42,7 @@ TEST(Storage, CreateBackup) {
std::filesystem::remove_all(config.db_dir, ec);
ASSERT_TRUE(!ec);
+ auto cleanup = MakeDBDirCleanup(config.db_dir);
auto storage = std::make_unique<engine::Storage>(&config);
auto s = storage->Open();
ASSERT_TRUE(s.IsOK());
@@ -59,9 +62,6 @@ TEST(Storage, CreateBackup) {
// check if backup success without caring about the sequence number
s = storage->CreateBackup();
ASSERT_TRUE(s.IsOK());
-
- std::filesystem::remove_all(config.db_dir, ec);
- ASSERT_TRUE(!ec);
}
TEST(Storage, ReadOnlyTransactions) {
@@ -186,6 +186,7 @@ TEST(Storage, GetFullReplDataInfoRejectsEmptyCheckpoint) {
std::filesystem::create_directory(test_dir, ec);
ASSERT_FALSE(ec);
+ auto cleanup = MakeDBDirCleanup(test_dir);
auto storage = std::make_unique<engine::Storage>(&config);
auto s = storage->Open();
ASSERT_TRUE(s.IsOK()) << s.Msg();
@@ -208,9 +209,6 @@ TEST(Storage, GetFullReplDataInfoRejectsEmptyCheckpoint) {
s = engine::Storage::ReplDataManager::GetFullReplDataInfo(storage.get(),
&files);
EXPECT_FALSE(s.IsOK());
EXPECT_TRUE(files.empty());
-
- std::filesystem::remove_all(test_dir, ec);
- ASSERT_FALSE(ec);
}
TEST(Storage, TryPurgeCheckpoint) {
@@ -227,6 +225,7 @@ TEST(Storage, TryPurgeCheckpoint) {
std::filesystem::create_directory(test_dir, ec);
ASSERT_FALSE(ec);
+ auto cleanup = MakeDBDirCleanup(test_dir);
auto storage = std::make_unique<engine::Storage>(&config);
auto s = storage->Open();
ASSERT_TRUE(s.IsOK()) << s.Msg();
@@ -257,9 +256,6 @@ TEST(Storage, TryPurgeCheckpoint) {
s = engine::Storage::ReplDataManager::GetFullReplDataInfo(storage.get(),
&files);
ASSERT_TRUE(s.IsOK()) << s.Msg();
EXPECT_TRUE(storage->ExistCheckpoint());
-
- std::filesystem::remove_all(test_dir, ec);
- ASSERT_FALSE(ec);
}
TEST(Storage, ReplDataManagerFileExistsCRC) {
@@ -276,6 +272,7 @@ TEST(Storage, ReplDataManagerFileExistsCRC) {
std::filesystem::create_directories(test_dir + "/sync", ec);
ASSERT_FALSE(ec);
+ auto cleanup = MakeDBDirCleanup(test_dir);
auto storage = std::make_unique<engine::Storage>(&config);
auto s = storage->Open();
ASSERT_TRUE(s.IsOK()) << s.Msg();
@@ -335,7 +332,4 @@ TEST(Storage, ReplDataManagerFileExistsCRC) {
std::string file_name = "large_file";
EXPECT_TRUE(engine::Storage::ReplDataManager::FileExists(storage.get(),
test_dir + "/sync", file_name, 0));
}
-
- std::filesystem::remove_all(test_dir, ec);
- ASSERT_FALSE(ec);
-}
\ No newline at end of file
+}
diff --git a/tests/cppunit/test_base.h b/tests/cppunit/test_base.h
index fd691e59b..16ae9472d 100644
--- a/tests/cppunit/test_base.h
+++ b/tests/cppunit/test_base.h
@@ -24,10 +24,21 @@
#include <filesystem>
#include <fstream>
+#include <string>
+#include "common/scope_exit.h"
#include "storage/redis_db.h"
#include "types/redis_hash.h"
+// Declare before storage so the database is closed before removing its
directory.
+inline auto MakeDBDirCleanup(const std::string &path) {
+ return MakeScopeExit([path] {
+ std::error_code ec;
+ std::filesystem::remove_all(path, ec);
+ ASSERT_FALSE(ec) << "Failed to remove database directory " << path << ": "
<< ec.message();
+ });
+}
+
class TestFixture { // NOLINT
public:
TestFixture(TestFixture &&) = delete;