I extended redis 7.0 and add happendCommand to append string for hash field value. The code can work and append string successfully, but after several calls, the server would crush. So there must be something wrong in the code, please give me some help, thanks a lot.
`
void happendCommand(client c) { robj o, *old; size_t totlen;
//check key
if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;
if (hashTypeExists(o, c->argv[2]->ptr)) {
old = hashTypeGetValueObject(o,c->argv[2]->ptr);
totlen = stringObjectLen(old)+sdslen(c->argv[3]->ptr);
if (checkStringLength(c,totlen) != C_OK){
addReply(c, shared.err);
return;
}
sds new_value = sdscatsds(old->ptr,c->argv[3]->ptr);
//hashTypeDelete(o,c->argv[2]->ptr);//comment or not, still crush
hashTypeSet(o,c->argv[2]->ptr,new_value ,HASH_SET_TAKE_VALUE);
//decrRefCount(old); // comment or not, still crush
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty ++;
}
else {
hashTypeTryConversion(o,c->argv,2,3);
hashTypeSet(o,c->argv[2]->ptr,sdscatsds(sdsempty(), c->argv[3]->ptr),HASH_SET_COPY);
addReply(c, shared.cone);
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_HASH,"hset",c->argv[1],c->db->id);
server.dirty++;
}
}
`
Comment From: sundb
Did you check if the encoding of the hash is LISTPACK or HT?
Comment From: zzeric
Always is LISTPACK.
Exception message is listpack.c:1276 'lpValidateNext(lp, &p, lpbytes)' is not true
Reference to lpAssertValidEntry / Validate that the entry doesn't reach outside the listpack allocation. / static inline void lpAssertValidEntry(unsigned char lp, size_t lpbytes, unsigned char p) { assert(lpValidateNext(lp, &p, lpbytes)); }
So it means the entry reach outside the listpack allocation. But I have no idea why cause this problem.
Comment From: oranagra
run it with valgrind, it'll probably help you find it. also, posting the stack trace of the crash here could help.
Comment From: zzeric
.O.O.O.O.O.O.O.==3083057== Invalid read of size 1 ==3083057== at 0x4843B60: __memcpy_chk (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==3083057== by 0x1FBE76: memcpy (string_fortified.h:34) ==3083057== by 0x1FBE76: memtest_preserving_test (memtest.c:317) ==3083057== by 0x1E13EB: memtest_test_linux_anonymous_maps (debug.c:1844) ==3083057== by 0x1E1518: doFastMemoryTest (debug.c:1885) ==3083057== by 0x1E469E: sigsegvHandler (debug.c:1982) ==3083057== by 0x49C53BF: ??? (in /usr/lib/x86_64-linux-gnu/libpthread-2.31.so) ==3083057== by 0x1D45F3: rewriteConfigAppendLine (config.c:1073) ==3083057== by 0x1D4BE9: rewriteConfigRewriteLine (config.c:1245) ==3083057== by 0x1D5FB3: getConfigDebugInfo (config.c:1648) ==3083057== by 0x1E0EAB: logConfigDebugInfo (debug.c:1735) ==3083057== by 0x1E165B: printCrashReport (debug.c:2004) ==3083057== by 0x1E17F2: _serverAssert (debug.c:1011) ==3083057== Address 0x54fffff is 834,543 bytes inside an unallocated block of size 3,980,240 in arena "client" ==3083057== ==3083057== Invalid read of size 1 ==3083057== at 0x4843B71: __memcpy_chk (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==3083057== by 0x1FBE76: memcpy (string_fortified.h:34) ==3083057== by 0x1FBE76: memtest_preserving_test (memtest.c:317) ==3083057== by 0x1E13EB: memtest_test_linux_anonymous_maps (debug.c:1844) ==3083057== by 0x1E1518: doFastMemoryTest (debug.c:1885) ==3083057== by 0x1E469E: sigsegvHandler (debug.c:1982) ==3083057== by 0x49C53BF: ??? (in /usr/lib/x86_64-linux-gnu/libpthread-2.31.so) ==3083057== by 0x1D45F3: rewriteConfigAppendLine (config.c:1073) ==3083057== by 0x1D4BE9: rewriteConfigRewriteLine (config.c:1245) ==3083057== by 0x1D5FB3: getConfigDebugInfo (config.c:1648) ==3083057== by 0x1E0EAB: logConfigDebugInfo (debug.c:1735) ==3083057== by 0x1E165B: printCrashReport (debug.c:2004) ==3083057== by 0x1E17F2: _serverAssert (debug.c:1011) ==3083057== Address 0x54ffffd is 834,541 bytes inside an unallocated block of size 3,980,240 in arena "client" ==3083057== ==3083057== Invalid write of size 8 ==3083057== at 0x1FB589: memtest_addressing (memtest.c:110) ==3083057== by 0x1FBEFB: memtest_preserving_test (memtest.c:320) ==3083057== by 0x1E13EB: memtest_test_linux_anonymous_maps (debug.c:1844) ==3083057== by 0x1E1518: doFastMemoryTest (debug.c:1885) ==3083057== by 0x1E469E: sigsegvHandler (debug.c:1982) ==3083057== by 0x49C53BF: ??? (in /usr/lib/x86_64-linux-gnu/libpthread-2.31.so) ==3083057== by 0x1D45F3: rewriteConfigAppendLine (config.c:1073) ==3083057== by 0x1D4BE9: rewriteConfigRewriteLine (config.c:1245) ==3083057== by 0x1D5FB3: getConfigDebugInfo (config.c:1648) ==3083057== by 0x1E0EAB: logConfigDebugInfo (debug.c:1735) ==3083057== by 0x1E165B: printCrashReport (debug.c:2004) ==3083057== by 0x1E17F2: _serverAssert (debug.c:1011) ==3083057== Address 0x5400000 is in a rwx anonymous segment ==3083057== ==3083057== Invalid read of size 8 ==3083057== at 0x1FB5C9: memtest_addressing (memtest.c:118) ==3083057== by 0x1FBEFB: memtest_preserving_test (memtest.c:320) ==3083057== by 0x1E13EB: memtest_test_linux_anonymous_maps (debug.c:1844) ==3083057== by 0x1E1518: doFastMemoryTest (debug.c:1885) ==3083057== by 0x1E469E: sigsegvHandler (debug.c:1982) ==3083057== by 0x49C53BF: ??? (in /usr/lib/x86_64-linux-gnu/libpthread-2.31.so) ==3083057== by 0x1D45F3: rewriteConfigAppendLine (config.c:1073) ==3083057== by 0x1D4BE9: rewriteConfigRewriteLine (config.c:1245) ==3083057== by 0x1D5FB3: getConfigDebugInfo (config.c:1648) ==3083057== by 0x1E0EAB: logConfigDebugInfo (debug.c:1735) ==3083057== by 0x1E165B: printCrashReport (debug.c:2004) ==3083057== by 0x1E17F2: _serverAssert (debug.c:1011) ==3083057== Address 0x5400000 is in a rwx anonymous segment ==3083057== ==3083057== Conditional jump or move depends on uninitialised value(s) ==3083057== at 0x1FB5D0: memtest_addressing (memtest.c:118) ==3083057== by 0x1FBEFB: memtest_preserving_test (memtest.c:320) ==3083057== by 0x1E13EB: memtest_test_linux_anonymous_maps (debug.c:1844) ==3083057== by 0x1E1518: doFastMemoryTest (debug.c:1885) ==3083057== by 0x1E469E: sigsegvHandler (debug.c:1982) ==3083057== by 0x49C53BF: ??? (in /usr/lib/x86_64-linux-gnu/libpthread-2.31.so) ==3083057== by 0x1D45F3: rewriteConfigAppendLine (config.c:1073) ==3083057== by 0x1D4BE9: rewriteConfigRewriteLine (config.c:1245) ==3083057== by 0x1D5FB3: getConfigDebugInfo (config.c:1648) ==3083057== by 0x1E0EAB: logConfigDebugInfo (debug.c:1735) ==3083057== by 0x1E165B: printCrashReport (debug.c:2004) ==3083057== by 0x1E17F2: _serverAssert (debug.c:1011) ==3083057== ==3083057== ==3083057== Process terminating with default action of signal 11 (SIGSEGV) ==3083057== Access not within mapped region at address 0xA800038 ==3083057== at 0x1FB76C: memtest_fill_random (memtest.c:162) ==3083057== by 0x1FBF13: memtest_preserving_test (memtest.c:321) ==3083057== by 0x1E13EB: memtest_test_linux_anonymous_maps (debug.c:1844) ==3083057== by 0x1E1518: doFastMemoryTest (debug.c:1885) ==3083057== by 0x1E469E: sigsegvHandler (debug.c:1982) ==3083057== by 0x49C53BF: ??? (in /usr/lib/x86_64-linux-gnu/libpthread-2.31.so) ==3083057== by 0x1D45F3: rewriteConfigAppendLine (config.c:1073) ==3083057== by 0x1D4BE9: rewriteConfigRewriteLine (config.c:1245) ==3083057== by 0x1D5FB3: getConfigDebugInfo (config.c:1648) ==3083057== by 0x1E0EAB: logConfigDebugInfo (debug.c:1735) ==3083057== by 0x1E165B: printCrashReport (debug.c:2004) ==3083057== by 0x1E17F2: _serverAssert (debug.c:1011) ==3083057== If you believe this happened as a result of a stack ==3083057== overflow in your program's main thread (unlikely but ==3083057== possible), you can try to increase the size of the ==3083057== main thread stack using the --main-stacksize= flag. ==3083057== The main thread stack size used in this run was 8388608. Fatal error: glibc detected an invalid stdio handle ==3083057== ==3083057== Process terminating with default action of signal 11 (SIGSEGV) ==3083057== General Protection Fault ==3083057== at 0x4A190FB: __clear_internal_signals (internal-signals.h:54) ==3083057== by 0x4A190FB: __libc_signal_block_app (internal-signals.h:75) ==3083057== by 0x4A190FB: raise (raise.c:40) ==3083057== by 0x49F8858: abort (abort.c:79) ==3083057== by 0x4A6314B: __libc_message.constprop.0 (libc_fatal.c:155) ==3083057== by 0x4A6345F: __libc_fatal (libc_fatal.c:164) ==3083057== by 0x4A63D6A: _IO_vtable_check (vtables.c:72) ==3083057== by 0x4A68E29: IO_validate_vtable (libioP.h:947) ==3083057== by 0x4A68E29: _IO_flush_all_lockp (genops.c:706) ==3083057== by 0x4A68F08: _IO_cleanup (genops.c:866) ==3083057== by 0x4B6EA8B: __libc_freeres (in /usr/lib/x86_64-linux-gnu/libc-2.31.so) ==3083057== by 0x48311C6: _vgnU_freeres (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_core-amd64-linux.so) ==3083057== by 0x5380FF7: ??? ==3083057== by 0x5300FF7: ??? ==3083057== by 0x7F: ??? ==3083057== ==3083057== HEAP SUMMARY: ==3083057== in use at exit: 80,592 bytes in 964 blocks ==3083057== total heap usage: 1,154 allocs, 190 frees, 133,863 bytes allocated ==3083057== ==3083057== 72 bytes in 3 blocks are possibly lost in loss record 251 of 356 ==3083057== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==3083057== by 0x4AEFED3: tsearch (tsearch.c:338) ==3083057== by 0x4A1C51A: __add_to_environ (setenv.c:231) ==3083057== by 0x484459F: setenv (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==3083057== by 0x20B55E: spt_copyenv (setproctitle.c:139) ==3083057== by 0x20B55E: spt_init (setproctitle.c:253) ==3083057== by 0x173F41: main (server.c:6816) ==3083057== ==3083057== 288 bytes in 1 blocks are possibly lost in loss record 287 of 356 ==3083057== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==3083057== by 0x40149CA: allocate_dtv (dl-tls.c:286) ==3083057== by 0x40149CA: _dl_allocate_tls (dl-tls.c:532) ==3083057== by 0x49BA322: allocate_stack (allocatestack.c:622) ==3083057== by 0x49BA322: pthread_create@@GLIBC_2.2.5 (pthread_create.c:660) ==3083057== by 0x295663: je_pthread_create_wrapper (background_thread.c:48) ==3083057== by 0x295663: background_thread_create_signals_masked (background_thread.c:365) ==3083057== by 0x295663: background_thread_create_locked (background_thread.c:573) ==3083057== by 0x295C99: je_background_threads_enable (background_thread.c:637) ==3083057== by 0x2A54D3: background_thread_ctl (ctl.c:1667) ==3083057== by 0x2BEC5A: je_ctl_byname (ctl.c:1313) ==3083057== by 0x18EAB9: set_jemalloc_bg_thread (zmalloc.c:597) ==3083057== by 0x17FD9C: InitServerLast (server.c:2637) ==3083057== by 0x1741BF: main (server.c:7023) ==3083057== ==3083057== 864 bytes in 3 blocks are possibly lost in loss record 334 of 356 ==3083057== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==3083057== by 0x40149CA: allocate_dtv (dl-tls.c:286) ==3083057== by 0x40149CA: _dl_allocate_tls (dl-tls.c:532) ==3083057== by 0x49BA322: allocate_stack (allocatestack.c:622) ==3083057== by 0x49BA322: pthread_create@@GLIBC_2.2.5 (pthread_create.c:660) ==3083057== by 0x1F9DD9: bioInit (bio.c:120) ==3083057== by 0x17FD8C: InitServerLast (server.c:2635) ==3083057== by 0x1741BF: main (server.c:7023) ==3083057== ==3083057== LEAK SUMMARY: ==3083057== definitely lost: 0 bytes in 0 blocks ==3083057== indirectly lost: 0 bytes in 0 blocks ==3083057== possibly lost: 1,224 bytes in 7 blocks ==3083057== still reachable: 79,368 bytes in 957 blocks ==3083057== suppressed: 0 bytes in 0 blocks ==3083057== Reachable blocks (those to which a pointer was found) are not shown. ==3083057== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==3083057== ==3083057== Use --track-origins=yes to see where uninitialised values come from ==3083057== For lists of detected and suppressed errors, rerun with: -s ==3083057== ERROR SUMMARY: 1210530 errors from 13 contexts (suppressed: 0 from 0)
Comment From: sundb
@zzeric Can you give more complete code about this command so we can help you?
Comment From: oranagra
so it doesn't crash (on a segfault), it fails on an assertion (all that valgrind text is irrelevant since it's part of the crash log code) what's the assertion about?
Comment From: sundb
The only reason I can think of is that listpack is treated as dict, resulting in listpack data corruption. Unless there is more code, it is difficult to find the real reason.
Comment From: zzeric
The only reason I can think of is that listpack is treated as dict, resulting in listpack data corruption. Unless there is more code, it is difficult to find the real reason.
Thank you!
The full code is simple, 1. add function define in server.h
void happendCommand(client *c);
- add happendCommand implement into t_hash.c,
void happendCommand(client c) { robj o, old,key,field,append; long long value; unsigned char *vstr; unsigned int vlen;
key = c->argv[1];
if ((o = hashTypeLookupWriteOrCreate(c,key)) == NULL) return;
//hashTypeTryConversion(o,c->argv,2,3);
field = c->argv[2];
append = c->argv[3];
if (hashTypeGetValue(o,field->ptr,&vstr,&vlen,&value) == C_OK) {
if(vstr){
old = createStringObject((char*)vstr,vlen);
sds new = sdscatsds(old->ptr,append->ptr);
hashTypeSet(o,key->ptr,new,HASH_SET_TAKE_VALUE);
freeStringObject(old);
}
else{
sds old_val = sdsfromlonglong(value);
sds new = sdscatsds(old_val,append->ptr);
hashTypeSet(o,key->ptr,new,HASH_SET_TAKE_VALUE);
sdsfree(old_val);
}
//addReplyLongLong(c,totlen);
}
else {
hashTypeSet(o,field->ptr,append->ptr,HASH_SET_COPY);
}
addReply(c, shared.cone);
signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_HASH,"happend",key,c->db->id);
server.dirty++;
}
- add happend related infomation into commands.c
/* HAPPEND *****/
/ HAPPEND history /
define HAPPEND_History NULL
/ HAPPEND tips /
define HAPPEND_tips NULL
/ HAPPEND argument table / struct redisCommandArg HAPPEND_Args[] = { {"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE}, {"field",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE}, {"append",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE}, {0} };
Comment From: sundb
sds old_val = sdsfromlonglong(value);
sds new = sdscatsds(old_val,append->ptr);
hashTypeSet(o,key->ptr,new,HASH_SET_TAKE_VALUE);
sdsfree(old_val); <-- can't free old sds here, you can remove this line and try again.
freeStringObject(old); is same.
sdscatsds will realloc old_val and return new sds, so free old val will result in releasing illegal memory or free new sds
Comment From: zzeric
c sds old_val = sdsfromlonglong(value); sds new = sdscatsds(old_val,append->ptr); hashTypeSet(o,key->ptr,new,HASH_SET_TAKE_VALUE); sdsfree(old_val); <-- can't free old sds here, you can remove this line and try again.
freeStringObject(old);is same.sdscatsdswill realloc old_val and return new sds, so free old val will result in releasing illegal memory or free new sds
Actually I did not free the old value at first,if I commented it,it will threw another exception.
------ STACK TRACE ------ EIP: /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(je_malloc_usable_size+0x89)[0x55c27edb4ea9]
Backtrace: /lib/x86_64-linux-gnu/libpthread.so.0(+0x153c0)[0x7f3d6be803c0] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(je_malloc_usable_size+0x89)[0x55c27edb4ea9] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(ztryrealloc_usable+0x4a)[0x55c27ecb857a] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(zrealloc+0x15)[0x55c27ecb8605] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(rewriteConfigAppendLine+0x26)[0x55c27ecfe4e6] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(rewriteConfigRewriteLine+0xf7)[0x55c27ecfeb77] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(getConfigDebugInfo+0x54)[0x55c27ecffeb4] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(logConfigDebugInfo+0xc)[0x55c27ed0adac] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(printCrashReport+0x1c)[0x55c27ed0b55c] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(_serverAssert+0x93)[0x55c27ed0b6f3] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(lpNext+0x98)[0x55c27ed67608] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(hashTypeGetFromListpack+0x8e)[0x55c27ecf7e1e] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(hashTypeGetValue+0x6c)[0x55c27ecf7f7c] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(happendCommand+0x66)[0x55c27ecf9616] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(call+0xdf)[0x55c27ecab2cf] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(processCommand+0x845)[0x55c27ecada45] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(processCommandAndResetClient+0x20)[0x55c27ecc3fb0] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(processInputBuffer+0xd8)[0x55c27ecc6d78] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(readQueryFromClient+0x2e8)[0x55c27ecca558] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(+0x1418dc)[0x55c27ed738dc] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(aeProcessEvents+0x1ea)[0x55c27eca228a] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(aeMain+0x1d)[0x55c27eca264d] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(main+0x32d)[0x55c27ec9e23d] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x7f3d6bca00b3] /home/zzeric/redis-7.0/src/redis-server 0.0.0.0:6388(_start+0x2e)[0x55c27ec9e88e]
Comment From: sundb
If old is EMBSTR, you can't use sdscatsds on it.
old = createStringObject((char*)vstr,vlen);
sds new = sdscatsds(old->ptr,append->ptr);
change it to:
sds old = sdsnewlen((char*)vstr,vlen);
sds new = sdscatsds(old,append->ptr);
Comment From: zzeric
If old is EMBSTR, you can't use
sdscatsdson it.
c old = createStringObject((char*)vstr,vlen); sds new = sdscatsds(old->ptr,append->ptr);change it to:
c sds old = sdsnewlen((char*)vstr,vlen); sds new = sdscatsds(old,append->ptr);
It works! Thanks my bro.
Comment From: zzeric
If old is EMBSTR, you can't use
sdscatsdson it.
c old = createStringObject((char*)vstr,vlen); sds new = sdscatsds(old->ptr,append->ptr);change it to:
c sds old = sdsnewlen((char*)vstr,vlen); sds new = sdscatsds(old,append->ptr);
a new problem is coming, if add bytes data into a field value, for example eval lua in redis-cli as
eval "redis.call('happend','a','field',struct.pack('<I<I<I',100,200,300))" 0 0
sdsnewlen((char*)vstr,vlen) can't read the bytes data correctly sdscatsds(old,append->ptr) also could not concat them in a right way
Is it possible to achieve that ?
Comment From: sundb
sds is binary-safe, it doesn't look like this code will be a problem, can you print them (old, new, append)?
Comment From: zzeric
sds is binary-safe, it doesn't look like this code will be a problem, can you print them (old, new, append)?
I try to print out the sds, just output a char “d”
Comment From: sundb
Is vlen 1? If it is 12, it means the length is right.
Comment From: zzeric
Is
vlen1? If it is 12, it means the length is right.
vlen is 12, serverLog just print out a char should be the serverLog method does not support binary. The first data set in is right, 127.0.0.1:6379>hget a field "\xc8\x00\x00\x00\xc8\x00\x00\x00,\x01\x00\x00" however when continue to append new data to the hash field, it does not work. It seams the sdscatsds method can not concat the old binary and the new ptr directly.
sds old = sdsnewlen((char*)vstr,vlen); sds empty = sdsnewlen("",0); sds tmp = sdscatsds(empty,append->ptr); sds new = sdscatsds(old,tmp);
After concat a non binary empty sds and the new ptr, then join the old and the tmp into a new sds, it works.
Comment From: sundb
Can you try again use following code:
void print_sds(sds s) {
for (unsigned int i = 0; i < sdslen(s); i++) {
printf("%02x|",((unsigned char*)s)[i]);
}
printf("\n");
}
sds old = sdsnewlen((char*)vstr,vlen);
print_sds(old);
print_sds(append->ptr);
sds new = sdscatsds(old,append->ptr);
print_sds(new);
Comment From: zzeric
Sorry, it's a bug of my code. hashTypeSet(o,key->ptr,new,HASH_SET_TAKE_VALUE); should be hashTypeSet(o,field->ptr,new,HASH_SET_TAKE_VALUE);
use print_sds function can print out the full binary sds.