Skip to content

Commit e426a6f

Browse files
authored
fix: correct per-type memory accounting when object type changes in AutoUpdater (#7142)
AutoUpdater::Run() applied the full delta (new_size - orig_size) to the current object type. When AddOrUpdate replaces a value with a different type (e.g., SORT STORE overwrites a SET with a LIST), the original bytes were tracked under the old type but the delta was subtracted from the new type's counter — which never had those bytes — causing an underflow crash. - Add orig_obj_type to AutoUpdater::Fields, captured at construction time - In Run(): when type changed, separately subtract orig_size from old type and add current_size to new type, instead of applying a signed delta to the new type - In ReduceHeapUsage(): use orig_obj_type for correct accounting, then sync it to current type so subsequent Run() sees consistent state - In AddTypeMemoryUsage(): crash with FATAL in debug builds on underflow (was ERROR) and log the per-type counter value instead of obj_memory_usage Signed-off-by: Roman Gershman <roman@dragonflydb.io>
1 parent 975b6e0 commit e426a6f

3 files changed

Lines changed: 29 additions & 9 deletions

File tree

src/server/db_slice.cc

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,10 @@ DbSlice::AutoUpdater::~AutoUpdater() {
473473
}
474474

475475
void DbSlice::AutoUpdater::ReduceHeapUsage() {
476-
AccountObjectMemory(fields_.key, fields_.it->second.ObjType(), -fields_.orig_value_heap_size,
476+
AccountObjectMemory(fields_.key, fields_.orig_obj_type, -fields_.orig_value_heap_size,
477477
fields_.db_slice->GetDBTable(fields_.db_ind));
478-
fields_.orig_value_heap_size = 0; // Reset to avoid double accounting.
478+
fields_.orig_value_heap_size = 0; // Reset to avoid double accounting.
479+
fields_.orig_obj_type = fields_.it->second.ObjType(); // Sync type after accounting.
479480
}
480481

481482
void DbSlice::AutoUpdater::Run() {
@@ -490,10 +491,22 @@ void DbSlice::AutoUpdater::Run() {
490491

491492
CHECK_NE(fields_.db_slice, nullptr);
492493

493-
ssize_t delta = static_cast<int64_t>(fields_.it->second.MallocUsed()) -
494-
static_cast<int64_t>(fields_.orig_value_heap_size);
495-
AccountObjectMemory(fields_.key, fields_.it->second.ObjType(), delta,
496-
fields_.db_slice->GetDBTable(fields_.db_ind));
494+
CompactObjType current_type = fields_.it->second.ObjType();
495+
int64_t current_size = static_cast<int64_t>(fields_.it->second.MallocUsed());
496+
DbTable* table = fields_.db_slice->GetDBTable(fields_.db_ind);
497+
498+
if (current_type != fields_.orig_obj_type) {
499+
// Type changed: remove old size from old type, add new size to new type separately.
500+
// Applying (current_size - orig_size) to the new type would incorrectly subtract
501+
// from a counter that never had the original bytes added to it.
502+
AccountObjectMemory(fields_.key, fields_.orig_obj_type,
503+
-static_cast<int64_t>(fields_.orig_value_heap_size), table);
504+
AccountObjectMemory(fields_.key, current_type, current_size, table);
505+
} else {
506+
ssize_t delta = current_size - static_cast<int64_t>(fields_.orig_value_heap_size);
507+
AccountObjectMemory(fields_.key, current_type, delta, table);
508+
}
509+
497510
fields_.db_slice->PostUpdate(fields_.db_ind, fields_.key);
498511
Cancel(); // Reset to not run again
499512
}
@@ -508,7 +521,8 @@ DbSlice::AutoUpdater::AutoUpdater(DbIndex db_ind, std::string_view key, const It
508521
.db_ind = db_ind,
509522
.it = it,
510523
.key = key,
511-
.orig_value_heap_size = it->second.MallocUsed()} {
524+
.orig_value_heap_size = it->second.MallocUsed(),
525+
.orig_obj_type = it->second.ObjType()} {
512526
DCHECK(IsValid(it));
513527
}
514528

src/server/db_slice.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ class DbSlice {
175175

176176
// The following fields are calculated at init time
177177
size_t orig_value_heap_size = 0;
178+
CompactObjType orig_obj_type = 0;
178179
};
179180

180181
AutoUpdater(DbIndex db_ind, std::string_view key, const Iterator& it, DbSlice* db_slice);

src/server/table.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,13 @@ void DbTableStats::AddTypeMemoryUsage(unsigned type, int64_t delta) {
2626
DCHECK_GE(obj_memory_usage, memory_usage_by_type[type]);
2727

2828
if (delta < 0 && memory_usage_by_type[type] < size_t(-delta)) {
29-
LOG_EVERY_T(ERROR, 1) << "Encountered underflow memory usage when aggregating per-type memory: "
30-
<< obj_memory_usage << " + " << delta << ", type: " << type;
29+
#ifdef NDEBUG
30+
LOG_EVERY_T(ERROR, 1)
31+
#else
32+
LOG_EVERY_T(FATAL, 1)
33+
#endif
34+
<< "Encountered underflow memory usage when aggregating per-type memory: "
35+
<< memory_usage_by_type[type] << " + " << delta << ", type: " << type;
3136

3237
// Truncate delta to avoid underflow, but keep the memory usage consistent with the sum of
3338
// per-type usage.

0 commit comments

Comments
 (0)