Merge branch 'unstable' of https://github.com/antirez/redis into unstable

Former-commit-id: 9322d604eea7b48df3feff47ce2c04f82291228f
This commit is contained in:
John Sully 2019-03-21 20:15:59 -04:00
commit 8a9e9ff3f4
13 changed files with 86 additions and 52 deletions

View File

@ -1248,7 +1248,7 @@ int rewriteModuleObject(rio *r, robj *key, robj *o) {
RedisModuleIO io; RedisModuleIO io;
moduleValue *mv = ptrFromObj(o); moduleValue *mv = ptrFromObj(o);
moduleType *mt = mv->type; moduleType *mt = mv->type;
moduleInitIOContext(io,mt,r); moduleInitIOContext(io,mt,r,key);
mt->aof_rewrite(&io,key,mv->value); mt->aof_rewrite(&io,key,mv->value);
if (io.ctx) { if (io.ctx) {
moduleFreeContext(io.ctx); moduleFreeContext(io.ctx);

View File

@ -4776,7 +4776,7 @@ NULL
/* Generates a DUMP-format representation of the object 'o', adding it to the /* Generates a DUMP-format representation of the object 'o', adding it to the
* io stream pointed by 'rio'. This function can't fail. */ * io stream pointed by 'rio'. This function can't fail. */
void createDumpPayload(rio *payload, robj *o) { void createDumpPayload(rio *payload, robj *o, robj *key) {
unsigned char buf[2]; unsigned char buf[2];
uint64_t crc; uint64_t crc;
@ -4784,7 +4784,7 @@ void createDumpPayload(rio *payload, robj *o) {
* byte followed by the serialized object. This is understood by RESTORE. */ * byte followed by the serialized object. This is understood by RESTORE. */
rioInitWithBuffer(payload,sdsempty()); rioInitWithBuffer(payload,sdsempty());
serverAssert(rdbSaveObjectType(payload,o)); serverAssert(rdbSaveObjectType(payload,o));
serverAssert(rdbSaveObject(payload,o)); serverAssert(rdbSaveObject(payload,o,key));
/* Write the footer, this is how it looks like: /* Write the footer, this is how it looks like:
* ----------------+---------------------+---------------+ * ----------------+---------------------+---------------+
@ -4842,7 +4842,7 @@ void dumpCommand(client *c) {
} }
/* Create the DUMP encoded representation. */ /* Create the DUMP encoded representation. */
createDumpPayload(&payload,o); createDumpPayload(&payload,o,c->argv[1]);
/* Transfer to the client */ /* Transfer to the client */
dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr); dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr);
@ -4915,7 +4915,7 @@ void restoreCommand(client *c) {
rioInitWithBuffer(&payload,ptrFromObj(c->argv[3])); rioInitWithBuffer(&payload,ptrFromObj(c->argv[3]));
if (((type = rdbLoadObjectType(&payload)) == -1) || if (((type = rdbLoadObjectType(&payload)) == -1) ||
((obj = rdbLoadObject(type,&payload)) == NULL)) ((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
{ {
addReplyError(c,"Bad data format"); addReplyError(c,"Bad data format");
return; return;
@ -5203,7 +5203,7 @@ try_again:
/* Emit the payload argument, that is the serialized object using /* Emit the payload argument, that is the serialized object using
* the DUMP format. */ * the DUMP format. */
createDumpPayload(&payload,ov[j]); createDumpPayload(&payload,ov[j],kv[j]);
serverAssertWithInfo(c,NULL, serverAssertWithInfo(c,NULL,
rioWriteBulkString(&cmd,payload.io.buffer.ptr, rioWriteBulkString(&cmd,payload.io.buffer.ptr,
sdslen(payload.io.buffer.ptr))); sdslen(payload.io.buffer.ptr)));

View File

@ -3439,6 +3439,14 @@ RedisModuleCtx *RM_GetContextFromIO(RedisModuleIO *io) {
return io->ctx; return io->ctx;
} }
/* Returns a RedisModuleString with the name of the key currently saving or
* loading, when an IO data type callback is called. There is no guarantee
* that the key name is always available, so this may return NULL.
*/
const RedisModuleString *RM_GetKeyNameFromIO(RedisModuleIO *io) {
return io->key;
}
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Logging * Logging
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
@ -5195,6 +5203,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(RetainString); REGISTER_API(RetainString);
REGISTER_API(StringCompare); REGISTER_API(StringCompare);
REGISTER_API(GetContextFromIO); REGISTER_API(GetContextFromIO);
REGISTER_API(GetKeyNameFromIO);
REGISTER_API(BlockClient); REGISTER_API(BlockClient);
REGISTER_API(UnblockClient); REGISTER_API(UnblockClient);
REGISTER_API(IsBlockedReplyRequest); REGISTER_API(IsBlockedReplyRequest);

View File

@ -752,7 +752,7 @@ size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
/* Save a Redis object. /* Save a Redis object.
* Returns -1 on error, number of bytes written on success. */ * Returns -1 on error, number of bytes written on success. */
ssize_t rdbSaveObject(rio *rdb, robj *o) { ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
ssize_t n = 0, nwritten = 0; ssize_t n = 0, nwritten = 0;
if (o->type == OBJ_STRING) { if (o->type == OBJ_STRING) {
@ -967,7 +967,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
RedisModuleIO io; RedisModuleIO io;
moduleValue *mv = ptrFromObj(o); moduleValue *mv = ptrFromObj(o);
moduleType *mt = mv->type; moduleType *mt = mv->type;
moduleInitIOContext(io,mt,rdb); moduleInitIOContext(io,mt,rdb,key);
/* Write the "module" identifier as prefix, so that we'll be able /* Write the "module" identifier as prefix, so that we'll be able
* to call the right module during loading. */ * to call the right module during loading. */
@ -997,7 +997,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
* this length with very little changes to the code. In the future * this length with very little changes to the code. In the future
* we could switch to a faster solution. */ * we could switch to a faster solution. */
size_t rdbSavedObjectLen(robj *o) { size_t rdbSavedObjectLen(robj *o) {
ssize_t len = rdbSaveObject(NULL,o); ssize_t len = rdbSaveObject(NULL,o,NULL);
serverAssertWithInfo(NULL,o,len != -1); serverAssertWithInfo(NULL,o,len != -1);
return len; return len;
} }
@ -1039,7 +1039,7 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
/* Save type, key, value */ /* Save type, key, value */
if (rdbSaveObjectType(rdb,val) == -1) return -1; if (rdbSaveObjectType(rdb,val) == -1) return -1;
if (rdbSaveStringObject(rdb,key) == -1) return -1; if (rdbSaveStringObject(rdb,key) == -1) return -1;
if (rdbSaveObject(rdb,val) == -1) return -1; if (rdbSaveObject(rdb,val,key) == -1) return -1;
return 1; return 1;
} }
@ -1401,7 +1401,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
/* Load a Redis object of the specified type from the specified file. /* Load a Redis object of the specified type from the specified file.
* On success a newly allocated object is returned, otherwise NULL. */ * On success a newly allocated object is returned, otherwise NULL. */
robj *rdbLoadObject(int rdbtype, rio *rdb) { robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
robj *o = NULL, *ele, *dec; robj *o = NULL, *ele, *dec;
uint64_t len; uint64_t len;
unsigned int i; unsigned int i;
@ -1788,7 +1788,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
exit(1); exit(1);
} }
RedisModuleIO io; RedisModuleIO io;
moduleInitIOContext(io,mt,rdb); moduleInitIOContext(io,mt,rdb,key);
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2; io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
/* Call the rdb_load method of the module providing the 10 bit /* Call the rdb_load method of the module providing the 10 bit
* encoding version in the lower 10 bits of the module ID. */ * encoding version in the lower 10 bits of the module ID. */
@ -2044,7 +2044,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
/* Read key */ /* Read key */
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr; if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
/* Read value */ /* Read value */
if ((val = rdbLoadObject(type,rdb)) == NULL) goto eoferr; if ((val = rdbLoadObject(type,rdb,key)) == NULL) goto eoferr;
/* Check if the key already expired. This function is used when loading /* Check if the key already expired. This function is used when loading
* an RDB file from disk, either at startup, or when an RDB was * an RDB file from disk, either at startup, or when an RDB was
* received from the master. In the latter case, the master is * received from the master. In the latter case, the master is

View File

@ -144,9 +144,9 @@ int rdbSaveFile(char *filename, rdbSaveInfo *rsi);
int rdbSaveFd(int fd, rdbSaveInfo *rsi); int rdbSaveFd(int fd, rdbSaveInfo *rsi);
int rdbSaveS3(char *path, rdbSaveInfo *rsi); int rdbSaveS3(char *path, rdbSaveInfo *rsi);
int rdbLoadS3(char *path, rdbSaveInfo *rsi); int rdbLoadS3(char *path, rdbSaveInfo *rsi);
ssize_t rdbSaveObject(rio *rdb, robj *o); ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
size_t rdbSavedObjectLen(robj *o); size_t rdbSavedObjectLen(robj *o);
robj *rdbLoadObject(int type, rio *rdb); robj *rdbLoadObject(int type, rio *rdb, robj *key);
void backgroundSaveDoneHandler(int exitcode, int bysignal); void backgroundSaveDoneHandler(int exitcode, int bysignal);
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime); int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
robj *rdbLoadStringObject(rio *rdb); robj *rdbLoadStringObject(rio *rdb);

View File

@ -285,7 +285,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
rdbstate.keys++; rdbstate.keys++;
/* Read value */ /* Read value */
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE; rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr; if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
/* Check if the key already expired. */ /* Check if the key already expired. */
if (expiretime != -1 && expiretime < now) if (expiretime != -1 && expiretime < now)
rdbstate.already_expired++; rdbstate.already_expired++;

View File

@ -278,6 +278,7 @@ int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, Re
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str); void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b); int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io); RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io);
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void); long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len); void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele); void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele);
@ -442,6 +443,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(RetainString); REDISMODULE_GET_API(RetainString);
REDISMODULE_GET_API(StringCompare); REDISMODULE_GET_API(StringCompare);
REDISMODULE_GET_API(GetContextFromIO); REDISMODULE_GET_API(GetContextFromIO);
REDISMODULE_GET_API(GetKeyNameFromIO);
REDISMODULE_GET_API(Milliseconds); REDISMODULE_GET_API(Milliseconds);
REDISMODULE_GET_API(DigestAddStringBuffer); REDISMODULE_GET_API(DigestAddStringBuffer);
REDISMODULE_GET_API(DigestAddLongLong); REDISMODULE_GET_API(DigestAddLongLong);

View File

@ -1143,14 +1143,23 @@ void replicationCreateMasterClient(int fd, int dbid) {
if (dbid != -1) selectDb(server.master,dbid); if (dbid != -1) selectDb(server.master,dbid);
} }
void restartAOF() { /* This function will try to re-enable the AOF file after the
int retry = 10; * master-replica synchronization: if it fails after multiple attempts
while (retry-- && startAppendOnly() == C_ERR) { * the replica cannot be considered reliable and exists with an
serverLog(LL_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second."); * error. */
void restartAOFAfterSYNC() {
unsigned int tries, max_tries = 10;
for (tries = 0; tries < max_tries; ++tries) {
if (startAppendOnly() == C_OK) break;
serverLog(LL_WARNING,
"Failed enabling the AOF after successful master synchronization! "
"Trying it again in one second.");
sleep(1); sleep(1);
} }
if (!retry) { if (tries == max_tries) {
serverLog(LL_WARNING,"FATAL: this replica instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now."); serverLog(LL_WARNING,
"FATAL: this replica instance finished the synchronization with "
"its master, but the AOF can't be turned on. Exiting now.");
exit(1); exit(1);
} }
} }
@ -1339,7 +1348,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
cancelReplicationHandshake(); cancelReplicationHandshake();
/* Re-enable the AOF if we disabled it earlier, in order to restore /* Re-enable the AOF if we disabled it earlier, in order to restore
* the original configuration. */ * the original configuration. */
if (aof_is_enabled) restartAOF(); if (aof_is_enabled) restartAOFAfterSYNC();
return; return;
} }
/* Final setup of the connected slave <- master link */ /* Final setup of the connected slave <- master link */
@ -1364,7 +1373,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Restart the AOF subsystem now that we finished the sync. This /* Restart the AOF subsystem now that we finished the sync. This
* will trigger an AOF rewrite, and when done will start appending * will trigger an AOF rewrite, and when done will start appending
* to the new file. */ * to the new file. */
if (aof_is_enabled) restartAOF(); if (aof_is_enabled) restartAOFAfterSYNC();
} }
return; return;

