Commit 819d291b authored by Oran Agra's avatar Oran Agra
Browse files

Merge remote-tracking branch 'origin/unstable' into 7.2

parents a51eb05b 936cfa46
...@@ -180,10 +180,17 @@ int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) ...@@ -180,10 +180,17 @@ int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen)
/* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
* EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
* logic consistent. */ * logic consistent.
if (errno == EWOULDBLOCK) { * Additionally, WSAALREADY is can be reported as WSAEINVAL to and this is
* translated to EIO. Convert appropriately
*/
int err = errno;
if (err == EWOULDBLOCK) {
errno = EINPROGRESS; errno = EINPROGRESS;
} }
else if (err == EIO) {
errno = EALREADY;
}
return ret != SOCKET_ERROR ? ret : -1; return ret != SOCKET_ERROR ? ret : -1;
} }
...@@ -205,6 +212,14 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle ...@@ -205,6 +212,14 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle
} else { } else {
ret = getsockopt(sockfd, level, optname, (char*)optval, optlen); ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
} }
if (ret != SOCKET_ERROR && level == SOL_SOCKET && optname == SO_ERROR) {
/* translate SO_ERROR codes, if non-zero */
int err = *(int*)optval;
if (err != 0) {
err = _wsaErrorToErrno(err);
*(int*)optval = err;
}
}
_updateErrno(ret != SOCKET_ERROR); _updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1; return ret != SOCKET_ERROR ? ret : -1;
} }
...@@ -245,4 +260,21 @@ int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) { ...@@ -245,4 +260,21 @@ int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
_updateErrno(ret != SOCKET_ERROR); _updateErrno(ret != SOCKET_ERROR);
return ret != SOCKET_ERROR ? ret : -1; return ret != SOCKET_ERROR ? ret : -1;
} }
int win32_redisKeepAlive(SOCKET sockfd, int interval_ms) {
struct tcp_keepalive cfg;
DWORD bytes_in;
int res;
cfg.onoff = 1;
cfg.keepaliveinterval = interval_ms;
cfg.keepalivetime = interval_ms;
res = WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, &cfg,
sizeof(struct tcp_keepalive), NULL, 0,
&bytes_in, NULL, NULL);
return res == 0 ? 0 : _wsaErrorToErrno(res);
}
#endif /* _WIN32 */ #endif /* _WIN32 */
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <stddef.h> #include <stddef.h>
#include <errno.h> #include <errno.h>
#include <mstcpip.h>
#ifdef _MSC_VER #ifdef _MSC_VER
typedef long long ssize_t; typedef long long ssize_t;
...@@ -71,6 +72,8 @@ ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags); ...@@ -71,6 +72,8 @@ ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
typedef ULONG nfds_t; typedef ULONG nfds_t;
int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout); int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
int win32_redisKeepAlive(SOCKET sockfd, int interval_ms);
#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION #ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res) #define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
#undef gai_strerror #undef gai_strerror
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "hiredis.h" #include "hiredis.h"
#include "async.h" #include "async.h"
#include "net.h"
#include <assert.h> #include <assert.h>
#include <errno.h> #include <errno.h>
...@@ -39,6 +40,14 @@ ...@@ -39,6 +40,14 @@
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <wincrypt.h> #include <wincrypt.h>
#ifdef OPENSSL_IS_BORINGSSL
#undef X509_NAME
#undef X509_EXTENSIONS
#undef PKCS7_ISSUER_AND_SERIAL
#undef PKCS7_SIGNER_INFO
#undef OCSP_REQUEST
#undef OCSP_RESPONSE
#endif
#else #else
#include <pthread.h> #include <pthread.h>
#endif #endif
...@@ -219,6 +228,25 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -219,6 +228,25 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
const char *cert_filename, const char *private_key_filename, const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error) const char *server_name, redisSSLContextError *error)
{ {
redisSSLOptions options = {
.cacert_filename = cacert_filename,
.capath = capath,
.cert_filename = cert_filename,
.private_key_filename = private_key_filename,
.server_name = server_name,
.verify_mode = REDIS_SSL_VERIFY_PEER,
};
return redisCreateSSLContextWithOptions(&options, error);
}
redisSSLContext *redisCreateSSLContextWithOptions(redisSSLOptions *options, redisSSLContextError *error) {
const char *cacert_filename = options->cacert_filename;
const char *capath = options->capath;
const char *cert_filename = options->cert_filename;
const char *private_key_filename = options->private_key_filename;
const char *server_name = options->server_name;
#ifdef _WIN32 #ifdef _WIN32
HCERTSTORE win_store = NULL; HCERTSTORE win_store = NULL;
PCCERT_CONTEXT win_ctx = NULL; PCCERT_CONTEXT win_ctx = NULL;
...@@ -235,7 +263,7 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -235,7 +263,7 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
} }
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL); SSL_CTX_set_verify(ctx->ssl_ctx, options->verify_mode, NULL);
if ((cert_filename != NULL && private_key_filename == NULL) || if ((cert_filename != NULL && private_key_filename == NULL) ||
(private_key_filename != NULL && cert_filename == NULL)) { (private_key_filename != NULL && cert_filename == NULL)) {
...@@ -273,6 +301,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char * ...@@ -273,6 +301,11 @@ redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *
if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED; if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
goto error; goto error;
} }
} else {
if (!SSL_CTX_set_default_verify_paths(ctx->ssl_ctx)) {
if (error) *error = REDIS_SSL_CTX_CLIENT_DEFAULT_CERT_FAILED;
goto error;
}
} }
if (cert_filename) { if (cert_filename) {
...@@ -560,6 +593,7 @@ static void redisSSLAsyncWrite(redisAsyncContext *ac) { ...@@ -560,6 +593,7 @@ static void redisSSLAsyncWrite(redisAsyncContext *ac) {
} }
redisContextFuncs redisContextSSLFuncs = { redisContextFuncs redisContextSSLFuncs = {
.close = redisNetClose,
.free_privctx = redisSSLFree, .free_privctx = redisSSLFree,
.async_read = redisSSLAsyncRead, .async_read = redisSSLAsyncRead,
.async_write = redisSSLAsyncWrite, .async_write = redisSSLAsyncWrite,
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "hiredis.h" #include "hiredis.h"
#include "async.h" #include "async.h"
#include "adapters/poll.h"
#ifdef HIREDIS_TEST_SSL #ifdef HIREDIS_TEST_SSL
#include "hiredis_ssl.h" #include "hiredis_ssl.h"
#endif #endif
...@@ -34,11 +35,11 @@ enum connection_type { ...@@ -34,11 +35,11 @@ enum connection_type {
struct config { struct config {
enum connection_type type; enum connection_type type;
struct timeval connect_timeout;
struct { struct {
const char *host; const char *host;
int port; int port;
struct timeval timeout;
} tcp; } tcp;
struct { struct {
...@@ -75,6 +76,15 @@ static int tests = 0, fails = 0, skips = 0; ...@@ -75,6 +76,15 @@ static int tests = 0, fails = 0, skips = 0;
#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
#define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; } #define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; }
static void millisleep(int ms)
{
#if _MSC_VER
Sleep(ms);
#else
usleep(ms*1000);
#endif
}
static long long usec(void) { static long long usec(void) {
#ifndef _MSC_VER #ifndef _MSC_VER
struct timeval tv; struct timeval tv;
...@@ -329,10 +339,14 @@ static void test_format_commands(void) { ...@@ -329,10 +339,14 @@ static void test_format_commands(void) {
FLOAT_WIDTH_TEST(float); FLOAT_WIDTH_TEST(float);
FLOAT_WIDTH_TEST(double); FLOAT_WIDTH_TEST(double);
test("Format command with invalid printf format: "); test("Format command with unhandled printf format (specifier 'p' not supported): ");
len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3); len = redisFormatCommand(&cmd,"key:%08p %b",(void*)1234,"foo",(size_t)3);
test_cond(len == -1); test_cond(len == -1);
test("Format command with invalid printf format (specifier missing): ");
len = redisFormatCommand(&cmd,"%-");
test_cond(len == -1);
const char *argv[3]; const char *argv[3];
argv[0] = "SET"; argv[0] = "SET";
argv[1] = "foo\0xxx"; argv[1] = "foo\0xxx";
...@@ -391,6 +405,16 @@ static void test_append_formatted_commands(struct config config) { ...@@ -391,6 +405,16 @@ static void test_append_formatted_commands(struct config config) {
disconnect(c, 0); disconnect(c, 0);
} }
static void test_tcp_options(struct config cfg) {
redisContext *c;
c = do_connect(cfg);
test("We can enable TCP_KEEPALIVE: ");
test_cond(redisEnableKeepAlive(c) == REDIS_OK);
disconnect(c, 0);
}
static void test_reply_reader(void) { static void test_reply_reader(void) {
redisReader *reader; redisReader *reader;
void *reply, *root; void *reply, *root;
...@@ -568,6 +592,19 @@ static void test_reply_reader(void) { ...@@ -568,6 +592,19 @@ static void test_reply_reader(void) {
test_cond(ret == REDIS_ERR && reply == NULL); test_cond(ret == REDIS_ERR && reply == NULL);
redisReaderFree(reader); redisReaderFree(reader);
test("Don't reset state after protocol error(not segfault): ");
reader = redisReaderCreate();
redisReaderFeed(reader,(char*)"*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$", 25);
ret = redisReaderGetReply(reader,&reply);
assert(ret == REDIS_OK);
redisReaderFeed(reader,(char*)"3\r\nval\r\n", 8);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
((redisReply*)reply)->elements == 3);
freeReplyObject(reply);
redisReaderFree(reader);
/* Regression test for issue #45 on GitHub. */ /* Regression test for issue #45 on GitHub. */
test("Don't do empty allocation for empty multi bulk: "); test("Don't do empty allocation for empty multi bulk: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
...@@ -637,12 +674,23 @@ static void test_reply_reader(void) { ...@@ -637,12 +674,23 @@ static void test_reply_reader(void) {
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
test("Set error when RESP3 double is NaN: "); test("Correctly parses RESP3 double NaN: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
redisReaderFeed(reader, ",nan\r\n",6); redisReaderFeed(reader, ",nan\r\n",6);
ret = redisReaderGetReply(reader,&reply); ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR && test_cond(ret == REDIS_OK &&
strcasecmp(reader->errstr,"Bad double value") == 0); ((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
isnan(((redisReply*)reply)->dval));
freeReplyObject(reply);
redisReaderFree(reader);
test("Correctly parses RESP3 double -Nan: ");
reader = redisReaderCreate();
redisReaderFeed(reader, ",-nan\r\n", 7);
ret = redisReaderGetReply(reader, &reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_DOUBLE &&
isnan(((redisReply*)reply)->dval));
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
...@@ -745,6 +793,20 @@ static void test_reply_reader(void) { ...@@ -745,6 +793,20 @@ static void test_reply_reader(void) {
!strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385")); !strcmp(((redisReply*)reply)->str,"3492890328409238509324850943850943825024385"));
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
test("Can parse RESP3 doubles in an array: ");
reader = redisReaderCreate();
redisReaderFeed(reader, "*1\r\n,3.14159265358979323846\r\n",31);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
((redisReply*)reply)->elements == 1 &&
((redisReply*)reply)->element[0]->type == REDIS_REPLY_DOUBLE &&
fabs(((redisReply*)reply)->element[0]->dval - 3.14159265358979323846) < 0.00000001 &&
((redisReply*)reply)->element[0]->len == 22 &&
strcmp(((redisReply*)reply)->element[0]->str, "3.14159265358979323846") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
} }
static void test_free_null(void) { static void test_free_null(void) {
...@@ -819,9 +881,9 @@ static void test_allocator_injection(void) { ...@@ -819,9 +881,9 @@ static void test_allocator_injection(void) {
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com" #define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
static void test_blocking_connection_errors(void) { static void test_blocking_connection_errors(void) {
redisContext *c;
struct addrinfo hints = {.ai_family = AF_INET}; struct addrinfo hints = {.ai_family = AF_INET};
struct addrinfo *ai_tmp = NULL; struct addrinfo *ai_tmp = NULL;
redisContext *c;
int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp); int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
if (rv != 0) { if (rv != 0) {
...@@ -835,6 +897,7 @@ static void test_blocking_connection_errors(void) { ...@@ -835,6 +897,7 @@ static void test_blocking_connection_errors(void) {
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
strcmp(c->errstr, "Name does not resolve") == 0 || strcmp(c->errstr, "Name does not resolve") == 0 ||
strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 || strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
strcmp(c->errstr, "node name or service name not known") == 0 ||
strcmp(c->errstr, "No address associated with hostname") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 ||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 || strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 || strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
...@@ -847,12 +910,26 @@ static void test_blocking_connection_errors(void) { ...@@ -847,12 +910,26 @@ static void test_blocking_connection_errors(void) {
} }
#ifndef _WIN32 #ifndef _WIN32
redisOptions opt = {0};
struct timeval tv;
test("Returns error when the port is not open: "); test("Returns error when the port is not open: ");
c = redisConnect((char*)"localhost", 1); c = redisConnect((char*)"localhost", 1);
test_cond(c->err == REDIS_ERR_IO && test_cond(c->err == REDIS_ERR_IO &&
strcmp(c->errstr,"Connection refused") == 0); strcmp(c->errstr,"Connection refused") == 0);
redisFree(c); redisFree(c);
/* Verify we don't regress from the fix in PR #1180 */
test("We don't clobber connection exception with setsockopt error: ");
tv = (struct timeval){.tv_sec = 0, .tv_usec = 500000};
opt.command_timeout = opt.connect_timeout = &tv;
REDIS_OPTIONS_SET_TCP(&opt, "localhost", 10337);
c = redisConnectWithOptions(&opt);
test_cond(c->err == REDIS_ERR_IO &&
strcmp(c->errstr, "Connection refused") == 0);
redisFree(c);
test("Returns error when the unix_sock socket path doesn't accept connections: "); test("Returns error when the unix_sock socket path doesn't accept connections: ");
c = redisConnectUnix((char*)"/tmp/idontexist.sock"); c = redisConnectUnix((char*)"/tmp/idontexist.sock");
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
...@@ -914,11 +991,19 @@ static void test_resp3_push_handler(redisContext *c) { ...@@ -914,11 +991,19 @@ static void test_resp3_push_handler(redisContext *c) {
old = redisSetPushCallback(c, push_handler); old = redisSetPushCallback(c, push_handler);
test("We can set a custom RESP3 PUSH handler: "); test("We can set a custom RESP3 PUSH handler: ");
reply = redisCommand(c, "SET key:0 val:0"); reply = redisCommand(c, "SET key:0 val:0");
/* We need another command because depending on the version of Redis, the
* notification may be delivered after the command's reply. */
assert(reply != NULL);
freeReplyObject(reply);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1);
freeReplyObject(reply); freeReplyObject(reply);
test("We properly handle a NIL invalidation payload: "); test("We properly handle a NIL invalidation payload: ");
reply = redisCommand(c, "FLUSHDB"); reply = redisCommand(c, "FLUSHDB");
assert(reply != NULL);
freeReplyObject(reply);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1); test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1);
freeReplyObject(reply); freeReplyObject(reply);
...@@ -929,6 +1014,12 @@ static void test_resp3_push_handler(redisContext *c) { ...@@ -929,6 +1014,12 @@ static void test_resp3_push_handler(redisContext *c) {
assert((reply = redisCommand(c, "GET key:0")) != NULL); assert((reply = redisCommand(c, "GET key:0")) != NULL);
freeReplyObject(reply); freeReplyObject(reply);
assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL); assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL);
/* Depending on Redis version, we may receive either push notification or
* status reply. Both cases are valid. */
if (reply->type == REDIS_REPLY_STATUS) {
freeReplyObject(reply);
reply = redisCommand(c, "PING");
}
test_cond(reply->type == REDIS_REPLY_PUSH); test_cond(reply->type == REDIS_REPLY_PUSH);
freeReplyObject(reply); freeReplyObject(reply);
...@@ -1089,6 +1180,13 @@ static void test_blocking_connection(struct config config) { ...@@ -1089,6 +1180,13 @@ static void test_blocking_connection(struct config config) {
strcasecmp(reply->element[1]->str,"pong") == 0); strcasecmp(reply->element[1]->str,"pong") == 0);
freeReplyObject(reply); freeReplyObject(reply);
test("Send command by passing argc/argv: ");
const char *argv[3] = {"SET", "foo", "bar"};
size_t argvlen[3] = {3, 3, 3};
reply = redisCommandArgv(c,3,argv,argvlen);
test_cond(reply->type == REDIS_REPLY_STATUS);
freeReplyObject(reply);
/* Make sure passing NULL to redisGetReply is safe */ /* Make sure passing NULL to redisGetReply is safe */
test("Can pass NULL to redisGetReply: "); test("Can pass NULL to redisGetReply: ");
assert(redisAppendCommand(c, "PING") == REDIS_OK); assert(redisAppendCommand(c, "PING") == REDIS_OK);
...@@ -1143,7 +1241,13 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -1143,7 +1241,13 @@ static void test_blocking_connection_timeouts(struct config config) {
test("Does not return a reply when the command times out: "); test("Does not return a reply when the command times out: ");
if (detect_debug_sleep(c)) { if (detect_debug_sleep(c)) {
redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd)); redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
// flush connection buffer without waiting for the reply
s = c->funcs->write(c); s = c->funcs->write(c);
assert(s == (ssize_t)hi_sdslen(c->obuf));
hi_sdsfree(c->obuf);
c->obuf = hi_sdsempty();
tv.tv_sec = 0; tv.tv_sec = 0;
tv.tv_usec = 10000; tv.tv_usec = 10000;
redisSetTimeout(c, tv); redisSetTimeout(c, tv);
...@@ -1156,6 +1260,9 @@ static void test_blocking_connection_timeouts(struct config config) { ...@@ -1156,6 +1260,9 @@ static void test_blocking_connection_timeouts(struct config config) {
strcmp(c->errstr, "recv timeout") == 0); strcmp(c->errstr, "recv timeout") == 0);
#endif #endif
freeReplyObject(reply); freeReplyObject(reply);
// wait for the DEBUG SLEEP to complete so that Redis server is unblocked for the following tests
millisleep(3000);
} else { } else {
test_skipped(); test_skipped();
} }
...@@ -1226,22 +1333,34 @@ static void test_blocking_io_errors(struct config config) { ...@@ -1226,22 +1333,34 @@ static void test_blocking_io_errors(struct config config) {
static void test_invalid_timeout_errors(struct config config) { static void test_invalid_timeout_errors(struct config config) {
redisContext *c; redisContext *c;
test("Set error when an invalid timeout usec value is given to redisConnectWithTimeout: "); test("Set error when an invalid timeout usec value is used during connect: ");
config.tcp.timeout.tv_sec = 0; config.connect_timeout.tv_sec = 0;
config.tcp.timeout.tv_usec = 10000001; config.connect_timeout.tv_usec = 10000001;
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); if (config.type == CONN_TCP || config.type == CONN_SSL) {
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
} else if(config.type == CONN_UNIX) {
c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
} else {
assert(NULL);
}
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c); redisFree(c);
test("Set error when an invalid timeout sec value is given to redisConnectWithTimeout: "); test("Set error when an invalid timeout sec value is used during connect: ");
config.tcp.timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1; config.connect_timeout.tv_sec = (((LONG_MAX) - 999) / 1000) + 1;
config.tcp.timeout.tv_usec = 0; config.connect_timeout.tv_usec = 0;
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.tcp.timeout); if (config.type == CONN_TCP || config.type == CONN_SSL) {
c = redisConnectWithTimeout(config.tcp.host, config.tcp.port, config.connect_timeout);
} else if(config.type == CONN_UNIX) {
c = redisConnectUnixWithTimeout(config.unix_sock.path, config.connect_timeout);
} else {
assert(NULL);
}
test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0); test_cond(c->err == REDIS_ERR_IO && strcmp(c->errstr, "Invalid timeout specified") == 0);
redisFree(c); redisFree(c);
...@@ -1729,10 +1848,14 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) { ...@@ -1729,10 +1848,14 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {
strcmp(reply->element[2]->str,"Hello!") == 0); strcmp(reply->element[2]->str,"Hello!") == 0);
state->checkpoint++; state->checkpoint++;
/* Unsubscribe to channels, including a channel X which we don't subscribe to */ /* Unsubscribe to channels, including channel X & Z which we don't subscribe to */
redisAsyncCommand(ac,unexpected_cb, redisAsyncCommand(ac,unexpected_cb,
(void*)"unsubscribe should not call unexpected_cb()", (void*)"unsubscribe should not call unexpected_cb()",
"unsubscribe B X A"); "unsubscribe B X A A Z");
/* Unsubscribe to patterns, none which we subscribe to */
redisAsyncCommand(ac,unexpected_cb,
(void*)"punsubscribe should not call unexpected_cb()",
"punsubscribe");
/* Send a regular command after unsubscribing, then disconnect */ /* Send a regular command after unsubscribing, then disconnect */
state->disconnect = 1; state->disconnect = 1;
redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo"); redisAsyncCommand(ac,integer_cb,state,"LPUSH mylist foo");
...@@ -1749,6 +1872,7 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) { ...@@ -1749,6 +1872,7 @@ void subscribe_channel_a_cb(redisAsyncContext *ac, void *r, void *privdata) {
void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) { void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {
redisReply *reply = r; redisReply *reply = r;
TestState *state = privdata; TestState *state = privdata;
(void)ac;
assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY && assert(reply != NULL && reply->type == REDIS_REPLY_ARRAY &&
reply->elements == 3); reply->elements == 3);
...@@ -1767,8 +1891,10 @@ void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) { ...@@ -1767,8 +1891,10 @@ void subscribe_channel_b_cb(redisAsyncContext *ac, void *r, void *privdata) {
/* Test handling of multiple channels /* Test handling of multiple channels
* - subscribe to channel A and B * - subscribe to channel A and B
* - a published message on A triggers an unsubscribe of channel B, X and A * - a published message on A triggers an unsubscribe of channel B, X, A and Z
* where channel X is not subscribed to. * where channel X and Z are not subscribed to.
* - the published message also triggers an unsubscribe to patterns. Since no
* pattern is subscribed to the responded pattern element type is NIL.
* - a command sent after unsubscribe triggers a disconnect */ * - a command sent after unsubscribe triggers a disconnect */
static void test_pubsub_multiple_channels(struct config config) { static void test_pubsub_multiple_channels(struct config config) {
test("Subscribe to multiple channels: "); test("Subscribe to multiple channels: ");
...@@ -1881,6 +2007,250 @@ static void test_monitor(struct config config) { ...@@ -1881,6 +2007,250 @@ static void test_monitor(struct config config) {
} }
#endif /* HIREDIS_TEST_ASYNC */ #endif /* HIREDIS_TEST_ASYNC */
/* tests for async api using polling adapter, requires no extra libraries*/
/* enum for the test cases, the callbacks have different logic based on them */
typedef enum astest_no
{
ASTEST_CONNECT=0,
ASTEST_CONN_TIMEOUT,
ASTEST_PINGPONG,
ASTEST_PINGPONG_TIMEOUT,
ASTEST_ISSUE_931,
ASTEST_ISSUE_931_PING
}astest_no;
/* a static context for the async tests */
struct _astest {
redisAsyncContext *ac;
astest_no testno;
int counter;
int connects;
int connect_status;
int disconnects;
int pongs;
int disconnect_status;
int connected;
int err;
char errstr[256];
};
static struct _astest astest;
/* async callbacks */
static void asCleanup(void* data)
{
struct _astest *t = (struct _astest *)data;
t->ac = NULL;
}
static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata);
static void connectCallback(redisAsyncContext *c, int status) {
struct _astest *t = (struct _astest *)c->data;
assert(t == &astest);
assert(t->connects == 0);
t->err = c->err;
strcpy(t->errstr, c->errstr);
t->connects++;
t->connect_status = status;
t->connected = status == REDIS_OK ? 1 : -1;
if (t->testno == ASTEST_ISSUE_931) {
/* disconnect again */
redisAsyncDisconnect(c);
}
else if (t->testno == ASTEST_ISSUE_931_PING)
{
redisAsyncCommand(c, commandCallback, NULL, "PING");
}
}
static void disconnectCallback(const redisAsyncContext *c, int status) {
assert(c->data == (void*)&astest);
assert(astest.disconnects == 0);
astest.err = c->err;
strcpy(astest.errstr, c->errstr);
astest.disconnects++;
astest.disconnect_status = status;
astest.connected = 0;
}
static void commandCallback(struct redisAsyncContext *ac, void* _reply, void* _privdata)
{
redisReply *reply = (redisReply*)_reply;
struct _astest *t = (struct _astest *)ac->data;
assert(t == &astest);
(void)_privdata;
t->err = ac->err;
strcpy(t->errstr, ac->errstr);
t->counter++;
if (t->testno == ASTEST_PINGPONG ||t->testno == ASTEST_ISSUE_931_PING)
{
assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
t->pongs++;
redisAsyncFree(ac);
}
if (t->testno == ASTEST_PINGPONG_TIMEOUT)
{
/* two ping pongs */
assert(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
t->pongs++;
if (t->counter == 1) {
int status = redisAsyncCommand(ac, commandCallback, NULL, "PING");
assert(status == REDIS_OK);
} else {
redisAsyncFree(ac);
}
}
}
static redisAsyncContext *do_aconnect(struct config config, astest_no testno)
{
redisOptions options = {0};
memset(&astest, 0, sizeof(astest));
astest.testno = testno;
astest.connect_status = astest.disconnect_status = -2;
if (config.type == CONN_TCP) {
options.type = REDIS_CONN_TCP;
options.connect_timeout = &config.connect_timeout;
REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
} else if (config.type == CONN_SSL) {
options.type = REDIS_CONN_TCP;
options.connect_timeout = &config.connect_timeout;
REDIS_OPTIONS_SET_TCP(&options, config.ssl.host, config.ssl.port);
} else if (config.type == CONN_UNIX) {
options.type = REDIS_CONN_UNIX;
options.endpoint.unix_socket = config.unix_sock.path;
} else if (config.type == CONN_FD) {
options.type = REDIS_CONN_USERFD;
/* Create a dummy connection just to get an fd to inherit */
redisContext *dummy_ctx = redisConnectUnix(config.unix_sock.path);
if (dummy_ctx) {
redisFD fd = disconnect(dummy_ctx, 1);
printf("Connecting to inherited fd %d\n", (int)fd);
options.endpoint.fd = fd;
}
}
redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
assert(c);
astest.ac = c;
c->data = &astest;
c->dataCleanup = asCleanup;
redisPollAttach(c);
redisAsyncSetConnectCallbackNC(c, connectCallback);
redisAsyncSetDisconnectCallback(c, disconnectCallback);
return c;
}
static void as_printerr(void) {
printf("Async err %d : %s\n", astest.err, astest.errstr);
}
#define ASASSERT(e) do { \
if (!(e)) \
as_printerr(); \
assert(e); \
} while (0);
static void test_async_polling(struct config config) {
int status;
redisAsyncContext *c;
struct config defaultconfig = config;
test("Async connect: ");
c = do_aconnect(config, ASTEST_CONNECT);
assert(c);
while(astest.connected == 0)
redisPollTick(c, 0.1);
assert(astest.connects == 1);
ASASSERT(astest.connect_status == REDIS_OK);
assert(astest.disconnects == 0);
test_cond(astest.connected == 1);
test("Async free after connect: ");
assert(astest.ac != NULL);
redisAsyncFree(c);
assert(astest.disconnects == 1);
assert(astest.ac == NULL);
test_cond(astest.disconnect_status == REDIS_OK);
if (config.type == CONN_TCP || config.type == CONN_SSL) {
/* timeout can only be simulated with network */
test("Async connect timeout: ");
config.tcp.host = "192.168.254.254"; /* blackhole ip */
config.connect_timeout.tv_usec = 100000;
c = do_aconnect(config, ASTEST_CONN_TIMEOUT);
assert(c);
assert(c->err == 0);
while(astest.connected == 0)
redisPollTick(c, 0.1);
assert(astest.connected == -1);
/*
* freeing should not be done, clearing should have happened.
*redisAsyncFree(c);
*/
assert(astest.ac == NULL);
test_cond(astest.connect_status == REDIS_ERR);
config = defaultconfig;
}
/* Test a ping/pong after connection */
test("Async PING/PONG: ");
c = do_aconnect(config, ASTEST_PINGPONG);
while(astest.connected == 0)
redisPollTick(c, 0.1);
status = redisAsyncCommand(c, commandCallback, NULL, "PING");
assert(status == REDIS_OK);
while(astest.ac)
redisPollTick(c, 0.1);
test_cond(astest.pongs == 1);
/* Test a ping/pong after connection that didn't time out.
* see https://github.com/redis/hiredis/issues/945
*/
if (config.type == CONN_TCP || config.type == CONN_SSL) {
test("Async PING/PONG after connect timeout: ");
config.connect_timeout.tv_usec = 10000; /* 10ms */
c = do_aconnect(config, ASTEST_PINGPONG_TIMEOUT);
while(astest.connected == 0)
redisPollTick(c, 0.1);
/* sleep 0.1 s, allowing old timeout to arrive */
millisleep(10);
status = redisAsyncCommand(c, commandCallback, NULL, "PING");
assert(status == REDIS_OK);
while(astest.ac)
redisPollTick(c, 0.1);
test_cond(astest.pongs == 2);
config = defaultconfig;
}
/* Test disconnect from an on_connect callback
* see https://github.com/redis/hiredis/issues/931
*/
test("Disconnect from onConnected callback (Issue #931): ");
c = do_aconnect(config, ASTEST_ISSUE_931);
while(astest.disconnects == 0)
redisPollTick(c, 0.1);
assert(astest.connected == 0);
assert(astest.connects == 1);
test_cond(astest.disconnects == 1);
/* Test ping/pong from an on_connect callback
* see https://github.com/redis/hiredis/issues/931
*/
test("Ping/Pong from onConnected callback (Issue #931): ");
c = do_aconnect(config, ASTEST_ISSUE_931_PING);
/* connect callback issues ping, reponse callback destroys context */
while(astest.ac)
redisPollTick(c, 0.1);
assert(astest.connected == 0);
assert(astest.connects == 1);
assert(astest.disconnects == 1);
test_cond(astest.pongs == 1);
}
/* End of Async polling_adapter driven tests */
int main(int argc, char **argv) { int main(int argc, char **argv) {
struct config cfg = { struct config cfg = {
.tcp = { .tcp = {
...@@ -1963,6 +2333,7 @@ int main(int argc, char **argv) { ...@@ -1963,6 +2333,7 @@ int main(int argc, char **argv) {
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
test_invalid_timeout_errors(cfg); test_invalid_timeout_errors(cfg);
test_append_formatted_commands(cfg); test_append_formatted_commands(cfg);
test_tcp_options(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path); printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
...@@ -1972,6 +2343,7 @@ int main(int argc, char **argv) { ...@@ -1972,6 +2343,7 @@ int main(int argc, char **argv) {
test_blocking_connection(cfg); test_blocking_connection(cfg);
test_blocking_connection_timeouts(cfg); test_blocking_connection_timeouts(cfg);
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
test_invalid_timeout_errors(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
} else { } else {
test_skipped(); test_skipped();
...@@ -2000,6 +2372,7 @@ int main(int argc, char **argv) { ...@@ -2000,6 +2372,7 @@ int main(int argc, char **argv) {
#endif #endif
#ifdef HIREDIS_TEST_ASYNC #ifdef HIREDIS_TEST_ASYNC
cfg.type = CONN_TCP;
printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port); printf("\nTesting asynchronous API against TCP connection (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
cfg.type = CONN_TCP; cfg.type = CONN_TCP;
...@@ -2017,6 +2390,15 @@ int main(int argc, char **argv) { ...@@ -2017,6 +2390,15 @@ int main(int argc, char **argv) {
} }
#endif /* HIREDIS_TEST_ASYNC */ #endif /* HIREDIS_TEST_ASYNC */
cfg.type = CONN_TCP;
printf("\nTesting asynchronous API using polling_adapter TCP (%s:%d):\n", cfg.tcp.host, cfg.tcp.port);
test_async_polling(cfg);
if (test_unix_socket) {
cfg.type = CONN_UNIX;
printf("\nTesting asynchronous API using polling_adapter UNIX (%s):\n", cfg.unix_sock.path);
test_async_polling(cfg);
}
if (test_inherit_fd) { if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path); printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
if (test_unix_socket) { if (test_unix_socket) {
......
...@@ -4,9 +4,17 @@ REDIS_SERVER=${REDIS_SERVER:-redis-server} ...@@ -4,9 +4,17 @@ REDIS_SERVER=${REDIS_SERVER:-redis-server}
REDIS_PORT=${REDIS_PORT:-56379} REDIS_PORT=${REDIS_PORT:-56379}
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443} REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
TEST_SSL=${TEST_SSL:-0} TEST_SSL=${TEST_SSL:-0}
SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0} SKIPS_AS_FAILS=${SKIPS_AS_FAILS:-0}
ENABLE_DEBUG_CMD=
SSL_TEST_ARGS= SSL_TEST_ARGS=
SKIPS_ARG= SKIPS_ARG=${SKIPS_ARG:-}
REDIS_DOCKER=${REDIS_DOCKER:-}
# We need to enable the DEBUG command for redis-server >= 7.0.0
REDIS_MAJOR_VERSION="$(redis-server --version|awk -F'[^0-9]+' '{ print $2 }')"
if [ "$REDIS_MAJOR_VERSION" -gt "6" ]; then
ENABLE_DEBUG_CMD="enable-debug-command local"
fi
tmpdir=$(mktemp -d) tmpdir=$(mktemp -d)
PID_FILE=${tmpdir}/hiredis-test-redis.pid PID_FILE=${tmpdir}/hiredis-test-redis.pid
...@@ -43,20 +51,34 @@ if [ "$TEST_SSL" = "1" ]; then ...@@ -43,20 +51,34 @@ if [ "$TEST_SSL" = "1" ]; then
fi fi
cleanup() { cleanup() {
if [ -n "${REDIS_DOCKER}" ] ; then
docker kill redis-test-server
else
set +e set +e
kill $(cat ${PID_FILE}) kill $(cat ${PID_FILE})
fi
rm -rf ${tmpdir} rm -rf ${tmpdir}
} }
trap cleanup INT TERM EXIT trap cleanup INT TERM EXIT
# base config
cat > ${tmpdir}/redis.conf <<EOF cat > ${tmpdir}/redis.conf <<EOF
daemonize yes
pidfile ${PID_FILE} pidfile ${PID_FILE}
port ${REDIS_PORT} port ${REDIS_PORT}
bind 127.0.0.1
unixsocket ${SOCK_FILE} unixsocket ${SOCK_FILE}
unixsocketperm 777
EOF EOF
# if not running in docker add these:
if [ ! -n "${REDIS_DOCKER}" ]; then
cat >> ${tmpdir}/redis.conf <<EOF
daemonize yes
${ENABLE_DEBUG_CMD}
bind 127.0.0.1
EOF
fi
# if doing ssl, add these
if [ "$TEST_SSL" = "1" ]; then if [ "$TEST_SSL" = "1" ]; then
cat >> ${tmpdir}/redis.conf <<EOF cat >> ${tmpdir}/redis.conf <<EOF
tls-port ${REDIS_SSL_PORT} tls-port ${REDIS_SSL_PORT}
...@@ -66,13 +88,25 @@ tls-key-file ${SSL_KEY} ...@@ -66,13 +88,25 @@ tls-key-file ${SSL_KEY}
EOF EOF
fi fi
echo ${tmpdir}
cat ${tmpdir}/redis.conf cat ${tmpdir}/redis.conf
${REDIS_SERVER} ${tmpdir}/redis.conf if [ -n "${REDIS_DOCKER}" ] ; then
chmod a+wx ${tmpdir}
chmod a+r ${tmpdir}/*
docker run -d --rm --name redis-test-server \
-p ${REDIS_PORT}:${REDIS_PORT} \
-p ${REDIS_SSL_PORT}:${REDIS_SSL_PORT} \
-v ${tmpdir}:${tmpdir} \
${REDIS_DOCKER} \
redis-server ${tmpdir}/redis.conf
else
${REDIS_SERVER} ${tmpdir}/redis.conf
fi
# Wait until we detect the unix socket # Wait until we detect the unix socket
echo waiting for server
while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done
# Treat skips as failures if directed # Treat skips as failures if directed
[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="--skips-as-fails" [ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="${SKIPS_ARG} --skips-as-fails"
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG} ${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include <assert.h> #include <assert.h>
#include <string.h> #include <string.h>
#include <math.h> #include <math.h>
#include <stdint.h>
#include <limits.h> #include <limits.h>
#include "lua.h" #include "lua.h"
#include "lauxlib.h" #include "lauxlib.h"
...@@ -141,13 +142,13 @@ typedef struct { ...@@ -141,13 +142,13 @@ typedef struct {
typedef struct { typedef struct {
json_token_type_t type; json_token_type_t type;
int index; size_t index;
union { union {
const char *string; const char *string;
double number; double number;
int boolean; int boolean;
} value; } value;
int string_len; size_t string_len;
} json_token_t; } json_token_t;
static const char *char2escape[256] = { static const char *char2escape[256] = {
...@@ -473,6 +474,8 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex) ...@@ -473,6 +474,8 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
* This buffer is reused constantly for small strings * This buffer is reused constantly for small strings
* If there are any excess pages, they won't be hit anyway. * If there are any excess pages, they won't be hit anyway.
* This gains ~5% speedup. */ * This gains ~5% speedup. */
if (len > SIZE_MAX / 6 - 3)
abort(); /* Overflow check */
strbuf_ensure_empty_length(json, len * 6 + 2); strbuf_ensure_empty_length(json, len * 6 + 2);
strbuf_append_char_unsafe(json, '\"'); strbuf_append_char_unsafe(json, '\"');
...@@ -706,7 +709,7 @@ static int json_encode(lua_State *l) ...@@ -706,7 +709,7 @@ static int json_encode(lua_State *l)
strbuf_t local_encode_buf; strbuf_t local_encode_buf;
strbuf_t *encode_buf; strbuf_t *encode_buf;
char *json; char *json;
int len; size_t len;
luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument");
......
...@@ -117,7 +117,9 @@ mp_buf *mp_buf_new(lua_State *L) { ...@@ -117,7 +117,9 @@ mp_buf *mp_buf_new(lua_State *L) {
void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) { void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
if (buf->free < len) { if (buf->free < len) {
size_t newsize = (buf->len+len)*2; size_t newsize = buf->len+len;
if (newsize < buf->len || newsize >= SIZE_MAX/2) abort();
newsize *= 2;
buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize); buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize);
buf->free = newsize - buf->len; buf->free = newsize - buf->len;
...@@ -173,7 +175,7 @@ void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) { ...@@ -173,7 +175,7 @@ void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) {
void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) { void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) {
unsigned char hdr[5]; unsigned char hdr[5];
int hdrlen; size_t hdrlen;
if (len < 32) { if (len < 32) {
hdr[0] = 0xa0 | (len&0xff); /* fix raw */ hdr[0] = 0xa0 | (len&0xff); /* fix raw */
...@@ -220,7 +222,7 @@ void mp_encode_double(lua_State *L, mp_buf *buf, double d) { ...@@ -220,7 +222,7 @@ void mp_encode_double(lua_State *L, mp_buf *buf, double d) {
void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) { void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
unsigned char b[9]; unsigned char b[9];
int enclen; size_t enclen;
if (n >= 0) { if (n >= 0) {
if (n <= 127) { if (n <= 127) {
...@@ -290,9 +292,9 @@ void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) { ...@@ -290,9 +292,9 @@ void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) {
mp_buf_append(L,buf,b,enclen); mp_buf_append(L,buf,b,enclen);
} }
void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) { void mp_encode_array(lua_State *L, mp_buf *buf, uint64_t n) {
unsigned char b[5]; unsigned char b[5];
int enclen; size_t enclen;
if (n <= 15) { if (n <= 15) {
b[0] = 0x90 | (n & 0xf); /* fix array */ b[0] = 0x90 | (n & 0xf); /* fix array */
...@@ -313,7 +315,7 @@ void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) { ...@@ -313,7 +315,7 @@ void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) {
mp_buf_append(L,buf,b,enclen); mp_buf_append(L,buf,b,enclen);
} }
void mp_encode_map(lua_State *L, mp_buf *buf, int64_t n) { void mp_encode_map(lua_State *L, mp_buf *buf, uint64_t n) {
unsigned char b[5]; unsigned char b[5];
int enclen; int enclen;
...@@ -791,7 +793,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { ...@@ -791,7 +793,7 @@ void mp_decode_to_lua_type(lua_State *L, mp_cur *c) {
} }
} }
int mp_unpack_full(lua_State *L, int limit, int offset) { int mp_unpack_full(lua_State *L, lua_Integer limit, lua_Integer offset) {
size_t len; size_t len;
const char *s; const char *s;
mp_cur c; mp_cur c;
...@@ -803,10 +805,10 @@ int mp_unpack_full(lua_State *L, int limit, int offset) { ...@@ -803,10 +805,10 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
if (offset < 0 || limit < 0) /* requesting negative off or lim is invalid */ if (offset < 0 || limit < 0) /* requesting negative off or lim is invalid */
return luaL_error(L, return luaL_error(L,
"Invalid request to unpack with offset of %d and limit of %d.", "Invalid request to unpack with offset of %d and limit of %d.",
offset, len); (int) offset, (int) len);
else if (offset > len) else if (offset > len)
return luaL_error(L, return luaL_error(L,
"Start offset %d greater than input length %d.", offset, len); "Start offset %d greater than input length %d.", (int) offset, (int) len);
if (decode_all) limit = INT_MAX; if (decode_all) limit = INT_MAX;
...@@ -828,12 +830,13 @@ int mp_unpack_full(lua_State *L, int limit, int offset) { ...@@ -828,12 +830,13 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
/* c->left is the remaining size of the input buffer. /* c->left is the remaining size of the input buffer.
* subtract the entire buffer size from the unprocessed size * subtract the entire buffer size from the unprocessed size
* to get our next start offset */ * to get our next start offset */
int offset = len - c.left; size_t new_offset = len - c.left;
if (new_offset > LONG_MAX) abort();
luaL_checkstack(L, 1, "in function mp_unpack_full"); luaL_checkstack(L, 1, "in function mp_unpack_full");
/* Return offset -1 when we have have processed the entire buffer. */ /* Return offset -1 when we have have processed the entire buffer. */
lua_pushinteger(L, c.left == 0 ? -1 : offset); lua_pushinteger(L, c.left == 0 ? -1 : (lua_Integer) new_offset);
/* Results are returned with the arg elements still /* Results are returned with the arg elements still
* in place. Lua takes care of only returning * in place. Lua takes care of only returning
* elements above the args for us. * elements above the args for us.
...@@ -852,15 +855,15 @@ int mp_unpack(lua_State *L) { ...@@ -852,15 +855,15 @@ int mp_unpack(lua_State *L) {
} }
int mp_unpack_one(lua_State *L) { int mp_unpack_one(lua_State *L) {
int offset = luaL_optinteger(L, 2, 0); lua_Integer offset = luaL_optinteger(L, 2, 0);
/* Variable pop because offset may not exist */ /* Variable pop because offset may not exist */
lua_pop(L, lua_gettop(L)-1); lua_pop(L, lua_gettop(L)-1);
return mp_unpack_full(L, 1, offset); return mp_unpack_full(L, 1, offset);
} }
int mp_unpack_limit(lua_State *L) { int mp_unpack_limit(lua_State *L) {
int limit = luaL_checkinteger(L, 2); lua_Integer limit = luaL_checkinteger(L, 2);
int offset = luaL_optinteger(L, 3, 0); lua_Integer offset = luaL_optinteger(L, 3, 0);
/* Variable pop because offset may not exist */ /* Variable pop because offset may not exist */
lua_pop(L, lua_gettop(L)-1); lua_pop(L, lua_gettop(L)-1);
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdarg.h> #include <stdarg.h>
#include <string.h> #include <string.h>
#include <stdint.h>
#include "strbuf.h" #include "strbuf.h"
...@@ -38,22 +39,22 @@ static void die(const char *fmt, ...) ...@@ -38,22 +39,22 @@ static void die(const char *fmt, ...)
va_end(arg); va_end(arg);
fprintf(stderr, "\n"); fprintf(stderr, "\n");
exit(-1); abort();
} }
void strbuf_init(strbuf_t *s, int len) void strbuf_init(strbuf_t *s, size_t len)
{ {
int size; size_t size;
if (len <= 0) if (!len)
size = STRBUF_DEFAULT_SIZE; size = STRBUF_DEFAULT_SIZE;
else else
size = len + 1; /* \0 terminator */ size = len + 1;
if (size < len)
die("Overflow, len: %zu", len);
s->buf = NULL; s->buf = NULL;
s->size = size; s->size = size;
s->length = 0; s->length = 0;
s->increment = STRBUF_DEFAULT_INCREMENT;
s->dynamic = 0; s->dynamic = 0;
s->reallocs = 0; s->reallocs = 0;
s->debug = 0; s->debug = 0;
...@@ -65,7 +66,7 @@ void strbuf_init(strbuf_t *s, int len) ...@@ -65,7 +66,7 @@ void strbuf_init(strbuf_t *s, int len)
strbuf_ensure_null(s); strbuf_ensure_null(s);
} }
strbuf_t *strbuf_new(int len) strbuf_t *strbuf_new(size_t len)
{ {
strbuf_t *s; strbuf_t *s;
...@@ -81,20 +82,10 @@ strbuf_t *strbuf_new(int len) ...@@ -81,20 +82,10 @@ strbuf_t *strbuf_new(int len)
return s; return s;
} }
void strbuf_set_increment(strbuf_t *s, int increment)
{
/* Increment > 0: Linear buffer growth rate
* Increment < -1: Exponential buffer growth rate */
if (increment == 0 || increment == -1)
die("BUG: Invalid string increment");
s->increment = increment;
}
static inline void debug_stats(strbuf_t *s) static inline void debug_stats(strbuf_t *s)
{ {
if (s->debug) { if (s->debug) {
fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n", fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %zd, size: %zd\n",
(long)s, s->reallocs, s->length, s->size); (long)s, s->reallocs, s->length, s->size);
} }
} }
...@@ -113,7 +104,7 @@ void strbuf_free(strbuf_t *s) ...@@ -113,7 +104,7 @@ void strbuf_free(strbuf_t *s)
free(s); free(s);
} }
char *strbuf_free_to_string(strbuf_t *s, int *len) char *strbuf_free_to_string(strbuf_t *s, size_t *len)
{ {
char *buf; char *buf;
...@@ -131,57 +122,62 @@ char *strbuf_free_to_string(strbuf_t *s, int *len) ...@@ -131,57 +122,62 @@ char *strbuf_free_to_string(strbuf_t *s, int *len)
return buf; return buf;
} }
static int calculate_new_size(strbuf_t *s, int len) static size_t calculate_new_size(strbuf_t *s, size_t len)
{ {
int reqsize, newsize; size_t reqsize, newsize;
if (len <= 0) if (len <= 0)
die("BUG: Invalid strbuf length requested"); die("BUG: Invalid strbuf length requested");
/* Ensure there is room for optional NULL termination */ /* Ensure there is room for optional NULL termination */
reqsize = len + 1; reqsize = len + 1;
if (reqsize < len)
die("Overflow, len: %zu", len);
/* If the user has requested to shrink the buffer, do it exactly */ /* If the user has requested to shrink the buffer, do it exactly */
if (s->size > reqsize) if (s->size > reqsize)
return reqsize; return reqsize;
newsize = s->size; newsize = s->size;
if (s->increment < 0) { if (reqsize >= SIZE_MAX / 2) {
newsize = reqsize;
} else {
/* Exponential sizing */ /* Exponential sizing */
while (newsize < reqsize) while (newsize < reqsize)
newsize *= -s->increment; newsize *= 2;
} else {
/* Linear sizing */
newsize = ((newsize + s->increment - 1) / s->increment) * s->increment;
} }
if (newsize < reqsize)
die("BUG: strbuf length would overflow, len: %zu", len);
return newsize; return newsize;
} }
/* Ensure strbuf can handle a string length bytes long (ignoring NULL /* Ensure strbuf can handle a string length bytes long (ignoring NULL
* optional termination). */ * optional termination). */
void strbuf_resize(strbuf_t *s, int len) void strbuf_resize(strbuf_t *s, size_t len)
{ {
int newsize; size_t newsize;
newsize = calculate_new_size(s, len); newsize = calculate_new_size(s, len);
if (s->debug > 1) { if (s->debug > 1) {
fprintf(stderr, "strbuf(%lx) resize: %d => %d\n", fprintf(stderr, "strbuf(%lx) resize: %zd => %zd\n",
(long)s, s->size, newsize); (long)s, s->size, newsize);
} }
s->size = newsize; s->size = newsize;
s->buf = realloc(s->buf, s->size); s->buf = realloc(s->buf, s->size);
if (!s->buf) if (!s->buf)
die("Out of memory"); die("Out of memory, len: %zu", len);
s->reallocs++; s->reallocs++;
} }
void strbuf_append_string(strbuf_t *s, const char *str) void strbuf_append_string(strbuf_t *s, const char *str)
{ {
int space, i; int i;
size_t space;
space = strbuf_empty_length(s); space = strbuf_empty_length(s);
...@@ -197,55 +193,6 @@ void strbuf_append_string(strbuf_t *s, const char *str) ...@@ -197,55 +193,6 @@ void strbuf_append_string(strbuf_t *s, const char *str)
} }
} }
/* strbuf_append_fmt() should only be used when an upper bound
* is known for the output string. */
void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...)
{
va_list arg;
int fmt_len;
strbuf_ensure_empty_length(s, len);
va_start(arg, fmt);
fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg);
va_end(arg);
if (fmt_len < 0)
die("BUG: Unable to convert number"); /* This should never happen.. */
s->length += fmt_len;
}
/* strbuf_append_fmt_retry() can be used when the there is no known
* upper bound for the output string. */
void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...)
{
va_list arg;
int fmt_len, try;
int empty_len;
/* If the first attempt to append fails, resize the buffer appropriately
* and try again */
for (try = 0; ; try++) {
va_start(arg, fmt);
/* Append the new formatted string */
/* fmt_len is the length of the string required, excluding the
* trailing NULL */
empty_len = strbuf_empty_length(s);
/* Add 1 since there is also space to store the terminating NULL. */
fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg);
va_end(arg);
if (fmt_len <= empty_len)
break; /* SUCCESS */
if (try > 0)
die("BUG: length of formatted string changed");
strbuf_resize(s, s->length + fmt_len);
}
s->length += fmt_len;
}
/* vi:ai et sw=4 ts=4: /* vi:ai et sw=4 ts=4:
*/ */
...@@ -27,15 +27,13 @@ ...@@ -27,15 +27,13 @@
/* Size: Total bytes allocated to *buf /* Size: Total bytes allocated to *buf
* Length: String length, excluding optional NULL terminator. * Length: String length, excluding optional NULL terminator.
* Increment: Allocation increments when resizing the string buffer.
* Dynamic: True if created via strbuf_new() * Dynamic: True if created via strbuf_new()
*/ */
typedef struct { typedef struct {
char *buf; char *buf;
int size; size_t size;
int length; size_t length;
int increment;
int dynamic; int dynamic;
int reallocs; int reallocs;
int debug; int debug;
...@@ -44,32 +42,26 @@ typedef struct { ...@@ -44,32 +42,26 @@ typedef struct {
#ifndef STRBUF_DEFAULT_SIZE #ifndef STRBUF_DEFAULT_SIZE
#define STRBUF_DEFAULT_SIZE 1023 #define STRBUF_DEFAULT_SIZE 1023
#endif #endif
#ifndef STRBUF_DEFAULT_INCREMENT
#define STRBUF_DEFAULT_INCREMENT -2
#endif
/* Initialise */ /* Initialise */
extern strbuf_t *strbuf_new(int len); extern strbuf_t *strbuf_new(size_t len);
extern void strbuf_init(strbuf_t *s, int len); extern void strbuf_init(strbuf_t *s, size_t len);
extern void strbuf_set_increment(strbuf_t *s, int increment);
/* Release */ /* Release */
extern void strbuf_free(strbuf_t *s); extern void strbuf_free(strbuf_t *s);
extern char *strbuf_free_to_string(strbuf_t *s, int *len); extern char *strbuf_free_to_string(strbuf_t *s, size_t *len);
/* Management */ /* Management */
extern void strbuf_resize(strbuf_t *s, int len); extern void strbuf_resize(strbuf_t *s, size_t len);
static int strbuf_empty_length(strbuf_t *s); static size_t strbuf_empty_length(strbuf_t *s);
static int strbuf_length(strbuf_t *s); static size_t strbuf_length(strbuf_t *s);
static char *strbuf_string(strbuf_t *s, int *len); static char *strbuf_string(strbuf_t *s, size_t *len);
static void strbuf_ensure_empty_length(strbuf_t *s, int len); static void strbuf_ensure_empty_length(strbuf_t *s, size_t len);
static char *strbuf_empty_ptr(strbuf_t *s); static char *strbuf_empty_ptr(strbuf_t *s);
static void strbuf_extend_length(strbuf_t *s, int len); static void strbuf_extend_length(strbuf_t *s, size_t len);
/* Update */ /* Update */
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...); static void strbuf_append_mem(strbuf_t *s, const char *c, size_t len);
extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...);
static void strbuf_append_mem(strbuf_t *s, const char *c, int len);
extern void strbuf_append_string(strbuf_t *s, const char *str); extern void strbuf_append_string(strbuf_t *s, const char *str);
static void strbuf_append_char(strbuf_t *s, const char c); static void strbuf_append_char(strbuf_t *s, const char c);
static void strbuf_ensure_null(strbuf_t *s); static void strbuf_ensure_null(strbuf_t *s);
...@@ -87,12 +79,12 @@ static inline int strbuf_allocated(strbuf_t *s) ...@@ -87,12 +79,12 @@ static inline int strbuf_allocated(strbuf_t *s)
/* Return bytes remaining in the string buffer /* Return bytes remaining in the string buffer
* Ensure there is space for a NULL terminator. */ * Ensure there is space for a NULL terminator. */
static inline int strbuf_empty_length(strbuf_t *s) static inline size_t strbuf_empty_length(strbuf_t *s)
{ {
return s->size - s->length - 1; return s->size - s->length - 1;
} }
static inline void strbuf_ensure_empty_length(strbuf_t *s, int len) static inline void strbuf_ensure_empty_length(strbuf_t *s, size_t len)
{ {
if (len > strbuf_empty_length(s)) if (len > strbuf_empty_length(s))
strbuf_resize(s, s->length + len); strbuf_resize(s, s->length + len);
...@@ -103,12 +95,12 @@ static inline char *strbuf_empty_ptr(strbuf_t *s) ...@@ -103,12 +95,12 @@ static inline char *strbuf_empty_ptr(strbuf_t *s)
return s->buf + s->length; return s->buf + s->length;
} }
static inline void strbuf_extend_length(strbuf_t *s, int len) static inline void strbuf_extend_length(strbuf_t *s, size_t len)
{ {
s->length += len; s->length += len;
} }
static inline int strbuf_length(strbuf_t *s) static inline size_t strbuf_length(strbuf_t *s)
{ {
return s->length; return s->length;
} }
...@@ -124,14 +116,14 @@ static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c) ...@@ -124,14 +116,14 @@ static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c)
s->buf[s->length++] = c; s->buf[s->length++] = c;
} }
static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len) static inline void strbuf_append_mem(strbuf_t *s, const char *c, size_t len)
{ {
strbuf_ensure_empty_length(s, len); strbuf_ensure_empty_length(s, len);
memcpy(s->buf + s->length, c, len); memcpy(s->buf + s->length, c, len);
s->length += len; s->length += len;
} }
static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len) static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, size_t len)
{ {
memcpy(s->buf + s->length, c, len); memcpy(s->buf + s->length, c, len);
s->length += len; s->length += len;
...@@ -142,7 +134,7 @@ static inline void strbuf_ensure_null(strbuf_t *s) ...@@ -142,7 +134,7 @@ static inline void strbuf_ensure_null(strbuf_t *s)
s->buf[s->length] = 0; s->buf[s->length] = 0;
} }
static inline char *strbuf_string(strbuf_t *s, int *len) static inline char *strbuf_string(strbuf_t *s, size_t *len)
{ {
if (len) if (len)
*len = s->length; *len = s->length;
......
...@@ -346,6 +346,7 @@ pidfile /var/run/redis_6379.pid ...@@ -346,6 +346,7 @@ pidfile /var/run/redis_6379.pid
# verbose (many rarely useful info, but not a mess like the debug level) # verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) # notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged) # warning (only very important / critical messages are logged)
# nothing (nothing is logged)
loglevel notice loglevel notice
# Specify the log file name. Also the empty string can be used to force # Specify the log file name. Also the empty string can be used to force
...@@ -1742,6 +1743,11 @@ aof-timestamp-enabled no ...@@ -1742,6 +1743,11 @@ aof-timestamp-enabled no
# #
# cluster-announce-hostname "" # cluster-announce-hostname ""
# Clusters can configure an optional nodename to be used in addition to the node ID for
# debugging and admin information. This name is broadcasted between nodes, so will be used
# in addition to the node ID when reporting cross node events such as node failures.
# cluster-announce-human-nodename ""
# Clusters can advertise how clients should connect to them using either their IP address, # Clusters can advertise how clients should connect to them using either their IP address,
# a user defined hostname, or by declaring they have no endpoint. Which endpoint is # a user defined hostname, or by declaring they have no endpoint. Which endpoint is
# shown as the preferred endpoint is set by using the cluster-preferred-endpoint-type # shown as the preferred endpoint is set by using the cluster-preferred-endpoint-type
......
...@@ -25,6 +25,7 @@ pidfile /var/run/redis-sentinel.pid ...@@ -25,6 +25,7 @@ pidfile /var/run/redis-sentinel.pid
# verbose (many rarely useful info, but not a mess like the debug level) # verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) # notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged) # warning (only very important / critical messages are logged)
# nothing (nothing is logged)
loglevel notice loglevel notice
# Specify the log file name. Also the empty string can be used to force # Specify the log file name. Also the empty string can be used to force
......
...@@ -23,7 +23,7 @@ ifeq ($(OPTIMIZATION),-O3) ...@@ -23,7 +23,7 @@ ifeq ($(OPTIMIZATION),-O3)
else else
REDIS_CFLAGS+=-flto=auto REDIS_CFLAGS+=-flto=auto
endif endif
REDIS_LDFLAGS+=-flto REDIS_LDFLAGS+=-O3 -flto
endif endif
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv
NODEPS:=clean distclean NODEPS:=clean distclean
...@@ -47,10 +47,10 @@ OPT=$(OPTIMIZATION) ...@@ -47,10 +47,10 @@ OPT=$(OPTIMIZATION)
# NUMBER_SIGN_CHAR is a workaround to support both GNU Make 4.3 and older versions. # NUMBER_SIGN_CHAR is a workaround to support both GNU Make 4.3 and older versions.
NUMBER_SIGN_CHAR := \# NUMBER_SIGN_CHAR := \#
C11_ATOMIC := $(shell sh -c 'echo "$(NUMBER_SIGN_CHAR)include <stdatomic.h>" > foo.c; \ C11_ATOMIC := $(shell sh -c 'echo "$(NUMBER_SIGN_CHAR)include <stdatomic.h>" > foo.c; \
$(CC) -std=c11 -c foo.c -o foo.o > /dev/null 2>&1; \ $(CC) -std=gnu11 -c foo.c -o foo.o > /dev/null 2>&1; \
if [ -f foo.o ]; then echo "yes"; rm foo.o; fi; rm foo.c') if [ -f foo.o ]; then echo "yes"; rm foo.o; fi; rm foo.c')
ifeq ($(C11_ATOMIC),yes) ifeq ($(C11_ATOMIC),yes)
STD+=-std=c11 STD+=-std=gnu11
else else
STD+=-std=c99 STD+=-std=c99
endif endif
......
...@@ -1923,26 +1923,28 @@ void ACLKillPubsubClientsIfNeeded(user *new, user *original) { ...@@ -1923,26 +1923,28 @@ void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
if (c->user == original && getClientType(c) == CLIENT_TYPE_PUBSUB) { if (c->user == original && getClientType(c) == CLIENT_TYPE_PUBSUB) {
/* Check for pattern violations. */ /* Check for pattern violations. */
listRewind(c->pubsub_patterns,&lpi); dictIterator *di = dictGetIterator(c->pubsub_patterns);
while (!kill && ((lpn = listNext(&lpi)) != NULL)) { dictEntry *de;
while (!kill && ((de = dictNext(di)) != NULL)) {
o = lpn->value; o = dictGetKey(de);
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 1); int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 1);
kill = (res == ACL_DENIED_CHANNEL); kill = (res == ACL_DENIED_CHANNEL);
} }
dictReleaseIterator(di);
/* Check for channel violations. */ /* Check for channel violations. */
if (!kill) { if (!kill) {
/* Check for global channels violation. */ /* Check for global channels violation. */
dictIterator *di = dictGetIterator(c->pubsub_channels); di = dictGetIterator(c->pubsub_channels);
dictEntry *de;
while (!kill && ((de = dictNext(di)) != NULL)) { while (!kill && ((de = dictNext(di)) != NULL)) {
o = dictGetKey(de); o = dictGetKey(de);
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0); int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0);
kill = (res == ACL_DENIED_CHANNEL); kill = (res == ACL_DENIED_CHANNEL);
} }
dictReleaseIterator(di); dictReleaseIterator(di);
}
if (!kill) {
/* Check for shard channels violation. */ /* Check for shard channels violation. */
di = dictGetIterator(c->pubsubshard_channels); di = dictGetIterator(c->pubsubshard_channels);
while (!kill && ((de = dictNext(di)) != NULL)) { while (!kill && ((de = dictNext(di)) != NULL)) {
...@@ -1950,7 +1952,6 @@ void ACLKillPubsubClientsIfNeeded(user *new, user *original) { ...@@ -1950,7 +1952,6 @@ void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0); int res = ACLCheckChannelAgainstList(upcoming, o->ptr, sdslen(o->ptr), 0);
kill = (res == ACL_DENIED_CHANNEL); kill = (res == ACL_DENIED_CHANNEL);
} }
dictReleaseIterator(di); dictReleaseIterator(di);
} }
...@@ -2112,10 +2113,6 @@ int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) { ...@@ -2112,10 +2113,6 @@ int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) {
return C_ERR; return C_ERR;
} }
/* Try to apply the user rules in a fake user to see if they
* are actually valid. */
user *fakeuser = ACLCreateUnlinkedUser();
/* Merged selectors before trying to process */ /* Merged selectors before trying to process */
int merged_argc; int merged_argc;
sds *acl_args = ACLMergeSelectorArguments(argv + 2, argc - 2, &merged_argc, argc_err); sds *acl_args = ACLMergeSelectorArguments(argv + 2, argc - 2, &merged_argc, argc_err);
...@@ -2124,6 +2121,10 @@ int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) { ...@@ -2124,6 +2121,10 @@ int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) {
return C_ERR; return C_ERR;
} }
/* Try to apply the user rules in a fake user to see if they
* are actually valid. */
user *fakeuser = ACLCreateUnlinkedUser();
for (int j = 0; j < merged_argc; j++) { for (int j = 0; j < merged_argc; j++) {
if (ACLSetUser(fakeuser,acl_args[j],sdslen(acl_args[j])) == C_ERR) { if (ACLSetUser(fakeuser,acl_args[j],sdslen(acl_args[j])) == C_ERR) {
if (errno != ENOENT) { if (errno != ENOENT) {
......
...@@ -142,7 +142,7 @@ void bioInit(void) { ...@@ -142,7 +142,7 @@ void bioInit(void) {
for (j = 0; j < BIO_WORKER_NUM; j++) { for (j = 0; j < BIO_WORKER_NUM; j++) {
void *arg = (void*)(unsigned long) j; void *arg = (void*)(unsigned long) j;
if (pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) { if (pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) {
serverLog(LL_WARNING,"Fatal: Can't initialize Background Jobs."); serverLog(LL_WARNING, "Fatal: Can't initialize Background Jobs. Error message: %s", strerror(errno));
exit(1); exit(1);
} }
bio_threads[j] = thread; bio_threads[j] = thread;
......
...@@ -325,6 +325,9 @@ void handleClientsBlockedOnKeys(void) { ...@@ -325,6 +325,9 @@ void handleClientsBlockedOnKeys(void) {
* (i.e. not from call(), module context, etc.) */ * (i.e. not from call(), module context, etc.) */
serverAssert(server.also_propagate.numops == 0); serverAssert(server.also_propagate.numops == 0);
/* If a command being unblocked causes another command to get unblocked,
* like a BLMOVE would do, then the new unblocked command will get processed
* right away rather than wait for later. */
while(listLength(server.ready_keys) != 0) { while(listLength(server.ready_keys) != 0) {
list *l; list *l;
...@@ -564,7 +567,10 @@ static void handleClientsBlockedOnKey(readyList *rl) { ...@@ -564,7 +567,10 @@ static void handleClientsBlockedOnKey(readyList *rl) {
listIter li; listIter li;
listRewind(clients,&li); listRewind(clients,&li);
while((ln = listNext(&li))) { /* Avoid processing more than the initial count so that we're not stuck
* in an endless loop in case the reprocessing of the command blocks again. */
long count = listLength(clients);
while ((ln = listNext(&li)) && count--) {
client *receiver = listNodeValue(ln); client *receiver = listNodeValue(ln);
robj *o = lookupKeyReadWithFlags(rl->db, rl->key, LOOKUP_NOEFFECTS); robj *o = lookupKeyReadWithFlags(rl->db, rl->key, LOOKUP_NOEFFECTS);
/* 1. In case new key was added/touched we need to verify it satisfy the /* 1. In case new key was added/touched we need to verify it satisfy the
...@@ -728,3 +734,30 @@ void totalNumberOfBlockingKeys(unsigned long *blocking_keys, unsigned long *blok ...@@ -728,3 +734,30 @@ void totalNumberOfBlockingKeys(unsigned long *blocking_keys, unsigned long *blok
if (bloking_keys_on_nokey) if (bloking_keys_on_nokey)
*bloking_keys_on_nokey = bkeys_on_nokey; *bloking_keys_on_nokey = bkeys_on_nokey;
} }
void blockedBeforeSleep(void) {
/* Handle precise timeouts of blocked clients. */
handleBlockedClientsTimeout();
/* Unblock all the clients blocked for synchronous replication
* in WAIT or WAITAOF. */
if (listLength(server.clients_waiting_acks))
processClientsWaitingReplicas();
/* Try to process blocked clients every once in while.
*
* Example: A module calls RM_SignalKeyAsReady from within a timer callback
* (So we don't visit processCommand() at all).
*
* This may unblock clients, so must be done before processUnblockedClients */
handleClientsBlockedOnKeys();
/* Check if there are clients unblocked by modules that implement
* blocking commands. */
if (moduleCount())
moduleHandleBlockedClients();
/* Try to process pending commands for clients that were just unblocked. */
if (listLength(server.unblocked_clients))
processUnblockedClients();
}
...@@ -352,10 +352,20 @@ void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo ...@@ -352,10 +352,20 @@ void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo
path = strchr(curr, '/'); path = strchr(curr, '/');
if (*curr != '/') { if (*curr != '/') {
host = path ? path - 1 : end; host = path ? path - 1 : end;
if (*curr == '[') {
curr += 1;
if ((port = strchr(curr, ']'))) {
if (*(port+1) == ':') {
connInfo->hostport = atoi(port + 2);
}
host = port - 1;
}
} else {
if ((port = strchr(curr, ':'))) { if ((port = strchr(curr, ':'))) {
connInfo->hostport = atoi(port + 1); connInfo->hostport = atoi(port + 1);
host = port - 1; host = port - 1;
} }
}
sdsfree(connInfo->hostip); sdsfree(connInfo->hostip);
connInfo->hostip = sdsnewlen(curr, host - curr + 1); connInfo->hostip = sdsnewlen(curr, host - curr + 1);
} }
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include "server.h" #include "server.h"
#include "cluster.h" #include "cluster.h"
#include "endianconv.h" #include "endianconv.h"
#include "connection.h"
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
...@@ -66,6 +67,8 @@ void clusterSetMaster(clusterNode *n); ...@@ -66,6 +67,8 @@ void clusterSetMaster(clusterNode *n);
void clusterHandleSlaveFailover(void); void clusterHandleSlaveFailover(void);
void clusterHandleSlaveMigration(int max_slaves); void clusterHandleSlaveMigration(int max_slaves);
int bitmapTestBit(unsigned char *bitmap, int pos); int bitmapTestBit(unsigned char *bitmap, int pos);
void bitmapSetBit(unsigned char *bitmap, int pos);
void bitmapClearBit(unsigned char *bitmap, int pos);
void clusterDoBeforeSleep(int flags); void clusterDoBeforeSleep(int flags);
void clusterSendUpdate(clusterLink *link, clusterNode *node); void clusterSendUpdate(clusterLink *link, clusterNode *node);
void resetManualFailover(void); void resetManualFailover(void);
...@@ -89,8 +92,33 @@ void clusterRemoveNodeFromShard(clusterNode *node); ...@@ -89,8 +92,33 @@ void clusterRemoveNodeFromShard(clusterNode *node);
int auxShardIdSetter(clusterNode *n, void *value, int length); int auxShardIdSetter(clusterNode *n, void *value, int length);
sds auxShardIdGetter(clusterNode *n, sds s); sds auxShardIdGetter(clusterNode *n, sds s);
int auxShardIdPresent(clusterNode *n); int auxShardIdPresent(clusterNode *n);
int auxHumanNodenameSetter(clusterNode *n, void *value, int length);
sds auxHumanNodenameGetter(clusterNode *n, sds s);
int auxHumanNodenamePresent(clusterNode *n);
int auxTcpPortSetter(clusterNode *n, void *value, int length);
sds auxTcpPortGetter(clusterNode *n, sds s);
int auxTcpPortPresent(clusterNode *n);
int auxTlsPortSetter(clusterNode *n, void *value, int length);
sds auxTlsPortGetter(clusterNode *n, sds s);
int auxTlsPortPresent(clusterNode *n);
static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen); static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen);
int getNodeDefaultClientPort(clusterNode *n) {
return server.tls_cluster ? n->tls_port : n->tcp_port;
}
static inline int getNodeDefaultReplicationPort(clusterNode *n) {
return server.tls_replication ? n->tls_port : n->tcp_port;
}
static inline int getNodeClientPort(clusterNode *n, int use_tls) {
return use_tls ? n->tls_port : n->tcp_port;
}
static inline int defaultClientPort(void) {
return server.tls_cluster ? server.tls_port : server.port;
}
/* Links to the next and previous entries for keys in the same slot are stored /* Links to the next and previous entries for keys in the same slot are stored
* in the dict entry metadata. See Slot to Key API below. */ * in the dict entry metadata. See Slot to Key API below. */
#define dictEntryNextInSlot(de) \ #define dictEntryNextInSlot(de) \
...@@ -98,6 +126,10 @@ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen); ...@@ -98,6 +126,10 @@ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen);
#define dictEntryPrevInSlot(de) \ #define dictEntryPrevInSlot(de) \
(((clusterDictEntryMetadata *)dictEntryMetadata(de))->prev) (((clusterDictEntryMetadata *)dictEntryMetadata(de))->prev)
#define isSlotUnclaimed(slot) \
(server.cluster->slots[slot] == NULL || \
bitmapTestBit(server.cluster->owner_not_claiming_slot, slot))
#define RCVBUF_INIT_LEN 1024 #define RCVBUF_INIT_LEN 1024
#define RCVBUF_MAX_PREALLOC (1<<20) /* 1MB */ #define RCVBUF_MAX_PREALLOC (1<<20) /* 1MB */
...@@ -171,8 +203,10 @@ typedef struct { ...@@ -171,8 +203,10 @@ typedef struct {
/* Assign index to each aux field */ /* Assign index to each aux field */
typedef enum { typedef enum {
af_start, af_shard_id,
af_shard_id = af_start, af_human_nodename,
af_tcp_port,
af_tls_port,
af_count, af_count,
} auxFieldIndex; } auxFieldIndex;
...@@ -182,14 +216,17 @@ typedef enum { ...@@ -182,14 +216,17 @@ typedef enum {
* 2. aux name can contain characters that pass the isValidAuxChar check only */ * 2. aux name can contain characters that pass the isValidAuxChar check only */
auxFieldHandler auxFieldHandlers[] = { auxFieldHandler auxFieldHandlers[] = {
{"shard-id", auxShardIdSetter, auxShardIdGetter, auxShardIdPresent}, {"shard-id", auxShardIdSetter, auxShardIdGetter, auxShardIdPresent},
{"nodename", auxHumanNodenameSetter, auxHumanNodenameGetter, auxHumanNodenamePresent},
{"tcp-port", auxTcpPortSetter, auxTcpPortGetter, auxTcpPortPresent},
{"tls-port", auxTlsPortSetter, auxTlsPortGetter, auxTlsPortPresent},
}; };
int isValidAuxChar(int c) { int isValidAuxChar(int c) {
return isalnum(c) || (strchr("!#$%&()*+-.:;<>?@[]^_{|}~", c) != NULL); return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~", c) == NULL);
} }
int isValidAuxString(sds s) { int isValidAuxString(char *s, unsigned int length) {
for (unsigned i = 0; i < sdslen(s); i++) { for (unsigned i = 0; i < length; i++) {
if (!isValidAuxChar(s[i])) return 0; if (!isValidAuxChar(s[i])) return 0;
} }
return 1; return 1;
...@@ -219,6 +256,68 @@ int auxShardIdPresent(clusterNode *n) { ...@@ -219,6 +256,68 @@ int auxShardIdPresent(clusterNode *n) {
return strlen(n->shard_id); return strlen(n->shard_id);
} }
int auxHumanNodenameSetter(clusterNode *n, void *value, int length) {
if (n && !strncmp(value, n->human_nodename, length)) {
return C_OK;
} else if (!n && (length == 0)) {
return C_OK;
}
if (n) {
n->human_nodename = sdscpylen(n->human_nodename, value, length);
} else if (sdslen(n->human_nodename) != 0) {
sdsclear(n->human_nodename);
} else {
return C_ERR;
}
return C_OK;
}
sds auxHumanNodenameGetter(clusterNode *n, sds s) {
return sdscatprintf(s, "%s", n->human_nodename);
}
int auxHumanNodenamePresent(clusterNode *n) {
return sdslen(n->human_nodename);
}
int auxTcpPortSetter(clusterNode *n, void *value, int length) {
if (length > 5 || length < 1) {
return C_ERR;
}
char buf[length + 1];
memcpy(buf, (char*)value, length);
buf[length] = '\0';
n->tcp_port = atoi(buf);
return (n->tcp_port < 0 || n->tcp_port >= 65536) ? C_ERR : C_OK;
}
sds auxTcpPortGetter(clusterNode *n, sds s) {
return sdscatprintf(s, "%d", n->tcp_port);
}
int auxTcpPortPresent(clusterNode *n) {
return n->tcp_port >= 0 && n->tcp_port < 65536;
}
int auxTlsPortSetter(clusterNode *n, void *value, int length) {
if (length > 5 || length < 1) {
return C_ERR;
}
char buf[length + 1];
memcpy(buf, (char*)value, length);
buf[length] = '\0';
n->tls_port = atoi(buf);
return (n->tls_port < 0 || n->tls_port >= 65536) ? C_ERR : C_OK;
}
sds auxTlsPortGetter(clusterNode *n, sds s) {
return sdscatprintf(s, "%d", n->tls_port);
}
int auxTlsPortPresent(clusterNode *n) {
return n->tls_port >= 0 && n->tls_port < 65536;
}
/* clusterLink send queue blocks */ /* clusterLink send queue blocks */
typedef struct { typedef struct {
size_t totlen; /* Total length of this block including the message */ size_t totlen; /* Total length of this block including the message */
...@@ -328,7 +427,7 @@ int clusterLoadConfig(char *filename) { ...@@ -328,7 +427,7 @@ int clusterLoadConfig(char *filename) {
clusterAddNode(n); clusterAddNode(n);
} }
/* Format for the node address and auxiliary argument information: /* Format for the node address and auxiliary argument information:
* ip:port[@cport][,hostname[,aux=val]*] */ * ip:port[@cport][,hostname][,aux=val]*] */
aux_argv = sdssplitlen(argv[1], sdslen(argv[1]), ",", 1, &aux_argc); aux_argv = sdssplitlen(argv[1], sdslen(argv[1]), ",", 1, &aux_argc);
if (aux_argv == NULL) { if (aux_argv == NULL) {
...@@ -348,7 +447,8 @@ int clusterLoadConfig(char *filename) { ...@@ -348,7 +447,8 @@ int clusterLoadConfig(char *filename) {
* the format of "aux=val" where both aux and val can contain * the format of "aux=val" where both aux and val can contain
* characters that pass the isValidAuxChar check only. The order * characters that pass the isValidAuxChar check only. The order
* of the aux fields is insignificant. */ * of the aux fields is insignificant. */
int aux_tcp_port = 0;
int aux_tls_port = 0;
for (int i = 2; i < aux_argc; i++) { for (int i = 2; i < aux_argc; i++) {
int field_argc; int field_argc;
sds *field_argv; sds *field_argv;
...@@ -362,7 +462,7 @@ int clusterLoadConfig(char *filename) { ...@@ -362,7 +462,7 @@ int clusterLoadConfig(char *filename) {
/* Validate that both aux and value contain valid characters only */ /* Validate that both aux and value contain valid characters only */
for (unsigned j = 0; j < 2; j++) { for (unsigned j = 0; j < 2; j++) {
if (!isValidAuxString(field_argv[j])) { if (!isValidAuxString(field_argv[j],sdslen(field_argv[j]))){
/* Invalid aux field format */ /* Invalid aux field format */
sdsfreesplitres(field_argv, field_argc); sdsfreesplitres(field_argv, field_argc);
sdsfreesplitres(argv,argc); sdsfreesplitres(argv,argc);
...@@ -379,6 +479,8 @@ int clusterLoadConfig(char *filename) { ...@@ -379,6 +479,8 @@ int clusterLoadConfig(char *filename) {
continue; continue;
} }
field_found = 1; field_found = 1;
aux_tcp_port |= j == af_tcp_port;
aux_tls_port |= j == af_tls_port;
if (auxFieldHandlers[j].setter(n, field_argv[1], sdslen(field_argv[1])) != C_OK) { if (auxFieldHandlers[j].setter(n, field_argv[1], sdslen(field_argv[1])) != C_OK) {
/* Invalid aux field format */ /* Invalid aux field format */
sdsfreesplitres(field_argv, field_argc); sdsfreesplitres(field_argv, field_argc);
...@@ -396,7 +498,6 @@ int clusterLoadConfig(char *filename) { ...@@ -396,7 +498,6 @@ int clusterLoadConfig(char *filename) {
sdsfreesplitres(field_argv, field_argc); sdsfreesplitres(field_argv, field_argc);
} }
/* Address and port */ /* Address and port */
if ((p = strrchr(aux_argv[0],':')) == NULL) { if ((p = strrchr(aux_argv[0],':')) == NULL) {
sdsfreesplitres(aux_argv, aux_argc); sdsfreesplitres(aux_argv, aux_argc);
...@@ -411,11 +512,23 @@ int clusterLoadConfig(char *filename) { ...@@ -411,11 +512,23 @@ int clusterLoadConfig(char *filename) {
*busp = '\0'; *busp = '\0';
busp++; busp++;
} }
n->port = atoi(port); /* If neither TCP or TLS port is found in aux field, it is considered
* an old version of nodes.conf file.*/
if (!aux_tcp_port && !aux_tls_port) {
if (server.tls_cluster) {
n->tls_port = atoi(port);
} else {
n->tcp_port = atoi(port);
}
} else if (!aux_tcp_port) {
n->tcp_port = atoi(port);
} else if (!aux_tls_port) {
n->tls_port = atoi(port);
}
/* In older versions of nodes.conf the "@busport" part is missing. /* In older versions of nodes.conf the "@busport" part is missing.
* In this case we set it to the default offset of 10000 from the * In this case we set it to the default offset of 10000 from the
* base port. */ * base port. */
n->cport = busp ? atoi(busp) : n->port + CLUSTER_PORT_INCR; n->cport = busp ? atoi(busp) : (getNodeDefaultClientPort(n) + CLUSTER_PORT_INCR);
/* The plaintext port for client in a TLS cluster (n->pport) is not /* The plaintext port for client in a TLS cluster (n->pport) is not
* stored in nodes.conf. It is received later over the bus protocol. */ * stored in nodes.conf. It is received later over the bus protocol. */
...@@ -601,7 +714,7 @@ int clusterSaveConfig(int do_fsync) { ...@@ -601,7 +714,7 @@ int clusterSaveConfig(int do_fsync) {
/* Get the nodes description and concatenate our "vars" directive to /* Get the nodes description and concatenate our "vars" directive to
* save currentEpoch and lastVoteEpoch. */ * save currentEpoch and lastVoteEpoch. */
ci = clusterGenNodesDescription(CLUSTER_NODE_HANDSHAKE, 0); ci = clusterGenNodesDescription(NULL, CLUSTER_NODE_HANDSHAKE, 0);
ci = sdscatprintf(ci,"vars currentEpoch %llu lastVoteEpoch %llu\n", ci = sdscatprintf(ci,"vars currentEpoch %llu lastVoteEpoch %llu\n",
(unsigned long long) server.cluster->currentEpoch, (unsigned long long) server.cluster->currentEpoch,
(unsigned long long) server.cluster->lastVoteEpoch); (unsigned long long) server.cluster->lastVoteEpoch);
...@@ -720,23 +833,20 @@ int clusterLockConfig(char *filename) { ...@@ -720,23 +833,20 @@ int clusterLockConfig(char *filename) {
} }
/* Derives our ports to be announced in the cluster bus. */ /* Derives our ports to be announced in the cluster bus. */
void deriveAnnouncedPorts(int *announced_port, int *announced_pport, void deriveAnnouncedPorts(int *announced_tcp_port, int *announced_tls_port,
int *announced_cport) { int *announced_cport) {
int port = server.tls_cluster ? server.tls_port : server.port;
/* Default announced ports. */
*announced_port = port;
*announced_pport = server.tls_cluster ? server.port : 0;
*announced_cport = server.cluster_port ? server.cluster_port : port + CLUSTER_PORT_INCR;
/* Config overriding announced ports. */ /* Config overriding announced ports. */
if (server.tls_cluster && server.cluster_announce_tls_port) { *announced_tcp_port = server.cluster_announce_port ?
*announced_port = server.cluster_announce_tls_port; server.cluster_announce_port : server.port;
*announced_pport = server.cluster_announce_port; *announced_tls_port = server.cluster_announce_tls_port ?
} else if (server.cluster_announce_port) { server.cluster_announce_tls_port : server.tls_port;
*announced_port = server.cluster_announce_port; /* Derive cluster bus port. */
}
if (server.cluster_announce_bus_port) { if (server.cluster_announce_bus_port) {
*announced_cport = server.cluster_announce_bus_port; *announced_cport = server.cluster_announce_bus_port;
} else if (server.cluster_port) {
*announced_cport = server.cluster_port;
} else {
*announced_cport = defaultClientPort() + CLUSTER_PORT_INCR;
} }
} }
...@@ -763,7 +873,7 @@ void clusterUpdateMyselfFlags(void) { ...@@ -763,7 +873,7 @@ void clusterUpdateMyselfFlags(void) {
* The option can be set at runtime via CONFIG SET. */ * The option can be set at runtime via CONFIG SET. */
void clusterUpdateMyselfAnnouncedPorts(void) { void clusterUpdateMyselfAnnouncedPorts(void) {
if (!myself) return; if (!myself) return;
deriveAnnouncedPorts(&myself->port,&myself->pport,&myself->cport); deriveAnnouncedPorts(&myself->tcp_port,&myself->tls_port,&myself->cport);
} }
/* We want to take myself->ip in sync with the cluster-announce-ip option. /* We want to take myself->ip in sync with the cluster-announce-ip option.
...@@ -811,6 +921,22 @@ static void updateAnnouncedHostname(clusterNode *node, char *new) { ...@@ -811,6 +921,22 @@ static void updateAnnouncedHostname(clusterNode *node, char *new) {
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
} }
static void updateAnnouncedHumanNodename(clusterNode *node, char *new) {
if (new && !strcmp(new, node->human_nodename)) {
return;
} else if (!new && (sdslen(node->human_nodename) == 0)) {
return;
}
if (new) {
node->human_nodename = sdscpy(node->human_nodename, new);
} else if (sdslen(node->human_nodename) != 0) {
sdsclear(node->human_nodename);
}
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
}
static void updateShardId(clusterNode *node, const char *shard_id) { static void updateShardId(clusterNode *node, const char *shard_id) {
if (memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) { if (memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
clusterRemoveNodeFromShard(node); clusterRemoveNodeFromShard(node);
...@@ -836,6 +962,11 @@ void clusterUpdateMyselfHostname(void) { ...@@ -836,6 +962,11 @@ void clusterUpdateMyselfHostname(void) {
updateAnnouncedHostname(myself, server.cluster_announce_hostname); updateAnnouncedHostname(myself, server.cluster_announce_hostname);
} }
void clusterUpdateMyselfHumanNodename(void) {
if (!myself) return;
updateAnnouncedHumanNodename(myself, server.cluster_announce_human_nodename);
}
void clusterInit(void) { void clusterInit(void) {
int saveconf = 0; int saveconf = 0;
...@@ -867,6 +998,8 @@ void clusterInit(void) { ...@@ -867,6 +998,8 @@ void clusterInit(void) {
memset(server.cluster->slots,0, sizeof(server.cluster->slots)); memset(server.cluster->slots,0, sizeof(server.cluster->slots));
clusterCloseAllSlots(); clusterCloseAllSlots();
memset(server.cluster->owner_not_claiming_slot, 0, sizeof(server.cluster->owner_not_claiming_slot));
/* Lock the cluster config file to make sure every node uses /* Lock the cluster config file to make sure every node uses
* its own nodes.conf. */ * its own nodes.conf. */
server.cluster_config_file_lock_fd = -1; server.cluster_config_file_lock_fd = -1;
...@@ -890,7 +1023,7 @@ void clusterInit(void) { ...@@ -890,7 +1023,7 @@ void clusterInit(void) {
/* Port sanity check II /* Port sanity check II
* The other handshake port check is triggered too late to stop * The other handshake port check is triggered too late to stop
* us from trying to use a too-high cluster port number. */ * us from trying to use a too-high cluster port number. */
int port = server.tls_cluster ? server.tls_port : server.port; int port = defaultClientPort();
if (!server.cluster_port && port > (65535-CLUSTER_PORT_INCR)) { if (!server.cluster_port && port > (65535-CLUSTER_PORT_INCR)) {
serverLog(LL_WARNING, "Redis port number too high. " serverLog(LL_WARNING, "Redis port number too high. "
"Cluster communication port is 10,000 port " "Cluster communication port is 10,000 port "
...@@ -911,7 +1044,7 @@ void clusterInit(void) { ...@@ -911,7 +1044,7 @@ void clusterInit(void) {
/* Set myself->port/cport/pport to my listening ports, we'll just need to /* Set myself->port/cport/pport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */ * discover the IP address via MEET messages. */
deriveAnnouncedPorts(&myself->port, &myself->pport, &myself->cport); deriveAnnouncedPorts(&myself->tcp_port, &myself->tls_port, &myself->cport);
server.cluster->mf_end = 0; server.cluster->mf_end = 0;
server.cluster->mf_slave = NULL; server.cluster->mf_slave = NULL;
...@@ -919,6 +1052,7 @@ void clusterInit(void) { ...@@ -919,6 +1052,7 @@ void clusterInit(void) {
clusterUpdateMyselfFlags(); clusterUpdateMyselfFlags();
clusterUpdateMyselfIp(); clusterUpdateMyselfIp();
clusterUpdateMyselfHostname(); clusterUpdateMyselfHostname();
clusterUpdateMyselfHumanNodename();
} }
void clusterInitListeners(void) { void clusterInitListeners(void) {
...@@ -927,7 +1061,7 @@ void clusterInitListeners(void) { ...@@ -927,7 +1061,7 @@ void clusterInitListeners(void) {
exit(1); exit(1);
} }
int port = server.tls_cluster ? server.tls_port : server.port; int port = defaultClientPort();
connListener *listener = &server.clistener; connListener *listener = &server.clistener;
listener->count = 0; listener->count = 0;
listener->bindaddr = server.bindaddr; listener->bindaddr = server.bindaddr;
...@@ -1256,9 +1390,10 @@ clusterNode *createClusterNode(char *nodename, int flags) { ...@@ -1256,9 +1390,10 @@ clusterNode *createClusterNode(char *nodename, int flags) {
node->inbound_link = NULL; node->inbound_link = NULL;
memset(node->ip,0,sizeof(node->ip)); memset(node->ip,0,sizeof(node->ip));
node->hostname = sdsempty(); node->hostname = sdsempty();
node->port = 0; node->human_nodename = sdsempty();
node->tcp_port = 0;
node->cport = 0; node->cport = 0;
node->pport = 0; node->tls_port = 0;
node->fail_reports = listCreate(); node->fail_reports = listCreate();
node->voted_time = 0; node->voted_time = 0;
node->orphaned_time = 0; node->orphaned_time = 0;
...@@ -1422,6 +1557,7 @@ void freeClusterNode(clusterNode *n) { ...@@ -1422,6 +1557,7 @@ void freeClusterNode(clusterNode *n) {
serverAssert(dictDelete(server.cluster->nodes,nodename) == DICT_OK); serverAssert(dictDelete(server.cluster->nodes,nodename) == DICT_OK);
sdsfree(nodename); sdsfree(nodename);
sdsfree(n->hostname); sdsfree(n->hostname);
sdsfree(n->human_nodename);
/* Release links and associated data structures. */ /* Release links and associated data structures. */
if (n->link) freeClusterLink(n->link); if (n->link) freeClusterLink(n->link);
...@@ -1694,9 +1830,9 @@ void clusterHandleConfigEpochCollision(clusterNode *sender) { ...@@ -1694,9 +1830,9 @@ void clusterHandleConfigEpochCollision(clusterNode *sender) {
myself->configEpoch = server.cluster->currentEpoch; myself->configEpoch = server.cluster->currentEpoch;
clusterSaveConfigOrDie(1); clusterSaveConfigOrDie(1);
serverLog(LL_VERBOSE, serverLog(LL_VERBOSE,
"WARNING: configEpoch collision with node %.40s." "WARNING: configEpoch collision with node %.40s (%s)."
" configEpoch set to %llu", " configEpoch set to %llu",
sender->name, sender->name,sender->human_nodename,
(unsigned long long) myself->configEpoch); (unsigned long long) myself->configEpoch);
} }
...@@ -1812,7 +1948,7 @@ void markNodeAsFailingIfNeeded(clusterNode *node) { ...@@ -1812,7 +1948,7 @@ void markNodeAsFailingIfNeeded(clusterNode *node) {
if (failures < needed_quorum) return; /* No weak agreement from masters. */ if (failures < needed_quorum) return; /* No weak agreement from masters. */
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Marking node %.40s as failing (quorum reached).", node->name); "Marking node %.40s (%s) as failing (quorum reached).", node->name, node->human_nodename);
/* Mark the node as failing. */ /* Mark the node as failing. */
node->flags &= ~CLUSTER_NODE_PFAIL; node->flags &= ~CLUSTER_NODE_PFAIL;
...@@ -1840,8 +1976,8 @@ void clearNodeFailureIfNeeded(clusterNode *node) { ...@@ -1840,8 +1976,8 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
* node again. */ * node again. */
if (nodeIsSlave(node) || node->numslots == 0) { if (nodeIsSlave(node) || node->numslots == 0) {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Clear FAIL state for node %.40s: %s is reachable again.", "Clear FAIL state for node %.40s (%s):%s is reachable again.",
node->name, node->name,node->human_nodename,
nodeIsSlave(node) ? "replica" : "master without slots"); nodeIsSlave(node) ? "replica" : "master without slots");
node->flags &= ~CLUSTER_NODE_FAIL; node->flags &= ~CLUSTER_NODE_FAIL;
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
...@@ -1856,8 +1992,8 @@ void clearNodeFailureIfNeeded(clusterNode *node) { ...@@ -1856,8 +1992,8 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
(server.cluster_node_timeout * CLUSTER_FAIL_UNDO_TIME_MULT)) (server.cluster_node_timeout * CLUSTER_FAIL_UNDO_TIME_MULT))
{ {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Clear FAIL state for node %.40s: is reachable again and nobody is serving its slots after some time.", "Clear FAIL state for node %.40s (%s): is reachable again and nobody is serving its slots after some time.",
node->name); node->name, node->human_nodename);
node->flags &= ~CLUSTER_NODE_FAIL; node->flags &= ~CLUSTER_NODE_FAIL;
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
} }
...@@ -1876,7 +2012,7 @@ int clusterHandshakeInProgress(char *ip, int port, int cport) { ...@@ -1876,7 +2012,7 @@ int clusterHandshakeInProgress(char *ip, int port, int cport) {
if (!nodeInHandshake(node)) continue; if (!nodeInHandshake(node)) continue;
if (!strcasecmp(node->ip,ip) && if (!strcasecmp(node->ip,ip) &&
node->port == port && getNodeDefaultClientPort(node) == port &&
node->cport == cport) break; node->cport == cport) break;
} }
dictReleaseIterator(di); dictReleaseIterator(di);
...@@ -1937,12 +2073,36 @@ int clusterStartHandshake(char *ip, int port, int cport) { ...@@ -1937,12 +2073,36 @@ int clusterStartHandshake(char *ip, int port, int cport) {
* handshake. */ * handshake. */
n = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_MEET); n = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE|CLUSTER_NODE_MEET);
memcpy(n->ip,norm_ip,sizeof(n->ip)); memcpy(n->ip,norm_ip,sizeof(n->ip));
n->port = port; if (server.tls_cluster) {
n->tls_port = port;
} else {
n->tcp_port = port;
}
n->cport = cport; n->cport = cport;
clusterAddNode(n); clusterAddNode(n);
return 1; return 1;
} }
static void getClientPortFromClusterMsg(clusterMsg *hdr, int *tls_port, int *tcp_port) {
if (server.tls_cluster) {
*tls_port = ntohs(hdr->port);
*tcp_port = ntohs(hdr->pport);
} else {
*tls_port = ntohs(hdr->pport);
*tcp_port = ntohs(hdr->port);
}
}
static void getClientPortFromGossip(clusterMsgDataGossip *g, int *tls_port, int *tcp_port) {
if (server.tls_cluster) {
*tls_port = ntohs(g->port);
*tcp_port = ntohs(g->pport);
} else {
*tls_port = ntohs(g->pport);
*tcp_port = ntohs(g->port);
}
}
/* Process the gossip section of PING or PONG packets. /* Process the gossip section of PING or PONG packets.
* Note that this function assumes that the packet is already sanity-checked * Note that this function assumes that the packet is already sanity-checked
* by the caller, not in the content of the gossip section, but in the * by the caller, not in the content of the gossip section, but in the
...@@ -1968,6 +2128,10 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) { ...@@ -1968,6 +2128,10 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
sdsfree(ci); sdsfree(ci);
} }
/* Convert port and pport into TCP port and TLS port. */
int msg_tls_port, msg_tcp_port;
getClientPortFromGossip(g, &msg_tls_port, &msg_tcp_port);
/* Update our state accordingly to the gossip sections */ /* Update our state accordingly to the gossip sections */
node = clusterLookupNode(g->nodename, CLUSTER_NAMELEN); node = clusterLookupNode(g->nodename, CLUSTER_NAMELEN);
if (node) { if (node) {
...@@ -1977,15 +2141,15 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) { ...@@ -1977,15 +2141,15 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
if (flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) { if (flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) {
if (clusterNodeAddFailureReport(node,sender)) { if (clusterNodeAddFailureReport(node,sender)) {
serverLog(LL_VERBOSE, serverLog(LL_VERBOSE,
"Node %.40s reported node %.40s as not reachable.", "Node %.40s (%s) reported node %.40s (%s) as not reachable.",
sender->name, node->name); sender->name, sender->human_nodename, node->name, node->human_nodename);
} }
markNodeAsFailingIfNeeded(node); markNodeAsFailingIfNeeded(node);
} else { } else {
if (clusterNodeDelFailureReport(node,sender)) { if (clusterNodeDelFailureReport(node,sender)) {
serverLog(LL_VERBOSE, serverLog(LL_VERBOSE,
"Node %.40s reported node %.40s is back online.", "Node %.40s (%s) reported node %.40s (%s) is back online.",
sender->name, node->name); sender->name, sender->human_nodename, node->name, node->human_nodename);
} }
} }
} }
...@@ -2021,13 +2185,14 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) { ...@@ -2021,13 +2185,14 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
!(flags & CLUSTER_NODE_NOADDR) && !(flags & CLUSTER_NODE_NOADDR) &&
!(flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) && !(flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) &&
(strcasecmp(node->ip,g->ip) || (strcasecmp(node->ip,g->ip) ||
node->port != ntohs(g->port) || node->tls_port != (server.tls_cluster ? ntohs(g->port) : ntohs(g->pport)) ||
node->tcp_port != (server.tls_cluster ? ntohs(g->pport) : ntohs(g->port)) ||
node->cport != ntohs(g->cport))) node->cport != ntohs(g->cport)))
{ {
if (node->link) freeClusterLink(node->link); if (node->link) freeClusterLink(node->link);
memcpy(node->ip,g->ip,NET_IP_STR_LEN); memcpy(node->ip,g->ip,NET_IP_STR_LEN);
node->port = ntohs(g->port); node->tcp_port = msg_tcp_port;
node->pport = ntohs(g->pport); node->tls_port = msg_tls_port;
node->cport = ntohs(g->cport); node->cport = ntohs(g->cport);
node->flags &= ~CLUSTER_NODE_NOADDR; node->flags &= ~CLUSTER_NODE_NOADDR;
} }
...@@ -2048,8 +2213,8 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) { ...@@ -2048,8 +2213,8 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
clusterNode *node; clusterNode *node;
node = createClusterNode(g->nodename, flags); node = createClusterNode(g->nodename, flags);
memcpy(node->ip,g->ip,NET_IP_STR_LEN); memcpy(node->ip,g->ip,NET_IP_STR_LEN);
node->port = ntohs(g->port); node->tcp_port = msg_tcp_port;
node->pport = ntohs(g->pport); node->tls_port = msg_tls_port;
node->cport = ntohs(g->cport); node->cport = ntohs(g->cport);
clusterAddNode(node); clusterAddNode(node);
} }
...@@ -2094,9 +2259,9 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link, ...@@ -2094,9 +2259,9 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
clusterMsg *hdr) clusterMsg *hdr)
{ {
char ip[NET_IP_STR_LEN] = {0}; char ip[NET_IP_STR_LEN] = {0};
int port = ntohs(hdr->port);
int pport = ntohs(hdr->pport);
int cport = ntohs(hdr->cport); int cport = ntohs(hdr->cport);
int tcp_port, tls_port;
getClientPortFromClusterMsg(hdr, &tls_port, &tcp_port);
/* We don't proceed if the link is the same as the sender link, as this /* We don't proceed if the link is the same as the sender link, as this
* function is designed to see if the node link is consistent with the * function is designed to see if the node link is consistent with the
...@@ -2111,23 +2276,23 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link, ...@@ -2111,23 +2276,23 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
* in the next round of PINGs */ * in the next round of PINGs */
if (nodeIp2String(ip,link,hdr->myip) == C_ERR) return 0; if (nodeIp2String(ip,link,hdr->myip) == C_ERR) return 0;
if (node->port == port && node->cport == cport && node->pport == pport && if (node->tcp_port == tcp_port && node->cport == cport && node->tls_port == tls_port &&
strcmp(ip,node->ip) == 0) return 0; strcmp(ip,node->ip) == 0) return 0;
/* IP / port is different, update it. */ /* IP / port is different, update it. */
memcpy(node->ip,ip,sizeof(ip)); memcpy(node->ip,ip,sizeof(ip));
node->port = port; node->tcp_port = tcp_port;
node->pport = pport; node->tls_port = tls_port;
node->cport = cport; node->cport = cport;
if (node->link) freeClusterLink(node->link); if (node->link) freeClusterLink(node->link);
node->flags &= ~CLUSTER_NODE_NOADDR; node->flags &= ~CLUSTER_NODE_NOADDR;
serverLog(LL_NOTICE,"Address updated for node %.40s, now %s:%d", serverLog(LL_NOTICE,"Address updated for node %.40s (%s), now %s:%d",
node->name, node->ip, node->port); node->name, node->human_nodename, node->ip, getNodeDefaultClientPort(node));
/* Check if this is our master and we have to change the /* Check if this is our master and we have to change the
* replication target as well. */ * replication target as well. */
if (nodeIsSlave(myself) && myself->slaveof == node) if (nodeIsSlave(myself) && myself->slaveof == node)
replicationSetMaster(node->ip, node->port); replicationSetMaster(node->ip, getNodeDefaultReplicationPort(node));
return 1; return 1;
} }
...@@ -2195,7 +2360,10 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc ...@@ -2195,7 +2360,10 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
sender_slots++; sender_slots++;
/* The slot is already bound to the sender of this message. */ /* The slot is already bound to the sender of this message. */
if (server.cluster->slots[j] == sender) continue; if (server.cluster->slots[j] == sender) {
bitmapClearBit(server.cluster->owner_not_claiming_slot, j);
continue;
}
/* The slot is in importing state, it should be modified only /* The slot is in importing state, it should be modified only
* manually via redis-cli (example: a resharding is in progress * manually via redis-cli (example: a resharding is in progress
...@@ -2204,10 +2372,10 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc ...@@ -2204,10 +2372,10 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
if (server.cluster->importing_slots_from[j]) continue; if (server.cluster->importing_slots_from[j]) continue;
/* We rebind the slot to the new node claiming it if: /* We rebind the slot to the new node claiming it if:
* 1) The slot was unassigned or the new node claims it with a * 1) The slot was unassigned or the previous owner no longer owns the slot or
* greater configEpoch. * the new node claims it with a greater configEpoch.
* 2) We are not currently importing the slot. */ * 2) We are not currently importing the slot. */
if (server.cluster->slots[j] == NULL || if (isSlotUnclaimed(j) ||
server.cluster->slots[j]->configEpoch < senderConfigEpoch) server.cluster->slots[j]->configEpoch < senderConfigEpoch)
{ {
/* Was this slot mine, and still contains keys? Mark it as /* Was this slot mine, and still contains keys? Mark it as
...@@ -2226,10 +2394,20 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc ...@@ -2226,10 +2394,20 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
} }
clusterDelSlot(j); clusterDelSlot(j);
clusterAddSlot(sender,j); clusterAddSlot(sender,j);
bitmapClearBit(server.cluster->owner_not_claiming_slot, j);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_UPDATE_STATE|
CLUSTER_TODO_FSYNC_CONFIG); CLUSTER_TODO_FSYNC_CONFIG);
} }
} else if (server.cluster->slots[j] == sender) {
/* The slot is currently bound to the sender but the sender is no longer
* claiming it. We don't want to unbind the slot yet as it can cause the cluster
* to move to FAIL state and also throw client error. Keeping the slot bound to
* the previous owner will cause a few client side redirects, but won't throw
* any errors. We will keep track of the uncertainty in ownership to avoid
* propagating misinformation about this slot's ownership using UPDATE
* messages. */
bitmapSetBit(server.cluster->owner_not_claiming_slot, j);
} }
} }
...@@ -2251,7 +2429,7 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc ...@@ -2251,7 +2429,7 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
sender_slots == migrated_our_slots)) { sender_slots == migrated_our_slots)) {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Configuration change detected. Reconfiguring myself " "Configuration change detected. Reconfiguring myself "
"as a replica of %.40s", sender->name); "as a replica of %.40s (%s)", sender->name, sender->human_nodename);
clusterSetMaster(sender); clusterSetMaster(sender);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_UPDATE_STATE|
...@@ -2267,8 +2445,8 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc ...@@ -2267,8 +2445,8 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
* into a replica if its last slot is removed. If no other node takes * into a replica if its last slot is removed. If no other node takes
* over the slot, there is nothing else to trigger replica migration. */ * over the slot, there is nothing else to trigger replica migration. */
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"I'm a sub-replica! Reconfiguring myself as a replica of grandmaster %.40s", "I'm a sub-replica! Reconfiguring myself as a replica of grandmaster %.40s (%s)",
myself->slaveof->slaveof->name); myself->slaveof->slaveof->name, myself->slaveof->slaveof->human_nodename);
clusterSetMaster(myself->slaveof->slaveof); clusterSetMaster(myself->slaveof->slaveof);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
CLUSTER_TODO_UPDATE_STATE| CLUSTER_TODO_UPDATE_STATE|
...@@ -2328,6 +2506,13 @@ uint32_t getHostnamePingExtSize(void) { ...@@ -2328,6 +2506,13 @@ uint32_t getHostnamePingExtSize(void) {
return getAlignedPingExtSize(sdslen(myself->hostname) + 1); return getAlignedPingExtSize(sdslen(myself->hostname) + 1);
} }
uint32_t getHumanNodenamePingExtSize(void) {
if (sdslen(myself->human_nodename) == 0) {
return 0;
}
return getAlignedPingExtSize(sdslen(myself->human_nodename) + 1);
}
uint32_t getShardIdPingExtSize(void) { uint32_t getShardIdPingExtSize(void) {
return getAlignedPingExtSize(sizeof(clusterMsgPingExtShardId)); return getAlignedPingExtSize(sizeof(clusterMsgPingExtShardId));
} }
...@@ -2376,6 +2561,20 @@ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) { ...@@ -2376,6 +2561,20 @@ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) {
extensions++; extensions++;
} }
if (sdslen(myself->human_nodename) != 0) {
if (cursor != NULL) {
/* Populate human_nodename */
clusterMsgPingExtHumanNodename *ext = preparePingExt(cursor, CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME, getHumanNodenamePingExtSize());
memcpy(ext->human_nodename, myself->human_nodename, sdslen(myself->human_nodename));
/* Move the write cursor */
cursor = nextPingExt(cursor);
}
totlen += getHumanNodenamePingExtSize();
extensions++;
}
/* Gossip forgotten nodes */ /* Gossip forgotten nodes */
if (dictSize(server.cluster->nodes_black_list) > 0) { if (dictSize(server.cluster->nodes_black_list) > 0) {
dictIterator *di = dictGetIterator(server.cluster->nodes_black_list); dictIterator *di = dictGetIterator(server.cluster->nodes_black_list);
...@@ -2424,6 +2623,7 @@ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) { ...@@ -2424,6 +2623,7 @@ uint32_t writePingExt(clusterMsg *hdr, int gossipcount) {
void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) { void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender, CLUSTER_NAMELEN); clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender, CLUSTER_NAMELEN);
char *ext_hostname = NULL; char *ext_hostname = NULL;
char *ext_humannodename = NULL;
char *ext_shardid = NULL; char *ext_shardid = NULL;
uint16_t extensions = ntohs(hdr->extensions); uint16_t extensions = ntohs(hdr->extensions);
/* Loop through all the extensions and process them */ /* Loop through all the extensions and process them */
...@@ -2433,6 +2633,9 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) { ...@@ -2433,6 +2633,9 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
if (type == CLUSTERMSG_EXT_TYPE_HOSTNAME) { if (type == CLUSTERMSG_EXT_TYPE_HOSTNAME) {
clusterMsgPingExtHostname *hostname_ext = (clusterMsgPingExtHostname *) &(ext->ext[0].hostname); clusterMsgPingExtHostname *hostname_ext = (clusterMsgPingExtHostname *) &(ext->ext[0].hostname);
ext_hostname = hostname_ext->hostname; ext_hostname = hostname_ext->hostname;
} else if (type == CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME) {
clusterMsgPingExtHumanNodename *humannodename_ext = (clusterMsgPingExtHumanNodename *) &(ext->ext[0].human_nodename);
ext_humannodename = humannodename_ext->human_nodename;
} else if (type == CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE) { } else if (type == CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE) {
clusterMsgPingExtForgottenNode *forgotten_node_ext = &(ext->ext[0].forgotten_node); clusterMsgPingExtForgottenNode *forgotten_node_ext = &(ext->ext[0].forgotten_node);
clusterNode *n = clusterLookupNode(forgotten_node_ext->name, CLUSTER_NAMELEN); clusterNode *n = clusterLookupNode(forgotten_node_ext->name, CLUSTER_NAMELEN);
...@@ -2461,6 +2664,7 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) { ...@@ -2461,6 +2664,7 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) {
* they don't have an announced hostname. Otherwise, we'll * they don't have an announced hostname. Otherwise, we'll
* set it now. */ * set it now. */
updateAnnouncedHostname(sender, ext_hostname); updateAnnouncedHostname(sender, ext_hostname);
updateAnnouncedHumanNodename(sender, ext_humannodename);
updateShardId(sender, ext_shardid); updateShardId(sender, ext_shardid);
} }
...@@ -2662,8 +2866,7 @@ int clusterProcessPacket(clusterLink *link) { ...@@ -2662,8 +2866,7 @@ int clusterProcessPacket(clusterLink *link) {
node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE); node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE);
serverAssert(nodeIp2String(node->ip,link,hdr->myip) == C_OK); serverAssert(nodeIp2String(node->ip,link,hdr->myip) == C_OK);
node->port = ntohs(hdr->port); getClientPortFromClusterMsg(hdr, &node->tls_port, &node->tcp_port);
node->pport = ntohs(hdr->pport);
node->cport = ntohs(hdr->cport); node->cport = ntohs(hdr->cport);
clusterAddNode(node); clusterAddNode(node);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
...@@ -2692,8 +2895,8 @@ int clusterProcessPacket(clusterLink *link) { ...@@ -2692,8 +2895,8 @@ int clusterProcessPacket(clusterLink *link) {
* IP/port of the node with the new one. */ * IP/port of the node with the new one. */
if (sender) { if (sender) {
serverLog(LL_VERBOSE, serverLog(LL_VERBOSE,
"Handshake: we already know node %.40s, " "Handshake: we already know node %.40s (%s), "
"updating the address if needed.", sender->name); "updating the address if needed.", sender->name, sender->human_nodename);
if (nodeUpdateAddressIfNeeded(sender,link,hdr)) if (nodeUpdateAddressIfNeeded(sender,link,hdr))
{ {
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
...@@ -2725,8 +2928,8 @@ int clusterProcessPacket(clusterLink *link) { ...@@ -2725,8 +2928,8 @@ int clusterProcessPacket(clusterLink *link) {
link->node->flags); link->node->flags);
link->node->flags |= CLUSTER_NODE_NOADDR; link->node->flags |= CLUSTER_NODE_NOADDR;
link->node->ip[0] = '\0'; link->node->ip[0] = '\0';
link->node->port = 0; link->node->tcp_port = 0;
link->node->pport = 0; link->node->tls_port = 0;
link->node->cport = 0; link->node->cport = 0;
freeClusterLink(link); freeClusterLink(link);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
...@@ -2861,7 +3064,7 @@ int clusterProcessPacket(clusterLink *link) { ...@@ -2861,7 +3064,7 @@ int clusterProcessPacket(clusterLink *link) {
for (j = 0; j < CLUSTER_SLOTS; j++) { for (j = 0; j < CLUSTER_SLOTS; j++) {
if (bitmapTestBit(hdr->myslots,j)) { if (bitmapTestBit(hdr->myslots,j)) {
if (server.cluster->slots[j] == sender || if (server.cluster->slots[j] == sender ||
server.cluster->slots[j] == NULL) continue; isSlotUnclaimed(j)) continue;
if (server.cluster->slots[j]->configEpoch > if (server.cluster->slots[j]->configEpoch >
senderConfigEpoch) senderConfigEpoch)
{ {
...@@ -2904,8 +3107,8 @@ int clusterProcessPacket(clusterLink *link) { ...@@ -2904,8 +3107,8 @@ int clusterProcessPacket(clusterLink *link) {
!(failing->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_MYSELF))) !(failing->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_MYSELF)))
{ {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"FAIL message received from %.40s about %.40s", "FAIL message received from %.40s (%s) about %.40s (%s)",
hdr->sender, hdr->data.fail.about.nodename); hdr->sender, sender->human_nodename, hdr->data.fail.about.nodename, failing->human_nodename);
failing->flags |= CLUSTER_NODE_FAIL; failing->flags |= CLUSTER_NODE_FAIL;
failing->fail_time = now; failing->fail_time = now;
failing->flags &= ~CLUSTER_NODE_PFAIL; failing->flags &= ~CLUSTER_NODE_PFAIL;
...@@ -2969,8 +3172,8 @@ int clusterProcessPacket(clusterLink *link) { ...@@ -2969,8 +3172,8 @@ int clusterProcessPacket(clusterLink *link) {
pauseActions(PAUSE_DURING_FAILOVER, pauseActions(PAUSE_DURING_FAILOVER,
now + (CLUSTER_MF_TIMEOUT * CLUSTER_MF_PAUSE_MULT), now + (CLUSTER_MF_TIMEOUT * CLUSTER_MF_PAUSE_MULT),
PAUSE_ACTIONS_CLIENT_WRITE_SET); PAUSE_ACTIONS_CLIENT_WRITE_SET);
serverLog(LL_NOTICE,"Manual failover requested by replica %.40s.", serverLog(LL_NOTICE,"Manual failover requested by replica %.40s (%s).",
sender->name); sender->name, sender->human_nodename);
/* We need to send a ping message to the replica, as it would carry /* We need to send a ping message to the replica, as it would carry
* `server.cluster->mf_master_offset`, which means the master paused clients * `server.cluster->mf_master_offset`, which means the master paused clients
* at offset `server.cluster->mf_master_offset`, so that the replica would * at offset `server.cluster->mf_master_offset`, so that the replica would
...@@ -3274,15 +3477,20 @@ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen) { ...@@ -3274,15 +3477,20 @@ static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen) {
} }
/* Handle cluster-announce-[tls-|bus-]port. */ /* Handle cluster-announce-[tls-|bus-]port. */
int announced_port, announced_pport, announced_cport; int announced_tcp_port, announced_tls_port, announced_cport;
deriveAnnouncedPorts(&announced_port, &announced_pport, &announced_cport); deriveAnnouncedPorts(&announced_tcp_port, &announced_tls_port, &announced_cport);
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots)); memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
memset(hdr->slaveof,0,CLUSTER_NAMELEN); memset(hdr->slaveof,0,CLUSTER_NAMELEN);
if (myself->slaveof != NULL) if (myself->slaveof != NULL)
memcpy(hdr->slaveof,myself->slaveof->name, CLUSTER_NAMELEN); memcpy(hdr->slaveof,myself->slaveof->name, CLUSTER_NAMELEN);
hdr->port = htons(announced_port); if (server.tls_cluster) {
hdr->pport = htons(announced_pport); hdr->port = htons(announced_tls_port);
hdr->pport = htons(announced_tcp_port);
} else {
hdr->port = htons(announced_tcp_port);
hdr->pport = htons(announced_tls_port);
}
hdr->cport = htons(announced_cport); hdr->cport = htons(announced_cport);
hdr->flags = htons(myself->flags); hdr->flags = htons(myself->flags);
hdr->state = server.cluster->state; hdr->state = server.cluster->state;
...@@ -3314,10 +3522,15 @@ void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) { ...@@ -3314,10 +3522,15 @@ void clusterSetGossipEntry(clusterMsg *hdr, int i, clusterNode *n) {
gossip->ping_sent = htonl(n->ping_sent/1000); gossip->ping_sent = htonl(n->ping_sent/1000);
gossip->pong_received = htonl(n->pong_received/1000); gossip->pong_received = htonl(n->pong_received/1000);
memcpy(gossip->ip,n->ip,sizeof(n->ip)); memcpy(gossip->ip,n->ip,sizeof(n->ip));
gossip->port = htons(n->port); if (server.tls_cluster) {
gossip->port = htons(n->tls_port);
gossip->pport = htons(n->tcp_port);
} else {
gossip->port = htons(n->tcp_port);
gossip->pport = htons(n->tls_port);
}
gossip->cport = htons(n->cport); gossip->cport = htons(n->cport);
gossip->flags = htons(n->flags); gossip->flags = htons(n->flags);
gossip->pport = htons(n->pport);
gossip->notused1 = 0; gossip->notused1 = 0;
} }
...@@ -3375,7 +3588,6 @@ void clusterSendPing(clusterLink *link, int type) { ...@@ -3375,7 +3588,6 @@ void clusterSendPing(clusterLink *link, int type) {
estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData); estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData);
estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted)); estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted));
estlen += writePingExt(NULL, 0); estlen += writePingExt(NULL, 0);
/* Note: clusterBuildMessageHdr() expects the buffer to be always at least /* Note: clusterBuildMessageHdr() expects the buffer to be always at least
* sizeof(clusterMsg) or more. */ * sizeof(clusterMsg) or more. */
if (estlen < (int)sizeof(clusterMsg)) estlen = sizeof(clusterMsg); if (estlen < (int)sizeof(clusterMsg)) estlen = sizeof(clusterMsg);
...@@ -3555,6 +3767,10 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) { ...@@ -3555,6 +3767,10 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN); memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch); hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots)); memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots));
for (unsigned int i = 0; i < sizeof(node->slots); i++) {
/* Don't advertise slots that the node stopped claiming */
hdr->data.update.nodecfg.slots[i] = hdr->data.update.nodecfg.slots[i] & (~server.cluster->owner_not_claiming_slot[i]);
}
clusterSendMessage(link,msgblock); clusterSendMessage(link,msgblock);
clusterMsgSendBlockDecrRefCount(msgblock); clusterMsgSendBlockDecrRefCount(msgblock);
...@@ -3703,8 +3919,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { ...@@ -3703,8 +3919,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
* request, if the request epoch was greater. */ * request, if the request epoch was greater. */
if (requestCurrentEpoch < server.cluster->currentEpoch) { if (requestCurrentEpoch < server.cluster->currentEpoch) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Failover auth denied to %.40s: reqEpoch (%llu) < curEpoch(%llu)", "Failover auth denied to %.40s (%s): reqEpoch (%llu) < curEpoch(%llu)",
node->name, node->name, node->human_nodename,
(unsigned long long) requestCurrentEpoch, (unsigned long long) requestCurrentEpoch,
(unsigned long long) server.cluster->currentEpoch); (unsigned long long) server.cluster->currentEpoch);
return; return;
...@@ -3713,8 +3929,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { ...@@ -3713,8 +3929,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
/* I already voted for this epoch? Return ASAP. */ /* I already voted for this epoch? Return ASAP. */
if (server.cluster->lastVoteEpoch == server.cluster->currentEpoch) { if (server.cluster->lastVoteEpoch == server.cluster->currentEpoch) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Failover auth denied to %.40s: already voted for epoch %llu", "Failover auth denied to %.40s (%s): already voted for epoch %llu",
node->name, node->name, node->human_nodename,
(unsigned long long) server.cluster->currentEpoch); (unsigned long long) server.cluster->currentEpoch);
return; return;
} }
...@@ -3727,16 +3943,16 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { ...@@ -3727,16 +3943,16 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
{ {
if (nodeIsMaster(node)) { if (nodeIsMaster(node)) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Failover auth denied to %.40s: it is a master node", "Failover auth denied to %.40s (%s): it is a master node",
node->name); node->name, node->human_nodename);
} else if (master == NULL) { } else if (master == NULL) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Failover auth denied to %.40s: I don't know its master", "Failover auth denied to %.40s (%s): I don't know its master",
node->name); node->name, node->human_nodename);
} else if (!nodeFailed(master)) { } else if (!nodeFailed(master)) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Failover auth denied to %.40s: its master is up", "Failover auth denied to %.40s (%s): its master is up",
node->name); node->name, node->human_nodename);
} }
return; return;
} }
...@@ -3747,9 +3963,9 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { ...@@ -3747,9 +3963,9 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
if (mstime() - node->slaveof->voted_time < server.cluster_node_timeout * 2) if (mstime() - node->slaveof->voted_time < server.cluster_node_timeout * 2)
{ {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Failover auth denied to %.40s: " "Failover auth denied to %.40s %s: "
"can't vote about this master before %lld milliseconds", "can't vote about this master before %lld milliseconds",
node->name, node->name, node->human_nodename,
(long long) ((server.cluster_node_timeout*2)- (long long) ((server.cluster_node_timeout*2)-
(mstime() - node->slaveof->voted_time))); (mstime() - node->slaveof->voted_time)));
return; return;
...@@ -3760,7 +3976,7 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { ...@@ -3760,7 +3976,7 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
* slots in the current configuration. */ * slots in the current configuration. */
for (j = 0; j < CLUSTER_SLOTS; j++) { for (j = 0; j < CLUSTER_SLOTS; j++) {
if (bitmapTestBit(claimed_slots, j) == 0) continue; if (bitmapTestBit(claimed_slots, j) == 0) continue;
if (server.cluster->slots[j] == NULL || if (isSlotUnclaimed(j) ||
server.cluster->slots[j]->configEpoch <= requestConfigEpoch) server.cluster->slots[j]->configEpoch <= requestConfigEpoch)
{ {
continue; continue;
...@@ -3769,9 +3985,9 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { ...@@ -3769,9 +3985,9 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
* is served by a master with a greater configEpoch than the one claimed * is served by a master with a greater configEpoch than the one claimed
* by the slave requesting our vote. Refuse to vote for this slave. */ * by the slave requesting our vote. Refuse to vote for this slave. */
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Failover auth denied to %.40s: " "Failover auth denied to %.40s (%s): "
"slot %d epoch (%llu) > reqEpoch (%llu)", "slot %d epoch (%llu) > reqEpoch (%llu)",
node->name, j, node->name, node->human_nodename, j,
(unsigned long long) server.cluster->slots[j]->configEpoch, (unsigned long long) server.cluster->slots[j]->configEpoch,
(unsigned long long) requestConfigEpoch); (unsigned long long) requestConfigEpoch);
return; return;
...@@ -3782,8 +3998,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { ...@@ -3782,8 +3998,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
node->slaveof->voted_time = mstime(); node->slaveof->voted_time = mstime();
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG); clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
clusterSendFailoverAuth(node); clusterSendFailoverAuth(node);
serverLog(LL_NOTICE, "Failover auth granted to %.40s for epoch %llu", serverLog(LL_NOTICE, "Failover auth granted to %.40s (%s) for epoch %llu",
node->name, (unsigned long long) server.cluster->currentEpoch); node->name, node->human_nodename, (unsigned long long) server.cluster->currentEpoch);
} }
/* This function returns the "rank" of this instance, a slave, in the context /* This function returns the "rank" of this instance, a slave, in the context
...@@ -4538,7 +4754,7 @@ void clusterCron(void) { ...@@ -4538,7 +4754,7 @@ void clusterCron(void) {
myself->slaveof && myself->slaveof &&
nodeHasAddr(myself->slaveof)) nodeHasAddr(myself->slaveof))
{ {
replicationSetMaster(myself->slaveof->ip, myself->slaveof->port); replicationSetMaster(myself->slaveof->ip, getNodeDefaultReplicationPort(myself->slaveof));
} }
/* Abort a manual failover if the timeout is reached. */ /* Abort a manual failover if the timeout is reached. */
...@@ -4941,7 +5157,7 @@ void clusterSetMaster(clusterNode *n) { ...@@ -4941,7 +5157,7 @@ void clusterSetMaster(clusterNode *n) {
myself->slaveof = n; myself->slaveof = n;
updateShardId(myself, n->shard_id); updateShardId(myself, n->shard_id);
clusterNodeAddSlave(n,myself); clusterNodeAddSlave(n,myself);
replicationSetMaster(n->ip, n->port); replicationSetMaster(n->ip, getNodeDefaultReplicationPort(n));
resetManualFailover(); resetManualFailover();
} }
...@@ -5000,34 +5216,36 @@ sds representSlotInfo(sds ci, uint16_t *slot_info_pairs, int slot_info_pairs_cou ...@@ -5000,34 +5216,36 @@ sds representSlotInfo(sds ci, uint16_t *slot_info_pairs, int slot_info_pairs_cou
* See clusterGenNodesDescription() top comment for more information. * See clusterGenNodesDescription() top comment for more information.
* *
* The function returns the string representation as an SDS string. */ * The function returns the string representation as an SDS string. */
sds clusterGenNodeDescription(clusterNode *node, int use_pport) { sds clusterGenNodeDescription(client *c, clusterNode *node, int tls_primary) {
int j, start; int j, start;
sds ci; sds ci;
int port = use_pport && node->pport ? node->pport : node->port; int port = getNodeClientPort(node, tls_primary);
/* Node coordinates */ /* Node coordinates */
ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN); ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
/* Node's ip/port and optional announced hostname */ ci = sdscatfmt(ci," %s:%i@%i",
if (sdslen(node->hostname) != 0) {
ci = sdscatprintf(ci," %s:%i@%i,%s",
node->ip,
port,
node->cport,
node->hostname);
} else {
ci = sdscatprintf(ci," %s:%i@%i,",
node->ip, node->ip,
port, port,
node->cport); node->cport);
if (sdslen(node->hostname) != 0) {
ci = sdscatfmt(ci,",%s", node->hostname);
}
if (sdslen(node->hostname) == 0) {
ci = sdscatfmt(ci,",", 1);
}
/* Don't expose aux fields to any clients yet but do allow them
* to be persisted to nodes.conf */
if (c == NULL) {
for (int i = af_count-1; i >=0; i--) {
if ((tls_primary && i == af_tls_port) || (!tls_primary && i == af_tcp_port)) {
continue;
} }
/* Node's aux fields */
for (int i = af_start; i < af_count; i++) {
if (auxFieldHandlers[i].isPresent(node)) { if (auxFieldHandlers[i].isPresent(node)) {
ci = sdscatprintf(ci, ",%s=", auxFieldHandlers[i].field); ci = sdscatprintf(ci, ",%s=", auxFieldHandlers[i].field);
ci = auxFieldHandlers[i].getter(node, ci); ci = auxFieldHandlers[i].getter(node, ci);
} }
} }
}
/* Flags */ /* Flags */
ci = sdscatlen(ci," ",1); ci = sdscatlen(ci," ",1);
...@@ -5144,13 +5362,13 @@ void clusterFreeNodesSlotsInfo(clusterNode *n) { ...@@ -5144,13 +5362,13 @@ void clusterFreeNodesSlotsInfo(clusterNode *n) {
* include all the known nodes in the representation, including nodes in * include all the known nodes in the representation, including nodes in
* the HANDSHAKE state. * the HANDSHAKE state.
* *
* Setting use_pport to 1 in a TLS cluster makes the result contain the * Setting tls_primary to 1 to put TLS port in the main <ip>:<port>
* plaintext client port rather then the TLS client port of each node. * field and put TCP port in aux field, instead of the opposite way.
* *
* The representation obtained using this function is used for the output * The representation obtained using this function is used for the output
* of the CLUSTER NODES function, and as format for the cluster * of the CLUSTER NODES function, and as format for the cluster
* configuration file (nodes.conf) for a given node. */ * configuration file (nodes.conf) for a given node. */
sds clusterGenNodesDescription(int filter, int use_pport) { sds clusterGenNodesDescription(client *c, int filter, int tls_primary) {
sds ci = sdsempty(), ni; sds ci = sdsempty(), ni;
dictIterator *di; dictIterator *di;
dictEntry *de; dictEntry *de;
...@@ -5163,7 +5381,7 @@ sds clusterGenNodesDescription(int filter, int use_pport) { ...@@ -5163,7 +5381,7 @@ sds clusterGenNodesDescription(int filter, int use_pport) {
clusterNode *node = dictGetVal(de); clusterNode *node = dictGetVal(de);
if (node->flags & filter) continue; if (node->flags & filter) continue;
ni = clusterGenNodeDescription(node, use_pport); ni = clusterGenNodeDescription(c, node, tls_primary);
ci = sdscatsds(ci,ni); ci = sdscatsds(ci,ni);
sdsfree(ni); sdsfree(ni);
ci = sdscatlen(ci,"\n",1); ci = sdscatlen(ci,"\n",1);
...@@ -5351,10 +5569,8 @@ void addNodeToNodeReply(client *c, clusterNode *node) { ...@@ -5351,10 +5569,8 @@ void addNodeToNodeReply(client *c, clusterNode *node) {
serverPanic("Unrecognized preferred endpoint type"); serverPanic("Unrecognized preferred endpoint type");
} }
/* Report non-TLS ports to non-TLS client in TLS cluster if available. */ /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */
int use_pport = (server.tls_cluster && addReplyLongLong(c, getNodeClientPort(node, connIsTLS(c->conn)));
c->conn && (c->conn->type != connectionTypeTls()));
addReplyLongLong(c, use_pport && node->pport ? node->pport : node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN); addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
/* Add the additional endpoint information, this is all the known networking information /* Add the additional endpoint information, this is all the known networking information
...@@ -5417,19 +5633,15 @@ void addNodeDetailsToShardReply(client *c, clusterNode *node) { ...@@ -5417,19 +5633,15 @@ void addNodeDetailsToShardReply(client *c, clusterNode *node) {
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN); addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
reply_count++; reply_count++;
/* We use server.tls_cluster as a proxy for whether or not if (node->tcp_port) {
* the remote port is the tls port or not */
int plaintext_port = server.tls_cluster ? node->pport : node->port;
int tls_port = server.tls_cluster ? node->port : 0;
if (plaintext_port) {
addReplyBulkCString(c, "port"); addReplyBulkCString(c, "port");
addReplyLongLong(c, plaintext_port); addReplyLongLong(c, node->tcp_port);
reply_count++; reply_count++;
} }
if (tls_port) { if (node->tls_port) {
addReplyBulkCString(c, "tls-port"); addReplyBulkCString(c, "tls-port");
addReplyLongLong(c, tls_port); addReplyLongLong(c, node->tls_port);
reply_count++; reply_count++;
} }
...@@ -5708,14 +5920,14 @@ NULL ...@@ -5708,14 +5920,14 @@ NULL
long long port, cport; long long port, cport;
if (getLongLongFromObject(c->argv[3], &port) != C_OK) { if (getLongLongFromObject(c->argv[3], &port) != C_OK) {
addReplyErrorFormat(c,"Invalid TCP base port specified: %s", addReplyErrorFormat(c,"Invalid base port specified: %s",
(char*)c->argv[3]->ptr); (char*)c->argv[3]->ptr);
return; return;
} }
if (c->argc == 5) { if (c->argc == 5) {
if (getLongLongFromObject(c->argv[4], &cport) != C_OK) { if (getLongLongFromObject(c->argv[4], &cport) != C_OK) {
addReplyErrorFormat(c,"Invalid TCP bus port specified: %s", addReplyErrorFormat(c,"Invalid bus port specified: %s",
(char*)c->argv[4]->ptr); (char*)c->argv[4]->ptr);
return; return;
} }
...@@ -5733,11 +5945,8 @@ NULL ...@@ -5733,11 +5945,8 @@ NULL
} }
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
/* CLUSTER NODES */ /* CLUSTER NODES */
/* Report plaintext ports, only if cluster is TLS but client is known to /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */
* be non-TLS). */ sds nodes = clusterGenNodesDescription(c, 0, connIsTLS(c->conn));
int use_pport = (server.tls_cluster &&
c->conn && (c->conn->type != connectionTypeTls()));
sds nodes = clusterGenNodesDescription(0, use_pport);
addReplyVerbatim(c,nodes,sdslen(nodes),"txt"); addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
sdsfree(nodes); sdsfree(nodes);
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
...@@ -5923,7 +6132,7 @@ NULL ...@@ -5923,7 +6132,7 @@ NULL
{ {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Configuration change detected. Reconfiguring myself " "Configuration change detected. Reconfiguring myself "
"as a replica of %.40s", n->name); "as a replica of %.40s (%s)", n->name, n->human_nodename);
clusterSetMaster(n); clusterSetMaster(n);
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG |
CLUSTER_TODO_UPDATE_STATE | CLUSTER_TODO_UPDATE_STATE |
...@@ -6099,12 +6308,10 @@ NULL ...@@ -6099,12 +6308,10 @@ NULL
return; return;
} }
/* Use plaintext port if cluster is TLS but client is non-TLS. */ /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */
int use_pport = (server.tls_cluster &&
c->conn && (c->conn->type != connectionTypeTls()));
addReplyArrayLen(c,n->numslaves); addReplyArrayLen(c,n->numslaves);
for (j = 0; j < n->numslaves; j++) { for (j = 0; j < n->numslaves; j++) {
sds ni = clusterGenNodeDescription(n->slaves[j], use_pport); sds ni = clusterGenNodeDescription(c, n->slaves[j], connIsTLS(c->conn));
addReplyBulkCString(c,ni); addReplyBulkCString(c,ni);
sdsfree(ni); sdsfree(ni);
} }
...@@ -6436,7 +6643,8 @@ void restoreCommand(client *c) { ...@@ -6436,7 +6643,8 @@ void restoreCommand(client *c) {
if (ttl && !absttl) ttl+=commandTimeSnapshot(); if (ttl && !absttl) ttl+=commandTimeSnapshot();
if (ttl && checkAlreadyExpired(ttl)) { if (ttl && checkAlreadyExpired(ttl)) {
if (deleted) { if (deleted) {
rewriteClientCommandVector(c,2,shared.del,key); robj *aux = server.lazyfree_lazy_server_del ? shared.unlink : shared.del;
rewriteClientCommandVector(c, 2, aux, key);
signalModifiedKey(c,c->db,key); signalModifiedKey(c,c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
server.dirty++; server.dirty++;
...@@ -7228,11 +7436,8 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co ...@@ -7228,11 +7436,8 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
} else if (error_code == CLUSTER_REDIR_MOVED || } else if (error_code == CLUSTER_REDIR_MOVED ||
error_code == CLUSTER_REDIR_ASK) error_code == CLUSTER_REDIR_ASK)
{ {
/* Redirect to IP:port. Include plaintext port if cluster is TLS but /* Report TLS ports to TLS client, and report non-TLS port to non-TLS client. */
* client is non-TLS. */ int port = getNodeClientPort(n, connIsTLS(c->conn));
int use_pport = (server.tls_cluster &&
c->conn && (c->conn->type != connectionTypeTls()));
int port = use_pport && n->pport ? n->pport : n->port;
addReplyErrorSds(c,sdscatprintf(sdsempty(), addReplyErrorSds(c,sdscatprintf(sdsempty(),
"-%s %d %s:%d", "-%s %d %s:%d",
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED", (error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
......
...@@ -141,9 +141,9 @@ typedef struct clusterNode { ...@@ -141,9 +141,9 @@ typedef struct clusterNode {
long long repl_offset; /* Last known repl offset for this node. */ long long repl_offset; /* Last known repl offset for this node. */
char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */ char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */
sds hostname; /* The known hostname for this node */ sds hostname; /* The known hostname for this node */
int port; /* Latest known clients port (TLS or plain). */ sds human_nodename; /* The known human readable nodename for this node */
int pport; /* Latest known clients plaintext port. Only used int tcp_port; /* Latest known clients TCP port. */
if the main clients port is for TLS. */ int tls_port; /* Latest known clients TLS port */
int cport; /* Latest known cluster port of this node. */ int cport; /* Latest known cluster port of this node. */
clusterLink *link; /* TCP/IP link established toward this node */ clusterLink *link; /* TCP/IP link established toward this node */
clusterLink *inbound_link; /* TCP/IP link accepted from this node */ clusterLink *inbound_link; /* TCP/IP link accepted from this node */
...@@ -213,6 +213,13 @@ typedef struct clusterState { ...@@ -213,6 +213,13 @@ typedef struct clusterState {
long long stats_pfail_nodes; /* Number of nodes in PFAIL status, long long stats_pfail_nodes; /* Number of nodes in PFAIL status,
excluding nodes without address. */ excluding nodes without address. */
unsigned long long stat_cluster_links_buffer_limit_exceeded; /* Total number of cluster links freed due to exceeding buffer limit */ unsigned long long stat_cluster_links_buffer_limit_exceeded; /* Total number of cluster links freed due to exceeding buffer limit */
/* Bit map for slots that are no longer claimed by the owner in cluster PING
* messages. During slot migration, the owner will stop claiming the slot after
* the ownership transfer. Set the bit corresponding to the slot when a node
* stops claiming the slot. This prevents spreading incorrect information (that
* source still owns the slot) using UPDATE messages. */
unsigned char owner_not_claiming_slot[CLUSTER_SLOTS / 8];
} clusterState; } clusterState;
/* Redis cluster messages header */ /* Redis cluster messages header */
...@@ -225,10 +232,10 @@ typedef struct { ...@@ -225,10 +232,10 @@ typedef struct {
uint32_t ping_sent; uint32_t ping_sent;
uint32_t pong_received; uint32_t pong_received;
char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */ char ip[NET_IP_STR_LEN]; /* IP address last time it was seen */
uint16_t port; /* base port last time it was seen */ uint16_t port; /* primary port last time it was seen */
uint16_t cport; /* cluster port last time it was seen */ uint16_t cport; /* cluster port last time it was seen */
uint16_t flags; /* node->flags copy */ uint16_t flags; /* node->flags copy */
uint16_t pport; /* plaintext-port, when base port is TLS */ uint16_t pport; /* secondary port last time it was seen */
uint16_t notused1; uint16_t notused1;
} clusterMsgDataGossip; } clusterMsgDataGossip;
...@@ -260,6 +267,7 @@ typedef struct { ...@@ -260,6 +267,7 @@ typedef struct {
* consistent manner. */ * consistent manner. */
typedef enum { typedef enum {
CLUSTERMSG_EXT_TYPE_HOSTNAME, CLUSTERMSG_EXT_TYPE_HOSTNAME,
CLUSTERMSG_EXT_TYPE_HUMAN_NODENAME,
CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE, CLUSTERMSG_EXT_TYPE_FORGOTTEN_NODE,
CLUSTERMSG_EXT_TYPE_SHARDID, CLUSTERMSG_EXT_TYPE_SHARDID,
} clusterMsgPingtypes; } clusterMsgPingtypes;
...@@ -271,6 +279,10 @@ typedef struct { ...@@ -271,6 +279,10 @@ typedef struct {
char hostname[1]; /* The announced hostname, ends with \0. */ char hostname[1]; /* The announced hostname, ends with \0. */
} clusterMsgPingExtHostname; } clusterMsgPingExtHostname;
typedef struct {
char human_nodename[1]; /* The announced nodename, ends with \0. */
} clusterMsgPingExtHumanNodename;
typedef struct { typedef struct {
char name[CLUSTER_NAMELEN]; /* Node name. */ char name[CLUSTER_NAMELEN]; /* Node name. */
uint64_t ttl; /* Remaining time to blacklist the node, in seconds. */ uint64_t ttl; /* Remaining time to blacklist the node, in seconds. */
...@@ -288,6 +300,7 @@ typedef struct { ...@@ -288,6 +300,7 @@ typedef struct {
uint16_t unused; /* 16 bits of padding to make this structure 8 byte aligned. */ uint16_t unused; /* 16 bits of padding to make this structure 8 byte aligned. */
union { union {
clusterMsgPingExtHostname hostname; clusterMsgPingExtHostname hostname;
clusterMsgPingExtHumanNodename human_nodename;
clusterMsgPingExtForgottenNode forgotten_node; clusterMsgPingExtForgottenNode forgotten_node;
clusterMsgPingExtShardId shard_id; clusterMsgPingExtShardId shard_id;
} ext[]; /* Actual extension information, formatted so that the data is 8 } ext[]; /* Actual extension information, formatted so that the data is 8
...@@ -331,7 +344,7 @@ typedef struct { ...@@ -331,7 +344,7 @@ typedef struct {
char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */ char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */
uint32_t totlen; /* Total length of this message */ uint32_t totlen; /* Total length of this message */
uint16_t ver; /* Protocol version, currently set to 1. */ uint16_t ver; /* Protocol version, currently set to 1. */
uint16_t port; /* TCP base port number. */ uint16_t port; /* Primary port number (TCP or TLS). */
uint16_t type; /* Message type */ uint16_t type; /* Message type */
uint16_t count; /* Only used for some kind of messages. */ uint16_t count; /* Only used for some kind of messages. */
uint64_t currentEpoch; /* The epoch accordingly to the sending node. */ uint64_t currentEpoch; /* The epoch accordingly to the sending node. */
...@@ -346,7 +359,8 @@ typedef struct { ...@@ -346,7 +359,8 @@ typedef struct {
char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */ char myip[NET_IP_STR_LEN]; /* Sender IP, if not all zeroed. */
uint16_t extensions; /* Number of extensions sent along with this packet. */ uint16_t extensions; /* Number of extensions sent along with this packet. */
char notused1[30]; /* 30 bytes reserved for future usage. */ char notused1[30]; /* 30 bytes reserved for future usage. */
uint16_t pport; /* Sender TCP plaintext port, if base port is TLS */ uint16_t pport; /* Secondary port number: if primary port is TCP port, this is
TLS port, and if primary port is TLS port, this is TCP port.*/
uint16_t cport; /* Sender TCP cluster bus port */ uint16_t cport; /* Sender TCP cluster bus port */
uint16_t flags; /* Sender node flags */ uint16_t flags; /* Sender node flags */
unsigned char state; /* Cluster state from the POV of the sender */ unsigned char state; /* Cluster state from the POV of the sender */
...@@ -422,8 +436,11 @@ void slotToChannelAdd(sds channel); ...@@ -422,8 +436,11 @@ void slotToChannelAdd(sds channel);
void slotToChannelDel(sds channel); void slotToChannelDel(sds channel);
void clusterUpdateMyselfHostname(void); void clusterUpdateMyselfHostname(void);
void clusterUpdateMyselfAnnouncedPorts(void); void clusterUpdateMyselfAnnouncedPorts(void);
sds clusterGenNodesDescription(int filter, int use_pport); sds clusterGenNodesDescription(client *c, int filter, int tls_primary);
sds genClusterInfoString(void); sds genClusterInfoString(void);
void freeClusterLink(clusterLink *link); void freeClusterLink(clusterLink *link);
void clusterUpdateMyselfHumanNodename(void);
int isValidAuxString(char *s, unsigned int length);
int getNodeDefaultClientPort(clusterNode *n);
#endif /* __CLUSTER_H */ #endif /* __CLUSTER_H */
...@@ -970,9 +970,9 @@ struct COMMAND_STRUCT CLUSTER_Subcommands[] = { ...@@ -970,9 +970,9 @@ struct COMMAND_STRUCT CLUSTER_Subcommands[] = {
{MAKE_CMD("saveconfig","Forces a node to save the cluster configuration to disk.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SAVECONFIG_History,0,CLUSTER_SAVECONFIG_Tips,0,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SAVECONFIG_Keyspecs,0,NULL,0)}, {MAKE_CMD("saveconfig","Forces a node to save the cluster configuration to disk.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SAVECONFIG_History,0,CLUSTER_SAVECONFIG_Tips,0,clusterCommand,2,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SAVECONFIG_Keyspecs,0,NULL,0)},
{MAKE_CMD("set-config-epoch","Sets the configuration epoch for a new node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SET_CONFIG_EPOCH_History,0,CLUSTER_SET_CONFIG_EPOCH_Tips,0,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SET_CONFIG_EPOCH_Keyspecs,0,NULL,1),.args=CLUSTER_SET_CONFIG_EPOCH_Args}, {MAKE_CMD("set-config-epoch","Sets the configuration epoch for a new node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SET_CONFIG_EPOCH_History,0,CLUSTER_SET_CONFIG_EPOCH_Tips,0,clusterCommand,3,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SET_CONFIG_EPOCH_Keyspecs,0,NULL,1),.args=CLUSTER_SET_CONFIG_EPOCH_Args},
{MAKE_CMD("setslot","Binds a hash slot to a node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SETSLOT_History,0,CLUSTER_SETSLOT_Tips,0,clusterCommand,-4,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SETSLOT_Keyspecs,0,NULL,2),.args=CLUSTER_SETSLOT_Args}, {MAKE_CMD("setslot","Binds a hash slot to a node.","O(1)","3.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SETSLOT_History,0,CLUSTER_SETSLOT_Tips,0,clusterCommand,-4,CMD_NO_ASYNC_LOADING|CMD_ADMIN|CMD_STALE,0,CLUSTER_SETSLOT_Keyspecs,0,NULL,2),.args=CLUSTER_SETSLOT_Args},
{MAKE_CMD("shards","Returns the mapping of cluster slots to shards.","O(N) where N is the total number of cluster nodes","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SHARDS_History,0,CLUSTER_SHARDS_Tips,1,clusterCommand,2,CMD_STALE,0,CLUSTER_SHARDS_Keyspecs,0,NULL,0)}, {MAKE_CMD("shards","Returns the mapping of cluster slots to shards.","O(N) where N is the total number of cluster nodes","7.0.0",CMD_DOC_NONE,NULL,NULL,"cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SHARDS_History,0,CLUSTER_SHARDS_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,0,CLUSTER_SHARDS_Keyspecs,0,NULL,0)},
{MAKE_CMD("slaves","Lists the replica nodes of a master node.","O(1)","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER REPLICAS`","5.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLAVES_History,0,CLUSTER_SLAVES_Tips,1,clusterCommand,3,CMD_ADMIN|CMD_STALE,0,CLUSTER_SLAVES_Keyspecs,0,NULL,1),.args=CLUSTER_SLAVES_Args}, {MAKE_CMD("slaves","Lists the replica nodes of a master node.","O(1)","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER REPLICAS`","5.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLAVES_History,0,CLUSTER_SLAVES_Tips,1,clusterCommand,3,CMD_ADMIN|CMD_STALE,0,CLUSTER_SLAVES_Keyspecs,0,NULL,1),.args=CLUSTER_SLAVES_Args},
{MAKE_CMD("slots","Returns the mapping of cluster slots to nodes.","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER SHARDS`","7.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLOTS_History,2,CLUSTER_SLOTS_Tips,1,clusterCommand,2,CMD_STALE,0,CLUSTER_SLOTS_Keyspecs,0,NULL,0)}, {MAKE_CMD("slots","Returns the mapping of cluster slots to nodes.","O(N) where N is the total number of Cluster nodes","3.0.0",CMD_DOC_DEPRECATED,"`CLUSTER SHARDS`","7.0.0","cluster",COMMAND_GROUP_CLUSTER,CLUSTER_SLOTS_History,2,CLUSTER_SLOTS_Tips,1,clusterCommand,2,CMD_LOADING|CMD_STALE,0,CLUSTER_SLOTS_Keyspecs,0,NULL,0)},
{0} {0}
}; };
...@@ -5359,7 +5359,9 @@ struct COMMAND_ARG SENTINEL_CKQUORUM_Args[] = { ...@@ -5359,7 +5359,9 @@ struct COMMAND_ARG SENTINEL_CKQUORUM_Args[] = {
#ifndef SKIP_CMD_HISTORY_TABLE #ifndef SKIP_CMD_HISTORY_TABLE
/* SENTINEL CONFIG history */ /* SENTINEL CONFIG history */
#define SENTINEL_CONFIG_History NULL commandHistory SENTINEL_CONFIG_History[] = {
{"7.2.0","Added the ability to set and get multiple parameters in one call."},
};
#endif #endif
#ifndef SKIP_CMD_TIPS_TABLE #ifndef SKIP_CMD_TIPS_TABLE
...@@ -5380,8 +5382,8 @@ struct COMMAND_ARG SENTINEL_CONFIG_action_set_Subargs[] = { ...@@ -5380,8 +5382,8 @@ struct COMMAND_ARG SENTINEL_CONFIG_action_set_Subargs[] = {
/* SENTINEL CONFIG action argument table */ /* SENTINEL CONFIG action argument table */
struct COMMAND_ARG SENTINEL_CONFIG_action_Subargs[] = { struct COMMAND_ARG SENTINEL_CONFIG_action_Subargs[] = {
{MAKE_ARG("set",ARG_TYPE_BLOCK,-1,"SET",NULL,NULL,CMD_ARG_NONE,2,NULL),.subargs=SENTINEL_CONFIG_action_set_Subargs}, {MAKE_ARG("set",ARG_TYPE_BLOCK,-1,"SET",NULL,NULL,CMD_ARG_MULTIPLE,2,NULL),.subargs=SENTINEL_CONFIG_action_set_Subargs},
{MAKE_ARG("parameter",ARG_TYPE_STRING,-1,"GET",NULL,NULL,CMD_ARG_NONE,0,NULL)}, {MAKE_ARG("parameter",ARG_TYPE_STRING,-1,"GET",NULL,NULL,CMD_ARG_MULTIPLE,0,NULL)},
}; };
/* SENTINEL CONFIG argument table */ /* SENTINEL CONFIG argument table */
...@@ -5811,7 +5813,7 @@ struct COMMAND_ARG SENTINEL_SLAVES_Args[] = { ...@@ -5811,7 +5813,7 @@ struct COMMAND_ARG SENTINEL_SLAVES_Args[] = {
/* SENTINEL command table */ /* SENTINEL command table */
struct COMMAND_STRUCT SENTINEL_Subcommands[] = { struct COMMAND_STRUCT SENTINEL_Subcommands[] = {
{MAKE_CMD("ckquorum","Checks for a Redis Sentinel quorum.",NULL,"2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_CKQUORUM_History,0,SENTINEL_CKQUORUM_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_CKQUORUM_Keyspecs,0,NULL,1),.args=SENTINEL_CKQUORUM_Args}, {MAKE_CMD("ckquorum","Checks for a Redis Sentinel quorum.",NULL,"2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_CKQUORUM_History,0,SENTINEL_CKQUORUM_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_CKQUORUM_Keyspecs,0,NULL,1),.args=SENTINEL_CKQUORUM_Args},
{MAKE_CMD("config","Configures Redis Sentinel.","O(1)","6.2.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_CONFIG_History,0,SENTINEL_CONFIG_Tips,0,sentinelCommand,-4,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_CONFIG_Keyspecs,0,NULL,1),.args=SENTINEL_CONFIG_Args}, {MAKE_CMD("config","Configures Redis Sentinel.","O(N) when N is the number of configuration parameters provided","6.2.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_CONFIG_History,1,SENTINEL_CONFIG_Tips,0,sentinelCommand,-4,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_CONFIG_Keyspecs,0,NULL,1),.args=SENTINEL_CONFIG_Args},
{MAKE_CMD("debug","Lists or updates the current configurable parameters of Redis Sentinel.","O(N) where N is the number of configurable parameters","7.0.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_DEBUG_History,0,SENTINEL_DEBUG_Tips,0,sentinelCommand,-2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_DEBUG_Keyspecs,0,NULL,1),.args=SENTINEL_DEBUG_Args}, {MAKE_CMD("debug","Lists or updates the current configurable parameters of Redis Sentinel.","O(N) where N is the number of configurable parameters","7.0.0",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_DEBUG_History,0,SENTINEL_DEBUG_Tips,0,sentinelCommand,-2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_DEBUG_Keyspecs,0,NULL,1),.args=SENTINEL_DEBUG_Args},
{MAKE_CMD("failover","Forces a Redis Sentinel failover.",NULL,"2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_FAILOVER_History,0,SENTINEL_FAILOVER_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_FAILOVER_Keyspecs,0,NULL,1),.args=SENTINEL_FAILOVER_Args}, {MAKE_CMD("failover","Forces a Redis Sentinel failover.",NULL,"2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_FAILOVER_History,0,SENTINEL_FAILOVER_Tips,0,sentinelCommand,3,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_FAILOVER_Keyspecs,0,NULL,1),.args=SENTINEL_FAILOVER_Args},
{MAKE_CMD("flushconfig","Rewrites the Redis Sentinel configuration file.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_FLUSHCONFIG_History,0,SENTINEL_FLUSHCONFIG_Tips,0,sentinelCommand,2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_FLUSHCONFIG_Keyspecs,0,NULL,0)}, {MAKE_CMD("flushconfig","Rewrites the Redis Sentinel configuration file.","O(1)","2.8.4",CMD_DOC_NONE,NULL,NULL,"sentinel",COMMAND_GROUP_SENTINEL,SENTINEL_FLUSHCONFIG_History,0,SENTINEL_FLUSHCONFIG_Tips,0,sentinelCommand,2,CMD_ADMIN|CMD_SENTINEL|CMD_ONLY_SENTINEL,0,SENTINEL_FLUSHCONFIG_Keyspecs,0,NULL,0)},
...@@ -10690,15 +10692,15 @@ struct COMMAND_STRUCT redisCommandTable[] = { ...@@ -10690,15 +10692,15 @@ struct COMMAND_STRUCT redisCommandTable[] = {
{MAKE_CMD("rpush","Appends one or more elements to a list. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSH_History,1,RPUSH_Tips,0,rpushCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSH_Keyspecs,1,NULL,2),.args=RPUSH_Args}, {MAKE_CMD("rpush","Appends one or more elements to a list. Creates the key if it doesn't exist.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","1.0.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSH_History,1,RPUSH_Tips,0,rpushCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSH_Keyspecs,1,NULL,2),.args=RPUSH_Args},
{MAKE_CMD("rpushx","Appends an element to a list only when the list exists.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSHX_History,1,RPUSHX_Tips,0,rpushxCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSHX_Keyspecs,1,NULL,2),.args=RPUSHX_Args}, {MAKE_CMD("rpushx","Appends an element to a list only when the list exists.","O(1) for each element added, so O(N) to add N elements when the command is called with multiple arguments.","2.2.0",CMD_DOC_NONE,NULL,NULL,"list",COMMAND_GROUP_LIST,RPUSHX_History,1,RPUSHX_Tips,0,rpushxCommand,-3,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_LIST,RPUSHX_Keyspecs,1,NULL,2),.args=RPUSHX_Args},
/* pubsub */ /* pubsub */
{MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns the client is already subscribed to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args}, {MAKE_CMD("psubscribe","Listens for messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PSUBSCRIBE_History,0,PSUBSCRIBE_Tips,0,psubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PSUBSCRIBE_Keyspecs,0,NULL,1),.args=PSUBSCRIBE_Args},
{MAKE_CMD("publish","Posts a message to a channel.","O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBLISH_History,0,PUBLISH_Tips,0,publishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE|CMD_SENTINEL,0,PUBLISH_Keyspecs,0,NULL,2),.args=PUBLISH_Args}, {MAKE_CMD("publish","Posts a message to a channel.","O(N+M) where N is the number of clients subscribed to the receiving channel and M is the total number of subscribed patterns (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBLISH_History,0,PUBLISH_Tips,0,publishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE|CMD_SENTINEL,0,PUBLISH_Keyspecs,0,NULL,2),.args=PUBLISH_Args},
{MAKE_CMD("pubsub","A container for Pub/Sub commands.","Depends on subcommand.","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_History,0,PUBSUB_Tips,0,NULL,-2,0,0,PUBSUB_Keyspecs,0,NULL,0),.subcommands=PUBSUB_Subcommands}, {MAKE_CMD("pubsub","A container for Pub/Sub commands.","Depends on subcommand.","2.8.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUBSUB_History,0,PUBSUB_Tips,0,NULL,-2,0,0,PUBSUB_Keyspecs,0,NULL,0),.subcommands=PUBSUB_Subcommands},
{MAKE_CMD("punsubscribe","Stops listening to messages published to channels that match one or more patterns.","O(N+M) where N is the number of patterns the client is already subscribed and M is the number of total patterns subscribed in the system (by any client).","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUNSUBSCRIBE_History,0,PUNSUBSCRIBE_Tips,0,punsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PUNSUBSCRIBE_Keyspecs,0,NULL,1),.args=PUNSUBSCRIBE_Args}, {MAKE_CMD("punsubscribe","Stops listening to messages published to channels that match one or more patterns.","O(N) where N is the number of patterns to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,PUNSUBSCRIBE_History,0,PUNSUBSCRIBE_Tips,0,punsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,PUNSUBSCRIBE_Keyspecs,0,NULL,1),.args=PUNSUBSCRIBE_Args},
{MAKE_CMD("spublish","Post a message to a shard channel","O(N) where N is the number of clients subscribed to the receiving shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SPUBLISH_History,0,SPUBLISH_Tips,0,spublishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE,0,SPUBLISH_Keyspecs,1,NULL,2),.args=SPUBLISH_Args}, {MAKE_CMD("spublish","Post a message to a shard channel","O(N) where N is the number of clients subscribed to the receiving shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SPUBLISH_History,0,SPUBLISH_Tips,0,spublishCommand,3,CMD_PUBSUB|CMD_LOADING|CMD_STALE|CMD_FAST|CMD_MAY_REPLICATE,0,SPUBLISH_Keyspecs,1,NULL,2),.args=SPUBLISH_Args},
{MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args}, {MAKE_CMD("ssubscribe","Listens for messages published to shard channels.","O(N) where N is the number of shard channels to subscribe to.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SSUBSCRIBE_History,0,SSUBSCRIBE_Tips,0,ssubscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SSUBSCRIBE_Keyspecs,1,NULL,1),.args=SSUBSCRIBE_Args},
{MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args}, {MAKE_CMD("subscribe","Listens for messages published to channels.","O(N) where N is the number of channels to subscribe to.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUBSCRIBE_History,0,SUBSCRIBE_Tips,0,subscribeCommand,-2,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,SUBSCRIBE_Keyspecs,0,NULL,1),.args=SUBSCRIBE_Args},
{MAKE_CMD("sunsubscribe","Stops listening to messages posted to shard channels.","O(N) where N is the number of clients already subscribed to a shard channel.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUNSUBSCRIBE_History,0,SUNSUBSCRIBE_Tips,0,sunsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SUNSUBSCRIBE_Keyspecs,1,NULL,1),.args=SUNSUBSCRIBE_Args}, {MAKE_CMD("sunsubscribe","Stops listening to messages posted to shard channels.","O(N) where N is the number of shard channels to unsubscribe.","7.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,SUNSUBSCRIBE_History,0,SUNSUBSCRIBE_Tips,0,sunsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE,0,SUNSUBSCRIBE_Keyspecs,1,NULL,1),.args=SUNSUBSCRIBE_Args},
{MAKE_CMD("unsubscribe","Stops listening to messages posted to channels.","O(N) where N is the number of clients already subscribed to a channel.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,UNSUBSCRIBE_History,0,UNSUBSCRIBE_Tips,0,unsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,UNSUBSCRIBE_Keyspecs,0,NULL,1),.args=UNSUBSCRIBE_Args}, {MAKE_CMD("unsubscribe","Stops listening to messages posted to channels.","O(N) where N is the number of channels to unsubscribe.","2.0.0",CMD_DOC_NONE,NULL,NULL,"pubsub",COMMAND_GROUP_PUBSUB,UNSUBSCRIBE_History,0,UNSUBSCRIBE_Tips,0,unsubscribeCommand,-1,CMD_PUBSUB|CMD_NOSCRIPT|CMD_LOADING|CMD_STALE|CMD_SENTINEL,0,UNSUBSCRIBE_Keyspecs,0,NULL,1),.args=UNSUBSCRIBE_Args},
/* scripting */ /* scripting */
{MAKE_CMD("eval","Executes a server-side Lua script.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVAL_History,0,EVAL_Tips,0,evalCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVAL_Keyspecs,1,evalGetKeys,4),.args=EVAL_Args}, {MAKE_CMD("eval","Executes a server-side Lua script.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVAL_History,0,EVAL_Tips,0,evalCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVAL_Keyspecs,1,evalGetKeys,4),.args=EVAL_Args},
{MAKE_CMD("evalsha","Executes a server-side Lua script by SHA1 digest.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVALSHA_History,0,EVALSHA_Tips,0,evalShaCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVALSHA_Keyspecs,1,evalGetKeys,4),.args=EVALSHA_Args}, {MAKE_CMD("evalsha","Executes a server-side Lua script by SHA1 digest.","Depends on the script that is executed.","2.6.0",CMD_DOC_NONE,NULL,NULL,"scripting",COMMAND_GROUP_SCRIPTING,EVALSHA_History,0,EVALSHA_Tips,0,evalShaCommand,-3,CMD_NOSCRIPT|CMD_SKIP_MONITOR|CMD_MAY_REPLICATE|CMD_NO_MANDATORY_KEYS|CMD_STALE,ACL_CATEGORY_SCRIPTING,EVALSHA_Keyspecs,1,evalGetKeys,4),.args=EVALSHA_Args},
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
"function": "clusterCommand", "function": "clusterCommand",
"history": [], "history": [],
"command_flags": [ "command_flags": [
"LOADING",
"STALE" "STALE"
], ],
"command_tips": [ "command_tips": [
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment