diff --git a/src/db.c b/src/db.c index 71c03fbe4..a09fce790 100644 --- a/src/db.c +++ b/src/db.c @@ -49,6 +49,9 @@ void updateLFU(robj *val) { * found in the specified DB. This function implements the functionality of * lookupKeyRead(), lookupKeyWrite() and their ...WithFlags() variants. * + * 'deref' is an optional output dictEntry reference argument, to get the + * associated dictEntry* of the key in case the key is found. + * * Side-effects of calling this function: * * 1. A key gets expired if it reached it's TTL. @@ -72,7 +75,7 @@ void updateLFU(robj *val) { * Even if the key expiry is master-driven, we can correctly report a key is * expired on replicas even if the master is lagging expiring our key via DELs * in the replication link. */ -robj *lookupKey(redisDb *db, robj *key, int flags) { +robj *lookupKey(redisDb *db, robj *key, int flags, dictEntry **deref) { dictEntry *de = dbFind(db, key->ptr); robj *val = NULL; if (de) { @@ -123,6 +126,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) { /* TODO: Use separate misses stats and notify event for WRITE */ } + if (val && deref) *deref = de; return val; } @@ -137,7 +141,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) { * the key. */ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) { serverAssert(!(flags & LOOKUP_WRITE)); - return lookupKey(db, key, flags); + return lookupKey(db, key, flags, NULL); } /* Like lookupKeyReadWithFlags(), but does not use any flag, which is the @@ -153,13 +157,20 @@ robj *lookupKeyRead(redisDb *db, robj *key) { * Returns the linked value object if the key exists or NULL if the key * does not exist in the specified DB. */ robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) { - return lookupKey(db, key, flags | LOOKUP_WRITE); + return lookupKey(db, key, flags | LOOKUP_WRITE, NULL); } robj *lookupKeyWrite(redisDb *db, robj *key) { return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE); } +/* Like lookupKeyWrite(), but accepts an optional dictEntry input, + * which can be used if we already have one, thus saving the dbFind call. + */ +robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref) { + return lookupKey(db, key, LOOKUP_NONE | LOOKUP_WRITE, deref); +} + robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) { robj *o = lookupKeyRead(c->db, key); if (!o) addReplyOrErrorObject(c, reply); @@ -294,6 +305,14 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) { dbSetValue(db, key, val, 0, NULL); } +/* Replace an existing key with a new value, we just replace value and don't + * emit any events. + * The dictEntry input is optional, can be used if we already have one. + */ +void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de) { + dbSetValue(db, key, val, 0, de); +} + /* High level Set operation. This function can be used in order to set * a key, whatever it was existing or not, to a new object. * @@ -308,6 +327,12 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val) { * The client 'c' argument may be set to NULL if the operation is performed * in a context where there is no clear client performing the operation. */ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) { + setKeyWithDictEntry(c,db,key,val,flags,NULL); +} + +/* Like setKey(), but accepts an optional dictEntry input, + * which can be used if we already have one, thus saving the dictFind call. */ +void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de) { int keyfound = 0; if (flags & SETKEY_ALREADY_EXIST) @@ -322,7 +347,7 @@ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags) { } else if (keyfound<0) { dbAddInternal(db,key,val,1); } else { - dbSetValue(db,key,val,1,NULL); + dbSetValue(db,key,val,1,de); } incrRefCount(val); if (!(flags & SETKEY_KEEPTTL)) removeExpire(db,key); @@ -452,12 +477,18 @@ int dbDelete(redisDb *db, robj *key) { * using an sdscat() call to append some data, or anything else. */ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) { + return dbUnshareStringValueWithDictEntry(db,key,o,NULL); +} + +/* Like dbUnshareStringValue(), but accepts a optional dictEntry, + * which can be used if we already have one, thus saving the dbFind call. */ +robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de) { serverAssert(o->type == OBJ_STRING); if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) { robj *decoded = getDecodedObject(o); o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr)); decrRefCount(decoded); - dbReplaceValue(db,key,o); + dbReplaceValueWithDictEntry(db,key,o,de); } return o; } @@ -1828,16 +1859,23 @@ int removeExpire(redisDb *db, robj *key) { return kvstoreDictDelete(db->expires, getKeySlot(key->ptr), key->ptr) == DICT_OK; } + /* Set an expire to the specified key. If the expire is set in the context * of an user calling a command 'c' is the client, otherwise 'c' is set * to NULL. The 'when' parameter is the absolute unix time in milliseconds * after which the key will no longer be considered valid. */ void setExpire(client *c, redisDb *db, robj *key, long long when) { - dictEntry *kde, *de, *existing; + setExpireWithDictEntry(c,db,key,when,NULL); +} + +/* Like setExpire(), but accepts an optional dictEntry input, + * which can be used if we already have one, thus saving the kvstoreDictFind call. */ +void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde) { + dictEntry *de, *existing; /* Reuse the sds from the main dict in the expire dict */ int slot = getKeySlot(key->ptr); - kde = kvstoreDictFind(db->keys, slot, key->ptr); + if (!kde) kde = kvstoreDictFind(db->keys, slot, key->ptr); serverAssertWithInfo(NULL,key,kde != NULL); de = kvstoreDictAddRaw(db->expires, slot, dictGetKey(kde), &existing); if (existing) { diff --git a/src/networking.c b/src/networking.c index 13ac603da..91ca00a32 100644 --- a/src/networking.c +++ b/src/networking.c @@ -970,6 +970,12 @@ void addReplyLongLong(client *c, long long ll) { } } +void addReplyLongLongFromStr(client *c, robj *str) { + addReplyProto(c,":",1); + addReply(c,str); + addReplyProto(c,"\r\n",2); +} + void addReplyAggregateLen(client *c, long length, int prefix) { serverAssert(length >= 0); if (prepareClientToWrite(c) != C_OK) return; diff --git a/src/server.h b/src/server.h index 7d7147874..9f34561eb 100644 --- a/src/server.h +++ b/src/server.h @@ -2631,6 +2631,7 @@ void addReplyDouble(client *c, double d); void addReplyBigNum(client *c, const char *num, size_t len); void addReplyHumanLongDouble(client *c, long double d); void addReplyLongLong(client *c, long long ll); +void addReplyLongLongFromStr(client *c, robj* str); void addReplyArrayLen(client *c, long length); void addReplyMapLen(client *c, long length); void addReplySetLen(client *c, long length); @@ -3366,10 +3367,12 @@ void propagateDeletion(redisDb *db, robj *key, int lazy); int keyIsExpired(redisDb *db, robj *key); long long getExpire(redisDb *db, robj *key); void setExpire(client *c, redisDb *db, robj *key, long long when); +void setExpireWithDictEntry(client *c, redisDb *db, robj *key, long long when, dictEntry *kde); int checkAlreadyExpired(long long when); int parseExtendedExpireArgumentsOrReply(client *c, int *flags); robj *lookupKeyRead(redisDb *db, robj *key); robj *lookupKeyWrite(redisDb *db, robj *key); +robj *lookupKeyWriteWithDictEntry(redisDb *db, robj *key, dictEntry **deref); robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply); robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply); robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags); @@ -3389,6 +3392,7 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle, dictEntry *dbAdd(redisDb *db, robj *key, robj *val); int dbAddRDBLoad(redisDb *db, sds key, robj *val); void dbReplaceValue(redisDb *db, robj *key, robj *val); +void dbReplaceValueWithDictEntry(redisDb *db, robj *key, robj *val, dictEntry *de); #define SETKEY_KEEPTTL 1 #define SETKEY_NO_SIGNAL 2 @@ -3396,11 +3400,14 @@ void dbReplaceValue(redisDb *db, robj *key, robj *val); #define SETKEY_DOESNT_EXIST 8 #define SETKEY_ADD_OR_UPDATE 16 /* Key most likely doesn't exists */ void setKey(client *c, redisDb *db, robj *key, robj *val, int flags); +void setKeyWithDictEntry(client *c, redisDb *db, robj *key, robj *val, int flags, dictEntry *de); robj *dbRandomKey(redisDb *db); int dbGenericDelete(redisDb *db, robj *key, int async, int flags); int dbSyncDelete(redisDb *db, robj *key); int dbDelete(redisDb *db, robj *key); robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o); +robj *dbUnshareStringValueWithDictEntry(redisDb *db, robj *key, robj *o, dictEntry *de); + #define EMPTYDB_NO_FLAGS 0 /* No flags. */ #define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */ diff --git a/src/t_string.c b/src/t_string.c index 067617a92..d1d6dce39 100644 --- a/src/t_string.c +++ b/src/t_string.c @@ -73,7 +73,8 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, if (getGenericCommand(c) == C_ERR) return; } - found = (lookupKeyWrite(c->db,key) != NULL); + dictEntry *de = NULL; + found = (lookupKeyWriteWithDictEntry(c->db,key,&de) != NULL); if ((flags & OBJ_SET_NX && found) || (flags & OBJ_SET_XX && !found)) @@ -88,12 +89,12 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, setkey_flags |= ((flags & OBJ_KEEPTTL) || expire) ? SETKEY_KEEPTTL : 0; setkey_flags |= found ? SETKEY_ALREADY_EXIST : SETKEY_DOESNT_EXIST; - setKey(c,c->db,key,val,setkey_flags); + setKeyWithDictEntry(c,c->db,key,val,setkey_flags,de); server.dirty++; notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id); if (expire) { - setExpire(c,c->db,key,milliseconds); + setExpireWithDictEntry(c,c->db,key,milliseconds,de); /* Propagate as SET Key Value PXAT millisecond-timestamp if there is * EX/PX/EXAT flag. */ if (!(flags & OBJ_PXAT)) { @@ -422,6 +423,7 @@ void setrangeCommand(client *c) { robj *o; long offset; sds value = c->argv[3]->ptr; + const size_t value_len = sdslen(value); if (getLongFromObjectOrReply(c,c->argv[2],&offset,NULL) != C_OK) return; @@ -431,19 +433,20 @@ void setrangeCommand(client *c) { return; } - o = lookupKeyWrite(c->db,c->argv[1]); + dictEntry *de; + o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de); if (o == NULL) { /* Return 0 when setting nothing on a non-existing string */ - if (sdslen(value) == 0) { + if (value_len == 0) { addReply(c,shared.czero); return; } /* Return when the resulting string exceeds allowed size */ - if (checkStringLength(c,offset,sdslen(value)) != C_OK) + if (checkStringLength(c,offset,value_len) != C_OK) return; - o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+sdslen(value))); + o = createObject(OBJ_STRING,sdsnewlen(NULL, offset+value_len)); dbAdd(c->db,c->argv[1],o); } else { size_t olen; @@ -454,22 +457,22 @@ void setrangeCommand(client *c) { /* Return existing string length when setting nothing */ olen = stringObjectLen(o); - if (sdslen(value) == 0) { + if (value_len == 0) { addReplyLongLong(c,olen); return; } /* Return when the resulting string exceeds allowed size */ - if (checkStringLength(c,offset,sdslen(value)) != C_OK) + if (checkStringLength(c,offset,value_len) != C_OK) return; /* Create a copy when the object is shared or encoded. */ - o = dbUnshareStringValue(c->db,c->argv[1],o); + o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de); } - if (sdslen(value) > 0) { - o->ptr = sdsgrowzero(o->ptr,offset+sdslen(value)); - memcpy((char*)o->ptr+offset,value,sdslen(value)); + if (value_len > 0) { + o->ptr = sdsgrowzero(o->ptr,offset+value_len); + memcpy((char*)o->ptr+offset,value,value_len); signalModifiedKey(c,c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_STRING, "setrange",c->argv[1],c->db->id); @@ -571,8 +574,8 @@ void msetnxCommand(client *c) { void incrDecrCommand(client *c, long long incr) { long long value, oldvalue; robj *o, *new; - - o = lookupKeyWrite(c->db,c->argv[1]); + dictEntry *de; + o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de); if (checkType(c,o,OBJ_STRING)) return; if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return; @@ -593,7 +596,7 @@ void incrDecrCommand(client *c, long long incr) { } else { new = createStringObjectFromLongLongForValue(value); if (o) { - dbReplaceValue(c->db,c->argv[1],new); + dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de); } else { dbAdd(c->db,c->argv[1],new); } @@ -601,7 +604,7 @@ void incrDecrCommand(client *c, long long incr) { signalModifiedKey(c,c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id); server.dirty++; - addReplyLongLong(c, value); + addReplyLongLongFromStr(c,new); } void incrCommand(client *c) { @@ -635,7 +638,8 @@ void incrbyfloatCommand(client *c) { long double incr, value; robj *o, *new; - o = lookupKeyWrite(c->db,c->argv[1]); + dictEntry *de; + o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de); if (checkType(c,o,OBJ_STRING)) return; if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != C_OK || getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != C_OK) @@ -648,7 +652,7 @@ void incrbyfloatCommand(client *c) { } new = createStringObjectFromLongDouble(value,1); if (o) - dbReplaceValue(c->db,c->argv[1],new); + dbReplaceValueWithDictEntry(c->db,c->argv[1],new,de); else dbAdd(c->db,c->argv[1],new); signalModifiedKey(c,c->db,c->argv[1]); @@ -668,7 +672,8 @@ void appendCommand(client *c) { size_t totlen; robj *o, *append; - o = lookupKeyWrite(c->db,c->argv[1]); + dictEntry *de; + o = lookupKeyWriteWithDictEntry(c->db,c->argv[1],&de); if (o == NULL) { /* Create the key */ c->argv[2] = tryObjectEncoding(c->argv[2]); @@ -682,12 +687,13 @@ void appendCommand(client *c) { /* "append" is an argument, so always an sds */ append = c->argv[2]; - if (checkStringLength(c,stringObjectLen(o),sdslen(append->ptr)) != C_OK) + const size_t append_len = sdslen(append->ptr); + if (checkStringLength(c,stringObjectLen(o),append_len) != C_OK) return; /* Append the value */ - o = dbUnshareStringValue(c->db,c->argv[1],o); - o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); + o = dbUnshareStringValueWithDictEntry(c->db,c->argv[1],o,de); + o->ptr = sdscatlen(o->ptr,append->ptr,append_len); totlen = sdslen(o->ptr); } signalModifiedKey(c,c->db,c->argv[1]);