View File

@ -719,7 +719,7 @@ struct redisCommand redisCommandTable[] = {
{"touch",touchCommand,-2, {"touch",touchCommand,-2,
"read-only fast @keyspace", "read-only fast @keyspace",
0,NULL,1,1,1,0,0,0}, 0,NULL,1,-1,1,0,0,0},
{"pttl",pttlCommand,2, {"pttl",pttlCommand,2,
"read-only fast random @keyspace", "read-only fast random @keyspace",
@ -867,7 +867,7 @@ struct redisCommand redisCommandTable[] = {
"no-script @keyspace", "no-script @keyspace",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
{"command",commandCommand,0, {"command",commandCommand,-1,
"ok-loading ok-stale random @connection", "ok-loading ok-stale random @connection",
0,NULL,0,0,0,0,0,0}, 0,NULL,0,0,0,0,0,0},
@ -4631,6 +4631,7 @@ static void sigShutdownHandler(int sig) {
rdbRemoveTempFile(getpid()); rdbRemoveTempFile(getpid());
exit(1); /* Exit with an error since this was not a clean shutdown. */ exit(1); /* Exit with an error since this was not a clean shutdown. */
} else if (server.loading) { } else if (server.loading) {
serverLogFromHandler(LL_WARNING, "Received shutdown signal during loading, exiting now.");
exit(0); exit(0);
} }

View File

@ -595,16 +595,18 @@ typedef struct RedisModuleIO {
int ver; /* Module serialization version: 1 (old), int ver; /* Module serialization version: 1 (old),
* 2 (current version with opcodes annotation). */ * 2 (current version with opcodes annotation). */
struct RedisModuleCtx *ctx; /* Optional context, see RM_GetContextFromIO()*/ struct RedisModuleCtx *ctx; /* Optional context, see RM_GetContextFromIO()*/
struct redisObject *key; /* Optional name of key processed */
} RedisModuleIO; } RedisModuleIO;
/* Macro to initialize an IO context. Note that the 'ver' field is populated /* Macro to initialize an IO context. Note that the 'ver' field is populated
* inside rdb.c according to the version of the value to load. */ * inside rdb.c according to the version of the value to load. */
#define moduleInitIOContext(iovar,mtype,rioptr) do { \ #define moduleInitIOContext(iovar,mtype,rioptr,keyptr) do { \
iovar.prio = rioptr; \ iovar.prio = rioptr; \
iovar.type = mtype; \ iovar.type = mtype; \
iovar.bytes = 0; \ iovar.bytes = 0; \
iovar.error = 0; \ iovar.error = 0; \
iovar.ver = 0; \ iovar.ver = 0; \
iovar.key = keyptr; \
iovar.ctx = NULL; \ iovar.ctx = NULL; \
} while(0); } while(0);

