From d74aff6d2a04c0bebff85d5c8e43d4eb42dae939 Mon Sep 17 00:00:00 2001 From: Abhijat Malviya Date: Sat, 10 May 2025 11:08:46 +0530 Subject: [PATCH] fix(hset_family): Fix val being overwritten by TTL When hset is loaded from rdb, if a ttl is specified by the user, the code recreates a string_view on top of the same memory location, tset_blob_, that was used for val earlier. This causes the val string view to point to the same value now as TTL has. For example if the val string view was 'x' earlier (points to tset_blob_, size 1), and ttl is 7777777, val now becomes '7'. To fix this val is now given it's own string. TTL is kept as string view as the pointer is not reused anywhere in the following loop. Signed-off-by: Abhijat Malviya --- src/server/rdb_load.cc | 3 +- tests/dragonfly/replication_test.py | 43 +++++++++++++++++++---------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/server/rdb_load.cc b/src/server/rdb_load.cc index 01b2d8653..a3444e92a 100644 --- a/src/server/rdb_load.cc +++ b/src/server/rdb_load.cc @@ -482,11 +482,12 @@ void RdbLoaderBase::OpaqueObjLoader::CreateHMap(const LoadTrace* ltrace) { } }); std::string key; + std::string val; for (size_t i = 0; i < ltrace->arr.size(); i += increment) { // ToSV may reference an internal buffer, therefore we can use only before the // next call to ToSV. To workaround, copy the key locally. key = ToSV(ltrace->arr[i].rdb_var); - string_view val = ToSV(ltrace->arr[i + 1].rdb_var); + val = ToSV(ltrace->arr[i + 1].rdb_var); if (ec_) return; diff --git a/tests/dragonfly/replication_test.py b/tests/dragonfly/replication_test.py index 3e1bba444..6a31de040 100644 --- a/tests/dragonfly/replication_test.py +++ b/tests/dragonfly/replication_test.py @@ -1,23 +1,18 @@ -import random - -from itertools import chain, repeat -import re -import pytest -import asyncio -import async_timeout import platform -import pymemcache -import logging +import shutil import tarfile import urllib.request -import shutil -from redis import asyncio as aioredis -from .utility import * -from .instance import DflyInstanceFactory, DflyInstance -from .seeder import Seeder as SeederV2 +from itertools import chain, repeat + +import async_timeout +import pymemcache + from . import dfly_args +from .instance import DflyInstanceFactory, DflyInstance from .proxy import Proxy from .seeder import DebugPopulateSeeder +from .seeder import Seeder as SeederV2 +from .utility import * ADMIN_PORT = 1211 @@ -3115,3 +3110,23 @@ async def test_partial_replication_on_same_source_master(df_factory, use_takeove lines = replica2.find_in_logs(f"Started full with localhost:{replica1.port}") assert len(lines) == 0 assert len(replica1.find_in_logs("No partial sync due to diff")) > 0 + + +async def test_replicate_hset_with_expiry(df_factory: DflyInstanceFactory): + master = df_factory.create(proactor_threads=2) + replica = df_factory.create(proactor_threads=2) + + master.start() + replica.start() + + cm = master.client() + await cm.execute_command("HSETEX key 86400 name 1234") + + cr = replica.client() + await cr.execute_command(f"REPLICAOF localhost {master.port}") + await wait_available_async(cr) + + result = await cr.hgetall("key") + + assert "name" in result + assert result["name"] == "1234"