Merge remote-tracking branch 'upstream/unstable' into tls
This commit is contained in:
commit
0db3b0a0ff
33
deps/hiredis/hiredis.c
vendored
33
deps/hiredis/hiredis.c
vendored
@ -121,21 +121,34 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
|
||||
if (r == NULL)
|
||||
return NULL;
|
||||
|
||||
buf = malloc(len+1);
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
assert(task->type == REDIS_REPLY_ERROR ||
|
||||
task->type == REDIS_REPLY_STATUS ||
|
||||
task->type == REDIS_REPLY_STRING);
|
||||
task->type == REDIS_REPLY_STRING ||
|
||||
task->type == REDIS_REPLY_VERB);
|
||||
|
||||
/* Copy string value */
|
||||
memcpy(buf,str,len);
|
||||
buf[len] = '\0';
|
||||
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);
|
||||
if (buf == NULL) {
|
||||
freeReplyObject(r);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(buf,str,len);
|
||||
buf[len] = '\0';
|
||||
r->len = len;
|
||||
}
|
||||
r->str = buf;
|
||||
r->len = len;
|
||||
|
||||
if (task->parent) {
|
||||
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 */
|
||||
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
|
||||
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 */
|
||||
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
|
||||
} 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. */
|
||||
bytelen += len+2; /* include \r\n */
|
||||
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)
|
||||
obj = r->fn->createString(cur,s+2,len);
|
||||
else
|
||||
obj = (void*)REDIS_REPLY_STRING;
|
||||
obj = (void*)(long)cur->type;
|
||||
success = 1;
|
||||
}
|
||||
}
|
||||
@ -524,6 +532,9 @@ static int processItem(redisReader *r) {
|
||||
case '#':
|
||||
cur->type = REDIS_REPLY_BOOL;
|
||||
break;
|
||||
case '=':
|
||||
cur->type = REDIS_REPLY_VERB;
|
||||
break;
|
||||
default:
|
||||
__redisReaderSetErrorProtocolByte(r,*p);
|
||||
return REDIS_ERR;
|
||||
@ -544,6 +555,7 @@ static int processItem(redisReader *r) {
|
||||
case REDIS_REPLY_BOOL:
|
||||
return processLineItem(r);
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_VERB:
|
||||
return processBulkItem(r);
|
||||
case REDIS_REPLY_ARRAY:
|
||||
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_DOUBLE 7
|
||||
#define REDIS_REPLY_BOOL 8
|
||||
#define REDIS_REPLY_VERB 9
|
||||
#define REDIS_REPLY_MAP 9
|
||||
#define REDIS_REPLY_SET 10
|
||||
#define REDIS_REPLY_ATTR 11
|
||||
#define REDIS_REPLY_PUSH 12
|
||||
#define REDIS_REPLY_BIGNUM 13
|
||||
#define REDIS_REPLY_VERB 14
|
||||
|
||||
#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);
|
||||
for (unsigned i = 0; i < max_background_threads; 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) {
|
||||
num_runs += info->tot_n_runs;
|
||||
nstime_add(&stats->run_interval, &info->tot_sleep_time);
|
||||
|
@ -13,4 +13,4 @@ then
|
||||
fi
|
||||
|
||||
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_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_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
|
||||
|
91
src/acl.c
91
src/acl.c
@ -28,6 +28,7 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "sha256.h"
|
||||
#include <fcntl.h>
|
||||
|
||||
/* =============================================================================
|
||||
@ -93,6 +94,9 @@ void ACLResetSubcommandsForCommand(user *u, unsigned long id);
|
||||
void ACLResetSubcommands(user *u);
|
||||
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
|
||||
* ==========================================================================*/
|
||||
@ -139,6 +143,25 @@ int time_independent_strcmp(char *a, char *b) {
|
||||
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
|
||||
* ==========================================================================*/
|
||||
@ -502,7 +525,7 @@ sds ACLDescribeUser(user *u) {
|
||||
listRewind(u->passwords,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
sds thispass = listNodeValue(ln);
|
||||
res = sdscatlen(res,">",1);
|
||||
res = sdscatlen(res,"#",1);
|
||||
res = sdscatsds(res,thispass);
|
||||
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.
|
||||
* For example >mypass will add "mypass" to the list.
|
||||
* 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.
|
||||
* !<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
|
||||
* is flagged as requiring no password: it means that every
|
||||
* 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
|
||||
* almost surely an error on the user side.
|
||||
* 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) {
|
||||
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")) {
|
||||
u->flags &= ~USER_FLAG_NOPASS;
|
||||
listEmpty(u->passwords);
|
||||
} else if (op[0] == '>') {
|
||||
sds newpass = sdsnewlen(op+1,oplen-1);
|
||||
} else if (op[0] == '>' || op[0] == '#') {
|
||||
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);
|
||||
/* 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;
|
||||
} else if (op[0] == '<') {
|
||||
sds delpass = sdsnewlen(op+1,oplen-1);
|
||||
} else if (op[0] == '<' || op[0] == '!') {
|
||||
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);
|
||||
sdsfree(delpass);
|
||||
if (ln) {
|
||||
@ -724,7 +789,10 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
||||
sds newpat = sdsnewlen(op+1,oplen-1);
|
||||
listNode *ln = listSearchKey(u->patterns,newpat);
|
||||
/* 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;
|
||||
} else if (op[0] == '+' && op[1] != '@') {
|
||||
if (strchr(op,'|') == NULL) {
|
||||
@ -822,6 +890,9 @@ char *ACLSetUserStringError(void) {
|
||||
else if (errno == ENODEV)
|
||||
errmsg = "The password you are trying to remove from the user does "
|
||||
"not exist";
|
||||
else if (errno == EBADMSG)
|
||||
errmsg = "The password hash must be exactly 64 characters and contain "
|
||||
"only lowercase hexadecimal characters";
|
||||
return errmsg;
|
||||
}
|
||||
|
||||
@ -879,11 +950,15 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(u->passwords,&li);
|
||||
sds hashed = ACLHashPassword(password->ptr,sdslen(password->ptr));
|
||||
while((ln = listNext(&li))) {
|
||||
sds thispass = listNodeValue(ln);
|
||||
if (!time_independent_strcmp(password->ptr, thispass))
|
||||
if (!time_independent_strcmp(hashed, thispass)) {
|
||||
sdsfree(hashed);
|
||||
return C_OK;
|
||||
}
|
||||
}
|
||||
sdsfree(hashed);
|
||||
|
||||
/* If we reached this point, no password matched. */
|
||||
errno = EINVAL;
|
||||
|
38
src/aof.c
38
src/aof.c
@ -264,9 +264,9 @@ int startAppendOnly(void) {
|
||||
strerror(errno));
|
||||
return C_ERR;
|
||||
}
|
||||
if (server.rdb_child_pid != -1) {
|
||||
if (hasActiveChildProcess() && server.aof_child_pid == -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 {
|
||||
/* 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
|
||||
@ -399,7 +399,7 @@ void flushAppendOnlyFile(int force) {
|
||||
* useful for graphing / monitoring purposes. */
|
||||
if (sync_in_progress) {
|
||||
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);
|
||||
} else {
|
||||
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
||||
@ -495,9 +495,8 @@ void flushAppendOnlyFile(int force) {
|
||||
try_fsync:
|
||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||
* children doing I/O in the background. */
|
||||
if (server.aof_no_fsync_on_rewrite &&
|
||||
(server.aof_child_pid != -1 || server.rdb_child_pid != -1))
|
||||
return;
|
||||
if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess())
|
||||
return;
|
||||
|
||||
/* Perform the fsync if needed. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
@ -1569,39 +1568,24 @@ void aofClosePipes(void) {
|
||||
*/
|
||||
int rewriteAppendOnlyFileBackground(void) {
|
||||
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;
|
||||
openChildInfoPipe();
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
if ((childpid = redisFork()) == 0) {
|
||||
char tmpfile[256];
|
||||
|
||||
/* Child */
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("redis-aof-rewrite");
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
|
||||
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||
|
||||
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);
|
||||
sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
|
||||
exitFromChild(0);
|
||||
} else {
|
||||
exitFromChild(1);
|
||||
}
|
||||
} 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) {
|
||||
closeChildInfoPipe();
|
||||
serverLog(LL_WARNING,
|
||||
@ -1615,7 +1599,6 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
server.aof_rewrite_scheduled = 0;
|
||||
server.aof_rewrite_time_start = time(NULL);
|
||||
server.aof_child_pid = childpid;
|
||||
updateDictResizePolicy();
|
||||
/* We set appendseldb to -1 in order to force the next call to the
|
||||
* feedAppendOnlyFile() to issue a SELECT command, so the differences
|
||||
* accumulated by the parent into server.aof_rewrite_buf will start
|
||||
@ -1630,13 +1613,14 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
void bgrewriteaofCommand(client *c) {
|
||||
if (server.aof_child_pid != -1) {
|
||||
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;
|
||||
addReplyStatus(c,"Background append only file rewriting scheduled");
|
||||
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
||||
addReplyStatus(c,"Background append only file rewriting started");
|
||||
} 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;
|
||||
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
|
||||
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
|
||||
* will call the function to process the packet. And so forth. */
|
||||
void clusterReadHandler(connection *conn) {
|
||||
char buf[sizeof(clusterMsg)];
|
||||
clusterMsg buf[1];
|
||||
ssize_t nread;
|
||||
clusterMsg *hdr;
|
||||
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. */
|
||||
void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
||||
unsigned char buf[sizeof(clusterMsg)], *payload;
|
||||
unsigned char *payload;
|
||||
clusterMsg buf[1];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
uint32_t totlen;
|
||||
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 */
|
||||
if (totlen < sizeof(buf)) {
|
||||
payload = buf;
|
||||
payload = (unsigned char*)buf;
|
||||
} else {
|
||||
payload = zmalloc(totlen);
|
||||
memcpy(payload,hdr,sizeof(*hdr));
|
||||
@ -2622,7 +2623,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
|
||||
|
||||
decrRefCount(channel);
|
||||
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.
|
||||
@ -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
|
||||
* nodes to do the same ASAP. */
|
||||
void clusterSendFail(char *nodename) {
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg buf[1];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
|
||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL);
|
||||
@ -2643,7 +2644,7 @@ void clusterSendFail(char *nodename) {
|
||||
* slots configuration. The node name, slots bitmap, and configEpoch info
|
||||
* are included. */
|
||||
void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg buf[1];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
|
||||
if (link == NULL) return;
|
||||
@ -2651,7 +2652,7 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
||||
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
|
||||
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
|
||||
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.
|
||||
@ -2659,7 +2660,8 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
|
||||
* If link is NULL, then the message is broadcasted to the whole cluster. */
|
||||
void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
||||
unsigned char *payload, uint32_t len) {
|
||||
unsigned char buf[sizeof(clusterMsg)], *heapbuf;
|
||||
unsigned char *heapbuf;
|
||||
clusterMsg buf[1];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
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 */
|
||||
if (totlen < sizeof(buf)) {
|
||||
heapbuf = buf;
|
||||
heapbuf = (unsigned char*)buf;
|
||||
} else {
|
||||
heapbuf = zmalloc(totlen);
|
||||
memcpy(heapbuf,hdr,sizeof(*hdr));
|
||||
@ -2687,7 +2689,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
|
||||
else
|
||||
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
|
||||
@ -2731,7 +2733,7 @@ void clusterPropagatePublish(robj *channel, robj *message) {
|
||||
* Note that we send the failover request to everybody, master and slave nodes,
|
||||
* but only the masters are supposed to reply to our query. */
|
||||
void clusterRequestFailoverAuth(void) {
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg buf[1];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
uint32_t totlen;
|
||||
|
||||
@ -2747,7 +2749,7 @@ void clusterRequestFailoverAuth(void) {
|
||||
|
||||
/* Send a FAILOVER_AUTH_ACK message to the specified node. */
|
||||
void clusterSendFailoverAuth(clusterNode *node) {
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg buf[1];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
uint32_t totlen;
|
||||
|
||||
@ -2755,12 +2757,12 @@ void clusterSendFailoverAuth(clusterNode *node) {
|
||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK);
|
||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||
hdr->totlen = htonl(totlen);
|
||||
clusterSendMessage(node->link,buf,totlen);
|
||||
clusterSendMessage(node->link,(unsigned char*)buf,totlen);
|
||||
}
|
||||
|
||||
/* Send a MFSTART message to the specified node. */
|
||||
void clusterSendMFStart(clusterNode *node) {
|
||||
unsigned char buf[sizeof(clusterMsg)];
|
||||
clusterMsg buf[1];
|
||||
clusterMsg *hdr = (clusterMsg*) buf;
|
||||
uint32_t totlen;
|
||||
|
||||
@ -2768,7 +2770,7 @@ void clusterSendMFStart(clusterNode *node) {
|
||||
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MFSTART);
|
||||
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
|
||||
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. */
|
||||
@ -4292,7 +4294,9 @@ NULL
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
|
||||
/* 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) {
|
||||
/* CLUSTER MYID */
|
||||
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
|
||||
@ -4534,10 +4538,8 @@ NULL
|
||||
"cluster_stats_messages_received:%lld\r\n", tot_msg_received);
|
||||
|
||||
/* Produce the reply protocol. */
|
||||
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",
|
||||
(unsigned long)sdslen(info)));
|
||||
addReplySds(c,info);
|
||||
addReply(c,shared.crlf);
|
||||
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||
sdsfree(info);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
|
||||
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-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},
|
||||
{"jemalloc-bg-thread",NULL,&server.jemalloc_bg_thread,1,1},
|
||||
{NULL, NULL, 0, 0}
|
||||
};
|
||||
|
||||
@ -438,6 +439,7 @@ void loadServerConfigFromString(char *config) {
|
||||
server.repl_diskless_load = configEnumGetValue(repl_diskless_load_enum,argv[1]);
|
||||
if (server.repl_diskless_load == INT_MIN) {
|
||||
err = "argument must be 'disabled', 'on-empty-db', 'swapdb' or 'flushdb'";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
|
||||
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);
|
||||
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
|
||||
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") &&
|
||||
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.
|
||||
* Don't do it if we have a saving child, as this will trigger
|
||||
* a copy on write madness. */
|
||||
if (server.rdb_child_pid == -1 &&
|
||||
server.aof_child_pid == -1 &&
|
||||
!(flags & LOOKUP_NOTOUCH))
|
||||
{
|
||||
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
|
||||
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
updateLFU(val);
|
||||
} else {
|
||||
@ -460,6 +457,13 @@ void flushdbCommand(client *c) {
|
||||
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
|
||||
server.dirty += emptyDb(c->db->id,flags,NULL);
|
||||
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]
|
||||
@ -482,6 +486,13 @@ void flushallCommand(client *c) {
|
||||
server.dirty = saved_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. */
|
||||
|
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) {
|
||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
||||
const char *help[] = {
|
||||
@ -324,6 +374,10 @@ void debugCommand(client *c) {
|
||||
"STRUCTSIZE -- Return the size of different Redis core C structures.",
|
||||
"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
|
||||
"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
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
@ -644,7 +698,8 @@ NULL
|
||||
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
|
||||
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) {
|
||||
robj *o;
|
||||
dict *ht = NULL;
|
||||
@ -671,7 +726,7 @@ NULL
|
||||
} else {
|
||||
char buf[4096];
|
||||
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) {
|
||||
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
|
||||
@ -682,6 +737,14 @@ NULL
|
||||
{
|
||||
stringmatchlen_fuzz_test();
|
||||
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 {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@ -1117,6 +1180,33 @@ void logRegisters(ucontext_t *uc) {
|
||||
(unsigned long) uc->uc_mcontext.mc_cs
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.mc_rsp);
|
||||
#elif defined(__aarch64__) /* Linux AArch64 */
|
||||
serverLog(LL_WARNING,
|
||||
"\n"
|
||||
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
|
||||
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
|
||||
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
|
||||
"X30:%016lx\n"
|
||||
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
|
||||
(unsigned long) uc->uc_mcontext.regs[18],
|
||||
(unsigned long) uc->uc_mcontext.regs[19],
|
||||
(unsigned long) uc->uc_mcontext.regs[20],
|
||||
(unsigned long) uc->uc_mcontext.regs[21],
|
||||
(unsigned long) uc->uc_mcontext.regs[22],
|
||||
(unsigned long) uc->uc_mcontext.regs[23],
|
||||
(unsigned long) uc->uc_mcontext.regs[24],
|
||||
(unsigned long) uc->uc_mcontext.regs[25],
|
||||
(unsigned long) uc->uc_mcontext.regs[26],
|
||||
(unsigned long) uc->uc_mcontext.regs[27],
|
||||
(unsigned long) uc->uc_mcontext.regs[28],
|
||||
(unsigned long) uc->uc_mcontext.regs[29],
|
||||
(unsigned long) uc->uc_mcontext.regs[30],
|
||||
(unsigned long) uc->uc_mcontext.pc,
|
||||
(unsigned long) uc->uc_mcontext.sp,
|
||||
(unsigned long) uc->uc_mcontext.pstate,
|
||||
(unsigned long) uc->uc_mcontext.fault_address
|
||||
);
|
||||
logStackContent((void**)uc->uc_mcontext.sp);
|
||||
#else
|
||||
serverLog(LL_WARNING,
|
||||
" Dumping of registers not supported for this OS/arch");
|
||||
@ -1344,6 +1434,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
/* Log dump of processor registers */
|
||||
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)
|
||||
/* Test memory */
|
||||
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)))
|
||||
de->v.val = newele, defragged++;
|
||||
} 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)))
|
||||
ln->value = newptr, defragged++;
|
||||
}
|
||||
@ -1039,7 +1039,7 @@ void activeDefragCycle(void) {
|
||||
mstime_t latency;
|
||||
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. */
|
||||
|
||||
/* 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
|
||||
* was freed to return back under the limit, the function returns C_ERR. */
|
||||
int freeMemoryIfNeeded(void) {
|
||||
int keys_freed = 0;
|
||||
/* By default replicas should ignore maxmemory
|
||||
* and just be masters exact copies. */
|
||||
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
|
||||
@ -467,7 +468,7 @@ int freeMemoryIfNeeded(void) {
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
while (mem_freed < mem_tofree) {
|
||||
int j, k, i, keys_freed = 0;
|
||||
int j, k, i;
|
||||
static unsigned int next_db = 0;
|
||||
sds bestkey = NULL;
|
||||
int bestdbid;
|
||||
@ -598,9 +599,7 @@ int freeMemoryIfNeeded(void) {
|
||||
mem_freed = mem_tofree;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!keys_freed) {
|
||||
} else {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
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 */
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
|
||||
|
||||
/* If no matching results, the user gets an empty reply. */
|
||||
if (ga->used == 0 && storekey == NULL) {
|
||||
addReplyNull(c);
|
||||
addReply(c,shared.emptyarray);
|
||||
geoArrayFree(ga);
|
||||
return;
|
||||
}
|
||||
@ -734,14 +734,14 @@ void geohashCommand(client *c) {
|
||||
r[1].max = 90;
|
||||
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
|
||||
|
||||
char buf[12];
|
||||
char buf[11];
|
||||
int i;
|
||||
for (i = 0; i < 11; i++) {
|
||||
for (i = 0; i < 10; i++) {
|
||||
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
|
||||
buf[i] = geoalphabet[idx];
|
||||
}
|
||||
buf[11] = '\0';
|
||||
addReplyBulkCBuffer(c,buf,11);
|
||||
buf[10] = '\0';
|
||||
addReplyBulkCBuffer(c,buf,10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1242,7 +1242,7 @@ void pfcountCommand(client *c) {
|
||||
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
|
||||
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]). */
|
||||
if (hllMerge(registers,o) == C_ERR) {
|
||||
addReplySds(c,sdsnew(invalid_hll_err));
|
||||
@ -1329,7 +1329,7 @@ void pfmergeCommand(client *c) {
|
||||
hdr = o->ptr;
|
||||
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]). */
|
||||
if (hllMerge(max,o) == C_ERR) {
|
||||
addReplySds(c,sdsnew(invalid_hll_err));
|
||||
|
@ -599,7 +599,7 @@ NULL
|
||||
event = dictGetKey(de);
|
||||
|
||||
graph = latencyCommandGenSparkeline(event,ts);
|
||||
addReplyBulkCString(c,graph);
|
||||
addReplyVerbatim(c,graph,sdslen(graph),"txt");
|
||||
sdsfree(graph);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
|
||||
/* LATENCY LATEST */
|
||||
@ -608,7 +608,7 @@ NULL
|
||||
/* LATENCY DOCTOR */
|
||||
sds report = createLatencyReport();
|
||||
|
||||
addReplyBulkCBuffer(c,report,sdslen(report));
|
||||
addReplyVerbatim(c,report,sdslen(report),"txt");
|
||||
sdsfree(report);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
|
||||
/* LATENCY RESET */
|
||||
|
136
src/lolwut.c
136
src/lolwut.c
@ -34,8 +34,11 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "lolwut.h"
|
||||
#include <math.h>
|
||||
|
||||
void lolwut5Command(client *c);
|
||||
void lolwut6Command(client *c);
|
||||
|
||||
/* The default target for LOLWUT if no matching version was found.
|
||||
* This is what unstable versions of Redis will display. */
|
||||
@ -43,14 +46,143 @@ void lolwutUnstableCommand(client *c) {
|
||||
sds rendered = sdsnew("Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
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) {
|
||||
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'))
|
||||
lolwut5Command(c);
|
||||
else if ((v[0] == '6' && v[1] == '.' && v[2] != '9') ||
|
||||
(v[0] == '5' && v[1] == '.' && v[2] == '9'))
|
||||
lolwut6Command(c);
|
||||
else
|
||||
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 "lolwut.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
|
||||
* braille character. The byte should correspond to the pixels arranged as
|
||||
* 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 */
|
||||
}
|
||||
|
||||
/* 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
|
||||
* generated by Georg Nees in the 60s. It explores the relationship between
|
||||
* 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;
|
||||
float square_side = (float)(canvas_width-padding*2) / squares_per_row;
|
||||
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 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;
|
||||
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
|
||||
* width/2 large and height/4 tall in order to hold the whole image without
|
||||
* overflowing or scrolling, since each Barille character is 2x4. */
|
||||
sds lwRenderCanvas(lwCanvas *canvas) {
|
||||
static sds renderCanvas(lwCanvas *canvas) {
|
||||
sds text = sdsempty();
|
||||
for (int y = 0; y < canvas->height; y += 4) {
|
||||
for (int x = 0; x < canvas->width; x += 2) {
|
||||
@ -272,11 +166,12 @@ void lolwut5Command(client *c) {
|
||||
|
||||
/* Generate some computer art and reply. */
|
||||
lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
|
||||
sds rendered = lwRenderCanvas(canvas);
|
||||
sds rendered = renderCanvas(canvas);
|
||||
rendered = sdscat(rendered,
|
||||
"\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
|
||||
rendered = sdscat(rendered,REDIS_VERSION);
|
||||
rendered = sdscatlen(rendered,"\n",1);
|
||||
addReplyBulkSds(c,rendered);
|
||||
addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
|
||||
sdsfree(rendered);
|
||||
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);
|
||||
}
|
617
src/module.c
617
src/module.c
@ -31,6 +31,7 @@
|
||||
#include "cluster.h"
|
||||
#include "rdb.h"
|
||||
#include <dlfcn.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#define REDISMODULE_CORE 1
|
||||
#include "redismodule.h"
|
||||
@ -41,6 +42,17 @@
|
||||
* 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. */
|
||||
struct RedisModule {
|
||||
void *handle; /* Module dlopen() handle. */
|
||||
@ -52,6 +64,8 @@ struct RedisModule {
|
||||
list *using; /* List of modules we use some APIs of. */
|
||||
list *filters; /* List of filters the module has registered. */
|
||||
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;
|
||||
|
||||
@ -133,10 +147,14 @@ struct RedisModuleCtx {
|
||||
int keys_count;
|
||||
|
||||
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;
|
||||
|
||||
#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_AUTO_MEMORY (1<<1)
|
||||
#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_THREAD_SAFE (1<<5)
|
||||
#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<6)
|
||||
#define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<7)
|
||||
|
||||
/* This represents a Redis key opened with RM_OpenKey(). */
|
||||
struct RedisModuleKey {
|
||||
@ -292,6 +311,18 @@ typedef struct RedisModuleCommandFilter {
|
||||
/* Registered filters */
|
||||
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
|
||||
* -------------------------------------------------------------------------- */
|
||||
@ -497,8 +528,47 @@ int RM_GetApi(const char *funcname, void **targetPtrPtr) {
|
||||
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. */
|
||||
void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||
moduleHandlePropagationAfterCommandCallback(ctx);
|
||||
autoMemoryCollect(ctx);
|
||||
poolAllocRelease(ctx);
|
||||
if (ctx->postponed_arrays) {
|
||||
@ -514,34 +584,16 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
|
||||
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
|
||||
* exported by modules. */
|
||||
void RedisModuleCommandDispatcher(client *c) {
|
||||
RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc;
|
||||
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
|
||||
|
||||
ctx.flags |= REDISMODULE_CTX_MODULE_COMMAND_CALL;
|
||||
ctx.module = cp->module;
|
||||
ctx.client = c;
|
||||
cp->func(&ctx,(void**)c->argv,c->argc);
|
||||
moduleHandlePropagationAfterCommandCallback(&ctx);
|
||||
moduleFreeContext(&ctx);
|
||||
|
||||
/* In some cases processMultibulkBuffer uses sdsMakeRoomFor to
|
||||
@ -772,6 +824,19 @@ long long RM_Milliseconds(void) {
|
||||
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
|
||||
* -------------------------------------------------------------------------- */
|
||||
@ -1126,10 +1191,9 @@ int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) {
|
||||
int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
|
||||
client *c = moduleGetReplyClient(ctx);
|
||||
if (c == NULL) return REDISMODULE_OK;
|
||||
sds strmsg = sdsnewlen(prefix,1);
|
||||
strmsg = sdscat(strmsg,msg);
|
||||
strmsg = sdscatlen(strmsg,"\r\n",2);
|
||||
addReplySds(c,strmsg);
|
||||
addReplyProto(c,prefix,strlen(prefix));
|
||||
addReplyProto(c,msg,strlen(msg));
|
||||
addReplyProto(c,"\r\n",2);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@ -1316,9 +1380,16 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
/* If we already emitted MULTI return ASAP. */
|
||||
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
|
||||
/* 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. */
|
||||
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);
|
||||
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
|
||||
}
|
||||
@ -1340,6 +1411,24 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* 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
|
||||
* or the command name does not belong to a known command. */
|
||||
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);
|
||||
if (argv == NULL) return REDISMODULE_ERR;
|
||||
|
||||
/* Replicate! */
|
||||
moduleReplicateMultiIfNeeded(ctx);
|
||||
alsoPropagate(cmd,ctx->client->db->id,argv,argc,
|
||||
PROPAGATE_AOF|PROPAGATE_REPL);
|
||||
/* 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);
|
||||
alsoPropagate(cmd,ctx->client->db->id,argv,argc,target);
|
||||
}
|
||||
|
||||
/* Release the argv. */
|
||||
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
|
||||
* 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.
|
||||
*
|
||||
* Example of REDISMODULE_HASH_CFIELD:
|
||||
@ -2678,12 +2780,11 @@ RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) {
|
||||
* to special modifiers in "fmt". For now only one exists:
|
||||
*
|
||||
* "!" -> REDISMODULE_ARGV_REPLICATE
|
||||
* "A" -> REDISMODULE_ARGV_NO_AOF
|
||||
* "R" -> REDISMODULE_ARGV_NO_REPLICAS
|
||||
*
|
||||
* On error (format specifier error) NULL is returned and nothing is
|
||||
* 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) {
|
||||
int argc = 0, argv_size, j;
|
||||
robj **argv = NULL;
|
||||
@ -2732,6 +2833,10 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
|
||||
}
|
||||
} else if (*p == '!') {
|
||||
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 {
|
||||
goto fmterr;
|
||||
}
|
||||
@ -2752,7 +2857,10 @@ fmterr:
|
||||
* NULL is returned and errno is set to the following values:
|
||||
*
|
||||
* 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, ...) {
|
||||
struct redisCommand *cmd;
|
||||
client *c = NULL;
|
||||
@ -2823,8 +2931,10 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
||||
/* Run the command */
|
||||
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
|
||||
if (replicate) {
|
||||
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
||||
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
||||
if (!(flags & REDISMODULE_ARGV_NO_AOF))
|
||||
call_flags |= CMD_CALL_PROPAGATE_AOF;
|
||||
if (!(flags & REDISMODULE_ARGV_NO_REPLICAS))
|
||||
call_flags |= CMD_CALL_PROPAGATE_REPL;
|
||||
}
|
||||
call(c,call_flags);
|
||||
|
||||
@ -3150,9 +3260,14 @@ void *RM_ModuleTypeGetValue(RedisModuleKey *key) {
|
||||
* RDB loading and saving functions
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Called when there is a load error in the context of a module. This cannot
|
||||
* be recovered like for the built-in types. */
|
||||
/* Called when there is a load error in the context of a module. On some
|
||||
* 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) {
|
||||
if (io->type->module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS) {
|
||||
io->error = 1;
|
||||
return;
|
||||
}
|
||||
serverLog(LL_WARNING,
|
||||
"Error loading data from RDB (short read or EOF). "
|
||||
"Read performed by module '%s' about type '%s' "
|
||||
@ -3163,6 +3278,33 @@ void moduleRDBLoadError(RedisModuleIO *io) {
|
||||
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
|
||||
* be called in the context of the rdb_save method of modules implementing new
|
||||
* data types. */
|
||||
@ -3186,6 +3328,7 @@ saveerr:
|
||||
* be called in the context of the rdb_load method of modules implementing
|
||||
* new data types. */
|
||||
uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
|
||||
if (io->error) return 0;
|
||||
if (io->ver == 2) {
|
||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||
if (opcode != RDB_MODULE_OPCODE_UINT) goto loaderr;
|
||||
@ -3197,7 +3340,7 @@ uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
|
||||
|
||||
loaderr:
|
||||
moduleRDBLoadError(io);
|
||||
return 0; /* Never reached. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Like RedisModule_SaveUnsigned() but for signed 64 bit values. */
|
||||
@ -3256,6 +3399,7 @@ saveerr:
|
||||
|
||||
/* Implements RM_LoadString() and RM_LoadStringBuffer() */
|
||||
void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
|
||||
if (io->error) return NULL;
|
||||
if (io->ver == 2) {
|
||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||
if (opcode != RDB_MODULE_OPCODE_STRING) goto loaderr;
|
||||
@ -3267,7 +3411,7 @@ void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
|
||||
|
||||
loaderr:
|
||||
moduleRDBLoadError(io);
|
||||
return NULL; /* Never reached. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 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().
|
||||
*
|
||||
* 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. */
|
||||
char *RM_LoadStringBuffer(RedisModuleIO *io, size_t *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
|
||||
* double value saved by RedisModule_SaveDouble(). */
|
||||
double RM_LoadDouble(RedisModuleIO *io) {
|
||||
if (io->error) return 0;
|
||||
if (io->ver == 2) {
|
||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||
if (opcode != RDB_MODULE_OPCODE_DOUBLE) goto loaderr;
|
||||
@ -3327,7 +3472,7 @@ double RM_LoadDouble(RedisModuleIO *io) {
|
||||
|
||||
loaderr:
|
||||
moduleRDBLoadError(io);
|
||||
return 0; /* Never reached. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* float value saved by RedisModule_SaveFloat(). */
|
||||
float RM_LoadFloat(RedisModuleIO *io) {
|
||||
if (io->error) return 0;
|
||||
if (io->ver == 2) {
|
||||
uint64_t opcode = rdbLoadLen(io->rio,NULL);
|
||||
if (opcode != RDB_MODULE_OPCODE_FLOAT) goto loaderr;
|
||||
@ -3363,7 +3509,7 @@ float RM_LoadFloat(RedisModuleIO *io) {
|
||||
|
||||
loaderr:
|
||||
moduleRDBLoadError(io);
|
||||
return 0; /* Never reached. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
/* 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
|
||||
* -------------------------------------------------------------------------- */
|
||||
@ -4729,6 +4884,194 @@ int RM_DictCompare(RedisModuleDictIter *di, const char *op, RedisModuleString *k
|
||||
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
|
||||
* -------------------------------------------------------------------------- */
|
||||
@ -4963,11 +5306,13 @@ int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *fi
|
||||
ln = listSearchKey(moduleCommandFilters,filter);
|
||||
if (!ln) return REDISMODULE_ERR;
|
||||
listDelNode(moduleCommandFilters,ln);
|
||||
|
||||
|
||||
ln = listSearchKey(ctx->module->filters,filter);
|
||||
if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */
|
||||
listDelNode(ctx->module->filters,ln);
|
||||
|
||||
zfree(filter);
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@ -5071,6 +5416,100 @@ int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos)
|
||||
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
|
||||
* -------------------------------------------------------------------------- */
|
||||
@ -5170,6 +5609,8 @@ void moduleLoadFromQueue(void) {
|
||||
void moduleFreeModuleStructure(struct RedisModule *module) {
|
||||
listRelease(module->types);
|
||||
listRelease(module->filters);
|
||||
listRelease(module->usedby);
|
||||
listRelease(module->using);
|
||||
sdsfree(module->name);
|
||||
zfree(module);
|
||||
}
|
||||
@ -5257,6 +5698,23 @@ int moduleUnload(sds name) {
|
||||
errno = EPERM;
|
||||
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);
|
||||
moduleUnregisterSharedAPI(module);
|
||||
@ -5304,6 +5762,62 @@ void addReplyLoadedModules(client *c) {
|
||||
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.
|
||||
*
|
||||
* MODULE LOAD <path> [args...] */
|
||||
@ -5452,6 +5966,8 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(ModuleTypeSetValue);
|
||||
REGISTER_API(ModuleTypeGetType);
|
||||
REGISTER_API(ModuleTypeGetValue);
|
||||
REGISTER_API(IsIOError);
|
||||
REGISTER_API(SetModuleOptions);
|
||||
REGISTER_API(SaveUnsigned);
|
||||
REGISTER_API(LoadUnsigned);
|
||||
REGISTER_API(SaveSigned);
|
||||
@ -5467,6 +5983,7 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(EmitAOF);
|
||||
REGISTER_API(Log);
|
||||
REGISTER_API(LogIOError);
|
||||
REGISTER_API(_Assert);
|
||||
REGISTER_API(StringAppendBuffer);
|
||||
REGISTER_API(RetainString);
|
||||
REGISTER_API(StringCompare);
|
||||
@ -5534,4 +6051,16 @@ void moduleRegisterCoreAPI(void) {
|
||||
REGISTER_API(CommandFilterArgInsert);
|
||||
REGISTER_API(CommandFilterArgReplace);
|
||||
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;
|
||||
}
|
||||
sds o = getAllClientsInfoString(type);
|
||||
addReplyBulkCBuffer(c,o,sdslen(o));
|
||||
addReplyVerbatim(c,o,sdslen(o),"txt");
|
||||
sdsfree(o);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
|
||||
/* CLIENT REPLY ON|OFF|SKIP */
|
||||
|
26
src/object.c
26
src/object.c
@ -1440,30 +1440,20 @@ NULL
|
||||
#if defined(USE_JEMALLOC)
|
||||
sds info = sdsempty();
|
||||
je_malloc_stats_print(inputCatSds, &info, NULL);
|
||||
addReplyBulkSds(c, info);
|
||||
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||
sdsfree(info);
|
||||
#else
|
||||
addReplyBulkCString(c,"Stats not supported for the current allocator");
|
||||
#endif
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
|
||||
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) {
|
||||
#if defined(USE_JEMALLOC)
|
||||
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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
addReplyError(c, "Error purging dirty pages");
|
||||
#else
|
||||
addReply(c, shared.ok);
|
||||
/* Nothing to do for other allocators. */
|
||||
#endif
|
||||
if (jemalloc_purge() == 0)
|
||||
addReply(c, shared.ok);
|
||||
else
|
||||
addReplyError(c, "Error purging dirty pages");
|
||||
} else {
|
||||
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;
|
||||
else if (lt) 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;
|
||||
} else /* (cmp < 0) */ {
|
||||
return lt ? 1 : 0;
|
||||
|
56
src/rdb.c
56
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".
|
||||
* 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) {
|
||||
int plain = flags & RDB_LOAD_PLAIN;
|
||||
int sds = flags & RDB_LOAD_SDS;
|
||||
@ -1335,40 +1335,25 @@ werr:
|
||||
|
||||
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
||||
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.lastbgsave_try = time(NULL);
|
||||
openChildInfoPipe();
|
||||
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
if ((childpid = redisFork()) == 0) {
|
||||
int retval;
|
||||
|
||||
/* Child */
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("redis-rdb-bgsave");
|
||||
retval = rdbSave(filename,rsi);
|
||||
if (retval == C_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||
|
||||
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);
|
||||
sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
|
||||
}
|
||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||
} 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) {
|
||||
closeChildInfoPipe();
|
||||
server.lastbgsave_status = C_ERR;
|
||||
@ -1380,7 +1365,6 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
|
||||
server.rdb_save_time_start = time(NULL);
|
||||
server.rdb_child_pid = childpid;
|
||||
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
|
||||
updateDictResizePolicy();
|
||||
return C_OK;
|
||||
}
|
||||
return C_OK; /* unreached */
|
||||
@ -2355,10 +2339,9 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
pid_t childpid;
|
||||
long long start;
|
||||
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
|
||||
* drained the pipe. */
|
||||
@ -2389,15 +2372,13 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
|
||||
/* Create the child process. */
|
||||
openChildInfoPipe();
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
if ((childpid = redisFork()) == 0) {
|
||||
/* Child */
|
||||
int retval;
|
||||
rio rdb;
|
||||
|
||||
rioInitWithFd(&rdb,server.rdb_pipe_write);
|
||||
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("redis-rdb-to-slaves");
|
||||
|
||||
retval = rdbSaveRioWithEOFMark(&rdb,NULL,rsi);
|
||||
@ -2405,17 +2386,9 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
retval = C_ERR;
|
||||
|
||||
if (retval == C_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||
|
||||
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);
|
||||
sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
|
||||
}
|
||||
|
||||
rioFreeFd(&rdb);
|
||||
close(server.rdb_pipe_write); /* wake up the reader, tell it we're done. */
|
||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||
@ -2443,10 +2416,6 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
server.rdb_pipe_numconns_writing = 0;
|
||||
closeChildInfoPipe();
|
||||
} 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",
|
||||
childpid);
|
||||
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) {
|
||||
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
|
||||
}
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
return (childpid == -1) ? C_ERR : C_OK;
|
||||
}
|
||||
@ -2497,15 +2465,15 @@ void bgsaveCommand(client *c) {
|
||||
|
||||
if (server.rdb_child_pid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
} else if (server.aof_child_pid != -1) {
|
||||
} else if (hasActiveChildProcess()) {
|
||||
if (schedule) {
|
||||
server.rdb_bgsave_scheduled = 1;
|
||||
addReplyStatus(c,"Background saving scheduled");
|
||||
} else {
|
||||
addReplyError(c,
|
||||
"An AOF log rewriting in progress: can't BGSAVE right now. "
|
||||
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
||||
"possible.");
|
||||
"Another child process is active (AOF?): can't BGSAVE right now. "
|
||||
"Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
|
||||
"possible.");
|
||||
}
|
||||
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
|
||||
addReplyStatus(c,"Background saving started");
|
||||
|
101
src/redis-cli.c
101
src/redis-cli.c
@ -228,6 +228,7 @@ static struct config {
|
||||
int hotkeys;
|
||||
int stdinarg; /* get last arg from stdin. (-x option) */
|
||||
char *auth;
|
||||
char *user;
|
||||
int output; /* output mode, see OUTPUT_* defines */
|
||||
sds mb_delim;
|
||||
char prompt[128];
|
||||
@ -240,6 +241,7 @@ static struct config {
|
||||
int verbose;
|
||||
clusterManagerCommand cluster_manager_command;
|
||||
int no_auth_warning;
|
||||
int resp3;
|
||||
} config;
|
||||
|
||||
/* User preferences. */
|
||||
@ -738,8 +740,13 @@ static int cliAuth(void) {
|
||||
redisReply *reply;
|
||||
if (config.auth == NULL) return REDIS_OK;
|
||||
|
||||
reply = redisCommand(context,"AUTH %s",config.auth);
|
||||
if (config.user == NULL)
|
||||
reply = redisCommand(context,"AUTH %s",config.auth);
|
||||
else
|
||||
reply = redisCommand(context,"AUTH %s %s",config.user,config.auth);
|
||||
if (reply != NULL) {
|
||||
if (reply->type == REDIS_REPLY_ERROR)
|
||||
fprintf(stderr,"Warning: AUTH failed\n");
|
||||
freeReplyObject(reply);
|
||||
return REDIS_OK;
|
||||
}
|
||||
@ -826,6 +833,21 @@ error:
|
||||
#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:
|
||||
* CC_FORCE: The connection is performed even if there is already
|
||||
* a connected socket.
|
||||
@ -874,11 +896,13 @@ static int cliConnect(int flags) {
|
||||
* errors. */
|
||||
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)
|
||||
return REDIS_ERR;
|
||||
if (cliSelect() != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
if (cliSwitchProto() != REDIS_OK)
|
||||
return REDIS_ERR;
|
||||
}
|
||||
return REDIS_OK;
|
||||
}
|
||||
@ -905,10 +929,17 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
|
||||
out = sdscatprintf(out,"(double) %s\n",r->str);
|
||||
break;
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_VERB:
|
||||
/* If you are producing output for the standard output we want
|
||||
* a more interesting output with quoted characters and so forth */
|
||||
out = sdscatrepr(out,r->str,r->len);
|
||||
out = sdscat(out,"\n");
|
||||
* 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 = sdscat(out,"\n");
|
||||
} else {
|
||||
out = sdscatlen(out,r->str,r->len);
|
||||
out = sdscat(out,"\n");
|
||||
}
|
||||
break;
|
||||
case REDIS_REPLY_NIL:
|
||||
out = sdscat(out,"(nil)\n");
|
||||
@ -1047,6 +1078,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
||||
break;
|
||||
case REDIS_REPLY_STATUS:
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_VERB:
|
||||
if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
|
||||
/* The Lua debugger replies with arrays of simple (status)
|
||||
* 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);
|
||||
}
|
||||
break;
|
||||
case REDIS_REPLY_BOOL:
|
||||
out = sdscat(out,r->integer ? "(true)" : "(false)");
|
||||
break;
|
||||
case REDIS_REPLY_INTEGER:
|
||||
out = sdscatprintf(out,"%lld",r->integer);
|
||||
break;
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
out = sdscatprintf(out,"%s",r->str);
|
||||
break;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
if (i > 0) out = sdscat(out,config.mb_delim);
|
||||
@ -1077,6 +1115,19 @@ static sds cliFormatReplyRaw(redisReply *r) {
|
||||
sdsfree(tmp);
|
||||
}
|
||||
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:
|
||||
fprintf(stderr,"Unknown reply type: %d\n", r->type);
|
||||
exit(1);
|
||||
@ -1099,13 +1150,21 @@ static sds cliFormatReplyCSV(redisReply *r) {
|
||||
case REDIS_REPLY_INTEGER:
|
||||
out = sdscatprintf(out,"%lld",r->integer);
|
||||
break;
|
||||
case REDIS_REPLY_DOUBLE:
|
||||
out = sdscatprintf(out,"%s",r->str);
|
||||
break;
|
||||
case REDIS_REPLY_STRING:
|
||||
case REDIS_REPLY_VERB:
|
||||
out = sdscatrepr(out,r->str,r->len);
|
||||
break;
|
||||
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;
|
||||
case REDIS_REPLY_ARRAY:
|
||||
case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
|
||||
for (i = 0; i < r->elements; i++) {
|
||||
sds tmp = cliFormatReplyCSV(r->element[i]);
|
||||
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) {
|
||||
config.dbnum = atoi(argv[1]);
|
||||
cliRefreshPrompt();
|
||||
} else if (!strcasecmp(command,"auth") && argc == 2) {
|
||||
} else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3))
|
||||
{
|
||||
cliSelect();
|
||||
}
|
||||
}
|
||||
@ -1389,8 +1449,12 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.dbnum = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i], "--no-auth-warning")) {
|
||||
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];
|
||||
} else if (!strcmp(argv[i],"--user") && !lastarg) {
|
||||
config.user = argv[++i];
|
||||
} else if (!strcmp(argv[i],"-u") && !lastarg) {
|
||||
parseRedisUri(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"--raw")) {
|
||||
@ -1546,6 +1610,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
printf("redis-cli %s\n", version);
|
||||
sdsfree(version);
|
||||
exit(0);
|
||||
} else if (!strcmp(argv[i],"-3")) {
|
||||
config.resp3 = 1;
|
||||
} else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
|
||||
if (config.cluster_manager_command.argc == 0) {
|
||||
int j = i + 1;
|
||||
@ -1621,11 +1687,14 @@ static void usage(void) {
|
||||
" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
|
||||
" variable to pass this password more safely\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"
|
||||
" -r <repeat> Execute specified command N times.\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"
|
||||
" -n <db> Database number.\n"
|
||||
" -3 Start session in RESP3 protocol mode.\n"
|
||||
" -x Read last argument from STDIN.\n"
|
||||
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\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"
|
||||
" TTY, it samples the latency for 1 second (you can use\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"
|
||||
" Default time interval is 15 sec. Change it using -i.\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"
|
||||
" no reply is received within <n> seconds.\n"
|
||||
" Default timeout: %d. Use 0 to wait forever.\n",
|
||||
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||
REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
|
||||
fprintf(stderr,
|
||||
" --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"
|
||||
@ -2476,7 +2547,12 @@ static int clusterManagerNodeConnect(clusterManagerNode *node) {
|
||||
* errors. */
|
||||
anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
|
||||
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);
|
||||
if (reply != NULL) freeReplyObject(reply);
|
||||
if (!ok) return 0;
|
||||
@ -3348,7 +3424,7 @@ static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
|
||||
redisReply *entry = reply->element[i];
|
||||
size_t idx = i + offset;
|
||||
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;
|
||||
if (dots) dots[i] = '.';
|
||||
}
|
||||
@ -7804,6 +7880,7 @@ int main(int argc, char **argv) {
|
||||
config.hotkeys = 0;
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.user = NULL;
|
||||
config.eval = NULL;
|
||||
config.eval_ldb = 0;
|
||||
config.eval_ldb_end = 0;
|
||||
|
@ -144,6 +144,9 @@ typedef uint64_t RedisModuleTimerID;
|
||||
/* Do filter RedisModule_Call() commands initiated by module itself. */
|
||||
#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 ------------------------ */
|
||||
|
||||
#ifndef REDISMODULE_CORE
|
||||
@ -164,6 +167,7 @@ typedef struct RedisModuleDict RedisModuleDict;
|
||||
typedef struct RedisModuleDictIter RedisModuleDictIter;
|
||||
typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
|
||||
typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
|
||||
typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
|
||||
|
||||
typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
|
||||
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 (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
|
||||
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
|
||||
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);
|
||||
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(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);
|
||||
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
|
||||
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);
|
||||
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__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);
|
||||
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
|
||||
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);
|
||||
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_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 */
|
||||
#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_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
|
||||
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
|
||||
|
||||
/* 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(ModuleTypeGetType);
|
||||
REDISMODULE_GET_API(ModuleTypeGetValue);
|
||||
REDISMODULE_GET_API(IsIOError);
|
||||
REDISMODULE_GET_API(SetModuleOptions);
|
||||
REDISMODULE_GET_API(SaveUnsigned);
|
||||
REDISMODULE_GET_API(LoadUnsigned);
|
||||
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(Log);
|
||||
REDISMODULE_GET_API(LogIOError);
|
||||
REDISMODULE_GET_API(_Assert);
|
||||
REDISMODULE_GET_API(StringAppendBuffer);
|
||||
REDISMODULE_GET_API(RetainString);
|
||||
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(DictCompare);
|
||||
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
|
||||
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(CommandFilterArgReplace);
|
||||
REDISMODULE_GET_API(CommandFilterArgDelete);
|
||||
REDISMODULE_GET_API(Fork);
|
||||
REDISMODULE_GET_API(ExitFromChild);
|
||||
REDISMODULE_GET_API(KillForkChild);
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#define RedisModule_Assert(_e) ((_e)?(void)0 : (RedisModule__Assert(#_e,__FILE__,__LINE__),exit(1)))
|
||||
|
||||
#else
|
||||
|
||||
/* Things only defined for the modules core, not exported to modules
|
||||
|
@ -32,6 +32,7 @@
|
||||
* files using this functions. */
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "release.h"
|
||||
#include "version.h"
|
||||
@ -50,3 +51,16 @@ uint64_t redisBuildId(void) {
|
||||
|
||||
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
|
||||
* 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. */
|
||||
if (retval == C_ERR) {
|
||||
serverLog(LL_WARNING,"BGSAVE for replication failed");
|
||||
@ -606,7 +606,7 @@ int startBgsaveForReplication(int mincapa) {
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
listRewind(server.slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
@ -751,11 +751,11 @@ void syncCommand(client *c) {
|
||||
/* Target is disk (or the slave is not capable of supporting
|
||||
* diskless replication) and we don't have a BGSAVE in progress,
|
||||
* let's start one. */
|
||||
if (server.aof_child_pid == -1) {
|
||||
if (!hasActiveChildProcess()) {
|
||||
startBgsaveForReplication(c->slave_capa);
|
||||
} else {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -1283,8 +1283,15 @@ void restartAOFAfterSYNC() {
|
||||
|
||||
static int useDisklessLoad() {
|
||||
/* 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);
|
||||
/* 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
|
||||
@ -2303,7 +2310,10 @@ void replicationSetMaster(char *ip, int port) {
|
||||
cancelReplicationHandshake();
|
||||
/* Before destroying our master state, create a cached master using
|
||||
* 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;
|
||||
}
|
||||
|
||||
@ -3048,7 +3058,7 @@ void replicationCron(void) {
|
||||
* In case of diskless replication, we make sure to wait the specified
|
||||
* number of seconds (according to configuration) so that other slaves
|
||||
* 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;
|
||||
int slaves_waiting = 0;
|
||||
int mincapa = -1;
|
||||
|
270
src/scripting.c
270
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_Status(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_randomseed (lua_State *L);
|
||||
void ldbInit(void);
|
||||
@ -132,9 +135,12 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
|
||||
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
|
||||
case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
|
||||
case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
|
||||
case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
||||
case '%': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
||||
case '~': p = redisProtocolToLuaType_MultiBulk(lua,reply,*p); break;
|
||||
case '*': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
|
||||
case '%': p = redisProtocolToLuaType_Aggregate(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;
|
||||
}
|
||||
@ -182,13 +188,13 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) {
|
||||
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');
|
||||
long long mbulklen;
|
||||
int j = 0;
|
||||
|
||||
string2ll(reply+1,p-reply-1,&mbulklen);
|
||||
if (server.lua_caller->resp == 2 || atype == '*') {
|
||||
if (server.lua_client->resp == 2 || atype == '*') {
|
||||
p += 2;
|
||||
if (mbulklen == -1) {
|
||||
lua_pushboolean(lua,0);
|
||||
@ -200,11 +206,15 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
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
|
||||
* 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;
|
||||
lua_newtable(lua);
|
||||
lua_pushstring(lua,atype == '%' ? "map" : "set");
|
||||
lua_newtable(lua);
|
||||
for (j = 0; j < mbulklen; j++) {
|
||||
p = redisProtocolToLuaType(lua,p);
|
||||
if (atype == '%') {
|
||||
@ -214,10 +224,44 @@ char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply, int atype) {
|
||||
}
|
||||
lua_settable(lua,-3);
|
||||
}
|
||||
lua_settable(lua,-3);
|
||||
}
|
||||
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
|
||||
* 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
|
||||
@ -292,6 +336,8 @@ void luaSortArray(lua_State *lua) {
|
||||
* 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) {
|
||||
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));
|
||||
break;
|
||||
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;
|
||||
case LUA_TNUMBER:
|
||||
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.
|
||||
* Status replies are returned as single element table with 'ok'
|
||||
* field. */
|
||||
|
||||
/* Handle error reply. */
|
||||
lua_pushstring(lua,"err");
|
||||
lua_gettable(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
@ -321,8 +373,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
lua_pop(lua,2);
|
||||
return;
|
||||
}
|
||||
lua_pop(lua,1); /* Discard field name pushed before. */
|
||||
|
||||
lua_pop(lua,1);
|
||||
/* Handle status reply. */
|
||||
lua_pushstring(lua,"ok");
|
||||
lua_gettable(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
@ -331,25 +384,81 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||
sdsmapchars(ok,"\r\n"," ",2);
|
||||
addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
|
||||
sdsfree(ok);
|
||||
lua_pop(lua,1);
|
||||
} else {
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
int j = 1, mbulklen = 0;
|
||||
|
||||
lua_pop(lua,1); /* Discard the 'ok' field value we popped */
|
||||
while(1) {
|
||||
lua_pushnumber(lua,j++);
|
||||
lua_gettable(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TNIL) {
|
||||
lua_pop(lua,1);
|
||||
break;
|
||||
}
|
||||
luaReplyToRedisReply(c, lua);
|
||||
mbulklen++;
|
||||
}
|
||||
setDeferredArrayLen(c,replylen,mbulklen);
|
||||
lua_pop(lua,2);
|
||||
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);
|
||||
int j = 1, mbulklen = 0;
|
||||
while(1) {
|
||||
lua_pushnumber(lua,j++);
|
||||
lua_gettable(lua,-2);
|
||||
t = lua_type(lua,-1);
|
||||
if (t == LUA_TNIL) {
|
||||
lua_pop(lua,1);
|
||||
break;
|
||||
}
|
||||
luaReplyToRedisReply(c, lua);
|
||||
mbulklen++;
|
||||
}
|
||||
setDeferredArrayLen(c,replylen,mbulklen);
|
||||
break;
|
||||
default:
|
||||
addReplyNull(c);
|
||||
@ -859,6 +968,25 @@ int luaLogCommand(lua_State *lua) {
|
||||
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.
|
||||
* ------------------------------------------------------------------------- */
|
||||
@ -955,6 +1083,7 @@ void scriptingInit(int setup) {
|
||||
if (setup) {
|
||||
server.lua_client = NULL;
|
||||
server.lua_caller = NULL;
|
||||
server.lua_cur_script = NULL;
|
||||
server.lua_timedout = 0;
|
||||
ldbInit();
|
||||
}
|
||||
@ -986,6 +1115,11 @@ void scriptingInit(int setup) {
|
||||
lua_pushcfunction(lua,luaLogCommand);
|
||||
lua_settable(lua,-3);
|
||||
|
||||
/* redis.setresp */
|
||||
lua_pushstring(lua,"setresp");
|
||||
lua_pushcfunction(lua,luaSetResp);
|
||||
lua_settable(lua,-3);
|
||||
|
||||
lua_pushstring(lua,"LOG_DEBUG");
|
||||
lua_pushnumber(lua,LL_DEBUG);
|
||||
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
|
||||
* execution time was reached. */
|
||||
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;
|
||||
/* Once the script timeouts we reenter the event loop to permit others
|
||||
* 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,"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);
|
||||
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
|
||||
* 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
|
||||
* debugger is call-back at every line executed by the script. */
|
||||
server.lua_caller = c;
|
||||
server.lua_cur_script = funcname + 2;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && ldb.active == 0) {
|
||||
@ -1416,6 +1556,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
queueClientForReprocessing(server.master);
|
||||
}
|
||||
server.lua_caller = NULL;
|
||||
server.lua_cur_script = NULL;
|
||||
|
||||
/* Call the Lua garbage collector from time to time to avoid a
|
||||
* full cycle performed by Lua, which adds too latency.
|
||||
@ -1693,7 +1834,7 @@ void ldbSendLogs(void) {
|
||||
int ldbStartSession(client *c) {
|
||||
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
|
||||
if (ldb.forked) {
|
||||
pid_t cp = fork();
|
||||
pid_t cp = redisFork();
|
||||
if (cp == -1) {
|
||||
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
|
||||
return 0;
|
||||
@ -1710,7 +1851,6 @@ int ldbStartSession(client *c) {
|
||||
* socket to make sure if the parent crashes a reset is sent
|
||||
* to the clients. */
|
||||
serverLog(LL_WARNING,"Redis forked for debugging eval");
|
||||
closeListeningSockets(0);
|
||||
} else {
|
||||
/* Parent */
|
||||
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_Status(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
|
||||
* 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_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;
|
||||
}
|
||||
@ -2121,6 +2271,62 @@ char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) {
|
||||
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.
|
||||
* If the resulting string is longer than 'len' plus a few more chars
|
||||
* used as prefix, it gets truncated. */
|
||||
|
@ -603,6 +603,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
long i;
|
||||
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);
|
||||
f = fmt; /* Next format specifier byte to process. */
|
||||
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
|
||||
* running childs. */
|
||||
void updateDictResizePolicy(void) {
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
|
||||
if (!hasActiveChildProcess())
|
||||
dictEnableResize();
|
||||
else
|
||||
dictDisableResize();
|
||||
}
|
||||
|
||||
int hasActiveChildProcess() {
|
||||
return server.rdb_child_pid != -1 ||
|
||||
server.aof_child_pid != -1 ||
|
||||
server.module_child_pid != -1;
|
||||
}
|
||||
|
||||
/* ======================= Cron: called every 100 ms ======================== */
|
||||
|
||||
/* 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
|
||||
* other processes saving the DB on disk. Otherwise rehashing is bad
|
||||
* 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
|
||||
* DB we'll be able to start from the successive in the next
|
||||
* cron loop iteration. */
|
||||
@ -1764,18 +1770,32 @@ void checkChildrenDone(void) {
|
||||
|
||||
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) {
|
||||
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),
|
||||
(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) {
|
||||
backgroundSaveDoneHandler(exitcode,bysignal);
|
||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||
} else if (pid == server.aof_child_pid) {
|
||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||
} else if (pid == server.module_child_pid) {
|
||||
ModuleForkDoneHandler(exitcode,bysignal);
|
||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||
} else {
|
||||
if (!ldbRemoveChild(pid)) {
|
||||
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
|
||||
* a BGSAVE was in progress. */
|
||||
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
|
||||
if (!hasActiveChildProcess() &&
|
||||
server.aof_rewrite_scheduled)
|
||||
{
|
||||
rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
|
||||
/* Check if a background saving or AOF rewrite in progress terminated. */
|
||||
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
|
||||
ldbPendingChildren())
|
||||
if (hasActiveChildProcess() || ldbPendingChildren())
|
||||
{
|
||||
checkChildrenDone();
|
||||
} else {
|
||||
@ -1968,8 +1987,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
|
||||
/* Trigger an AOF rewrite if needed. */
|
||||
if (server.aof_state == AOF_ON &&
|
||||
server.rdb_child_pid == -1 &&
|
||||
server.aof_child_pid == -1 &&
|
||||
!hasActiveChildProcess() &&
|
||||
server.aof_rewrite_perc &&
|
||||
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
|
||||
* make sure when refactoring this file to keep this order. This is useful
|
||||
* 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.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
|
||||
server.lastbgsave_status == C_OK))
|
||||
@ -2261,6 +2279,7 @@ void initServerConfig(void) {
|
||||
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
|
||||
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
|
||||
server.active_expire_enabled = 1;
|
||||
server.jemalloc_bg_thread = 1;
|
||||
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
|
||||
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
|
||||
server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER;
|
||||
@ -2828,6 +2847,7 @@ void initServer(void) {
|
||||
server.cronloops = 0;
|
||||
server.rdb_child_pid = -1;
|
||||
server.aof_child_pid = -1;
|
||||
server.module_child_pid = -1;
|
||||
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||
server.rdb_pipe_conns = NULL;
|
||||
server.rdb_pipe_numconns = 0;
|
||||
@ -2851,6 +2871,7 @@ void initServer(void) {
|
||||
server.stat_peak_memory = 0;
|
||||
server.stat_rdb_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.process_rss = 0;
|
||||
server.cron_malloc_stats.allocator_allocated = 0;
|
||||
@ -2926,8 +2947,17 @@ void initServer(void) {
|
||||
scriptingInit(1);
|
||||
slowlogInit();
|
||||
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();
|
||||
initThreadedIO();
|
||||
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
|
||||
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
|
||||
* 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;
|
||||
if (auth_required || DefaultUser->flags & USER_FLAG_DISABLED) {
|
||||
if (auth_required) {
|
||||
/* 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);
|
||||
addReply(c,shared.noautherr);
|
||||
return C_OK;
|
||||
@ -3453,7 +3484,10 @@ int processCommand(client *c) {
|
||||
* is in MULTI/EXEC context? Error. */
|
||||
if (out_of_memory &&
|
||||
(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);
|
||||
addReply(c, shared.oomerr);
|
||||
return C_OK;
|
||||
@ -3605,6 +3639,12 @@ int prepareForShutdown(int flags) {
|
||||
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) {
|
||||
/* Kill the AOF saving child as the AOF we already have may be longer
|
||||
* but contains the full dataset anyway. */
|
||||
@ -3874,12 +3914,15 @@ sds genRedisInfoString(char *section) {
|
||||
time_t uptime = server.unixtime-server.stat_starttime;
|
||||
int j;
|
||||
struct rusage self_ru, c_ru;
|
||||
int allsections = 0, defsections = 0;
|
||||
int allsections = 0, defsections = 0, everything = 0, modules = 0;
|
||||
int sections = 0;
|
||||
|
||||
if (section == NULL) section = "default";
|
||||
allsections = strcasecmp(section,"all") == 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_CHILDREN, &c_ru);
|
||||
@ -3902,32 +3945,32 @@ sds genRedisInfoString(char *section) {
|
||||
call_uname = 0;
|
||||
}
|
||||
|
||||
info = sdscatprintf(info,
|
||||
info = sdscatfmt(info,
|
||||
"# Server\r\n"
|
||||
"redis_version:%s\r\n"
|
||||
"redis_git_sha1:%s\r\n"
|
||||
"redis_git_dirty:%d\r\n"
|
||||
"redis_build_id:%llx\r\n"
|
||||
"redis_git_dirty:%i\r\n"
|
||||
"redis_build_id:%s\r\n"
|
||||
"redis_mode:%s\r\n"
|
||||
"os:%s %s %s\r\n"
|
||||
"arch_bits:%d\r\n"
|
||||
"arch_bits:%i\r\n"
|
||||
"multiplexing_api:%s\r\n"
|
||||
"atomicvar_api:%s\r\n"
|
||||
"gcc_version:%d.%d.%d\r\n"
|
||||
"process_id:%ld\r\n"
|
||||
"gcc_version:%i.%i.%i\r\n"
|
||||
"process_id:%I\r\n"
|
||||
"run_id:%s\r\n"
|
||||
"tcp_port:%d\r\n"
|
||||
"uptime_in_seconds:%jd\r\n"
|
||||
"uptime_in_days:%jd\r\n"
|
||||
"hz:%d\r\n"
|
||||
"configured_hz:%d\r\n"
|
||||
"lru_clock:%ld\r\n"
|
||||
"tcp_port:%i\r\n"
|
||||
"uptime_in_seconds:%I\r\n"
|
||||
"uptime_in_days:%I\r\n"
|
||||
"hz:%i\r\n"
|
||||
"configured_hz:%i\r\n"
|
||||
"lru_clock:%u\r\n"
|
||||
"executable:%s\r\n"
|
||||
"config_file:%s\r\n",
|
||||
REDIS_VERSION,
|
||||
redisGitSHA1(),
|
||||
strtol(redisGitDirty(),NULL,10) > 0,
|
||||
(unsigned long long) redisBuildId(),
|
||||
redisBuildIdString(),
|
||||
mode,
|
||||
name.sysname, name.release, name.machine,
|
||||
server.arch_bits,
|
||||
@ -3938,14 +3981,14 @@ sds genRedisInfoString(char *section) {
|
||||
#else
|
||||
0,0,0,
|
||||
#endif
|
||||
(long) getpid(),
|
||||
(int64_t) getpid(),
|
||||
server.runid,
|
||||
server.port ? server.port : server.tls_port,
|
||||
(intmax_t)uptime,
|
||||
(intmax_t)(uptime/(3600*24)),
|
||||
(int64_t)uptime,
|
||||
(int64_t)(uptime/(3600*24)),
|
||||
server.hz,
|
||||
server.config_hz,
|
||||
(unsigned long) server.lruclock,
|
||||
server.lruclock,
|
||||
server.executable ? server.executable : "",
|
||||
server.configfile ? server.configfile : "");
|
||||
}
|
||||
@ -4071,8 +4114,11 @@ sds genRedisInfoString(char *section) {
|
||||
mh->allocator_rss_bytes,
|
||||
mh->rss_extra,
|
||||
mh->rss_extra_bytes,
|
||||
mh->total_frag, /* this is the total RSS overhead, including fragmentation, */
|
||||
mh->total_frag_bytes, /* named so for backwards compatibility */
|
||||
mh->total_frag, /* This is the total RSS overhead, including
|
||||
fragmentation, but not just it. This field
|
||||
(and the next one) is named like that just
|
||||
for backward compatibility. */
|
||||
mh->total_frag_bytes,
|
||||
freeMemoryGetNotCountedMemory(),
|
||||
mh->repl_backlog,
|
||||
mh->clients_slaves,
|
||||
@ -4105,7 +4151,9 @@ sds genRedisInfoString(char *section) {
|
||||
"aof_current_rewrite_time_sec:%jd\r\n"
|
||||
"aof_last_bgrewrite_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.dirty,
|
||||
server.rdb_child_pid != -1,
|
||||
@ -4123,7 +4171,9 @@ sds genRedisInfoString(char *section) {
|
||||
-1 : time(NULL)-server.aof_rewrite_time_start),
|
||||
(server.aof_lastbgrewrite_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) {
|
||||
info = sdscatprintf(info,
|
||||
@ -4281,7 +4331,7 @@ sds genRedisInfoString(char *section) {
|
||||
if (server.repl_state != REPL_STATE_CONNECTED) {
|
||||
info = sdscatprintf(info,
|
||||
"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,
|
||||
"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);
|
||||
}
|
||||
|
||||
/* 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 */
|
||||
if (allsections || !strcasecmp(section,"commandstats")) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -4434,7 +4502,9 @@ void infoCommand(client *c) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
addReplyBulkSds(c, genRedisInfoString(section));
|
||||
sds info = genRedisInfoString(section);
|
||||
addReplyVerbatim(c,info,sdslen(info),"txt");
|
||||
sdsfree(info);
|
||||
}
|
||||
|
||||
void monitorCommand(client *c) {
|
||||
@ -4621,6 +4691,61 @@ void setupSignalHandlers(void) {
|
||||
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);
|
||||
|
||||
/* Returns 1 if there is --sentinel among the arguments or if
|
||||
@ -4647,12 +4772,14 @@ void loadDataFromDisk(void) {
|
||||
(float)(ustime()-start)/1000000);
|
||||
|
||||
/* 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_offset != -1 &&
|
||||
/* Note that older implementations may save a repl_stream_db
|
||||
* of -1 inside the RDB file in a wrong way, see more information
|
||||
* in function rdbPopulateSaveInfo. */
|
||||
* of -1 inside the RDB file in a wrong way, see more
|
||||
* information in function rdbPopulateSaveInfo. */
|
||||
rsi.repl_stream_db != -1)
|
||||
{
|
||||
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
|
||||
@ -4828,9 +4955,9 @@ int main(int argc, char **argv) {
|
||||
srand(time(NULL)^getpid());
|
||||
gettimeofday(&tv,NULL);
|
||||
|
||||
char hashseed[16];
|
||||
getRandomHexChars(hashseed,sizeof(hashseed));
|
||||
dictSetHashFunctionSeed((uint8_t*)hashseed);
|
||||
uint8_t hashseed[16];
|
||||
getRandomBytes(hashseed,sizeof(hashseed));
|
||||
dictSetHashFunctionSeed(hashseed);
|
||||
server.sentinel_mode = checkForSentinelMode(argc,argv);
|
||||
initServerConfig();
|
||||
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
|
||||
@ -4960,6 +5087,7 @@ int main(int argc, char **argv) {
|
||||
#endif
|
||||
moduleLoadFromQueue();
|
||||
ACLLoadUsersAtStartup();
|
||||
InitServerLast();
|
||||
loadDataFromDisk();
|
||||
if (server.cluster_enabled) {
|
||||
if (verifyClusterConfigWithData() == C_ERR) {
|
||||
@ -4974,6 +5102,7 @@ int main(int argc, char **argv) {
|
||||
if (server.sofd > 0)
|
||||
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
|
||||
} else {
|
||||
InitServerLast();
|
||||
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_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. */
|
||||
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
|
||||
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
|
||||
@ -1060,6 +1068,7 @@ struct clusterState;
|
||||
#define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL
|
||||
#define CHILD_INFO_TYPE_RDB 0
|
||||
#define CHILD_INFO_TYPE_AOF 1
|
||||
#define CHILD_INFO_TYPE_MODULE 3
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
@ -1095,6 +1104,7 @@ struct redisServer {
|
||||
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
|
||||
client blocked on a module command needs
|
||||
to be processed. */
|
||||
pid_t module_child_pid; /* PID of module child */
|
||||
/* Networking */
|
||||
int port; /* TCP 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. */
|
||||
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
|
||||
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
|
||||
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
|
||||
/* The following two are used to track instantaneous metrics, like
|
||||
* number of operations per second, network traffic. */
|
||||
struct {
|
||||
@ -1185,6 +1196,7 @@ struct redisServer {
|
||||
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
|
||||
int active_expire_enabled; /* Can be disabled for testing purposes. */
|
||||
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 */
|
||||
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 */
|
||||
@ -1408,6 +1420,7 @@ struct redisServer {
|
||||
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_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 */
|
||||
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
|
||||
mstime_t lua_time_limit; /* Script timeout in milliseconds */
|
||||
@ -1575,7 +1588,11 @@ void moduleAcquireGIL(void);
|
||||
void moduleReleaseGIL(void);
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||
void moduleCallCommandFilters(client *c);
|
||||
void ModuleForkDoneHandler(int exitcode, int bysignal);
|
||||
int TerminateModuleForkChild(int child_pid, int wait);
|
||||
ssize_t rdbSaveModulesAux(rio *rdb, int when);
|
||||
int moduleAllDatatypesHandleErrors();
|
||||
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections);
|
||||
|
||||
/* Utils */
|
||||
long long ustime(void);
|
||||
@ -1840,6 +1857,11 @@ void closeChildInfoPipe(void);
|
||||
void sendChildInfo(int process_type);
|
||||
void receiveChildInfo(void);
|
||||
|
||||
/* Fork helpers */
|
||||
int redisFork();
|
||||
int hasActiveChildProcess();
|
||||
void sendChildCOWInfo(int ptype, char *pname);
|
||||
|
||||
/* acl.c -- Authentication related prototypes. */
|
||||
extern rax *Users;
|
||||
extern user *DefaultUser;
|
||||
@ -1940,6 +1962,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
|
||||
void call(client *c, 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 redisOpArrayInit(redisOpArray *oa);
|
||||
void redisOpArrayFree(redisOpArray *oa);
|
||||
void forceCommandPropagation(client *c, int flags);
|
||||
void preventCommandPropagation(client *c);
|
||||
void preventCommandAOF(client *c);
|
||||
@ -2159,6 +2183,7 @@ void dictSdsDestructor(void *privdata, void *val);
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
uint64_t redisBuildId(void);
|
||||
char *redisBuildIdString(void);
|
||||
|
||||
/* Commands prototypes */
|
||||
void authCommand(client *c);
|
||||
@ -2373,6 +2398,7 @@ void bugReportStart(void);
|
||||
void serverLogObjectDebugInfo(const robj *o);
|
||||
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
|
||||
sds genRedisInfoString(char *section);
|
||||
sds genModulesInfoString(sds info);
|
||||
void enableWatchdog(int period);
|
||||
void disableWatchdog(void);
|
||||
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.
|
||||
* Two interesting conditions to speedup the function that happen to be
|
||||
* 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
|
||||
#endif
|
||||
|
||||
|
@ -88,7 +88,7 @@ typedef struct streamNACK {
|
||||
|
||||
/* Stream propagation informations, passed to functions in order to propagate
|
||||
* XCLAIM commands to AOF and slaves. */
|
||||
typedef struct sreamPropInfo {
|
||||
typedef struct streamPropInfo {
|
||||
robj *keyname;
|
||||
robj *groupname;
|
||||
} streamPropInfo;
|
||||
|
@ -242,17 +242,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
|
||||
* the current node is full. */
|
||||
if (lp != NULL) {
|
||||
if (server.stream_node_max_bytes &&
|
||||
lp_bytes > server.stream_node_max_bytes)
|
||||
lp_bytes >= server.stream_node_max_bytes)
|
||||
{
|
||||
lp = NULL;
|
||||
} else if (server.stream_node_max_entries) {
|
||||
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;
|
||||
if (lp == NULL || lp_bytes > server.stream_node_max_bytes) {
|
||||
if (lp == NULL || lp_bytes >= server.stream_node_max_bytes) {
|
||||
master_id = id;
|
||||
streamEncodeID(rax_key,&id);
|
||||
/* Create the listpack having the master entry ID and fields. */
|
||||
|
@ -326,6 +326,7 @@ size_t zmalloc_get_rss(void) {
|
||||
#endif
|
||||
|
||||
#if defined(USE_JEMALLOC)
|
||||
|
||||
int zmalloc_get_allocator_info(size_t *allocated,
|
||||
size_t *active,
|
||||
size_t *resident) {
|
||||
@ -347,13 +348,44 @@ int zmalloc_get_allocator_info(size_t *allocated,
|
||||
je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
|
||||
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
|
||||
|
||||
int zmalloc_get_allocator_info(size_t *allocated,
|
||||
size_t *active,
|
||||
size_t *resident) {
|
||||
*allocated = *resident = *active = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void set_jemalloc_bg_thread(int enable) {
|
||||
((void)(enable));
|
||||
}
|
||||
|
||||
int jemalloc_purge() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* 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));
|
||||
size_t zmalloc_get_rss(void);
|
||||
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_smap_bytes_by_field(char *field, long pid);
|
||||
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"}} {
|
||||
set slave [srv 0 client]
|
||||
set slave_host [srv 0 host]
|
||||
|
@ -13,16 +13,28 @@ endif
|
||||
|
||||
.SUFFIXES: .c .so .xo .o
|
||||
|
||||
all: commandfilter.so testrdb.so
|
||||
all: commandfilter.so testrdb.so fork.so infotest.so propagate.so
|
||||
|
||||
.c.xo:
|
||||
$(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
|
||||
|
||||
commandfilter.xo: ../../src/redismodule.h
|
||||
fork.xo: ../../src/redismodule.h
|
||||
testrdb.xo: ../../src/redismodule.h
|
||||
infotest.xo: ../../src/redismodule.h
|
||||
propagate.xo: ../../src/redismodule.h
|
||||
|
||||
commandfilter.so: commandfilter.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
fork.so: fork.xo
|
||||
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
|
||||
|
||||
testrdb.so: testrdb.xo
|
||||
$(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) {
|
||||
int count = RedisModule_LoadSigned(rdb);
|
||||
if (RedisModule_IsIOError(rdb))
|
||||
return NULL;
|
||||
assert(count==1);
|
||||
assert(encver==1);
|
||||
RedisModuleString *str = RedisModule_LoadString(rdb);
|
||||
@ -57,6 +59,8 @@ int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
|
||||
RedisModule_FreeString(ctx, before_str);
|
||||
before_str = NULL;
|
||||
int count = RedisModule_LoadSigned(rdb);
|
||||
if (RedisModule_IsIOError(rdb))
|
||||
return REDISMODULE_ERR;
|
||||
if (count)
|
||||
before_str = RedisModule_LoadString(rdb);
|
||||
} else {
|
||||
@ -64,14 +68,19 @@ int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
|
||||
RedisModule_FreeString(ctx, after_str);
|
||||
after_str = NULL;
|
||||
int count = RedisModule_LoadSigned(rdb);
|
||||
if (RedisModule_IsIOError(rdb))
|
||||
return REDISMODULE_ERR;
|
||||
if (count)
|
||||
after_str = RedisModule_LoadString(rdb);
|
||||
}
|
||||
if (RedisModule_IsIOError(rdb))
|
||||
return REDISMODULE_ERR;
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
void testrdb_type_free(void *value) {
|
||||
RedisModule_FreeString(NULL, (RedisModuleString*)value);
|
||||
if (value)
|
||||
RedisModule_FreeString(NULL, (RedisModuleString*)value);
|
||||
}
|
||||
|
||||
int testrdb_set_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
@ -171,6 +180,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
if (RedisModule_Init(ctx,"testrdb",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS);
|
||||
|
||||
if (argc > 0)
|
||||
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} {
|
||||
if {![string match $pattern $value]} {
|
||||
error "assertion:Expected '$value' to match '$pattern'"
|
||||
|
@ -35,6 +35,32 @@ start_server {tags {"acl"}} {
|
||||
set e
|
||||
} {*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} {
|
||||
catch {r SET foo bar} e
|
||||
set e
|
||||
@ -67,7 +93,7 @@ start_server {tags {"acl"}} {
|
||||
set e
|
||||
} {*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 SADD myset a b c; # Should not raise an error
|
||||
r ACL setuser newuser +@all -@string
|
||||
|
@ -129,7 +129,7 @@ start_server {tags {"geo"}} {
|
||||
r del points
|
||||
r geoadd points -5.6 42.6 test
|
||||
lindex [r geohash points test] 0
|
||||
} {ezs42e44yx0}
|
||||
} {ezs42e44yx}
|
||||
|
||||
test {GEOPOS simple} {
|
||||
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
|
||||
}
|
||||
|
||||
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 XTRIM mystream MAXLEN ~ 85
|
||||
assert {[r xlen mystream] == 89}
|
||||
assert {[r xlen mystream] == 90}
|
||||
r config set stream-node-max-entries 1
|
||||
r debug loadaof
|
||||
r XADD mystream * xitem v
|
||||
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.
|
||||
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
|
||||
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.
|
||||
|
||||
In order to stop a cluster:
|
||||
|
@ -1,10 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Settings
|
||||
CLUSTER_HOST=127.0.0.1
|
||||
PORT=30000
|
||||
TIMEOUT=2000
|
||||
NODES=6
|
||||
REPLICAS=1
|
||||
PROTECTED_MODE=yes
|
||||
|
||||
# You may want to put the above config parameters into config.sh in order to
|
||||
# override the defaults without modifying this script.
|
||||
@ -22,7 +24,7 @@ then
|
||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||
PORT=$((PORT+1))
|
||||
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
|
||||
exit 0
|
||||
fi
|
||||
@ -32,7 +34,7 @@ then
|
||||
HOSTS=""
|
||||
while [ $((PORT < ENDPORT)) != "0" ]; do
|
||||
PORT=$((PORT+1))
|
||||
HOSTS="$HOSTS 127.0.0.1:$PORT"
|
||||
HOSTS="$HOSTS $CLUSTER_HOST:$PORT"
|
||||
done
|
||||
../../src/redis-cli --cluster create $HOSTS --cluster-replicas $REPLICAS
|
||||
exit 0
|
||||
|
Loading…
x
Reference in New Issue
Block a user