View File

@ -58,7 +58,7 @@ redisSortOperation *createSortOperation(int type, robj *pattern) {
* *
* The returned object will always have its refcount increased by 1 * The returned object will always have its refcount increased by 1
* when it is non-NULL. */ * when it is non-NULL. */
robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) { robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst, int writeflag) {
char *p, *f, *k; char *p, *f, *k;
sds spat, ssub; sds spat, ssub;
robj *keyobj, *fieldobj = NULL, *o; robj *keyobj, *fieldobj = NULL, *o;
@ -106,7 +106,10 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
decrRefCount(subst); /* Incremented by decodeObject() */ decrRefCount(subst); /* Incremented by decodeObject() */
/* Lookup substituted key */ /* Lookup substituted key */
o = lookupKeyRead(db,keyobj); if (!writeflag)
o = lookupKeyRead(db,keyobj);
else
o = lookupKeyWrite(db,keyobj);
if (o == NULL) goto noobj; if (o == NULL) goto noobj;
if (fieldobj) { if (fieldobj) {
@ -198,30 +201,12 @@ void sortCommand(client *c) {
robj *sortval, *sortby = NULL, *storekey = NULL; robj *sortval, *sortby = NULL, *storekey = NULL;
redisSortObject *vector; /* Resulting vector to sort */ redisSortObject *vector; /* Resulting vector to sort */
/* Lookup the key to sort. It must be of the right types */
sortval = lookupKeyRead(c->db,c->argv[1]);
if (sortval && sortval->type != OBJ_SET &&
sortval->type != OBJ_LIST &&
sortval->type != OBJ_ZSET)
{
addReply(c,shared.wrongtypeerr);
return;
}
/* Create a list of operations to perform for every sorted element. /* Create a list of operations to perform for every sorted element.
* Operations can be GET */ * Operations can be GET */
operations = listCreate(); operations = listCreate();
listSetFreeMethod(operations,zfree); listSetFreeMethod(operations,zfree);
j = 2; /* options start at argv[2] */ j = 2; /* options start at argv[2] */
/* Now we need to protect sortval incrementing its count, in the future
* SORT may have options able to overwrite/delete keys during the sorting
* and the sorted key itself may get destroyed */
if (sortval)
incrRefCount(sortval);
else
sortval = createQuicklistObject();
/* The SORT command has an SQL-alike syntax, parse it */ /* The SORT command has an SQL-alike syntax, parse it */
while(j < c->argc) { while(j < c->argc) {
int leftargs = c->argc-j-1; int leftargs = c->argc-j-1;
@ -280,11 +265,33 @@ void sortCommand(client *c) {
/* Handle syntax errors set during options parsing. */ /* Handle syntax errors set during options parsing. */
if (syntax_error) { if (syntax_error) {
decrRefCount(sortval);
listRelease(operations); listRelease(operations);
return; return;
} }
/* Lookup the key to sort. It must be of the right types */
if (storekey)
sortval = lookupKeyRead(c->db,c->argv[1]);
else
sortval = lookupKeyWrite(c->db,c->argv[1]);
if (sortval && sortval->type != OBJ_SET &&
sortval->type != OBJ_LIST &&
sortval->type != OBJ_ZSET)
{
listRelease(operations);
addReply(c,shared.wrongtypeerr);
return;
}
/* Now we need to protect sortval incrementing its count, in the future
* SORT may have options able to overwrite/delete keys during the sorting
* and the sorted key itself may get destroyed */
if (sortval)
incrRefCount(sortval);
else
sortval = createQuicklistObject();
/* When sorting a set with no sort specified, we must sort the output /* When sorting a set with no sort specified, we must sort the output
* so the result is consistent across scripting and replication. * so the result is consistent across scripting and replication.
* *
@ -452,7 +459,7 @@ void sortCommand(client *c) {
robj *byval; robj *byval;
if (sortby) { if (sortby) {
/* lookup value to sort by */ /* lookup value to sort by */
byval = lookupKeyByPattern(c->db,sortby,vector[j].obj); byval = lookupKeyByPattern(c->db,sortby,vector[j].obj,storekey!=NULL);
if (!byval) continue; if (!byval) continue;
} else { } else {
/* use object itself to sort by */ /* use object itself to sort by */
@ -515,7 +522,7 @@ void sortCommand(client *c) {
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
redisSortOperation *sop = ln->value; redisSortOperation *sop = ln->value;
robj *val = lookupKeyByPattern(c->db,sop->pattern, robj *val = lookupKeyByPattern(c->db,sop->pattern,
vector[j].obj); vector[j].obj,storekey!=NULL);
if (sop->type == SORT_OP_GET) { if (sop->type == SORT_OP_GET) {
if (!val) { if (!val) {
@ -545,7 +552,7 @@ void sortCommand(client *c) {
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
redisSortOperation *sop = ln->value; redisSortOperation *sop = ln->value;
robj *val = lookupKeyByPattern(c->db,sop->pattern, robj *val = lookupKeyByPattern(c->db,sop->pattern,
vector[j].obj); vector[j].obj,storekey!=NULL);
if (sop->type == SORT_OP_GET) { if (sop->type == SORT_OP_GET) {
if (!val) val = createStringObject("",0); if (!val) val = createStringObject("",0);

View File

@ -415,7 +415,7 @@ void spopWithCountCommand(client *c) {
/* Make sure a key with the name inputted exists, and that it's type is /* Make sure a key with the name inputted exists, and that it's type is
* indeed a set. Otherwise, return nil */ * indeed a set. Otherwise, return nil */
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
== NULL || checkType(c,set,OBJ_SET)) return; == NULL || checkType(c,set,OBJ_SET)) return;
/* If count is zero, serve an empty multibulk ASAP to avoid special /* If count is zero, serve an empty multibulk ASAP to avoid special

View File

@ -159,6 +159,10 @@ void *zrealloc(void *ptr, size_t size, enum MALLOC_CLASS class) {
size_t oldsize; size_t oldsize;
void *newptr; void *newptr;
if (size == 0 && ptr != NULL) {
zfree(ptr);
return NULL;
}
if (ptr == NULL) return zmalloc(size, class); if (ptr == NULL) return zmalloc(size, class);
#ifdef HAVE_MALLOC_SIZE #ifdef HAVE_MALLOC_SIZE
oldsize = zmalloc_size(ptr); oldsize = zmalloc_size(ptr);