mirror of
https://github.com/fluencelabs/redis
synced 2025-05-05 15:32:14 +00:00
Merge remote-tracking branch 'upstream/unstable' into tls
This commit is contained in:
commit
0db3b0a0ff
27
deps/hiredis/hiredis.c
vendored
27
deps/hiredis/hiredis.c
vendored
@ -121,21 +121,34 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
|||||||
if (r == NULL)
|
if (r == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
assert(task->type == REDIS_REPLY_ERROR ||
|
||||||
|
task->type == REDIS_REPLY_STATUS ||
|
||||||
|
task->type == REDIS_REPLY_STRING ||
|
||||||
|
task->type == REDIS_REPLY_VERB);
|
||||||
|
|
||||||
|
/* Copy string value */
|
||||||
|
if (task->type == REDIS_REPLY_VERB) {
|
||||||
|
buf = malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
|
||||||
|
if (buf == NULL) {
|
||||||
|
freeReplyObject(r);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(r->vtype,str,3);
|
||||||
|
r->vtype[3] = '\0';
|
||||||
|
memcpy(buf,str+4,len-4);
|
||||||
|
buf[len-4] = '\0';
|
||||||
|
r->len = len-4;
|
||||||
|
} else {
|
||||||
buf = malloc(len+1);
|
buf = malloc(len+1);
|
||||||
if (buf == NULL) {
|
if (buf == NULL) {
|
||||||
freeReplyObject(r);
|
freeReplyObject(r);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(task->type == REDIS_REPLY_ERROR ||
|
|
||||||
task->type == REDIS_REPLY_STATUS ||
|
|
||||||
task->type == REDIS_REPLY_STRING);
|
|
||||||
|
|
||||||
/* Copy string value */
|
|
||||||
memcpy(buf,str,len);
|
memcpy(buf,str,len);
|
||||||
buf[len] = '\0';
|
buf[len] = '\0';
|
||||||
r->str = buf;
|
|
||||||
r->len = len;
|
r->len = len;
|
||||||
|
}
|
||||||
|
r->str = buf;
|
||||||
|
|
||||||
if (task->parent) {
|
if (task->parent) {
|
||||||
parent = task->parent->obj;
|
parent = task->parent->obj;
|
||||||
|
2
deps/hiredis/hiredis.h
vendored
2
deps/hiredis/hiredis.h
vendored
@ -102,6 +102,8 @@ typedef struct redisReply {
|
|||||||
size_t len; /* Length of string */
|
size_t len; /* Length of string */
|
||||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||||
and REDIS_REPLY_DOUBLE (in additionl to dval). */
|
and REDIS_REPLY_DOUBLE (in additionl to dval). */
|
||||||
|
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
|
||||||
|
terminated 3 character content type, such as "txt". */
|
||||||
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
|
||||||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||||
} redisReply;
|
} redisReply;
|
||||||
|
14
deps/hiredis/read.c
vendored
14
deps/hiredis/read.c
vendored
@ -380,10 +380,18 @@ static int processBulkItem(redisReader *r) {
|
|||||||
/* Only continue when the buffer contains the entire bulk item. */
|
/* Only continue when the buffer contains the entire bulk item. */
|
||||||
bytelen += len+2; /* include \r\n */
|
bytelen += len+2; /* include \r\n */
|
||||||
if (r->pos+bytelen <= r->len) {
|
if (r->pos+bytelen <= r->len) {
|
||||||
|
if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
|
||||||
|
(cur->type == REDIS_REPLY_VERB && s[5] != ':'))
|
||||||
|
{
|
||||||
|
__redisReaderSetError(r,REDIS_ERR_PROTOCOL,
|
||||||
|
"Verbatim string 4 bytes of content type are "
|
||||||
|
"missing or incorrectly encoded.");
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
if (r->fn && r->fn->createString)
|
if (r->fn && r->fn->createString)
|
||||||
obj = r->fn->createString(cur,s+2,len);
|
obj = r->fn->createString(cur,s+2,len);
|
||||||
else
|
else
|
||||||
obj = (void*)REDIS_REPLY_STRING;
|
obj = (void*)(long)cur->type;
|
||||||
success = 1;
|
success = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -524,6 +532,9 @@ static int processItem(redisReader *r) {
|
|||||||
case '#':
|
case '#':
|
||||||
cur->type = REDIS_REPLY_BOOL;
|
cur->type = REDIS_REPLY_BOOL;
|
||||||
break;
|
break;
|
||||||
|
case '=':
|
||||||
|
cur->type = REDIS_REPLY_VERB;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
__redisReaderSetErrorProtocolByte(r,*p);
|
__redisReaderSetErrorProtocolByte(r,*p);
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
@ -544,6 +555,7 @@ static int processItem(redisReader *r) {
|
|||||||
case REDIS_REPLY_BOOL:
|
case REDIS_REPLY_BOOL:
|
||||||
return processLineItem(r);
|
return processLineItem(r);
|
||||||
case REDIS_REPLY_STRING:
|
case REDIS_REPLY_STRING:
|
||||||
|
case REDIS_REPLY_VERB:
|
||||||
return processBulkItem(r);
|
return processBulkItem(r);
|
||||||
case REDIS_REPLY_ARRAY:
|
case REDIS_REPLY_ARRAY:
|
||||||
case REDIS_REPLY_MAP:
|
case REDIS_REPLY_MAP:
|
||||||
|
2
deps/hiredis/read.h
vendored
2
deps/hiredis/read.h
vendored
@ -56,12 +56,12 @@
|
|||||||
#define REDIS_REPLY_ERROR 6
|
#define REDIS_REPLY_ERROR 6
|
||||||
#define REDIS_REPLY_DOUBLE 7
|
#define REDIS_REPLY_DOUBLE 7
|
||||||
#define REDIS_REPLY_BOOL 8
|
#define REDIS_REPLY_BOOL 8
|
||||||
#define REDIS_REPLY_VERB 9
|
|
||||||
#define REDIS_REPLY_MAP 9
|
#define REDIS_REPLY_MAP 9
|
||||||
#define REDIS_REPLY_SET 10
|
#define REDIS_REPLY_SET 10
|
||||||
#define REDIS_REPLY_ATTR 11
|
#define REDIS_REPLY_ATTR 11
|
||||||
#define REDIS_REPLY_PUSH 12
|
#define REDIS_REPLY_PUSH 12
|
||||||
#define REDIS_REPLY_BIGNUM 13
|
#define REDIS_REPLY_BIGNUM 13
|
||||||
|
#define REDIS_REPLY_VERB 14
|
||||||
|
|
||||||
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
|
||||||
|
|
||||||
|
8
deps/jemalloc/src/background_thread.c
vendored
8
deps/jemalloc/src/background_thread.c
vendored
@ -787,7 +787,13 @@ background_thread_stats_read(tsdn_t *tsdn, background_thread_stats_t *stats) {
|
|||||||
nstime_init(&stats->run_interval, 0);
|
nstime_init(&stats->run_interval, 0);
|
||||||
for (unsigned i = 0; i < max_background_threads; i++) {
|
for (unsigned i = 0; i < max_background_threads; i++) {
|
||||||
background_thread_info_t *info = &background_thread_info[i];
|
background_thread_info_t *info = &background_thread_info[i];
|
||||||
malloc_mutex_lock(tsdn, &info->mtx);
|
if (malloc_mutex_trylock(tsdn, &info->mtx)) {
|
||||||
|
/*
|
||||||
|
* Each background thread run may take a long time;
|
||||||
|
* avoid waiting on the stats if the thread is active.
|
||||||
|
*/
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (info->state != background_thread_stopped) {
|
if (info->state != background_thread_stopped) {
|
||||||
num_runs += info->tot_n_runs;
|
num_runs += info->tot_n_runs;
|
||||||
nstime_add(&stats->run_interval, &info->tot_sleep_time);
|
nstime_add(&stats->run_interval, &info->tot_sleep_time);
|
||||||
|
@ -13,4 +13,4 @@ then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
make -C tests/modules && \
|
make -C tests/modules && \
|
||||||
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/testrdb "${@}"
|
$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/fork --single unit/moduleapi/testrdb --single unit/moduleapi/infotest --single unit/moduleapi/propagate "${@}"
|
||||||
|
@ -172,7 +172,7 @@ endif
|
|||||||
|
|
||||||
REDIS_SERVER_NAME=redis-server
|
REDIS_SERVER_NAME=redis-server
|
||||||
REDIS_SENTINEL_NAME=redis-sentinel
|
REDIS_SENTINEL_NAME=redis-sentinel
|
||||||
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o acl.o gopher.o tracking.o connection.o tls.o
|
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o acl.o gopher.o tracking.o connection.o tls.o sha256.o
|
||||||
REDIS_CLI_NAME=redis-cli
|
REDIS_CLI_NAME=redis-cli
|
||||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
|
||||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||||
|
91
src/acl.c
91
src/acl.c
@ -28,6 +28,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "sha256.h"
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
@ -93,6 +94,9 @@ void ACLResetSubcommandsForCommand(user *u, unsigned long id);
|
|||||||
void ACLResetSubcommands(user *u);
|
void ACLResetSubcommands(user *u);
|
||||||
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
|
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
|
||||||
|
|
||||||
|
/* The length of the string representation of a hashed password. */
|
||||||
|
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
* Helper functions for the rest of the ACL implementation
|
* Helper functions for the rest of the ACL implementation
|
||||||
* ==========================================================================*/
|
* ==========================================================================*/
|
||||||
@ -139,6 +143,25 @@ int time_independent_strcmp(char *a, char *b) {
|
|||||||
return diff; /* If zero strings are the same. */
|
return diff; /* If zero strings are the same. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Given an SDS string, returns the SHA256 hex representation as a
|
||||||
|
* new SDS string. */
|
||||||
|
sds ACLHashPassword(unsigned char *cleartext, size_t len) {
|
||||||
|
SHA256_CTX ctx;
|
||||||
|
unsigned char hash[SHA256_BLOCK_SIZE];
|
||||||
|
char hex[HASH_PASSWORD_LEN];
|
||||||
|
char *cset = "0123456789abcdef";
|
||||||
|
|
||||||
|
sha256_init(&ctx);
|
||||||
|
sha256_update(&ctx,(unsigned char*)cleartext,len);
|
||||||
|
sha256_final(&ctx,hash);
|
||||||
|
|
||||||
|
for (int j = 0; j < SHA256_BLOCK_SIZE; j++) {
|
||||||
|
hex[j*2] = cset[((hash[j]&0xF0)>>4)];
|
||||||
|
hex[j*2+1] = cset[(hash[j]&0xF)];
|
||||||
|
}
|
||||||
|
return sdsnewlen(hex,HASH_PASSWORD_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
* Low level ACL API
|
* Low level ACL API
|
||||||
* ==========================================================================*/
|
* ==========================================================================*/
|
||||||
@ -502,7 +525,7 @@ sds ACLDescribeUser(user *u) {
|
|||||||
listRewind(u->passwords,&li);
|
listRewind(u->passwords,&li);
|
||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
sds thispass = listNodeValue(ln);
|
sds thispass = listNodeValue(ln);
|
||||||
res = sdscatlen(res,">",1);
|
res = sdscatlen(res,"#",1);
|
||||||
res = sdscatsds(res,thispass);
|
res = sdscatsds(res,thispass);
|
||||||
res = sdscatlen(res," ",1);
|
res = sdscatlen(res," ",1);
|
||||||
}
|
}
|
||||||
@ -629,7 +652,14 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
|||||||
* ><password> Add this password to the list of valid password for the user.
|
* ><password> Add this password to the list of valid password for the user.
|
||||||
* For example >mypass will add "mypass" to the list.
|
* For example >mypass will add "mypass" to the list.
|
||||||
* This directive clears the "nopass" flag (see later).
|
* This directive clears the "nopass" flag (see later).
|
||||||
|
* #<hash> Add this password hash to the list of valid hashes for
|
||||||
|
* the user. This is useful if you have previously computed
|
||||||
|
* the hash, and don't want to store it in plaintext.
|
||||||
|
* This directive clears the "nopass" flag (see later).
|
||||||
* <<password> Remove this password from the list of valid passwords.
|
* <<password> Remove this password from the list of valid passwords.
|
||||||
|
* !<hash> Remove this hashed password from the list of valid passwords.
|
||||||
|
* This is useful when you want to remove a password just by
|
||||||
|
* hash without knowing its plaintext version at all.
|
||||||
* nopass All the set passwords of the user are removed, and the user
|
* nopass All the set passwords of the user are removed, and the user
|
||||||
* is flagged as requiring no password: it means that every
|
* is flagged as requiring no password: it means that every
|
||||||
* password will work against this user. If this directive is
|
* password will work against this user. If this directive is
|
||||||
@ -665,6 +695,7 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
|
|||||||
* EEXIST: You are adding a key pattern after "*" was already added. This is
|
* EEXIST: You are adding a key pattern after "*" was already added. This is
|
||||||
* almost surely an error on the user side.
|
* almost surely an error on the user side.
|
||||||
* ENODEV: The password you are trying to remove from the user does not exist.
|
* ENODEV: The password you are trying to remove from the user does not exist.
|
||||||
|
* EBADMSG: The hash you are trying to add is not a valid hash.
|
||||||
*/
|
*/
|
||||||
int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||||
if (oplen == -1) oplen = strlen(op);
|
if (oplen == -1) oplen = strlen(op);
|
||||||
@ -700,14 +731,48 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||||||
} else if (!strcasecmp(op,"resetpass")) {
|
} else if (!strcasecmp(op,"resetpass")) {
|
||||||
u->flags &= ~USER_FLAG_NOPASS;
|
u->flags &= ~USER_FLAG_NOPASS;
|
||||||
listEmpty(u->passwords);
|
listEmpty(u->passwords);
|
||||||
} else if (op[0] == '>') {
|
} else if (op[0] == '>' || op[0] == '#') {
|
||||||
sds newpass = sdsnewlen(op+1,oplen-1);
|
sds newpass;
|
||||||
|
if (op[0] == '>') {
|
||||||
|
newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
||||||
|
} else {
|
||||||
|
if (oplen != HASH_PASSWORD_LEN + 1) {
|
||||||
|
errno = EBADMSG;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Password hashes can only be characters that represent
|
||||||
|
* hexadecimal values, which are numbers and lowercase
|
||||||
|
* characters 'a' through 'f'.
|
||||||
|
*/
|
||||||
|
for(int i = 1; i < HASH_PASSWORD_LEN + 1; i++) {
|
||||||
|
char c = op[i];
|
||||||
|
if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
|
||||||
|
errno = EBADMSG;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newpass = sdsnewlen(op+1,oplen-1);
|
||||||
|
}
|
||||||
|
|
||||||
listNode *ln = listSearchKey(u->passwords,newpass);
|
listNode *ln = listSearchKey(u->passwords,newpass);
|
||||||
/* Avoid re-adding the same password multiple times. */
|
/* Avoid re-adding the same password multiple times. */
|
||||||
if (ln == NULL) listAddNodeTail(u->passwords,newpass);
|
if (ln == NULL)
|
||||||
|
listAddNodeTail(u->passwords,newpass);
|
||||||
|
else
|
||||||
|
sdsfree(newpass);
|
||||||
u->flags &= ~USER_FLAG_NOPASS;
|
u->flags &= ~USER_FLAG_NOPASS;
|
||||||
} else if (op[0] == '<') {
|
} else if (op[0] == '<' || op[0] == '!') {
|
||||||
sds delpass = sdsnewlen(op+1,oplen-1);
|
sds delpass;
|
||||||
|
if (op[0] == '<') {
|
||||||
|
delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
|
||||||
|
} else {
|
||||||
|
if (oplen != HASH_PASSWORD_LEN + 1) {
|
||||||
|
errno = EBADMSG;
|
||||||
|
return C_ERR;
|
||||||
|
}
|
||||||
|
delpass = sdsnewlen(op+1,oplen-1);
|
||||||
|
}
|
||||||
listNode *ln = listSearchKey(u->passwords,delpass);
|
listNode *ln = listSearchKey(u->passwords,delpass);
|
||||||
sdsfree(delpass);
|
sdsfree(delpass);
|
||||||
if (ln) {
|
if (ln) {
|
||||||
@ -724,7 +789,10 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||||||
sds newpat = sdsnewlen(op+1,oplen-1);
|
sds newpat = sdsnewlen(op+1,oplen-1);
|
||||||
listNode *ln = listSearchKey(u->patterns,newpat);
|
listNode *ln = listSearchKey(u->patterns,newpat);
|
||||||
/* Avoid re-adding the same pattern multiple times. */
|
/* Avoid re-adding the same pattern multiple times. */
|
||||||
if (ln == NULL) listAddNodeTail(u->patterns,newpat);
|
if (ln == NULL)
|
||||||
|
listAddNodeTail(u->patterns,newpat);
|
||||||
|
else
|
||||||
|
sdsfree(newpat);
|
||||||
u->flags &= ~USER_FLAG_ALLKEYS;
|
u->flags &= ~USER_FLAG_ALLKEYS;
|
||||||
} else if (op[0] == '+' && op[1] != '@') {
|
} else if (op[0] == '+' && op[1] != '@') {
|
||||||
if (strchr(op,'|') == NULL) {
|
if (strchr(op,'|') == NULL) {
|
||||||
@ -822,6 +890,9 @@ char *ACLSetUserStringError(void) {
|
|||||||
else if (errno == ENODEV)
|
else if (errno == ENODEV)
|
||||||
errmsg = "The password you are trying to remove from the user does "
|
errmsg = "The password you are trying to remove from the user does "
|
||||||
"not exist";
|
"not exist";
|
||||||
|
else if (errno == EBADMSG)
|
||||||
|
errmsg = "The password hash must be exactly 64 characters and contain "
|
||||||
|
"only lowercase hexadecimal characters";
|
||||||
return errmsg;
|
return errmsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -879,11 +950,15 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
|
|||||||
listIter li;
|
listIter li;
|
||||||
listNode *ln;
|
listNode *ln;
|
||||||
listRewind(u->passwords,&li);
|
listRewind(u->passwords,&li);
|
||||||
|
sds hashed = ACLHashPassword(password->ptr,sdslen(password->ptr));
|
||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
sds thispass = listNodeValue(ln);
|
sds thispass = listNodeValue(ln);
|
||||||
if (!time_independent_strcmp(password->ptr, thispass))
|
if (!time_independent_strcmp(hashed, thispass)) {
|
||||||
|
sdsfree(hashed);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
sdsfree(hashed);
|
||||||
|
|
||||||
/* If we reached this point, no password matched. */
|
/* If we reached this point, no password matched. */
|
||||||
errno = EINVAL;
|
errno = EINVAL;
|
||||||
|
36
src/aof.c
36
src/aof.c
@ -264,9 +264,9 @@ int startAppendOnly(void) {
|
|||||||
strerror(errno));
|
strerror(errno));
|
||||||
return C_ERR;
|
return C_ERR;
|
||||||
}
|
}
|
||||||
if (server.rdb_child_pid != -1) {
|
if (hasActiveChildProcess() && server.aof_child_pid == -1) {
|
||||||
server.aof_rewrite_scheduled = 1;
|
server.aof_rewrite_scheduled = 1;
|
||||||
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
|
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
|
||||||
} else {
|
} else {
|
||||||
/* If there is a pending AOF rewrite, we need to switch it off and
|
/* If there is a pending AOF rewrite, we need to switch it off and
|
||||||
* start a new one: the old one cannot be reused because it is not
|
* start a new one: the old one cannot be reused because it is not
|
||||||
@ -399,7 +399,7 @@ void flushAppendOnlyFile(int force) {
|
|||||||
* useful for graphing / monitoring purposes. */
|
* useful for graphing / monitoring purposes. */
|
||||||
if (sync_in_progress) {
|
if (sync_in_progress) {
|
||||||
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
||||||
} else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
|
} else if (hasActiveChildProcess()) {
|
||||||
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
||||||
} else {
|
} else {
|
||||||
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
||||||
@ -495,8 +495,7 @@ void flushAppendOnlyFile(int force) {
|
|||||||
try_fsync:
|
try_fsync:
|
||||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||||
* children doing I/O in the background. */
|
* children doing I/O in the background. */
|
||||||
if (server.aof_no_fsync_on_rewrite &&
|
if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess())
|
||||||
(server.aof_child_pid != -1 || server.rdb_child_pid != -1))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Perform the fsync if needed. */
|
/* Perform the fsync if needed. */
|
||||||
@ -1569,39 +1568,24 @@ void aofClosePipes(void) {
|
|||||||
*/
|
*/
|
||||||
int rewriteAppendOnlyFileBackground(void) {
|
int rewriteAppendOnlyFileBackground(void) {
|
||||||
pid_t childpid;
|
pid_t childpid;
|
||||||
long long start;
|
|
||||||
|
|
||||||
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
|
if (hasActiveChildProcess()) return C_ERR;
|
||||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||||
openChildInfoPipe();
|
openChildInfoPipe();
|
||||||
start = ustime();
|
if ((childpid = redisFork()) == 0) {
|
||||||
if ((childpid = fork()) == 0) {
|
|
||||||
char tmpfile[256];
|
char tmpfile[256];
|
||||||
|
|
||||||
/* Child */
|
/* Child */
|
||||||
closeListeningSockets(0);
|
|
||||||
redisSetProcTitle("redis-aof-rewrite");
|
redisSetProcTitle("redis-aof-rewrite");
|
||||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
|
||||||
|
|
||||||
if (private_dirty) {
|
|
||||||
serverLog(LL_NOTICE,
|
|
||||||
"AOF rewrite: %zu MB of memory used by copy-on-write",
|
|
||||||
private_dirty/(1024*1024));
|
|
||||||
}
|
|
||||||
|
|
||||||
server.child_info_data.cow_size = private_dirty;
|
|
||||||
sendChildInfo(CHILD_INFO_TYPE_AOF);
|
|
||||||
exitFromChild(0);
|
exitFromChild(0);
|
||||||
} else {
|
} else {
|
||||||
exitFromChild(1);
|
exitFromChild(1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
server.stat_fork_time = ustime()-start;
|
|
||||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
|
||||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
|
||||||
if (childpid == -1) {
|
if (childpid == -1) {
|
||||||
closeChildInfoPipe();
|
closeChildInfoPipe();
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
@ -1615,7 +1599,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
server.aof_rewrite_scheduled = 0;
|
server.aof_rewrite_scheduled = 0;
|
||||||
server.aof_rewrite_time_start = time(NULL);
|
server.aof_rewrite_time_start = time(NULL);
|
||||||
server.aof_child_pid = childpid;
|
server.aof_child_pid = childpid;
|
||||||
updateDictResizePolicy();
|
|
||||||
/* We set appendseldb to -1 in order to force the next call to the
|
/* We set appendseldb to -1 in order to force the next call to the
|
||||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||||
* accumulated by the parent into server.aof_rewrite_buf will start
|
* accumulated by the parent into server.aof_rewrite_buf will start
|
||||||
@ -1630,13 +1613,14 @@ int rewriteAppendOnlyFileBackground(void) {
|
|||||||
void bgrewriteaofCommand(client *c) {
|
void bgrewriteaofCommand(client *c) {
|
||||||
if (server.aof_child_pid != -1) {
|
if (server.aof_child_pid != -1) {
|
||||||
addReplyError(c,"Background append only file rewriting already in progress");
|
addReplyError(c,"Background append only file rewriting already in progress");
|
||||||
} else if (server.rdb_child_pid != -1) {
|
} else if (hasActiveChildProcess()) {
|
||||||
server.aof_rewrite_scheduled = 1;
|
server.aof_rewrite_scheduled = 1;
|
||||||
addReplyStatus(c,"Background append only file rewriting scheduled");
|
addReplyStatus(c,"Background append only file rewriting scheduled");
|
||||||
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
||||||
addReplyStatus(c,"Background append only file rewriting started");
|
addReplyStatus(c,"Background append only file rewriting started");
|
||||||
} else {
|
} else {
|
||||||
addReply(c,shared.err);
|
addReplyError(c,"Can't execute an AOF background rewriting. "
|
||||||
|
"Please check the server logs for more information.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +80,8 @@ void receiveChildInfo(void) {
|
|||||||
server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
|
server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
|
||||||
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
||||||
server.stat_aof_cow_bytes = server.child_info_data.cow_size;
|
server.stat_aof_cow_bytes = server.child_info_data.cow_size;
|
||||||
|
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_MODULE) {
|
||||||
|
server.stat_module_cow_bytes = server.child_info_data.cow_size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2210,7 +2210,7 @@ void clusterLinkConnectHandler(connection *conn) {
|
|||||||
* full length of the packet. When a whole packet is in memory this function
|
* full length of the packet. When a whole packet is in memory this function
|
||||||
* will call the function to process the packet. And so forth. */
|
* will call the function to process the packet. And so forth. */
|
||||||
void clusterReadHandler(connection *conn) {
|
void clusterReadHandler(connection *conn) {
|
||||||
char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
ssize_t nread;
|
ssize_t nread;
|
||||||
clusterMsg *hdr;
|
clusterMsg *hdr;
|
||||||
clusterLink *link = connGetPrivateData(conn);
|
clusterLink *link = connGetPrivateData(conn);
|
||||||
@ -2585,7 +2585,8 @@ void clusterBroadcastPong(int target) {
|
|||||||
*
|
*
|
||||||
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
||||||
void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
||||||
unsigned char buf[sizeof(clusterMsg)], *payload;
|
unsigned char *payload;
|
||||||
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
uint32_t channel_len, message_len;
|
uint32_t channel_len, message_len;
|
||||||
@ -2605,7 +2606,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
|||||||
|
|
||||||
/* Try to use the local buffer if possible */
|
/* Try to use the local buffer if possible */
|
||||||
if (totlen < sizeof(buf)) {
|
if (totlen < sizeof(buf)) {
|
||||||
payload = buf;
|
payload = (unsigned char*)buf;
|
||||||
} else {
|
} else {
|
||||||
payload = zmalloc(totlen);
|
payload = zmalloc(totlen);
|
||||||
memcpy(payload,hdr,sizeof(*hdr));
|
memcpy(payload,hdr,sizeof(*hdr));
|
||||||
@ -2622,7 +2623,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
|||||||
|
|
||||||
decrRefCount(channel);
|
decrRefCount(channel);
|
||||||
decrRefCount(message);
|
decrRefCount(message);
|
||||||
if (payload != buf) zfree(payload);
|
if (payload != (unsigned char*)buf) zfree(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send a FAIL message to all the nodes we are able to contact.
|
/* Send a FAIL message to all the nodes we are able to contact.
|
||||||
@ -2631,7 +2632,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
|||||||
* we switch the node state to CLUSTER_NODE_FAIL and ask all the other
|
* we switch the node state to CLUSTER_NODE_FAIL and ask all the other
|
||||||
* nodes to do the same ASAP. */
|
* nodes to do the same ASAP. */
|
||||||
void clusterSendFail(char *nodename) {
|
void clusterSendFail(char *nodename) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
|
|
||||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL);
|
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL);
|
||||||
@ -2643,7 +2644,7 @@ void clusterSendFail(char *nodename) {
|
|||||||
* slots configuration. The node name, slots bitmap, and configEpoch info
|
* slots configuration. The node name, slots bitmap, and configEpoch info
|
||||||
* are included. */
|
* are included. */
|
||||||
void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
|
|
||||||
if (link == NULL) return;
|
if (link == NULL) return;
|
||||||
@ -2651,7 +2652,7 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
|||||||
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
|
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
|
||||||
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
|
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
|
||||||
memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots));
|
memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots));
|
||||||
clusterSendMessage(link,buf,ntohl(hdr->totlen));
|
clusterSendMessage(link,(unsigned char*)buf,ntohl(hdr->totlen));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send a MODULE message.
|
/* Send a MODULE message.
|
||||||
@ -2659,7 +2660,8 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
|||||||
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
||||||
void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
||||||
unsigned char *payload, uint32_t len) {
|
unsigned char *payload, uint32_t len) {
|
||||||
unsigned char buf[sizeof(clusterMsg)], *heapbuf;
|
unsigned char *heapbuf;
|
||||||
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
|
|
||||||
@ -2674,7 +2676,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
|||||||
|
|
||||||
/* Try to use the local buffer if possible */
|
/* Try to use the local buffer if possible */
|
||||||
if (totlen < sizeof(buf)) {
|
if (totlen < sizeof(buf)) {
|
||||||
heapbuf = buf;
|
heapbuf = (unsigned char*)buf;
|
||||||
} else {
|
} else {
|
||||||
heapbuf = zmalloc(totlen);
|
heapbuf = zmalloc(totlen);
|
||||||
memcpy(heapbuf,hdr,sizeof(*hdr));
|
memcpy(heapbuf,hdr,sizeof(*hdr));
|
||||||
@ -2687,7 +2689,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
|||||||
else
|
else
|
||||||
clusterBroadcastMessage(heapbuf,totlen);
|
clusterBroadcastMessage(heapbuf,totlen);
|
||||||
|
|
||||||
if (heapbuf != buf) zfree(heapbuf);
|
if (heapbuf != (unsigned char*)buf) zfree(heapbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function gets a cluster node ID string as target, the same way the nodes
|
/* This function gets a cluster node ID string as target, the same way the nodes
|
||||||
@ -2731,7 +2733,7 @@ void clusterPropagatePublish(robj *channel, robj *message) {
|
|||||||
* Note that we send the failover request to everybody, master and slave nodes,
|
* Note that we send the failover request to everybody, master and slave nodes,
|
||||||
* but only the masters are supposed to reply to our query. */
|
* but only the masters are supposed to reply to our query. */
|
||||||
void clusterRequestFailoverAuth(void) {
|
void clusterRequestFailoverAuth(void) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
|
|
||||||
@ -2747,7 +2749,7 @@ void clusterRequestFailoverAuth(void) {
|
|||||||
|
|
||||||
/* Send a FAILOVER_AUTH_ACK message to the specified node. */
|
/* Send a FAILOVER_AUTH_ACK message to the specified node. */
|
||||||
void clusterSendFailoverAuth(clusterNode *node) {
|
void clusterSendFailoverAuth(clusterNode *node) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
|
|
||||||
@ -2755,12 +2757,12 @@ void clusterSendFailoverAuth(clusterNode *node) {
|
|||||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK);
|
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK);
|
||||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||||
hdr->totlen = htonl(totlen);
|
hdr->totlen = htonl(totlen);
|
||||||
clusterSendMessage(node->link,buf,totlen);
|
clusterSendMessage(node->link,(unsigned char*)buf,totlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send a MFSTART message to the specified node. */
|
/* Send a MFSTART message to the specified node. */
|
||||||
void clusterSendMFStart(clusterNode *node) {
|
void clusterSendMFStart(clusterNode *node) {
|
||||||
unsigned char buf[sizeof(clusterMsg)];
|
clusterMsg buf[1];
|
||||||
clusterMsg *hdr = (clusterMsg*) buf;
|
clusterMsg *hdr = (clusterMsg*) buf;
|
||||||
uint32_t totlen;
|
uint32_t totlen;
|
||||||
|
|
||||||
@ -2768,7 +2770,7 @@ void clusterSendMFStart(clusterNode *node) {
|
|||||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MFSTART);
|
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MFSTART);
|
||||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||||
hdr->totlen = htonl(totlen);
|
hdr->totlen = htonl(totlen);
|
||||||
clusterSendMessage(node->link,buf,totlen);
|
clusterSendMessage(node->link,(unsigned char*)buf,totlen);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Vote for the node asking for our vote if there are the conditions. */
|
/* Vote for the node asking for our vote if there are the conditions. */
|
||||||
@ -4292,7 +4294,9 @@ NULL
|
|||||||
}
|
}
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
|
||||||
/* CLUSTER NODES */
|
/* CLUSTER NODES */
|
||||||
addReplyBulkSds(c,clusterGenNodesDescription(0));
|
sds nodes = clusterGenNodesDescription(0);
|
||||||
|
addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
|
||||||
|
sdsfree(nodes);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
|
||||||
/* CLUSTER MYID */
|
/* CLUSTER MYID */
|
||||||
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
|
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
|
||||||
@ -4534,10 +4538,8 @@ NULL
|
|||||||
"cluster_stats_messages_received:%lld\r\n", tot_msg_received);
|
"cluster_stats_messages_received:%lld\r\n", tot_msg_received);
|
||||||
|
|
||||||
/* Produce the reply protocol. */
|
/* Produce the reply protocol. */
|
||||||
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",
|
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||||
(unsigned long)sdslen(info)));
|
sdsfree(info);
|
||||||
addReplySds(c,info);
|
|
||||||
addReply(c,shared.crlf);
|
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
|
||||||
int retval = clusterSaveConfig(1);
|
int retval = clusterSaveConfig(1);
|
||||||
|
|
||||||
|
@ -144,6 +144,7 @@ configYesNo configs_yesno[] = {
|
|||||||
{"replica-serve-stale-data","slave-serve-stale-data",&server.repl_serve_stale_data,1,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA},
|
{"replica-serve-stale-data","slave-serve-stale-data",&server.repl_serve_stale_data,1,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA},
|
||||||
{"replica-read-only","slave-read-only",&server.repl_slave_ro,1,CONFIG_DEFAULT_SLAVE_READ_ONLY},
|
{"replica-read-only","slave-read-only",&server.repl_slave_ro,1,CONFIG_DEFAULT_SLAVE_READ_ONLY},
|
||||||
{"replica-ignore-maxmemory","slave-ignore-maxmemory",&server.repl_slave_ignore_maxmemory,1,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY},
|
{"replica-ignore-maxmemory","slave-ignore-maxmemory",&server.repl_slave_ignore_maxmemory,1,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY},
|
||||||
|
{"jemalloc-bg-thread",NULL,&server.jemalloc_bg_thread,1,1},
|
||||||
{NULL, NULL, 0, 0}
|
{NULL, NULL, 0, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -438,6 +439,7 @@ void loadServerConfigFromString(char *config) {
|
|||||||
server.repl_diskless_load = configEnumGetValue(repl_diskless_load_enum,argv[1]);
|
server.repl_diskless_load = configEnumGetValue(repl_diskless_load_enum,argv[1]);
|
||||||
if (server.repl_diskless_load == INT_MIN) {
|
if (server.repl_diskless_load == INT_MIN) {
|
||||||
err = "argument must be 'disabled', 'on-empty-db', 'swapdb' or 'flushdb'";
|
err = "argument must be 'disabled', 'on-empty-db', 'swapdb' or 'flushdb'";
|
||||||
|
goto loaderr;
|
||||||
}
|
}
|
||||||
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
|
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
|
||||||
server.repl_diskless_sync_delay = atoi(argv[1]);
|
server.repl_diskless_sync_delay = atoi(argv[1]);
|
||||||
@ -678,6 +680,10 @@ void loadServerConfigFromString(char *config) {
|
|||||||
server.lua_time_limit = strtoll(argv[1],NULL,10);
|
server.lua_time_limit = strtoll(argv[1],NULL,10);
|
||||||
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
|
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
|
||||||
server.lua_always_replicate_commands = yesnotoi(argv[1]);
|
server.lua_always_replicate_commands = yesnotoi(argv[1]);
|
||||||
|
if (server.lua_always_replicate_commands == -1) {
|
||||||
|
err = "argument must be 'yes' or 'no'";
|
||||||
|
goto loaderr;
|
||||||
|
}
|
||||||
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
|
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
|
||||||
argc == 2)
|
argc == 2)
|
||||||
{
|
{
|
||||||
|
19
src/db.c
19
src/db.c
@ -60,10 +60,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
|
|||||||
/* Update the access time for the ageing algorithm.
|
/* Update the access time for the ageing algorithm.
|
||||||
* Don't do it if we have a saving child, as this will trigger
|
* Don't do it if we have a saving child, as this will trigger
|
||||||
* a copy on write madness. */
|
* a copy on write madness. */
|
||||||
if (server.rdb_child_pid == -1 &&
|
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
|
||||||
server.aof_child_pid == -1 &&
|
|
||||||
!(flags & LOOKUP_NOTOUCH))
|
|
||||||
{
|
|
||||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||||
updateLFU(val);
|
updateLFU(val);
|
||||||
} else {
|
} else {
|
||||||
@ -460,6 +457,13 @@ void flushdbCommand(client *c) {
|
|||||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||||
server.dirty += emptyDb(c->db->id,flags,NULL);
|
server.dirty += emptyDb(c->db->id,flags,NULL);
|
||||||
addReply(c,shared.ok);
|
addReply(c,shared.ok);
|
||||||
|
#if defined(USE_JEMALLOC)
|
||||||
|
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||||
|
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||||
|
* harm and this way the flush and purge will be synchroneus. */
|
||||||
|
if (!(flags & EMPTYDB_ASYNC))
|
||||||
|
jemalloc_purge();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FLUSHALL [ASYNC]
|
/* FLUSHALL [ASYNC]
|
||||||
@ -482,6 +486,13 @@ void flushallCommand(client *c) {
|
|||||||
server.dirty = saved_dirty;
|
server.dirty = saved_dirty;
|
||||||
}
|
}
|
||||||
server.dirty++;
|
server.dirty++;
|
||||||
|
#if defined(USE_JEMALLOC)
|
||||||
|
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
|
||||||
|
* for large databases, flushdb blocks for long anyway, so a bit more won't
|
||||||
|
* harm and this way the flush and purge will be synchroneus. */
|
||||||
|
if (!(flags & EMPTYDB_ASYNC))
|
||||||
|
jemalloc_purge();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This command implements DEL and LAZYDEL. */
|
/* This command implements DEL and LAZYDEL. */
|
||||||
|
100
src/debug.c
100
src/debug.c
@ -297,6 +297,56 @@ void computeDatasetDigest(unsigned char *final) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_JEMALLOC
|
||||||
|
void mallctl_int(client *c, robj **argv, int argc) {
|
||||||
|
int ret;
|
||||||
|
/* start with the biggest size (int64), and if that fails, try smaller sizes (int32, bool) */
|
||||||
|
int64_t old = 0, val;
|
||||||
|
if (argc > 1) {
|
||||||
|
long long ll;
|
||||||
|
if (getLongLongFromObjectOrReply(c, argv[1], &ll, NULL) != C_OK)
|
||||||
|
return;
|
||||||
|
val = ll;
|
||||||
|
}
|
||||||
|
size_t sz = sizeof(old);
|
||||||
|
while (sz > 0) {
|
||||||
|
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, argc > 1? &val: NULL, argc > 1?sz: 0))) {
|
||||||
|
if (ret==EINVAL) {
|
||||||
|
/* size might be wrong, try a smaller one */
|
||||||
|
sz /= 2;
|
||||||
|
#if BYTE_ORDER == BIG_ENDIAN
|
||||||
|
val <<= 8*sz;
|
||||||
|
#endif
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addReplyErrorFormat(c,"%s", strerror(ret));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
#if BYTE_ORDER == BIG_ENDIAN
|
||||||
|
old >>= 64 - 8*sz;
|
||||||
|
#endif
|
||||||
|
addReplyLongLong(c, old);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addReplyErrorFormat(c,"%s", strerror(EINVAL));
|
||||||
|
}
|
||||||
|
|
||||||
|
void mallctl_string(client *c, robj **argv, int argc) {
|
||||||
|
int ret;
|
||||||
|
char *old;
|
||||||
|
size_t sz = sizeof(old);
|
||||||
|
/* for strings, it seems we need to first get the old value, before overriding it. */
|
||||||
|
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) {
|
||||||
|
addReplyErrorFormat(c,"%s", strerror(ret));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
addReplyBulkCString(c, old);
|
||||||
|
if(argc > 1)
|
||||||
|
je_mallctl(argv[0]->ptr, NULL, 0, &argv[1]->ptr, sizeof(char*));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void debugCommand(client *c) {
|
void debugCommand(client *c) {
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||||
const char *help[] = {
|
const char *help[] = {
|
||||||
@ -324,6 +374,10 @@ void debugCommand(client *c) {
|
|||||||
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
||||||
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
||||||
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
|
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
|
||||||
|
#ifdef USE_JEMALLOC
|
||||||
|
"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.",
|
||||||
|
"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.",
|
||||||
|
#endif
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
addReplyHelp(c, help);
|
addReplyHelp(c, help);
|
||||||
@ -644,7 +698,8 @@ NULL
|
|||||||
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
|
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
|
||||||
stats = sdscat(stats,buf);
|
stats = sdscat(stats,buf);
|
||||||
|
|
||||||
addReplyBulkSds(c,stats);
|
addReplyVerbatim(c,stats,sdslen(stats),"txt");
|
||||||
|
sdsfree(stats);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
|
||||||
robj *o;
|
robj *o;
|
||||||
dict *ht = NULL;
|
dict *ht = NULL;
|
||||||
@ -671,7 +726,7 @@ NULL
|
|||||||
} else {
|
} else {
|
||||||
char buf[4096];
|
char buf[4096];
|
||||||
dictGetStats(buf,sizeof(buf),ht);
|
dictGetStats(buf,sizeof(buf),ht);
|
||||||
addReplyBulkCString(c,buf);
|
addReplyVerbatim(c,buf,strlen(buf),"txt");
|
||||||
}
|
}
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
|
||||||
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
|
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
|
||||||
@ -682,6 +737,14 @@ NULL
|
|||||||
{
|
{
|
||||||
stringmatchlen_fuzz_test();
|
stringmatchlen_fuzz_test();
|
||||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||||
|
#ifdef USE_JEMALLOC
|
||||||
|
} else if(!strcasecmp(c->argv[1]->ptr,"mallctl") && c->argc >= 3) {
|
||||||
|
mallctl_int(c, c->argv+2, c->argc-2);
|
||||||
|
return;
|
||||||
|
} else if(!strcasecmp(c->argv[1]->ptr,"mallctl-str") && c->argc >= 3) {
|
||||||
|
mallctl_string(c, c->argv+2, c->argc-2);
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
addReplySubcommandSyntaxError(c);
|
addReplySubcommandSyntaxError(c);
|
||||||
return;
|
return;
|
||||||
@ -1117,6 +1180,33 @@ void logRegisters(ucontext_t *uc) {
|
|||||||
(unsigned long) uc->uc_mcontext.mc_cs
|
(unsigned long) uc->uc_mcontext.mc_cs
|
||||||
);
|
);
|
||||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
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
|
#else
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
" Dumping of registers not supported for this OS/arch");
|
" Dumping of registers not supported for this OS/arch");
|
||||||
@ -1344,6 +1434,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
|||||||
/* Log dump of processor registers */
|
/* Log dump of processor registers */
|
||||||
logRegisters(uc);
|
logRegisters(uc);
|
||||||
|
|
||||||
|
/* Log Modules INFO */
|
||||||
|
serverLogRaw(LL_WARNING|LL_RAW, "\n------ MODULES INFO OUTPUT ------\n");
|
||||||
|
infostring = modulesCollectInfo(sdsempty(), NULL, 1, 0);
|
||||||
|
serverLogRaw(LL_WARNING|LL_RAW, infostring);
|
||||||
|
sdsfree(infostring);
|
||||||
|
|
||||||
#if defined(HAVE_PROC_MAPS)
|
#if defined(HAVE_PROC_MAPS)
|
||||||
/* Test memory */
|
/* Test memory */
|
||||||
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
|
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
|
||||||
|
@ -374,7 +374,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
|
|||||||
if ((newele = activeDefragStringOb(ele, &defragged)))
|
if ((newele = activeDefragStringOb(ele, &defragged)))
|
||||||
de->v.val = newele, defragged++;
|
de->v.val = newele, defragged++;
|
||||||
} else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
|
} else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
|
||||||
void *newptr, *ptr = ln->value;
|
void *newptr, *ptr = dictGetVal(de);
|
||||||
if ((newptr = activeDefragAlloc(ptr)))
|
if ((newptr = activeDefragAlloc(ptr)))
|
||||||
ln->value = newptr, defragged++;
|
ln->value = newptr, defragged++;
|
||||||
}
|
}
|
||||||
@ -1039,7 +1039,7 @@ void activeDefragCycle(void) {
|
|||||||
mstime_t latency;
|
mstime_t latency;
|
||||||
int quit = 0;
|
int quit = 0;
|
||||||
|
|
||||||
if (server.aof_child_pid!=-1 || server.rdb_child_pid!=-1)
|
if (hasActiveChildProcess())
|
||||||
return; /* Defragging memory while there's a fork will just do damage. */
|
return; /* Defragging memory while there's a fork will just do damage. */
|
||||||
|
|
||||||
/* Once a second, check if we the fragmentation justfies starting a scan
|
/* Once a second, check if we the fragmentation justfies starting a scan
|
||||||
|
@ -444,6 +444,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
|
|||||||
* Otehrwise if we are over the memory limit, but not enough memory
|
* Otehrwise if we are over the memory limit, but not enough memory
|
||||||
* was freed to return back under the limit, the function returns C_ERR. */
|
* was freed to return back under the limit, the function returns C_ERR. */
|
||||||
int freeMemoryIfNeeded(void) {
|
int freeMemoryIfNeeded(void) {
|
||||||
|
int keys_freed = 0;
|
||||||
/* By default replicas should ignore maxmemory
|
/* By default replicas should ignore maxmemory
|
||||||
* and just be masters exact copies. */
|
* and just be masters exact copies. */
|
||||||
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||||
@ -467,7 +468,7 @@ int freeMemoryIfNeeded(void) {
|
|||||||
|
|
||||||
latencyStartMonitor(latency);
|
latencyStartMonitor(latency);
|
||||||
while (mem_freed < mem_tofree) {
|
while (mem_freed < mem_tofree) {
|
||||||
int j, k, i, keys_freed = 0;
|
int j, k, i;
|
||||||
static unsigned int next_db = 0;
|
static unsigned int next_db = 0;
|
||||||
sds bestkey = NULL;
|
sds bestkey = NULL;
|
||||||
int bestdbid;
|
int bestdbid;
|
||||||
@ -598,9 +599,7 @@ int freeMemoryIfNeeded(void) {
|
|||||||
mem_freed = mem_tofree;
|
mem_freed = mem_tofree;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if (!keys_freed) {
|
|
||||||
latencyEndMonitor(latency);
|
latencyEndMonitor(latency);
|
||||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||||
goto cant_free; /* nothing to free... */
|
goto cant_free; /* nothing to free... */
|
||||||
|
12
src/geo.c
12
src/geo.c
@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) {
|
|||||||
|
|
||||||
/* Look up the requested zset */
|
/* Look up the requested zset */
|
||||||
robj *zobj = NULL;
|
robj *zobj = NULL;
|
||||||
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == NULL ||
|
if ((zobj = lookupKeyReadOrReply(c, key, shared.emptyarray)) == NULL ||
|
||||||
checkType(c, zobj, OBJ_ZSET)) {
|
checkType(c, zobj, OBJ_ZSET)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
|
|||||||
|
|
||||||
/* If no matching results, the user gets an empty reply. */
|
/* If no matching results, the user gets an empty reply. */
|
||||||
if (ga->used == 0 && storekey == NULL) {
|
if (ga->used == 0 && storekey == NULL) {
|
||||||
addReplyNull(c);
|
addReply(c,shared.emptyarray);
|
||||||
geoArrayFree(ga);
|
geoArrayFree(ga);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -734,14 +734,14 @@ void geohashCommand(client *c) {
|
|||||||
r[1].max = 90;
|
r[1].max = 90;
|
||||||
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
|
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
|
||||||
|
|
||||||
char buf[12];
|
char buf[11];
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < 11; i++) {
|
for (i = 0; i < 10; i++) {
|
||||||
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
||||||
buf[i] = geoalphabet[idx];
|
buf[i] = geoalphabet[idx];
|
||||||
}
|
}
|
||||||
buf[11] = '\0';
|
buf[10] = '\0';
|
||||||
addReplyBulkCBuffer(c,buf,11);
|
addReplyBulkCBuffer(c,buf,10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1242,7 +1242,7 @@ void pfcountCommand(client *c) {
|
|||||||
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
|
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
|
||||||
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
if (isHLLObjectOrReply(c,o) != C_OK) return;
|
||||||
|
|
||||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||||
* to MAX(max[i],hll[i]). */
|
* to MAX(max[i],hll[i]). */
|
||||||
if (hllMerge(registers,o) == C_ERR) {
|
if (hllMerge(registers,o) == C_ERR) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplySds(c,sdsnew(invalid_hll_err));
|
||||||
@ -1329,7 +1329,7 @@ void pfmergeCommand(client *c) {
|
|||||||
hdr = o->ptr;
|
hdr = o->ptr;
|
||||||
if (hdr->encoding == HLL_DENSE) use_dense = 1;
|
if (hdr->encoding == HLL_DENSE) use_dense = 1;
|
||||||
|
|
||||||
/* Merge with this HLL with our 'max' HHL by setting max[i]
|
/* Merge with this HLL with our 'max' HLL by setting max[i]
|
||||||
* to MAX(max[i],hll[i]). */
|
* to MAX(max[i],hll[i]). */
|
||||||
if (hllMerge(max,o) == C_ERR) {
|
if (hllMerge(max,o) == C_ERR) {
|
||||||
addReplySds(c,sdsnew(invalid_hll_err));
|
addReplySds(c,sdsnew(invalid_hll_err));
|
||||||
|
@ -599,7 +599,7 @@ NULL
|
|||||||
event = dictGetKey(de);
|
event = dictGetKey(de);
|
||||||
|
|
||||||
graph = latencyCommandGenSparkeline(event,ts);
|
graph = latencyCommandGenSparkeline(event,ts);
|
||||||
addReplyBulkCString(c,graph);
|
addReplyVerbatim(c,graph,sdslen(graph),"txt");
|
||||||
sdsfree(graph);
|
sdsfree(graph);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
|
||||||
/* LATENCY LATEST */
|
/* LATENCY LATEST */
|
||||||
@ -608,7 +608,7 @@ NULL
|
|||||||
/* LATENCY DOCTOR */
|
/* LATENCY DOCTOR */
|
||||||
sds report = createLatencyReport();
|
sds report = createLatencyReport();
|
||||||
|
|
||||||
addReplyBulkCBuffer(c,report,sdslen(report));
|
addReplyVerbatim(c,report,sdslen(report),"txt");
|
||||||
sdsfree(report);
|
sdsfree(report);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
|
||||||
/* LATENCY RESET */
|
/* LATENCY RESET */
|
||||||
|
136
src/lolwut.c
136
src/lolwut.c
@ -34,8 +34,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "lolwut.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
void lolwut5Command(client *c);
|
void lolwut5Command(client *c);
|
||||||
|
void lolwut6Command(client *c);
|
||||||
|
|
||||||
/* The default target for LOLWUT if no matching version was found.
|
/* The default target for LOLWUT if no matching version was found.
|
||||||
* This is what unstable versions of Redis will display. */
|
* This is what unstable versions of Redis will display. */
|
||||||
@ -43,14 +46,143 @@ void lolwutUnstableCommand(client *c) {
|
|||||||
sds rendered = sdsnew("Redis ver. ");
|
sds rendered = sdsnew("Redis ver. ");
|
||||||
rendered = sdscat(rendered,REDIS_VERSION);
|
rendered = sdscat(rendered,REDIS_VERSION);
|
||||||
rendered = sdscatlen(rendered,"\n",1);
|
rendered = sdscatlen(rendered,"\n",1);
|
||||||
addReplyBulkSds(c,rendered);
|
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||||
|
sdsfree(rendered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* LOLWUT [VERSION <version>] [... version specific arguments ...] */
|
||||||
void lolwutCommand(client *c) {
|
void lolwutCommand(client *c) {
|
||||||
char *v = REDIS_VERSION;
|
char *v = REDIS_VERSION;
|
||||||
if ((v[0] == '5' && v[1] == '.') ||
|
char verstr[64];
|
||||||
|
|
||||||
|
if (c->argc >= 3 && !strcasecmp(c->argv[1]->ptr,"version")) {
|
||||||
|
long ver;
|
||||||
|
if (getLongFromObjectOrReply(c,c->argv[2],&ver,NULL) != C_OK) return;
|
||||||
|
snprintf(verstr,sizeof(verstr),"%u.0.0",(unsigned int)ver);
|
||||||
|
v = verstr;
|
||||||
|
|
||||||
|
/* Adjust argv/argc to filter the "VERSION ..." option, since the
|
||||||
|
* specific LOLWUT version implementations don't know about it
|
||||||
|
* and expect their arguments. */
|
||||||
|
c->argv += 2;
|
||||||
|
c->argc -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((v[0] == '5' && v[1] == '.' && v[2] != '9') ||
|
||||||
(v[0] == '4' && v[1] == '.' && v[2] == '9'))
|
(v[0] == '4' && v[1] == '.' && v[2] == '9'))
|
||||||
lolwut5Command(c);
|
lolwut5Command(c);
|
||||||
|
else if ((v[0] == '6' && v[1] == '.' && v[2] != '9') ||
|
||||||
|
(v[0] == '5' && v[1] == '.' && v[2] == '9'))
|
||||||
|
lolwut6Command(c);
|
||||||
else
|
else
|
||||||
lolwutUnstableCommand(c);
|
lolwutUnstableCommand(c);
|
||||||
|
|
||||||
|
/* Fix back argc/argv in case of VERSION argument. */
|
||||||
|
if (v == verstr) {
|
||||||
|
c->argv -= 2;
|
||||||
|
c->argc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ========================== LOLWUT Canvase ===============================
|
||||||
|
* Many LOWUT versions will likely print some computer art to the screen.
|
||||||
|
* This is the case with LOLWUT 5 and LOLWUT 6, so here there is a generic
|
||||||
|
* canvas implementation that can be reused. */
|
||||||
|
|
||||||
|
/* Allocate and return a new canvas of the specified size. */
|
||||||
|
lwCanvas *lwCreateCanvas(int width, int height, int bgcolor) {
|
||||||
|
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
||||||
|
canvas->width = width;
|
||||||
|
canvas->height = height;
|
||||||
|
canvas->pixels = zmalloc(width*height);
|
||||||
|
memset(canvas->pixels,bgcolor,width*height);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free the canvas created by lwCreateCanvas(). */
|
||||||
|
void lwFreeCanvas(lwCanvas *canvas) {
|
||||||
|
zfree(canvas->pixels);
|
||||||
|
zfree(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
|
||||||
|
* dot will be displyed, and 1 means dot will be displayed.
|
||||||
|
* Coordinates are arranged so that left-top corner is 0,0. You can write
|
||||||
|
* out of the size of the canvas without issues. */
|
||||||
|
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
|
||||||
|
if (x < 0 || x >= canvas->width ||
|
||||||
|
y < 0 || y >= canvas->height) return;
|
||||||
|
canvas->pixels[x+y*canvas->width] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the value of the specified pixel on the canvas. */
|
||||||
|
int lwGetPixel(lwCanvas *canvas, int x, int y) {
|
||||||
|
if (x < 0 || x >= canvas->width ||
|
||||||
|
y < 0 || y >= canvas->height) return 0;
|
||||||
|
return canvas->pixels[x+y*canvas->width];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
|
||||||
|
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
|
||||||
|
int dx = abs(x2-x1);
|
||||||
|
int dy = abs(y2-y1);
|
||||||
|
int sx = (x1 < x2) ? 1 : -1;
|
||||||
|
int sy = (y1 < y2) ? 1 : -1;
|
||||||
|
int err = dx-dy, e2;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
lwDrawPixel(canvas,x1,y1,color);
|
||||||
|
if (x1 == x2 && y1 == y2) break;
|
||||||
|
e2 = err*2;
|
||||||
|
if (e2 > -dy) {
|
||||||
|
err -= dy;
|
||||||
|
x1 += sx;
|
||||||
|
}
|
||||||
|
if (e2 < dx) {
|
||||||
|
err += dx;
|
||||||
|
y1 += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw a square centered at the specified x,y coordinates, with the specified
|
||||||
|
* rotation angle and size. In order to write a rotated square, we use the
|
||||||
|
* trivial fact that the parametric equation:
|
||||||
|
*
|
||||||
|
* x = sin(k)
|
||||||
|
* y = cos(k)
|
||||||
|
*
|
||||||
|
* Describes a circle for values going from 0 to 2*PI. So basically if we start
|
||||||
|
* at 45 degrees, that is k = PI/4, with the first point, and then we find
|
||||||
|
* the other three points incrementing K by PI/2 (90 degrees), we'll have the
|
||||||
|
* points of the square. In order to rotate the square, we just start with
|
||||||
|
* k = PI/4 + rotation_angle, and we are done.
|
||||||
|
*
|
||||||
|
* Of course the vanilla equations above will describe the square inside a
|
||||||
|
* circle of radius 1, so in order to draw larger squares we'll have to
|
||||||
|
* multiply the obtained coordinates, and then translate them. However this
|
||||||
|
* is much simpler than implementing the abstract concept of 2D shape and then
|
||||||
|
* performing the rotation/translation transformation, so for LOLWUT it's
|
||||||
|
* a good approach. */
|
||||||
|
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color) {
|
||||||
|
int px[4], py[4];
|
||||||
|
|
||||||
|
/* Adjust the desired size according to the fact that the square inscribed
|
||||||
|
* into a circle of radius 1 has the side of length SQRT(2). This way
|
||||||
|
* size becomes a simple multiplication factor we can use with our
|
||||||
|
* coordinates to magnify them. */
|
||||||
|
size /= 1.4142135623;
|
||||||
|
size = round(size);
|
||||||
|
|
||||||
|
/* Compute the four points. */
|
||||||
|
float k = M_PI/4 + angle;
|
||||||
|
for (int j = 0; j < 4; j++) {
|
||||||
|
px[j] = round(sin(k) * size + x);
|
||||||
|
py[j] = round(cos(k) * size + y);
|
||||||
|
k += M_PI/2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw the square. */
|
||||||
|
for (int j = 0; j < 4; j++)
|
||||||
|
lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],color);
|
||||||
}
|
}
|
||||||
|
49
src/lolwut.h
Normal file
49
src/lolwut.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* This structure represents our canvas. Drawing functions will take a pointer
|
||||||
|
* to a canvas to write to it. Later the canvas can be rendered to a string
|
||||||
|
* suitable to be printed on the screen, using unicode Braille characters. */
|
||||||
|
|
||||||
|
/* This represents a very simple generic canvas in order to draw stuff.
|
||||||
|
* It's up to each LOLWUT versions to translate what they draw to the
|
||||||
|
* screen, depending on the result to accomplish. */
|
||||||
|
typedef struct lwCanvas {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
char *pixels;
|
||||||
|
} lwCanvas;
|
||||||
|
|
||||||
|
/* Drawing functions implemented inside lolwut.c. */
|
||||||
|
lwCanvas *lwCreateCanvas(int width, int height, int bgcolor);
|
||||||
|
void lwFreeCanvas(lwCanvas *canvas);
|
||||||
|
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color);
|
||||||
|
int lwGetPixel(lwCanvas *canvas, int x, int y);
|
||||||
|
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color);
|
||||||
|
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color);
|
119
src/lolwut5.c
119
src/lolwut5.c
@ -34,17 +34,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
#include "lolwut.h"
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
/* This structure represents our canvas. Drawing functions will take a pointer
|
|
||||||
* to a canvas to write to it. Later the canvas can be rendered to a string
|
|
||||||
* suitable to be printed on the screen, using unicode Braille characters. */
|
|
||||||
typedef struct lwCanvas {
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
char *pixels;
|
|
||||||
} lwCanvas;
|
|
||||||
|
|
||||||
/* Translate a group of 8 pixels (2x4 vertical rectangle) to the corresponding
|
/* Translate a group of 8 pixels (2x4 vertical rectangle) to the corresponding
|
||||||
* braille character. The byte should correspond to the pixels arranged as
|
* braille character. The byte should correspond to the pixels arranged as
|
||||||
* follows, where 0 is the least significant bit, and 7 the most significant
|
* follows, where 0 is the least significant bit, and 7 the most significant
|
||||||
@ -69,104 +61,6 @@ void lwTranslatePixelsGroup(int byte, char *output) {
|
|||||||
output[2] = 0x80 | (code & 0x3F); /* 10-xxxxxx */
|
output[2] = 0x80 | (code & 0x3F); /* 10-xxxxxx */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate and return a new canvas of the specified size. */
|
|
||||||
lwCanvas *lwCreateCanvas(int width, int height) {
|
|
||||||
lwCanvas *canvas = zmalloc(sizeof(*canvas));
|
|
||||||
canvas->width = width;
|
|
||||||
canvas->height = height;
|
|
||||||
canvas->pixels = zmalloc(width*height);
|
|
||||||
memset(canvas->pixels,0,width*height);
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free the canvas created by lwCreateCanvas(). */
|
|
||||||
void lwFreeCanvas(lwCanvas *canvas) {
|
|
||||||
zfree(canvas->pixels);
|
|
||||||
zfree(canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
|
|
||||||
* dot will be displyed, and 1 means dot will be displayed.
|
|
||||||
* Coordinates are arranged so that left-top corner is 0,0. You can write
|
|
||||||
* out of the size of the canvas without issues. */
|
|
||||||
void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
|
|
||||||
if (x < 0 || x >= canvas->width ||
|
|
||||||
y < 0 || y >= canvas->height) return;
|
|
||||||
canvas->pixels[x+y*canvas->width] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return the value of the specified pixel on the canvas. */
|
|
||||||
int lwGetPixel(lwCanvas *canvas, int x, int y) {
|
|
||||||
if (x < 0 || x >= canvas->width ||
|
|
||||||
y < 0 || y >= canvas->height) return 0;
|
|
||||||
return canvas->pixels[x+y*canvas->width];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
|
|
||||||
void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
|
|
||||||
int dx = abs(x2-x1);
|
|
||||||
int dy = abs(y2-y1);
|
|
||||||
int sx = (x1 < x2) ? 1 : -1;
|
|
||||||
int sy = (y1 < y2) ? 1 : -1;
|
|
||||||
int err = dx-dy, e2;
|
|
||||||
|
|
||||||
while(1) {
|
|
||||||
lwDrawPixel(canvas,x1,y1,color);
|
|
||||||
if (x1 == x2 && y1 == y2) break;
|
|
||||||
e2 = err*2;
|
|
||||||
if (e2 > -dy) {
|
|
||||||
err -= dy;
|
|
||||||
x1 += sx;
|
|
||||||
}
|
|
||||||
if (e2 < dx) {
|
|
||||||
err += dx;
|
|
||||||
y1 += sy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Draw a square centered at the specified x,y coordinates, with the specified
|
|
||||||
* rotation angle and size. In order to write a rotated square, we use the
|
|
||||||
* trivial fact that the parametric equation:
|
|
||||||
*
|
|
||||||
* x = sin(k)
|
|
||||||
* y = cos(k)
|
|
||||||
*
|
|
||||||
* Describes a circle for values going from 0 to 2*PI. So basically if we start
|
|
||||||
* at 45 degrees, that is k = PI/4, with the first point, and then we find
|
|
||||||
* the other three points incrementing K by PI/2 (90 degrees), we'll have the
|
|
||||||
* points of the square. In order to rotate the square, we just start with
|
|
||||||
* k = PI/4 + rotation_angle, and we are done.
|
|
||||||
*
|
|
||||||
* Of course the vanilla equations above will describe the square inside a
|
|
||||||
* circle of radius 1, so in order to draw larger squares we'll have to
|
|
||||||
* multiply the obtained coordinates, and then translate them. However this
|
|
||||||
* is much simpler than implementing the abstract concept of 2D shape and then
|
|
||||||
* performing the rotation/translation transformation, so for LOLWUT it's
|
|
||||||
* a good approach. */
|
|
||||||
void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle) {
|
|
||||||
int px[4], py[4];
|
|
||||||
|
|
||||||
/* Adjust the desired size according to the fact that the square inscribed
|
|
||||||
* into a circle of radius 1 has the side of length SQRT(2). This way
|
|
||||||
* size becomes a simple multiplication factor we can use with our
|
|
||||||
* coordinates to magnify them. */
|
|
||||||
size /= 1.4142135623;
|
|
||||||
size = round(size);
|
|
||||||
|
|
||||||
/* Compute the four points. */
|
|
||||||
float k = M_PI/4 + angle;
|
|
||||||
for (int j = 0; j < 4; j++) {
|
|
||||||
px[j] = round(sin(k) * size + x);
|
|
||||||
py[j] = round(cos(k) * size + y);
|
|
||||||
k += M_PI/2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Draw the square. */
|
|
||||||
for (int j = 0; j < 4; j++)
|
|
||||||
lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Schotter, the output of LOLWUT of Redis 5, is a computer graphic art piece
|
/* Schotter, the output of LOLWUT of Redis 5, is a computer graphic art piece
|
||||||
* generated by Georg Nees in the 60s. It explores the relationship between
|
* generated by Georg Nees in the 60s. It explores the relationship between
|
||||||
* caos and order.
|
* caos and order.
|
||||||
@ -180,7 +74,7 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
|||||||
int padding = canvas_width > 4 ? 2 : 0;
|
int padding = canvas_width > 4 ? 2 : 0;
|
||||||
float square_side = (float)(canvas_width-padding*2) / squares_per_row;
|
float square_side = (float)(canvas_width-padding*2) / squares_per_row;
|
||||||
int canvas_height = square_side * squares_per_col + padding*2;
|
int canvas_height = square_side * squares_per_col + padding*2;
|
||||||
lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height);
|
lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height, 0);
|
||||||
|
|
||||||
for (int y = 0; y < squares_per_col; y++) {
|
for (int y = 0; y < squares_per_col; y++) {
|
||||||
for (int x = 0; x < squares_per_row; x++) {
|
for (int x = 0; x < squares_per_row; x++) {
|
||||||
@ -200,7 +94,7 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
|||||||
sx += r2*square_side/3;
|
sx += r2*square_side/3;
|
||||||
sy += r3*square_side/3;
|
sy += r3*square_side/3;
|
||||||
}
|
}
|
||||||
lwDrawSquare(canvas,sx,sy,square_side,angle);
|
lwDrawSquare(canvas,sx,sy,square_side,angle,1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -212,7 +106,7 @@ lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_
|
|||||||
* logical canvas. The actual returned string will require a terminal that is
|
* logical canvas. The actual returned string will require a terminal that is
|
||||||
* width/2 large and height/4 tall in order to hold the whole image without
|
* width/2 large and height/4 tall in order to hold the whole image without
|
||||||
* overflowing or scrolling, since each Barille character is 2x4. */
|
* overflowing or scrolling, since each Barille character is 2x4. */
|
||||||
sds lwRenderCanvas(lwCanvas *canvas) {
|
static sds renderCanvas(lwCanvas *canvas) {
|
||||||
sds text = sdsempty();
|
sds text = sdsempty();
|
||||||
for (int y = 0; y < canvas->height; y += 4) {
|
for (int y = 0; y < canvas->height; y += 4) {
|
||||||
for (int x = 0; x < canvas->width; x += 2) {
|
for (int x = 0; x < canvas->width; x += 2) {
|
||||||
@ -272,11 +166,12 @@ void lolwut5Command(client *c) {
|
|||||||
|
|
||||||
/* Generate some computer art and reply. */
|
/* Generate some computer art and reply. */
|
||||||
lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
|
lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
|
||||||
sds rendered = lwRenderCanvas(canvas);
|
sds rendered = renderCanvas(canvas);
|
||||||
rendered = sdscat(rendered,
|
rendered = sdscat(rendered,
|
||||||
"\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
|
"\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
|
||||||
rendered = sdscat(rendered,REDIS_VERSION);
|
rendered = sdscat(rendered,REDIS_VERSION);
|
||||||
rendered = sdscatlen(rendered,"\n",1);
|
rendered = sdscatlen(rendered,"\n",1);
|
||||||
addReplyBulkSds(c,rendered);
|
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||||
|
sdsfree(rendered);
|
||||||
lwFreeCanvas(canvas);
|
lwFreeCanvas(canvas);
|
||||||
}
|
}
|
||||||
|
200
src/lolwut6.c
Normal file
200
src/lolwut6.c
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* ----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* This file implements the LOLWUT command. The command should do something
|
||||||
|
* fun and interesting, and should be replaced by a new implementation at
|
||||||
|
* each new version of Redis.
|
||||||
|
*
|
||||||
|
* Thanks to Michele Hiki Falcone for the original image that ispired
|
||||||
|
* the image, part of his game, Plaguemon.
|
||||||
|
*
|
||||||
|
* Thanks to the Shhh computer art collective for the help in tuning the
|
||||||
|
* output to have a better artistic effect.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "server.h"
|
||||||
|
#include "lolwut.h"
|
||||||
|
|
||||||
|
/* Render the canvas using the four gray levels of the standard color
|
||||||
|
* terminal: they match very well to the grayscale display of the gameboy. */
|
||||||
|
static sds renderCanvas(lwCanvas *canvas) {
|
||||||
|
sds text = sdsempty();
|
||||||
|
for (int y = 0; y < canvas->height; y++) {
|
||||||
|
for (int x = 0; x < canvas->width; x++) {
|
||||||
|
int color = lwGetPixel(canvas,x,y);
|
||||||
|
char *ce; /* Color escape sequence. */
|
||||||
|
|
||||||
|
/* Note that we set both the foreground and background color.
|
||||||
|
* This way we are able to get a more consistent result among
|
||||||
|
* different terminals implementations. */
|
||||||
|
switch(color) {
|
||||||
|
case 0: ce = "0;30;40m"; break; /* Black */
|
||||||
|
case 1: ce = "0;90;100m"; break; /* Gray 1 */
|
||||||
|
case 2: ce = "0;37;47m"; break; /* Gray 2 */
|
||||||
|
case 3: ce = "0;97;107m"; break; /* White */
|
||||||
|
}
|
||||||
|
text = sdscatprintf(text,"\033[%s \033[0m",ce);
|
||||||
|
}
|
||||||
|
if (y != canvas->height-1) text = sdscatlen(text,"\n",1);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Draw a skyscraper on the canvas, according to the parameters in the
|
||||||
|
* 'skyscraper' structure. Window colors are random and are always one
|
||||||
|
* of the two grays. */
|
||||||
|
struct skyscraper {
|
||||||
|
int xoff; /* X offset. */
|
||||||
|
int width; /* Pixels width. */
|
||||||
|
int height; /* Pixels height. */
|
||||||
|
int windows; /* Draw windows if true. */
|
||||||
|
int color; /* Color of the skyscraper. */
|
||||||
|
};
|
||||||
|
|
||||||
|
void generateSkyscraper(lwCanvas *canvas, struct skyscraper *si) {
|
||||||
|
int starty = canvas->height-1;
|
||||||
|
int endy = starty - si->height + 1;
|
||||||
|
for (int y = starty; y >= endy; y--) {
|
||||||
|
for (int x = si->xoff; x < si->xoff+si->width; x++) {
|
||||||
|
/* The roof is four pixels less wide. */
|
||||||
|
if (y == endy && (x <= si->xoff+1 || x >= si->xoff+si->width-2))
|
||||||
|
continue;
|
||||||
|
int color = si->color;
|
||||||
|
/* Alter the color if this is a place where we want to
|
||||||
|
* draw a window. We check that we are in the inner part of the
|
||||||
|
* skyscraper, so that windows are far from the borders. */
|
||||||
|
if (si->windows &&
|
||||||
|
x > si->xoff+1 &&
|
||||||
|
x < si->xoff+si->width-2 &&
|
||||||
|
y > endy+1 &&
|
||||||
|
y < starty-1)
|
||||||
|
{
|
||||||
|
/* Calculate the x,y position relative to the start of
|
||||||
|
* the window area. */
|
||||||
|
int relx = x - (si->xoff+1);
|
||||||
|
int rely = y - (endy+1);
|
||||||
|
|
||||||
|
/* Note that we want the windows to be two pixels wide
|
||||||
|
* but just one pixel tall, because terminal "pixels"
|
||||||
|
* (characters) are not square. */
|
||||||
|
if (relx/2 % 2 && rely % 2) {
|
||||||
|
do {
|
||||||
|
color = 1 + rand() % 2;
|
||||||
|
} while (color == si->color);
|
||||||
|
/* Except we want adjacent pixels creating the same
|
||||||
|
* window to be the same color. */
|
||||||
|
if (relx % 2) color = lwGetPixel(canvas,x-1,y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lwDrawPixel(canvas,x,y,color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate a skyline inspired by the parallax backgrounds of 8 bit games. */
|
||||||
|
void generateSkyline(lwCanvas *canvas) {
|
||||||
|
struct skyscraper si;
|
||||||
|
|
||||||
|
/* First draw the background skyscraper without windows, using the
|
||||||
|
* two different grays. We use two passes to make sure that the lighter
|
||||||
|
* ones are always in the background. */
|
||||||
|
for (int color = 2; color >= 1; color--) {
|
||||||
|
si.color = color;
|
||||||
|
for (int offset = -10; offset < canvas->width;) {
|
||||||
|
offset += rand() % 8;
|
||||||
|
si.xoff = offset;
|
||||||
|
si.width = 10 + rand()%9;
|
||||||
|
if (color == 2)
|
||||||
|
si.height = canvas->height/2 + rand()%canvas->height/2;
|
||||||
|
else
|
||||||
|
si.height = canvas->height/2 + rand()%canvas->height/3;
|
||||||
|
si.windows = 0;
|
||||||
|
generateSkyscraper(canvas, &si);
|
||||||
|
if (color == 2)
|
||||||
|
offset += si.width/2;
|
||||||
|
else
|
||||||
|
offset += si.width+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now draw the foreground skyscraper with the windows. */
|
||||||
|
si.color = 0;
|
||||||
|
for (int offset = -10; offset < canvas->width;) {
|
||||||
|
offset += rand() % 8;
|
||||||
|
si.xoff = offset;
|
||||||
|
si.width = 5 + rand()%14;
|
||||||
|
if (si.width % 4) si.width += (si.width % 3);
|
||||||
|
si.height = canvas->height/3 + rand()%canvas->height/2;
|
||||||
|
si.windows = 1;
|
||||||
|
generateSkyscraper(canvas, &si);
|
||||||
|
offset += si.width+5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The LOLWUT 6 command:
|
||||||
|
*
|
||||||
|
* LOLWUT [columns] [rows]
|
||||||
|
*
|
||||||
|
* By default the command uses 80 columns, 40 squares per row
|
||||||
|
* per column.
|
||||||
|
*/
|
||||||
|
void lolwut6Command(client *c) {
|
||||||
|
long cols = 80;
|
||||||
|
long rows = 20;
|
||||||
|
|
||||||
|
/* Parse the optional arguments if any. */
|
||||||
|
if (c->argc > 1 &&
|
||||||
|
getLongFromObjectOrReply(c,c->argv[1],&cols,NULL) != C_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (c->argc > 2 &&
|
||||||
|
getLongFromObjectOrReply(c,c->argv[2],&rows,NULL) != C_OK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Limits. We want LOLWUT to be always reasonably fast and cheap to execute
|
||||||
|
* so we have maximum number of columns, rows, and output resulution. */
|
||||||
|
if (cols < 1) cols = 1;
|
||||||
|
if (cols > 1000) cols = 1000;
|
||||||
|
if (rows < 1) rows = 1;
|
||||||
|
if (rows > 1000) rows = 1000;
|
||||||
|
|
||||||
|
/* Generate the city skyline and reply. */
|
||||||
|
lwCanvas *canvas = lwCreateCanvas(cols,rows,3);
|
||||||
|
generateSkyline(canvas);
|
||||||
|
sds rendered = renderCanvas(canvas);
|
||||||
|
rendered = sdscat(rendered,
|
||||||
|
"\nDedicated to the 8 bit game developers of past and present.\n"
|
||||||
|
"Original 8 bit image from Plaguemon by hikikomori. Redis ver. ");
|
||||||
|
rendered = sdscat(rendered,REDIS_VERSION);
|
||||||
|
rendered = sdscatlen(rendered,"\n",1);
|
||||||
|
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||||
|
sdsfree(rendered);
|
||||||
|
lwFreeCanvas(canvas);
|
||||||
|
}
|
609
src/module.c
609
src/module.c
@ -31,6 +31,7 @@
|
|||||||
#include "cluster.h"
|
#include "cluster.h"
|
||||||
#include "rdb.h"
|
#include "rdb.h"
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
#define REDISMODULE_CORE 1
|
#define REDISMODULE_CORE 1
|
||||||
#include "redismodule.h"
|
#include "redismodule.h"
|
||||||
@ -41,6 +42,17 @@
|
|||||||
* pointers that have an API the module can call with them)
|
* pointers that have an API the module can call with them)
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
typedef struct RedisModuleInfoCtx {
|
||||||
|
struct RedisModule *module;
|
||||||
|
sds requested_section;
|
||||||
|
sds info; /* info string we collected so far */
|
||||||
|
int sections; /* number of sections we collected so far */
|
||||||
|
int in_section; /* indication if we're in an active section or not */
|
||||||
|
int in_dict_field; /* indication that we're curreintly appending to a dict */
|
||||||
|
} RedisModuleInfoCtx;
|
||||||
|
|
||||||
|
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
|
||||||
|
|
||||||
/* This structure represents a module inside the system. */
|
/* This structure represents a module inside the system. */
|
||||||
struct RedisModule {
|
struct RedisModule {
|
||||||
void *handle; /* Module dlopen() handle. */
|
void *handle; /* Module dlopen() handle. */
|
||||||
@ -52,6 +64,8 @@ struct RedisModule {
|
|||||||
list *using; /* List of modules we use some APIs of. */
|
list *using; /* List of modules we use some APIs of. */
|
||||||
list *filters; /* List of filters the module has registered. */
|
list *filters; /* List of filters the module has registered. */
|
||||||
int in_call; /* RM_Call() nesting level */
|
int in_call; /* RM_Call() nesting level */
|
||||||
|
int options; /* Module options and capabilities. */
|
||||||
|
RedisModuleInfoFunc info_cb; /* callback for module to add INFO fields. */
|
||||||
};
|
};
|
||||||
typedef struct RedisModule RedisModule;
|
typedef struct RedisModule RedisModule;
|
||||||
|
|
||||||
@ -133,10 +147,14 @@ struct RedisModuleCtx {
|
|||||||
int keys_count;
|
int keys_count;
|
||||||
|
|
||||||
struct RedisModulePoolAllocBlock *pa_head;
|
struct RedisModulePoolAllocBlock *pa_head;
|
||||||
|
redisOpArray saved_oparray; /* When propagating commands in a callback
|
||||||
|
we reallocate the "also propagate" op
|
||||||
|
array. Here we save the old one to
|
||||||
|
restore it later. */
|
||||||
};
|
};
|
||||||
typedef struct RedisModuleCtx RedisModuleCtx;
|
typedef struct RedisModuleCtx RedisModuleCtx;
|
||||||
|
|
||||||
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL}
|
#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL, {0}}
|
||||||
#define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
|
#define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
|
||||||
#define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
|
#define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
|
||||||
#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2)
|
#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2)
|
||||||
@ -144,6 +162,7 @@ typedef struct RedisModuleCtx RedisModuleCtx;
|
|||||||
#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4)
|
#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4)
|
||||||
#define REDISMODULE_CTX_THREAD_SAFE (1<<5)
|
#define REDISMODULE_CTX_THREAD_SAFE (1<<5)
|
||||||
#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<6)
|
#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<6)
|
||||||
|
#define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<7)
|
||||||
|
|
||||||
/* This represents a Redis key opened with RM_OpenKey(). */
|
/* This represents a Redis key opened with RM_OpenKey(). */
|
||||||
struct RedisModuleKey {
|
struct RedisModuleKey {
|
||||||
@ -292,6 +311,18 @@ typedef struct RedisModuleCommandFilter {
|
|||||||
/* Registered filters */
|
/* Registered filters */
|
||||||
static list *moduleCommandFilters;
|
static list *moduleCommandFilters;
|
||||||
|
|
||||||
|
typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
|
||||||
|
|
||||||
|
static struct RedisModuleForkInfo {
|
||||||
|
RedisModuleForkDoneHandler done_handler;
|
||||||
|
void* done_handler_user_data;
|
||||||
|
} moduleForkInfo = {0};
|
||||||
|
|
||||||
|
/* Flags for moduleCreateArgvFromUserFormat(). */
|
||||||
|
#define REDISMODULE_ARGV_REPLICATE (1<<0)
|
||||||
|
#define REDISMODULE_ARGV_NO_AOF (1<<1)
|
||||||
|
#define REDISMODULE_ARGV_NO_REPLICAS (1<<2)
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
* Prototypes
|
* Prototypes
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
@ -497,8 +528,47 @@ int RM_GetApi(const char *funcname, void **targetPtrPtr) {
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Helper function for when a command callback is called, in order to handle
|
||||||
|
* details needed to correctly replicate commands. */
|
||||||
|
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
||||||
|
client *c = ctx->client;
|
||||||
|
|
||||||
|
/* We don't need to do anything here if the context was never used
|
||||||
|
* in order to propagate commands. */
|
||||||
|
if (!(ctx->flags & REDISMODULE_CTX_MULTI_EMITTED)) return;
|
||||||
|
|
||||||
|
if (c->flags & CLIENT_LUA) return;
|
||||||
|
|
||||||
|
/* Handle the replication of the final EXEC, since whatever a command
|
||||||
|
* emits is always wrapped around MULTI/EXEC. */
|
||||||
|
robj *propargv[1];
|
||||||
|
propargv[0] = createStringObject("EXEC",4);
|
||||||
|
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
||||||
|
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||||
|
decrRefCount(propargv[0]);
|
||||||
|
|
||||||
|
/* If this is not a module command context (but is instead a simple
|
||||||
|
* callback context), we have to handle directly the "also propagate"
|
||||||
|
* array and emit it. In a module command call this will be handled
|
||||||
|
* directly by call(). */
|
||||||
|
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL) &&
|
||||||
|
server.also_propagate.numops)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < server.also_propagate.numops; j++) {
|
||||||
|
redisOp *rop = &server.also_propagate.ops[j];
|
||||||
|
int target = rop->target;
|
||||||
|
if (target)
|
||||||
|
propagate(rop->cmd,rop->dbid,rop->argv,rop->argc,target);
|
||||||
|
}
|
||||||
|
redisOpArrayFree(&server.also_propagate);
|
||||||
|
}
|
||||||
|
/* Restore the previous oparray in case of nexted use of the API. */
|
||||||
|
server.also_propagate = ctx->saved_oparray;
|
||||||
|
}
|
||||||
|
|
||||||
/* Free the context after the user function was called. */
|
/* Free the context after the user function was called. */
|
||||||
void moduleFreeContext(RedisModuleCtx *ctx) {
|
void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||||
|
moduleHandlePropagationAfterCommandCallback(ctx);
|
||||||
autoMemoryCollect(ctx);
|
autoMemoryCollect(ctx);
|
||||||
poolAllocRelease(ctx);
|
poolAllocRelease(ctx);
|
||||||
if (ctx->postponed_arrays) {
|
if (ctx->postponed_arrays) {
|
||||||
@ -514,34 +584,16 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
|
|||||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) freeClient(ctx->client);
|
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) freeClient(ctx->client);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper function for when a command callback is called, in order to handle
|
|
||||||
* details needed to correctly replicate commands. */
|
|
||||||
void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
|
|
||||||
client *c = ctx->client;
|
|
||||||
|
|
||||||
if (c->flags & CLIENT_LUA) return;
|
|
||||||
|
|
||||||
/* Handle the replication of the final EXEC, since whatever a command
|
|
||||||
* emits is always wrapped around MULTI/EXEC. */
|
|
||||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
|
|
||||||
robj *propargv[1];
|
|
||||||
propargv[0] = createStringObject("EXEC",4);
|
|
||||||
alsoPropagate(server.execCommand,c->db->id,propargv,1,
|
|
||||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
|
||||||
decrRefCount(propargv[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This Redis command binds the normal Redis command invocation with commands
|
/* This Redis command binds the normal Redis command invocation with commands
|
||||||
* exported by modules. */
|
* exported by modules. */
|
||||||
void RedisModuleCommandDispatcher(client *c) {
|
void RedisModuleCommandDispatcher(client *c) {
|
||||||
RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc;
|
RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc;
|
||||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||||
|
|
||||||
|
ctx.flags |= REDISMODULE_CTX_MODULE_COMMAND_CALL;
|
||||||
ctx.module = cp->module;
|
ctx.module = cp->module;
|
||||||
ctx.client = c;
|
ctx.client = c;
|
||||||
cp->func(&ctx,(void**)c->argv,c->argc);
|
cp->func(&ctx,(void**)c->argv,c->argc);
|
||||||
moduleHandlePropagationAfterCommandCallback(&ctx);
|
|
||||||
moduleFreeContext(&ctx);
|
moduleFreeContext(&ctx);
|
||||||
|
|
||||||
/* In some cases processMultibulkBuffer uses sdsMakeRoomFor to
|
/* In some cases processMultibulkBuffer uses sdsMakeRoomFor to
|
||||||
@ -772,6 +824,19 @@ long long RM_Milliseconds(void) {
|
|||||||
return mstime();
|
return mstime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Set flags defining capabilities or behavior bit flags.
|
||||||
|
*
|
||||||
|
* REDISMODULE_OPTIONS_HANDLE_IO_ERRORS:
|
||||||
|
* Generally, modules don't need to bother with this, as the process will just
|
||||||
|
* terminate if a read error happens, however, setting this flag would allow
|
||||||
|
* repl-diskless-load to work if enabled.
|
||||||
|
* The module should use RedisModule_IsIOError after reads, before using the
|
||||||
|
* data that was read, and in case of error, propagate it upwards, and also be
|
||||||
|
* able to release the partially populated value and all it's allocations. */
|
||||||
|
void RM_SetModuleOptions(RedisModuleCtx *ctx, int options) {
|
||||||
|
ctx->module->options = options;
|
||||||
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
* Automatic memory management for modules
|
* Automatic memory management for modules
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
@ -1126,10 +1191,9 @@ int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) {
|
|||||||
int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
|
int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
|
||||||
client *c = moduleGetReplyClient(ctx);
|
client *c = moduleGetReplyClient(ctx);
|
||||||
if (c == NULL) return REDISMODULE_OK;
|
if (c == NULL) return REDISMODULE_OK;
|
||||||
sds strmsg = sdsnewlen(prefix,1);
|
addReplyProto(c,prefix,strlen(prefix));
|
||||||
strmsg = sdscat(strmsg,msg);
|
addReplyProto(c,msg,strlen(msg));
|
||||||
strmsg = sdscatlen(strmsg,"\r\n",2);
|
addReplyProto(c,"\r\n",2);
|
||||||
addReplySds(c,strmsg);
|
|
||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1316,9 +1380,16 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
|||||||
/* If we already emitted MULTI return ASAP. */
|
/* If we already emitted MULTI return ASAP. */
|
||||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
|
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
|
||||||
/* If this is a thread safe context, we do not want to wrap commands
|
/* If this is a thread safe context, we do not want to wrap commands
|
||||||
* executed into MUTLI/EXEC, they are executed as single commands
|
* executed into MULTI/EXEC, they are executed as single commands
|
||||||
* from an external client in essence. */
|
* from an external client in essence. */
|
||||||
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) return;
|
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) return;
|
||||||
|
/* If this is a callback context, and not a module command execution
|
||||||
|
* context, we have to setup the op array for the "also propagate" API
|
||||||
|
* so that RM_Replicate() will work. */
|
||||||
|
if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL)) {
|
||||||
|
ctx->saved_oparray = server.also_propagate;
|
||||||
|
redisOpArrayInit(&server.also_propagate);
|
||||||
|
}
|
||||||
execCommandPropagateMulti(ctx->client);
|
execCommandPropagateMulti(ctx->client);
|
||||||
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
|
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
|
||||||
}
|
}
|
||||||
@ -1340,6 +1411,24 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
|||||||
*
|
*
|
||||||
* Please refer to RedisModule_Call() for more information.
|
* Please refer to RedisModule_Call() for more information.
|
||||||
*
|
*
|
||||||
|
* Using the special "A" and "R" modifiers, the caller can exclude either
|
||||||
|
* the AOF or the replicas from the propagation of the specified command.
|
||||||
|
* Otherwise, by default, the command will be propagated in both channels.
|
||||||
|
*
|
||||||
|
* ## Note about calling this function from a thread safe context:
|
||||||
|
*
|
||||||
|
* Normally when you call this function from the callback implementing a
|
||||||
|
* module command, or any other callback provided by the Redis Module API,
|
||||||
|
* Redis will accumulate all the calls to this function in the context of
|
||||||
|
* the callback, and will propagate all the commands wrapped in a MULTI/EXEC
|
||||||
|
* transaction. However when calling this function from a threaded safe context
|
||||||
|
* that can live an undefined amount of time, and can be locked/unlocked in
|
||||||
|
* at will, the behavior is different: MULTI/EXEC wrapper is not emitted
|
||||||
|
* and the command specified is inserted in the AOF and replication stream
|
||||||
|
* immediately.
|
||||||
|
*
|
||||||
|
* ## Return value
|
||||||
|
*
|
||||||
* The command returns REDISMODULE_ERR if the format specifiers are invalid
|
* The command returns REDISMODULE_ERR if the format specifiers are invalid
|
||||||
* or the command name does not belong to a known command. */
|
* or the command name does not belong to a known command. */
|
||||||
int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
|
int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
|
||||||
@ -1357,10 +1446,23 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
|
|||||||
va_end(ap);
|
va_end(ap);
|
||||||
if (argv == NULL) return REDISMODULE_ERR;
|
if (argv == NULL) return REDISMODULE_ERR;
|
||||||
|
|
||||||
/* Replicate! */
|
/* Select the propagation target. Usually is AOF + replicas, however
|
||||||
|
* the caller can exclude one or the other using the "A" or "R"
|
||||||
|
* modifiers. */
|
||||||
|
int target = 0;
|
||||||
|
if (!(flags & REDISMODULE_ARGV_NO_AOF)) target |= PROPAGATE_AOF;
|
||||||
|
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS)) target |= PROPAGATE_REPL;
|
||||||
|
|
||||||
|
/* Replicate! When we are in a threaded context, we want to just insert
|
||||||
|
* the replicated command ASAP, since it is not clear when the context
|
||||||
|
* will stop being used, so accumulating stuff does not make much sense,
|
||||||
|
* nor we could easily use the alsoPropagate() API from threads. */
|
||||||
|
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) {
|
||||||
|
propagate(cmd,ctx->client->db->id,argv,argc,target);
|
||||||
|
} else {
|
||||||
moduleReplicateMultiIfNeeded(ctx);
|
moduleReplicateMultiIfNeeded(ctx);
|
||||||
alsoPropagate(cmd,ctx->client->db->id,argv,argc,
|
alsoPropagate(cmd,ctx->client->db->id,argv,argc,target);
|
||||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
}
|
||||||
|
|
||||||
/* Release the argv. */
|
/* Release the argv. */
|
||||||
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
|
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
|
||||||
@ -2389,7 +2491,7 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
|
|||||||
*
|
*
|
||||||
* REDISMODULE_HASH_EXISTS: instead of setting the value of the field
|
* REDISMODULE_HASH_EXISTS: instead of setting the value of the field
|
||||||
* expecting a RedisModuleString pointer to pointer, the function just
|
* expecting a RedisModuleString pointer to pointer, the function just
|
||||||
* reports if the field esists or not and expects an integer pointer
|
* reports if the field exists or not and expects an integer pointer
|
||||||
* as the second element of each pair.
|
* as the second element of each pair.
|
||||||
*
|
*
|
||||||
* Example of REDISMODULE_HASH_CFIELD:
|
* Example of REDISMODULE_HASH_CFIELD:
|
||||||
@ -2678,12 +2780,11 @@ RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) {
|
|||||||
* to special modifiers in "fmt". For now only one exists:
|
* to special modifiers in "fmt". For now only one exists:
|
||||||
*
|
*
|
||||||
* "!" -> REDISMODULE_ARGV_REPLICATE
|
* "!" -> REDISMODULE_ARGV_REPLICATE
|
||||||
|
* "A" -> REDISMODULE_ARGV_NO_AOF
|
||||||
|
* "R" -> REDISMODULE_ARGV_NO_REPLICAS
|
||||||
*
|
*
|
||||||
* On error (format specifier error) NULL is returned and nothing is
|
* On error (format specifier error) NULL is returned and nothing is
|
||||||
* allocated. On success the argument vector is returned. */
|
* allocated. On success the argument vector is returned. */
|
||||||
|
|
||||||
#define REDISMODULE_ARGV_REPLICATE (1<<0)
|
|
||||||
|
|
||||||
robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap) {
|
robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap) {
|
||||||
int argc = 0, argv_size, j;
|
int argc = 0, argv_size, j;
|
||||||
robj **argv = NULL;
|
robj **argv = NULL;
|
||||||
@ -2732,6 +2833,10 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
|
|||||||
}
|
}
|
||||||
} else if (*p == '!') {
|
} else if (*p == '!') {
|
||||||
if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE;
|
if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE;
|
||||||
|
} else if (*p == 'A') {
|
||||||
|
if (flags) (*flags) |= REDISMODULE_ARGV_NO_AOF;
|
||||||
|
} else if (*p == 'R') {
|
||||||
|
if (flags) (*flags) |= REDISMODULE_ARGV_NO_REPLICAS;
|
||||||
} else {
|
} else {
|
||||||
goto fmterr;
|
goto fmterr;
|
||||||
}
|
}
|
||||||
@ -2752,7 +2857,10 @@ fmterr:
|
|||||||
* NULL is returned and errno is set to the following values:
|
* NULL is returned and errno is set to the following values:
|
||||||
*
|
*
|
||||||
* EINVAL: command non existing, wrong arity, wrong format specifier.
|
* EINVAL: command non existing, wrong arity, wrong format specifier.
|
||||||
* EPERM: operation in Cluster instance with key in non local slot. */
|
* EPERM: operation in Cluster instance with key in non local slot.
|
||||||
|
*
|
||||||
|
* This API is documented here: https://redis.io/topics/modules-intro
|
||||||
|
*/
|
||||||
RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
|
RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
|
||||||
struct redisCommand *cmd;
|
struct redisCommand *cmd;
|
||||||
client *c = NULL;
|
client *c = NULL;
|
||||||
@ -2823,7 +2931,9 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
|||||||
/* Run the command */
|
/* Run the command */
|
||||||
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
|
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
|
||||||
if (replicate) {
|
if (replicate) {
|
||||||
|
if (!(flags & REDISMODULE_ARGV_NO_AOF))
|
||||||
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
||||||
|
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS))
|
||||||
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
||||||
}
|
}
|
||||||
call(c,call_flags);
|
call(c,call_flags);
|
||||||
@ -3150,9 +3260,14 @@ void *RM_ModuleTypeGetValue(RedisModuleKey *key) {
|
|||||||
* RDB loading and saving functions
|
* RDB loading and saving functions
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* Called when there is a load error in the context of a module. This cannot
|
/* Called when there is a load error in the context of a module. On some
|
||||||
* be recovered like for the built-in types. */
|
* modules this cannot be recovered, but if the module declared capability
|
||||||
|
* to handle errors, we'll raise a flag rather than exiting. */
|
||||||
void moduleRDBLoadError(RedisModuleIO *io) {
|
void moduleRDBLoadError(RedisModuleIO *io) {
|
||||||
|
if (io->type->module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS) {
|
||||||
|
io->error = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
"Error loading data from RDB (short read or EOF). "
|
"Error loading data from RDB (short read or EOF). "
|
||||||
"Read performed by module '%s' about type '%s' "
|
"Read performed by module '%s' about type '%s' "
|
||||||
@ -3163,6 +3278,33 @@ void moduleRDBLoadError(RedisModuleIO *io) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns 0 if there's at least one registered data type that did not declare
|
||||||
|
* REDISMODULE_OPTIONS_HANDLE_IO_ERRORS, in which case diskless loading should
|
||||||
|
* be avoided since it could cause data loss. */
|
||||||
|
int moduleAllDatatypesHandleErrors() {
|
||||||
|
dictIterator *di = dictGetIterator(modules);
|
||||||
|
dictEntry *de;
|
||||||
|
|
||||||
|
while ((de = dictNext(di)) != NULL) {
|
||||||
|
struct RedisModule *module = dictGetVal(de);
|
||||||
|
if (listLength(module->types) &&
|
||||||
|
!(module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS))
|
||||||
|
{
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns true if any previous IO API failed.
|
||||||
|
* for Load* APIs the REDISMODULE_OPTIONS_HANDLE_IO_ERRORS flag must be set with
|
||||||
|
* RediModule_SetModuleOptions first. */
|
||||||
|
int RM_IsIOError(RedisModuleIO *io) {
|
||||||
|
return io->error;
|
||||||
|
}
|
||||||
|
|
||||||
/* Save an unsigned 64 bit value into the RDB file. This function should only
|
/* Save an unsigned 64 bit value into the RDB file. This function should only
|
||||||
* be called in the context of the rdb_save method of modules implementing new
|
* be called in the context of the rdb_save method of modules implementing new
|
||||||
* data types. */
|
* data types. */
|
||||||
@ -3186,6 +3328,7 @@ saveerr:
|
|||||||
* be called in the context of the rdb_load method of modules implementing
|
* be called in the context of the rdb_load method of modules implementing
|
||||||
* new data types. */
|
* new data types. */
|
||||||
uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
|
uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
|
||||||
|
if (io->error) return 0;
|
||||||
if (io->ver == 2) {
|
if (io->ver == 2) {
|
||||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||||
if (opcode != RDB_MODULE_OPCODE_UINT) goto loaderr;
|
if (opcode != RDB_MODULE_OPCODE_UINT) goto loaderr;
|
||||||
@ -3197,7 +3340,7 @@ uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
|
|||||||
|
|
||||||
loaderr:
|
loaderr:
|
||||||
moduleRDBLoadError(io);
|
moduleRDBLoadError(io);
|
||||||
return 0; /* Never reached. */
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Like RedisModule_SaveUnsigned() but for signed 64 bit values. */
|
/* Like RedisModule_SaveUnsigned() but for signed 64 bit values. */
|
||||||
@ -3256,6 +3399,7 @@ saveerr:
|
|||||||
|
|
||||||
/* Implements RM_LoadString() and RM_LoadStringBuffer() */
|
/* Implements RM_LoadString() and RM_LoadStringBuffer() */
|
||||||
void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
|
void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
|
||||||
|
if (io->error) return NULL;
|
||||||
if (io->ver == 2) {
|
if (io->ver == 2) {
|
||||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||||
if (opcode != RDB_MODULE_OPCODE_STRING) goto loaderr;
|
if (opcode != RDB_MODULE_OPCODE_STRING) goto loaderr;
|
||||||
@ -3267,7 +3411,7 @@ void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
|
|||||||
|
|
||||||
loaderr:
|
loaderr:
|
||||||
moduleRDBLoadError(io);
|
moduleRDBLoadError(io);
|
||||||
return NULL; /* Never reached. */
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* In the context of the rdb_load method of a module data type, loads a string
|
/* In the context of the rdb_load method of a module data type, loads a string
|
||||||
@ -3288,7 +3432,7 @@ RedisModuleString *RM_LoadString(RedisModuleIO *io) {
|
|||||||
* RedisModule_Realloc() or RedisModule_Free().
|
* RedisModule_Realloc() or RedisModule_Free().
|
||||||
*
|
*
|
||||||
* The size of the string is stored at '*lenptr' if not NULL.
|
* The size of the string is stored at '*lenptr' if not NULL.
|
||||||
* The returned string is not automatically NULL termianted, it is loaded
|
* The returned string is not automatically NULL terminated, it is loaded
|
||||||
* exactly as it was stored inisde the RDB file. */
|
* exactly as it was stored inisde the RDB file. */
|
||||||
char *RM_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr) {
|
char *RM_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr) {
|
||||||
return moduleLoadString(io,1,lenptr);
|
return moduleLoadString(io,1,lenptr);
|
||||||
@ -3316,6 +3460,7 @@ saveerr:
|
|||||||
/* In the context of the rdb_save method of a module data type, loads back the
|
/* In the context of the rdb_save method of a module data type, loads back the
|
||||||
* double value saved by RedisModule_SaveDouble(). */
|
* double value saved by RedisModule_SaveDouble(). */
|
||||||
double RM_LoadDouble(RedisModuleIO *io) {
|
double RM_LoadDouble(RedisModuleIO *io) {
|
||||||
|
if (io->error) return 0;
|
||||||
if (io->ver == 2) {
|
if (io->ver == 2) {
|
||||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||||
if (opcode != RDB_MODULE_OPCODE_DOUBLE) goto loaderr;
|
if (opcode != RDB_MODULE_OPCODE_DOUBLE) goto loaderr;
|
||||||
@ -3327,7 +3472,7 @@ double RM_LoadDouble(RedisModuleIO *io) {
|
|||||||
|
|
||||||
loaderr:
|
loaderr:
|
||||||
moduleRDBLoadError(io);
|
moduleRDBLoadError(io);
|
||||||
return 0; /* Never reached. */
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* In the context of the rdb_save method of a module data type, saves a float
|
/* In the context of the rdb_save method of a module data type, saves a float
|
||||||
@ -3352,6 +3497,7 @@ saveerr:
|
|||||||
/* In the context of the rdb_save method of a module data type, loads back the
|
/* In the context of the rdb_save method of a module data type, loads back the
|
||||||
* float value saved by RedisModule_SaveFloat(). */
|
* float value saved by RedisModule_SaveFloat(). */
|
||||||
float RM_LoadFloat(RedisModuleIO *io) {
|
float RM_LoadFloat(RedisModuleIO *io) {
|
||||||
|
if (io->error) return 0;
|
||||||
if (io->ver == 2) {
|
if (io->ver == 2) {
|
||||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||||
if (opcode != RDB_MODULE_OPCODE_FLOAT) goto loaderr;
|
if (opcode != RDB_MODULE_OPCODE_FLOAT) goto loaderr;
|
||||||
@ -3363,7 +3509,7 @@ float RM_LoadFloat(RedisModuleIO *io) {
|
|||||||
|
|
||||||
loaderr:
|
loaderr:
|
||||||
moduleRDBLoadError(io);
|
moduleRDBLoadError(io);
|
||||||
return 0; /* Never reached. */
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Iterate over modules, and trigger rdb aux saving for the ones modules types
|
/* Iterate over modules, and trigger rdb aux saving for the ones modules types
|
||||||
@ -3598,6 +3744,15 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...
|
|||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Redis-like assert function.
|
||||||
|
*
|
||||||
|
* A failed assertion will shut down the server and produce logging information
|
||||||
|
* that looks identical to information generated by Redis itself.
|
||||||
|
*/
|
||||||
|
void RM__Assert(const char *estr, const char *file, int line) {
|
||||||
|
_serverAssert(estr, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
* Blocking clients from modules
|
* Blocking clients from modules
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
@ -4729,6 +4884,194 @@ int RM_DictCompare(RedisModuleDictIter *di, const char *op, RedisModuleString *k
|
|||||||
return res ? REDISMODULE_OK : REDISMODULE_ERR;
|
return res ? REDISMODULE_OK : REDISMODULE_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
* Modules Info fields
|
||||||
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
int RM_InfoEndDictField(RedisModuleInfoCtx *ctx);
|
||||||
|
|
||||||
|
/* Used to start a new section, before adding any fields. the section name will
|
||||||
|
* be prefixed by "<modulename>_" and must only include A-Z,a-z,0-9.
|
||||||
|
* NULL or empty string indicates the default section (only <modulename>) is used.
|
||||||
|
* When return value is REDISMODULE_ERR, the section should and will be skipped. */
|
||||||
|
int RM_InfoAddSection(RedisModuleInfoCtx *ctx, char *name) {
|
||||||
|
sds full_name = sdsdup(ctx->module->name);
|
||||||
|
if (name != NULL && strlen(name) > 0)
|
||||||
|
full_name = sdscatfmt(full_name, "_%s", name);
|
||||||
|
|
||||||
|
/* Implicitly end dicts, instead of returning an error which is likely un checked. */
|
||||||
|
if (ctx->in_dict_field)
|
||||||
|
RM_InfoEndDictField(ctx);
|
||||||
|
|
||||||
|
/* proceed only if:
|
||||||
|
* 1) no section was requested (emit all)
|
||||||
|
* 2) the module name was requested (emit all)
|
||||||
|
* 3) this specific section was requested. */
|
||||||
|
if (ctx->requested_section) {
|
||||||
|
if (strcasecmp(ctx->requested_section, full_name) &&
|
||||||
|
strcasecmp(ctx->requested_section, ctx->module->name)) {
|
||||||
|
sdsfree(full_name);
|
||||||
|
ctx->in_section = 0;
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ctx->sections++) ctx->info = sdscat(ctx->info,"\r\n");
|
||||||
|
ctx->info = sdscatfmt(ctx->info, "# %S\r\n", full_name);
|
||||||
|
ctx->in_section = 1;
|
||||||
|
sdsfree(full_name);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Starts a dict field, similar to the ones in INFO KEYSPACE. Use normal
|
||||||
|
* RedisModule_InfoAddField* functions to add the items to this field, and
|
||||||
|
* terminate with RedisModule_InfoEndDictField. */
|
||||||
|
int RM_InfoBeginDictField(RedisModuleInfoCtx *ctx, char *name) {
|
||||||
|
if (!ctx->in_section)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
/* Implicitly end dicts, instead of returning an error which is likely un checked. */
|
||||||
|
if (ctx->in_dict_field)
|
||||||
|
RM_InfoEndDictField(ctx);
|
||||||
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
|
"%s_%s:",
|
||||||
|
ctx->module->name,
|
||||||
|
name);
|
||||||
|
ctx->in_dict_field = 1;
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ends a dict field, see RedisModule_InfoBeginDictField */
|
||||||
|
int RM_InfoEndDictField(RedisModuleInfoCtx *ctx) {
|
||||||
|
if (!ctx->in_dict_field)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
/* trim the last ',' if found. */
|
||||||
|
if (ctx->info[sdslen(ctx->info)-1]==',')
|
||||||
|
sdsIncrLen(ctx->info, -1);
|
||||||
|
ctx->info = sdscat(ctx->info, "\r\n");
|
||||||
|
ctx->in_dict_field = 0;
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used by RedisModuleInfoFunc to add info fields.
|
||||||
|
* Each field will be automatically prefixed by "<modulename>_".
|
||||||
|
* Field names or values must not include \r\n of ":" */
|
||||||
|
int RM_InfoAddFieldString(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value) {
|
||||||
|
if (!ctx->in_section)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (ctx->in_dict_field) {
|
||||||
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
|
"%s=%S,",
|
||||||
|
field,
|
||||||
|
(sds)value->ptr);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
|
"%s_%s:%S\r\n",
|
||||||
|
ctx->module->name,
|
||||||
|
field,
|
||||||
|
(sds)value->ptr);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RM_InfoAddFieldCString(RedisModuleInfoCtx *ctx, char *field, char *value) {
|
||||||
|
if (!ctx->in_section)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (ctx->in_dict_field) {
|
||||||
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
|
"%s=%s,",
|
||||||
|
field,
|
||||||
|
value);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
|
"%s_%s:%s\r\n",
|
||||||
|
ctx->module->name,
|
||||||
|
field,
|
||||||
|
value);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RM_InfoAddFieldDouble(RedisModuleInfoCtx *ctx, char *field, double value) {
|
||||||
|
if (!ctx->in_section)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (ctx->in_dict_field) {
|
||||||
|
ctx->info = sdscatprintf(ctx->info,
|
||||||
|
"%s=%.17g,",
|
||||||
|
field,
|
||||||
|
value);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
ctx->info = sdscatprintf(ctx->info,
|
||||||
|
"%s_%s:%.17g\r\n",
|
||||||
|
ctx->module->name,
|
||||||
|
field,
|
||||||
|
value);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RM_InfoAddFieldLongLong(RedisModuleInfoCtx *ctx, char *field, long long value) {
|
||||||
|
if (!ctx->in_section)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (ctx->in_dict_field) {
|
||||||
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
|
"%s=%I,",
|
||||||
|
field,
|
||||||
|
value);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
|
"%s_%s:%I\r\n",
|
||||||
|
ctx->module->name,
|
||||||
|
field,
|
||||||
|
value);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RM_InfoAddFieldULongLong(RedisModuleInfoCtx *ctx, char *field, unsigned long long value) {
|
||||||
|
if (!ctx->in_section)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
if (ctx->in_dict_field) {
|
||||||
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
|
"%s=%U,",
|
||||||
|
field,
|
||||||
|
value);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
ctx->info = sdscatfmt(ctx->info,
|
||||||
|
"%s_%s:%U\r\n",
|
||||||
|
ctx->module->name,
|
||||||
|
field,
|
||||||
|
value);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RM_RegisterInfoFunc(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) {
|
||||||
|
ctx->module->info_cb = cb;
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections) {
|
||||||
|
dictIterator *di = dictGetIterator(modules);
|
||||||
|
dictEntry *de;
|
||||||
|
|
||||||
|
while ((de = dictNext(di)) != NULL) {
|
||||||
|
struct RedisModule *module = dictGetVal(de);
|
||||||
|
if (!module->info_cb)
|
||||||
|
continue;
|
||||||
|
RedisModuleInfoCtx info_ctx = {module, section, info, sections, 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)
|
||||||
|
RM_InfoEndDictField(&info_ctx);
|
||||||
|
info = info_ctx.info;
|
||||||
|
sections = info_ctx.sections;
|
||||||
|
}
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
* Modules utility APIs
|
* Modules utility APIs
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
@ -4968,6 +5311,8 @@ int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *fi
|
|||||||
if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */
|
if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */
|
||||||
listDelNode(ctx->module->filters,ln);
|
listDelNode(ctx->module->filters,ln);
|
||||||
|
|
||||||
|
zfree(filter);
|
||||||
|
|
||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5071,6 +5416,100 @@ int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos)
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------------------
|
||||||
|
* Module fork API
|
||||||
|
* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Create a background child process with the current frozen snaphost of the
|
||||||
|
* main process where you can do some processing in the background without
|
||||||
|
* affecting / freezing the traffic and no need for threads and GIL locking.
|
||||||
|
* Note that Redis allows for only one concurrent fork.
|
||||||
|
* When the child wants to exit, it should call RedisModule_ExitFromChild.
|
||||||
|
* If the parent wants to kill the child it should call RedisModule_KillForkChild
|
||||||
|
* The done handler callback will be executed on the parent process when the
|
||||||
|
* child existed (but not when killed)
|
||||||
|
* Return: -1 on failure, on success the parent process will get a positive PID
|
||||||
|
* of the child, and the child process will get 0.
|
||||||
|
*/
|
||||||
|
int RM_Fork(RedisModuleForkDoneHandler cb, void *user_data) {
|
||||||
|
pid_t childpid;
|
||||||
|
if (hasActiveChildProcess()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
openChildInfoPipe();
|
||||||
|
if ((childpid = redisFork()) == 0) {
|
||||||
|
/* Child */
|
||||||
|
redisSetProcTitle("redis-module-fork");
|
||||||
|
} else if (childpid == -1) {
|
||||||
|
closeChildInfoPipe();
|
||||||
|
serverLog(LL_WARNING,"Can't fork for module: %s", strerror(errno));
|
||||||
|
} else {
|
||||||
|
/* Parent */
|
||||||
|
server.module_child_pid = childpid;
|
||||||
|
moduleForkInfo.done_handler = cb;
|
||||||
|
moduleForkInfo.done_handler_user_data = user_data;
|
||||||
|
serverLog(LL_NOTICE, "Module fork started pid: %d ", childpid);
|
||||||
|
}
|
||||||
|
return childpid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call from the child process when you want to terminate it.
|
||||||
|
* retcode will be provided to the done handler executed on the parent process.
|
||||||
|
*/
|
||||||
|
int RM_ExitFromChild(int retcode) {
|
||||||
|
sendChildCOWInfo(CHILD_INFO_TYPE_MODULE, "Module fork");
|
||||||
|
exitFromChild(retcode);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Kill the active module forked child, if there is one active and the
|
||||||
|
* pid matches, and returns C_OK. Otherwise if there is no active module
|
||||||
|
* child or the pid does not match, return C_ERR without doing anything. */
|
||||||
|
int TerminateModuleForkChild(int child_pid, int wait) {
|
||||||
|
/* Module child should be active and pid should match. */
|
||||||
|
if (server.module_child_pid == -1 ||
|
||||||
|
server.module_child_pid != child_pid) return C_ERR;
|
||||||
|
|
||||||
|
int statloc;
|
||||||
|
serverLog(LL_NOTICE,"Killing running module fork child: %ld",
|
||||||
|
(long) server.module_child_pid);
|
||||||
|
if (kill(server.module_child_pid,SIGUSR1) != -1 && wait) {
|
||||||
|
while(wait4(server.module_child_pid,&statloc,0,NULL) !=
|
||||||
|
server.module_child_pid);
|
||||||
|
}
|
||||||
|
/* Reset the buffer accumulating changes while the child saves. */
|
||||||
|
server.module_child_pid = -1;
|
||||||
|
moduleForkInfo.done_handler = NULL;
|
||||||
|
moduleForkInfo.done_handler_user_data = NULL;
|
||||||
|
closeChildInfoPipe();
|
||||||
|
updateDictResizePolicy();
|
||||||
|
return C_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can be used to kill the forked child process from the parent process.
|
||||||
|
* child_pid whould be the return value of RedisModule_Fork. */
|
||||||
|
int RM_KillForkChild(int child_pid) {
|
||||||
|
/* Kill module child, wait for child exit. */
|
||||||
|
if (TerminateModuleForkChild(child_pid,1) == C_OK)
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
else
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleForkDoneHandler(int exitcode, int bysignal) {
|
||||||
|
serverLog(LL_NOTICE,
|
||||||
|
"Module fork exited pid: %d, retcode: %d, bysignal: %d",
|
||||||
|
server.module_child_pid, exitcode, bysignal);
|
||||||
|
if (moduleForkInfo.done_handler) {
|
||||||
|
moduleForkInfo.done_handler(exitcode, bysignal,
|
||||||
|
moduleForkInfo.done_handler_user_data);
|
||||||
|
}
|
||||||
|
server.module_child_pid = -1;
|
||||||
|
moduleForkInfo.done_handler = NULL;
|
||||||
|
moduleForkInfo.done_handler_user_data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------------------------
|
/* --------------------------------------------------------------------------
|
||||||
* Modules API internals
|
* Modules API internals
|
||||||
* -------------------------------------------------------------------------- */
|
* -------------------------------------------------------------------------- */
|
||||||
@ -5170,6 +5609,8 @@ void moduleLoadFromQueue(void) {
|
|||||||
void moduleFreeModuleStructure(struct RedisModule *module) {
|
void moduleFreeModuleStructure(struct RedisModule *module) {
|
||||||
listRelease(module->types);
|
listRelease(module->types);
|
||||||
listRelease(module->filters);
|
listRelease(module->filters);
|
||||||
|
listRelease(module->usedby);
|
||||||
|
listRelease(module->using);
|
||||||
sdsfree(module->name);
|
sdsfree(module->name);
|
||||||
zfree(module);
|
zfree(module);
|
||||||
}
|
}
|
||||||
@ -5258,6 +5699,23 @@ int moduleUnload(sds name) {
|
|||||||
return REDISMODULE_ERR;
|
return REDISMODULE_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Give module a chance to clean up. */
|
||||||
|
int (*onunload)(void *);
|
||||||
|
onunload = (int (*)(void *))(unsigned long) dlsym(module->handle, "RedisModule_OnUnload");
|
||||||
|
if (onunload) {
|
||||||
|
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||||
|
ctx.module = module;
|
||||||
|
ctx.client = moduleFreeContextReusedClient;
|
||||||
|
int unload_status = onunload((void*)&ctx);
|
||||||
|
moduleFreeContext(&ctx);
|
||||||
|
|
||||||
|
if (unload_status == REDISMODULE_ERR) {
|
||||||
|
serverLog(LL_WARNING, "Module %s OnUnload failed. Unload canceled.", name);
|
||||||
|
errno = ECANCELED;
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
moduleUnregisterCommands(module);
|
moduleUnregisterCommands(module);
|
||||||
moduleUnregisterSharedAPI(module);
|
moduleUnregisterSharedAPI(module);
|
||||||
moduleUnregisterUsedAPI(module);
|
moduleUnregisterUsedAPI(module);
|
||||||
@ -5304,6 +5762,62 @@ void addReplyLoadedModules(client *c) {
|
|||||||
dictReleaseIterator(di);
|
dictReleaseIterator(di);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Helper for genModulesInfoString(): given a list of modules, return
|
||||||
|
* am SDS string in the form "[modulename|modulename2|...]" */
|
||||||
|
sds genModulesInfoStringRenderModulesList(list *l) {
|
||||||
|
listIter li;
|
||||||
|
listNode *ln;
|
||||||
|
listRewind(l,&li);
|
||||||
|
sds output = sdsnew("[");
|
||||||
|
while((ln = listNext(&li))) {
|
||||||
|
RedisModule *module = ln->value;
|
||||||
|
output = sdscat(output,module->name);
|
||||||
|
}
|
||||||
|
output = sdstrim(output,"|");
|
||||||
|
output = sdscat(output,"]");
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helper for genModulesInfoString(): render module options as an SDS string. */
|
||||||
|
sds genModulesInfoStringRenderModuleOptions(struct RedisModule *module) {
|
||||||
|
sds output = sdsnew("[");
|
||||||
|
if (module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS)
|
||||||
|
output = sdscat(output,"handle-io-errors|");
|
||||||
|
output = sdstrim(output,"|");
|
||||||
|
output = sdscat(output,"]");
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Helper function for the INFO command: adds loaded modules as to info's
|
||||||
|
* output.
|
||||||
|
*
|
||||||
|
* After the call, the passed sds info string is no longer valid and all the
|
||||||
|
* references must be substituted with the new pointer returned by the call. */
|
||||||
|
sds genModulesInfoString(sds info) {
|
||||||
|
dictIterator *di = dictGetIterator(modules);
|
||||||
|
dictEntry *de;
|
||||||
|
|
||||||
|
while ((de = dictNext(di)) != NULL) {
|
||||||
|
sds name = dictGetKey(de);
|
||||||
|
struct RedisModule *module = dictGetVal(de);
|
||||||
|
|
||||||
|
sds usedby = genModulesInfoStringRenderModulesList(module->usedby);
|
||||||
|
sds using = genModulesInfoStringRenderModulesList(module->using);
|
||||||
|
sds options = genModulesInfoStringRenderModuleOptions(module);
|
||||||
|
info = sdscatfmt(info,
|
||||||
|
"module:name=%S,ver=%i,api=%i,filters=%i,"
|
||||||
|
"usedby=%S,using=%S,options=%S\r\n",
|
||||||
|
name, module->ver, module->apiver,
|
||||||
|
(int)listLength(module->filters), usedby, using, options);
|
||||||
|
sdsfree(usedby);
|
||||||
|
sdsfree(using);
|
||||||
|
sdsfree(options);
|
||||||
|
}
|
||||||
|
dictReleaseIterator(di);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
/* Redis MODULE command.
|
/* Redis MODULE command.
|
||||||
*
|
*
|
||||||
* MODULE LOAD <path> [args...] */
|
* MODULE LOAD <path> [args...] */
|
||||||
@ -5452,6 +5966,8 @@ void moduleRegisterCoreAPI(void) {
|
|||||||
REGISTER_API(ModuleTypeSetValue);
|
REGISTER_API(ModuleTypeSetValue);
|
||||||
REGISTER_API(ModuleTypeGetType);
|
REGISTER_API(ModuleTypeGetType);
|
||||||
REGISTER_API(ModuleTypeGetValue);
|
REGISTER_API(ModuleTypeGetValue);
|
||||||
|
REGISTER_API(IsIOError);
|
||||||
|
REGISTER_API(SetModuleOptions);
|
||||||
REGISTER_API(SaveUnsigned);
|
REGISTER_API(SaveUnsigned);
|
||||||
REGISTER_API(LoadUnsigned);
|
REGISTER_API(LoadUnsigned);
|
||||||
REGISTER_API(SaveSigned);
|
REGISTER_API(SaveSigned);
|
||||||
@ -5467,6 +5983,7 @@ void moduleRegisterCoreAPI(void) {
|
|||||||
REGISTER_API(EmitAOF);
|
REGISTER_API(EmitAOF);
|
||||||
REGISTER_API(Log);
|
REGISTER_API(Log);
|
||||||
REGISTER_API(LogIOError);
|
REGISTER_API(LogIOError);
|
||||||
|
REGISTER_API(_Assert);
|
||||||
REGISTER_API(StringAppendBuffer);
|
REGISTER_API(StringAppendBuffer);
|
||||||
REGISTER_API(RetainString);
|
REGISTER_API(RetainString);
|
||||||
REGISTER_API(StringCompare);
|
REGISTER_API(StringCompare);
|
||||||
@ -5534,4 +6051,16 @@ void moduleRegisterCoreAPI(void) {
|
|||||||
REGISTER_API(CommandFilterArgInsert);
|
REGISTER_API(CommandFilterArgInsert);
|
||||||
REGISTER_API(CommandFilterArgReplace);
|
REGISTER_API(CommandFilterArgReplace);
|
||||||
REGISTER_API(CommandFilterArgDelete);
|
REGISTER_API(CommandFilterArgDelete);
|
||||||
|
REGISTER_API(Fork);
|
||||||
|
REGISTER_API(ExitFromChild);
|
||||||
|
REGISTER_API(KillForkChild);
|
||||||
|
REGISTER_API(RegisterInfoFunc);
|
||||||
|
REGISTER_API(InfoAddSection);
|
||||||
|
REGISTER_API(InfoBeginDictField);
|
||||||
|
REGISTER_API(InfoEndDictField);
|
||||||
|
REGISTER_API(InfoAddFieldString);
|
||||||
|
REGISTER_API(InfoAddFieldCString);
|
||||||
|
REGISTER_API(InfoAddFieldDouble);
|
||||||
|
REGISTER_API(InfoAddFieldLongLong);
|
||||||
|
REGISTER_API(InfoAddFieldULongLong);
|
||||||
}
|
}
|
||||||
|
@ -2046,7 +2046,7 @@ NULL
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sds o = getAllClientsInfoString(type);
|
sds o = getAllClientsInfoString(type);
|
||||||
addReplyBulkCBuffer(c,o,sdslen(o));
|
addReplyVerbatim(c,o,sdslen(o),"txt");
|
||||||
sdsfree(o);
|
sdsfree(o);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
|
||||||
/* CLIENT REPLY ON|OFF|SKIP */
|
/* CLIENT REPLY ON|OFF|SKIP */
|
||||||
|
22
src/object.c
22
src/object.c
@ -1440,30 +1440,20 @@ NULL
|
|||||||
#if defined(USE_JEMALLOC)
|
#if defined(USE_JEMALLOC)
|
||||||
sds info = sdsempty();
|
sds info = sdsempty();
|
||||||
je_malloc_stats_print(inputCatSds, &info, NULL);
|
je_malloc_stats_print(inputCatSds, &info, NULL);
|
||||||
addReplyBulkSds(c, info);
|
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||||
|
sdsfree(info);
|
||||||
#else
|
#else
|
||||||
addReplyBulkCString(c,"Stats not supported for the current allocator");
|
addReplyBulkCString(c,"Stats not supported for the current allocator");
|
||||||
#endif
|
#endif
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
|
||||||
sds report = getMemoryDoctorReport();
|
sds report = getMemoryDoctorReport();
|
||||||
addReplyBulkSds(c,report);
|
addReplyVerbatim(c,report,sdslen(report),"txt");
|
||||||
|
sdsfree(report);
|
||||||
} else if (!strcasecmp(c->argv[1]->ptr,"purge") && c->argc == 2) {
|
} else if (!strcasecmp(c->argv[1]->ptr,"purge") && c->argc == 2) {
|
||||||
#if defined(USE_JEMALLOC)
|
if (jemalloc_purge() == 0)
|
||||||
char tmp[32];
|
|
||||||
unsigned narenas = 0;
|
|
||||||
size_t sz = sizeof(unsigned);
|
|
||||||
if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
|
|
||||||
sprintf(tmp, "arena.%d.purge", narenas);
|
|
||||||
if (!je_mallctl(tmp, NULL, 0, NULL, 0)) {
|
|
||||||
addReply(c, shared.ok);
|
addReply(c, shared.ok);
|
||||||
return;
|
else
|
||||||
}
|
|
||||||
}
|
|
||||||
addReplyError(c, "Error purging dirty pages");
|
addReplyError(c, "Error purging dirty pages");
|
||||||
#else
|
|
||||||
addReply(c, shared.ok);
|
|
||||||
/* Nothing to do for other allocators. */
|
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr);
|
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr);
|
||||||
}
|
}
|
||||||
|
@ -1791,7 +1791,8 @@ int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key
|
|||||||
if (eq && key_len == iter->key_len) return 1;
|
if (eq && key_len == iter->key_len) return 1;
|
||||||
else if (lt) return iter->key_len < key_len;
|
else if (lt) return iter->key_len < key_len;
|
||||||
else if (gt) return iter->key_len > key_len;
|
else if (gt) return iter->key_len > key_len;
|
||||||
} if (cmp > 0) {
|
return 0;
|
||||||
|
} else if (cmp > 0) {
|
||||||
return gt ? 1 : 0;
|
return gt ? 1 : 0;
|
||||||
} else /* (cmp < 0) */ {
|
} else /* (cmp < 0) */ {
|
||||||
return lt ? 1 : 0;
|
return lt ? 1 : 0;
|
||||||
|
50
src/rdb.c
50
src/rdb.c
@ -260,7 +260,7 @@ int rdbEncodeInteger(long long value, unsigned char *enc) {
|
|||||||
|
|
||||||
/* Loads an integer-encoded object with the specified encoding type "enctype".
|
/* Loads an integer-encoded object with the specified encoding type "enctype".
|
||||||
* The returned value changes according to the flags, see
|
* The returned value changes according to the flags, see
|
||||||
* rdbGenerincLoadStringObject() for more info. */
|
* rdbGenericLoadStringObject() for more info. */
|
||||||
void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
|
||||||
int plain = flags & RDB_LOAD_PLAIN;
|
int plain = flags & RDB_LOAD_PLAIN;
|
||||||
int sds = flags & RDB_LOAD_SDS;
|
int sds = flags & RDB_LOAD_SDS;
|
||||||
@ -1335,40 +1335,25 @@ werr:
|
|||||||
|
|
||||||
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
||||||
pid_t childpid;
|
pid_t childpid;
|
||||||
long long start;
|
|
||||||
|
|
||||||
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
|
if (hasActiveChildProcess()) return C_ERR;
|
||||||
|
|
||||||
server.dirty_before_bgsave = server.dirty;
|
server.dirty_before_bgsave = server.dirty;
|
||||||
server.lastbgsave_try = time(NULL);
|
server.lastbgsave_try = time(NULL);
|
||||||
openChildInfoPipe();
|
openChildInfoPipe();
|
||||||
|
|
||||||
start = ustime();
|
if ((childpid = redisFork()) == 0) {
|
||||||
if ((childpid = fork()) == 0) {
|
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
/* Child */
|
/* Child */
|
||||||
closeListeningSockets(0);
|
|
||||||
redisSetProcTitle("redis-rdb-bgsave");
|
redisSetProcTitle("redis-rdb-bgsave");
|
||||||
retval = rdbSave(filename,rsi);
|
retval = rdbSave(filename,rsi);
|
||||||
if (retval == C_OK) {
|
if (retval == C_OK) {
|
||||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
|
||||||
|
|
||||||
if (private_dirty) {
|
|
||||||
serverLog(LL_NOTICE,
|
|
||||||
"RDB: %zu MB of memory used by copy-on-write",
|
|
||||||
private_dirty/(1024*1024));
|
|
||||||
}
|
|
||||||
|
|
||||||
server.child_info_data.cow_size = private_dirty;
|
|
||||||
sendChildInfo(CHILD_INFO_TYPE_RDB);
|
|
||||||
}
|
}
|
||||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
server.stat_fork_time = ustime()-start;
|
|
||||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
|
||||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
|
||||||
if (childpid == -1) {
|
if (childpid == -1) {
|
||||||
closeChildInfoPipe();
|
closeChildInfoPipe();
|
||||||
server.lastbgsave_status = C_ERR;
|
server.lastbgsave_status = C_ERR;
|
||||||
@ -1380,7 +1365,6 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
|||||||
server.rdb_save_time_start = time(NULL);
|
server.rdb_save_time_start = time(NULL);
|
||||||
server.rdb_child_pid = childpid;
|
server.rdb_child_pid = childpid;
|
||||||
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
|
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
|
||||||
updateDictResizePolicy();
|
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
return C_OK; /* unreached */
|
return C_OK; /* unreached */
|
||||||
@ -2355,10 +2339,9 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
listNode *ln;
|
listNode *ln;
|
||||||
listIter li;
|
listIter li;
|
||||||
pid_t childpid;
|
pid_t childpid;
|
||||||
long long start;
|
|
||||||
int pipefds[2];
|
int pipefds[2];
|
||||||
|
|
||||||
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
|
if (hasActiveChildProcess()) return C_ERR;
|
||||||
|
|
||||||
/* Even if the previous fork child exited, don't start a new one until we
|
/* Even if the previous fork child exited, don't start a new one until we
|
||||||
* drained the pipe. */
|
* drained the pipe. */
|
||||||
@ -2389,15 +2372,13 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
|
|
||||||
/* Create the child process. */
|
/* Create the child process. */
|
||||||
openChildInfoPipe();
|
openChildInfoPipe();
|
||||||
start = ustime();
|
if ((childpid = redisFork()) == 0) {
|
||||||
if ((childpid = fork()) == 0) {
|
|
||||||
/* Child */
|
/* Child */
|
||||||
int retval;
|
int retval;
|
||||||
rio rdb;
|
rio rdb;
|
||||||
|
|
||||||
rioInitWithFd(&rdb,server.rdb_pipe_write);
|
rioInitWithFd(&rdb,server.rdb_pipe_write);
|
||||||
|
|
||||||
closeListeningSockets(0);
|
|
||||||
redisSetProcTitle("redis-rdb-to-slaves");
|
redisSetProcTitle("redis-rdb-to-slaves");
|
||||||
|
|
||||||
retval = rdbSaveRioWithEOFMark(&rdb,NULL,rsi);
|
retval = rdbSaveRioWithEOFMark(&rdb,NULL,rsi);
|
||||||
@ -2405,17 +2386,9 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
retval = C_ERR;
|
retval = C_ERR;
|
||||||
|
|
||||||
if (retval == C_OK) {
|
if (retval == C_OK) {
|
||||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
|
||||||
|
|
||||||
if (private_dirty) {
|
|
||||||
serverLog(LL_NOTICE,
|
|
||||||
"RDB: %zu MB of memory used by copy-on-write",
|
|
||||||
private_dirty/(1024*1024));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
server.child_info_data.cow_size = private_dirty;
|
|
||||||
sendChildInfo(CHILD_INFO_TYPE_RDB);
|
|
||||||
}
|
|
||||||
rioFreeFd(&rdb);
|
rioFreeFd(&rdb);
|
||||||
close(server.rdb_pipe_write); /* wake up the reader, tell it we're done. */
|
close(server.rdb_pipe_write); /* wake up the reader, tell it we're done. */
|
||||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||||
@ -2443,10 +2416,6 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
server.rdb_pipe_numconns_writing = 0;
|
server.rdb_pipe_numconns_writing = 0;
|
||||||
closeChildInfoPipe();
|
closeChildInfoPipe();
|
||||||
} else {
|
} else {
|
||||||
server.stat_fork_time = ustime()-start;
|
|
||||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
|
||||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
|
||||||
|
|
||||||
serverLog(LL_NOTICE,"Background RDB transfer started by pid %d",
|
serverLog(LL_NOTICE,"Background RDB transfer started by pid %d",
|
||||||
childpid);
|
childpid);
|
||||||
server.rdb_save_time_start = time(NULL);
|
server.rdb_save_time_start = time(NULL);
|
||||||
@ -2456,7 +2425,6 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
|||||||
if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
|
if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
|
||||||
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
|
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
|
||||||
}
|
}
|
||||||
updateDictResizePolicy();
|
|
||||||
}
|
}
|
||||||
return (childpid == -1) ? C_ERR : C_OK;
|
return (childpid == -1) ? C_ERR : C_OK;
|
||||||
}
|
}
|
||||||
@ -2497,13 +2465,13 @@ void bgsaveCommand(client *c) {
|
|||||||
|
|
||||||
if (server.rdb_child_pid != -1) {
|
if (server.rdb_child_pid != -1) {
|
||||||
addReplyError(c,"Background save already in progress");
|
addReplyError(c,"Background save already in progress");
|
||||||
} else if (server.aof_child_pid != -1) {
|
} else if (hasActiveChildProcess()) {
|
||||||
if (schedule) {
|
if (schedule) {
|
||||||
server.rdb_bgsave_scheduled = 1;
|
server.rdb_bgsave_scheduled = 1;
|
||||||
addReplyStatus(c,"Background saving scheduled");
|
addReplyStatus(c,"Background saving scheduled");
|
||||||
} else {
|
} else {
|
||||||
addReplyError(c,
|
addReplyError(c,
|
||||||
"An AOF log rewriting in progress: can't BGSAVE right now. "
|
"Another child process is active (AOF?): can't BGSAVE right now. "
|
||||||
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
||||||
"possible.");
|
"possible.");
|
||||||
}
|
}
|
||||||
|
@ -228,6 +228,7 @@ static struct config {
|
|||||||
int hotkeys;
|
int hotkeys;
|
||||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||||
char *auth;
|
char *auth;
|
||||||
|
char *user;
|
||||||
int output; /* output mode, see OUTPUT_* defines */
|
int output; /* output mode, see OUTPUT_* defines */
|
||||||
sds mb_delim;
|
sds mb_delim;
|
||||||
char prompt[128];
|
char prompt[128];
|
||||||
@ -240,6 +241,7 @@ static struct config {
|
|||||||
int verbose;
|
int verbose;
|
||||||
clusterManagerCommand cluster_manager_command;
|
clusterManagerCommand cluster_manager_command;
|
||||||
int no_auth_warning;
|
int no_auth_warning;
|
||||||
|
int resp3;
|
||||||
} config;
|
} config;
|
||||||
|
|
||||||
/* User preferences. */
|
/* User preferences. */
|
||||||
@ -738,8 +740,13 @@ static int cliAuth(void) {
|
|||||||
redisReply *reply;
|
redisReply *reply;
|
||||||
if (config.auth == NULL) return REDIS_OK;
|
if (config.auth == NULL) return REDIS_OK;
|
||||||
|
|
||||||
|
if (config.user == NULL)
|
||||||
reply = redisCommand(context,"AUTH %s",config.auth);
|
reply = redisCommand(context,"AUTH %s",config.auth);
|
||||||
|
else
|
||||||
|
reply = redisCommand(context,"AUTH %s %s",config.user,config.auth);
|
||||||
if (reply != NULL) {
|
if (reply != NULL) {
|
||||||
|
if (reply->type == REDIS_REPLY_ERROR)
|
||||||
|
fprintf(stderr,"Warning: AUTH failed\n");
|
||||||
freeReplyObject(reply);
|
freeReplyObject(reply);
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
@ -826,6 +833,21 @@ error:
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Select RESP3 mode if redis-cli was started with the -3 option. */
|
||||||
|
static int cliSwitchProto(void) {
|
||||||
|
redisReply *reply;
|
||||||
|
if (config.resp3 == 0) return REDIS_OK;
|
||||||
|
|
||||||
|
reply = redisCommand(context,"HELLO 3");
|
||||||
|
if (reply != NULL) {
|
||||||
|
int result = REDIS_OK;
|
||||||
|
if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
|
||||||
|
freeReplyObject(reply);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return REDIS_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
/* Connect to the server. It is possible to pass certain flags to the function:
|
/* Connect to the server. It is possible to pass certain flags to the function:
|
||||||
* CC_FORCE: The connection is performed even if there is already
|
* CC_FORCE: The connection is performed even if there is already
|
||||||
* a connected socket.
|
* a connected socket.
|
||||||
@ -874,11 +896,13 @@ static int cliConnect(int flags) {
|
|||||||
* errors. */
|
* errors. */
|
||||||
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
||||||
|
|
||||||
/* Do AUTH and select the right DB. */
|
/* Do AUTH, select the right DB, switch to RESP3 if needed. */
|
||||||
if (cliAuth() != REDIS_OK)
|
if (cliAuth() != REDIS_OK)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
if (cliSelect() != REDIS_OK)
|
if (cliSelect() != REDIS_OK)
|
||||||
return REDIS_ERR;
|
return REDIS_ERR;
|
||||||
|
if (cliSwitchProto() != REDIS_OK)
|
||||||
|
return REDIS_ERR;
|
||||||
}
|
}
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
@ -905,10 +929,17 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
|||||||
out = sdscatprintf(out,"(double) %s\n",r->str);
|
out = sdscatprintf(out,"(double) %s\n",r->str);
|
||||||
break;
|
break;
|
||||||
case REDIS_REPLY_STRING:
|
case REDIS_REPLY_STRING:
|
||||||
|
case REDIS_REPLY_VERB:
|
||||||
/* If you are producing output for the standard output we want
|
/* If you are producing output for the standard output we want
|
||||||
* a more interesting output with quoted characters and so forth */
|
* a more interesting output with quoted characters and so forth,
|
||||||
|
* unless it's a verbatim string type. */
|
||||||
|
if (r->type == REDIS_REPLY_STRING) {
|
||||||
out = sdscatrepr(out,r->str,r->len);
|
out = sdscatrepr(out,r->str,r->len);
|
||||||
out = sdscat(out,"\n");
|
out = sdscat(out,"\n");
|
||||||
|
} else {
|
||||||
|
out = sdscatlen(out,r->str,r->len);
|
||||||
|
out = sdscat(out,"\n");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case REDIS_REPLY_NIL:
|
case REDIS_REPLY_NIL:
|
||||||
out = sdscat(out,"(nil)\n");
|
out = sdscat(out,"(nil)\n");
|
||||||
@ -1047,6 +1078,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
|||||||
break;
|
break;
|
||||||
case REDIS_REPLY_STATUS:
|
case REDIS_REPLY_STATUS:
|
||||||
case REDIS_REPLY_STRING:
|
case REDIS_REPLY_STRING:
|
||||||
|
case REDIS_REPLY_VERB:
|
||||||
if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
|
if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
|
||||||
/* The Lua debugger replies with arrays of simple (status)
|
/* The Lua debugger replies with arrays of simple (status)
|
||||||
* strings. We colorize the output for more fun if this
|
* strings. We colorize the output for more fun if this
|
||||||
@ -1066,9 +1098,15 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
|||||||
out = sdscatlen(out,r->str,r->len);
|
out = sdscatlen(out,r->str,r->len);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case REDIS_REPLY_BOOL:
|
||||||
|
out = sdscat(out,r->integer ? "(true)" : "(false)");
|
||||||
|
break;
|
||||||
case REDIS_REPLY_INTEGER:
|
case REDIS_REPLY_INTEGER:
|
||||||
out = sdscatprintf(out,"%lld",r->integer);
|
out = sdscatprintf(out,"%lld",r->integer);
|
||||||
break;
|
break;
|
||||||
|
case REDIS_REPLY_DOUBLE:
|
||||||
|
out = sdscatprintf(out,"%s",r->str);
|
||||||
|
break;
|
||||||
case REDIS_REPLY_ARRAY:
|
case REDIS_REPLY_ARRAY:
|
||||||
for (i = 0; i < r->elements; i++) {
|
for (i = 0; i < r->elements; i++) {
|
||||||
if (i > 0) out = sdscat(out,config.mb_delim);
|
if (i > 0) out = sdscat(out,config.mb_delim);
|
||||||
@ -1077,6 +1115,19 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
|||||||
sdsfree(tmp);
|
sdsfree(tmp);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case REDIS_REPLY_MAP:
|
||||||
|
for (i = 0; i < r->elements; i += 2) {
|
||||||
|
if (i > 0) out = sdscat(out,config.mb_delim);
|
||||||
|
tmp = cliFormatReplyRaw(r->element[i]);
|
||||||
|
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||||
|
sdsfree(tmp);
|
||||||
|
|
||||||
|
out = sdscatlen(out," ",1);
|
||||||
|
tmp = cliFormatReplyRaw(r->element[i+1]);
|
||||||
|
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||||
|
sdsfree(tmp);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
fprintf(stderr,"Unknown reply type: %d\n", r->type);
|
fprintf(stderr,"Unknown reply type: %d\n", r->type);
|
||||||
exit(1);
|
exit(1);
|
||||||
@ -1099,13 +1150,21 @@ static sds cliFormatReplyCSV(redisReply *r) {
|
|||||||
case REDIS_REPLY_INTEGER:
|
case REDIS_REPLY_INTEGER:
|
||||||
out = sdscatprintf(out,"%lld",r->integer);
|
out = sdscatprintf(out,"%lld",r->integer);
|
||||||
break;
|
break;
|
||||||
|
case REDIS_REPLY_DOUBLE:
|
||||||
|
out = sdscatprintf(out,"%s",r->str);
|
||||||
|
break;
|
||||||
case REDIS_REPLY_STRING:
|
case REDIS_REPLY_STRING:
|
||||||
|
case REDIS_REPLY_VERB:
|
||||||
out = sdscatrepr(out,r->str,r->len);
|
out = sdscatrepr(out,r->str,r->len);
|
||||||
break;
|
break;
|
||||||
case REDIS_REPLY_NIL:
|
case REDIS_REPLY_NIL:
|
||||||
out = sdscat(out,"NIL");
|
out = sdscat(out,"NULL");
|
||||||
|
break;
|
||||||
|
case REDIS_REPLY_BOOL:
|
||||||
|
out = sdscat(out,r->integer ? "true" : "false");
|
||||||
break;
|
break;
|
||||||
case REDIS_REPLY_ARRAY:
|
case REDIS_REPLY_ARRAY:
|
||||||
|
case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
|
||||||
for (i = 0; i < r->elements; i++) {
|
for (i = 0; i < r->elements; i++) {
|
||||||
sds tmp = cliFormatReplyCSV(r->element[i]);
|
sds tmp = cliFormatReplyCSV(r->element[i]);
|
||||||
out = sdscatlen(out,tmp,sdslen(tmp));
|
out = sdscatlen(out,tmp,sdslen(tmp));
|
||||||
@ -1299,7 +1358,8 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
|||||||
if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) {
|
if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) {
|
||||||
config.dbnum = atoi(argv[1]);
|
config.dbnum = atoi(argv[1]);
|
||||||
cliRefreshPrompt();
|
cliRefreshPrompt();
|
||||||
} else if (!strcasecmp(command,"auth") && argc == 2) {
|
} else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3))
|
||||||
|
{
|
||||||
cliSelect();
|
cliSelect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1389,8 +1449,12 @@ static int parseOptions(int argc, char **argv) {
|
|||||||
config.dbnum = atoi(argv[++i]);
|
config.dbnum = atoi(argv[++i]);
|
||||||
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
||||||
config.no_auth_warning = 1;
|
config.no_auth_warning = 1;
|
||||||
} else if (!strcmp(argv[i],"-a") && !lastarg) {
|
} else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
|
||||||
|
&& !lastarg)
|
||||||
|
{
|
||||||
config.auth = argv[++i];
|
config.auth = argv[++i];
|
||||||
|
} else if (!strcmp(argv[i],"--user") && !lastarg) {
|
||||||
|
config.user = argv[++i];
|
||||||
} else if (!strcmp(argv[i],"-u") && !lastarg) {
|
} else if (!strcmp(argv[i],"-u") && !lastarg) {
|
||||||
parseRedisUri(argv[++i]);
|
parseRedisUri(argv[++i]);
|
||||||
} else if (!strcmp(argv[i],"--raw")) {
|
} else if (!strcmp(argv[i],"--raw")) {
|
||||||
@ -1546,6 +1610,8 @@ static int parseOptions(int argc, char **argv) {
|
|||||||
printf("redis-cli %s\n", version);
|
printf("redis-cli %s\n", version);
|
||||||
sdsfree(version);
|
sdsfree(version);
|
||||||
exit(0);
|
exit(0);
|
||||||
|
} else if (!strcmp(argv[i],"-3")) {
|
||||||
|
config.resp3 = 1;
|
||||||
} else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
|
} else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
|
||||||
if (config.cluster_manager_command.argc == 0) {
|
if (config.cluster_manager_command.argc == 0) {
|
||||||
int j = i + 1;
|
int j = i + 1;
|
||||||
@ -1621,11 +1687,14 @@ static void usage(void) {
|
|||||||
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
|
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
|
||||||
" variable to pass this password more safely\n"
|
" variable to pass this password more safely\n"
|
||||||
" (if both are used, this argument takes predecence).\n"
|
" (if both are used, this argument takes predecence).\n"
|
||||||
|
" -user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
|
||||||
|
" -pass <password> Alias of -a for consistency with the new --user option.\n"
|
||||||
" -u <uri> Server URI.\n"
|
" -u <uri> Server URI.\n"
|
||||||
" -r <repeat> Execute specified command N times.\n"
|
" -r <repeat> Execute specified command N times.\n"
|
||||||
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
|
||||||
" It is possible to specify sub-second times like -i 0.1.\n"
|
" It is possible to specify sub-second times like -i 0.1.\n"
|
||||||
" -n <db> Database number.\n"
|
" -n <db> Database number.\n"
|
||||||
|
" -3 Start session in RESP3 protocol mode.\n"
|
||||||
" -x Read last argument from STDIN.\n"
|
" -x Read last argument from STDIN.\n"
|
||||||
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
|
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
|
||||||
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
|
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
|
||||||
@ -1649,7 +1718,9 @@ static void usage(void) {
|
|||||||
" --csv is specified, or if you redirect the output to a non\n"
|
" --csv is specified, or if you redirect the output to a non\n"
|
||||||
" TTY, it samples the latency for 1 second (you can use\n"
|
" TTY, it samples the latency for 1 second (you can use\n"
|
||||||
" -i to change the interval), then produces a single output\n"
|
" -i to change the interval), then produces a single output\n"
|
||||||
" and exits.\n"
|
" and exits.\n",version);
|
||||||
|
|
||||||
|
fprintf(stderr,
|
||||||
" --latency-history Like --latency but tracking latency changes over time.\n"
|
" --latency-history Like --latency but tracking latency changes over time.\n"
|
||||||
" Default time interval is 15 sec. Change it using -i.\n"
|
" Default time interval is 15 sec. Change it using -i.\n"
|
||||||
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
|
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
|
||||||
@ -1661,7 +1732,7 @@ static void usage(void) {
|
|||||||
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
|
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
|
||||||
" no reply is received within <n> seconds.\n"
|
" no reply is received within <n> seconds.\n"
|
||||||
" Default timeout: %d. Use 0 to wait forever.\n",
|
" Default timeout: %d. Use 0 to wait forever.\n",
|
||||||
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
|
" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
|
||||||
" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
|
" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
|
||||||
@ -2476,7 +2547,12 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) {
|
|||||||
* errors. */
|
* errors. */
|
||||||
anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
||||||
if (config.auth) {
|
if (config.auth) {
|
||||||
redisReply *reply = redisCommand(node->context,"AUTH %s",config.auth);
|
redisReply *reply;
|
||||||
|
if (config.user == NULL)
|
||||||
|
reply = redisCommand(node->context,"AUTH %s", config.auth);
|
||||||
|
else
|
||||||
|
reply = redisCommand(node->context,"AUTH %s %s",
|
||||||
|
config.user,config.auth);
|
||||||
int ok = clusterManagerCheckRedisReply(node, reply, NULL);
|
int ok = clusterManagerCheckRedisReply(node, reply, NULL);
|
||||||
if (reply != NULL) freeReplyObject(reply);
|
if (reply != NULL) freeReplyObject(reply);
|
||||||
if (!ok) return 0;
|
if (!ok) return 0;
|
||||||
@ -3348,7 +3424,7 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
|
|||||||
redisReply *entry = reply->element[i];
|
redisReply *entry = reply->element[i];
|
||||||
size_t idx = i + offset;
|
size_t idx = i + offset;
|
||||||
assert(entry->type == REDIS_REPLY_STRING);
|
assert(entry->type == REDIS_REPLY_STRING);
|
||||||
argv[idx] = (char *) sdsnew(entry->str);
|
argv[idx] = (char *) sdsnewlen(entry->str, entry->len);
|
||||||
argv_len[idx] = entry->len;
|
argv_len[idx] = entry->len;
|
||||||
if (dots) dots[i] = '.';
|
if (dots) dots[i] = '.';
|
||||||
}
|
}
|
||||||
@ -7804,6 +7880,7 @@ int main(int argc, char **argv) {
|
|||||||
config.hotkeys = 0;
|
config.hotkeys = 0;
|
||||||
config.stdinarg = 0;
|
config.stdinarg = 0;
|
||||||
config.auth = NULL;
|
config.auth = NULL;
|
||||||
|
config.user = NULL;
|
||||||
config.eval = NULL;
|
config.eval = NULL;
|
||||||
config.eval_ldb = 0;
|
config.eval_ldb = 0;
|
||||||
config.eval_ldb_end = 0;
|
config.eval_ldb_end = 0;
|
||||||
|
@ -144,6 +144,9 @@ typedef uint64_t RedisModuleTimerID;
|
|||||||
/* Do filter RedisModule_Call() commands initiated by module itself. */
|
/* Do filter RedisModule_Call() commands initiated by module itself. */
|
||||||
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
|
#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
|
||||||
|
|
||||||
|
/* Declare that the module can handle errors with RedisModule_SetModuleOptions. */
|
||||||
|
#define REDISMODULE_OPTIONS_HANDLE_IO_ERRORS (1<<0)
|
||||||
|
|
||||||
/* ------------------------- End of common defines ------------------------ */
|
/* ------------------------- End of common defines ------------------------ */
|
||||||
|
|
||||||
#ifndef REDISMODULE_CORE
|
#ifndef REDISMODULE_CORE
|
||||||
@ -164,6 +167,7 @@ typedef struct RedisModuleDict RedisModuleDict;
|
|||||||
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
||||||
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
||||||
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||||
|
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
|
||||||
|
|
||||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||||
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
|
||||||
@ -179,6 +183,8 @@ typedef void (*RedisModuleTypeFreeFunc)(void *value);
|
|||||||
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
|
typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
|
||||||
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
||||||
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
|
||||||
|
typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
|
||||||
|
typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
|
||||||
|
|
||||||
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
#define REDISMODULE_TYPE_METHOD_VERSION 2
|
||||||
typedef struct RedisModuleTypeMethods {
|
typedef struct RedisModuleTypeMethods {
|
||||||
@ -280,6 +286,8 @@ RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx
|
|||||||
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
|
||||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
|
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
|
||||||
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
|
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value);
|
void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value);
|
||||||
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
|
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value);
|
void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value);
|
||||||
@ -295,6 +303,7 @@ void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value)
|
|||||||
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
|
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
|
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, ...);
|
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
|
||||||
|
void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
|
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
|
||||||
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
|
||||||
@ -326,6 +335,15 @@ RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ct
|
|||||||
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value);
|
||||||
|
|
||||||
/* Experimental APIs */
|
/* Experimental APIs */
|
||||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||||
@ -366,6 +384,9 @@ const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(R
|
|||||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||||
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
|
int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode);
|
||||||
|
int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* This is included inline inside each Redis module. */
|
/* This is included inline inside each Redis module. */
|
||||||
@ -453,6 +474,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(ModuleTypeSetValue);
|
REDISMODULE_GET_API(ModuleTypeSetValue);
|
||||||
REDISMODULE_GET_API(ModuleTypeGetType);
|
REDISMODULE_GET_API(ModuleTypeGetType);
|
||||||
REDISMODULE_GET_API(ModuleTypeGetValue);
|
REDISMODULE_GET_API(ModuleTypeGetValue);
|
||||||
|
REDISMODULE_GET_API(IsIOError);
|
||||||
|
REDISMODULE_GET_API(SetModuleOptions);
|
||||||
REDISMODULE_GET_API(SaveUnsigned);
|
REDISMODULE_GET_API(SaveUnsigned);
|
||||||
REDISMODULE_GET_API(LoadUnsigned);
|
REDISMODULE_GET_API(LoadUnsigned);
|
||||||
REDISMODULE_GET_API(SaveSigned);
|
REDISMODULE_GET_API(SaveSigned);
|
||||||
@ -468,6 +491,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(EmitAOF);
|
REDISMODULE_GET_API(EmitAOF);
|
||||||
REDISMODULE_GET_API(Log);
|
REDISMODULE_GET_API(Log);
|
||||||
REDISMODULE_GET_API(LogIOError);
|
REDISMODULE_GET_API(LogIOError);
|
||||||
|
REDISMODULE_GET_API(_Assert);
|
||||||
REDISMODULE_GET_API(StringAppendBuffer);
|
REDISMODULE_GET_API(StringAppendBuffer);
|
||||||
REDISMODULE_GET_API(RetainString);
|
REDISMODULE_GET_API(RetainString);
|
||||||
REDISMODULE_GET_API(StringCompare);
|
REDISMODULE_GET_API(StringCompare);
|
||||||
@ -499,6 +523,15 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(DictPrev);
|
REDISMODULE_GET_API(DictPrev);
|
||||||
REDISMODULE_GET_API(DictCompare);
|
REDISMODULE_GET_API(DictCompare);
|
||||||
REDISMODULE_GET_API(DictCompareC);
|
REDISMODULE_GET_API(DictCompareC);
|
||||||
|
REDISMODULE_GET_API(RegisterInfoFunc);
|
||||||
|
REDISMODULE_GET_API(InfoAddSection);
|
||||||
|
REDISMODULE_GET_API(InfoBeginDictField);
|
||||||
|
REDISMODULE_GET_API(InfoEndDictField);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldString);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldCString);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldDouble);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldLongLong);
|
||||||
|
REDISMODULE_GET_API(InfoAddFieldULongLong);
|
||||||
|
|
||||||
#ifdef REDISMODULE_EXPERIMENTAL_API
|
#ifdef REDISMODULE_EXPERIMENTAL_API
|
||||||
REDISMODULE_GET_API(GetThreadSafeContext);
|
REDISMODULE_GET_API(GetThreadSafeContext);
|
||||||
@ -537,6 +570,9 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
REDISMODULE_GET_API(CommandFilterArgInsert);
|
REDISMODULE_GET_API(CommandFilterArgInsert);
|
||||||
REDISMODULE_GET_API(CommandFilterArgReplace);
|
REDISMODULE_GET_API(CommandFilterArgReplace);
|
||||||
REDISMODULE_GET_API(CommandFilterArgDelete);
|
REDISMODULE_GET_API(CommandFilterArgDelete);
|
||||||
|
REDISMODULE_GET_API(Fork);
|
||||||
|
REDISMODULE_GET_API(ExitFromChild);
|
||||||
|
REDISMODULE_GET_API(KillForkChild);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
|
||||||
@ -544,6 +580,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define RedisModule_Assert(_e) ((_e)?(void)0 : (RedisModule__Assert(#_e,__FILE__,__LINE__),exit(1)))
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/* Things only defined for the modules core, not exported to modules
|
/* Things only defined for the modules core, not exported to modules
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
* files using this functions. */
|
* files using this functions. */
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "release.h"
|
#include "release.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
@ -50,3 +51,16 @@ uint64_t redisBuildId(void) {
|
|||||||
|
|
||||||
return crc64(0,(unsigned char*)buildid,strlen(buildid));
|
return crc64(0,(unsigned char*)buildid,strlen(buildid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return a cached value of the build string in order to avoid recomputing
|
||||||
|
* and converting it in hex every time: this string is shown in the INFO
|
||||||
|
* output that should be fast. */
|
||||||
|
char *redisBuildIdString(void) {
|
||||||
|
static char buf[32];
|
||||||
|
static int cached = 0;
|
||||||
|
if (!cached) {
|
||||||
|
snprintf(buf,sizeof(buf),"%llx",(unsigned long long) redisBuildId());
|
||||||
|
cached = 1;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
@ -585,7 +585,7 @@ int startBgsaveForReplication(int mincapa) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* If we failed to BGSAVE, remove the slaves waiting for a full
|
/* If we failed to BGSAVE, remove the slaves waiting for a full
|
||||||
* resynchorinization from the list of salves, inform them with
|
* resynchorinization from the list of slaves, inform them with
|
||||||
* an error about what happened, close the connection ASAP. */
|
* an error about what happened, close the connection ASAP. */
|
||||||
if (retval == C_ERR) {
|
if (retval == C_ERR) {
|
||||||
serverLog(LL_WARNING,"BGSAVE for replication failed");
|
serverLog(LL_WARNING,"BGSAVE for replication failed");
|
||||||
@ -606,7 +606,7 @@ int startBgsaveForReplication(int mincapa) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* If the target is socket, rdbSaveToSlavesSockets() already setup
|
/* If the target is socket, rdbSaveToSlavesSockets() already setup
|
||||||
* the salves for a full resync. Otherwise for disk target do it now.*/
|
* the slaves for a full resync. Otherwise for disk target do it now.*/
|
||||||
if (!socket_target) {
|
if (!socket_target) {
|
||||||
listRewind(server.slaves,&li);
|
listRewind(server.slaves,&li);
|
||||||
while((ln = listNext(&li))) {
|
while((ln = listNext(&li))) {
|
||||||
@ -751,11 +751,11 @@ void syncCommand(client *c) {
|
|||||||
/* Target is disk (or the slave is not capable of supporting
|
/* Target is disk (or the slave is not capable of supporting
|
||||||
* diskless replication) and we don't have a BGSAVE in progress,
|
* diskless replication) and we don't have a BGSAVE in progress,
|
||||||
* let's start one. */
|
* let's start one. */
|
||||||
if (server.aof_child_pid == -1) {
|
if (!hasActiveChildProcess()) {
|
||||||
startBgsaveForReplication(c->slave_capa);
|
startBgsaveForReplication(c->slave_capa);
|
||||||
} else {
|
} else {
|
||||||
serverLog(LL_NOTICE,
|
serverLog(LL_NOTICE,
|
||||||
"No BGSAVE in progress, but an AOF rewrite is active. "
|
"No BGSAVE in progress, but another BG operation is active. "
|
||||||
"BGSAVE for replication delayed");
|
"BGSAVE for replication delayed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1283,8 +1283,15 @@ void restartAOFAfterSYNC() {
|
|||||||
|
|
||||||
static int useDisklessLoad() {
|
static int useDisklessLoad() {
|
||||||
/* compute boolean decision to use diskless load */
|
/* compute boolean decision to use diskless load */
|
||||||
return server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
|
int enabled = server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
|
||||||
(server.repl_diskless_load == REPL_DISKLESS_LOAD_WHEN_DB_EMPTY && dbTotalServerKeyCount()==0);
|
(server.repl_diskless_load == REPL_DISKLESS_LOAD_WHEN_DB_EMPTY && dbTotalServerKeyCount()==0);
|
||||||
|
/* Check all modules handle read errors, otherwise it's not safe to use diskless load. */
|
||||||
|
if (enabled && !moduleAllDatatypesHandleErrors()) {
|
||||||
|
serverLog(LL_WARNING,
|
||||||
|
"Skipping diskless-load because there are modules that don't handle read errors.");
|
||||||
|
enabled = 0;
|
||||||
|
}
|
||||||
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper function for readSyncBulkPayload() to make backups of the current
|
/* Helper function for readSyncBulkPayload() to make backups of the current
|
||||||
@ -2303,7 +2310,10 @@ void replicationSetMaster(char *ip, int port) {
|
|||||||
cancelReplicationHandshake();
|
cancelReplicationHandshake();
|
||||||
/* Before destroying our master state, create a cached master using
|
/* Before destroying our master state, create a cached master using
|
||||||
* our own parameters, to later PSYNC with the new master. */
|
* our own parameters, to later PSYNC with the new master. */
|
||||||
if (was_master) replicationCacheMasterUsingMyself();
|
if (was_master) {
|
||||||
|
replicationDiscardCachedMaster();
|
||||||
|
replicationCacheMasterUsingMyself();
|
||||||
|
}
|
||||||
server.repl_state = REPL_STATE_CONNECT;
|
server.repl_state = REPL_STATE_CONNECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3048,7 +3058,7 @@ void replicationCron(void) {
|
|||||||
* In case of diskless replication, we make sure to wait the specified
|
* In case of diskless replication, we make sure to wait the specified
|
||||||
* number of seconds (according to configuration) so that other slaves
|
* number of seconds (according to configuration) so that other slaves
|
||||||
* have the time to arrive before we start streaming. */
|
* have the time to arrive before we start streaming. */
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
|
if (!hasActiveChildProcess()) {
|
||||||
time_t idle, max_idle = 0;
|
time_t idle, max_idle = 0;
|
||||||
int slaves_waiting = 0;
|
int slaves_waiting = 0;
|
||||||
int mincapa = -1;
|
int mincapa = -1;
|
||||||
|
244
src/scripting.c
244
src/scripting.c
@ -42,7 +42,10 @@ char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
|
|||||||
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
|
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
|
||||||
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
|
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
|
||||||
char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
|
char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
|
||||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype);
|
char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype);
|
||||||
|
char *redisProtocolToLuaType_Null(lua_State *lua, char *reply);
|
||||||
|
char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf);
|
||||||
|
char *redisProtocolToLuaType_Double(lua_State *lua, char *reply);
|
||||||
int redis_math_random (lua_State *L);
|
int redis_math_random (lua_State *L);
|
||||||
int redis_math_randomseed (lua_State *L);
|
int redis_math_randomseed (lua_State *L);
|
||||||
void ldbInit(void);
|
void ldbInit(void);
|
||||||
@ -132,9 +135,12 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
|
|||||||
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
|
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
|
||||||
case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
|
case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
|
||||||
case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
|
case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
|
||||||
case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
case '*': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
|
||||||
case '%': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
case '%': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
|
||||||
case '~': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
case '~': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
|
||||||
|
case '_': p = redisProtocolToLuaType_Null(lua,reply); break;
|
||||||
|
case '#': p = redisProtocolToLuaType_Bool(lua,reply,p[1]); break;
|
||||||
|
case ',': p = redisProtocolToLuaType_Double(lua,reply); break;
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@ -182,13 +188,13 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) {
|
|||||||
return p+2;
|
return p+2;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
|
char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype) {
|
||||||
char *p = strchr(reply+1,'\r');
|
char *p = strchr(reply+1,'\r');
|
||||||
long long mbulklen;
|
long long mbulklen;
|
||||||
int j = 0;
|
int j = 0;
|
||||||
|
|
||||||
string2ll(reply+1,p-reply-1,&mbulklen);
|
string2ll(reply+1,p-reply-1,&mbulklen);
|
||||||
if (server.lua_caller->resp == 2 || atype == '*') {
|
if (server.lua_client->resp == 2 || atype == '*') {
|
||||||
p += 2;
|
p += 2;
|
||||||
if (mbulklen == -1) {
|
if (mbulklen == -1) {
|
||||||
lua_pushboolean(lua,0);
|
lua_pushboolean(lua,0);
|
||||||
@ -200,11 +206,15 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
|
|||||||
p = redisProtocolToLuaType(lua,p);
|
p = redisProtocolToLuaType(lua,p);
|
||||||
lua_settable(lua,-3);
|
lua_settable(lua,-3);
|
||||||
}
|
}
|
||||||
} else if (server.lua_caller->resp == 3) {
|
} else if (server.lua_client->resp == 3) {
|
||||||
/* Here we handle only Set and Map replies in RESP3 mode, since arrays
|
/* Here we handle only Set and Map replies in RESP3 mode, since arrays
|
||||||
* follow the above RESP2 code path. */
|
* follow the above RESP2 code path. Note that those are represented
|
||||||
|
* as a table with the "map" or "set" field populated with the actual
|
||||||
|
* table representing the set or the map type. */
|
||||||
p += 2;
|
p += 2;
|
||||||
lua_newtable(lua);
|
lua_newtable(lua);
|
||||||
|
lua_pushstring(lua,atype == '%' ? "map" : "set");
|
||||||
|
lua_newtable(lua);
|
||||||
for (j = 0; j < mbulklen; j++) {
|
for (j = 0; j < mbulklen; j++) {
|
||||||
p = redisProtocolToLuaType(lua,p);
|
p = redisProtocolToLuaType(lua,p);
|
||||||
if (atype == '%') {
|
if (atype == '%') {
|
||||||
@ -214,10 +224,44 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
|
|||||||
}
|
}
|
||||||
lua_settable(lua,-3);
|
lua_settable(lua,-3);
|
||||||
}
|
}
|
||||||
|
lua_settable(lua,-3);
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *redisProtocolToLuaType_Null(lua_State *lua, char *reply) {
|
||||||
|
char *p = strchr(reply+1,'\r');
|
||||||
|
lua_pushnil(lua);
|
||||||
|
return p+2;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf) {
|
||||||
|
char *p = strchr(reply+1,'\r');
|
||||||
|
lua_pushboolean(lua,tf == 't');
|
||||||
|
return p+2;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *redisProtocolToLuaType_Double(lua_State *lua, char *reply) {
|
||||||
|
char *p = strchr(reply+1,'\r');
|
||||||
|
char buf[MAX_LONG_DOUBLE_CHARS+1];
|
||||||
|
size_t len = p-reply-1;
|
||||||
|
double d;
|
||||||
|
|
||||||
|
if (len <= MAX_LONG_DOUBLE_CHARS) {
|
||||||
|
memcpy(buf,reply+1,len);
|
||||||
|
buf[len] = '\0';
|
||||||
|
d = strtod(buf,NULL); /* We expect a valid representation. */
|
||||||
|
} else {
|
||||||
|
d = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_newtable(lua);
|
||||||
|
lua_pushstring(lua,"double");
|
||||||
|
lua_pushnumber(lua,d);
|
||||||
|
lua_settable(lua,-3);
|
||||||
|
return p+2;
|
||||||
|
}
|
||||||
|
|
||||||
/* This function is used in order to push an error on the Lua stack in the
|
/* This function is used in order to push an error on the Lua stack in the
|
||||||
* format used by redis.pcall to return errors, which is a lua table
|
* format used by redis.pcall to return errors, which is a lua table
|
||||||
* with a single "err" field set to the error string. Note that this
|
* with a single "err" field set to the error string. Note that this
|
||||||
@ -292,6 +336,8 @@ void luaSortArray(lua_State *lua) {
|
|||||||
* Lua reply to Redis reply conversion functions.
|
* Lua reply to Redis reply conversion functions.
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Reply to client 'c' converting the top element in the Lua stack to a
|
||||||
|
* Redis reply. As a side effect the element is consumed from the stack. */
|
||||||
void luaReplyToRedisReply(client *c, lua_State *lua) {
|
void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||||
int t = lua_type(lua,-1);
|
int t = lua_type(lua,-1);
|
||||||
|
|
||||||
@ -300,7 +346,11 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
|||||||
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
|
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
|
||||||
break;
|
break;
|
||||||
case LUA_TBOOLEAN:
|
case LUA_TBOOLEAN:
|
||||||
addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.null[c->resp]);
|
if (server.lua_client->resp == 2)
|
||||||
|
addReply(c,lua_toboolean(lua,-1) ? shared.cone :
|
||||||
|
shared.null[c->resp]);
|
||||||
|
else
|
||||||
|
addReplyBool(c,lua_toboolean(lua,-1));
|
||||||
break;
|
break;
|
||||||
case LUA_TNUMBER:
|
case LUA_TNUMBER:
|
||||||
addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
|
addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
|
||||||
@ -310,6 +360,8 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
|||||||
* Error are returned as a single element table with 'err' field.
|
* Error are returned as a single element table with 'err' field.
|
||||||
* Status replies are returned as single element table with 'ok'
|
* Status replies are returned as single element table with 'ok'
|
||||||
* field. */
|
* field. */
|
||||||
|
|
||||||
|
/* Handle error reply. */
|
||||||
lua_pushstring(lua,"err");
|
lua_pushstring(lua,"err");
|
||||||
lua_gettable(lua,-2);
|
lua_gettable(lua,-2);
|
||||||
t = lua_type(lua,-1);
|
t = lua_type(lua,-1);
|
||||||
@ -321,8 +373,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
|||||||
lua_pop(lua,2);
|
lua_pop(lua,2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
lua_pop(lua,1); /* Discard field name pushed before. */
|
||||||
|
|
||||||
lua_pop(lua,1);
|
/* Handle status reply. */
|
||||||
lua_pushstring(lua,"ok");
|
lua_pushstring(lua,"ok");
|
||||||
lua_gettable(lua,-2);
|
lua_gettable(lua,-2);
|
||||||
t = lua_type(lua,-1);
|
t = lua_type(lua,-1);
|
||||||
@ -331,12 +384,69 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
|||||||
sdsmapchars(ok,"\r\n"," ",2);
|
sdsmapchars(ok,"\r\n"," ",2);
|
||||||
addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
|
addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
|
||||||
sdsfree(ok);
|
sdsfree(ok);
|
||||||
lua_pop(lua,1);
|
lua_pop(lua,2);
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
lua_pop(lua,1); /* Discard field name pushed before. */
|
||||||
|
|
||||||
|
/* Handle double reply. */
|
||||||
|
lua_pushstring(lua,"double");
|
||||||
|
lua_gettable(lua,-2);
|
||||||
|
t = lua_type(lua,-1);
|
||||||
|
if (t == LUA_TNUMBER) {
|
||||||
|
addReplyDouble(c,lua_tonumber(lua,-1));
|
||||||
|
lua_pop(lua,2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lua_pop(lua,1); /* Discard field name pushed before. */
|
||||||
|
|
||||||
|
/* Handle map reply. */
|
||||||
|
lua_pushstring(lua,"map");
|
||||||
|
lua_gettable(lua,-2);
|
||||||
|
t = lua_type(lua,-1);
|
||||||
|
if (t == LUA_TTABLE) {
|
||||||
|
int maplen = 0;
|
||||||
|
void *replylen = addReplyDeferredLen(c);
|
||||||
|
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||||
|
while (lua_next(lua,-2)) {
|
||||||
|
/* Stack now: table, key, value */
|
||||||
|
luaReplyToRedisReply(c, lua); /* Return value. */
|
||||||
|
lua_pushvalue(lua,-1); /* Dup key before consuming. */
|
||||||
|
luaReplyToRedisReply(c, lua); /* Return key. */
|
||||||
|
/* Stack now: table, key. */
|
||||||
|
maplen++;
|
||||||
|
}
|
||||||
|
setDeferredMapLen(c,replylen,maplen);
|
||||||
|
lua_pop(lua,2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lua_pop(lua,1); /* Discard field name pushed before. */
|
||||||
|
|
||||||
|
/* Handle set reply. */
|
||||||
|
lua_pushstring(lua,"set");
|
||||||
|
lua_gettable(lua,-2);
|
||||||
|
t = lua_type(lua,-1);
|
||||||
|
if (t == LUA_TTABLE) {
|
||||||
|
int setlen = 0;
|
||||||
|
void *replylen = addReplyDeferredLen(c);
|
||||||
|
lua_pushnil(lua); /* Use nil to start iteration. */
|
||||||
|
while (lua_next(lua,-2)) {
|
||||||
|
/* Stack now: table, key, true */
|
||||||
|
lua_pop(lua,1); /* Discard the boolean value. */
|
||||||
|
lua_pushvalue(lua,-1); /* Dup key before consuming. */
|
||||||
|
luaReplyToRedisReply(c, lua); /* Return key. */
|
||||||
|
/* Stack now: table, key. */
|
||||||
|
setlen++;
|
||||||
|
}
|
||||||
|
setDeferredSetLen(c,replylen,setlen);
|
||||||
|
lua_pop(lua,2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lua_pop(lua,1); /* Discard field name pushed before. */
|
||||||
|
|
||||||
|
/* Handle the array reply. */
|
||||||
void *replylen = addReplyDeferredLen(c);
|
void *replylen = addReplyDeferredLen(c);
|
||||||
int j = 1, mbulklen = 0;
|
int j = 1, mbulklen = 0;
|
||||||
|
|
||||||
lua_pop(lua,1); /* Discard the 'ok' field value we popped */
|
|
||||||
while(1) {
|
while(1) {
|
||||||
lua_pushnumber(lua,j++);
|
lua_pushnumber(lua,j++);
|
||||||
lua_gettable(lua,-2);
|
lua_gettable(lua,-2);
|
||||||
@ -349,7 +459,6 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
|||||||
mbulklen++;
|
mbulklen++;
|
||||||
}
|
}
|
||||||
setDeferredArrayLen(c,replylen,mbulklen);
|
setDeferredArrayLen(c,replylen,mbulklen);
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
addReplyNull(c);
|
addReplyNull(c);
|
||||||
@ -859,6 +968,25 @@ int luaLogCommand(lua_State *lua) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* redis.setresp() */
|
||||||
|
int luaSetResp(lua_State *lua) {
|
||||||
|
int argc = lua_gettop(lua);
|
||||||
|
|
||||||
|
if (argc != 1) {
|
||||||
|
lua_pushstring(lua, "redis.setresp() requires one argument.");
|
||||||
|
return lua_error(lua);
|
||||||
|
}
|
||||||
|
|
||||||
|
int resp = lua_tonumber(lua,-argc);
|
||||||
|
if (resp != 2 && resp != 3) {
|
||||||
|
lua_pushstring(lua, "RESP version must be 2 or 3.");
|
||||||
|
return lua_error(lua);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.lua_client->resp = resp;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------
|
||||||
* Lua engine initialization and reset.
|
* Lua engine initialization and reset.
|
||||||
* ------------------------------------------------------------------------- */
|
* ------------------------------------------------------------------------- */
|
||||||
@ -955,6 +1083,7 @@ void scriptingInit(int setup) {
|
|||||||
if (setup) {
|
if (setup) {
|
||||||
server.lua_client = NULL;
|
server.lua_client = NULL;
|
||||||
server.lua_caller = NULL;
|
server.lua_caller = NULL;
|
||||||
|
server.lua_cur_script = NULL;
|
||||||
server.lua_timedout = 0;
|
server.lua_timedout = 0;
|
||||||
ldbInit();
|
ldbInit();
|
||||||
}
|
}
|
||||||
@ -986,6 +1115,11 @@ void scriptingInit(int setup) {
|
|||||||
lua_pushcfunction(lua,luaLogCommand);
|
lua_pushcfunction(lua,luaLogCommand);
|
||||||
lua_settable(lua,-3);
|
lua_settable(lua,-3);
|
||||||
|
|
||||||
|
/* redis.setresp */
|
||||||
|
lua_pushstring(lua,"setresp");
|
||||||
|
lua_pushcfunction(lua,luaSetResp);
|
||||||
|
lua_settable(lua,-3);
|
||||||
|
|
||||||
lua_pushstring(lua,"LOG_DEBUG");
|
lua_pushstring(lua,"LOG_DEBUG");
|
||||||
lua_pushnumber(lua,LL_DEBUG);
|
lua_pushnumber(lua,LL_DEBUG);
|
||||||
lua_settable(lua,-3);
|
lua_settable(lua,-3);
|
||||||
@ -1274,7 +1408,11 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
|||||||
/* Set the timeout condition if not already set and the maximum
|
/* Set the timeout condition if not already set and the maximum
|
||||||
* execution time was reached. */
|
* execution time was reached. */
|
||||||
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
||||||
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
serverLog(LL_WARNING,
|
||||||
|
"Lua slow script detected: still in execution after %lld milliseconds. "
|
||||||
|
"You can try killing the script using the SCRIPT KILL command. "
|
||||||
|
"Script SHA1 is: %s",
|
||||||
|
elapsed, server.lua_cur_script);
|
||||||
server.lua_timedout = 1;
|
server.lua_timedout = 1;
|
||||||
/* Once the script timeouts we reenter the event loop to permit others
|
/* Once the script timeouts we reenter the event loop to permit others
|
||||||
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
|
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
|
||||||
@ -1379,8 +1517,9 @@ void evalGenericCommand(client *c, int evalsha) {
|
|||||||
luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
|
luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
|
||||||
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
|
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
|
||||||
|
|
||||||
/* Select the right DB in the context of the Lua client */
|
/* Set the Lua client database and protocol. */
|
||||||
selectDb(server.lua_client,c->db->id);
|
selectDb(server.lua_client,c->db->id);
|
||||||
|
server.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */
|
||||||
|
|
||||||
/* Set a hook in order to be able to stop the script execution if it
|
/* Set a hook in order to be able to stop the script execution if it
|
||||||
* is running for too much time.
|
* is running for too much time.
|
||||||
@ -1390,6 +1529,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
|||||||
* If we are debugging, we set instead a "line" hook so that the
|
* If we are debugging, we set instead a "line" hook so that the
|
||||||
* debugger is call-back at every line executed by the script. */
|
* debugger is call-back at every line executed by the script. */
|
||||||
server.lua_caller = c;
|
server.lua_caller = c;
|
||||||
|
server.lua_cur_script = funcname + 2;
|
||||||
server.lua_time_start = mstime();
|
server.lua_time_start = mstime();
|
||||||
server.lua_kill = 0;
|
server.lua_kill = 0;
|
||||||
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
||||||
@ -1416,6 +1556,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
|||||||
queueClientForReprocessing(server.master);
|
queueClientForReprocessing(server.master);
|
||||||
}
|
}
|
||||||
server.lua_caller = NULL;
|
server.lua_caller = NULL;
|
||||||
|
server.lua_cur_script = NULL;
|
||||||
|
|
||||||
/* Call the Lua garbage collector from time to time to avoid a
|
/* Call the Lua garbage collector from time to time to avoid a
|
||||||
* full cycle performed by Lua, which adds too latency.
|
* full cycle performed by Lua, which adds too latency.
|
||||||
@ -1693,7 +1834,7 @@ void ldbSendLogs(void) {
|
|||||||
int ldbStartSession(client *c) {
|
int ldbStartSession(client *c) {
|
||||||
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
|
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
|
||||||
if (ldb.forked) {
|
if (ldb.forked) {
|
||||||
pid_t cp = fork();
|
pid_t cp = redisFork();
|
||||||
if (cp == -1) {
|
if (cp == -1) {
|
||||||
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
|
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
|
||||||
return 0;
|
return 0;
|
||||||
@ -1710,7 +1851,6 @@ int ldbStartSession(client *c) {
|
|||||||
* socket to make sure if the parent crashes a reset is sent
|
* socket to make sure if the parent crashes a reset is sent
|
||||||
* to the clients. */
|
* to the clients. */
|
||||||
serverLog(LL_WARNING,"Redis forked for debugging eval");
|
serverLog(LL_WARNING,"Redis forked for debugging eval");
|
||||||
closeListeningSockets(0);
|
|
||||||
} else {
|
} else {
|
||||||
/* Parent */
|
/* Parent */
|
||||||
listAddNodeTail(ldb.children,(void*)(unsigned long)cp);
|
listAddNodeTail(ldb.children,(void*)(unsigned long)cp);
|
||||||
@ -2053,6 +2193,11 @@ char *ldbRedisProtocolToHuman_Int(sds *o, char *reply);
|
|||||||
char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply);
|
char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply);
|
||||||
char *ldbRedisProtocolToHuman_Status(sds *o, char *reply);
|
char *ldbRedisProtocolToHuman_Status(sds *o, char *reply);
|
||||||
char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply);
|
char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply);
|
||||||
|
char *ldbRedisProtocolToHuman_Set(sds *o, char *reply);
|
||||||
|
char *ldbRedisProtocolToHuman_Map(sds *o, char *reply);
|
||||||
|
char *ldbRedisProtocolToHuman_Null(sds *o, char *reply);
|
||||||
|
char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply);
|
||||||
|
char *ldbRedisProtocolToHuman_Double(sds *o, char *reply);
|
||||||
|
|
||||||
/* Get Redis protocol from 'reply' and appends it in human readable form to
|
/* Get Redis protocol from 'reply' and appends it in human readable form to
|
||||||
* the passed SDS string 'o'.
|
* the passed SDS string 'o'.
|
||||||
@ -2067,6 +2212,11 @@ char *ldbRedisProtocolToHuman(sds *o, char *reply) {
|
|||||||
case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break;
|
case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break;
|
||||||
case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break;
|
case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break;
|
||||||
case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break;
|
case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break;
|
||||||
|
case '~': p = ldbRedisProtocolToHuman_Set(o,reply); break;
|
||||||
|
case '%': p = ldbRedisProtocolToHuman_Map(o,reply); break;
|
||||||
|
case '_': p = ldbRedisProtocolToHuman_Null(o,reply); break;
|
||||||
|
case '#': p = ldbRedisProtocolToHuman_Bool(o,reply); break;
|
||||||
|
case ',': p = ldbRedisProtocolToHuman_Double(o,reply); break;
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@ -2121,6 +2271,62 @@ char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *ldbRedisProtocolToHuman_Set(sds *o, char *reply) {
|
||||||
|
char *p = strchr(reply+1,'\r');
|
||||||
|
long long mbulklen;
|
||||||
|
int j = 0;
|
||||||
|
|
||||||
|
string2ll(reply+1,p-reply-1,&mbulklen);
|
||||||
|
p += 2;
|
||||||
|
*o = sdscatlen(*o,"~(",2);
|
||||||
|
for (j = 0; j < mbulklen; j++) {
|
||||||
|
p = ldbRedisProtocolToHuman(o,p);
|
||||||
|
if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
|
||||||
|
}
|
||||||
|
*o = sdscatlen(*o,")",1);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ldbRedisProtocolToHuman_Map(sds *o, char *reply) {
|
||||||
|
char *p = strchr(reply+1,'\r');
|
||||||
|
long long mbulklen;
|
||||||
|
int j = 0;
|
||||||
|
|
||||||
|
string2ll(reply+1,p-reply-1,&mbulklen);
|
||||||
|
p += 2;
|
||||||
|
*o = sdscatlen(*o,"{",1);
|
||||||
|
for (j = 0; j < mbulklen; j++) {
|
||||||
|
p = ldbRedisProtocolToHuman(o,p);
|
||||||
|
*o = sdscatlen(*o," => ",4);
|
||||||
|
p = ldbRedisProtocolToHuman(o,p);
|
||||||
|
if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
|
||||||
|
}
|
||||||
|
*o = sdscatlen(*o,"}",1);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ldbRedisProtocolToHuman_Null(sds *o, char *reply) {
|
||||||
|
char *p = strchr(reply+1,'\r');
|
||||||
|
*o = sdscatlen(*o,"(null)",6);
|
||||||
|
return p+2;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply) {
|
||||||
|
char *p = strchr(reply+1,'\r');
|
||||||
|
if (reply[1] == 't')
|
||||||
|
*o = sdscatlen(*o,"#true",5);
|
||||||
|
else
|
||||||
|
*o = sdscatlen(*o,"#false",6);
|
||||||
|
return p+2;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *ldbRedisProtocolToHuman_Double(sds *o, char *reply) {
|
||||||
|
char *p = strchr(reply+1,'\r');
|
||||||
|
*o = sdscatlen(*o,"(double) ",9);
|
||||||
|
*o = sdscatlen(*o,reply+1,p-reply-1);
|
||||||
|
return p+2;
|
||||||
|
}
|
||||||
|
|
||||||
/* Log a Redis reply as debugger output, in an human readable format.
|
/* Log a Redis reply as debugger output, in an human readable format.
|
||||||
* If the resulting string is longer than 'len' plus a few more chars
|
* If the resulting string is longer than 'len' plus a few more chars
|
||||||
* used as prefix, it gets truncated. */
|
* used as prefix, it gets truncated. */
|
||||||
|
@ -603,6 +603,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
|||||||
long i;
|
long i;
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
|
||||||
|
/* To avoid continuous reallocations, let's start with a buffer that
|
||||||
|
* can hold at least two times the format string itself. It's not the
|
||||||
|
* best heuristic but seems to work in practice. */
|
||||||
|
s = sdsMakeRoomFor(s, initlen + strlen(fmt)*2);
|
||||||
va_start(ap,fmt);
|
va_start(ap,fmt);
|
||||||
f = fmt; /* Next format specifier byte to process. */
|
f = fmt; /* Next format specifier byte to process. */
|
||||||
i = initlen; /* Position of the next byte to write to dest str. */
|
i = initlen; /* Position of the next byte to write to dest str. */
|
||||||
|
217
src/server.c
217
src/server.c
@ -1449,12 +1449,18 @@ int incrementallyRehash(int dbid) {
|
|||||||
* for dict.c to resize the hash tables accordingly to the fact we have o not
|
* for dict.c to resize the hash tables accordingly to the fact we have o not
|
||||||
* running childs. */
|
* running childs. */
|
||||||
void updateDictResizePolicy(void) {
|
void updateDictResizePolicy(void) {
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
|
if (!hasActiveChildProcess())
|
||||||
dictEnableResize();
|
dictEnableResize();
|
||||||
else
|
else
|
||||||
dictDisableResize();
|
dictDisableResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int hasActiveChildProcess() {
|
||||||
|
return server.rdb_child_pid != -1 ||
|
||||||
|
server.aof_child_pid != -1 ||
|
||||||
|
server.module_child_pid != -1;
|
||||||
|
}
|
||||||
|
|
||||||
/* ======================= Cron: called every 100 ms ======================== */
|
/* ======================= Cron: called every 100 ms ======================== */
|
||||||
|
|
||||||
/* Add a sample to the operations per second array of samples. */
|
/* Add a sample to the operations per second array of samples. */
|
||||||
@ -1691,7 +1697,7 @@ void databasesCron(void) {
|
|||||||
/* Perform hash tables rehashing if needed, but only if there are no
|
/* Perform hash tables rehashing if needed, but only if there are no
|
||||||
* other processes saving the DB on disk. Otherwise rehashing is bad
|
* other processes saving the DB on disk. Otherwise rehashing is bad
|
||||||
* as will cause a lot of copy-on-write of memory pages. */
|
* as will cause a lot of copy-on-write of memory pages. */
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
|
if (!hasActiveChildProcess()) {
|
||||||
/* We use global counters so if we stop the computation at a given
|
/* We use global counters so if we stop the computation at a given
|
||||||
* DB we'll be able to start from the successive in the next
|
* DB we'll be able to start from the successive in the next
|
||||||
* cron loop iteration. */
|
* cron loop iteration. */
|
||||||
@ -1764,18 +1770,32 @@ void checkChildrenDone(void) {
|
|||||||
|
|
||||||
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
||||||
|
|
||||||
|
/* sigKillChildHandler catches the signal and calls exit(), but we
|
||||||
|
* must make sure not to flag lastbgsave_status, etc incorrectly.
|
||||||
|
* We could directly terminate the child process via SIGUSR1
|
||||||
|
* without handling it, but in this case Valgrind will log an
|
||||||
|
* annoying error. */
|
||||||
|
if (exitcode == SERVER_CHILD_NOERROR_RETVAL) {
|
||||||
|
bysignal = SIGUSR1;
|
||||||
|
exitcode = 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (pid == -1) {
|
if (pid == -1) {
|
||||||
serverLog(LL_WARNING,"wait3() returned an error: %s. "
|
serverLog(LL_WARNING,"wait3() returned an error: %s. "
|
||||||
"rdb_child_pid = %d, aof_child_pid = %d",
|
"rdb_child_pid = %d, aof_child_pid = %d, module_child_pid = %d",
|
||||||
strerror(errno),
|
strerror(errno),
|
||||||
(int) server.rdb_child_pid,
|
(int) server.rdb_child_pid,
|
||||||
(int) server.aof_child_pid);
|
(int) server.aof_child_pid,
|
||||||
|
(int) server.module_child_pid);
|
||||||
} else if (pid == server.rdb_child_pid) {
|
} else if (pid == server.rdb_child_pid) {
|
||||||
backgroundSaveDoneHandler(exitcode,bysignal);
|
backgroundSaveDoneHandler(exitcode,bysignal);
|
||||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||||
} else if (pid == server.aof_child_pid) {
|
} else if (pid == server.aof_child_pid) {
|
||||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
backgroundRewriteDoneHandler(exitcode,bysignal);
|
||||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||||
|
} else if (pid == server.module_child_pid) {
|
||||||
|
ModuleForkDoneHandler(exitcode,bysignal);
|
||||||
|
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||||
} else {
|
} else {
|
||||||
if (!ldbRemoveChild(pid)) {
|
if (!ldbRemoveChild(pid)) {
|
||||||
serverLog(LL_WARNING,
|
serverLog(LL_WARNING,
|
||||||
@ -1930,15 +1950,14 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
|
|
||||||
/* Start a scheduled AOF rewrite if this was requested by the user while
|
/* Start a scheduled AOF rewrite if this was requested by the user while
|
||||||
* a BGSAVE was in progress. */
|
* a BGSAVE was in progress. */
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
|
if (!hasActiveChildProcess() &&
|
||||||
server.aof_rewrite_scheduled)
|
server.aof_rewrite_scheduled)
|
||||||
{
|
{
|
||||||
rewriteAppendOnlyFileBackground();
|
rewriteAppendOnlyFileBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if a background saving or AOF rewrite in progress terminated. */
|
/* Check if a background saving or AOF rewrite in progress terminated. */
|
||||||
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
|
if (hasActiveChildProcess() || ldbPendingChildren())
|
||||||
ldbPendingChildren())
|
|
||||||
{
|
{
|
||||||
checkChildrenDone();
|
checkChildrenDone();
|
||||||
} else {
|
} else {
|
||||||
@ -1968,8 +1987,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
|
|
||||||
/* Trigger an AOF rewrite if needed. */
|
/* Trigger an AOF rewrite if needed. */
|
||||||
if (server.aof_state == AOF_ON &&
|
if (server.aof_state == AOF_ON &&
|
||||||
server.rdb_child_pid == -1 &&
|
!hasActiveChildProcess() &&
|
||||||
server.aof_child_pid == -1 &&
|
|
||||||
server.aof_rewrite_perc &&
|
server.aof_rewrite_perc &&
|
||||||
server.aof_current_size > server.aof_rewrite_min_size)
|
server.aof_current_size > server.aof_rewrite_min_size)
|
||||||
{
|
{
|
||||||
@ -2027,7 +2045,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
|||||||
* Note: this code must be after the replicationCron() call above so
|
* Note: this code must be after the replicationCron() call above so
|
||||||
* make sure when refactoring this file to keep this order. This is useful
|
* make sure when refactoring this file to keep this order. This is useful
|
||||||
* because we want to give priority to RDB savings for replication. */
|
* because we want to give priority to RDB savings for replication. */
|
||||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
|
if (!hasActiveChildProcess() &&
|
||||||
server.rdb_bgsave_scheduled &&
|
server.rdb_bgsave_scheduled &&
|
||||||
(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
|
(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
|
||||||
server.lastbgsave_status == C_OK))
|
server.lastbgsave_status == C_OK))
|
||||||
@ -2261,6 +2279,7 @@ void initServerConfig(void) {
|
|||||||
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
||||||
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
|
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
|
||||||
server.active_expire_enabled = 1;
|
server.active_expire_enabled = 1;
|
||||||
|
server.jemalloc_bg_thread = 1;
|
||||||
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
|
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
|
||||||
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
|
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
|
||||||
server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER;
|
server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER;
|
||||||
@ -2828,6 +2847,7 @@ void initServer(void) {
|
|||||||
server.cronloops = 0;
|
server.cronloops = 0;
|
||||||
server.rdb_child_pid = -1;
|
server.rdb_child_pid = -1;
|
||||||
server.aof_child_pid = -1;
|
server.aof_child_pid = -1;
|
||||||
|
server.module_child_pid = -1;
|
||||||
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||||
server.rdb_pipe_conns = NULL;
|
server.rdb_pipe_conns = NULL;
|
||||||
server.rdb_pipe_numconns = 0;
|
server.rdb_pipe_numconns = 0;
|
||||||
@ -2851,6 +2871,7 @@ void initServer(void) {
|
|||||||
server.stat_peak_memory = 0;
|
server.stat_peak_memory = 0;
|
||||||
server.stat_rdb_cow_bytes = 0;
|
server.stat_rdb_cow_bytes = 0;
|
||||||
server.stat_aof_cow_bytes = 0;
|
server.stat_aof_cow_bytes = 0;
|
||||||
|
server.stat_module_cow_bytes = 0;
|
||||||
server.cron_malloc_stats.zmalloc_used = 0;
|
server.cron_malloc_stats.zmalloc_used = 0;
|
||||||
server.cron_malloc_stats.process_rss = 0;
|
server.cron_malloc_stats.process_rss = 0;
|
||||||
server.cron_malloc_stats.allocator_allocated = 0;
|
server.cron_malloc_stats.allocator_allocated = 0;
|
||||||
@ -2926,8 +2947,17 @@ void initServer(void) {
|
|||||||
scriptingInit(1);
|
scriptingInit(1);
|
||||||
slowlogInit();
|
slowlogInit();
|
||||||
latencyMonitorInit();
|
latencyMonitorInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Some steps in server initialization need to be done last (after modules
|
||||||
|
* are loaded).
|
||||||
|
* Specifically, creation of threads due to a race bug in ld.so, in which
|
||||||
|
* Thread Local Storage initialization collides with dlopen call.
|
||||||
|
* see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329 */
|
||||||
|
void InitServerLast() {
|
||||||
bioInit();
|
bioInit();
|
||||||
initThreadedIO();
|
initThreadedIO();
|
||||||
|
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
|
||||||
server.initial_memory_usage = zmalloc_used_memory();
|
server.initial_memory_usage = zmalloc_used_memory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3383,11 +3413,12 @@ int processCommand(client *c) {
|
|||||||
|
|
||||||
/* Check if the user is authenticated. This check is skipped in case
|
/* Check if the user is authenticated. This check is skipped in case
|
||||||
* the default user is flagged as "nopass" and is active. */
|
* the default user is flagged as "nopass" and is active. */
|
||||||
int auth_required = !(DefaultUser->flags & USER_FLAG_NOPASS) &&
|
int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
|
||||||
|
DefaultUser->flags & USER_FLAG_DISABLED) &&
|
||||||
!c->authenticated;
|
!c->authenticated;
|
||||||
if (auth_required || DefaultUser->flags & USER_FLAG_DISABLED) {
|
if (auth_required) {
|
||||||
/* AUTH and HELLO are valid even in non authenticated state. */
|
/* AUTH and HELLO are valid even in non authenticated state. */
|
||||||
if (c->cmd->proc != authCommand || c->cmd->proc == helloCommand) {
|
if (c->cmd->proc != authCommand && c->cmd->proc != helloCommand) {
|
||||||
flagTransaction(c);
|
flagTransaction(c);
|
||||||
addReply(c,shared.noautherr);
|
addReply(c,shared.noautherr);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
@ -3453,7 +3484,10 @@ int processCommand(client *c) {
|
|||||||
* is in MULTI/EXEC context? Error. */
|
* is in MULTI/EXEC context? Error. */
|
||||||
if (out_of_memory &&
|
if (out_of_memory &&
|
||||||
(c->cmd->flags & CMD_DENYOOM ||
|
(c->cmd->flags & CMD_DENYOOM ||
|
||||||
(c->flags & CLIENT_MULTI && c->cmd->proc != execCommand))) {
|
(c->flags & CLIENT_MULTI &&
|
||||||
|
c->cmd->proc != execCommand &&
|
||||||
|
c->cmd->proc != discardCommand)))
|
||||||
|
{
|
||||||
flagTransaction(c);
|
flagTransaction(c);
|
||||||
addReply(c, shared.oomerr);
|
addReply(c, shared.oomerr);
|
||||||
return C_OK;
|
return C_OK;
|
||||||
@ -3605,6 +3639,12 @@ int prepareForShutdown(int flags) {
|
|||||||
killRDBChild();
|
killRDBChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kill module child if there is one. */
|
||||||
|
if (server.module_child_pid != -1) {
|
||||||
|
serverLog(LL_WARNING,"There is a module fork child. Killing it!");
|
||||||
|
TerminateModuleForkChild(server.module_child_pid,0);
|
||||||
|
}
|
||||||
|
|
||||||
if (server.aof_state != AOF_OFF) {
|
if (server.aof_state != AOF_OFF) {
|
||||||
/* Kill the AOF saving child as the AOF we already have may be longer
|
/* Kill the AOF saving child as the AOF we already have may be longer
|
||||||
* but contains the full dataset anyway. */
|
* but contains the full dataset anyway. */
|
||||||
@ -3874,12 +3914,15 @@ sds genRedisInfoString(char *section) {
|
|||||||
time_t uptime = server.unixtime-server.stat_starttime;
|
time_t uptime = server.unixtime-server.stat_starttime;
|
||||||
int j;
|
int j;
|
||||||
struct rusage self_ru, c_ru;
|
struct rusage self_ru, c_ru;
|
||||||
int allsections = 0, defsections = 0;
|
int allsections = 0, defsections = 0, everything = 0, modules = 0;
|
||||||
int sections = 0;
|
int sections = 0;
|
||||||
|
|
||||||
if (section == NULL) section = "default";
|
if (section == NULL) section = "default";
|
||||||
allsections = strcasecmp(section,"all") == 0;
|
allsections = strcasecmp(section,"all") == 0;
|
||||||
defsections = strcasecmp(section,"default") == 0;
|
defsections = strcasecmp(section,"default") == 0;
|
||||||
|
everything = strcasecmp(section,"everything") == 0;
|
||||||
|
modules = strcasecmp(section,"modules") == 0;
|
||||||
|
if (everything) allsections = 1;
|
||||||
|
|
||||||
getrusage(RUSAGE_SELF, &self_ru);
|
getrusage(RUSAGE_SELF, &self_ru);
|
||||||
getrusage(RUSAGE_CHILDREN, &c_ru);
|
getrusage(RUSAGE_CHILDREN, &c_ru);
|
||||||
@ -3902,32 +3945,32 @@ sds genRedisInfoString(char *section) {
|
|||||||
call_uname = 0;
|
call_uname = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
info = sdscatprintf(info,
|
info = sdscatfmt(info,
|
||||||
"# Server\r\n"
|
"# Server\r\n"
|
||||||
"redis_version:%s\r\n"
|
"redis_version:%s\r\n"
|
||||||
"redis_git_sha1:%s\r\n"
|
"redis_git_sha1:%s\r\n"
|
||||||
"redis_git_dirty:%d\r\n"
|
"redis_git_dirty:%i\r\n"
|
||||||
"redis_build_id:%llx\r\n"
|
"redis_build_id:%s\r\n"
|
||||||
"redis_mode:%s\r\n"
|
"redis_mode:%s\r\n"
|
||||||
"os:%s %s %s\r\n"
|
"os:%s %s %s\r\n"
|
||||||
"arch_bits:%d\r\n"
|
"arch_bits:%i\r\n"
|
||||||
"multiplexing_api:%s\r\n"
|
"multiplexing_api:%s\r\n"
|
||||||
"atomicvar_api:%s\r\n"
|
"atomicvar_api:%s\r\n"
|
||||||
"gcc_version:%d.%d.%d\r\n"
|
"gcc_version:%i.%i.%i\r\n"
|
||||||
"process_id:%ld\r\n"
|
"process_id:%I\r\n"
|
||||||
"run_id:%s\r\n"
|
"run_id:%s\r\n"
|
||||||
"tcp_port:%d\r\n"
|
"tcp_port:%i\r\n"
|
||||||
"uptime_in_seconds:%jd\r\n"
|
"uptime_in_seconds:%I\r\n"
|
||||||
"uptime_in_days:%jd\r\n"
|
"uptime_in_days:%I\r\n"
|
||||||
"hz:%d\r\n"
|
"hz:%i\r\n"
|
||||||
"configured_hz:%d\r\n"
|
"configured_hz:%i\r\n"
|
||||||
"lru_clock:%ld\r\n"
|
"lru_clock:%u\r\n"
|
||||||
"executable:%s\r\n"
|
"executable:%s\r\n"
|
||||||
"config_file:%s\r\n",
|
"config_file:%s\r\n",
|
||||||
REDIS_VERSION,
|
REDIS_VERSION,
|
||||||
redisGitSHA1(),
|
redisGitSHA1(),
|
||||||
strtol(redisGitDirty(),NULL,10) > 0,
|
strtol(redisGitDirty(),NULL,10) > 0,
|
||||||
(unsigned long long) redisBuildId(),
|
redisBuildIdString(),
|
||||||
mode,
|
mode,
|
||||||
name.sysname, name.release, name.machine,
|
name.sysname, name.release, name.machine,
|
||||||
server.arch_bits,
|
server.arch_bits,
|
||||||
@ -3938,14 +3981,14 @@ sds genRedisInfoString(char *section) {
|
|||||||
#else
|
#else
|
||||||
0,0,0,
|
0,0,0,
|
||||||
#endif
|
#endif
|
||||||
(long) getpid(),
|
(int64_t) getpid(),
|
||||||
server.runid,
|
server.runid,
|
||||||
server.port ? server.port : server.tls_port,
|
server.port ? server.port : server.tls_port,
|
||||||
(intmax_t)uptime,
|
(int64_t)uptime,
|
||||||
(intmax_t)(uptime/(3600*24)),
|
(int64_t)(uptime/(3600*24)),
|
||||||
server.hz,
|
server.hz,
|
||||||
server.config_hz,
|
server.config_hz,
|
||||||
(unsigned long) server.lruclock,
|
server.lruclock,
|
||||||
server.executable ? server.executable : "",
|
server.executable ? server.executable : "",
|
||||||
server.configfile ? server.configfile : "");
|
server.configfile ? server.configfile : "");
|
||||||
}
|
}
|
||||||
@ -4071,8 +4114,11 @@ sds genRedisInfoString(char *section) {
|
|||||||
mh->allocator_rss_bytes,
|
mh->allocator_rss_bytes,
|
||||||
mh->rss_extra,
|
mh->rss_extra,
|
||||||
mh->rss_extra_bytes,
|
mh->rss_extra_bytes,
|
||||||
mh->total_frag, /* this is the total RSS overhead, including fragmentation, */
|
mh->total_frag, /* This is the total RSS overhead, including
|
||||||
mh->total_frag_bytes, /* named so for backwards compatibility */
|
fragmentation, but not just it. This field
|
||||||
|
(and the next one) is named like that just
|
||||||
|
for backward compatibility. */
|
||||||
|
mh->total_frag_bytes,
|
||||||
freeMemoryGetNotCountedMemory(),
|
freeMemoryGetNotCountedMemory(),
|
||||||
mh->repl_backlog,
|
mh->repl_backlog,
|
||||||
mh->clients_slaves,
|
mh->clients_slaves,
|
||||||
@ -4105,7 +4151,9 @@ sds genRedisInfoString(char *section) {
|
|||||||
"aof_current_rewrite_time_sec:%jd\r\n"
|
"aof_current_rewrite_time_sec:%jd\r\n"
|
||||||
"aof_last_bgrewrite_status:%s\r\n"
|
"aof_last_bgrewrite_status:%s\r\n"
|
||||||
"aof_last_write_status:%s\r\n"
|
"aof_last_write_status:%s\r\n"
|
||||||
"aof_last_cow_size:%zu\r\n",
|
"aof_last_cow_size:%zu\r\n"
|
||||||
|
"module_fork_in_progress:%d\r\n"
|
||||||
|
"module_fork_last_cow_size:%zu\r\n",
|
||||||
server.loading,
|
server.loading,
|
||||||
server.dirty,
|
server.dirty,
|
||||||
server.rdb_child_pid != -1,
|
server.rdb_child_pid != -1,
|
||||||
@ -4123,7 +4171,9 @@ sds genRedisInfoString(char *section) {
|
|||||||
-1 : time(NULL)-server.aof_rewrite_time_start),
|
-1 : time(NULL)-server.aof_rewrite_time_start),
|
||||||
(server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
|
(server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
|
||||||
(server.aof_last_write_status == C_OK) ? "ok" : "err",
|
(server.aof_last_write_status == C_OK) ? "ok" : "err",
|
||||||
server.stat_aof_cow_bytes);
|
server.stat_aof_cow_bytes,
|
||||||
|
server.module_child_pid != -1,
|
||||||
|
server.stat_module_cow_bytes);
|
||||||
|
|
||||||
if (server.aof_enabled) {
|
if (server.aof_enabled) {
|
||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
@ -4281,7 +4331,7 @@ sds genRedisInfoString(char *section) {
|
|||||||
if (server.repl_state != REPL_STATE_CONNECTED) {
|
if (server.repl_state != REPL_STATE_CONNECTED) {
|
||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
"master_link_down_since_seconds:%jd\r\n",
|
"master_link_down_since_seconds:%jd\r\n",
|
||||||
(intmax_t)server.unixtime-server.repl_down_since);
|
(intmax_t)(server.unixtime-server.repl_down_since));
|
||||||
}
|
}
|
||||||
info = sdscatprintf(info,
|
info = sdscatprintf(info,
|
||||||
"slave_priority:%d\r\n"
|
"slave_priority:%d\r\n"
|
||||||
@ -4379,6 +4429,13 @@ sds genRedisInfoString(char *section) {
|
|||||||
(long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec);
|
(long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
if (allsections || defsections || !strcasecmp(section,"modules")) {
|
||||||
|
if (sections++) info = sdscat(info,"\r\n");
|
||||||
|
info = sdscatprintf(info,"# Modules\r\n");
|
||||||
|
info = genModulesInfoString(info);
|
||||||
|
}
|
||||||
|
|
||||||
/* Command statistics */
|
/* Command statistics */
|
||||||
if (allsections || !strcasecmp(section,"commandstats")) {
|
if (allsections || !strcasecmp(section,"commandstats")) {
|
||||||
if (sections++) info = sdscat(info,"\r\n");
|
if (sections++) info = sdscat(info,"\r\n");
|
||||||
@ -4424,6 +4481,17 @@ sds genRedisInfoString(char *section) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get info from modules.
|
||||||
|
* if user asked for "everything" or "modules", or a specific section
|
||||||
|
* that's not found yet. */
|
||||||
|
if (everything || modules ||
|
||||||
|
(!allsections && !defsections && sections==0)) {
|
||||||
|
info = modulesCollectInfo(info,
|
||||||
|
everything || modules ? NULL: section,
|
||||||
|
0, /* not a crash report */
|
||||||
|
sections);
|
||||||
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4434,7 +4502,9 @@ void infoCommand(client *c) {
|
|||||||
addReply(c,shared.syntaxerr);
|
addReply(c,shared.syntaxerr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addReplyBulkSds(c, genRedisInfoString(section));
|
sds info = genRedisInfoString(section);
|
||||||
|
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||||
|
sdsfree(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
void monitorCommand(client *c) {
|
void monitorCommand(client *c) {
|
||||||
@ -4621,6 +4691,61 @@ void setupSignalHandlers(void) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This is the signal handler for children process. It is currently useful
|
||||||
|
* in order to track the SIGUSR1, that we send to a child in order to terminate
|
||||||
|
* it in a clean way, without the parent detecting an error and stop
|
||||||
|
* accepting writes because of a write error condition. */
|
||||||
|
static void sigKillChildHandler(int sig) {
|
||||||
|
UNUSED(sig);
|
||||||
|
serverLogFromHandler(LL_WARNING, "Received SIGUSR1 in child, exiting now.");
|
||||||
|
exitFromChild(SERVER_CHILD_NOERROR_RETVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupChildSignalHandlers(void) {
|
||||||
|
struct sigaction act;
|
||||||
|
|
||||||
|
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
|
||||||
|
* Otherwise, sa_handler is used. */
|
||||||
|
sigemptyset(&act.sa_mask);
|
||||||
|
act.sa_flags = 0;
|
||||||
|
act.sa_handler = sigKillChildHandler;
|
||||||
|
sigaction(SIGUSR1, &act, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redisFork() {
|
||||||
|
int childpid;
|
||||||
|
long long start = ustime();
|
||||||
|
if ((childpid = fork()) == 0) {
|
||||||
|
/* Child */
|
||||||
|
closeListeningSockets(0);
|
||||||
|
setupChildSignalHandlers();
|
||||||
|
} else {
|
||||||
|
/* Parent */
|
||||||
|
server.stat_fork_time = ustime()-start;
|
||||||
|
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||||
|
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||||
|
if (childpid == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
updateDictResizePolicy();
|
||||||
|
}
|
||||||
|
return childpid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendChildCOWInfo(int ptype, char *pname) {
|
||||||
|
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||||
|
|
||||||
|
if (private_dirty) {
|
||||||
|
serverLog(LL_NOTICE,
|
||||||
|
"%s: %zu MB of memory used by copy-on-write",
|
||||||
|
pname, private_dirty/(1024*1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
server.child_info_data.cow_size = private_dirty;
|
||||||
|
sendChildInfo(ptype);
|
||||||
|
}
|
||||||
|
|
||||||
void memtest(size_t megabytes, int passes);
|
void memtest(size_t megabytes, int passes);
|
||||||
|
|
||||||
/* Returns 1 if there is --sentinel among the arguments or if
|
/* Returns 1 if there is --sentinel among the arguments or if
|
||||||
@ -4647,12 +4772,14 @@ void loadDataFromDisk(void) {
|
|||||||
(float)(ustime()-start)/1000000);
|
(float)(ustime()-start)/1000000);
|
||||||
|
|
||||||
/* Restore the replication ID / offset from the RDB file. */
|
/* Restore the replication ID / offset from the RDB file. */
|
||||||
if ((server.masterhost || (server.cluster_enabled && nodeIsSlave(server.cluster->myself)))&&
|
if ((server.masterhost ||
|
||||||
|
(server.cluster_enabled &&
|
||||||
|
nodeIsSlave(server.cluster->myself))) &&
|
||||||
rsi.repl_id_is_set &&
|
rsi.repl_id_is_set &&
|
||||||
rsi.repl_offset != -1 &&
|
rsi.repl_offset != -1 &&
|
||||||
/* Note that older implementations may save a repl_stream_db
|
/* Note that older implementations may save a repl_stream_db
|
||||||
* of -1 inside the RDB file in a wrong way, see more information
|
* of -1 inside the RDB file in a wrong way, see more
|
||||||
* in function rdbPopulateSaveInfo. */
|
* information in function rdbPopulateSaveInfo. */
|
||||||
rsi.repl_stream_db != -1)
|
rsi.repl_stream_db != -1)
|
||||||
{
|
{
|
||||||
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
||||||
@ -4828,9 +4955,9 @@ int main(int argc, char **argv) {
|
|||||||
srand(time(NULL)^getpid());
|
srand(time(NULL)^getpid());
|
||||||
gettimeofday(&tv,NULL);
|
gettimeofday(&tv,NULL);
|
||||||
|
|
||||||
char hashseed[16];
|
uint8_t hashseed[16];
|
||||||
getRandomHexChars(hashseed,sizeof(hashseed));
|
getRandomBytes(hashseed,sizeof(hashseed));
|
||||||
dictSetHashFunctionSeed((uint8_t*)hashseed);
|
dictSetHashFunctionSeed(hashseed);
|
||||||
server.sentinel_mode = checkForSentinelMode(argc,argv);
|
server.sentinel_mode = checkForSentinelMode(argc,argv);
|
||||||
initServerConfig();
|
initServerConfig();
|
||||||
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
|
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
|
||||||
@ -4960,6 +5087,7 @@ int main(int argc, char **argv) {
|
|||||||
#endif
|
#endif
|
||||||
moduleLoadFromQueue();
|
moduleLoadFromQueue();
|
||||||
ACLLoadUsersAtStartup();
|
ACLLoadUsersAtStartup();
|
||||||
|
InitServerLast();
|
||||||
loadDataFromDisk();
|
loadDataFromDisk();
|
||||||
if (server.cluster_enabled) {
|
if (server.cluster_enabled) {
|
||||||
if (verifyClusterConfigWithData() == C_ERR) {
|
if (verifyClusterConfigWithData() == C_ERR) {
|
||||||
@ -4974,6 +5102,7 @@ int main(int argc, char **argv) {
|
|||||||
if (server.sofd > 0)
|
if (server.sofd > 0)
|
||||||
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
|
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
|
||||||
} else {
|
} else {
|
||||||
|
InitServerLast();
|
||||||
sentinelIsRunning();
|
sentinelIsRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
src/server.h
26
src/server.h
@ -182,6 +182,14 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||||||
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
|
||||||
#define ACTIVE_EXPIRE_CYCLE_FAST 1
|
#define ACTIVE_EXPIRE_CYCLE_FAST 1
|
||||||
|
|
||||||
|
/* Children process will exit with this status code to signal that the
|
||||||
|
* process terminated without an error: this is useful in order to kill
|
||||||
|
* a saving child (RDB or AOF one), without triggering in the parent the
|
||||||
|
* write protection that is normally turned on on write errors.
|
||||||
|
* Usually children that are terminated with SIGUSR1 will exit with this
|
||||||
|
* special code. */
|
||||||
|
#define SERVER_CHILD_NOERROR_RETVAL 255
|
||||||
|
|
||||||
/* Instantaneous metrics tracking. */
|
/* Instantaneous metrics tracking. */
|
||||||
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
|
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
|
||||||
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
|
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
|
||||||
@ -1060,6 +1068,7 @@ struct clusterState;
|
|||||||
#define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL
|
#define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL
|
||||||
#define CHILD_INFO_TYPE_RDB 0
|
#define CHILD_INFO_TYPE_RDB 0
|
||||||
#define CHILD_INFO_TYPE_AOF 1
|
#define CHILD_INFO_TYPE_AOF 1
|
||||||
|
#define CHILD_INFO_TYPE_MODULE 3
|
||||||
|
|
||||||
struct redisServer {
|
struct redisServer {
|
||||||
/* General */
|
/* General */
|
||||||
@ -1095,6 +1104,7 @@ struct redisServer {
|
|||||||
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
||||||
client blocked on a module command needs
|
client blocked on a module command needs
|
||||||
to be processed. */
|
to be processed. */
|
||||||
|
pid_t module_child_pid; /* PID of module child */
|
||||||
/* Networking */
|
/* Networking */
|
||||||
int port; /* TCP listening port */
|
int port; /* TCP listening port */
|
||||||
int tls_port; /* TLS listening port */
|
int tls_port; /* TLS listening port */
|
||||||
@ -1171,6 +1181,7 @@ struct redisServer {
|
|||||||
_Atomic long long stat_net_output_bytes; /* Bytes written to network. */
|
_Atomic long long stat_net_output_bytes; /* Bytes written to network. */
|
||||||
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
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_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
|
||||||
|
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
|
||||||
/* The following two are used to track instantaneous metrics, like
|
/* The following two are used to track instantaneous metrics, like
|
||||||
* number of operations per second, network traffic. */
|
* number of operations per second, network traffic. */
|
||||||
struct {
|
struct {
|
||||||
@ -1185,6 +1196,7 @@ struct redisServer {
|
|||||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||||
int active_defrag_enabled;
|
int active_defrag_enabled;
|
||||||
|
int jemalloc_bg_thread; /* Enable jemalloc background thread */
|
||||||
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
|
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
|
||||||
int active_defrag_threshold_lower; /* minimum percentage of fragmentation to start active defrag */
|
int active_defrag_threshold_lower; /* minimum percentage of fragmentation to start active defrag */
|
||||||
int active_defrag_threshold_upper; /* maximum percentage of fragmentation at which we use maximum effort */
|
int active_defrag_threshold_upper; /* maximum percentage of fragmentation at which we use maximum effort */
|
||||||
@ -1408,6 +1420,7 @@ struct redisServer {
|
|||||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||||
client *lua_client; /* The "fake client" to query Redis from Lua */
|
client *lua_client; /* The "fake client" to query Redis from Lua */
|
||||||
client *lua_caller; /* The client running EVAL right now, or NULL */
|
client *lua_caller; /* The client running EVAL right now, or NULL */
|
||||||
|
char* lua_cur_script; /* SHA1 of the script currently running, or NULL */
|
||||||
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
|
||||||
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
||||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||||
@ -1575,7 +1588,11 @@ void moduleAcquireGIL(void);
|
|||||||
void moduleReleaseGIL(void);
|
void moduleReleaseGIL(void);
|
||||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||||
void moduleCallCommandFilters(client *c);
|
void moduleCallCommandFilters(client *c);
|
||||||
|
void ModuleForkDoneHandler(int exitcode, int bysignal);
|
||||||
|
int TerminateModuleForkChild(int child_pid, int wait);
|
||||||
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
||||||
|
int moduleAllDatatypesHandleErrors();
|
||||||
|
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections);
|
||||||
|
|
||||||
/* Utils */
|
/* Utils */
|
||||||
long long ustime(void);
|
long long ustime(void);
|
||||||
@ -1840,6 +1857,11 @@ void closeChildInfoPipe(void);
|
|||||||
void sendChildInfo(int process_type);
|
void sendChildInfo(int process_type);
|
||||||
void receiveChildInfo(void);
|
void receiveChildInfo(void);
|
||||||
|
|
||||||
|
/* Fork helpers */
|
||||||
|
int redisFork();
|
||||||
|
int hasActiveChildProcess();
|
||||||
|
void sendChildCOWInfo(int ptype, char *pname);
|
||||||
|
|
||||||
/* acl.c -- Authentication related prototypes. */
|
/* acl.c -- Authentication related prototypes. */
|
||||||
extern rax *Users;
|
extern rax *Users;
|
||||||
extern user *DefaultUser;
|
extern user *DefaultUser;
|
||||||
@ -1940,6 +1962,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
|
|||||||
void call(client *c, int flags);
|
void call(client *c, int flags);
|
||||||
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
|
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
|
||||||
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
|
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
|
||||||
|
void redisOpArrayInit(redisOpArray *oa);
|
||||||
|
void redisOpArrayFree(redisOpArray *oa);
|
||||||
void forceCommandPropagation(client *c, int flags);
|
void forceCommandPropagation(client *c, int flags);
|
||||||
void preventCommandPropagation(client *c);
|
void preventCommandPropagation(client *c);
|
||||||
void preventCommandAOF(client *c);
|
void preventCommandAOF(client *c);
|
||||||
@ -2159,6 +2183,7 @@ void dictSdsDestructor(void *privdata, void *val);
|
|||||||
char *redisGitSHA1(void);
|
char *redisGitSHA1(void);
|
||||||
char *redisGitDirty(void);
|
char *redisGitDirty(void);
|
||||||
uint64_t redisBuildId(void);
|
uint64_t redisBuildId(void);
|
||||||
|
char *redisBuildIdString(void);
|
||||||
|
|
||||||
/* Commands prototypes */
|
/* Commands prototypes */
|
||||||
void authCommand(client *c);
|
void authCommand(client *c);
|
||||||
@ -2373,6 +2398,7 @@ void bugReportStart(void);
|
|||||||
void serverLogObjectDebugInfo(const robj *o);
|
void serverLogObjectDebugInfo(const robj *o);
|
||||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
|
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
|
||||||
sds genRedisInfoString(char *section);
|
sds genRedisInfoString(char *section);
|
||||||
|
sds genModulesInfoString(sds info);
|
||||||
void enableWatchdog(int period);
|
void enableWatchdog(int period);
|
||||||
void disableWatchdog(void);
|
void disableWatchdog(void);
|
||||||
void watchdogScheduleSignal(int period);
|
void watchdogScheduleSignal(int period);
|
||||||
|
158
src/sha256.c
Normal file
158
src/sha256.c
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/*********************************************************************
|
||||||
|
* Filename: sha256.c
|
||||||
|
* Author: Brad Conte (brad AT bradconte.com)
|
||||||
|
* Copyright:
|
||||||
|
* Disclaimer: This code is presented "as is" without any guarantees.
|
||||||
|
* Details: Implementation of the SHA-256 hashing algorithm.
|
||||||
|
SHA-256 is one of the three algorithms in the SHA2
|
||||||
|
specification. The others, SHA-384 and SHA-512, are not
|
||||||
|
offered in this implementation.
|
||||||
|
Algorithm specification can be found here:
|
||||||
|
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
|
||||||
|
This implementation uses little endian byte order.
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
/*************************** HEADER FILES ***************************/
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "sha256.h"
|
||||||
|
|
||||||
|
/****************************** MACROS ******************************/
|
||||||
|
#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
|
||||||
|
#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
|
||||||
|
|
||||||
|
#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
|
||||||
|
#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
|
||||||
|
#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
|
||||||
|
#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
|
||||||
|
#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
|
||||||
|
#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
|
||||||
|
|
||||||
|
/**************************** VARIABLES *****************************/
|
||||||
|
static const WORD k[64] = {
|
||||||
|
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
|
||||||
|
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
|
||||||
|
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
|
||||||
|
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
|
||||||
|
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
|
||||||
|
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
|
||||||
|
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
|
||||||
|
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
|
||||||
|
};
|
||||||
|
|
||||||
|
/*********************** FUNCTION DEFINITIONS ***********************/
|
||||||
|
void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
|
||||||
|
{
|
||||||
|
WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
|
||||||
|
|
||||||
|
for (i = 0, j = 0; i < 16; ++i, j += 4)
|
||||||
|
m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
|
||||||
|
for ( ; i < 64; ++i)
|
||||||
|
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
|
||||||
|
|
||||||
|
a = ctx->state[0];
|
||||||
|
b = ctx->state[1];
|
||||||
|
c = ctx->state[2];
|
||||||
|
d = ctx->state[3];
|
||||||
|
e = ctx->state[4];
|
||||||
|
f = ctx->state[5];
|
||||||
|
g = ctx->state[6];
|
||||||
|
h = ctx->state[7];
|
||||||
|
|
||||||
|
for (i = 0; i < 64; ++i) {
|
||||||
|
t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
|
||||||
|
t2 = EP0(a) + MAJ(a,b,c);
|
||||||
|
h = g;
|
||||||
|
g = f;
|
||||||
|
f = e;
|
||||||
|
e = d + t1;
|
||||||
|
d = c;
|
||||||
|
c = b;
|
||||||
|
b = a;
|
||||||
|
a = t1 + t2;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->state[0] += a;
|
||||||
|
ctx->state[1] += b;
|
||||||
|
ctx->state[2] += c;
|
||||||
|
ctx->state[3] += d;
|
||||||
|
ctx->state[4] += e;
|
||||||
|
ctx->state[5] += f;
|
||||||
|
ctx->state[6] += g;
|
||||||
|
ctx->state[7] += h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sha256_init(SHA256_CTX *ctx)
|
||||||
|
{
|
||||||
|
ctx->datalen = 0;
|
||||||
|
ctx->bitlen = 0;
|
||||||
|
ctx->state[0] = 0x6a09e667;
|
||||||
|
ctx->state[1] = 0xbb67ae85;
|
||||||
|
ctx->state[2] = 0x3c6ef372;
|
||||||
|
ctx->state[3] = 0xa54ff53a;
|
||||||
|
ctx->state[4] = 0x510e527f;
|
||||||
|
ctx->state[5] = 0x9b05688c;
|
||||||
|
ctx->state[6] = 0x1f83d9ab;
|
||||||
|
ctx->state[7] = 0x5be0cd19;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
|
||||||
|
{
|
||||||
|
WORD i;
|
||||||
|
|
||||||
|
for (i = 0; i < len; ++i) {
|
||||||
|
ctx->data[ctx->datalen] = data[i];
|
||||||
|
ctx->datalen++;
|
||||||
|
if (ctx->datalen == 64) {
|
||||||
|
sha256_transform(ctx, ctx->data);
|
||||||
|
ctx->bitlen += 512;
|
||||||
|
ctx->datalen = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sha256_final(SHA256_CTX *ctx, BYTE hash[])
|
||||||
|
{
|
||||||
|
WORD i;
|
||||||
|
|
||||||
|
i = ctx->datalen;
|
||||||
|
|
||||||
|
// Pad whatever data is left in the buffer.
|
||||||
|
if (ctx->datalen < 56) {
|
||||||
|
ctx->data[i++] = 0x80;
|
||||||
|
while (i < 56)
|
||||||
|
ctx->data[i++] = 0x00;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ctx->data[i++] = 0x80;
|
||||||
|
while (i < 64)
|
||||||
|
ctx->data[i++] = 0x00;
|
||||||
|
sha256_transform(ctx, ctx->data);
|
||||||
|
memset(ctx->data, 0, 56);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append to the padding the total message's length in bits and transform.
|
||||||
|
ctx->bitlen += ctx->datalen * 8;
|
||||||
|
ctx->data[63] = ctx->bitlen;
|
||||||
|
ctx->data[62] = ctx->bitlen >> 8;
|
||||||
|
ctx->data[61] = ctx->bitlen >> 16;
|
||||||
|
ctx->data[60] = ctx->bitlen >> 24;
|
||||||
|
ctx->data[59] = ctx->bitlen >> 32;
|
||||||
|
ctx->data[58] = ctx->bitlen >> 40;
|
||||||
|
ctx->data[57] = ctx->bitlen >> 48;
|
||||||
|
ctx->data[56] = ctx->bitlen >> 56;
|
||||||
|
sha256_transform(ctx, ctx->data);
|
||||||
|
|
||||||
|
// Since this implementation uses little endian byte ordering and SHA uses big endian,
|
||||||
|
// reverse all the bytes when copying the final state to the output hash.
|
||||||
|
for (i = 0; i < 4; ++i) {
|
||||||
|
hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
|
||||||
|
hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
|
||||||
|
hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
|
||||||
|
hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
|
||||||
|
hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
|
||||||
|
hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
|
||||||
|
hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
|
||||||
|
hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
|
||||||
|
}
|
||||||
|
}
|
35
src/sha256.h
Normal file
35
src/sha256.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*********************************************************************
|
||||||
|
* Filename: sha256.h
|
||||||
|
* Author: Brad Conte (brad AT bradconte.com)
|
||||||
|
* Copyright:
|
||||||
|
* Disclaimer: This code is presented "as is" without any guarantees.
|
||||||
|
* Details: Defines the API for the corresponding SHA1 implementation.
|
||||||
|
*********************************************************************/
|
||||||
|
|
||||||
|
#ifndef SHA256_H
|
||||||
|
#define SHA256_H
|
||||||
|
|
||||||
|
/*************************** HEADER FILES ***************************/
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/****************************** MACROS ******************************/
|
||||||
|
#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest
|
||||||
|
|
||||||
|
/**************************** DATA TYPES ****************************/
|
||||||
|
typedef uint8_t BYTE; // 8-bit byte
|
||||||
|
typedef uint32_t WORD; // 32-bit word
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BYTE data[64];
|
||||||
|
WORD datalen;
|
||||||
|
unsigned long long bitlen;
|
||||||
|
WORD state[8];
|
||||||
|
} SHA256_CTX;
|
||||||
|
|
||||||
|
/*********************** FUNCTION DECLARATIONS **********************/
|
||||||
|
void sha256_init(SHA256_CTX *ctx);
|
||||||
|
void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
|
||||||
|
void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
|
||||||
|
|
||||||
|
#endif // SHA256_H
|
@ -58,7 +58,8 @@ int siptlw(int c) {
|
|||||||
/* Test of the CPU is Little Endian and supports not aligned accesses.
|
/* Test of the CPU is Little Endian and supports not aligned accesses.
|
||||||
* Two interesting conditions to speedup the function that happen to be
|
* Two interesting conditions to speedup the function that happen to be
|
||||||
* in most of x86 servers. */
|
* in most of x86 servers. */
|
||||||
#if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__)
|
#if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__) \
|
||||||
|
|| defined (__aarch64__) || defined (__arm64__)
|
||||||
#define UNALIGNED_LE_CPU
|
#define UNALIGNED_LE_CPU
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ typedef struct streamNACK {
|
|||||||
|
|
||||||
/* Stream propagation informations, passed to functions in order to propagate
|
/* Stream propagation informations, passed to functions in order to propagate
|
||||||
* XCLAIM commands to AOF and slaves. */
|
* XCLAIM commands to AOF and slaves. */
|
||||||
typedef struct sreamPropInfo {
|
typedef struct streamPropInfo {
|
||||||
robj *keyname;
|
robj *keyname;
|
||||||
robj *groupname;
|
robj *groupname;
|
||||||
} streamPropInfo;
|
} streamPropInfo;
|
||||||
|
@ -242,17 +242,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
|||||||
* the current node is full. */
|
* the current node is full. */
|
||||||
if (lp != NULL) {
|
if (lp != NULL) {
|
||||||
if (server.stream_node_max_bytes &&
|
if (server.stream_node_max_bytes &&
|
||||||
lp_bytes > server.stream_node_max_bytes)
|
lp_bytes >= server.stream_node_max_bytes)
|
||||||
{
|
{
|
||||||
lp = NULL;
|
lp = NULL;
|
||||||
} else if (server.stream_node_max_entries) {
|
} else if (server.stream_node_max_entries) {
|
||||||
int64_t count = lpGetInteger(lpFirst(lp));
|
int64_t count = lpGetInteger(lpFirst(lp));
|
||||||
if (count > server.stream_node_max_entries) lp = NULL;
|
if (count >= server.stream_node_max_entries) lp = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int flags = STREAM_ITEM_FLAG_NONE;
|
int flags = STREAM_ITEM_FLAG_NONE;
|
||||||
if (lp == NULL || lp_bytes > server.stream_node_max_bytes) {
|
if (lp == NULL || lp_bytes >= server.stream_node_max_bytes) {
|
||||||
master_id = id;
|
master_id = id;
|
||||||
streamEncodeID(rax_key,&id);
|
streamEncodeID(rax_key,&id);
|
||||||
/* Create the listpack having the master entry ID and fields. */
|
/* Create the listpack having the master entry ID and fields. */
|
||||||
|
@ -326,6 +326,7 @@ size_t zmalloc_get_rss(void) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_JEMALLOC)
|
#if defined(USE_JEMALLOC)
|
||||||
|
|
||||||
int zmalloc_get_allocator_info(size_t *allocated,
|
int zmalloc_get_allocator_info(size_t *allocated,
|
||||||
size_t *active,
|
size_t *active,
|
||||||
size_t *resident) {
|
size_t *resident) {
|
||||||
@ -347,13 +348,44 @@ int zmalloc_get_allocator_info(size_t *allocated,
|
|||||||
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
|
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_jemalloc_bg_thread(int enable) {
|
||||||
|
/* let jemalloc do purging asynchronously, required when there's no traffic
|
||||||
|
* after flushdb */
|
||||||
|
char val = !!enable;
|
||||||
|
je_mallctl("background_thread", NULL, 0, &val, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int jemalloc_purge() {
|
||||||
|
/* return all unused (reserved) pages to the OS */
|
||||||
|
char tmp[32];
|
||||||
|
unsigned narenas = 0;
|
||||||
|
size_t sz = sizeof(unsigned);
|
||||||
|
if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
|
||||||
|
sprintf(tmp, "arena.%d.purge", narenas);
|
||||||
|
if (!je_mallctl(tmp, NULL, 0, NULL, 0))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
int zmalloc_get_allocator_info(size_t *allocated,
|
int zmalloc_get_allocator_info(size_t *allocated,
|
||||||
size_t *active,
|
size_t *active,
|
||||||
size_t *resident) {
|
size_t *resident) {
|
||||||
*allocated = *resident = *active = 0;
|
*allocated = *resident = *active = 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_jemalloc_bg_thread(int enable) {
|
||||||
|
((void)(enable));
|
||||||
|
}
|
||||||
|
|
||||||
|
int jemalloc_purge() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Get the sum of the specified field (converted form kb to bytes) in
|
/* Get the sum of the specified field (converted form kb to bytes) in
|
||||||
|
@ -86,6 +86,8 @@ size_t zmalloc_used_memory(void);
|
|||||||
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
|
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
|
||||||
size_t zmalloc_get_rss(void);
|
size_t zmalloc_get_rss(void);
|
||||||
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
|
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
|
||||||
|
void set_jemalloc_bg_thread(int enable);
|
||||||
|
int jemalloc_purge();
|
||||||
size_t zmalloc_get_private_dirty(long pid);
|
size_t zmalloc_get_private_dirty(long pid);
|
||||||
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
|
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
|
||||||
size_t zmalloc_get_memory_size(void);
|
size_t zmalloc_get_memory_size(void);
|
||||||
|
@ -115,3 +115,17 @@ start_server_and_kill_it [list "dir" $server_path] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start_server {} {
|
||||||
|
test {Test FLUSHALL aborts bgsave} {
|
||||||
|
r config set rdb-key-save-delay 1000
|
||||||
|
r debug populate 1000
|
||||||
|
r bgsave
|
||||||
|
assert_equal [s rdb_bgsave_in_progress] 1
|
||||||
|
r flushall
|
||||||
|
after 200
|
||||||
|
assert_equal [s rdb_bgsave_in_progress] 0
|
||||||
|
# make sure the server is still writable
|
||||||
|
r set x xx
|
||||||
|
}
|
||||||
|
}
|
@ -319,7 +319,7 @@ start_server {tags {"repl"}} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test {slave fails full sync and diskless load swapdb recoveres it} {
|
test {slave fails full sync and diskless load swapdb recovers it} {
|
||||||
start_server {tags {"repl"}} {
|
start_server {tags {"repl"}} {
|
||||||
set slave [srv 0 client]
|
set slave [srv 0 client]
|
||||||
set slave_host [srv 0 host]
|
set slave_host [srv 0 host]
|
||||||
|
@ -13,16 +13,28 @@ endif
|
|||||||
|
|
||||||
.SUFFIXES: .c .so .xo .o
|
.SUFFIXES: .c .so .xo .o
|
||||||
|
|
||||||
all: commandfilter.so testrdb.so
|
all: commandfilter.so testrdb.so fork.so infotest.so propagate.so
|
||||||
|
|
||||||
.c.xo:
|
.c.xo:
|
||||||
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||||
|
|
||||||
commandfilter.xo: ../../src/redismodule.h
|
commandfilter.xo: ../../src/redismodule.h
|
||||||
|
fork.xo: ../../src/redismodule.h
|
||||||
testrdb.xo: ../../src/redismodule.h
|
testrdb.xo: ../../src/redismodule.h
|
||||||
|
infotest.xo: ../../src/redismodule.h
|
||||||
|
propagate.xo: ../../src/redismodule.h
|
||||||
|
|
||||||
commandfilter.so: commandfilter.xo
|
commandfilter.so: commandfilter.xo
|
||||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||||
|
|
||||||
|
fork.so: fork.xo
|
||||||
|
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||||
|
|
||||||
testrdb.so: testrdb.xo
|
testrdb.so: testrdb.xo
|
||||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||||
|
|
||||||
|
infotest.so: infotest.xo
|
||||||
|
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||||
|
|
||||||
|
propagate.so: propagate.xo
|
||||||
|
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||||
|
84
tests/modules/fork.c
Normal file
84
tests/modules/fork.c
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#define REDISMODULE_EXPERIMENTAL_API
|
||||||
|
#include "redismodule.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define UNUSED(V) ((void) V)
|
||||||
|
|
||||||
|
int child_pid = -1;
|
||||||
|
int exitted_with_code = -1;
|
||||||
|
|
||||||
|
void done_handler(int exitcode, int bysignal, void *user_data) {
|
||||||
|
child_pid = -1;
|
||||||
|
exitted_with_code = exitcode;
|
||||||
|
assert(user_data==(void*)0xdeadbeef);
|
||||||
|
UNUSED(bysignal);
|
||||||
|
}
|
||||||
|
|
||||||
|
int fork_create(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
long long code_to_exit_with;
|
||||||
|
if (argc != 2) {
|
||||||
|
RedisModule_WrongArity(ctx);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
RedisModule_StringToLongLong(argv[1], &code_to_exit_with);
|
||||||
|
exitted_with_code = -1;
|
||||||
|
child_pid = RedisModule_Fork(done_handler, (void*)0xdeadbeef);
|
||||||
|
if (child_pid < 0) {
|
||||||
|
RedisModule_ReplyWithError(ctx, "Fork failed");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
} else if (child_pid > 0) {
|
||||||
|
/* parent */
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, child_pid);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* child */
|
||||||
|
RedisModule_Log(ctx, "notice", "fork child started");
|
||||||
|
usleep(200000);
|
||||||
|
RedisModule_Log(ctx, "notice", "fork child exiting");
|
||||||
|
RedisModule_ExitFromChild(code_to_exit_with);
|
||||||
|
/* unreachable */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fork_exitcode(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
UNUSED(argv);
|
||||||
|
UNUSED(argc);
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, exitted_with_code);
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fork_kill(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
UNUSED(argv);
|
||||||
|
UNUSED(argc);
|
||||||
|
if (RedisModule_KillForkChild(child_pid) != REDISMODULE_OK)
|
||||||
|
RedisModule_ReplyWithError(ctx, "KillForkChild failed");
|
||||||
|
else
|
||||||
|
RedisModule_ReplyWithLongLong(ctx, 1);
|
||||||
|
child_pid = -1;
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
UNUSED(argv);
|
||||||
|
UNUSED(argc);
|
||||||
|
if (RedisModule_Init(ctx,"fork",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"fork.create", fork_create,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"fork.exitcode", fork_exitcode,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"fork.kill", fork_kill,"",0,0,0) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
41
tests/modules/infotest.c
Normal file
41
tests/modules/infotest.c
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#include "redismodule.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) {
|
||||||
|
RedisModule_InfoAddSection(ctx, "");
|
||||||
|
RedisModule_InfoAddFieldLongLong(ctx, "global", -2);
|
||||||
|
|
||||||
|
RedisModule_InfoAddSection(ctx, "Spanish");
|
||||||
|
RedisModule_InfoAddFieldCString(ctx, "uno", "one");
|
||||||
|
RedisModule_InfoAddFieldLongLong(ctx, "dos", 2);
|
||||||
|
|
||||||
|
RedisModule_InfoAddSection(ctx, "Italian");
|
||||||
|
RedisModule_InfoAddFieldLongLong(ctx, "due", 2);
|
||||||
|
RedisModule_InfoAddFieldDouble(ctx, "tre", 3.3);
|
||||||
|
|
||||||
|
RedisModule_InfoAddSection(ctx, "keyspace");
|
||||||
|
RedisModule_InfoBeginDictField(ctx, "db0");
|
||||||
|
RedisModule_InfoAddFieldLongLong(ctx, "keys", 3);
|
||||||
|
RedisModule_InfoAddFieldLongLong(ctx, "expires", 1);
|
||||||
|
RedisModule_InfoEndDictField(ctx);
|
||||||
|
|
||||||
|
if (for_crash_report) {
|
||||||
|
RedisModule_InfoAddSection(ctx, "Klingon");
|
||||||
|
RedisModule_InfoAddFieldCString(ctx, "one", "wa’");
|
||||||
|
RedisModule_InfoAddFieldCString(ctx, "two", "cha’");
|
||||||
|
RedisModule_InfoAddFieldCString(ctx, "three", "wej");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
if (RedisModule_Init(ctx,"infotest",1,REDISMODULE_APIVER_1)
|
||||||
|
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_RegisterInfoFunc(ctx, InfoFunc) == REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
104
tests/modules/propagate.c
Normal file
104
tests/modules/propagate.c
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/* This module is used to test the propagation (replication + AOF) of
|
||||||
|
* commands, via the RedisModule_Replicate() interface, in asynchronous
|
||||||
|
* contexts, such as callbacks not implementing commands, and thread safe
|
||||||
|
* contexts.
|
||||||
|
*
|
||||||
|
* We create a timer callback and a threads using a thread safe context.
|
||||||
|
* Using both we try to propagate counters increments, and later we check
|
||||||
|
* if the replica contains the changes as expected.
|
||||||
|
*
|
||||||
|
* -----------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define REDISMODULE_EXPERIMENTAL_API
|
||||||
|
#include "redismodule.h"
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
/* Timer callback. */
|
||||||
|
void timerHandler(RedisModuleCtx *ctx, void *data) {
|
||||||
|
REDISMODULE_NOT_USED(ctx);
|
||||||
|
REDISMODULE_NOT_USED(data);
|
||||||
|
|
||||||
|
static int times = 0;
|
||||||
|
|
||||||
|
RedisModule_Replicate(ctx,"INCR","c","timer");
|
||||||
|
times++;
|
||||||
|
|
||||||
|
if (times < 10)
|
||||||
|
RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
|
||||||
|
else
|
||||||
|
times = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The thread entry point. */
|
||||||
|
void *threadMain(void *arg) {
|
||||||
|
REDISMODULE_NOT_USED(arg);
|
||||||
|
RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(NULL);
|
||||||
|
RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
RedisModule_ThreadSafeContextLock(ctx);
|
||||||
|
RedisModule_Replicate(ctx,"INCR","c","thread");
|
||||||
|
RedisModule_ThreadSafeContextUnlock(ctx);
|
||||||
|
}
|
||||||
|
RedisModule_FreeThreadSafeContext(ctx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||||
|
{
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
|
||||||
|
RedisModuleTimerID timer_id =
|
||||||
|
RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
|
||||||
|
REDISMODULE_NOT_USED(timer_id);
|
||||||
|
|
||||||
|
pthread_t tid;
|
||||||
|
if (pthread_create(&tid,NULL,threadMain,NULL) != 0)
|
||||||
|
return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
|
||||||
|
REDISMODULE_NOT_USED(tid);
|
||||||
|
|
||||||
|
RedisModule_ReplyWithSimpleString(ctx,"OK");
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||||
|
REDISMODULE_NOT_USED(argv);
|
||||||
|
REDISMODULE_NOT_USED(argc);
|
||||||
|
|
||||||
|
if (RedisModule_Init(ctx,"propagate-test",1,REDISMODULE_APIVER_1)
|
||||||
|
== REDISMODULE_ERR) return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
if (RedisModule_CreateCommand(ctx,"propagate-test",
|
||||||
|
propagateTestCommand,
|
||||||
|
"",1,1,1) == REDISMODULE_ERR)
|
||||||
|
return REDISMODULE_ERR;
|
||||||
|
return REDISMODULE_OK;
|
||||||
|
}
|
@ -15,6 +15,8 @@ RedisModuleString *after_str = NULL;
|
|||||||
|
|
||||||
void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
|
void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
|
||||||
int count = RedisModule_LoadSigned(rdb);
|
int count = RedisModule_LoadSigned(rdb);
|
||||||
|
if (RedisModule_IsIOError(rdb))
|
||||||
|
return NULL;
|
||||||
assert(count==1);
|
assert(count==1);
|
||||||
assert(encver==1);
|
assert(encver==1);
|
||||||
RedisModuleString *str = RedisModule_LoadString(rdb);
|
RedisModuleString *str = RedisModule_LoadString(rdb);
|
||||||
@ -57,6 +59,8 @@ int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
|
|||||||
RedisModule_FreeString(ctx, before_str);
|
RedisModule_FreeString(ctx, before_str);
|
||||||
before_str = NULL;
|
before_str = NULL;
|
||||||
int count = RedisModule_LoadSigned(rdb);
|
int count = RedisModule_LoadSigned(rdb);
|
||||||
|
if (RedisModule_IsIOError(rdb))
|
||||||
|
return REDISMODULE_ERR;
|
||||||
if (count)
|
if (count)
|
||||||
before_str = RedisModule_LoadString(rdb);
|
before_str = RedisModule_LoadString(rdb);
|
||||||
} else {
|
} else {
|
||||||
@ -64,13 +68,18 @@ int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
|
|||||||
RedisModule_FreeString(ctx, after_str);
|
RedisModule_FreeString(ctx, after_str);
|
||||||
after_str = NULL;
|
after_str = NULL;
|
||||||
int count = RedisModule_LoadSigned(rdb);
|
int count = RedisModule_LoadSigned(rdb);
|
||||||
|
if (RedisModule_IsIOError(rdb))
|
||||||
|
return REDISMODULE_ERR;
|
||||||
if (count)
|
if (count)
|
||||||
after_str = RedisModule_LoadString(rdb);
|
after_str = RedisModule_LoadString(rdb);
|
||||||
}
|
}
|
||||||
|
if (RedisModule_IsIOError(rdb))
|
||||||
|
return REDISMODULE_ERR;
|
||||||
return REDISMODULE_OK;
|
return REDISMODULE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void testrdb_type_free(void *value) {
|
void testrdb_type_free(void *value) {
|
||||||
|
if (value)
|
||||||
RedisModule_FreeString(NULL, (RedisModuleString*)value);
|
RedisModule_FreeString(NULL, (RedisModuleString*)value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +180,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||||||
if (RedisModule_Init(ctx,"testrdb",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
|
if (RedisModule_Init(ctx,"testrdb",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
|
||||||
return REDISMODULE_ERR;
|
return REDISMODULE_ERR;
|
||||||
|
|
||||||
|
RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS);
|
||||||
|
|
||||||
if (argc > 0)
|
if (argc > 0)
|
||||||
RedisModule_StringToLongLong(argv[0], &conf_aux_count);
|
RedisModule_StringToLongLong(argv[0], &conf_aux_count);
|
||||||
|
|
||||||
|
@ -15,6 +15,12 @@ proc assert {condition} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proc assert_no_match {pattern value} {
|
||||||
|
if {[string match $pattern $value]} {
|
||||||
|
error "assertion:Expected '$value' to not match '$pattern'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
proc assert_match {pattern value} {
|
proc assert_match {pattern value} {
|
||||||
if {![string match $pattern $value]} {
|
if {![string match $pattern $value]} {
|
||||||
error "assertion:Expected '$value' to match '$pattern'"
|
error "assertion:Expected '$value' to match '$pattern'"
|
||||||
|
@ -35,6 +35,32 @@ start_server {tags {"acl"}} {
|
|||||||
set e
|
set e
|
||||||
} {*WRONGPASS*}
|
} {*WRONGPASS*}
|
||||||
|
|
||||||
|
test {Test password hashes can be added} {
|
||||||
|
r ACL setuser newuser #34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6
|
||||||
|
catch {r AUTH newuser passwd4} e
|
||||||
|
assert {$e eq "OK"}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test password hashes validate input} {
|
||||||
|
# Validate Length
|
||||||
|
catch {r ACL setuser newuser #34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e} e
|
||||||
|
# Validate character outside set
|
||||||
|
catch {r ACL setuser newuser #34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4eq} e
|
||||||
|
set e
|
||||||
|
} {*Error in ACL SETUSER modifier*}
|
||||||
|
|
||||||
|
test {ACL GETUSER returns the password hash instead of the actual password} {
|
||||||
|
set passstr [dict get [r ACL getuser newuser] passwords]
|
||||||
|
assert_match {*34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6*} $passstr
|
||||||
|
assert_no_match {*passwd4*} $passstr
|
||||||
|
}
|
||||||
|
|
||||||
|
test {Test hashed passwords removal} {
|
||||||
|
r ACL setuser newuser !34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6
|
||||||
|
set passstr [dict get [r ACL getuser newuser] passwords]
|
||||||
|
assert_no_match {*34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6*} $passstr
|
||||||
|
}
|
||||||
|
|
||||||
test {By default users are not able to access any command} {
|
test {By default users are not able to access any command} {
|
||||||
catch {r SET foo bar} e
|
catch {r SET foo bar} e
|
||||||
set e
|
set e
|
||||||
@ -67,7 +93,7 @@ start_server {tags {"acl"}} {
|
|||||||
set e
|
set e
|
||||||
} {*NOPERM*}
|
} {*NOPERM*}
|
||||||
|
|
||||||
test {ACLs can include or excluse whole classes of commands} {
|
test {ACLs can include or exclude whole classes of commands} {
|
||||||
r ACL setuser newuser -@all +@set +acl
|
r ACL setuser newuser -@all +@set +acl
|
||||||
r SADD myset a b c; # Should not raise an error
|
r SADD myset a b c; # Should not raise an error
|
||||||
r ACL setuser newuser +@all -@string
|
r ACL setuser newuser +@all -@string
|
||||||
|
@ -129,7 +129,7 @@ start_server {tags {"geo"}} {
|
|||||||
r del points
|
r del points
|
||||||
r geoadd points -5.6 42.6 test
|
r geoadd points -5.6 42.6 test
|
||||||
lindex [r geohash points test] 0
|
lindex [r geohash points test] 0
|
||||||
} {ezs42e44yx0}
|
} {ezs42e44yx}
|
||||||
|
|
||||||
test {GEOPOS simple} {
|
test {GEOPOS simple} {
|
||||||
r del points
|
r del points
|
||||||
|
32
tests/unit/moduleapi/fork.tcl
Normal file
32
tests/unit/moduleapi/fork.tcl
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
set testmodule [file normalize tests/modules/fork.so]
|
||||||
|
|
||||||
|
proc count_log_message {pattern} {
|
||||||
|
set result [exec grep -c $pattern < [srv 0 stdout]]
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server {tags {"modules"}} {
|
||||||
|
r module load $testmodule
|
||||||
|
|
||||||
|
test {Module fork} {
|
||||||
|
# the argument to fork.create is the exitcode on termination
|
||||||
|
r fork.create 3
|
||||||
|
wait_for_condition 20 100 {
|
||||||
|
[r fork.exitcode] != -1
|
||||||
|
} else {
|
||||||
|
fail "fork didn't terminate"
|
||||||
|
}
|
||||||
|
r fork.exitcode
|
||||||
|
} {3}
|
||||||
|
|
||||||
|
test {Module fork kill} {
|
||||||
|
r fork.create 3
|
||||||
|
after 20
|
||||||
|
r fork.kill
|
||||||
|
after 100
|
||||||
|
|
||||||
|
assert {[count_log_message "fork child started"] eq "2"}
|
||||||
|
assert {[count_log_message "Received SIGUSR1 in child"] eq "1"}
|
||||||
|
assert {[count_log_message "fork child exiting"] eq "1"}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
63
tests/unit/moduleapi/infotest.tcl
Normal file
63
tests/unit/moduleapi/infotest.tcl
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
set testmodule [file normalize tests/modules/infotest.so]
|
||||||
|
|
||||||
|
# Return value for INFO property
|
||||||
|
proc field {info property} {
|
||||||
|
if {[regexp "\r\n$property:(.*?)\r\n" $info _ value]} {
|
||||||
|
set _ $value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start_server {tags {"modules"}} {
|
||||||
|
r module load $testmodule log-key 0
|
||||||
|
|
||||||
|
test {module info all} {
|
||||||
|
set info [r info all]
|
||||||
|
# info all does not contain modules
|
||||||
|
assert { ![string match "*Spanish*" $info] }
|
||||||
|
assert { ![string match "*infotest_*" $info] }
|
||||||
|
assert { [string match "*used_memory*" $info] }
|
||||||
|
}
|
||||||
|
|
||||||
|
test {module info everything} {
|
||||||
|
set info [r info everything]
|
||||||
|
# info everything contains all default sections, but not ones for crash report
|
||||||
|
assert { [string match "*infotest_global*" $info] }
|
||||||
|
assert { [string match "*Spanish*" $info] }
|
||||||
|
assert { [string match "*Italian*" $info] }
|
||||||
|
assert { [string match "*used_memory*" $info] }
|
||||||
|
assert { ![string match "*Klingon*" $info] }
|
||||||
|
field $info infotest_dos
|
||||||
|
} {2}
|
||||||
|
|
||||||
|
test {module info modules} {
|
||||||
|
set info [r info modules]
|
||||||
|
# info all does not contain modules
|
||||||
|
assert { [string match "*Spanish*" $info] }
|
||||||
|
assert { [string match "*infotest_global*" $info] }
|
||||||
|
assert { ![string match "*used_memory*" $info] }
|
||||||
|
}
|
||||||
|
|
||||||
|
test {module info one module} {
|
||||||
|
set info [r info INFOTEST]
|
||||||
|
# info all does not contain modules
|
||||||
|
assert { [string match "*Spanish*" $info] }
|
||||||
|
assert { ![string match "*used_memory*" $info] }
|
||||||
|
field $info infotest_global
|
||||||
|
} {-2}
|
||||||
|
|
||||||
|
test {module info one section} {
|
||||||
|
set info [r info INFOTEST_SPANISH]
|
||||||
|
assert { ![string match "*used_memory*" $info] }
|
||||||
|
assert { ![string match "*Italian*" $info] }
|
||||||
|
assert { ![string match "*infotest_global*" $info] }
|
||||||
|
field $info infotest_uno
|
||||||
|
} {one}
|
||||||
|
|
||||||
|
test {module info dict} {
|
||||||
|
set info [r info infotest_keyspace]
|
||||||
|
set keyspace [field $info infotest_db0]
|
||||||
|
set keys [scan [regexp -inline {keys\=([\d]*)} $keyspace] keys=%d]
|
||||||
|
} {3}
|
||||||
|
|
||||||
|
# TODO: test crash report.
|
||||||
|
}
|
30
tests/unit/moduleapi/propagate.tcl
Normal file
30
tests/unit/moduleapi/propagate.tcl
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
set testmodule [file normalize tests/modules/propagate.so]
|
||||||
|
|
||||||
|
tags "modules" {
|
||||||
|
test {Modules can propagate in async and threaded contexts} {
|
||||||
|
start_server {} {
|
||||||
|
set replica [srv 0 client]
|
||||||
|
set replica_host [srv 0 host]
|
||||||
|
set replica_port [srv 0 port]
|
||||||
|
start_server [list overrides [list loadmodule "$testmodule"]] {
|
||||||
|
set master [srv 0 client]
|
||||||
|
set master_host [srv 0 host]
|
||||||
|
set master_port [srv 0 port]
|
||||||
|
|
||||||
|
# Start the replication process...
|
||||||
|
$replica replicaof $master_host $master_port
|
||||||
|
wait_for_sync $replica
|
||||||
|
|
||||||
|
after 1000
|
||||||
|
$master propagate-test
|
||||||
|
|
||||||
|
wait_for_condition 5000 10 {
|
||||||
|
([$replica get timer] eq "10") && \
|
||||||
|
([$replica get thread] eq "10")
|
||||||
|
} else {
|
||||||
|
fail "The two counters don't match the expected value."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -56,7 +56,67 @@ tags "modules" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tags {repl} {
|
||||||
|
test {diskless loading short read with module} {
|
||||||
|
start_server [list overrides [list loadmodule "$testmodule"]] {
|
||||||
|
set replica [srv 0 client]
|
||||||
|
set replica_host [srv 0 host]
|
||||||
|
set replica_port [srv 0 port]
|
||||||
|
start_server [list overrides [list loadmodule "$testmodule"]] {
|
||||||
|
set master [srv 0 client]
|
||||||
|
set master_host [srv 0 host]
|
||||||
|
set master_port [srv 0 port]
|
||||||
|
|
||||||
# TODO: test short read handling
|
# Set master and replica to use diskless replication
|
||||||
|
$master config set repl-diskless-sync yes
|
||||||
|
$master config set rdbcompression no
|
||||||
|
$replica config set repl-diskless-load swapdb
|
||||||
|
for {set k 0} {$k < 30} {incr k} {
|
||||||
|
r testrdb.set.key key$k [string repeat A [expr {int(rand()*1000000)}]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start the replication process...
|
||||||
|
$master config set repl-diskless-sync-delay 0
|
||||||
|
$replica replicaof $master_host $master_port
|
||||||
|
|
||||||
|
# kill the replication at various points
|
||||||
|
set attempts 3
|
||||||
|
if {$::accurate} { set attempts 10 }
|
||||||
|
for {set i 0} {$i < $attempts} {incr i} {
|
||||||
|
# wait for the replica to start reading the rdb
|
||||||
|
# using the log file since the replica only responds to INFO once in 2mb
|
||||||
|
wait_for_log_message -1 "*Loading DB in memory*" 5 2000 1
|
||||||
|
|
||||||
|
# add some additional random sleep so that we kill the master on a different place each time
|
||||||
|
after [expr {int(rand()*100)}]
|
||||||
|
|
||||||
|
# kill the replica connection on the master
|
||||||
|
set killed [$master client kill type replica]
|
||||||
|
|
||||||
|
if {[catch {
|
||||||
|
set res [wait_for_log_message -1 "*Internal error in RDB*" 5 100 10]
|
||||||
|
if {$::verbose} {
|
||||||
|
puts $res
|
||||||
|
}
|
||||||
|
}]} {
|
||||||
|
puts "failed triggering short read"
|
||||||
|
# force the replica to try another full sync
|
||||||
|
$master client kill type replica
|
||||||
|
$master set asdf asdf
|
||||||
|
# the side effect of resizing the backlog is that it is flushed (16k is the min size)
|
||||||
|
$master config set repl-backlog-size [expr {16384 + $i}]
|
||||||
|
}
|
||||||
|
# wait for loading to stop (fail)
|
||||||
|
wait_for_condition 100 10 {
|
||||||
|
[s -1 loading] eq 0
|
||||||
|
} else {
|
||||||
|
fail "Replica didn't disconnect"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# enable fast shutdown
|
||||||
|
$master config set rdb-key-save-delay 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,4 +306,18 @@ start_server {tags {"multi"}} {
|
|||||||
}
|
}
|
||||||
close_replication_stream $repl
|
close_replication_stream $repl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test {DISCARD should not fail during OOM} {
|
||||||
|
set rd [redis_deferring_client]
|
||||||
|
$rd config set maxmemory 1
|
||||||
|
assert {[$rd read] eq {OK}}
|
||||||
|
r multi
|
||||||
|
catch {r set x 1} e
|
||||||
|
assert_match {OOM*} $e
|
||||||
|
r discard
|
||||||
|
$rd config set maxmemory 0
|
||||||
|
assert {[$rd read] eq {OK}}
|
||||||
|
$rd close
|
||||||
|
r ping
|
||||||
|
} {PONG}
|
||||||
}
|
}
|
||||||
|
@ -355,12 +355,12 @@ start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries
|
|||||||
r XADD mystream * xitem v
|
r XADD mystream * xitem v
|
||||||
}
|
}
|
||||||
r XTRIM mystream MAXLEN ~ 85
|
r XTRIM mystream MAXLEN ~ 85
|
||||||
assert {[r xlen mystream] == 89}
|
assert {[r xlen mystream] == 90}
|
||||||
r config set stream-node-max-entries 1
|
r config set stream-node-max-entries 1
|
||||||
r debug loadaof
|
r debug loadaof
|
||||||
r XADD mystream * xitem v
|
r XADD mystream * xitem v
|
||||||
incr j
|
incr j
|
||||||
assert {[r xlen mystream] == 90}
|
assert {[r xlen mystream] == 91}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ To create a cluster, follow these steps:
|
|||||||
number of instances you want to create.
|
number of instances you want to create.
|
||||||
2. Use "./create-cluster start" in order to run the instances.
|
2. Use "./create-cluster start" in order to run the instances.
|
||||||
3. Use "./create-cluster create" in order to execute redis-cli --cluster create, so that
|
3. Use "./create-cluster create" in order to execute redis-cli --cluster create, so that
|
||||||
an actual Redis cluster will be created.
|
an actual Redis cluster will be created. (If you're accessing your setup via a local container, ensure that the CLUSTER_HOST value is changed to your local IP)
|
||||||
4. Now you are ready to play with the cluster. AOF files and logs for each instances are created in the current directory.
|
4. Now you are ready to play with the cluster. AOF files and logs for each instances are created in the current directory.
|
||||||
|
|
||||||
In order to stop a cluster:
|
In order to stop a cluster:
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Settings
|
# Settings
|
||||||
|
CLUSTER_HOST=127.0.0.1
|
||||||
PORT=30000
|
PORT=30000
|
||||||
TIMEOUT=2000
|
TIMEOUT=2000
|
||||||
NODES=6
|
NODES=6
|
||||||
REPLICAS=1
|
REPLICAS=1
|
||||||
|
PROTECTED_MODE=yes
|
||||||
|
|
||||||
# You may want to put the above config parameters into config.sh in order to
|
# You may want to put the above config parameters into config.sh in order to
|
||||||
# override the defaults without modifying this script.
|
# override the defaults without modifying this script.
|
||||||
@ -22,7 +24,7 @@ then
|
|||||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||||
PORT=$((PORT+1))
|
PORT=$((PORT+1))
|
||||||
echo "Starting $PORT"
|
echo "Starting $PORT"
|
||||||
../../src/redis-server --port $PORT --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes
|
../../src/redis-server --port $PORT --protected-mode $PROTECTED_MODE --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes
|
||||||
done
|
done
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@ -32,7 +34,7 @@ then
|
|||||||
HOSTS=""
|
HOSTS=""
|
||||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||||
PORT=$((PORT+1))
|
PORT=$((PORT+1))
|
||||||
HOSTS="$HOSTS 127.0.0.1:$PORT"
|
HOSTS="$HOSTS $CLUSTER_HOST:$PORT"
|
||||||
done
|
done
|
||||||
../../src/redis-cli --cluster create $HOSTS --cluster-replicas $REPLICAS
|
../../src/redis-cli --cluster create $HOSTS --cluster-replicas $REPLICAS
|
||||||
exit 0
|
exit 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user