Skip to content

Commit 05eb323

Browse files
authored
fix(server): Fix #207 (#208)
1. Erase expiry data from the expire table in case of evictions. 2. Add test that covers the bug. Signed-off-by: Roman Gershman <[email protected]>
1 parent d2b7987 commit 05eb323

File tree

10 files changed

+52
-5
lines changed

10 files changed

+52
-5
lines changed

src/redis/object.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1175,7 +1175,7 @@ sds getMemoryDoctorReport(void) {
11751175
return s;
11761176
}
11771177

1178-
#endif
1178+
11791179

11801180
/* Set the object LRU/LFU depending on server.maxmemory_policy.
11811181
* The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU.
@@ -1210,3 +1210,4 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
12101210
return 0;
12111211
}
12121212

1213+
#endif

src/server/db_slice.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ unsigned PrimeEvictionPolicy::Evict(const PrimeTable::HotspotBuckets& eb, PrimeT
124124
auto last_slot_it = bucket_it;
125125
last_slot_it += (PrimeTable::kBucketWidth - 1);
126126
if (!last_slot_it.is_done()) {
127+
if (last_slot_it->second.HasExpire()) {
128+
ExpireTable* expire_tbl = db_slice_->GetTables(db_indx_).second;
129+
CHECK_EQ(1u, expire_tbl->Erase(last_slot_it->first));
130+
}
127131
UpdateStatsOnDeletion(last_slot_it, db_slice_->MutableStats(db_indx_));
128132
}
129133
CHECK(me->ShiftRight(bucket_it));
@@ -446,7 +450,6 @@ bool DbSlice::UpdateExpire(DbIndex db_ind, PrimeIterator it, uint64_t at) {
446450

447451
if (!it->second.HasExpire() && at) {
448452
uint64_t delta = at - expire_base_[0]; // TODO: employ multigen expire updates.
449-
450453
CHECK(db.expire.Insert(it->first.AsRef(), ExpirePeriod(delta)).second);
451454
it->second.SetExpire(true);
452455

src/server/db_slice.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ class DbSlice {
237237
return db_arr_;
238238
}
239239

240+
void TEST_EnableCacheMode() {
241+
caching_mode_ = 1;
242+
}
243+
240244
private:
241245
void CreateDb(DbIndex index);
242246

src/server/dragonfly_test.cc

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extern "C" {
77
#include "redis/zmalloc.h"
88
}
99

10+
#include <absl/flags/reflection.h>
1011
#include <absl/strings/ascii.h>
1112
#include <absl/strings/str_join.h>
1213
#include <absl/strings/strip.h>
@@ -444,7 +445,7 @@ TEST_F(DflyEngineTest, OOM) {
444445
max_memory_limit = 0;
445446
size_t i = 0;
446447
RespExpr resp;
447-
for (; i < 10000; i += 3) {
448+
for (; i < 5000; i += 3) {
448449
resp = Run({"mset", StrCat("key", i), "bar", StrCat("key", i + 1), "bar", StrCat("key", i + 2),
449450
"bar"});
450451
if (resp != "OK")
@@ -464,7 +465,7 @@ TEST_F(DflyEngineTest, OOM) {
464465
}
465466
run_args.push_back("bar");
466467

467-
for (unsigned i = 0; i < 10000; ++i) {
468+
for (unsigned i = 0; i < 5000; ++i) {
468469
run_args[1] = StrCat("key", cmd, i);
469470
resp = Run(run_args);
470471

@@ -477,6 +478,27 @@ TEST_F(DflyEngineTest, OOM) {
477478
}
478479
}
479480

481+
/// Reproduces the case where items with expiry data were evicted,
482+
/// and then written with the same key.
483+
TEST_F(DflyEngineTest, Bug207) {
484+
shard_set->TEST_EnableHeartBeat();
485+
shard_set->TEST_EnableCacheMode();
486+
487+
max_memory_limit = 0;
488+
489+
ssize_t i = 0;
490+
RespExpr resp;
491+
for (; i < 5000; ++i) {
492+
resp = Run({"setex", StrCat("key", i), "30", "bar"});
493+
// we evict some items because 5000 is too much when max_memory_limit is zero.
494+
ASSERT_EQ(resp, "OK");
495+
}
496+
497+
for (; i > 0; --i) {
498+
resp = Run({"setex", StrCat("key", i), "30", "bar"});
499+
}
500+
}
501+
480502
TEST_F(DflyEngineTest, PSubscribe) {
481503
single_response_ = false;
482504
auto resp = pp_->at(1)->Await([&] { return Run({"psubscribe", "a*", "b*"}); });

src/server/engine_shard_set.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,4 +381,8 @@ void EngineShardSet::TEST_EnableHeartBeat() {
381381
RunBriefInParallel([](EngineShard* shard) { shard->TEST_EnableHeartbeat(); });
382382
}
383383

384+
void EngineShardSet::TEST_EnableCacheMode() {
385+
RunBriefInParallel([](EngineShard* shard) { shard->db_slice().TEST_EnableCacheMode(); });
386+
}
387+
384388
} // namespace dfly

src/server/engine_shard_set.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class EngineShardSet {
226226

227227
// Used in tests
228228
void TEST_EnableHeartBeat();
229+
void TEST_EnableCacheMode();
229230

230231
private:
231232
void InitThreadLocal(util::ProactorBase* pb, bool update_db_time);

src/server/string_family.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ void StringFamily::ExtendGeneric(CmdArgList args, bool prepend, ConnectionContex
501501
builder->SendSetSkipped();
502502
}
503503

504+
/// (P)SETEX key seconds value
504505
void StringFamily::SetExGeneric(bool seconds, CmdArgList args, ConnectionContext* cntx) {
505506
string_view key = ArgS(args, 1);
506507
string_view ex = ArgS(args, 2);

src/server/table.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ namespace dfly {
1010

1111
#define ADD(x) (x) += o.x
1212

13+
// It should be const, but we override this variable in our tests so that they run faster.
14+
unsigned kInitSegmentLog = 3;
15+
1316
DbTableStats& DbTableStats::operator+=(const DbTableStats& o) {
1417
constexpr size_t kDbSz = sizeof(DbTableStats);
1518
static_assert(kDbSz == 56);
@@ -26,7 +29,7 @@ DbTableStats& DbTableStats::operator+=(const DbTableStats& o) {
2629
}
2730

2831
DbTable::DbTable(std::pmr::memory_resource* mr)
29-
: prime(2, detail::PrimeTablePolicy{}, mr),
32+
: prime(kInitSegmentLog, detail::PrimeTablePolicy{}, mr),
3033
expire(0, detail::ExpireTablePolicy{}, mr),
3134
mcflag(0, detail::ExpireTablePolicy{}, mr) {
3235
}

src/server/table.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ struct DbTable : boost::intrusive_ref_counter<DbTable, boost::thread_unsafe_coun
7171
void Release(IntentLock::Mode mode, std::string_view key, unsigned count);
7272
};
7373

74+
// We use reference counting semantics of DbTable when doing snapshotting.
75+
// There we need to preserve the copy of the table in case someone flushes it during
76+
// the snapshot process. We copy the pointers in StartSnapshotInShard function.
7477
using DbTableArray = std::vector<boost::intrusive_ptr<DbTable>>;
7578

7679
} // namespace dfly

src/server/test_utils.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ using namespace std;
2323
ABSL_DECLARE_FLAG(string, dbfilename);
2424

2525
namespace dfly {
26+
27+
extern unsigned kInitSegmentLog;
28+
2629
using MP = MemcacheParser;
2730
using namespace util;
2831
using namespace testing;
@@ -113,6 +116,8 @@ BaseFamilyTest::~BaseFamilyTest() {
113116
}
114117

115118
void BaseFamilyTest::SetUpTestSuite() {
119+
kInitSegmentLog = 1;
120+
116121
absl::SetFlag(&FLAGS_dbfilename, "");
117122
init_zmalloc_threadlocal(mi_heap_get_backing());
118123
}

0 commit comments

Comments
 (0)