Skip to content

Commit 957ca30

Browse files
suxb201claude
andcommitted
fix: use FUNCTION LOAD REPLACE when restoring function libraries
Without REPLACE, sync fails with "ERR Library 'xxx' already exists" when the destination already holds the library — for example after a redis-shake restart, retry, or against a destination that was previously synced. Fixes #1044 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent a2a7e4e commit 957ca30

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

internal/rdb/rdb.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func (ld *Loader) parseRDBEntry(ctx context.Context, rd *bufio.Reader) {
145145
function := structure.ReadString(rd)
146146
log.Debugf("function: %s", function)
147147
e := entry.NewEntry()
148-
e.Argv = []string{"function", "load", function}
148+
e.Argv = []string{"function", "load", "replace", function}
149149
ld.ch <- e
150150
case kFlagModuleAux:
151151
moduleId := structure.ReadLength(rd) // module id

tests/cases/function_library.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import pybbt
2+
3+
import helpers as h
4+
from helpers.constant import REDIS_SERVER_VERSION
5+
6+
7+
SRC_LIB = """#!lua name=mylib
8+
redis.register_function('myfunc', function() return 'src_version' end)
9+
"""
10+
11+
DST_LIB = """#!lua name=mylib
12+
redis.register_function('myfunc', function() return 'dst_version' end)
13+
"""
14+
15+
16+
@pybbt.case()
17+
def test_function_library_rdb_replace_existing():
18+
"""RDB sync must overwrite a function library that already exists on dst.
19+
20+
Reproduces issue #1044: without REPLACE, FUNCTION LOAD fails with
21+
"ERR Library 'mylib' already exists" when dst already holds the library
22+
(e.g. after a redis-shake restart or retry).
23+
"""
24+
if REDIS_SERVER_VERSION < 7.0:
25+
return
26+
27+
src = h.Redis()
28+
dst = h.Redis()
29+
30+
src.do("function", "load", SRC_LIB)
31+
pybbt.ASSERT_TRUE(src.do("save"))
32+
33+
dst.do("function", "load", DST_LIB)
34+
pybbt.ASSERT_EQ(dst.do("fcall", "myfunc", 0), b"dst_version")
35+
36+
opts = h.ShakeOpts.create_rdb_opts(f"{src.dir}/dump.rdb", dst)
37+
pybbt.log(f"opts: {opts}")
38+
h.Shake.run_once(opts)
39+
40+
pybbt.ASSERT_EQ(dst.do("fcall", "myfunc", 0), b"src_version")
41+
42+
43+
@pybbt.case()
44+
def test_function_library_sync_replace_existing():
45+
"""Same as the RDB case, but exercising the sync_reader path."""
46+
if REDIS_SERVER_VERSION < 7.0:
47+
return
48+
49+
src = h.Redis()
50+
dst = h.Redis()
51+
52+
src.do("function", "load", SRC_LIB)
53+
dst.do("function", "load", DST_LIB)
54+
55+
opts = h.ShakeOpts.create_sync_opts(src, dst)
56+
pybbt.log(f"opts: {opts}")
57+
shake = h.Shake(opts)
58+
59+
try:
60+
shake.wait_for_sync(timeout=10)
61+
except Exception as e:
62+
with open(f"{shake.dir}/data/shake.log") as f:
63+
pybbt.log(f.read())
64+
raise e
65+
66+
pybbt.ASSERT_EQ(dst.do("fcall", "myfunc", 0), b"src_version")

0 commit comments

Comments
 (0)