Compare commits
84
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ad44b76b9 | ||
|
|
d88f52ee7d | ||
|
|
399a6b2b47 | ||
|
|
451872527c | ||
|
|
3d5b2d41b5 | ||
|
|
30adc62232 | ||
|
|
96688aa646 | ||
|
|
a6350f717d | ||
|
|
f69876280c | ||
|
|
e93dcba46e | ||
|
|
9a9109431b | ||
|
|
bb30b9f913 | ||
|
|
094b47391d | ||
|
|
2c42f6a8b8 | ||
|
|
121c51f4f3 | ||
|
|
af3c722fec | ||
|
|
ca8d6f1072 | ||
|
|
8dc28b6c75 | ||
|
|
7bd5ccaad0 | ||
|
|
7261a5550f | ||
|
|
25063f75d7 | ||
|
|
af5c11874c | ||
|
|
4bc4d2c767 | ||
|
|
cf3789f045 | ||
|
|
b0bbfed238 | ||
|
|
c76d0e7be9 | ||
|
|
1b0d30aeb7 | ||
|
|
a76a961cdd | ||
|
|
f80b946bea | ||
|
|
a8857320a5 | ||
|
|
cbf212f981 | ||
|
|
4158664d2b | ||
|
|
4e8e7fb067 | ||
|
|
ef610802c7 | ||
|
|
5b73ea3cf6 | ||
|
|
eb9ca33414 | ||
|
|
78df016584 | ||
|
|
4665b3ebfb | ||
|
|
85a0d29d04 | ||
|
|
416d394cd9 | ||
|
|
0062194d74 | ||
|
|
10b626b3d5 | ||
|
|
2f53ad30ee | ||
|
|
5a3d85745a | ||
|
|
3f96e1623d | ||
|
|
88351275fe | ||
|
|
fa4f50739a | ||
|
|
dfef407499 | ||
|
|
8d6dc136e1 | ||
|
|
b73d87f5e5 | ||
|
|
babada8b5f | ||
|
|
80ec0431e8 | ||
|
|
545a5046f5 | ||
|
|
10afb9639a | ||
|
|
88e66ecf94 | ||
|
|
8cdc15c309 | ||
|
|
4cbf3f5ddd | ||
|
|
c9c03c3ee6 | ||
|
|
b3400559be | ||
|
|
1010c1b43e | ||
|
|
c4dc5b80b2 | ||
|
|
38076fd6ba | ||
|
|
1d13ff0b39 | ||
|
|
0c52ce6c8e | ||
|
|
a20c206de7 | ||
|
|
6c8221580c | ||
|
|
d6eb3afd13 | ||
|
|
1b4bc60999 | ||
|
|
12d7479144 | ||
|
|
86c76cad74 | ||
|
|
1ef44f8243 | ||
|
|
3f0afab301 | ||
|
|
9a1843ef2d | ||
|
|
1547d72cf3 | ||
|
|
262262fbb2 | ||
|
|
6548157293 | ||
|
|
a477b68109 | ||
|
|
d72b7ed4fb | ||
|
|
38f6207f88 | ||
|
|
660645c742 | ||
|
|
3c0ed0309a | ||
|
|
173cca5413 | ||
|
|
8beec4f0e7 | ||
|
|
dfb12f0628 |
@@ -13,6 +13,8 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get install tcl8.5
|
||||
./runtest --clients 2 --verbose
|
||||
- name: module api test
|
||||
run: ./runtest-moduleapi --clients 2 --verbose
|
||||
|
||||
build-ubuntu-old:
|
||||
runs-on: ubuntu-16.04
|
||||
|
||||
+8
-1
@@ -943,13 +943,20 @@ replica-priority 100
|
||||
# In all the above cases the default is to delete objects in a blocking way,
|
||||
# like if DEL was called. However you can configure each case specifically
|
||||
# in order to instead release memory in a non-blocking way like if UNLINK
|
||||
# was called, using the following configuration directives:
|
||||
# was called, using the following configuration directives.
|
||||
|
||||
lazyfree-lazy-eviction no
|
||||
lazyfree-lazy-expire no
|
||||
lazyfree-lazy-server-del no
|
||||
replica-lazy-flush no
|
||||
|
||||
# It is also possible, for the case when to replace the user code DEL calls
|
||||
# with UNLINK calls is not easy, to modify the default behavior of the DEL
|
||||
# command to act exactly like UNLINK, using the following configuration
|
||||
# directive:
|
||||
|
||||
lazyfree-lazy-user-del no
|
||||
|
||||
################################ THREADED I/O #################################
|
||||
|
||||
# Redis is mostly single threaded, however there are certain threaded
|
||||
|
||||
@@ -1847,18 +1847,18 @@ void aclCommand(client *c) {
|
||||
}
|
||||
} else if (!strcasecmp(sub,"help")) {
|
||||
const char *help[] = {
|
||||
"LOAD -- Reload users from the ACL file.",
|
||||
"SAVE -- Save the current config to the ACL file."
|
||||
"LIST -- Show user details in config file format.",
|
||||
"USERS -- List all the registered usernames.",
|
||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
||||
"GETUSER <username> -- Get the user details.",
|
||||
"DELUSER <username> [...] -- Delete a list of users.",
|
||||
"CAT -- List available categories.",
|
||||
"CAT <category> -- List commands inside category.",
|
||||
"GENPASS -- Generate a secure user password.",
|
||||
"WHOAMI -- Return the current connection username.",
|
||||
"LOG [<count> | RESET] -- Show the ACL log entries.",
|
||||
"LOAD -- Reload users from the ACL file.",
|
||||
"SAVE -- Save the current config to the ACL file.",
|
||||
"LIST -- Show user details in config file format.",
|
||||
"USERS -- List all the registered usernames.",
|
||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
||||
"GETUSER <username> -- Get the user details.",
|
||||
"DELUSER <username> [...] -- Delete a list of users.",
|
||||
"CAT -- List available categories.",
|
||||
"CAT <category> -- List commands inside category.",
|
||||
"GENPASS -- Generate a secure user password.",
|
||||
"WHOAMI -- Return the current connection username.",
|
||||
"LOG [<count> | RESET] -- Show the ACL log entries.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c,help);
|
||||
|
||||
@@ -831,7 +831,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
|
||||
|
||||
/* Run the command in the context of a fake client */
|
||||
fakeClient->cmd = cmd;
|
||||
fakeClient->cmd = fakeClient->lastcmd = cmd;
|
||||
if (fakeClient->flags & CLIENT_MULTI &&
|
||||
fakeClient->cmd->proc != execCommand)
|
||||
{
|
||||
|
||||
+2
-2
@@ -933,7 +933,7 @@ int clusterAddNode(clusterNode *node) {
|
||||
return (retval == DICT_OK) ? C_OK : C_ERR;
|
||||
}
|
||||
|
||||
/* Remove a node from the cluster. The functio performs the high level
|
||||
/* Remove a node from the cluster. The function performs the high level
|
||||
* cleanup, calling freeClusterNode() for the low level cleanup.
|
||||
* Here we do the following:
|
||||
*
|
||||
@@ -4966,7 +4966,7 @@ void restoreCommand(client *c) {
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
|
||||
((obj = rdbLoadObject(type,&payload,c->argv[1]->ptr)) == NULL))
|
||||
{
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
|
||||
@@ -2099,6 +2099,7 @@ standardConfig configs[] = {
|
||||
createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_eviction, 0, NULL, NULL),
|
||||
createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_expire, 0, NULL, NULL),
|
||||
createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_server_del, 0, NULL, NULL),
|
||||
createBoolConfig("lazyfree-lazy-user-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_user_del , 0, NULL, NULL),
|
||||
createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, server.repl_disable_tcp_nodelay, 0, NULL, NULL),
|
||||
createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, server.repl_diskless_sync, 0, NULL, NULL),
|
||||
createBoolConfig("gopher-enabled", NULL, MODIFIABLE_CONFIG, server.gopher_enabled, 0, NULL, NULL),
|
||||
|
||||
@@ -182,9 +182,28 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
|
||||
serverAssertWithInfo(NULL,key,retval == DICT_OK);
|
||||
if (val->type == OBJ_LIST ||
|
||||
val->type == OBJ_ZSET)
|
||||
val->type == OBJ_ZSET ||
|
||||
val->type == OBJ_STREAM)
|
||||
signalKeyAsReady(db, key);
|
||||
if (server.cluster_enabled) slotToKeyAdd(key->ptr);
|
||||
}
|
||||
|
||||
/* This is a special version of dbAdd() that is used only when loading
|
||||
* keys from the RDB file: the key is passed as an SDS string that is
|
||||
* retained by the function (and not freed by the caller).
|
||||
*
|
||||
* Moreover this function will not abort if the key is already busy, to
|
||||
* give more control to the caller, nor will signal the key as ready
|
||||
* since it is not useful in this context.
|
||||
*
|
||||
* The function returns 1 if the key was added to the database, taking
|
||||
* ownership of the SDS string, otherwise 0 is returned, and is up to the
|
||||
* caller to free the SDS string. */
|
||||
int dbAddRDBLoad(redisDb *db, sds key, robj *val) {
|
||||
int retval = dictAdd(db->dict, key, val);
|
||||
if (retval != DICT_OK) return 0;
|
||||
if (server.cluster_enabled) slotToKeyAdd(key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Overwrite an existing key with a new value. Incrementing the reference
|
||||
@@ -220,7 +239,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
* unless 'keepttl' is true.
|
||||
*
|
||||
* All the new keys in the database should be created via this interface. */
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl) {
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl, int signal) {
|
||||
if (lookupKeyWrite(db,key) == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
} else {
|
||||
@@ -228,12 +247,12 @@ void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl) {
|
||||
}
|
||||
incrRefCount(val);
|
||||
if (!keepttl) removeExpire(db,key);
|
||||
signalModifiedKey(db,key);
|
||||
if (signal) signalModifiedKey(db,key);
|
||||
}
|
||||
|
||||
/* Common case for genericSetKey() where the TTL is not retained. */
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
genericSetKey(db,key,val,0);
|
||||
genericSetKey(db,key,val,0,1);
|
||||
}
|
||||
|
||||
/* Return true if the specified key exists in the specified database.
|
||||
@@ -287,7 +306,7 @@ int dbSyncDelete(redisDb *db, robj *key) {
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
|
||||
if (dictDelete(db->dict,key->ptr) == DICT_OK) {
|
||||
if (server.cluster_enabled) slotToKeyDel(key);
|
||||
if (server.cluster_enabled) slotToKeyDel(key->ptr);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
@@ -555,7 +574,7 @@ void delGenericCommand(client *c, int lazy) {
|
||||
}
|
||||
|
||||
void delCommand(client *c) {
|
||||
delGenericCommand(c,0);
|
||||
delGenericCommand(c,server.lazyfree_lazy_user_del);
|
||||
}
|
||||
|
||||
void unlinkCommand(client *c) {
|
||||
@@ -1553,6 +1572,32 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* LCS ... [KEYS <key1> <key2>] ... */
|
||||
int *lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
{
|
||||
int i;
|
||||
int *keys = getKeysTempBuffer;
|
||||
UNUSED(cmd);
|
||||
|
||||
/* We need to parse the options of the command in order to check for the
|
||||
* "KEYS" argument before the "STRINGS" argument. */
|
||||
for (i = 1; i < argc; i++) {
|
||||
char *arg = argv[i]->ptr;
|
||||
int moreargs = (argc-1) - i;
|
||||
|
||||
if (!strcasecmp(arg, "strings")) {
|
||||
break;
|
||||
} else if (!strcasecmp(arg, "keys") && moreargs >= 2) {
|
||||
keys[0] = i+1;
|
||||
keys[1] = i+2;
|
||||
*numkeys = 2;
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
*numkeys = 0;
|
||||
return keys;
|
||||
}
|
||||
|
||||
/* Helper function to extract keys from memory command.
|
||||
* MEMORY USAGE <key> */
|
||||
int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
|
||||
@@ -1620,17 +1665,17 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
|
||||
* a fast way a key that belongs to a specified hash slot. This is useful
|
||||
* while rehashing the cluster and in other conditions when we need to
|
||||
* understand if we have keys for a given hash slot. */
|
||||
void slotToKeyUpdateKey(robj *key, int add) {
|
||||
unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));
|
||||
void slotToKeyUpdateKey(sds key, int add) {
|
||||
size_t keylen = sdslen(key);
|
||||
unsigned int hashslot = keyHashSlot(key,keylen);
|
||||
unsigned char buf[64];
|
||||
unsigned char *indexed = buf;
|
||||
size_t keylen = sdslen(key->ptr);
|
||||
|
||||
server.cluster->slots_keys_count[hashslot] += add ? 1 : -1;
|
||||
if (keylen+2 > 64) indexed = zmalloc(keylen+2);
|
||||
indexed[0] = (hashslot >> 8) & 0xff;
|
||||
indexed[1] = hashslot & 0xff;
|
||||
memcpy(indexed+2,key->ptr,keylen);
|
||||
memcpy(indexed+2,key,keylen);
|
||||
if (add) {
|
||||
raxInsert(server.cluster->slots_to_keys,indexed,keylen+2,NULL,NULL);
|
||||
} else {
|
||||
@@ -1639,11 +1684,11 @@ void slotToKeyUpdateKey(robj *key, int add) {
|
||||
if (indexed != buf) zfree(indexed);
|
||||
}
|
||||
|
||||
void slotToKeyAdd(robj *key) {
|
||||
void slotToKeyAdd(sds key) {
|
||||
slotToKeyUpdateKey(key,1);
|
||||
}
|
||||
|
||||
void slotToKeyDel(robj *key) {
|
||||
void slotToKeyDel(sds key) {
|
||||
slotToKeyUpdateKey(key,0);
|
||||
}
|
||||
|
||||
|
||||
+95
-36
@@ -366,7 +366,7 @@ void debugCommand(client *c) {
|
||||
"OOM -- Crash the server simulating an out-of-memory error.",
|
||||
"PANIC -- Crash the server simulating a panic.",
|
||||
"POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.",
|
||||
"RELOAD -- Save the RDB on disk and reload it back in memory.",
|
||||
"RELOAD [MERGE] [NOFLUSH] [NOSAVE] -- Save the RDB on disk and reload it back in memory. By default it will save the RDB file and load it back. With the NOFLUSH option the current database is not removed before loading the new one, but conficts in keys will kill the server with an exception. When MERGE is used, conflicting keys will be loaded (the key in the loaded RDB file will win). When NOSAVE is used, the server will not save the current dataset in the RDB file before loading. Use DEBUG RELOAD NOSAVE when you want just to load the RDB file you placed in the Redis working directory in order to replace the current dataset in memory. Use DEBUG RELOAD NOSAVE NOFLUSH MERGE when you want to add what is in the current RDB file placed in the Redis current directory, with the current memory content. Use DEBUG RELOAD when you want to verify Redis is able to persist the current dataset in the RDB file, flush the memory content, and load it back.",
|
||||
"RESTART -- Graceful restart: save config, db, restart.",
|
||||
"SDSLEN <key> -- Show low level SDS string info representing key and value.",
|
||||
"SEGFAULT -- Crash the server with sigsegv.",
|
||||
@@ -411,15 +411,44 @@ NULL
|
||||
serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
int flush = 1, save = 1;
|
||||
int flags = RDBFLAGS_NONE;
|
||||
|
||||
/* Parse the additional options that modify the RELOAD
|
||||
* behavior. */
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
if (!strcasecmp(opt,"MERGE")) {
|
||||
flags |= RDBFLAGS_ALLOW_DUP;
|
||||
} else if (!strcasecmp(opt,"NOFLUSH")) {
|
||||
flush = 0;
|
||||
} else if (!strcasecmp(opt,"NOSAVE")) {
|
||||
save = 0;
|
||||
} else {
|
||||
addReplyError(c,"DEBUG RELOAD only supports the "
|
||||
"MERGE, NOFLUSH and NOSAVE options.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
|
||||
/* The default beahvior is to save the RDB file before loading
|
||||
* it back. */
|
||||
if (save) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(server.rdb_filename,rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* The default behavior is to remove the current dataset from
|
||||
* memory before loading the RDB file, however when MERGE is
|
||||
* used together with NOFLUSH, we are able to merge two datasets. */
|
||||
if (flush) emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
|
||||
|
||||
protectClient(c);
|
||||
int ret = rdbLoad(server.rdb_filename,NULL,RDBFLAGS_NONE);
|
||||
int ret = rdbLoad(server.rdb_filename,NULL,flags);
|
||||
unprotectClient(c);
|
||||
if (ret != C_OK) {
|
||||
addReplyError(c,"Error trying to load the RDB dump");
|
||||
@@ -490,7 +519,7 @@ NULL
|
||||
"encoding:%s serializedlength:%zu "
|
||||
"lru:%d lru_seconds_idle:%llu%s",
|
||||
(void*)val, val->refcount,
|
||||
strenc, rdbSavedObjectLen(val),
|
||||
strenc, rdbSavedObjectLen(val, c->argv[2]),
|
||||
val->lru, estimateObjectIdleTime(val)/1000, extra);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"sdslen") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
@@ -817,6 +846,8 @@ void serverLogObjectDebugInfo(const robj *o) {
|
||||
serverLog(LL_WARNING,"Sorted set size: %d", (int) zsetLength(o));
|
||||
if (o->encoding == OBJ_ENCODING_SKIPLIST)
|
||||
serverLog(LL_WARNING,"Skiplist level: %d", (int) ((const zset*)o->ptr)->zsl->level);
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
serverLog(LL_WARNING,"Stream size: %d", (int) streamLength(o));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1045,6 +1076,61 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext.gregs[18]
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.gregs[15]);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
|
||||
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
|
||||
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
|
||||
"X30:%016lx\n"
|
||||
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.regs[18],
|
||||
(unsigned long) uc->uc_mcontext.regs[19],
|
||||
(unsigned long) uc->uc_mcontext.regs[20],
|
||||
(unsigned long) uc->uc_mcontext.regs[21],
|
||||
(unsigned long) uc->uc_mcontext.regs[22],
|
||||
(unsigned long) uc->uc_mcontext.regs[23],
|
||||
(unsigned long) uc->uc_mcontext.regs[24],
|
||||
(unsigned long) uc->uc_mcontext.regs[25],
|
||||
(unsigned long) uc->uc_mcontext.regs[26],
|
||||
(unsigned long) uc->uc_mcontext.regs[27],
|
||||
(unsigned long) uc->uc_mcontext.regs[28],
|
||||
(unsigned long) uc->uc_mcontext.regs[29],
|
||||
(unsigned long) uc->uc_mcontext.regs[30],
|
||||
(unsigned long) uc->uc_mcontext.pc,
|
||||
(unsigned long) uc->uc_mcontext.sp,
|
||||
(unsigned long) uc->uc_mcontext.pstate,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.sp);
|
||||
#elif defined(__arm__) /* Linux ARM */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"R10:%016lx R9 :%016lx\nR8 :%016lx R7 :%016lx\n"
|
||||
"R6 :%016lx R5 :%016lx\nR4 :%016lx R3 :%016lx\n"
|
||||
"R2 :%016lx R1 :%016lx\nR0 :%016lx EC :%016lx\n"
|
||||
"fp: %016lx ip:%016lx\n",
|
||||
"pc:%016lx sp:%016lx\ncpsr:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.arm_r10,
|
||||
(unsigned long) uc->uc_mcontext.arm_r9,
|
||||
(unsigned long) uc->uc_mcontext.arm_r8,
|
||||
(unsigned long) uc->uc_mcontext.arm_r7,
|
||||
(unsigned long) uc->uc_mcontext.arm_r6,
|
||||
(unsigned long) uc->uc_mcontext.arm_r5,
|
||||
(unsigned long) uc->uc_mcontext.arm_r4,
|
||||
(unsigned long) uc->uc_mcontext.arm_r3,
|
||||
(unsigned long) uc->uc_mcontext.arm_r2,
|
||||
(unsigned long) uc->uc_mcontext.arm_r1,
|
||||
(unsigned long) uc->uc_mcontext.arm_r0,
|
||||
(unsigned long) uc->uc_mcontext.error_code,
|
||||
(unsigned long) uc->uc_mcontext.arm_fp,
|
||||
(unsigned long) uc->uc_mcontext.arm_ip,
|
||||
(unsigned long) uc->uc_mcontext.arm_pc,
|
||||
(unsigned long) uc->uc_mcontext.arm_sp,
|
||||
(unsigned long) uc->uc_mcontext.arm_cpsr,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.arm_sp);
|
||||
#endif
|
||||
#elif defined(__FreeBSD__)
|
||||
#if defined(__x86_64__)
|
||||
@@ -1185,33 +1271,6 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext.mc_cs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
|
||||
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
|
||||
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
|
||||
"X30:%016lx\n"
|
||||
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.regs[18],
|
||||
(unsigned long) uc->uc_mcontext.regs[19],
|
||||
(unsigned long) uc->uc_mcontext.regs[20],
|
||||
(unsigned long) uc->uc_mcontext.regs[21],
|
||||
(unsigned long) uc->uc_mcontext.regs[22],
|
||||
(unsigned long) uc->uc_mcontext.regs[23],
|
||||
(unsigned long) uc->uc_mcontext.regs[24],
|
||||
(unsigned long) uc->uc_mcontext.regs[25],
|
||||
(unsigned long) uc->uc_mcontext.regs[26],
|
||||
(unsigned long) uc->uc_mcontext.regs[27],
|
||||
(unsigned long) uc->uc_mcontext.regs[28],
|
||||
(unsigned long) uc->uc_mcontext.regs[29],
|
||||
(unsigned long) uc->uc_mcontext.regs[30],
|
||||
(unsigned long) uc->uc_mcontext.pc,
|
||||
(unsigned long) uc->uc_mcontext.sp,
|
||||
(unsigned long) uc->uc_mcontext.pstate,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.sp);
|
||||
#else
|
||||
serverLog(LL_WARNING,
|
||||
" Dumping of registers not supported for this OS/arch");
|
||||
|
||||
@@ -590,6 +590,7 @@ void pttlCommand(client *c) {
|
||||
void persistCommand(client *c) {
|
||||
if (lookupKeyWrite(c->db,c->argv[1])) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"persist",c->argv[1],c->db->id);
|
||||
addReply(c,shared.cone);
|
||||
server.dirty++;
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
* field to NULL in order to lazy free it later. */
|
||||
if (de) {
|
||||
dictFreeUnlinkedEntry(db->dict,de);
|
||||
if (server.cluster_enabled) slotToKeyDel(key);
|
||||
if (server.cluster_enabled) slotToKeyDel(key->ptr);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
|
||||
+10
-7
@@ -2157,7 +2157,7 @@ RedisModuleString *RM_RandomKey(RedisModuleCtx *ctx) {
|
||||
int RM_StringSet(RedisModuleKey *key, RedisModuleString *str) {
|
||||
if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR;
|
||||
RM_DeleteKey(key);
|
||||
setKey(key->db,key->key,str);
|
||||
genericSetKey(key->db,key->key,str,0,0);
|
||||
key->value = str;
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
@@ -2237,7 +2237,7 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
|
||||
if (key->value == NULL) {
|
||||
/* Empty key: create it with the new size. */
|
||||
robj *o = createObject(OBJ_STRING,sdsnewlen(NULL, newlen));
|
||||
setKey(key->db,key->key,o);
|
||||
genericSetKey(key->db,key->key,o,0,0);
|
||||
key->value = o;
|
||||
decrRefCount(o);
|
||||
} else {
|
||||
@@ -3625,7 +3625,7 @@ int RM_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value) {
|
||||
if (!(key->mode & REDISMODULE_WRITE) || key->iter) return REDISMODULE_ERR;
|
||||
RM_DeleteKey(key);
|
||||
robj *o = createModuleObject(mt,value);
|
||||
setKey(key->db,key->key,o);
|
||||
genericSetKey(key->db,key->key,o,0,0);
|
||||
decrRefCount(o);
|
||||
key->value = o;
|
||||
return REDISMODULE_OK;
|
||||
@@ -4393,14 +4393,17 @@ RedisModuleBlockedClient *moduleBlockClient(RedisModuleCtx *ctx, RedisModuleCmdF
|
||||
* can really be unblocked, since the module was able to serve the client.
|
||||
* If the callback returns REDISMODULE_OK, then the client can be unblocked,
|
||||
* otherwise the client remains blocked and we'll retry again when one of
|
||||
* the keys it blocked for becomes "ready" again. */
|
||||
* the keys it blocked for becomes "ready" again.
|
||||
* This function returns 1 if client was served (and should be unblocked) */
|
||||
int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
|
||||
int served = 0;
|
||||
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
|
||||
|
||||
/* Protect against re-processing: don't serve clients that are already
|
||||
* in the unblocking list for any reason (including RM_UnblockClient()
|
||||
* explicit call). */
|
||||
if (bc->unblocked) return REDISMODULE_ERR;
|
||||
* explicit call). See #6798. */
|
||||
if (bc->unblocked) return 0;
|
||||
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
ctx.flags |= REDISMODULE_CTX_BLOCKED_REPLY;
|
||||
ctx.blocked_ready_key = key;
|
||||
@@ -5975,7 +5978,7 @@ sds modulesCollectInfo(sds info, const char *section, int for_crash_report, int
|
||||
struct RedisModule *module = dictGetVal(de);
|
||||
if (!module->info_cb)
|
||||
continue;
|
||||
RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0};
|
||||
RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0, 0};
|
||||
module->info_cb(&info_ctx, for_crash_report);
|
||||
/* Implicitly end dicts (no way to handle errors, and we must add the newline). */
|
||||
if (info_ctx.in_dict_field)
|
||||
|
||||
+41
-3
@@ -157,6 +157,8 @@ client *createClient(connection *conn) {
|
||||
c->client_list_node = NULL;
|
||||
c->client_tracking_redirection = 0;
|
||||
c->client_tracking_prefixes = NULL;
|
||||
c->client_cron_last_memory_usage = 0;
|
||||
c->client_cron_last_memory_type = CLIENT_TYPE_NORMAL;
|
||||
c->auth_callback = NULL;
|
||||
c->auth_callback_privdata = NULL;
|
||||
c->auth_module = NULL;
|
||||
@@ -373,13 +375,25 @@ void addReplyErrorLength(client *c, const char *s, size_t len) {
|
||||
* will produce an error. However it is useful to log such events since
|
||||
* they are rare and may hint at errors in a script or a bug in Redis. */
|
||||
int ctype = getClientType(c);
|
||||
if (ctype == CLIENT_TYPE_MASTER || ctype == CLIENT_TYPE_SLAVE) {
|
||||
char* to = ctype == CLIENT_TYPE_MASTER? "master": "replica";
|
||||
char* from = ctype == CLIENT_TYPE_MASTER? "replica": "master";
|
||||
if (ctype == CLIENT_TYPE_MASTER || ctype == CLIENT_TYPE_SLAVE || c->id == CLIENT_ID_AOF) {
|
||||
char *to, *from;
|
||||
|
||||
if (c->id == CLIENT_ID_AOF) {
|
||||
to = "AOF-loading-client";
|
||||
from = "server";
|
||||
} else if (ctype == CLIENT_TYPE_MASTER) {
|
||||
to = "master";
|
||||
from = "replica";
|
||||
} else {
|
||||
to = "replica";
|
||||
from = "master";
|
||||
}
|
||||
|
||||
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
|
||||
serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
|
||||
"to its %s: '%s' after processing the command "
|
||||
"'%s'", from, to, s, cmdname);
|
||||
server.stat_unexpected_error_replies++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,6 +1162,11 @@ void freeClient(client *c) {
|
||||
listDelNode(server.clients_to_close,ln);
|
||||
}
|
||||
|
||||
/* Remove the contribution that this client gave to our
|
||||
* incrementally computed memory usage. */
|
||||
server.stat_clients_type_memory[c->client_cron_last_memory_type] -=
|
||||
c->client_cron_last_memory_usage;
|
||||
|
||||
/* Release other dynamically allocated client structure fields,
|
||||
* and finally release the client structure itself. */
|
||||
if (c->name) decrRefCount(c->name);
|
||||
@@ -2314,6 +2333,25 @@ NULL
|
||||
return;
|
||||
}
|
||||
|
||||
if (options & CLIENT_TRACKING_OPTIN && options & CLIENT_TRACKING_OPTOUT)
|
||||
{
|
||||
addReplyError(c,
|
||||
"You can't specify both OPTIN mode and OPTOUT mode");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((options & CLIENT_TRACKING_OPTIN && c->flags & CLIENT_TRACKING_OPTOUT) ||
|
||||
(options & CLIENT_TRACKING_OPTOUT && c->flags & CLIENT_TRACKING_OPTIN))
|
||||
{
|
||||
addReplyError(c,
|
||||
"You can't switch OPTIN/OPTOUT mode before disabling "
|
||||
"tracking for this client, and then re-enabling it with "
|
||||
"a different mode.");
|
||||
zfree(prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
enableTracking(c,redir,options,prefix,numprefix);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"off")) {
|
||||
disableTracking(c);
|
||||
|
||||
+18
-25
@@ -347,7 +347,15 @@ void freeStreamObject(robj *o) {
|
||||
}
|
||||
|
||||
void incrRefCount(robj *o) {
|
||||
if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount++;
|
||||
if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT) {
|
||||
o->refcount++;
|
||||
} else {
|
||||
if (o->refcount == OBJ_SHARED_REFCOUNT) {
|
||||
/* Nothing to do: this refcount is immutable. */
|
||||
} else if (o->refcount == OBJ_STATIC_REFCOUNT) {
|
||||
serverPanic("You tried to retain an object allocated in the stack");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decrRefCount(robj *o) {
|
||||
@@ -974,30 +982,15 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
mh->repl_backlog = mem;
|
||||
mem_total += mem;
|
||||
|
||||
mem = 0;
|
||||
if (listLength(server.clients)) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
size_t mem_normal = 0, mem_slaves = 0;
|
||||
|
||||
listRewind(server.clients,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
size_t mem_curr = 0;
|
||||
client *c = listNodeValue(ln);
|
||||
int type = getClientType(c);
|
||||
mem_curr += getClientOutputBufferMemoryUsage(c);
|
||||
mem_curr += sdsAllocSize(c->querybuf);
|
||||
mem_curr += sizeof(client);
|
||||
if (type == CLIENT_TYPE_SLAVE)
|
||||
mem_slaves += mem_curr;
|
||||
else
|
||||
mem_normal += mem_curr;
|
||||
}
|
||||
mh->clients_slaves = mem_slaves;
|
||||
mh->clients_normal = mem_normal;
|
||||
mem = mem_slaves + mem_normal;
|
||||
}
|
||||
mem_total+=mem;
|
||||
/* Computing the memory used by the clients would be O(N) if done
|
||||
* here online. We use our values computed incrementally by
|
||||
* clientsCronTrackClientsMemUsage(). */
|
||||
mh->clients_slaves = server.stat_clients_type_memory[CLIENT_TYPE_SLAVE];
|
||||
mh->clients_normal = server.stat_clients_type_memory[CLIENT_TYPE_MASTER]+
|
||||
server.stat_clients_type_memory[CLIENT_TYPE_PUBSUB]+
|
||||
server.stat_clients_type_memory[CLIENT_TYPE_NORMAL];
|
||||
mem_total += mh->clients_slaves;
|
||||
mem_total += mh->clients_normal;
|
||||
|
||||
mem = 0;
|
||||
if (server.aof_state != AOF_OFF) {
|
||||
|
||||
+41
-11
@@ -206,6 +206,8 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
|
||||
|
||||
/* Subscribe a client to a pattern. Returns 1 if the operation succeeded, or 0 if the client was already subscribed to that pattern. */
|
||||
int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
dictEntry *de;
|
||||
list *clients;
|
||||
int retval = 0;
|
||||
|
||||
if (listSearchKey(c->pubsub_patterns,pattern) == NULL) {
|
||||
@@ -217,6 +219,16 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
pat->pattern = getDecodedObject(pattern);
|
||||
pat->client = c;
|
||||
listAddNodeTail(server.pubsub_patterns,pat);
|
||||
/* Add the client to the pattern -> list of clients hash table */
|
||||
de = dictFind(server.pubsub_patterns_dict,pattern);
|
||||
if (de == NULL) {
|
||||
clients = listCreate();
|
||||
dictAdd(server.pubsub_patterns_dict,pattern,clients);
|
||||
incrRefCount(pattern);
|
||||
} else {
|
||||
clients = dictGetVal(de);
|
||||
}
|
||||
listAddNodeTail(clients,c);
|
||||
}
|
||||
/* Notify the client */
|
||||
addReplyPubsubPatSubscribed(c,pattern);
|
||||
@@ -226,6 +238,8 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
|
||||
/* Unsubscribe a client from a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was not subscribed to the specified channel. */
|
||||
int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
dictEntry *de;
|
||||
list *clients;
|
||||
listNode *ln;
|
||||
pubsubPattern pat;
|
||||
int retval = 0;
|
||||
@@ -238,6 +252,18 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
|
||||
pat.pattern = pattern;
|
||||
ln = listSearchKey(server.pubsub_patterns,&pat);
|
||||
listDelNode(server.pubsub_patterns,ln);
|
||||
/* Remove the client from the pattern -> clients list hash table */
|
||||
de = dictFind(server.pubsub_patterns_dict,pattern);
|
||||
serverAssertWithInfo(c,NULL,de != NULL);
|
||||
clients = dictGetVal(de);
|
||||
ln = listSearchKey(clients,c);
|
||||
serverAssertWithInfo(c,NULL,ln != NULL);
|
||||
listDelNode(clients,ln);
|
||||
if (listLength(clients) == 0) {
|
||||
/* Free the list and associated hash entry at all if this was
|
||||
* the latest client. */
|
||||
dictDelete(server.pubsub_patterns_dict,pattern);
|
||||
}
|
||||
}
|
||||
/* Notify the client */
|
||||
if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
|
||||
@@ -284,6 +310,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
|
||||
int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
int receivers = 0;
|
||||
dictEntry *de;
|
||||
dictIterator *di;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
|
||||
@@ -302,23 +329,26 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
||||
}
|
||||
}
|
||||
/* Send to clients listening to matching channels */
|
||||
if (listLength(server.pubsub_patterns)) {
|
||||
listRewind(server.pubsub_patterns,&li);
|
||||
di = dictGetIterator(server.pubsub_patterns_dict);
|
||||
if (di) {
|
||||
channel = getDecodedObject(channel);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
pubsubPattern *pat = ln->value;
|
||||
|
||||
if (stringmatchlen((char*)pat->pattern->ptr,
|
||||
sdslen(pat->pattern->ptr),
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *pattern = dictGetKey(de);
|
||||
list *clients = dictGetVal(de);
|
||||
if (!stringmatchlen((char*)pattern->ptr,
|
||||
sdslen(pattern->ptr),
|
||||
(char*)channel->ptr,
|
||||
sdslen(channel->ptr),0))
|
||||
{
|
||||
addReplyPubsubPatMessage(pat->client,
|
||||
pat->pattern,channel,message);
|
||||
sdslen(channel->ptr),0)) continue;
|
||||
|
||||
listRewind(clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
addReplyPubsubPatMessage(c,pattern,channel,message);
|
||||
receivers++;
|
||||
}
|
||||
}
|
||||
decrRefCount(channel);
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
return receivers;
|
||||
}
|
||||
|
||||
+2
-2
@@ -110,7 +110,7 @@ quicklist *quicklistCreate(void) {
|
||||
return quicklist;
|
||||
}
|
||||
|
||||
#define COMPRESS_MAX (1 << QL_COMP_BITS)
|
||||
#define COMPRESS_MAX ((1 << QL_COMP_BITS)-1)
|
||||
void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
if (compress > COMPRESS_MAX) {
|
||||
compress = COMPRESS_MAX;
|
||||
@@ -120,7 +120,7 @@ void quicklistSetCompressDepth(quicklist *quicklist, int compress) {
|
||||
quicklist->compress = compress;
|
||||
}
|
||||
|
||||
#define FILL_MAX (1 << (QL_FILL_BITS-1))
|
||||
#define FILL_MAX ((1 << (QL_FILL_BITS-1))-1)
|
||||
void quicklistSetFill(quicklist *quicklist, int fill) {
|
||||
if (fill > FILL_MAX) {
|
||||
fill = FILL_MAX;
|
||||
|
||||
@@ -1002,8 +1002,8 @@ ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
|
||||
* the rdbSaveObject() function. Currently we use a trick to get
|
||||
* this length with very little changes to the code. In the future
|
||||
* we could switch to a faster solution. */
|
||||
size_t rdbSavedObjectLen(robj *o) {
|
||||
ssize_t len = rdbSaveObject(NULL,o,NULL);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key) {
|
||||
ssize_t len = rdbSaveObject(NULL,o,key);
|
||||
serverAssertWithInfo(NULL,o,len != -1);
|
||||
return len;
|
||||
}
|
||||
@@ -1175,10 +1175,7 @@ int rdbSaveRio(rio *rdb, int *error, int rdbflags, rdbSaveInfo *rsi) {
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
|
||||
if (rdbSaveLen(rdb,j) == -1) goto werr;
|
||||
|
||||
/* Write the RESIZE DB opcode. We trim the size to UINT32_MAX, which
|
||||
* is currently the largest type we are able to represent in RDB sizes.
|
||||
* However this does not limit the actual size of the DB to load since
|
||||
* these sizes are just hints to resize the hash tables. */
|
||||
/* Write the RESIZE DB opcode. */
|
||||
uint64_t db_size, expires_size;
|
||||
db_size = dictSize(db->dict);
|
||||
expires_size = dictSize(db->expires);
|
||||
@@ -1425,7 +1422,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
|
||||
|
||||
/* Load a Redis object of the specified type from the specified file.
|
||||
* On success a newly allocated object is returned, otherwise NULL. */
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
robj *rdbLoadObject(int rdbtype, rio *rdb, sds key) {
|
||||
robj *o = NULL, *ele, *dec;
|
||||
uint64_t len;
|
||||
unsigned int i;
|
||||
@@ -1889,7 +1886,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
|
||||
exit(1);
|
||||
}
|
||||
RedisModuleIO io;
|
||||
moduleInitIOContext(io,mt,rdb,key);
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj,key);
|
||||
moduleInitIOContext(io,mt,rdb,&keyobj);
|
||||
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
|
||||
/* Call the rdb_load method of the module providing the 10 bit
|
||||
* encoding version in the lower 10 bits of the module ID. */
|
||||
@@ -2047,7 +2046,8 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
long long lru_clock = LRU_CLOCK();
|
||||
|
||||
while(1) {
|
||||
robj *key, *val;
|
||||
sds key;
|
||||
robj *val;
|
||||
|
||||
/* Read type. */
|
||||
if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
|
||||
@@ -2219,10 +2219,11 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
}
|
||||
|
||||
/* Read key */
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
if ((key = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL)) == NULL)
|
||||
goto eoferr;
|
||||
/* Read value */
|
||||
if ((val = rdbLoadObject(type,rdb,key)) == NULL) {
|
||||
decrRefCount(key);
|
||||
sdsfree(key);
|
||||
goto eoferr;
|
||||
}
|
||||
|
||||
@@ -2230,26 +2231,49 @@ int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the slave. */
|
||||
if (iAmMaster() && !(rdbflags&RDBFLAGS_AOF_PREAMBLE) && expiretime != -1 && expiretime < now) {
|
||||
decrRefCount(key);
|
||||
* snapshot taken by the master may not be reflected on the slave.
|
||||
* Similarly if the RDB is the preamble of an AOF file, we want to
|
||||
* load all the keys as they are, since the log of operations later
|
||||
* assume to work in an exact keyspace state. */
|
||||
if (iAmMaster() &&
|
||||
!(rdbflags&RDBFLAGS_AOF_PREAMBLE) &&
|
||||
expiretime != -1 && expiretime < now)
|
||||
{
|
||||
sdsfree(key);
|
||||
decrRefCount(val);
|
||||
} else {
|
||||
robj keyobj;
|
||||
|
||||
/* Add the new object in the hash table */
|
||||
dbAdd(db,key,val);
|
||||
int added = dbAddRDBLoad(db,key,val);
|
||||
if (!added) {
|
||||
if (rdbflags & RDBFLAGS_ALLOW_DUP) {
|
||||
/* This flag is useful for DEBUG RELOAD special modes.
|
||||
* When it's set we allow new keys to replace the current
|
||||
* keys with the same name. */
|
||||
initStaticStringObject(keyobj,key);
|
||||
dbSyncDelete(db,&keyobj);
|
||||
dbAddRDBLoad(db,key,val);
|
||||
} else {
|
||||
serverLog(LL_WARNING,
|
||||
"RDB has duplicated key '%s' in DB %d",key,db->id);
|
||||
serverPanic("Duplicated key found in RDB file");
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the expire time if needed */
|
||||
if (expiretime != -1) setExpire(NULL,db,key,expiretime);
|
||||
if (expiretime != -1) {
|
||||
initStaticStringObject(keyobj,key);
|
||||
setExpire(NULL,db,&keyobj,expiretime);
|
||||
}
|
||||
|
||||
/* Set usage information (for eviction). */
|
||||
objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock,1000);
|
||||
|
||||
/* Decrement the key refcount since dbAdd() will take its
|
||||
* own reference. */
|
||||
decrRefCount(key);
|
||||
}
|
||||
if (server.key_load_delay)
|
||||
usleep(server.key_load_delay);
|
||||
|
||||
/* Loading the database more slowly is useful in order to test
|
||||
* certain edge cases. */
|
||||
if (server.key_load_delay) usleep(server.key_load_delay);
|
||||
|
||||
/* Reset the state that is key-specified and is populated by
|
||||
* opcodes before the key, so that we start from scratch again. */
|
||||
|
||||
@@ -122,9 +122,10 @@
|
||||
#define RDB_LOAD_SDS (1<<2)
|
||||
|
||||
/* flags on the purpose of rdb save or load */
|
||||
#define RDBFLAGS_NONE 0
|
||||
#define RDBFLAGS_AOF_PREAMBLE (1<<0)
|
||||
#define RDBFLAGS_REPLICATION (1<<1)
|
||||
#define RDBFLAGS_NONE 0 /* No special RDB loading. */
|
||||
#define RDBFLAGS_AOF_PREAMBLE (1<<0) /* Load/save the RDB as AOF preamble. */
|
||||
#define RDBFLAGS_REPLICATION (1<<1) /* Load/save for SYNC. */
|
||||
#define RDBFLAGS_ALLOW_DUP (1<<2) /* Allow duplicated keys when loading.*/
|
||||
|
||||
int rdbSaveType(rio *rdb, unsigned char type);
|
||||
int rdbLoadType(rio *rdb);
|
||||
@@ -143,8 +144,8 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(char *filename, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
robj *rdbLoadObject(int type, rio *rdb, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o, robj *key);
|
||||
robj *rdbLoadObject(int type, rio *rdb, sds key);
|
||||
void backgroundSaveDoneHandler(int exitcode, int bysignal);
|
||||
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
|
||||
ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
|
||||
|
||||
@@ -287,7 +287,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
|
||||
rdbstate.keys++;
|
||||
/* Read value */
|
||||
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
|
||||
if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
|
||||
if ((val = rdbLoadObject(type,&rdb,key->ptr)) == NULL) goto eoferr;
|
||||
/* Check if the key already expired. */
|
||||
if (expiretime != -1 && expiretime < now)
|
||||
rdbstate.already_expired++;
|
||||
|
||||
+10
-1
@@ -470,7 +470,11 @@ RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongLong)(Re
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str);
|
||||
#ifdef __GNUC__
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||
#else
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...);
|
||||
#endif
|
||||
void REDISMODULE_API_FUNC(RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
const char *REDISMODULE_API_FUNC(RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len);
|
||||
int REDISMODULE_API_FUNC(RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err);
|
||||
@@ -554,8 +558,13 @@ void REDISMODULE_API_FUNC(RedisModule_SaveLongDouble)(RedisModuleIO *io, long do
|
||||
long double REDISMODULE_API_FUNC(RedisModule_LoadLongDouble)(RedisModuleIO *io);
|
||||
void *REDISMODULE_API_FUNC(RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt);
|
||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt);
|
||||
#ifdef __GNUC__
|
||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) __attribute__ ((format (printf, 3, 4)));
|
||||
#else
|
||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
||||
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
||||
#endif
|
||||
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
|
||||
void REDISMODULE_API_FUNC(RedisModule_LatencyAddSample)(const char *event, mstime_t latency);
|
||||
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
|
||||
@@ -673,7 +682,7 @@ int REDISMODULE_API_FUNC(RedisModule_AuthenticateClientWithUser)(RedisModuleCtx
|
||||
void REDISMODULE_API_FUNC(RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id);
|
||||
#endif
|
||||
|
||||
#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
|
||||
#define RedisModule_IsAOFClient(id) ((id) == CLIENT_ID_AOF)
|
||||
|
||||
/* This is included inline inside each Redis module. */
|
||||
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
|
||||
|
||||
+4
-4
@@ -657,12 +657,11 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
!server.loading && /* Don't care about mem if loading. */
|
||||
!server.masterhost && /* Slave must execute the script. */
|
||||
server.lua_write_dirty == 0 && /* Script had no side effects so far. */
|
||||
server.lua_oom && /* Detected OOM when script start. */
|
||||
(cmd->flags & CMD_DENYOOM))
|
||||
{
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
|
||||
luaPushError(lua, shared.oomerr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
luaPushError(lua, shared.oomerr->ptr);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (cmd->flags & CMD_RANDOM) server.lua_random_dirty = 1;
|
||||
@@ -960,6 +959,7 @@ int luaLogCommand(lua_State *lua) {
|
||||
lua_pushstring(lua, "Invalid debug level.");
|
||||
return lua_error(lua);
|
||||
}
|
||||
if (level < server.verbosity) return 0;
|
||||
|
||||
/* Glue together all the arguments */
|
||||
log = sdsempty();
|
||||
|
||||
+51
-11
@@ -672,15 +672,15 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"multi",multiCommand,1,
|
||||
"no-script fast @transaction",
|
||||
"no-script fast ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"exec",execCommand,1,
|
||||
"no-script no-monitor no-slowlog @transaction",
|
||||
"no-script no-monitor no-slowlog ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"discard",discardCommand,1,
|
||||
"no-script fast @transaction",
|
||||
"no-script fast ok-loading ok-stale @transaction",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"sync",syncCommand,1,
|
||||
@@ -947,11 +947,11 @@ struct redisCommand redisCommandTable[] = {
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"xread",xreadCommand,-4,
|
||||
"read-only no-script @stream @blocking",
|
||||
"read-only @stream @blocking",
|
||||
0,xreadGetKeys,1,1,1,0,0,0},
|
||||
|
||||
{"xreadgroup",xreadCommand,-7,
|
||||
"write no-script @stream @blocking",
|
||||
"write @stream @blocking",
|
||||
0,xreadGetKeys,1,1,1,0,0,0},
|
||||
|
||||
{"xgroup",xgroupCommand,-2,
|
||||
@@ -1004,7 +1004,11 @@ struct redisCommand redisCommandTable[] = {
|
||||
|
||||
{"acl",aclCommand,-2,
|
||||
"admin no-script no-slowlog ok-loading ok-stale",
|
||||
0,NULL,0,0,0,0,0,0}
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"lcs",lcsCommand,-4,
|
||||
"write use-memory @string",
|
||||
0,lcsGetKeys,0,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
@@ -1589,6 +1593,28 @@ int clientsCronTrackExpansiveClients(client *c) {
|
||||
return 0; /* This function never terminates the client. */
|
||||
}
|
||||
|
||||
/* Iterating all the clients in getMemoryOverheadData() is too slow and
|
||||
* in turn would make the INFO command too slow. So we perform this
|
||||
* computation incrementally and track the (not instantaneous but updated
|
||||
* to the second) total memory used by clients using clinetsCron() in
|
||||
* a more incremental way (depending on server.hz). */
|
||||
int clientsCronTrackClientsMemUsage(client *c) {
|
||||
size_t mem = 0;
|
||||
int type = getClientType(c);
|
||||
mem += getClientOutputBufferMemoryUsage(c);
|
||||
mem += sdsAllocSize(c->querybuf);
|
||||
mem += sizeof(client);
|
||||
/* Now that we have the memory used by the client, remove the old
|
||||
* value from the old categoty, and add it back. */
|
||||
server.stat_clients_type_memory[c->client_cron_last_memory_type] -=
|
||||
c->client_cron_last_memory_usage;
|
||||
server.stat_clients_type_memory[type] += mem;
|
||||
/* Remember what we added and where, to remove it next time. */
|
||||
c->client_cron_last_memory_usage = mem;
|
||||
c->client_cron_last_memory_type = type;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the max samples in the memory usage of clients tracked by
|
||||
* the function clientsCronTrackExpansiveClients(). */
|
||||
void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
|
||||
@@ -1649,6 +1675,7 @@ void clientsCron(void) {
|
||||
if (clientsCronHandleTimeout(c,now)) continue;
|
||||
if (clientsCronResizeQueryBuffer(c)) continue;
|
||||
if (clientsCronTrackExpansiveClients(c)) continue;
|
||||
if (clientsCronTrackClientsMemUsage(c)) continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2661,6 +2688,7 @@ void resetServerStats(void) {
|
||||
}
|
||||
server.stat_net_input_bytes = 0;
|
||||
server.stat_net_output_bytes = 0;
|
||||
server.stat_unexpected_error_replies = 0;
|
||||
server.aof_delayed_fsync = 0;
|
||||
}
|
||||
|
||||
@@ -2756,6 +2784,7 @@ void initServer(void) {
|
||||
evictionPoolAlloc(); /* Initialize the LRU keys pool. */
|
||||
server.pubsub_channels = dictCreate(&keylistDictType,NULL);
|
||||
server.pubsub_patterns = listCreate();
|
||||
server.pubsub_patterns_dict = dictCreate(&keylistDictType,NULL);
|
||||
listSetFreeMethod(server.pubsub_patterns,freePubsubPattern);
|
||||
listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern);
|
||||
server.cronloops = 0;
|
||||
@@ -2786,6 +2815,8 @@ void initServer(void) {
|
||||
server.stat_rdb_cow_bytes = 0;
|
||||
server.stat_aof_cow_bytes = 0;
|
||||
server.stat_module_cow_bytes = 0;
|
||||
for (int j = 0; j < CLIENT_TYPE_COUNT; j++)
|
||||
server.stat_clients_type_memory[j] = 0;
|
||||
server.cron_malloc_stats.zmalloc_used = 0;
|
||||
server.cron_malloc_stats.process_rss = 0;
|
||||
server.cron_malloc_stats.allocator_allocated = 0;
|
||||
@@ -3265,7 +3296,7 @@ void call(client *c, int flags) {
|
||||
if (flags & CMD_CALL_PROPAGATE) {
|
||||
int multi_emitted = 0;
|
||||
/* Wrap the commands in server.also_propagate array,
|
||||
* but don't wrap it if we are already in MULIT context,
|
||||
* but don't wrap it if we are already in MULTI context,
|
||||
* in case the nested MULTI/EXEC.
|
||||
*
|
||||
* And if the array contains only one command, no need to
|
||||
@@ -3439,6 +3470,13 @@ int processCommand(client *c) {
|
||||
addReply(c, shared.oomerr);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Save out_of_memory result at script start, otherwise if we check OOM
|
||||
* untill first write within script, memory used by lua stack and
|
||||
* arguments might interfere. */
|
||||
if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand) {
|
||||
server.lua_oom = out_of_memory;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure to use a reasonable amount of memory for client side
|
||||
@@ -3974,12 +4012,12 @@ sds genRedisInfoString(const char *section) {
|
||||
"client_recent_max_output_buffer:%zu\r\n"
|
||||
"blocked_clients:%d\r\n"
|
||||
"tracking_clients:%d\r\n"
|
||||
"clients_in_timeout_table:%lld\r\n",
|
||||
"clients_in_timeout_table:%llu\r\n",
|
||||
listLength(server.clients)-listLength(server.slaves),
|
||||
maxin, maxout,
|
||||
server.blocked_clients,
|
||||
server.tracking_clients,
|
||||
raxSize(server.clients_timeout_table));
|
||||
(unsigned long long) raxSize(server.clients_timeout_table));
|
||||
}
|
||||
|
||||
/* Memory */
|
||||
@@ -4229,7 +4267,8 @@ sds genRedisInfoString(const char *section) {
|
||||
"active_defrag_key_hits:%lld\r\n"
|
||||
"active_defrag_key_misses:%lld\r\n"
|
||||
"tracking_total_keys:%lld\r\n"
|
||||
"tracking_total_items:%lld\r\n",
|
||||
"tracking_total_items:%lld\r\n"
|
||||
"unexpected_error_replies:%lld\r\n",
|
||||
server.stat_numconnections,
|
||||
server.stat_numcommands,
|
||||
getInstantaneousMetric(STATS_METRIC_COMMAND),
|
||||
@@ -4258,7 +4297,8 @@ sds genRedisInfoString(const char *section) {
|
||||
server.stat_active_defrag_key_hits,
|
||||
server.stat_active_defrag_key_misses,
|
||||
(unsigned long long) trackingGetTotalKeys(),
|
||||
(unsigned long long) trackingGetTotalItems());
|
||||
(unsigned long long) trackingGetTotalItems(),
|
||||
server.stat_unexpected_error_replies);
|
||||
}
|
||||
|
||||
/* Replication */
|
||||
|
||||
+25
-7
@@ -274,6 +274,7 @@ typedef long long ustime_t; /* microsecond time type. */
|
||||
#define CLIENT_TYPE_SLAVE 1 /* Slaves. */
|
||||
#define CLIENT_TYPE_PUBSUB 2 /* Clients subscribed to PubSub channels. */
|
||||
#define CLIENT_TYPE_MASTER 3 /* Master. */
|
||||
#define CLIENT_TYPE_COUNT 4 /* Total number of client types. */
|
||||
#define CLIENT_TYPE_OBUF_COUNT 3 /* Number of clients to expose to output
|
||||
buffer configuration. Just the first
|
||||
three: normal, slave, pubsub. */
|
||||
@@ -596,7 +597,9 @@ typedef struct RedisModuleDigest {
|
||||
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
|
||||
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
|
||||
|
||||
#define OBJ_SHARED_REFCOUNT INT_MAX
|
||||
#define OBJ_SHARED_REFCOUNT INT_MAX /* Global object never destroyed. */
|
||||
#define OBJ_STATIC_REFCOUNT (INT_MAX-1) /* Object allocated in the stack. */
|
||||
#define OBJ_FIRST_SPECIAL_REFCOUNT OBJ_STATIC_REFCOUNT
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned encoding:4;
|
||||
@@ -617,7 +620,7 @@ char *getObjectTypeName(robj*);
|
||||
* we'll update it when the structure is changed, to avoid bugs like
|
||||
* bug #85 introduced exactly in this way. */
|
||||
#define initStaticStringObject(_var,_ptr) do { \
|
||||
_var.refcount = 1; \
|
||||
_var.refcount = OBJ_STATIC_REFCOUNT; \
|
||||
_var.type = OBJ_STRING; \
|
||||
_var.encoding = OBJ_ENCODING_RAW; \
|
||||
_var.ptr = _ptr; \
|
||||
@@ -820,10 +823,10 @@ typedef struct client {
|
||||
* when the authenticated user
|
||||
* changes. */
|
||||
void *auth_callback_privdata; /* Private data that is passed when the auth
|
||||
* changed callback is executed. Opaque for
|
||||
* changed callback is executed. Opaque for
|
||||
* Redis Core. */
|
||||
void *auth_module; /* The module that owns the callback, which is used
|
||||
* to disconnect the client if the module is
|
||||
* to disconnect the client if the module is
|
||||
* unloaded for cleanup. Opaque for Redis Core.*/
|
||||
|
||||
/* If this client is in tracking mode and this field is non zero,
|
||||
@@ -833,6 +836,13 @@ typedef struct client {
|
||||
rax *client_tracking_prefixes; /* A dictionary of prefixes we are already
|
||||
subscribed to in BCAST mode, in the
|
||||
context of client side caching. */
|
||||
/* In clientsCronTrackClientsMemUsage() we track the memory usage of
|
||||
* each client and add it to the sum of all the clients of a given type,
|
||||
* however we need to remember what was the old contribution of each
|
||||
* client, and in which categoty the client was, in order to remove it
|
||||
* before adding it the new value. */
|
||||
uint64_t client_cron_last_memory_usage;
|
||||
int client_cron_last_memory_type;
|
||||
/* Response buffer */
|
||||
int bufpos;
|
||||
char buf[PROTO_REPLY_CHUNK_BYTES];
|
||||
@@ -1129,6 +1139,8 @@ struct redisServer {
|
||||
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
||||
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
|
||||
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
|
||||
uint64_t stat_clients_type_memory[CLIENT_TYPE_COUNT];/* Mem usage by type */
|
||||
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
|
||||
/* The following two are used to track instantaneous metrics, like
|
||||
* number of operations per second, network traffic. */
|
||||
struct {
|
||||
@@ -1348,6 +1360,7 @@ struct redisServer {
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
dict *pubsub_patterns_dict; /* A dict of pubsub_patterns */
|
||||
int notify_keyspace_events; /* Events to propagate via Pub/Sub. This is an
|
||||
xor of NOTIFY_... flags. */
|
||||
/* Cluster */
|
||||
@@ -1390,10 +1403,12 @@ struct redisServer {
|
||||
execution. */
|
||||
int lua_kill; /* Kill the script if true. */
|
||||
int lua_always_replicate_commands; /* Default replication type. */
|
||||
int lua_oom; /* OOM detected when script start? */
|
||||
/* Lazy free */
|
||||
int lazyfree_lazy_eviction;
|
||||
int lazyfree_lazy_expire;
|
||||
int lazyfree_lazy_server_del;
|
||||
int lazyfree_lazy_user_del;
|
||||
/* Latency monitor */
|
||||
long long latency_monitor_threshold;
|
||||
dict *latency_events;
|
||||
@@ -2054,8 +2069,9 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
#define LOOKUP_NONE 0
|
||||
#define LOOKUP_NOTOUCH (1<<0)
|
||||
void dbAdd(redisDb *db, robj *key, robj *val);
|
||||
int dbAddRDBLoad(redisDb *db, sds key, robj *val);
|
||||
void dbOverwrite(redisDb *db, robj *key, robj *val);
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl);
|
||||
void genericSetKey(redisDb *db, robj *key, robj *val, int keepttl, int signal);
|
||||
void setKey(redisDb *db, robj *key, robj *val);
|
||||
int dbExists(redisDb *db, robj *key);
|
||||
robj *dbRandomKey(redisDb *db);
|
||||
@@ -2080,8 +2096,8 @@ unsigned int delKeysInSlot(unsigned int hashslot);
|
||||
int verifyClusterConfigWithData(void);
|
||||
void scanGenericCommand(client *c, robj *o, unsigned long cursor);
|
||||
int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor);
|
||||
void slotToKeyAdd(robj *key);
|
||||
void slotToKeyDel(robj *key);
|
||||
void slotToKeyAdd(sds key);
|
||||
void slotToKeyDel(sds key);
|
||||
void slotToKeyFlush(void);
|
||||
int dbAsyncDelete(redisDb *db, robj *key);
|
||||
void emptyDbAsync(redisDb *db);
|
||||
@@ -2099,6 +2115,7 @@ int *migrateGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkey
|
||||
int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *memoryGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
int *lcsGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
|
||||
|
||||
/* Cluster */
|
||||
void clusterInit(void);
|
||||
@@ -2370,6 +2387,7 @@ void xdelCommand(client *c);
|
||||
void xtrimCommand(client *c);
|
||||
void lolwutCommand(client *c);
|
||||
void aclCommand(client *c);
|
||||
void lcsCommand(client *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
||||
+18
-2
@@ -935,7 +935,6 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
streamIterator si;
|
||||
int64_t numfields;
|
||||
streamID id;
|
||||
int propagate_last_id = 0;
|
||||
|
||||
/* If the client is asking for some history, we serve it using a
|
||||
* different function, so that we return entries *solely* from its
|
||||
@@ -951,6 +950,8 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
arraylen_ptr = addReplyDeferredLen(c);
|
||||
streamIteratorStart(&si,s,start,end,rev);
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
int propagate_last_id = 0;
|
||||
|
||||
/* Update the group last_id if needed. */
|
||||
if (group && streamCompareID(&id,&group->last_id) > 0) {
|
||||
group->last_id = id;
|
||||
@@ -1373,6 +1374,11 @@ void xreadCommand(client *c) {
|
||||
int moreargs = c->argc-i-1;
|
||||
char *o = c->argv[i]->ptr;
|
||||
if (!strcasecmp(o,"BLOCK") && moreargs) {
|
||||
if (c->flags & CLIENT_LUA) {
|
||||
/* There is no sense to use BLOCK option within LUA */
|
||||
addReplyErrorFormat(c, "%s command is not allowed with BLOCK option from scripts", (char *)c->argv[0]->ptr);
|
||||
return;
|
||||
}
|
||||
i++;
|
||||
if (getTimeoutFromObjectOrReply(c,c->argv[i],&timeout,
|
||||
UNIT_MILLISECONDS) != C_OK) return;
|
||||
@@ -1921,11 +1927,21 @@ void xackCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Start parsing the IDs, so that we abort ASAP if there is a syntax
|
||||
* error: the return value of this command cannot be an error in case
|
||||
* the client successfully acknowledged some messages, so it should be
|
||||
* executed in a "all or nothing" fashion. */
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
streamID id;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
}
|
||||
|
||||
int acknowledged = 0;
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
streamID id;
|
||||
unsigned char buf[sizeof(streamID)];
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
|
||||
if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK)
|
||||
serverPanic("StreamID invalid after check. Should not be possible.");
|
||||
streamEncodeID(buf,&id);
|
||||
|
||||
/* Lookup the ID in the group PEL: it will have a reference to the
|
||||
|
||||
+205
-1
@@ -84,7 +84,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
return;
|
||||
}
|
||||
genericSetKey(c->db,key,val,flags & OBJ_SET_KEEPTTL);
|
||||
genericSetKey(c->db,key,val,flags & OBJ_SET_KEEPTTL,1);
|
||||
server.dirty++;
|
||||
if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
|
||||
@@ -479,3 +479,207 @@ void strlenCommand(client *c) {
|
||||
checkType(c,o,OBJ_STRING)) return;
|
||||
addReplyLongLong(c,stringObjectLen(o));
|
||||
}
|
||||
|
||||
/* LCS -- Longest common subsequence.
|
||||
*
|
||||
* LCS [IDX] [MINMATCHLEN <len>]
|
||||
* STRINGS <string> <string> | KEYS <keya> <keyb> */
|
||||
void lcsCommand(client *c) {
|
||||
uint32_t i, j;
|
||||
long long minmatchlen = 0;
|
||||
sds a = NULL, b = NULL;
|
||||
int getlen = 0, getidx = 0, withmatchlen = 0;
|
||||
robj *obja = NULL, *objb = NULL;
|
||||
|
||||
for (j = 1; j < (uint32_t)c->argc; j++) {
|
||||
char *opt = c->argv[j]->ptr;
|
||||
int moreargs = (c->argc-1) - j;
|
||||
|
||||
if (!strcasecmp(opt,"IDX")) {
|
||||
getidx = 1;
|
||||
} else if (!strcasecmp(opt,"LEN")) {
|
||||
getlen = 1;
|
||||
} else if (!strcasecmp(opt,"WITHMATCHLEN")) {
|
||||
withmatchlen = 1;
|
||||
} else if (!strcasecmp(opt,"MINMATCHLEN") && moreargs) {
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[j+1],&minmatchlen,NULL)
|
||||
!= C_OK) return;
|
||||
if (minmatchlen < 0) minmatchlen = 0;
|
||||
j++;
|
||||
} else if (!strcasecmp(opt,"STRINGS")) {
|
||||
if (a != NULL) {
|
||||
addReplyError(c,"Either use STRINGS or KEYS");
|
||||
return;
|
||||
}
|
||||
a = c->argv[j+1]->ptr;
|
||||
b = c->argv[j+2]->ptr;
|
||||
j += 2;
|
||||
} else if (!strcasecmp(opt,"KEYS")) {
|
||||
if (a != NULL) {
|
||||
addReplyError(c,"Either use STRINGS or KEYS");
|
||||
return;
|
||||
}
|
||||
obja = lookupKeyRead(c->db,c->argv[j+1]);
|
||||
objb = lookupKeyRead(c->db,c->argv[j+2]);
|
||||
obja = obja ? getDecodedObject(obja) : createStringObject("",0);
|
||||
objb = objb ? getDecodedObject(objb) : createStringObject("",0);
|
||||
a = obja->ptr;
|
||||
b = objb->ptr;
|
||||
j += 2;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Complain if the user passed ambiguous parameters. */
|
||||
if (a == NULL) {
|
||||
addReplyError(c,"Please specify two strings: "
|
||||
"STRINGS or KEYS options are mandatory");
|
||||
return;
|
||||
} else if (getlen && getidx) {
|
||||
addReplyError(c,
|
||||
"If you want both the length and indexes, please "
|
||||
"just use IDX.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compute the LCS using the vanilla dynamic programming technique of
|
||||
* building a table of LCS(x,y) substrings. */
|
||||
uint32_t alen = sdslen(a);
|
||||
uint32_t blen = sdslen(b);
|
||||
|
||||
/* Setup an uint32_t array to store at LCS[i,j] the length of the
|
||||
* LCS A0..i-1, B0..j-1. Note that we have a linear array here, so
|
||||
* we index it as LCS[j+(blen+1)*j] */
|
||||
uint32_t *lcs = zmalloc((alen+1)*(blen+1)*sizeof(uint32_t));
|
||||
#define LCS(A,B) lcs[(B)+((A)*(blen+1))]
|
||||
|
||||
/* Start building the LCS table. */
|
||||
for (uint32_t i = 0; i <= alen; i++) {
|
||||
for (uint32_t j = 0; j <= blen; j++) {
|
||||
if (i == 0 || j == 0) {
|
||||
/* If one substring has length of zero, the
|
||||
* LCS length is zero. */
|
||||
LCS(i,j) = 0;
|
||||
} else if (a[i-1] == b[j-1]) {
|
||||
/* The len LCS (and the LCS itself) of two
|
||||
* sequences with the same final character, is the
|
||||
* LCS of the two sequences without the last char
|
||||
* plus that last char. */
|
||||
LCS(i,j) = LCS(i-1,j-1)+1;
|
||||
} else {
|
||||
/* If the last character is different, take the longest
|
||||
* between the LCS of the first string and the second
|
||||
* minus the last char, and the reverse. */
|
||||
uint32_t lcs1 = LCS(i-1,j);
|
||||
uint32_t lcs2 = LCS(i,j-1);
|
||||
LCS(i,j) = lcs1 > lcs2 ? lcs1 : lcs2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Store the actual LCS string in "result" if needed. We create
|
||||
* it backward, but the length is already known, we store it into idx. */
|
||||
uint32_t idx = LCS(alen,blen);
|
||||
sds result = NULL; /* Resulting LCS string. */
|
||||
void *arraylenptr = NULL; /* Deffered length of the array for IDX. */
|
||||
uint32_t arange_start = alen, /* alen signals that values are not set. */
|
||||
arange_end = 0,
|
||||
brange_start = 0,
|
||||
brange_end = 0;
|
||||
|
||||
/* Do we need to compute the actual LCS string? Allocate it in that case. */
|
||||
int computelcs = getidx || !getlen;
|
||||
if (computelcs) result = sdsnewlen(SDS_NOINIT,idx);
|
||||
|
||||
/* Start with a deferred array if we have to emit the ranges. */
|
||||
uint32_t arraylen = 0; /* Number of ranges emitted in the array. */
|
||||
if (getidx) {
|
||||
addReplyMapLen(c,2);
|
||||
addReplyBulkCString(c,"matches");
|
||||
arraylenptr = addReplyDeferredLen(c);
|
||||
}
|
||||
|
||||
i = alen, j = blen;
|
||||
while (computelcs && i > 0 && j > 0) {
|
||||
int emit_range = 0;
|
||||
if (a[i-1] == b[j-1]) {
|
||||
/* If there is a match, store the character and reduce
|
||||
* the indexes to look for a new match. */
|
||||
result[idx-1] = a[i-1];
|
||||
|
||||
/* Track the current range. */
|
||||
if (arange_start == alen) {
|
||||
arange_start = i-1;
|
||||
arange_end = i-1;
|
||||
brange_start = j-1;
|
||||
brange_end = j-1;
|
||||
} else {
|
||||
/* Let's see if we can extend the range backward since
|
||||
* it is contiguous. */
|
||||
if (arange_start == i && brange_start == j) {
|
||||
arange_start--;
|
||||
brange_start--;
|
||||
} else {
|
||||
emit_range = 1;
|
||||
}
|
||||
}
|
||||
/* Emit the range if we matched with the first byte of
|
||||
* one of the two strings. We'll exit the loop ASAP. */
|
||||
if (arange_start == 0 || brange_start == 0) emit_range = 1;
|
||||
idx--; i--; j--;
|
||||
} else {
|
||||
/* Otherwise reduce i and j depending on the largest
|
||||
* LCS between, to understand what direction we need to go. */
|
||||
uint32_t lcs1 = LCS(i-1,j);
|
||||
uint32_t lcs2 = LCS(i,j-1);
|
||||
if (lcs1 > lcs2)
|
||||
i--;
|
||||
else
|
||||
j--;
|
||||
if (arange_start != alen) emit_range = 1;
|
||||
}
|
||||
|
||||
/* Emit the current range if needed. */
|
||||
uint32_t match_len = arange_end - arange_start + 1;
|
||||
if (emit_range) {
|
||||
if (minmatchlen == 0 || match_len >= minmatchlen) {
|
||||
if (arraylenptr) {
|
||||
addReplyArrayLen(c,2+withmatchlen);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c,arange_start);
|
||||
addReplyLongLong(c,arange_end);
|
||||
addReplyArrayLen(c,2);
|
||||
addReplyLongLong(c,brange_start);
|
||||
addReplyLongLong(c,brange_end);
|
||||
if (withmatchlen) addReplyLongLong(c,match_len);
|
||||
arraylen++;
|
||||
}
|
||||
}
|
||||
arange_start = alen; /* Restart at the next match. */
|
||||
}
|
||||
}
|
||||
|
||||
/* Signal modified key, increment dirty, ... */
|
||||
|
||||
/* Reply depending on the given options. */
|
||||
if (arraylenptr) {
|
||||
addReplyBulkCString(c,"len");
|
||||
addReplyLongLong(c,LCS(alen,blen));
|
||||
setDeferredArrayLen(c,arraylenptr,arraylen);
|
||||
} else if (getlen) {
|
||||
addReplyLongLong(c,LCS(alen,blen));
|
||||
} else {
|
||||
addReplyBulkSds(c,result);
|
||||
result = NULL;
|
||||
}
|
||||
|
||||
/* Cleanup. */
|
||||
if (obja) decrRefCount(obja);
|
||||
if (objb) decrRefCount(objb);
|
||||
sdsfree(result);
|
||||
zfree(lcs);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -602,6 +602,10 @@ int ld2string(char *buf, size_t len, long double value, ld2string_mode mode) {
|
||||
}
|
||||
if (*p == '.') l--;
|
||||
}
|
||||
if (l == 2 && buf[0] == '-' && buf[1] == '0') {
|
||||
buf[0] = '0';
|
||||
l = 1;
|
||||
}
|
||||
break;
|
||||
default: return 0; /* Invalid mode. */
|
||||
}
|
||||
|
||||
+99
-23
@@ -109,41 +109,33 @@ int fsl_push(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return RedisModule_ReplyWithError(ctx,"ERR new element has to be greater than the head element");
|
||||
|
||||
fsl->list[fsl->length++] = ele;
|
||||
|
||||
if (fsl->length >= 2)
|
||||
RedisModule_SignalKeyAsReady(ctx, argv[1]);
|
||||
RedisModule_SignalKeyAsReady(ctx, argv[1]);
|
||||
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
int bpop2_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int bpop_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
RedisModuleString *keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||
|
||||
fsl_t *fsl;
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0) || !fsl)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (!fsl || fsl->length < 2)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int bpop2_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int bpop_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||
}
|
||||
|
||||
|
||||
/* FSL.BPOP2 <key> <timeout> - Block clients until list has two or more elements.
|
||||
/* FSL.BPOP <key> <timeout> - Block clients until list has two or more elements.
|
||||
* When that happens, unblock client and pop the last two elements (from the right). */
|
||||
int fsl_bpop2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
int fsl_bpop(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 3)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
@@ -155,13 +147,10 @@ int fsl_bpop2(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!fsl || fsl->length < 2) {
|
||||
/* Key is empty or has <2 elements, we must block */
|
||||
RedisModule_BlockClientOnKeys(ctx, bpop2_reply_callback, bpop2_timeout_callback,
|
||||
if (!fsl) {
|
||||
RedisModule_BlockClientOnKeys(ctx, bpop_reply_callback, bpop_timeout_callback,
|
||||
NULL, timeout, &argv[1], 1, NULL);
|
||||
} else {
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
}
|
||||
|
||||
@@ -175,10 +164,10 @@ int bpopgt_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int arg
|
||||
long long *pgt = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
|
||||
fsl_t *fsl;
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0))
|
||||
if (!get_fsl(ctx, keyname, REDISMODULE_READ, 0, &fsl, 0) || !fsl)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (!fsl || fsl->list[fsl->length-1] <= *pgt)
|
||||
if (fsl->list[fsl->length-1] <= *pgt)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[--fsl->length]);
|
||||
@@ -218,7 +207,6 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
/* We use malloc so the tests in blockedonkeys.tcl can check for memory leaks */
|
||||
long long *pgt = RedisModule_Alloc(sizeof(long long));
|
||||
*pgt = gt;
|
||||
/* Key is empty or has <2 elements, we must block */
|
||||
RedisModule_BlockClientOnKeys(ctx, bpopgt_reply_callback, bpopgt_timeout_callback,
|
||||
bpopgt_free_privdata, timeout, &argv[1], 1, pgt);
|
||||
} else {
|
||||
@@ -228,6 +216,88 @@ int fsl_bpopgt(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int bpoppush_reply_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
RedisModuleString *src_keyname = RedisModule_GetBlockedClientReadyKey(ctx);
|
||||
RedisModuleString *dst_keyname = RedisModule_GetBlockedClientPrivateData(ctx);
|
||||
|
||||
fsl_t *src;
|
||||
if (!get_fsl(ctx, src_keyname, REDISMODULE_READ, 0, &src, 0) || !src)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
fsl_t *dst;
|
||||
if (!get_fsl(ctx, dst_keyname, REDISMODULE_WRITE, 1, &dst, 0) || !dst)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
long long ele = src->list[--src->length];
|
||||
dst->list[dst->length++] = ele;
|
||||
RedisModule_SignalKeyAsReady(ctx, dst_keyname);
|
||||
return RedisModule_ReplyWithLongLong(ctx, ele);
|
||||
}
|
||||
|
||||
int bpoppush_timeout_callback(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
return RedisModule_ReplyWithSimpleString(ctx, "Request timedout");
|
||||
}
|
||||
|
||||
void bpoppush_free_privdata(RedisModuleCtx *ctx, void *privdata) {
|
||||
RedisModule_FreeString(ctx, privdata);
|
||||
}
|
||||
|
||||
/* FSL.BPOPPUSH <src> <dst> <timeout> - Block clients until <src> has an element.
|
||||
* When that happens, unblock client, pop the last element from <src> and push it to <dst>
|
||||
* (from the right). */
|
||||
int fsl_bpoppush(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 4)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
long long timeout;
|
||||
if (RedisModule_StringToLongLong(argv[3],&timeout) != REDISMODULE_OK || timeout < 0)
|
||||
return RedisModule_ReplyWithError(ctx,"ERR invalid timeout");
|
||||
|
||||
fsl_t *src;
|
||||
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &src, 1))
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!src) {
|
||||
/* Retain string for reply callback */
|
||||
RedisModule_RetainString(ctx, argv[2]);
|
||||
/* Key is empty, we must block */
|
||||
RedisModule_BlockClientOnKeys(ctx, bpoppush_reply_callback, bpoppush_timeout_callback,
|
||||
bpoppush_free_privdata, timeout, &argv[1], 1, argv[2]);
|
||||
} else {
|
||||
fsl_t *dst;
|
||||
if (!get_fsl(ctx, argv[2], REDISMODULE_WRITE, 1, &dst, 1))
|
||||
return REDISMODULE_OK;
|
||||
long long ele = src->list[--src->length];
|
||||
dst->list[dst->length++] = ele;
|
||||
RedisModule_SignalKeyAsReady(ctx, argv[2]);
|
||||
RedisModule_ReplyWithLongLong(ctx, ele);
|
||||
}
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* FSL.GETALL <key> - Reply with an array containing all elements. */
|
||||
int fsl_getall(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2)
|
||||
return RedisModule_WrongArity(ctx);
|
||||
|
||||
fsl_t *fsl;
|
||||
if (!get_fsl(ctx, argv[1], REDISMODULE_READ, 0, &fsl, 1))
|
||||
return REDISMODULE_OK;
|
||||
|
||||
if (!fsl)
|
||||
return RedisModule_ReplyWithArray(ctx, 0);
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, fsl->length);
|
||||
for (int i = 0; i < fsl->length; i++)
|
||||
RedisModule_ReplyWithLongLong(ctx, fsl->list[i]);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_NOT_USED(argv);
|
||||
REDISMODULE_NOT_USED(argc);
|
||||
@@ -252,11 +322,17 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.push",fsl_push,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpop2",fsl_bpop2,"",0,0,0) == REDISMODULE_ERR)
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpop",fsl_bpop,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpopgt",fsl_bpopgt,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.bpoppush",fsl_bpoppush,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx,"fsl.getall",fsl_getall,"",0,0,0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -159,12 +159,9 @@ proc start_server {options {code undefined}} {
|
||||
if {$::external} {
|
||||
if {[llength $::servers] == 0} {
|
||||
set srv {}
|
||||
# In test_server_main(tests/test_helper.tcl:215~218), increase the value of start_port
|
||||
# and assign it to ::port through the `--port` option, so we need to reduce it.
|
||||
set baseport [expr {$::port-100}]
|
||||
dict set srv "host" $::host
|
||||
dict set srv "port" $baseport
|
||||
set client [redis $::host $baseport 0 $::tls]
|
||||
dict set srv "port" $::port
|
||||
set client [redis $::host $::port 0 $::tls]
|
||||
dict set srv "client" $client
|
||||
$client select 9
|
||||
|
||||
|
||||
+11
-8
@@ -212,13 +212,19 @@ proc test_server_main {} {
|
||||
|
||||
# Start the client instances
|
||||
set ::clients_pids {}
|
||||
set start_port [expr {$::port+100}]
|
||||
for {set j 0} {$j < $::numclients} {incr j} {
|
||||
set start_port [find_available_port $start_port]
|
||||
if {$::external} {
|
||||
set p [exec $tclsh [info script] {*}$::argv \
|
||||
--client $port --port $start_port &]
|
||||
--client $port --port $::port &]
|
||||
lappend ::clients_pids $p
|
||||
incr start_port 10
|
||||
} else {
|
||||
set start_port [expr {$::port+100}]
|
||||
for {set j 0} {$j < $::numclients} {incr j} {
|
||||
set start_port [find_available_port $start_port]
|
||||
set p [exec $tclsh [info script] {*}$::argv \
|
||||
--client $port --port $start_port &]
|
||||
lappend ::clients_pids $p
|
||||
incr start_port 10
|
||||
}
|
||||
}
|
||||
|
||||
# Setup global state for the test server
|
||||
@@ -506,9 +512,6 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
|
||||
} elseif {$opt eq {--host}} {
|
||||
set ::external 1
|
||||
set ::host $arg
|
||||
# If we use an external server, we can only set numclients to 1,
|
||||
# otherwise the port will be miscalculated.
|
||||
set ::numclients 1
|
||||
incr j
|
||||
} elseif {$opt eq {--port}} {
|
||||
set ::port $arg
|
||||
|
||||
@@ -3,37 +3,70 @@ set testmodule [file normalize tests/modules/blockonkeys.so]
|
||||
start_server {tags {"modules"}} {
|
||||
r module load $testmodule
|
||||
|
||||
test "Module client blocked on keys: Circular BPOPPUSH" {
|
||||
set rd1 [redis_deferring_client]
|
||||
set rd2 [redis_deferring_client]
|
||||
|
||||
r del src dst
|
||||
|
||||
$rd1 fsl.bpoppush src dst 0
|
||||
$rd2 fsl.bpoppush dst src 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {2}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
|
||||
r fsl.push src 42
|
||||
|
||||
assert_equal {42} [r fsl.getall src]
|
||||
assert_equal {} [r fsl.getall dst]
|
||||
}
|
||||
|
||||
test "Module client blocked on keys: Self-referential BPOPPUSH" {
|
||||
set rd1 [redis_deferring_client]
|
||||
|
||||
r del src
|
||||
|
||||
$rd1 fsl.bpoppush src src 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push src 42
|
||||
|
||||
assert_equal {42} [r fsl.getall src]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (no metadata): No block} {
|
||||
r del k
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
r fsl.bpop2 k 0
|
||||
} {34 33}
|
||||
r fsl.bpop k 0
|
||||
} {34}
|
||||
|
||||
test {Module client blocked on keys (no metadata): Timeout} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpop2 k 1
|
||||
$rd fsl.bpop k 1
|
||||
assert_equal {Request timedout} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (no metadata): Blocked, case 1} {
|
||||
test {Module client blocked on keys (no metadata): Blocked} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpop2 k 0
|
||||
$rd fsl.bpop k 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push k 34
|
||||
assert_equal {34 33} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (no metadata): Blocked, case 2} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
$rd fsl.bpop2 k 0
|
||||
assert_equal {34 33} [$rd read]
|
||||
assert_equal {34} [$rd read]
|
||||
}
|
||||
|
||||
test {Module client blocked on keys (with metadata): No block} {
|
||||
@@ -60,6 +93,12 @@ start_server {tags {"modules"}} {
|
||||
set cid [$rd read]
|
||||
r fsl.push k 33
|
||||
$rd fsl.bpopgt k 33 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push k 34
|
||||
assert_equal {34} [$rd read]
|
||||
r client kill id $cid ;# try to smoke-out client-related memory leak
|
||||
@@ -69,6 +108,12 @@ start_server {tags {"modules"}} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
r fsl.push k 35
|
||||
@@ -82,6 +127,12 @@ start_server {tags {"modules"}} {
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r client kill id $cid ;# try to smoke-out client-related memory leak
|
||||
}
|
||||
|
||||
@@ -91,6 +142,12 @@ start_server {tags {"modules"}} {
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r client unblock $cid timeout ;# try to smoke-out client-related memory leak
|
||||
assert_equal {Request timedout} [$rd read]
|
||||
}
|
||||
@@ -101,6 +158,12 @@ start_server {tags {"modules"}} {
|
||||
$rd client id
|
||||
set cid [$rd read]
|
||||
$rd fsl.bpopgt k 35 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r client unblock $cid error ;# try to smoke-out client-related memory leak
|
||||
assert_error "*unblocked*" {$rd read}
|
||||
}
|
||||
@@ -108,13 +171,18 @@ start_server {tags {"modules"}} {
|
||||
test {Module client blocked on keys does not wake up on wrong type} {
|
||||
r del k
|
||||
set rd [redis_deferring_client]
|
||||
$rd fsl.bpop2 k 0
|
||||
$rd fsl.bpop k 0
|
||||
;# wait until clients are actually blocked
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 blocked_clients] eq {1}
|
||||
} else {
|
||||
fail "Clients are not blocked"
|
||||
}
|
||||
r lpush k 12
|
||||
r lpush k 13
|
||||
r lpush k 14
|
||||
r del k
|
||||
r fsl.push k 33
|
||||
r fsl.push k 34
|
||||
assert_equal {34 33} [$rd read]
|
||||
assert_equal {34} [$rd read]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,40 @@ tags "modules" {
|
||||
} else {
|
||||
fail "The two counters don't match the expected value."
|
||||
}
|
||||
|
||||
$master propagate-test-2
|
||||
$master propagate-test-3
|
||||
$master multi
|
||||
$master propagate-test-2
|
||||
$master propagate-test-3
|
||||
$master exec
|
||||
wait_for_ofs_sync $master $replica
|
||||
|
||||
assert_equal [s -1 unexpected_error_replies] 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags "modules aof" {
|
||||
test {Modules RM_Replicate replicates MULTI/EXEC correctly} {
|
||||
start_server [list overrides [list loadmodule "$testmodule"]] {
|
||||
# Enable the AOF
|
||||
r config set appendonly yes
|
||||
r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
|
||||
waitForBgrewriteaof r
|
||||
|
||||
r propagate-test-2
|
||||
r propagate-test-3
|
||||
r multi
|
||||
r propagate-test-2
|
||||
r propagate-test-3
|
||||
r exec
|
||||
|
||||
# Load the AOF
|
||||
r debug loadaof
|
||||
|
||||
assert_equal [s 0 unexpected_error_replies] 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,17 @@ start_server {tags {"scripting"}} {
|
||||
set e
|
||||
} {*not allowed*}
|
||||
|
||||
test {EVAL - Scripts can't run XREAD and XREADGROUP with BLOCK option} {
|
||||
r del s
|
||||
r xgroup create s g $ MKSTREAM
|
||||
set res [r eval {return redis.pcall('xread','STREAMS','s','$')} 1 s]
|
||||
assert {$res eq {}}
|
||||
assert_error "*xread command is not allowed with BLOCK option from scripts" {r eval {return redis.pcall('xread','BLOCK',0,'STREAMS','s','$')} 1 s}
|
||||
set res [r eval {return redis.pcall('xreadgroup','group','g','c','STREAMS','s','>')} 1 s]
|
||||
assert {$res eq {}}
|
||||
assert_error "*xreadgroup command is not allowed with BLOCK option from scripts" {r eval {return redis.pcall('xreadgroup','group','g','c','BLOCK',0,'STREAMS','s','>')} 1 s}
|
||||
}
|
||||
|
||||
test {EVAL - Scripts can't run certain commands} {
|
||||
set e {}
|
||||
r debug lua-always-replicate-commands 0
|
||||
|
||||
@@ -151,4 +151,11 @@ start_server {tags {"incr"}} {
|
||||
catch {r incrbyfloat foo 1} err
|
||||
format $err
|
||||
} {ERR*valid*}
|
||||
|
||||
test {No negative zero} {
|
||||
r del foo
|
||||
r incrbyfloat foo [expr double(1)/41]
|
||||
r incrbyfloat foo [expr double(-1)/41]
|
||||
r get foo
|
||||
} {0}
|
||||
}
|
||||
|
||||
@@ -93,6 +93,18 @@ start_server {
|
||||
assert {[r XACK mystream mygroup $id1 $id2] eq 1}
|
||||
}
|
||||
|
||||
test {XACK should fail if got at least one invalid ID} {
|
||||
r del mystream
|
||||
r xgroup create s g $ MKSTREAM
|
||||
r xadd s * f1 v1
|
||||
set c [llength [lindex [r xreadgroup group g c streams s >] 0 1]]
|
||||
assert {$c == 1}
|
||||
set pending [r xpending s g - + 10 c]
|
||||
set id1 [lindex $pending 0 0]
|
||||
assert_error "*Invalid stream ID specified*" {r xack s g $id1 invalid-id}
|
||||
assert {[r xack s g $id1] eq 1}
|
||||
}
|
||||
|
||||
test {PEL NACK reassignment after XGROUP SETID event} {
|
||||
r del events
|
||||
r xadd events * f1 v1
|
||||
@@ -170,6 +182,27 @@ start_server {
|
||||
assert_error "*NOGROUP*" {$rd read}
|
||||
}
|
||||
|
||||
test {RENAME can unblock XREADGROUP with data} {
|
||||
r del mystream
|
||||
r XGROUP CREATE mystream mygroup $ MKSTREAM
|
||||
set rd [redis_deferring_client]
|
||||
$rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
|
||||
r XGROUP CREATE mystream2 mygroup $ MKSTREAM
|
||||
r XADD mystream2 100 f1 v1
|
||||
r RENAME mystream2 mystream
|
||||
assert_equal "{mystream {{100-0 {f1 v1}}}}" [$rd read] ;# mystream2 had mygroup before RENAME
|
||||
}
|
||||
|
||||
test {RENAME can unblock XREADGROUP with -NOGROUP} {
|
||||
r del mystream
|
||||
r XGROUP CREATE mystream mygroup $ MKSTREAM
|
||||
set rd [redis_deferring_client]
|
||||
$rd XREADGROUP GROUP mygroup Alice BLOCK 0 STREAMS mystream ">"
|
||||
r XADD mystream2 100 f1 v1
|
||||
r RENAME mystream2 mystream
|
||||
assert_error "*NOGROUP*" {$rd read} ;# mystream2 didn't have mygroup before RENAME
|
||||
}
|
||||
|
||||
test {XCLAIM can claim PEL items from another consumer} {
|
||||
# Add 3 items into the stream, and create a consumer group
|
||||
r del mystream
|
||||
|
||||
@@ -419,4 +419,34 @@ start_server {tags {"string"}} {
|
||||
r set foo bar
|
||||
r getrange foo 0 4294967297
|
||||
} {bar}
|
||||
|
||||
set rna1 {CACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTTCGTCCGGGTGTG}
|
||||
set rna2 {ATTAAAGGTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTT}
|
||||
set rnalcs {ACCTTCCCAGGTAACAAACCAACCAACTTTCGATCTCTTGTAGATCTGTTCTCTAAACGAACTTTAAAATCTGTGTGGCTGTCACTCGGCTGCATGCTTAGTGCACTCACGCAGTATAATTAATAACTAATTACTGTCGTTGACAGGACACGAGTAACTCGTCTATCTTCTGCAGGCTGCTTACGGTTTCGTCCGTGTTGCAGCCGATCATCAGCACATCTAGGTTT}
|
||||
|
||||
test {LCS string output with STRINGS option} {
|
||||
r LCS STRINGS $rna1 $rna2
|
||||
} $rnalcs
|
||||
|
||||
test {LCS len} {
|
||||
r LCS LEN STRINGS $rna1 $rna2
|
||||
} [string length $rnalcs]
|
||||
|
||||
test {LCS with KEYS option} {
|
||||
r set virus1 $rna1
|
||||
r set virus2 $rna2
|
||||
r LCS KEYS virus1 virus2
|
||||
} $rnalcs
|
||||
|
||||
test {LCS indexes} {
|
||||
dict get [r LCS IDX KEYS virus1 virus2] matches
|
||||
} {{{238 238} {239 239}} {{236 236} {238 238}} {{229 230} {236 237}} {{224 224} {235 235}} {{1 222} {13 234}}}
|
||||
|
||||
test {LCS indexes with match len} {
|
||||
dict get [r LCS IDX KEYS virus1 virus2 WITHMATCHLEN] matches
|
||||
} {{{238 238} {239 239} 1} {{236 236} {238 238} 1} {{229 230} {236 237} 2} {{224 224} {235 235} 1} {{1 222} {13 234} 222}}
|
||||
|
||||
test {LCS indexes with match len and minimum match len} {
|
||||
dict get [r LCS IDX KEYS virus1 virus2 WITHMATCHLEN MINMATCHLEN 5] matches
|
||||
} {{{1 222} {13 234} 222}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user