mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2024-12-14 11:58:02 +00:00
add dense_set.SetExpiryTime in preparation for fieldexpire (#3780)
* feat: add DenseSet::IteratorBase::SetExpiryTime This commit is in preparation for adding FIELDEXPIRE and HEXPIRE. * fix: 0 is a valid input for MakeSetSds
This commit is contained in:
parent
e3214cb603
commit
2ab480e160
12 changed files with 115 additions and 38 deletions
|
@ -46,6 +46,17 @@ DenseSet::IteratorBase::IteratorBase(const DenseSet* owner, bool is_end)
|
|||
}
|
||||
}
|
||||
|
||||
void DenseSet::IteratorBase::SetExpiryTime(uint32_t ttl_sec) {
|
||||
if (!HasExpiry()) {
|
||||
auto src = curr_entry_->GetObject();
|
||||
void* new_obj = owner_->ObjectClone(src, false, true);
|
||||
curr_entry_->SetObject(new_obj);
|
||||
curr_entry_->SetTtl(true);
|
||||
owner_->ObjDelete(src, false);
|
||||
}
|
||||
owner_->ObjUpdateExpireTime(curr_entry_->GetObject(), ttl_sec);
|
||||
}
|
||||
|
||||
void DenseSet::IteratorBase::Advance() {
|
||||
bool step_link = false;
|
||||
DCHECK(curr_entry_);
|
||||
|
@ -211,7 +222,7 @@ void DenseSet::CloneBatch(unsigned len, CloneItem* items, DenseSet* other) const
|
|||
auto& src = items[i];
|
||||
if (src.obj) {
|
||||
// The majority of the CPU is spent in this block.
|
||||
void* new_obj = other->ObjectClone(src.obj, src.has_ttl);
|
||||
void* new_obj = other->ObjectClone(src.obj, src.has_ttl, false);
|
||||
uint64_t hash = Hash(src.obj, 0);
|
||||
other->AddUnique(new_obj, src.has_ttl, hash);
|
||||
src.obj = nullptr;
|
||||
|
|
|
@ -187,6 +187,8 @@ class DenseSet {
|
|||
return curr_entry_->HasTtl() ? owner_->ObjExpireTime(curr_entry_->GetObject()) : UINT32_MAX;
|
||||
}
|
||||
|
||||
void SetExpiryTime(uint32_t ttl_sec);
|
||||
|
||||
bool HasExpiry() const {
|
||||
return curr_entry_->HasTtl();
|
||||
}
|
||||
|
@ -265,8 +267,9 @@ class DenseSet {
|
|||
virtual bool ObjEqual(const void* left, const void* right, uint32_t right_cookie) const = 0;
|
||||
virtual size_t ObjectAllocSize(const void* obj) const = 0;
|
||||
virtual uint32_t ObjExpireTime(const void* obj) const = 0;
|
||||
virtual void ObjUpdateExpireTime(const void* obj, uint32_t ttl_sec) = 0;
|
||||
virtual void ObjDelete(void* obj, bool has_ttl) const = 0;
|
||||
virtual void* ObjectClone(const void* obj, bool has_ttl) const = 0;
|
||||
virtual void* ObjectClone(const void* obj, bool has_ttl, bool add_ttl) const = 0;
|
||||
|
||||
void CollectExpired();
|
||||
|
||||
|
|
|
@ -128,12 +128,16 @@ uint32_t ScoreMap::ObjExpireTime(const void* obj) const {
|
|||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
void ScoreMap::ObjUpdateExpireTime(const void* obj, uint32_t ttl_sec) {
|
||||
// Should not reach.
|
||||
}
|
||||
|
||||
void ScoreMap::ObjDelete(void* obj, bool has_ttl) const {
|
||||
sds s1 = (sds)obj;
|
||||
sdsfree(s1);
|
||||
}
|
||||
|
||||
void* ScoreMap::ObjectClone(const void* obj, bool has_ttl) const {
|
||||
void* ScoreMap::ObjectClone(const void* obj, bool has_ttl, bool add_ttl) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -122,8 +122,9 @@ class ScoreMap : public DenseSet {
|
|||
bool ObjEqual(const void* left, const void* right, uint32_t right_cookie) const final;
|
||||
size_t ObjectAllocSize(const void* obj) const final;
|
||||
uint32_t ObjExpireTime(const void* obj) const final;
|
||||
void ObjDelete(void* obj, bool has_ttl) const final;
|
||||
void* ObjectClone(const void* obj, bool has_ttl) const final;
|
||||
void ObjUpdateExpireTime(const void* obj, uint32_t ttl_sec) override;
|
||||
void ObjDelete(void* obj, bool has_ttl) const override;
|
||||
void* ObjectClone(const void* obj, bool has_ttl, bool add_ttl) const final;
|
||||
};
|
||||
|
||||
} // namespace dfly
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
#include "core/sds_utils.h"
|
||||
|
||||
#include "base/endian.h"
|
||||
|
||||
extern "C" {
|
||||
#include "redis/sds.h"
|
||||
#include "redis/zmalloc.h"
|
||||
|
@ -43,6 +45,12 @@ inline int SdsHdrSize(char type) {
|
|||
|
||||
} // namespace
|
||||
|
||||
void SdsUpdateExpireTime(const void* obj, uint32_t time_at, uint32_t offset) {
|
||||
sds str = (sds)obj;
|
||||
char* valptr = str + sdslen(str) + 1;
|
||||
absl::little_endian::Store32(valptr + offset, time_at);
|
||||
}
|
||||
|
||||
char* AllocSdsWithSpace(uint32_t strlen, uint32_t space) {
|
||||
size_t usable;
|
||||
char type = SdsReqType(strlen);
|
||||
|
|
|
@ -13,4 +13,7 @@ namespace dfly {
|
|||
// sds string (keys) with metadata attached to them.
|
||||
char* AllocSdsWithSpace(uint32_t strlen, uint32_t space);
|
||||
|
||||
// Updates the expire time of the sds object. The offset is the number of bytes
|
||||
void SdsUpdateExpireTime(const void* obj, uint32_t time_at, uint32_t offset);
|
||||
|
||||
} // namespace dfly
|
||||
|
|
|
@ -276,6 +276,10 @@ uint32_t StringMap::ObjExpireTime(const void* obj) const {
|
|||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
void StringMap::ObjUpdateExpireTime(const void* obj, uint32_t ttl_sec) {
|
||||
return SdsUpdateExpireTime(obj, time_now() + ttl_sec, 8);
|
||||
}
|
||||
|
||||
void StringMap::ObjDelete(void* obj, bool has_ttl) const {
|
||||
sds s1 = (sds)obj;
|
||||
sds value = GetValue(s1);
|
||||
|
@ -283,8 +287,13 @@ void StringMap::ObjDelete(void* obj, bool has_ttl) const {
|
|||
sdsfree(s1);
|
||||
}
|
||||
|
||||
void* StringMap::ObjectClone(const void* obj, bool has_ttl) const {
|
||||
return nullptr;
|
||||
void* StringMap::ObjectClone(const void* obj, bool has_ttl, bool add_ttl) const {
|
||||
uint32_t ttl_sec = add_ttl ? 0 : (has_ttl ? ObjExpireTime(obj) : UINT32_MAX);
|
||||
sds str = (sds)obj;
|
||||
auto pair = detail::SdsPair(str, GetValue(str));
|
||||
auto [newkey, sdsval_tag] = CreateEntry(pair->first, pair->second, time_now(), ttl_sec);
|
||||
|
||||
return (void*)newkey;
|
||||
}
|
||||
|
||||
detail::SdsPair StringMap::iterator::BreakToPair(void* obj) {
|
||||
|
|
|
@ -100,9 +100,9 @@ class StringMap : public DenseSet {
|
|||
|
||||
using IteratorBase::ExpiryTime;
|
||||
using IteratorBase::HasExpiry;
|
||||
using IteratorBase::SetExpiryTime;
|
||||
};
|
||||
|
||||
// Returns true if field was added
|
||||
// otherwise updates its value and returns false.
|
||||
bool AddOrUpdate(std::string_view field, std::string_view value, uint32_t ttl_sec = UINT32_MAX);
|
||||
|
||||
|
@ -114,7 +114,7 @@ class StringMap : public DenseSet {
|
|||
|
||||
bool Contains(std::string_view s1) const;
|
||||
|
||||
/// @brief Returns value of the key or nullptr if key not found.
|
||||
/// @brief Returns value of the key or an empty iterator if key not found.
|
||||
/// @param key
|
||||
/// @return sds
|
||||
iterator Find(std::string_view member) {
|
||||
|
@ -157,8 +157,9 @@ class StringMap : public DenseSet {
|
|||
bool ObjEqual(const void* left, const void* right, uint32_t right_cookie) const final;
|
||||
size_t ObjectAllocSize(const void* obj) const final;
|
||||
uint32_t ObjExpireTime(const void* obj) const final;
|
||||
void ObjDelete(void* obj, bool has_ttl) const final;
|
||||
void* ObjectClone(const void* obj, bool has_ttl) const final;
|
||||
void ObjUpdateExpireTime(const void* obj, uint32_t ttl_sec) override;
|
||||
void ObjDelete(void* obj, bool has_ttl) const override;
|
||||
void* ObjectClone(const void* obj, bool has_ttl, bool add_ttl) const final;
|
||||
};
|
||||
|
||||
} // namespace dfly
|
||||
|
|
|
@ -127,6 +127,25 @@ TEST_F(StringMapTest, IterateExpired) {
|
|||
EXPECT_EQ(it, sm_->end());
|
||||
}
|
||||
|
||||
TEST_F(StringMapTest, SetFieldExpireHasExpiry) {
|
||||
EXPECT_TRUE(sm_->AddOrUpdate("k1", "v1", 5));
|
||||
auto k = sm_->Find("k1");
|
||||
EXPECT_TRUE(k.HasExpiry());
|
||||
EXPECT_EQ(k.ExpiryTime(), 5);
|
||||
k.SetExpiryTime(1);
|
||||
EXPECT_TRUE(k.HasExpiry());
|
||||
EXPECT_EQ(k.ExpiryTime(), 1);
|
||||
}
|
||||
|
||||
TEST_F(StringMapTest, SetFieldExpireNoHasExpiry) {
|
||||
EXPECT_TRUE(sm_->AddOrUpdate("k1", "v1"));
|
||||
auto k = sm_->Find("k1");
|
||||
EXPECT_FALSE(k.HasExpiry());
|
||||
k.SetExpiryTime(1);
|
||||
EXPECT_TRUE(k.HasExpiry());
|
||||
EXPECT_EQ(k.ExpiryTime(), 1);
|
||||
}
|
||||
|
||||
unsigned total_wasted_memory = 0;
|
||||
|
||||
TEST_F(StringMapTest, ReallocIfNeeded) {
|
||||
|
|
|
@ -27,7 +27,7 @@ inline bool MayHaveTtl(sds s) {
|
|||
|
||||
sds AllocImmutableWithTtl(uint32_t len, uint32_t at) {
|
||||
sds res = AllocSdsWithSpace(len, sizeof(at));
|
||||
absl::little_endian::Store32(res + len + 1, at);
|
||||
absl::little_endian::Store32(res + len + 1, at); // Save TTL
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -43,22 +43,8 @@ bool StringSet::AddSds(sds s1) {
|
|||
}
|
||||
|
||||
bool StringSet::Add(string_view src, uint32_t ttl_sec) {
|
||||
DCHECK_GT(ttl_sec, 0u); // ttl_sec == 0 would mean find and delete immediately
|
||||
|
||||
sds newsds = nullptr;
|
||||
bool has_ttl = false;
|
||||
|
||||
if (ttl_sec == UINT32_MAX) {
|
||||
newsds = sdsnewlen(src.data(), src.size());
|
||||
} else {
|
||||
uint32_t at = time_now() + ttl_sec;
|
||||
DCHECK_LT(time_now(), at);
|
||||
|
||||
newsds = AllocImmutableWithTtl(src.size(), at);
|
||||
if (!src.empty())
|
||||
memcpy(newsds, src.data(), src.size());
|
||||
has_ttl = true;
|
||||
}
|
||||
sds newsds = MakeSetSds(src, ttl_sec);
|
||||
bool has_ttl = ttl_sec != UINT32_MAX;
|
||||
|
||||
if (AddOrFindObj(newsds, has_ttl) != nullptr) {
|
||||
sdsfree(newsds);
|
||||
|
@ -129,22 +115,32 @@ uint32_t StringSet::ObjExpireTime(const void* str) const {
|
|||
return absl::little_endian::Load32(ttlptr);
|
||||
}
|
||||
|
||||
void StringSet::ObjUpdateExpireTime(const void* obj, uint32_t ttl_sec) {
|
||||
return SdsUpdateExpireTime(obj, time_now() + ttl_sec, 0);
|
||||
}
|
||||
|
||||
void StringSet::ObjDelete(void* obj, bool has_ttl) const {
|
||||
sdsfree((sds)obj);
|
||||
}
|
||||
|
||||
void* StringSet::ObjectClone(const void* obj, bool has_ttl) const {
|
||||
void* StringSet::ObjectClone(const void* obj, bool has_ttl, bool add_ttl) const {
|
||||
sds src = (sds)obj;
|
||||
if (has_ttl) {
|
||||
size_t slen = sdslen(src);
|
||||
char* ttlptr = src + slen + 1;
|
||||
uint32_t at = absl::little_endian::Load32(ttlptr);
|
||||
sds newsds = AllocImmutableWithTtl(slen, at);
|
||||
if (slen)
|
||||
memcpy(newsds, src, slen);
|
||||
string_view sv{src, sdslen(src)};
|
||||
uint32_t ttl_sec = add_ttl ? 0 : (has_ttl ? ObjExpireTime(obj) : UINT32_MAX);
|
||||
return (void*)MakeSetSds(sv, ttl_sec);
|
||||
}
|
||||
|
||||
sds StringSet::MakeSetSds(string_view src, uint32_t ttl_sec) const {
|
||||
if (ttl_sec != UINT32_MAX) {
|
||||
uint32_t at = time_now() + ttl_sec;
|
||||
|
||||
sds newsds = AllocImmutableWithTtl(src.size(), at);
|
||||
if (!src.empty())
|
||||
memcpy(newsds, src.data(), src.size());
|
||||
return newsds;
|
||||
}
|
||||
return sdsnewlen(src, sdslen(src));
|
||||
|
||||
return sdsnewlen(src.data(), src.size());
|
||||
}
|
||||
|
||||
} // namespace dfly
|
||||
|
|
|
@ -88,6 +88,7 @@ class StringSet : public DenseSet {
|
|||
|
||||
using IteratorBase::ExpiryTime;
|
||||
using IteratorBase::HasExpiry;
|
||||
using IteratorBase::SetExpiryTime;
|
||||
};
|
||||
|
||||
iterator begin() {
|
||||
|
@ -111,8 +112,10 @@ class StringSet : public DenseSet {
|
|||
|
||||
size_t ObjectAllocSize(const void* s1) const override;
|
||||
uint32_t ObjExpireTime(const void* obj) const override;
|
||||
void ObjUpdateExpireTime(const void* obj, uint32_t ttl_sec) override;
|
||||
void ObjDelete(void* obj, bool has_ttl) const override;
|
||||
void* ObjectClone(const void* obj, bool has_ttl) const override;
|
||||
void* ObjectClone(const void* obj, bool has_ttl, bool add_ttl) const override;
|
||||
sds MakeSetSds(std::string_view src, uint32_t ttl_sec) const;
|
||||
};
|
||||
|
||||
} // end namespace dfly
|
||||
|
|
|
@ -377,6 +377,25 @@ TEST_F(StringSetTest, Iteration) {
|
|||
EXPECT_EQ(to_insert.size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(StringSetTest, SetFieldExpireHasExpiry) {
|
||||
EXPECT_TRUE(ss_->Add("k1", 100));
|
||||
auto k = ss_->Find("k1");
|
||||
EXPECT_TRUE(k.HasExpiry());
|
||||
EXPECT_EQ(k.ExpiryTime(), 100);
|
||||
k.SetExpiryTime(1);
|
||||
EXPECT_TRUE(k.HasExpiry());
|
||||
EXPECT_EQ(k.ExpiryTime(), 1);
|
||||
}
|
||||
|
||||
TEST_F(StringSetTest, SetFieldExpireNoHasExpiry) {
|
||||
EXPECT_TRUE(ss_->Add("k1"));
|
||||
auto k = ss_->Find("k1");
|
||||
EXPECT_FALSE(k.HasExpiry());
|
||||
k.SetExpiryTime(10);
|
||||
EXPECT_TRUE(k.HasExpiry());
|
||||
EXPECT_EQ(k.ExpiryTime(), 10);
|
||||
}
|
||||
|
||||
TEST_F(StringSetTest, Ttl) {
|
||||
EXPECT_TRUE(ss_->Add("bla"sv, 1));
|
||||
EXPECT_FALSE(ss_->Add("bla"sv, 1));
|
||||
|
|
Loading…
Reference in a new issue