This is an automated email from the ASF dual-hosted git repository.
jihuayu 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 b272b5c1b fix(set): propagate storage errors in Set instead of
treating as NotFound (#3428)
b272b5c1b is described below
commit b272b5c1b673385ca6e58cf5c595fce252782e22
Author: Songqing Zhang <[email protected]>
AuthorDate: Thu Apr 9 09:01:42 2026 +0800
fix(set): propagate storage errors in Set instead of treating as NotFound
(#3428)
Previously, when `storage_->Get()` returned a non-NotFound error (e.g.,
I/O error or corruption) during SADD, the code fell through to the
`batch->Put` path, incorrectly treating the error as "member not found"
and adding the member. This could silently inflate `metadata.size` and
insert duplicate members on storage failures.
Add an explicit `!s.IsNotFound()` check to propagate real errors, which
is consistent with how `Set::Remove` already handles the same pattern.
---------
Co-authored-by: 纪华裕 <[email protected]>
---
src/types/redis_set.cc | 4 +++-
tests/cppunit/types/set_test.cc | 51 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/src/types/redis_set.cc b/src/types/redis_set.cc
index 07d5c7829..a2f4c4ad8 100644
--- a/src/types/redis_set.cc
+++ b/src/types/redis_set.cc
@@ -78,6 +78,7 @@ rocksdb::Status Set::Add(engine::Context &ctx, const Slice
&user_key, const std:
std::string sub_key = InternalKey(ns_key, member, metadata.version,
storage_->IsSlotIdEncoded()).Encode();
s = storage_->Get(ctx, ctx.GetReadOptions(), sub_key, &value);
if (s.ok()) continue;
+ if (!s.IsNotFound()) return s;
s = batch->Put(sub_key, Slice());
if (!s.ok()) return s;
*added_cnt += 1;
@@ -114,7 +115,8 @@ rocksdb::Status Set::Remove(engine::Context &ctx, const
Slice &user_key, const s
}
std::string sub_key = InternalKey(ns_key, member, metadata.version,
storage_->IsSlotIdEncoded()).Encode();
s = storage_->Get(ctx, ctx.GetReadOptions(), sub_key, &value);
- if (!s.ok()) continue;
+ if (s.IsNotFound()) continue;
+ if (!s.ok()) return s;
s = batch->Delete(sub_key);
if (!s.ok()) return s;
*removed_cnt += 1;
diff --git a/tests/cppunit/types/set_test.cc b/tests/cppunit/types/set_test.cc
index 44f230d23..41746f4fd 100644
--- a/tests/cppunit/types/set_test.cc
+++ b/tests/cppunit/types/set_test.cc
@@ -248,6 +248,57 @@ TEST_F(RedisSetTest, InterCard) {
s = set_->Del(*ctx_, k4);
}
+TEST_F(RedisSetTest, AddExistingMembers) {
+ uint64_t ret = 0;
+ rocksdb::Status s = set_->Add(*ctx_, key_, fields_, &ret);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(fields_.size(), ret);
+
+ s = set_->Add(*ctx_, key_, fields_, &ret);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(0, ret);
+
+ uint64_t card = 0;
+ s = set_->Card(*ctx_, key_, &card);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(fields_.size(), card);
+
+ std::vector<Slice> mixed_members = {"set-key-1", "set-key-new-1",
"set-key-new-2"};
+ s = set_->Add(*ctx_, key_, mixed_members, &ret);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(2, ret);
+
+ s = set_->Card(*ctx_, key_, &card);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(fields_.size() + 2, card);
+}
+
+TEST_F(RedisSetTest, RemoveNonExistingMembers) {
+ uint64_t ret = 0;
+ rocksdb::Status s = set_->Add(*ctx_, key_, fields_, &ret);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(fields_.size(), ret);
+
+ std::vector<Slice> non_existing = {"non-exist-1", "non-exist-2"};
+ s = set_->Remove(*ctx_, key_, non_existing, &ret);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(0, ret);
+
+ uint64_t card = 0;
+ s = set_->Card(*ctx_, key_, &card);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(fields_.size(), card);
+
+ std::vector<Slice> mixed = {"set-key-1", "non-exist-1"};
+ s = set_->Remove(*ctx_, key_, mixed, &ret);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(1, ret);
+
+ s = set_->Card(*ctx_, key_, &card);
+ EXPECT_TRUE(s.ok());
+ EXPECT_EQ(fields_.size() - 1, card);
+}
+
TEST_F(RedisSetTest, Overwrite) {
uint64_t ret = 0;
rocksdb::Status s = set_->Add(*ctx_, key_, fields_, &ret);