Unverified Commit 3f14bfd8 authored by Loris Cro's avatar Loris Cro Committed by GitHub
Browse files

Merge pull request #1 from antirez/unstable

update to latest unstable
parents 8ea4bdd9 720d1fd3
...@@ -94,6 +94,9 @@ void ACLResetSubcommandsForCommand(user *u, unsigned long id); ...@@ -94,6 +94,9 @@ void ACLResetSubcommandsForCommand(user *u, unsigned long id);
void ACLResetSubcommands(user *u); void ACLResetSubcommands(user *u);
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub); void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
/* The length of the string representation of a hashed password. */
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
/* ============================================================================= /* =============================================================================
* Helper functions for the rest of the ACL implementation * Helper functions for the rest of the ACL implementation
* ==========================================================================*/ * ==========================================================================*/
...@@ -145,7 +148,7 @@ int time_independent_strcmp(char *a, char *b) { ...@@ -145,7 +148,7 @@ int time_independent_strcmp(char *a, char *b) {
sds ACLHashPassword(unsigned char *cleartext, size_t len) { sds ACLHashPassword(unsigned char *cleartext, size_t len) {
SHA256_CTX ctx; SHA256_CTX ctx;
unsigned char hash[SHA256_BLOCK_SIZE]; unsigned char hash[SHA256_BLOCK_SIZE];
char hex[SHA256_BLOCK_SIZE*2]; char hex[HASH_PASSWORD_LEN];
char *cset = "0123456789abcdef"; char *cset = "0123456789abcdef";
sha256_init(&ctx); sha256_init(&ctx);
...@@ -156,7 +159,7 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) { ...@@ -156,7 +159,7 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) {
hex[j*2] = cset[((hash[j]&0xF0)>>4)]; hex[j*2] = cset[((hash[j]&0xF0)>>4)];
hex[j*2+1] = cset[(hash[j]&0xF)]; hex[j*2+1] = cset[(hash[j]&0xF)];
} }
return sdsnewlen(hex,SHA256_BLOCK_SIZE*2); return sdsnewlen(hex,HASH_PASSWORD_LEN);
} }
/* ============================================================================= /* =============================================================================
...@@ -522,7 +525,7 @@ sds ACLDescribeUser(user *u) { ...@@ -522,7 +525,7 @@ sds ACLDescribeUser(user *u) {
listRewind(u->passwords,&li); listRewind(u->passwords,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
sds thispass = listNodeValue(ln); sds thispass = listNodeValue(ln);
res = sdscatlen(res,">",1); res = sdscatlen(res,"#",1);
res = sdscatsds(res,thispass); res = sdscatsds(res,thispass);
res = sdscatlen(res," ",1); res = sdscatlen(res," ",1);
} }
...@@ -649,7 +652,14 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) { ...@@ -649,7 +652,14 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
* ><password> Add this password to the list of valid password for the user. * ><password> Add this password to the list of valid password for the user.
* For example >mypass will add "mypass" to the list. * For example >mypass will add "mypass" to the list.
* This directive clears the "nopass" flag (see later). * This directive clears the "nopass" flag (see later).
* #<hash> Add this password hash to the list of valid hashes for
* the user. This is useful if you have previously computed
* the hash, and don't want to store it in plaintext.
* This directive clears the "nopass" flag (see later).
* <<password> Remove this password from the list of valid passwords. * <<password> Remove this password from the list of valid passwords.
* !<hash> Remove this hashed password from the list of valid passwords.
* This is useful when you want to remove a password just by
* hash without knowing its plaintext version at all.
* nopass All the set passwords of the user are removed, and the user * nopass All the set passwords of the user are removed, and the user
* is flagged as requiring no password: it means that every * is flagged as requiring no password: it means that every
* password will work against this user. If this directive is * password will work against this user. If this directive is
...@@ -685,6 +695,7 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) { ...@@ -685,6 +695,7 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
* EEXIST: You are adding a key pattern after "*" was already added. This is * EEXIST: You are adding a key pattern after "*" was already added. This is
* almost surely an error on the user side. * almost surely an error on the user side.
* ENODEV: The password you are trying to remove from the user does not exist. * ENODEV: The password you are trying to remove from the user does not exist.
* EBADMSG: The hash you are trying to add is not a valid hash.
*/ */
int ACLSetUser(user *u, const char *op, ssize_t oplen) { int ACLSetUser(user *u, const char *op, ssize_t oplen) {
if (oplen == -1) oplen = strlen(op); if (oplen == -1) oplen = strlen(op);
...@@ -720,8 +731,30 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { ...@@ -720,8 +731,30 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
} else if (!strcasecmp(op,"resetpass")) { } else if (!strcasecmp(op,"resetpass")) {
u->flags &= ~USER_FLAG_NOPASS; u->flags &= ~USER_FLAG_NOPASS;
listEmpty(u->passwords); listEmpty(u->passwords);
} else if (op[0] == '>') { } else if (op[0] == '>' || op[0] == '#') {
sds newpass = ACLHashPassword((unsigned char*)op+1,oplen-1); sds newpass;
if (op[0] == '>') {
newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
} else {
if (oplen != HASH_PASSWORD_LEN + 1) {
errno = EBADMSG;
return C_ERR;
}
/* Password hashes can only be characters that represent
* hexadecimal values, which are numbers and lowercase
* characters 'a' through 'f'.
*/
for(int i = 1; i < HASH_PASSWORD_LEN + 1; i++) {
char c = op[i];
if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
errno = EBADMSG;
return C_ERR;
}
}
newpass = sdsnewlen(op+1,oplen-1);
}
listNode *ln = listSearchKey(u->passwords,newpass); listNode *ln = listSearchKey(u->passwords,newpass);
/* Avoid re-adding the same password multiple times. */ /* Avoid re-adding the same password multiple times. */
if (ln == NULL) if (ln == NULL)
...@@ -729,8 +762,17 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) { ...@@ -729,8 +762,17 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
else else
sdsfree(newpass); sdsfree(newpass);
u->flags &= ~USER_FLAG_NOPASS; u->flags &= ~USER_FLAG_NOPASS;
} else if (op[0] == '<') { } else if (op[0] == '<' || op[0] == '!') {
sds delpass = ACLHashPassword((unsigned char*)op+1,oplen-1); sds delpass;
if (op[0] == '<') {
delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
} else {
if (oplen != HASH_PASSWORD_LEN + 1) {
errno = EBADMSG;
return C_ERR;
}
delpass = sdsnewlen(op+1,oplen-1);
}
listNode *ln = listSearchKey(u->passwords,delpass); listNode *ln = listSearchKey(u->passwords,delpass);
sdsfree(delpass); sdsfree(delpass);
if (ln) { if (ln) {
...@@ -848,6 +890,9 @@ char *ACLSetUserStringError(void) { ...@@ -848,6 +890,9 @@ char *ACLSetUserStringError(void) {
else if (errno == ENODEV) else if (errno == ENODEV)
errmsg = "The password you are trying to remove from the user does " errmsg = "The password you are trying to remove from the user does "
"not exist"; "not exist";
else if (errno == EBADMSG)
errmsg = "The password hash must be exactly 64 characters and contain "
"only lowercase hexadecimal characters";
return errmsg; return errmsg;
} }
......
...@@ -76,6 +76,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) { ...@@ -76,6 +76,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
eventLoop->maxfd = -1; eventLoop->maxfd = -1;
eventLoop->beforesleep = NULL; eventLoop->beforesleep = NULL;
eventLoop->aftersleep = NULL; eventLoop->aftersleep = NULL;
eventLoop->flags = 0;
if (aeApiCreate(eventLoop) == -1) goto err; if (aeApiCreate(eventLoop) == -1) goto err;
/* Events with mask == AE_NONE are not set. So let's initialize the /* Events with mask == AE_NONE are not set. So let's initialize the
* vector with it. */ * vector with it. */
...@@ -97,6 +98,14 @@ int aeGetSetSize(aeEventLoop *eventLoop) { ...@@ -97,6 +98,14 @@ int aeGetSetSize(aeEventLoop *eventLoop) {
return eventLoop->setsize; return eventLoop->setsize;
} }
/* Tells the next iteration/s of the event processing to set timeout of 0. */
void aeSetDontWait(aeEventLoop *eventLoop, int noWait) {
if (noWait)
eventLoop->flags |= AE_DONT_WAIT;
else
eventLoop->flags &= ~AE_DONT_WAIT;
}
/* Resize the maximum set size of the event loop. /* Resize the maximum set size of the event loop.
* If the requested set size is smaller than the current set size, but * If the requested set size is smaller than the current set size, but
* there is already a file descriptor in use that is >= the requested * there is already a file descriptor in use that is >= the requested
...@@ -406,6 +415,11 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags) ...@@ -406,6 +415,11 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
} }
} }
if (eventLoop->flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
}
/* Call the multiplexing API, will return only on timeout or when /* Call the multiplexing API, will return only on timeout or when
* some event fires. */ * some event fires. */
numevents = aeApiPoll(eventLoop, tvp); numevents = aeApiPoll(eventLoop, tvp);
......
...@@ -106,6 +106,7 @@ typedef struct aeEventLoop { ...@@ -106,6 +106,7 @@ typedef struct aeEventLoop {
void *apidata; /* This is used for polling API specific data */ void *apidata; /* This is used for polling API specific data */
aeBeforeSleepProc *beforesleep; aeBeforeSleepProc *beforesleep;
aeBeforeSleepProc *aftersleep; aeBeforeSleepProc *aftersleep;
int flags;
} aeEventLoop; } aeEventLoop;
/* Prototypes */ /* Prototypes */
...@@ -128,5 +129,6 @@ void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep ...@@ -128,5 +129,6 @@ void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep); void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep);
int aeGetSetSize(aeEventLoop *eventLoop); int aeGetSetSize(aeEventLoop *eventLoop);
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize); int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
void aeSetDontWait(aeEventLoop *eventLoop, int noWait);
#endif #endif
...@@ -121,8 +121,8 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { ...@@ -121,8 +121,8 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
if (e->events & EPOLLIN) mask |= AE_READABLE; if (e->events & EPOLLIN) mask |= AE_READABLE;
if (e->events & EPOLLOUT) mask |= AE_WRITABLE; if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
if (e->events & EPOLLERR) mask |= AE_WRITABLE; if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
if (e->events & EPOLLHUP) mask |= AE_WRITABLE; if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
eventLoop->fired[j].fd = e->data.fd; eventLoop->fired[j].fd = e->data.fd;
eventLoop->fired[j].mask = mask; eventLoop->fired[j].mask = mask;
} }
......
...@@ -279,8 +279,8 @@ static int anetCreateSocket(char *err, int domain) { ...@@ -279,8 +279,8 @@ static int anetCreateSocket(char *err, int domain) {
#define ANET_CONNECT_NONE 0 #define ANET_CONNECT_NONE 0
#define ANET_CONNECT_NONBLOCK 1 #define ANET_CONNECT_NONBLOCK 1
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */ #define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
static int anetTcpGenericConnect(char *err, char *addr, int port, static int anetTcpGenericConnect(char *err, const char *addr, int port,
char *source_addr, int flags) const char *source_addr, int flags)
{ {
int s = ANET_ERR, rv; int s = ANET_ERR, rv;
char portstr[6]; /* strlen("65535") + 1; */ char portstr[6]; /* strlen("65535") + 1; */
...@@ -359,31 +359,31 @@ end: ...@@ -359,31 +359,31 @@ end:
} }
} }
int anetTcpConnect(char *err, char *addr, int port) int anetTcpConnect(char *err, const char *addr, int port)
{ {
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE); return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
} }
int anetTcpNonBlockConnect(char *err, char *addr, int port) int anetTcpNonBlockConnect(char *err, const char *addr, int port)
{ {
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK); return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
} }
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
char *source_addr) const char *source_addr)
{ {
return anetTcpGenericConnect(err,addr,port,source_addr, return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK); ANET_CONNECT_NONBLOCK);
} }
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
char *source_addr) const char *source_addr)
{ {
return anetTcpGenericConnect(err,addr,port,source_addr, return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING); ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING);
} }
int anetUnixGenericConnect(char *err, char *path, int flags) int anetUnixGenericConnect(char *err, const char *path, int flags)
{ {
int s; int s;
struct sockaddr_un sa; struct sockaddr_un sa;
...@@ -411,12 +411,12 @@ int anetUnixGenericConnect(char *err, char *path, int flags) ...@@ -411,12 +411,12 @@ int anetUnixGenericConnect(char *err, char *path, int flags)
return s; return s;
} }
int anetUnixConnect(char *err, char *path) int anetUnixConnect(char *err, const char *path)
{ {
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE); return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
} }
int anetUnixNonBlockConnect(char *err, char *path) int anetUnixNonBlockConnect(char *err, const char *path)
{ {
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK); return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
} }
......
...@@ -49,12 +49,12 @@ ...@@ -49,12 +49,12 @@
#undef ip_len #undef ip_len
#endif #endif
int anetTcpConnect(char *err, char *addr, int port); int anetTcpConnect(char *err, const char *addr, int port);
int anetTcpNonBlockConnect(char *err, char *addr, int port); int anetTcpNonBlockConnect(char *err, const char *addr, int port);
int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr); int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, char *source_addr); int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
int anetUnixConnect(char *err, char *path); int anetUnixConnect(char *err, const char *path);
int anetUnixNonBlockConnect(char *err, char *path); int anetUnixNonBlockConnect(char *err, const char *path);
int anetRead(int fd, char *buf, int count); int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len); int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len); int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
......
...@@ -264,9 +264,9 @@ int startAppendOnly(void) { ...@@ -264,9 +264,9 @@ int startAppendOnly(void) {
strerror(errno)); strerror(errno));
return C_ERR; return C_ERR;
} }
if (server.rdb_child_pid != -1) { if (hasActiveChildProcess() && server.aof_child_pid == -1) {
server.aof_rewrite_scheduled = 1; server.aof_rewrite_scheduled = 1;
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible."); serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
} else { } else {
/* If there is a pending AOF rewrite, we need to switch it off and /* If there is a pending AOF rewrite, we need to switch it off and
* start a new one: the old one cannot be reused because it is not * start a new one: the old one cannot be reused because it is not
...@@ -385,6 +385,10 @@ void flushAppendOnlyFile(int force) { ...@@ -385,6 +385,10 @@ void flushAppendOnlyFile(int force) {
* there is much to do about the whole server stopping for power problems * there is much to do about the whole server stopping for power problems
* or alike */ * or alike */
if (server.aof_flush_sleep && sdslen(server.aof_buf)) {
usleep(server.aof_flush_sleep);
}
latencyStartMonitor(latency); latencyStartMonitor(latency);
nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf)); nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
latencyEndMonitor(latency); latencyEndMonitor(latency);
...@@ -395,7 +399,7 @@ void flushAppendOnlyFile(int force) { ...@@ -395,7 +399,7 @@ void flushAppendOnlyFile(int force) {
* useful for graphing / monitoring purposes. */ * useful for graphing / monitoring purposes. */
if (sync_in_progress) { if (sync_in_progress) {
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency); latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
} else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) { } else if (hasActiveChildProcess()) {
latencyAddSampleIfNeeded("aof-write-active-child",latency); latencyAddSampleIfNeeded("aof-write-active-child",latency);
} else { } else {
latencyAddSampleIfNeeded("aof-write-alone",latency); latencyAddSampleIfNeeded("aof-write-alone",latency);
...@@ -491,8 +495,7 @@ void flushAppendOnlyFile(int force) { ...@@ -491,8 +495,7 @@ void flushAppendOnlyFile(int force) {
try_fsync: try_fsync:
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
* children doing I/O in the background. */ * children doing I/O in the background. */
if (server.aof_no_fsync_on_rewrite && if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess())
(server.aof_child_pid != -1 || server.rdb_child_pid != -1))
return; return;
/* Perform the fsync if needed. */ /* Perform the fsync if needed. */
...@@ -649,11 +652,12 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a ...@@ -649,11 +652,12 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
/* In Redis commands are always executed in the context of a client, so in /* In Redis commands are always executed in the context of a client, so in
* order to load the append only file we need to create a fake client. */ * order to load the append only file we need to create a fake client. */
struct client *createFakeClient(void) { struct client *createAOFClient(void) {
struct client *c = zmalloc(sizeof(*c)); struct client *c = zmalloc(sizeof(*c));
selectDb(c,0); selectDb(c,0);
c->fd = -1; c->id = CLIENT_ID_AOF; /* So modules can identify it's the AOF client. */
c->conn = NULL;
c->name = NULL; c->name = NULL;
c->querybuf = sdsempty(); c->querybuf = sdsempty();
c->querybuf_peak = 0; c->querybuf_peak = 0;
...@@ -726,8 +730,8 @@ int loadAppendOnlyFile(char *filename) { ...@@ -726,8 +730,8 @@ int loadAppendOnlyFile(char *filename) {
* to the same file we're about to read. */ * to the same file we're about to read. */
server.aof_state = AOF_OFF; server.aof_state = AOF_OFF;
fakeClient = createFakeClient(); fakeClient = createAOFClient();
startLoadingFile(fp, filename); startLoadingFile(fp, filename, RDBFLAGS_AOF_PREAMBLE);
/* Check if this AOF file has an RDB preamble. In that case we need to /* Check if this AOF file has an RDB preamble. In that case we need to
* load the RDB file and later continue loading the AOF tail. */ * load the RDB file and later continue loading the AOF tail. */
...@@ -742,7 +746,7 @@ int loadAppendOnlyFile(char *filename) { ...@@ -742,7 +746,7 @@ int loadAppendOnlyFile(char *filename) {
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file..."); serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
if (fseek(fp,0,SEEK_SET) == -1) goto readerr; if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
rioInitWithFile(&rdb,fp); rioInitWithFile(&rdb,fp);
if (rdbLoadRio(&rdb,NULL,1) != C_OK) { if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) {
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted"); serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
goto readerr; goto readerr;
} else { } else {
...@@ -763,6 +767,7 @@ int loadAppendOnlyFile(char *filename) { ...@@ -763,6 +767,7 @@ int loadAppendOnlyFile(char *filename) {
if (!(loops++ % 1000)) { if (!(loops++ % 1000)) {
loadingProgress(ftello(fp)); loadingProgress(ftello(fp));
processEventsWhileBlocked(); processEventsWhileBlocked();
processModuleLoadingProgressEvent(1);
} }
if (fgets(buf,sizeof(buf),fp) == NULL) { if (fgets(buf,sizeof(buf),fp) == NULL) {
...@@ -836,6 +841,8 @@ int loadAppendOnlyFile(char *filename) { ...@@ -836,6 +841,8 @@ int loadAppendOnlyFile(char *filename) {
freeFakeClientArgv(fakeClient); freeFakeClientArgv(fakeClient);
fakeClient->cmd = NULL; fakeClient->cmd = NULL;
if (server.aof_load_truncated) valid_up_to = ftello(fp); if (server.aof_load_truncated) valid_up_to = ftello(fp);
if (server.key_load_delay)
usleep(server.key_load_delay);
} }
/* This point can only be reached when EOF is reached without errors. /* This point can only be reached when EOF is reached without errors.
...@@ -853,7 +860,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */ ...@@ -853,7 +860,7 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
fclose(fp); fclose(fp);
freeFakeClient(fakeClient); freeFakeClient(fakeClient);
server.aof_state = old_aof_state; server.aof_state = old_aof_state;
stopLoading(); stopLoading(1);
aofUpdateCurrentSize(); aofUpdateCurrentSize();
server.aof_rewrite_base_size = server.aof_current_size; server.aof_rewrite_base_size = server.aof_current_size;
server.aof_fsync_offset = server.aof_current_size; server.aof_fsync_offset = server.aof_current_size;
...@@ -1394,9 +1401,11 @@ int rewriteAppendOnlyFile(char *filename) { ...@@ -1394,9 +1401,11 @@ int rewriteAppendOnlyFile(char *filename) {
if (server.aof_rewrite_incremental_fsync) if (server.aof_rewrite_incremental_fsync)
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES); rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
startSaving(RDBFLAGS_AOF_PREAMBLE);
if (server.aof_use_rdb_preamble) { if (server.aof_use_rdb_preamble) {
int error; int error;
if (rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) { if (rdbSaveRio(&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) {
errno = error; errno = error;
goto werr; goto werr;
} }
...@@ -1459,15 +1468,18 @@ int rewriteAppendOnlyFile(char *filename) { ...@@ -1459,15 +1468,18 @@ int rewriteAppendOnlyFile(char *filename) {
if (rename(tmpfile,filename) == -1) { if (rename(tmpfile,filename) == -1) {
serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno)); serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
unlink(tmpfile); unlink(tmpfile);
stopSaving(0);
return C_ERR; return C_ERR;
} }
serverLog(LL_NOTICE,"SYNC append only file rewrite performed"); serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
stopSaving(1);
return C_OK; return C_OK;
werr: werr:
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno)); serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
fclose(fp); fclose(fp);
unlink(tmpfile); unlink(tmpfile);
stopSaving(0);
return C_ERR; return C_ERR;
} }
...@@ -1563,39 +1575,24 @@ void aofClosePipes(void) { ...@@ -1563,39 +1575,24 @@ void aofClosePipes(void) {
*/ */
int rewriteAppendOnlyFileBackground(void) { int rewriteAppendOnlyFileBackground(void) {
pid_t childpid; pid_t childpid;
long long start;
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR; if (hasActiveChildProcess()) return C_ERR;
if (aofCreatePipes() != C_OK) return C_ERR; if (aofCreatePipes() != C_OK) return C_ERR;
openChildInfoPipe(); openChildInfoPipe();
start = ustime(); if ((childpid = redisFork()) == 0) {
if ((childpid = fork()) == 0) {
char tmpfile[256]; char tmpfile[256];
/* Child */ /* Child */
closeListeningSockets(0);
redisSetProcTitle("redis-aof-rewrite"); redisSetProcTitle("redis-aof-rewrite");
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
if (rewriteAppendOnlyFile(tmpfile) == C_OK) { if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
size_t private_dirty = zmalloc_get_private_dirty(-1); sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
if (private_dirty) {
serverLog(LL_NOTICE,
"AOF rewrite: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
server.child_info_data.cow_size = private_dirty;
sendChildInfo(CHILD_INFO_TYPE_AOF);
exitFromChild(0); exitFromChild(0);
} else { } else {
exitFromChild(1); exitFromChild(1);
} }
} else { } else {
/* Parent */ /* Parent */
server.stat_fork_time = ustime()-start;
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
if (childpid == -1) { if (childpid == -1) {
closeChildInfoPipe(); closeChildInfoPipe();
serverLog(LL_WARNING, serverLog(LL_WARNING,
...@@ -1609,7 +1606,6 @@ int rewriteAppendOnlyFileBackground(void) { ...@@ -1609,7 +1606,6 @@ int rewriteAppendOnlyFileBackground(void) {
server.aof_rewrite_scheduled = 0; server.aof_rewrite_scheduled = 0;
server.aof_rewrite_time_start = time(NULL); server.aof_rewrite_time_start = time(NULL);
server.aof_child_pid = childpid; server.aof_child_pid = childpid;
updateDictResizePolicy();
/* We set appendseldb to -1 in order to force the next call to the /* We set appendseldb to -1 in order to force the next call to the
* feedAppendOnlyFile() to issue a SELECT command, so the differences * feedAppendOnlyFile() to issue a SELECT command, so the differences
* accumulated by the parent into server.aof_rewrite_buf will start * accumulated by the parent into server.aof_rewrite_buf will start
...@@ -1624,13 +1620,14 @@ int rewriteAppendOnlyFileBackground(void) { ...@@ -1624,13 +1620,14 @@ int rewriteAppendOnlyFileBackground(void) {
void bgrewriteaofCommand(client *c) { void bgrewriteaofCommand(client *c) {
if (server.aof_child_pid != -1) { if (server.aof_child_pid != -1) {
addReplyError(c,"Background append only file rewriting already in progress"); addReplyError(c,"Background append only file rewriting already in progress");
} else if (server.rdb_child_pid != -1) { } else if (hasActiveChildProcess()) {
server.aof_rewrite_scheduled = 1; server.aof_rewrite_scheduled = 1;
addReplyStatus(c,"Background append only file rewriting scheduled"); addReplyStatus(c,"Background append only file rewriting scheduled");
} else if (rewriteAppendOnlyFileBackground() == C_OK) { } else if (rewriteAppendOnlyFileBackground() == C_OK) {
addReplyStatus(c,"Background append only file rewriting started"); addReplyStatus(c,"Background append only file rewriting started");
} else { } else {
addReply(c,shared.err); addReplyError(c,"Can't execute an AOF background rewriting. "
"Please check the server logs for more information.");
} }
} }
......
...@@ -174,6 +174,7 @@ void unblockClient(client *c) { ...@@ -174,6 +174,7 @@ void unblockClient(client *c) {
} else if (c->btype == BLOCKED_WAIT) { } else if (c->btype == BLOCKED_WAIT) {
unblockClientWaitingReplicas(c); unblockClientWaitingReplicas(c);
} else if (c->btype == BLOCKED_MODULE) { } else if (c->btype == BLOCKED_MODULE) {
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
unblockClientFromModule(c); unblockClientFromModule(c);
} else { } else {
serverPanic("Unknown btype in unblockClient()."); serverPanic("Unknown btype in unblockClient().");
...@@ -430,6 +431,49 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) { ...@@ -430,6 +431,49 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
} }
} }
/* Helper function for handleClientsBlockedOnKeys(). This function is called
* in order to check if we can serve clients blocked by modules using
* RM_BlockClientOnKeys(), when the corresponding key was signaled as ready:
* our goal here is to call the RedisModuleBlockedClient reply() callback to
* see if the key is really able to serve the client, and in that case,
* unblock it. */
void serveClientsBlockedOnKeyByModule(readyList *rl) {
dictEntry *de;
/* We serve clients in the same order they blocked for
* this key, from the first blocked to the last. */
de = dictFind(rl->db->blocking_keys,rl->key);
if (de) {
list *clients = dictGetVal(de);
int numclients = listLength(clients);
while(numclients--) {
listNode *clientnode = listFirst(clients);
client *receiver = clientnode->value;
/* Put at the tail, so that at the next call
* we'll not run into it again: clients here may not be
* ready to be served, so they'll remain in the list
* sometimes. We want also be able to skip clients that are
* not blocked for the MODULE type safely. */
listDelNode(clients,clientnode);
listAddNodeTail(clients,receiver);
if (receiver->btype != BLOCKED_MODULE) continue;
/* Note that if *this* client cannot be served by this key,
* it does not mean that another client that is next into the
* list cannot be served as well: they may be blocked by
* different modules with different triggers to consider if a key
* is ready or not. This means we can't exit the loop but need
* to continue after the first failure. */
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
moduleUnblockClient(receiver);
}
}
}
/* This function should be called by Redis every time a single command, /* This function should be called by Redis every time a single command,
* a MULTI/EXEC block, or a Lua script, terminated its execution after * a MULTI/EXEC block, or a Lua script, terminated its execution after
* being called by a client. It handles serving clients blocked in * being called by a client. It handles serving clients blocked in
...@@ -480,6 +524,10 @@ void handleClientsBlockedOnKeys(void) { ...@@ -480,6 +524,10 @@ void handleClientsBlockedOnKeys(void) {
serveClientsBlockedOnSortedSetKey(o,rl); serveClientsBlockedOnSortedSetKey(o,rl);
else if (o->type == OBJ_STREAM) else if (o->type == OBJ_STREAM)
serveClientsBlockedOnStreamKey(o,rl); serveClientsBlockedOnStreamKey(o,rl);
/* We want to serve clients blocked on module keys
* regardless of the object type: we don't know what the
* module is trying to accomplish right now. */
serveClientsBlockedOnKeyByModule(rl);
} }
/* Free this item. */ /* Free this item. */
......
...@@ -80,6 +80,8 @@ void receiveChildInfo(void) { ...@@ -80,6 +80,8 @@ void receiveChildInfo(void) {
server.stat_rdb_cow_bytes = server.child_info_data.cow_size; server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) { } else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
server.stat_aof_cow_bytes = server.child_info_data.cow_size; server.stat_aof_cow_bytes = server.child_info_data.cow_size;
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_MODULE) {
server.stat_module_cow_bytes = server.child_info_data.cow_size;
} }
} }
} }
...@@ -49,7 +49,7 @@ clusterNode *myself = NULL; ...@@ -49,7 +49,7 @@ clusterNode *myself = NULL;
clusterNode *createClusterNode(char *nodename, int flags); clusterNode *createClusterNode(char *nodename, int flags);
int clusterAddNode(clusterNode *node); int clusterAddNode(clusterNode *node);
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask); void clusterReadHandler(connection *conn);
void clusterSendPing(clusterLink *link, int type); void clusterSendPing(clusterLink *link, int type);
void clusterSendFail(char *nodename); void clusterSendFail(char *nodename);
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request); void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
...@@ -477,7 +477,8 @@ void clusterInit(void) { ...@@ -477,7 +477,8 @@ 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. */
if (server.port > (65535-CLUSTER_PORT_INCR)) { int port = server.tls_cluster ? server.tls_port : server.port;
if (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 "
"numbers higher than your Redis port. " "numbers higher than your Redis port. "
...@@ -485,8 +486,7 @@ void clusterInit(void) { ...@@ -485,8 +486,7 @@ void clusterInit(void) {
"lower than 55535."); "lower than 55535.");
exit(1); exit(1);
} }
if (listenToPort(port+CLUSTER_PORT_INCR,
if (listenToPort(server.port+CLUSTER_PORT_INCR,
server.cfd,&server.cfd_count) == C_ERR) server.cfd,&server.cfd_count) == C_ERR)
{ {
exit(1); exit(1);
...@@ -508,8 +508,8 @@ void clusterInit(void) { ...@@ -508,8 +508,8 @@ void clusterInit(void) {
/* Set myself->port / cport to my listening ports, we'll just need to /* Set myself->port / cport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */ * discover the IP address via MEET messages. */
myself->port = server.port; myself->port = port;
myself->cport = server.port+CLUSTER_PORT_INCR; myself->cport = port+CLUSTER_PORT_INCR;
if (server.cluster_announce_port) if (server.cluster_announce_port)
myself->port = server.cluster_announce_port; myself->port = server.cluster_announce_port;
if (server.cluster_announce_bus_port) if (server.cluster_announce_bus_port)
...@@ -593,7 +593,7 @@ clusterLink *createClusterLink(clusterNode *node) { ...@@ -593,7 +593,7 @@ clusterLink *createClusterLink(clusterNode *node) {
link->sndbuf = sdsempty(); link->sndbuf = sdsempty();
link->rcvbuf = sdsempty(); link->rcvbuf = sdsempty();
link->node = node; link->node = node;
link->fd = -1; link->conn = NULL;
return link; return link;
} }
...@@ -601,23 +601,45 @@ clusterLink *createClusterLink(clusterNode *node) { ...@@ -601,23 +601,45 @@ clusterLink *createClusterLink(clusterNode *node) {
* This function will just make sure that the original node associated * This function will just make sure that the original node associated
* with this link will have the 'link' field set to NULL. */ * with this link will have the 'link' field set to NULL. */
void freeClusterLink(clusterLink *link) { void freeClusterLink(clusterLink *link) {
if (link->fd != -1) { if (link->conn) {
aeDeleteFileEvent(server.el, link->fd, AE_READABLE|AE_WRITABLE); connClose(link->conn);
link->conn = NULL;
} }
sdsfree(link->sndbuf); sdsfree(link->sndbuf);
sdsfree(link->rcvbuf); sdsfree(link->rcvbuf);
if (link->node) if (link->node)
link->node->link = NULL; link->node->link = NULL;
close(link->fd);
zfree(link); zfree(link);
} }
static void clusterConnAcceptHandler(connection *conn) {
clusterLink *link;
if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_VERBOSE,
"Error accepting cluster node connection: %s", connGetLastError(conn));
connClose(conn);
return;
}
/* Create a link object we use to handle the connection.
* It gets passed to the readable handler when data is available.
* Initiallly the link->node pointer is set to NULL as we don't know
* which node is, but the right node is references once we know the
* node identity. */
link = createClusterLink(NULL);
link->conn = conn;
connSetPrivateData(conn, link);
/* Register read handler */
connSetReadHandler(conn, clusterReadHandler);
}
#define MAX_CLUSTER_ACCEPTS_PER_CALL 1000 #define MAX_CLUSTER_ACCEPTS_PER_CALL 1000
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd; int cport, cfd;
int max = MAX_CLUSTER_ACCEPTS_PER_CALL; int max = MAX_CLUSTER_ACCEPTS_PER_CALL;
char cip[NET_IP_STR_LEN]; char cip[NET_IP_STR_LEN];
clusterLink *link;
UNUSED(el); UNUSED(el);
UNUSED(mask); UNUSED(mask);
UNUSED(privdata); UNUSED(privdata);
...@@ -634,19 +656,24 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -634,19 +656,24 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
"Error accepting cluster node: %s", server.neterr); "Error accepting cluster node: %s", server.neterr);
return; return;
} }
anetNonBlock(NULL,cfd);
anetEnableTcpNoDelay(NULL,cfd); connection *conn = server.tls_cluster ? connCreateAcceptedTLS(cfd,1) : connCreateAcceptedSocket(cfd);
connNonBlock(conn);
connEnableTcpNoDelay(conn);
/* Use non-blocking I/O for cluster messages. */ /* Use non-blocking I/O for cluster messages. */
serverLog(LL_VERBOSE,"Accepted cluster node %s:%d", cip, cport); serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport);
/* Create a link object we use to handle the connection.
* It gets passed to the readable handler when data is available. /* Accept the connection now. connAccept() may call our handler directly
* Initiallly the link->node pointer is set to NULL as we don't know * or schedule it for later depending on connection implementation.
* which node is, but the right node is references once we know the */
* node identity. */ if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
link = createClusterLink(NULL); serverLog(LL_VERBOSE,
link->fd = cfd; "Error accepting cluster node connection: %s",
aeCreateFileEvent(server.el,cfd,AE_READABLE,clusterReadHandler,link); connGetLastError(conn));
connClose(conn);
return;
}
} }
} }
...@@ -1447,7 +1474,7 @@ void nodeIp2String(char *buf, clusterLink *link, char *announced_ip) { ...@@ -1447,7 +1474,7 @@ void nodeIp2String(char *buf, clusterLink *link, char *announced_ip) {
memcpy(buf,announced_ip,NET_IP_STR_LEN); memcpy(buf,announced_ip,NET_IP_STR_LEN);
buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */ buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */
} else { } else {
anetPeerToString(link->fd, buf, NET_IP_STR_LEN, NULL); connPeerToString(link->conn, buf, NET_IP_STR_LEN, NULL);
} }
} }
...@@ -1751,7 +1778,7 @@ int clusterProcessPacket(clusterLink *link) { ...@@ -1751,7 +1778,7 @@ int clusterProcessPacket(clusterLink *link) {
{ {
char ip[NET_IP_STR_LEN]; char ip[NET_IP_STR_LEN];
if (anetSockName(link->fd,ip,sizeof(ip),NULL) != -1 && if (connSockName(link->conn,ip,sizeof(ip),NULL) != -1 &&
strcmp(ip,myself->ip)) strcmp(ip,myself->ip))
{ {
memcpy(myself->ip,ip,NET_IP_STR_LEN); memcpy(myself->ip,ip,NET_IP_STR_LEN);
...@@ -2118,35 +2145,76 @@ void handleLinkIOError(clusterLink *link) { ...@@ -2118,35 +2145,76 @@ void handleLinkIOError(clusterLink *link) {
/* Send data. This is handled using a trivial send buffer that gets /* Send data. This is handled using a trivial send buffer that gets
* consumed by write(). We don't try to optimize this for speed too much * consumed by write(). We don't try to optimize this for speed too much
* as this is a very low traffic channel. */ * as this is a very low traffic channel. */
void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) { void clusterWriteHandler(connection *conn) {
clusterLink *link = (clusterLink*) privdata; clusterLink *link = connGetPrivateData(conn);
ssize_t nwritten; ssize_t nwritten;
UNUSED(el);
UNUSED(mask);
nwritten = write(fd, link->sndbuf, sdslen(link->sndbuf)); nwritten = connWrite(conn, link->sndbuf, sdslen(link->sndbuf));
if (nwritten <= 0) { if (nwritten <= 0) {
serverLog(LL_DEBUG,"I/O error writing to node link: %s", serverLog(LL_DEBUG,"I/O error writing to node link: %s",
(nwritten == -1) ? strerror(errno) : "short write"); (nwritten == -1) ? connGetLastError(conn) : "short write");
handleLinkIOError(link); handleLinkIOError(link);
return; return;
} }
sdsrange(link->sndbuf,nwritten,-1); sdsrange(link->sndbuf,nwritten,-1);
if (sdslen(link->sndbuf) == 0) if (sdslen(link->sndbuf) == 0)
aeDeleteFileEvent(server.el, link->fd, AE_WRITABLE); connSetWriteHandler(link->conn, NULL);
}
/* A connect handler that gets called when a connection to another node
* gets established.
*/
void clusterLinkConnectHandler(connection *conn) {
clusterLink *link = connGetPrivateData(conn);
clusterNode *node = link->node;
/* Check if connection succeeded */
if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_VERBOSE, "Connection with Node %.40s at %s:%d failed: %s",
node->name, node->ip, node->cport,
connGetLastError(conn));
freeClusterLink(link);
return;
}
/* Register a read handler from now on */
connSetReadHandler(conn, clusterReadHandler);
/* Queue a PING in the new connection ASAP: this is crucial
* to avoid false positives in failure detection.
*
* If the node is flagged as MEET, we send a MEET message instead
* of a PING one, to force the receiver to add us in its node
* table. */
mstime_t old_ping_sent = node->ping_sent;
clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
if (old_ping_sent) {
/* If there was an active ping before the link was
* disconnected, we want to restore the ping time, otherwise
* replaced by the clusterSendPing() call. */
node->ping_sent = old_ping_sent;
}
/* We can clear the flag after the first packet is sent.
* If we'll never receive a PONG, we'll never send new packets
* to this node. Instead after the PONG is received and we
* are no longer in meet/handshake status, we want to send
* normal PING packets. */
node->flags &= ~CLUSTER_NODE_MEET;
serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
node->name, node->ip, node->cport);
} }
/* Read data. Try to read the first field of the header first to check the /* Read data. Try to read the first field of the header first to check the
* full length of the packet. When a whole packet is in memory this function * full length of the packet. When a whole packet is in memory this function
* will call the function to process the packet. And so forth. */ * will call the function to process the packet. And so forth. */
void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) { void clusterReadHandler(connection *conn) {
char buf[sizeof(clusterMsg)]; clusterMsg buf[1];
ssize_t nread; ssize_t nread;
clusterMsg *hdr; clusterMsg *hdr;
clusterLink *link = (clusterLink*) privdata; clusterLink *link = connGetPrivateData(conn);
unsigned int readlen, rcvbuflen; unsigned int readlen, rcvbuflen;
UNUSED(el);
UNUSED(mask);
while(1) { /* Read as long as there is data to read. */ while(1) { /* Read as long as there is data to read. */
rcvbuflen = sdslen(link->rcvbuf); rcvbuflen = sdslen(link->rcvbuf);
...@@ -2174,13 +2242,13 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -2174,13 +2242,13 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
if (readlen > sizeof(buf)) readlen = sizeof(buf); if (readlen > sizeof(buf)) readlen = sizeof(buf);
} }
nread = read(fd,buf,readlen); nread = connRead(conn,buf,readlen);
if (nread == -1 && errno == EAGAIN) return; /* No more data ready. */ if (nread == -1 && (connGetState(conn) == CONN_STATE_CONNECTED)) return; /* No more data ready. */
if (nread <= 0) { if (nread <= 0) {
/* I/O error... */ /* I/O error... */
serverLog(LL_DEBUG,"I/O error reading from node link: %s", serverLog(LL_DEBUG,"I/O error reading from node link: %s",
(nread == 0) ? "connection closed" : strerror(errno)); (nread == 0) ? "connection closed" : connGetLastError(conn));
handleLinkIOError(link); handleLinkIOError(link);
return; return;
} else { } else {
...@@ -2209,8 +2277,7 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -2209,8 +2277,7 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
* from event handlers that will do stuff with the same link later. */ * from event handlers that will do stuff with the same link later. */
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) { void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
if (sdslen(link->sndbuf) == 0 && msglen != 0) if (sdslen(link->sndbuf) == 0 && msglen != 0)
aeCreateFileEvent(server.el,link->fd,AE_WRITABLE|AE_BARRIER, connSetWriteHandlerWithBarrier(link->conn, clusterWriteHandler, 1);
clusterWriteHandler,link);
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen); link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
...@@ -2276,11 +2343,12 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) { ...@@ -2276,11 +2343,12 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
} }
/* Handle cluster-announce-port as well. */ /* Handle cluster-announce-port as well. */
int port = server.tls_cluster ? server.tls_port : server.port;
int announced_port = server.cluster_announce_port ? int announced_port = server.cluster_announce_port ?
server.cluster_announce_port : server.port; server.cluster_announce_port : port;
int announced_cport = server.cluster_announce_bus_port ? int announced_cport = server.cluster_announce_bus_port ?
server.cluster_announce_bus_port : server.cluster_announce_bus_port :
(server.port + CLUSTER_PORT_INCR); (port + CLUSTER_PORT_INCR);
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);
...@@ -2517,7 +2585,8 @@ void clusterBroadcastPong(int target) { ...@@ -2517,7 +2585,8 @@ void clusterBroadcastPong(int target) {
* *
* If link is NULL, then the message is broadcasted to the whole cluster. */ * If link is NULL, then the message is broadcasted to the whole cluster. */
void clusterSendPublish(clusterLink *link, robj *channel, robj *message) { void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
unsigned char buf[sizeof(clusterMsg)], *payload; unsigned char *payload;
clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf; clusterMsg *hdr = (clusterMsg*) buf;
uint32_t totlen; uint32_t totlen;
uint32_t channel_len, message_len; uint32_t channel_len, message_len;
...@@ -2537,7 +2606,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) { ...@@ -2537,7 +2606,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
/* Try to use the local buffer if possible */ /* Try to use the local buffer if possible */
if (totlen < sizeof(buf)) { if (totlen < sizeof(buf)) {
payload = buf; payload = (unsigned char*)buf;
} else { } else {
payload = zmalloc(totlen); payload = zmalloc(totlen);
memcpy(payload,hdr,sizeof(*hdr)); memcpy(payload,hdr,sizeof(*hdr));
...@@ -2554,7 +2623,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) { ...@@ -2554,7 +2623,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
decrRefCount(channel); decrRefCount(channel);
decrRefCount(message); decrRefCount(message);
if (payload != buf) zfree(payload); if (payload != (unsigned char*)buf) zfree(payload);
} }
/* Send a FAIL message to all the nodes we are able to contact. /* Send a FAIL message to all the nodes we are able to contact.
...@@ -2563,7 +2632,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) { ...@@ -2563,7 +2632,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
* we switch the node state to CLUSTER_NODE_FAIL and ask all the other * we switch the node state to CLUSTER_NODE_FAIL and ask all the other
* nodes to do the same ASAP. */ * nodes to do the same ASAP. */
void clusterSendFail(char *nodename) { void clusterSendFail(char *nodename) {
unsigned char buf[sizeof(clusterMsg)]; clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf; clusterMsg *hdr = (clusterMsg*) buf;
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL); clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL);
...@@ -2575,7 +2644,7 @@ void clusterSendFail(char *nodename) { ...@@ -2575,7 +2644,7 @@ void clusterSendFail(char *nodename) {
* slots configuration. The node name, slots bitmap, and configEpoch info * slots configuration. The node name, slots bitmap, and configEpoch info
* are included. */ * are included. */
void clusterSendUpdate(clusterLink *link, clusterNode *node) { void clusterSendUpdate(clusterLink *link, clusterNode *node) {
unsigned char buf[sizeof(clusterMsg)]; clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf; clusterMsg *hdr = (clusterMsg*) buf;
if (link == NULL) return; if (link == NULL) return;
...@@ -2583,7 +2652,7 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) { ...@@ -2583,7 +2652,7 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN); memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch); hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots)); memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots));
clusterSendMessage(link,buf,ntohl(hdr->totlen)); clusterSendMessage(link,(unsigned char*)buf,ntohl(hdr->totlen));
} }
/* Send a MODULE message. /* Send a MODULE message.
...@@ -2591,7 +2660,8 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) { ...@@ -2591,7 +2660,8 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
* If link is NULL, then the message is broadcasted to the whole cluster. */ * If link is NULL, then the message is broadcasted to the whole cluster. */
void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type, void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
unsigned char *payload, uint32_t len) { unsigned char *payload, uint32_t len) {
unsigned char buf[sizeof(clusterMsg)], *heapbuf; unsigned char *heapbuf;
clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf; clusterMsg *hdr = (clusterMsg*) buf;
uint32_t totlen; uint32_t totlen;
...@@ -2606,7 +2676,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type, ...@@ -2606,7 +2676,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
/* Try to use the local buffer if possible */ /* Try to use the local buffer if possible */
if (totlen < sizeof(buf)) { if (totlen < sizeof(buf)) {
heapbuf = buf; heapbuf = (unsigned char*)buf;
} else { } else {
heapbuf = zmalloc(totlen); heapbuf = zmalloc(totlen);
memcpy(heapbuf,hdr,sizeof(*hdr)); memcpy(heapbuf,hdr,sizeof(*hdr));
...@@ -2619,7 +2689,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type, ...@@ -2619,7 +2689,7 @@ void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
else else
clusterBroadcastMessage(heapbuf,totlen); clusterBroadcastMessage(heapbuf,totlen);
if (heapbuf != buf) zfree(heapbuf); if (heapbuf != (unsigned char*)buf) zfree(heapbuf);
} }
/* This function gets a cluster node ID string as target, the same way the nodes /* This function gets a cluster node ID string as target, the same way the nodes
...@@ -2663,7 +2733,7 @@ void clusterPropagatePublish(robj *channel, robj *message) { ...@@ -2663,7 +2733,7 @@ void clusterPropagatePublish(robj *channel, robj *message) {
* Note that we send the failover request to everybody, master and slave nodes, * Note that we send the failover request to everybody, master and slave nodes,
* but only the masters are supposed to reply to our query. */ * but only the masters are supposed to reply to our query. */
void clusterRequestFailoverAuth(void) { void clusterRequestFailoverAuth(void) {
unsigned char buf[sizeof(clusterMsg)]; clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf; clusterMsg *hdr = (clusterMsg*) buf;
uint32_t totlen; uint32_t totlen;
...@@ -2679,7 +2749,7 @@ void clusterRequestFailoverAuth(void) { ...@@ -2679,7 +2749,7 @@ void clusterRequestFailoverAuth(void) {
/* Send a FAILOVER_AUTH_ACK message to the specified node. */ /* Send a FAILOVER_AUTH_ACK message to the specified node. */
void clusterSendFailoverAuth(clusterNode *node) { void clusterSendFailoverAuth(clusterNode *node) {
unsigned char buf[sizeof(clusterMsg)]; clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf; clusterMsg *hdr = (clusterMsg*) buf;
uint32_t totlen; uint32_t totlen;
...@@ -2687,12 +2757,12 @@ void clusterSendFailoverAuth(clusterNode *node) { ...@@ -2687,12 +2757,12 @@ void clusterSendFailoverAuth(clusterNode *node) {
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK); clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK);
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData); totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
hdr->totlen = htonl(totlen); hdr->totlen = htonl(totlen);
clusterSendMessage(node->link,buf,totlen); clusterSendMessage(node->link,(unsigned char*)buf,totlen);
} }
/* Send a MFSTART message to the specified node. */ /* Send a MFSTART message to the specified node. */
void clusterSendMFStart(clusterNode *node) { void clusterSendMFStart(clusterNode *node) {
unsigned char buf[sizeof(clusterMsg)]; clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf; clusterMsg *hdr = (clusterMsg*) buf;
uint32_t totlen; uint32_t totlen;
...@@ -2700,7 +2770,7 @@ void clusterSendMFStart(clusterNode *node) { ...@@ -2700,7 +2770,7 @@ void clusterSendMFStart(clusterNode *node) {
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MFSTART); clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MFSTART);
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData); totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
hdr->totlen = htonl(totlen); hdr->totlen = htonl(totlen);
clusterSendMessage(node->link,buf,totlen); clusterSendMessage(node->link,(unsigned char*)buf,totlen);
} }
/* Vote for the node asking for our vote if there are the conditions. */ /* Vote for the node asking for our vote if there are the conditions. */
...@@ -3383,13 +3453,11 @@ void clusterCron(void) { ...@@ -3383,13 +3453,11 @@ void clusterCron(void) {
} }
if (node->link == NULL) { if (node->link == NULL) {
int fd; clusterLink *link = createClusterLink(node);
mstime_t old_ping_sent; link->conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
clusterLink *link; connSetPrivateData(link->conn, link);
if (connConnect(link->conn, node->ip, node->cport, NET_FIRST_BIND_ADDR,
fd = anetTcpNonBlockBindConnect(server.neterr, node->ip, clusterLinkConnectHandler) == -1) {
node->cport, NET_FIRST_BIND_ADDR);
if (fd == -1) {
/* We got a synchronous error from connect before /* We got a synchronous error from connect before
* clusterSendPing() had a chance to be called. * clusterSendPing() had a chance to be called.
* If node->ping_sent is zero, failure detection can't work, * If node->ping_sent is zero, failure detection can't work,
...@@ -3399,37 +3467,11 @@ void clusterCron(void) { ...@@ -3399,37 +3467,11 @@ void clusterCron(void) {
serverLog(LL_DEBUG, "Unable to connect to " serverLog(LL_DEBUG, "Unable to connect to "
"Cluster Node [%s]:%d -> %s", node->ip, "Cluster Node [%s]:%d -> %s", node->ip,
node->cport, server.neterr); node->cport, server.neterr);
freeClusterLink(link);
continue; continue;
} }
link = createClusterLink(node);
link->fd = fd;
node->link = link; node->link = link;
aeCreateFileEvent(server.el,link->fd,AE_READABLE,
clusterReadHandler,link);
/* Queue a PING in the new connection ASAP: this is crucial
* to avoid false positives in failure detection.
*
* If the node is flagged as MEET, we send a MEET message instead
* of a PING one, to force the receiver to add us in its node
* table. */
old_ping_sent = node->ping_sent;
clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
if (old_ping_sent) {
/* If there was an active ping before the link was
* disconnected, we want to restore the ping time, otherwise
* replaced by the clusterSendPing() call. */
node->ping_sent = old_ping_sent;
}
/* We can clear the flag after the first packet is sent.
* If we'll never receive a PONG, we'll never send new packets
* to this node. Instead after the PONG is received and we
* are no longer in meet/handshake status, we want to send
* normal PING packets. */
node->flags &= ~CLUSTER_NODE_MEET;
serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
node->name, node->ip, node->cport);
} }
} }
dictReleaseIterator(di); dictReleaseIterator(di);
...@@ -4252,7 +4294,9 @@ NULL ...@@ -4252,7 +4294,9 @@ NULL
} }
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
/* CLUSTER NODES */ /* CLUSTER NODES */
addReplyBulkSds(c,clusterGenNodesDescription(0)); sds nodes = clusterGenNodesDescription(0);
addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
sdsfree(nodes);
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
/* CLUSTER MYID */ /* CLUSTER MYID */
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN); addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
...@@ -4494,10 +4538,8 @@ NULL ...@@ -4494,10 +4538,8 @@ NULL
"cluster_stats_messages_received:%lld\r\n", tot_msg_received); "cluster_stats_messages_received:%lld\r\n", tot_msg_received);
/* Produce the reply protocol. */ /* Produce the reply protocol. */
addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n", addReplyVerbatim(c,info,sdslen(info),"txt");
(unsigned long)sdslen(info))); sdsfree(info);
addReplySds(c,info);
addReply(c,shared.crlf);
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
int retval = clusterSaveConfig(1); int retval = clusterSaveConfig(1);
...@@ -4940,7 +4982,7 @@ void restoreCommand(client *c) { ...@@ -4940,7 +4982,7 @@ void restoreCommand(client *c) {
#define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */ #define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */
typedef struct migrateCachedSocket { typedef struct migrateCachedSocket {
int fd; connection *conn;
long last_dbid; long last_dbid;
time_t last_use_time; time_t last_use_time;
} migrateCachedSocket; } migrateCachedSocket;
...@@ -4957,7 +4999,7 @@ typedef struct migrateCachedSocket { ...@@ -4957,7 +4999,7 @@ typedef struct migrateCachedSocket {
* should be called so that the connection will be created from scratch * should be called so that the connection will be created from scratch
* the next time. */ * the next time. */
migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) { migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) {
int fd; connection *conn;
sds name = sdsempty(); sds name = sdsempty();
migrateCachedSocket *cs; migrateCachedSocket *cs;
...@@ -4977,34 +5019,27 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti ...@@ -4977,34 +5019,27 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
/* Too many items, drop one at random. */ /* Too many items, drop one at random. */
dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets); dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets);
cs = dictGetVal(de); cs = dictGetVal(de);
close(cs->fd); connClose(cs->conn);
zfree(cs); zfree(cs);
dictDelete(server.migrate_cached_sockets,dictGetKey(de)); dictDelete(server.migrate_cached_sockets,dictGetKey(de));
} }
/* Create the socket */ /* Create the socket */
fd = anetTcpNonBlockConnect(server.neterr,c->argv[1]->ptr, conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
atoi(c->argv[2]->ptr)); if (connBlockingConnect(conn, c->argv[1]->ptr, atoi(c->argv[2]->ptr), timeout)
if (fd == -1) { != C_OK) {
sdsfree(name);
addReplyErrorFormat(c,"Can't connect to target node: %s",
server.neterr);
return NULL;
}
anetEnableTcpNoDelay(server.neterr,fd);
/* Check if it connects within the specified timeout. */
if ((aeWait(fd,AE_WRITABLE,timeout) & AE_WRITABLE) == 0) {
sdsfree(name);
addReplySds(c, addReplySds(c,
sdsnew("-IOERR error or timeout connecting to the client\r\n")); sdsnew("-IOERR error or timeout connecting to the client\r\n"));
close(fd); connClose(conn);
sdsfree(name);
return NULL; return NULL;
} }
connEnableTcpNoDelay(conn);
/* Add to the cache and return it to the caller. */ /* Add to the cache and return it to the caller. */
cs = zmalloc(sizeof(*cs)); cs = zmalloc(sizeof(*cs));
cs->fd = fd; cs->conn = conn;
cs->last_dbid = -1; cs->last_dbid = -1;
cs->last_use_time = server.unixtime; cs->last_use_time = server.unixtime;
dictAdd(server.migrate_cached_sockets,name,cs); dictAdd(server.migrate_cached_sockets,name,cs);
...@@ -5025,7 +5060,7 @@ void migrateCloseSocket(robj *host, robj *port) { ...@@ -5025,7 +5060,7 @@ void migrateCloseSocket(robj *host, robj *port) {
return; return;
} }
close(cs->fd); connClose(cs->conn);
zfree(cs); zfree(cs);
dictDelete(server.migrate_cached_sockets,name); dictDelete(server.migrate_cached_sockets,name);
sdsfree(name); sdsfree(name);
...@@ -5039,7 +5074,7 @@ void migrateCloseTimedoutSockets(void) { ...@@ -5039,7 +5074,7 @@ void migrateCloseTimedoutSockets(void) {
migrateCachedSocket *cs = dictGetVal(de); migrateCachedSocket *cs = dictGetVal(de);
if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) { if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) {
close(cs->fd); connClose(cs->conn);
zfree(cs); zfree(cs);
dictDelete(server.migrate_cached_sockets,dictGetKey(de)); dictDelete(server.migrate_cached_sockets,dictGetKey(de));
} }
...@@ -5221,7 +5256,7 @@ try_again: ...@@ -5221,7 +5256,7 @@ try_again:
while ((towrite = sdslen(buf)-pos) > 0) { while ((towrite = sdslen(buf)-pos) > 0) {
towrite = (towrite > (64*1024) ? (64*1024) : towrite); towrite = (towrite > (64*1024) ? (64*1024) : towrite);
nwritten = syncWrite(cs->fd,buf+pos,towrite,timeout); nwritten = connSyncWrite(cs->conn,buf+pos,towrite,timeout);
if (nwritten != (signed)towrite) { if (nwritten != (signed)towrite) {
write_error = 1; write_error = 1;
goto socket_err; goto socket_err;
...@@ -5235,11 +5270,11 @@ try_again: ...@@ -5235,11 +5270,11 @@ try_again:
char buf2[1024]; /* Restore reply. */ char buf2[1024]; /* Restore reply. */
/* Read the AUTH reply if needed. */ /* Read the AUTH reply if needed. */
if (password && syncReadLine(cs->fd, buf0, sizeof(buf0), timeout) <= 0) if (password && connSyncReadLine(cs->conn, buf0, sizeof(buf0), timeout) <= 0)
goto socket_err; goto socket_err;
/* Read the SELECT reply if needed. */ /* Read the SELECT reply if needed. */
if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0) if (select && connSyncReadLine(cs->conn, buf1, sizeof(buf1), timeout) <= 0)
goto socket_err; goto socket_err;
/* Read the RESTORE replies. */ /* Read the RESTORE replies. */
...@@ -5254,7 +5289,7 @@ try_again: ...@@ -5254,7 +5289,7 @@ try_again:
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1)); if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
for (j = 0; j < num_keys; j++) { for (j = 0; j < num_keys; j++) {
if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) { if (connSyncReadLine(cs->conn, buf2, sizeof(buf2), timeout) <= 0) {
socket_error = 1; socket_error = 1;
break; break;
} }
......
...@@ -40,7 +40,7 @@ struct clusterNode; ...@@ -40,7 +40,7 @@ struct clusterNode;
/* clusterLink encapsulates everything needed to talk with a remote node. */ /* clusterLink encapsulates everything needed to talk with a remote node. */
typedef struct clusterLink { typedef struct clusterLink {
mstime_t ctime; /* Link creation time */ mstime_t ctime; /* Link creation time */
int fd; /* TCP socket file descriptor */ connection *conn; /* Connection to remote node */
sds sndbuf; /* Packet send buffer */ sds sndbuf; /* Packet send buffer */
sds rcvbuf; /* Packet reception buffer */ sds rcvbuf; /* Packet reception buffer */
struct clusterNode *node; /* Node related to this link if any, or NULL */ struct clusterNode *node; /* Node related to this link if any, or NULL */
......
...@@ -144,6 +144,7 @@ configYesNo configs_yesno[] = { ...@@ -144,6 +144,7 @@ configYesNo configs_yesno[] = {
{"replica-serve-stale-data","slave-serve-stale-data",&server.repl_serve_stale_data,1,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA}, {"replica-serve-stale-data","slave-serve-stale-data",&server.repl_serve_stale_data,1,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA},
{"replica-read-only","slave-read-only",&server.repl_slave_ro,1,CONFIG_DEFAULT_SLAVE_READ_ONLY}, {"replica-read-only","slave-read-only",&server.repl_slave_ro,1,CONFIG_DEFAULT_SLAVE_READ_ONLY},
{"replica-ignore-maxmemory","slave-ignore-maxmemory",&server.repl_slave_ignore_maxmemory,1,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY}, {"replica-ignore-maxmemory","slave-ignore-maxmemory",&server.repl_slave_ignore_maxmemory,1,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY},
{"jemalloc-bg-thread",NULL,&server.jemalloc_bg_thread,1,1},
{NULL, NULL, 0, 0} {NULL, NULL, 0, 0}
}; };
...@@ -219,7 +220,7 @@ void queueLoadModule(sds path, sds *argv, int argc) { ...@@ -219,7 +220,7 @@ void queueLoadModule(sds path, sds *argv, int argc) {
} }
void loadServerConfigFromString(char *config) { void loadServerConfigFromString(char *config) {
char *err = NULL; const char *err = NULL;
int linenum = 0, totlines, i; int linenum = 0, totlines, i;
int slaveof_linenum = 0; int slaveof_linenum = 0;
sds *lines; sds *lines;
...@@ -438,6 +439,7 @@ void loadServerConfigFromString(char *config) { ...@@ -438,6 +439,7 @@ void loadServerConfigFromString(char *config) {
server.repl_diskless_load = configEnumGetValue(repl_diskless_load_enum,argv[1]); server.repl_diskless_load = configEnumGetValue(repl_diskless_load_enum,argv[1]);
if (server.repl_diskless_load == INT_MIN) { if (server.repl_diskless_load == INT_MIN) {
err = "argument must be 'disabled', 'on-empty-db', 'swapdb' or 'flushdb'"; err = "argument must be 'disabled', 'on-empty-db', 'swapdb' or 'flushdb'";
goto loaderr;
} }
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) { } else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
server.repl_diskless_sync_delay = atoi(argv[1]); server.repl_diskless_sync_delay = atoi(argv[1]);
...@@ -513,6 +515,12 @@ void loadServerConfigFromString(char *config) { ...@@ -513,6 +515,12 @@ void loadServerConfigFromString(char *config) {
err = "rdb-key-save-delay can't be negative"; err = "rdb-key-save-delay can't be negative";
goto loaderr; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"key-load-delay") && argc==2) {
server.key_load_delay = atoi(argv[1]);
if (server.key_load_delay < 0) {
err = "key-load-delay can't be negative";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) { } else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) { if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN"; err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
...@@ -672,6 +680,10 @@ void loadServerConfigFromString(char *config) { ...@@ -672,6 +680,10 @@ void loadServerConfigFromString(char *config) {
server.lua_time_limit = strtoll(argv[1],NULL,10); server.lua_time_limit = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) { } else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
server.lua_always_replicate_commands = yesnotoi(argv[1]); server.lua_always_replicate_commands = yesnotoi(argv[1]);
if (server.lua_always_replicate_commands == -1) {
err = "argument must be 'yes' or 'no'";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") && } else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
argc == 2) argc == 2)
{ {
...@@ -791,6 +803,45 @@ void loadServerConfigFromString(char *config) { ...@@ -791,6 +803,45 @@ void loadServerConfigFromString(char *config) {
err = sentinelHandleConfiguration(argv+1,argc-1); err = sentinelHandleConfiguration(argv+1,argc-1);
if (err) goto loaderr; if (err) goto loaderr;
} }
#ifdef USE_OPENSSL
} else if (!strcasecmp(argv[0],"tls-port") && argc == 2) {
server.tls_port = atoi(argv[1]);
if (server.port < 0 || server.port > 65535) {
err = "Invalid tls-port"; goto loaderr;
}
} else if (!strcasecmp(argv[0],"tls-cluster") && argc == 2) {
server.tls_cluster = yesnotoi(argv[1]);
} else if (!strcasecmp(argv[0],"tls-replication") && argc == 2) {
server.tls_replication = yesnotoi(argv[1]);
} else if (!strcasecmp(argv[0],"tls-auth-clients") && argc == 2) {
server.tls_auth_clients = yesnotoi(argv[1]);
} else if (!strcasecmp(argv[0],"tls-cert-file") && argc == 2) {
zfree(server.tls_ctx_config.cert_file);
server.tls_ctx_config.cert_file = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-key-file") && argc == 2) {
zfree(server.tls_ctx_config.key_file);
server.tls_ctx_config.key_file = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-dh-params-file") && argc == 2) {
zfree(server.tls_ctx_config.dh_params_file);
server.tls_ctx_config.dh_params_file = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-ca-cert-file") && argc == 2) {
zfree(server.tls_ctx_config.ca_cert_file);
server.tls_ctx_config.ca_cert_file = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-ca-cert-dir") && argc == 2) {
zfree(server.tls_ctx_config.ca_cert_dir);
server.tls_ctx_config.ca_cert_dir = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-protocols") && argc >= 2) {
zfree(server.tls_ctx_config.protocols);
server.tls_ctx_config.protocols = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-ciphers") && argc == 2) {
zfree(server.tls_ctx_config.ciphers);
server.tls_ctx_config.ciphers = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-ciphersuites") && argc == 2) {
zfree(server.tls_ctx_config.ciphersuites);
server.tls_ctx_config.ciphersuites = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"tls-prefer-server-ciphers") && argc == 2) {
server.tls_ctx_config.prefer_server_ciphers = yesnotoi(argv[1]);
#endif /* USE_OPENSSL */
} else { } else {
err = "Bad directive or wrong number of arguments"; goto loaderr; err = "Bad directive or wrong number of arguments"; goto loaderr;
} }
...@@ -1164,6 +1215,8 @@ void configSetCommand(client *c) { ...@@ -1164,6 +1215,8 @@ void configSetCommand(client *c) {
"replica-priority",server.slave_priority,0,INT_MAX) { "replica-priority",server.slave_priority,0,INT_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
"rdb-key-save-delay",server.rdb_key_save_delay,0,LLONG_MAX) { "rdb-key-save-delay",server.rdb_key_save_delay,0,LLONG_MAX) {
} config_set_numerical_field(
"key-load-delay",server.key_load_delay,0,LLONG_MAX) {
} config_set_numerical_field( } config_set_numerical_field(
"slave-announce-port",server.slave_announce_port,0,65535) { "slave-announce-port",server.slave_announce_port,0,65535) {
} config_set_numerical_field( } config_set_numerical_field(
...@@ -1233,7 +1286,100 @@ void configSetCommand(client *c) { ...@@ -1233,7 +1286,100 @@ void configSetCommand(client *c) {
"appendfsync",server.aof_fsync,aof_fsync_enum) { "appendfsync",server.aof_fsync,aof_fsync_enum) {
} config_set_enum_field( } config_set_enum_field(
"repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum) { "repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum) {
#ifdef USE_OPENSSL
/* TLS fields. */
} config_set_special_field("tls-cert-file") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.cert_file = (char *) o->ptr;
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c,
"Unable to configure tls-cert-file. Check server logs.");
return;
}
zfree(server.tls_ctx_config.cert_file);
server.tls_ctx_config.cert_file = zstrdup(o->ptr);
} config_set_special_field("tls-key-file") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.key_file = (char *) o->ptr;
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c,
"Unable to configure tls-key-file. Check server logs.");
return;
}
zfree(server.tls_ctx_config.key_file);
server.tls_ctx_config.key_file = zstrdup(o->ptr);
} config_set_special_field("tls-dh-params-file") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.dh_params_file = (char *) o->ptr;
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c,
"Unable to configure tls-dh-params-file. Check server logs.");
return;
}
zfree(server.tls_ctx_config.dh_params_file);
server.tls_ctx_config.dh_params_file = zstrdup(o->ptr);
} config_set_special_field("tls-ca-cert-file") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.ca_cert_file = (char *) o->ptr;
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c,
"Unable to configure tls-ca-cert-file. Check server logs.");
return;
}
zfree(server.tls_ctx_config.ca_cert_file);
server.tls_ctx_config.ca_cert_file = zstrdup(o->ptr);
} config_set_special_field("tls-ca-cert-dir") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.ca_cert_dir = (char *) o->ptr;
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c,
"Unable to configure tls-ca-cert-dir. Check server logs.");
return;
}
zfree(server.tls_ctx_config.ca_cert_dir);
server.tls_ctx_config.ca_cert_dir = zstrdup(o->ptr);
} config_set_bool_field("tls-auth-clients", server.tls_auth_clients) {
} config_set_bool_field("tls-replication", server.tls_replication) {
} config_set_bool_field("tls-cluster", server.tls_cluster) {
} config_set_special_field("tls-protocols") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.protocols = (char *) o->ptr;
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c,
"Unable to configure tls-protocols. Check server logs.");
return;
}
zfree(server.tls_ctx_config.protocols);
server.tls_ctx_config.protocols = zstrdup(o->ptr);
} config_set_special_field("tls-ciphers") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.ciphers = (char *) o->ptr;
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c,
"Unable to configure tls-ciphers. Check server logs.");
return;
}
zfree(server.tls_ctx_config.ciphers);
server.tls_ctx_config.ciphers = zstrdup(o->ptr);
} config_set_special_field("tls-ciphersuites") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.ciphersuites = (char *) o->ptr;
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c,
"Unable to configure tls-ciphersuites. Check server logs.");
return;
}
zfree(server.tls_ctx_config.ciphersuites);
server.tls_ctx_config.ciphersuites = zstrdup(o->ptr);
} config_set_special_field("tls-prefer-server-ciphers") {
redisTLSContextConfig tmpctx = server.tls_ctx_config;
tmpctx.prefer_server_ciphers = yesnotoi(o->ptr);
if (tlsConfigure(&tmpctx) == C_ERR) {
addReplyError(c, "Unable to reconfigure TLS. Check server logs.");
return;
}
server.tls_ctx_config.prefer_server_ciphers = tmpctx.prefer_server_ciphers;
#endif /* USE_OPENSSL */
/* Everyhing else is an error... */ /* Everyhing else is an error... */
} config_set_else { } config_set_else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
...@@ -1307,6 +1453,16 @@ void configGetCommand(client *c) { ...@@ -1307,6 +1453,16 @@ void configGetCommand(client *c) {
config_get_string_field("pidfile",server.pidfile); config_get_string_field("pidfile",server.pidfile);
config_get_string_field("slave-announce-ip",server.slave_announce_ip); config_get_string_field("slave-announce-ip",server.slave_announce_ip);
config_get_string_field("replica-announce-ip",server.slave_announce_ip); config_get_string_field("replica-announce-ip",server.slave_announce_ip);
#ifdef USE_OPENSSL
config_get_string_field("tls-cert-file",server.tls_ctx_config.cert_file);
config_get_string_field("tls-key-file",server.tls_ctx_config.key_file);
config_get_string_field("tls-dh-params-file",server.tls_ctx_config.dh_params_file);
config_get_string_field("tls-ca-cert-file",server.tls_ctx_config.ca_cert_file);
config_get_string_field("tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir);
config_get_string_field("tls-protocols",server.tls_ctx_config.protocols);
config_get_string_field("tls-ciphers",server.tls_ctx_config.ciphers);
config_get_string_field("tls-ciphersuites",server.tls_ctx_config.ciphersuites);
#endif
/* Numerical values */ /* Numerical values */
config_get_numerical_field("maxmemory",server.maxmemory); config_get_numerical_field("maxmemory",server.maxmemory);
...@@ -1354,6 +1510,7 @@ void configGetCommand(client *c) { ...@@ -1354,6 +1510,7 @@ void configGetCommand(client *c) {
config_get_numerical_field("slowlog-max-len", server.slowlog_max_len); config_get_numerical_field("slowlog-max-len", server.slowlog_max_len);
config_get_numerical_field("tracking-table-max-fill", server.tracking_table_max_fill); config_get_numerical_field("tracking-table-max-fill", server.tracking_table_max_fill);
config_get_numerical_field("port",server.port); config_get_numerical_field("port",server.port);
config_get_numerical_field("tls-port",server.tls_port);
config_get_numerical_field("cluster-announce-port",server.cluster_announce_port); config_get_numerical_field("cluster-announce-port",server.cluster_announce_port);
config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port); config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port);
config_get_numerical_field("tcp-backlog",server.tcp_backlog); config_get_numerical_field("tcp-backlog",server.tcp_backlog);
...@@ -1381,6 +1538,7 @@ void configGetCommand(client *c) { ...@@ -1381,6 +1538,7 @@ void configGetCommand(client *c) {
config_get_numerical_field("cluster-replica-validity-factor",server.cluster_slave_validity_factor); config_get_numerical_field("cluster-replica-validity-factor",server.cluster_slave_validity_factor);
config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay); config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
config_get_numerical_field("rdb-key-save-delay",server.rdb_key_save_delay); config_get_numerical_field("rdb-key-save-delay",server.rdb_key_save_delay);
config_get_numerical_field("key-load-delay",server.key_load_delay);
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive); config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
/* Bool (yes/no) values */ /* Bool (yes/no) values */
...@@ -1393,7 +1551,11 @@ void configGetCommand(client *c) { ...@@ -1393,7 +1551,11 @@ void configGetCommand(client *c) {
} }
config_get_bool_field("activedefrag", server.active_defrag_enabled); config_get_bool_field("activedefrag", server.active_defrag_enabled);
config_get_bool_field("tls-cluster",server.tls_cluster);
config_get_bool_field("tls-replication",server.tls_replication);
config_get_bool_field("tls-auth-clients",server.tls_auth_clients);
config_get_bool_field("tls-prefer-server-ciphers",
server.tls_ctx_config.prefer_server_ciphers);
/* Enum values */ /* Enum values */
config_get_enum_field("maxmemory-policy", config_get_enum_field("maxmemory-policy",
server.maxmemory_policy,maxmemory_policy_enum); server.maxmemory_policy,maxmemory_policy_enum);
...@@ -1507,6 +1669,7 @@ void configGetCommand(client *c) { ...@@ -1507,6 +1669,7 @@ void configGetCommand(client *c) {
} }
matches++; matches++;
} }
setDeferredMapLen(c,replylen,matches); setDeferredMapLen(c,replylen,matches);
} }
...@@ -2113,7 +2276,7 @@ int rewriteConfig(char *path) { ...@@ -2113,7 +2276,7 @@ int rewriteConfig(char *path) {
} }
rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE); rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE);
rewriteConfigNumericalOption(state,"port",server.port,CONFIG_DEFAULT_SERVER_PORT); rewriteConfigNumericalOption(state,"tls-port",server.tls_port,CONFIG_DEFAULT_SERVER_TLS_PORT);
rewriteConfigNumericalOption(state,"cluster-announce-port",server.cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT); rewriteConfigNumericalOption(state,"cluster-announce-port",server.cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT);
rewriteConfigNumericalOption(state,"cluster-announce-bus-port",server.cluster_announce_bus_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT); rewriteConfigNumericalOption(state,"cluster-announce-bus-port",server.cluster_announce_bus_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT);
rewriteConfigNumericalOption(state,"tcp-backlog",server.tcp_backlog,CONFIG_DEFAULT_TCP_BACKLOG); rewriteConfigNumericalOption(state,"tcp-backlog",server.tcp_backlog,CONFIG_DEFAULT_TCP_BACKLOG);
...@@ -2195,6 +2358,21 @@ int rewriteConfig(char *path) { ...@@ -2195,6 +2358,21 @@ int rewriteConfig(char *path) {
rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ); rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE); rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE);
rewriteConfigNumericalOption(state,"rdb-key-save-delay",server.rdb_key_save_delay,CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY); rewriteConfigNumericalOption(state,"rdb-key-save-delay",server.rdb_key_save_delay,CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY);
rewriteConfigNumericalOption(state,"key-load-delay",server.key_load_delay,CONFIG_DEFAULT_KEY_LOAD_DELAY);
#ifdef USE_OPENSSL
rewriteConfigYesNoOption(state,"tls-cluster",server.tls_cluster,0);
rewriteConfigYesNoOption(state,"tls-replication",server.tls_replication,0);
rewriteConfigYesNoOption(state,"tls-auth-clients",server.tls_auth_clients,1);
rewriteConfigStringOption(state,"tls-cert-file",server.tls_ctx_config.cert_file,NULL);
rewriteConfigStringOption(state,"tls-key-file",server.tls_ctx_config.key_file,NULL);
rewriteConfigStringOption(state,"tls-dh-params-file",server.tls_ctx_config.dh_params_file,NULL);
rewriteConfigStringOption(state,"tls-ca-cert-file",server.tls_ctx_config.ca_cert_file,NULL);
rewriteConfigStringOption(state,"tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir,NULL);
rewriteConfigStringOption(state,"tls-protocols",server.tls_ctx_config.protocols,NULL);
rewriteConfigStringOption(state,"tls-ciphers",server.tls_ctx_config.ciphers,NULL);
rewriteConfigStringOption(state,"tls-ciphersuites",server.tls_ctx_config.ciphersuites,NULL);
rewriteConfigYesNoOption(state,"tls-prefer-server-ciphers",server.tls_ctx_config.prefer_server_ciphers,0);
#endif
/* Rewrite Sentinel config if in Sentinel mode. */ /* Rewrite Sentinel config if in Sentinel mode. */
if (server.sentinel_mode) rewriteConfigSentinelOption(state); if (server.sentinel_mode) rewriteConfigSentinelOption(state);
......
/*
* Copyright (c) 2019, Redis Labs
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "server.h"
#include "connhelpers.h"
/* The connections module provides a lean abstraction of network connections
* to avoid direct socket and async event management across the Redis code base.
*
* It does NOT provide advanced connection features commonly found in similar
* libraries such as complete in/out buffer management, throttling, etc. These
* functions remain in networking.c.
*
* The primary goal is to allow transparent handling of TCP and TLS based
* connections. To do so, connections have the following properties:
*
* 1. A connection may live before its corresponding socket exists. This
* allows various context and configuration setting to be handled before
* establishing the actual connection.
* 2. The caller may register/unregister logical read/write handlers to be
* called when the connection has data to read from/can accept writes.
* These logical handlers may or may not correspond to actual AE events,
* depending on the implementation (for TCP they are; for TLS they aren't).
*/
ConnectionType CT_Socket;
/* When a connection is created we must know its type already, but the
* underlying socket may or may not exist:
*
* - For accepted connections, it exists as we do not model the listen/accept
* part; So caller calls connCreateSocket() followed by connAccept().
* - For outgoing connections, the socket is created by the connection module
* itself; So caller calls connCreateSocket() followed by connConnect(),
* which registers a connect callback that fires on connected/error state
* (and after any transport level handshake was done).
*
* NOTE: An earlier version relied on connections being part of other structs
* and not independently allocated. This could lead to further optimizations
* like using container_of(), etc. However it was discontinued in favor of
* this approach for these reasons:
*
* 1. In some cases conns are created/handled outside the context of the
* containing struct, in which case it gets a bit awkward to copy them.
* 2. Future implementations may wish to allocate arbitrary data for the
* connection.
* 3. The container_of() approach is anyway risky because connections may
* be embedded in different structs, not just client.
*/
connection *connCreateSocket() {
connection *conn = zcalloc(sizeof(connection));
conn->type = &CT_Socket;
conn->fd = -1;
return conn;
}
/* Create a new socket-type connection that is already associated with
* an accepted connection.
*
* The socket is not read for I/O until connAccept() was called and
* invoked the connection-level accept handler.
*/
connection *connCreateAcceptedSocket(int fd) {
connection *conn = connCreateSocket();
conn->fd = fd;
conn->state = CONN_STATE_ACCEPTING;
return conn;
}
static int connSocketConnect(connection *conn, const char *addr, int port, const char *src_addr,
ConnectionCallbackFunc connect_handler) {
int fd = anetTcpNonBlockBestEffortBindConnect(NULL,addr,port,src_addr);
if (fd == -1) {
conn->state = CONN_STATE_ERROR;
conn->last_errno = errno;
return C_ERR;
}
conn->fd = fd;
conn->state = CONN_STATE_CONNECTING;
conn->conn_handler = connect_handler;
aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE,
conn->type->ae_handler, conn);
return C_OK;
}
/* Returns true if a write handler is registered */
int connHasWriteHandler(connection *conn) {
return conn->write_handler != NULL;
}
/* Returns true if a read handler is registered */
int connHasReadHandler(connection *conn) {
return conn->read_handler != NULL;
}
/* Associate a private data pointer with the connection */
void connSetPrivateData(connection *conn, void *data) {
conn->private_data = data;
}
/* Get the associated private data pointer */
void *connGetPrivateData(connection *conn) {
return conn->private_data;
}
/* ------ Pure socket connections ------- */
/* A very incomplete list of implementation-specific calls. Much of the above shall
* move here as we implement additional connection types.
*/
/* Close the connection and free resources. */
static void connSocketClose(connection *conn) {
if (conn->fd != -1) {
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
close(conn->fd);
conn->fd = -1;
}
/* If called from within a handler, schedule the close but
* keep the connection until the handler returns.
*/
if (conn->flags & CONN_FLAG_IN_HANDLER) {
conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
return;
}
zfree(conn);
}
static int connSocketWrite(connection *conn, const void *data, size_t data_len) {
int ret = write(conn->fd, data, data_len);
if (ret < 0 && errno != EAGAIN) {
conn->last_errno = errno;
conn->state = CONN_STATE_ERROR;
}
return ret;
}
static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
int ret = read(conn->fd, buf, buf_len);
if (!ret) {
conn->state = CONN_STATE_CLOSED;
} else if (ret < 0 && errno != EAGAIN) {
conn->last_errno = errno;
conn->state = CONN_STATE_ERROR;
}
return ret;
}
static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
conn->state = CONN_STATE_CONNECTED;
if (!callHandler(conn, accept_handler)) return C_ERR;
return C_OK;
}
/* Register a write handler, to be called when the connection is writable.
* If NULL, the existing handler is removed.
*
* The barrier flag indicates a write barrier is requested, resulting with
* CONN_FLAG_WRITE_BARRIER set. This will ensure that the write handler is
* always called before and not after the read handler in a single event
* loop.
*/
static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
if (func == conn->write_handler) return C_OK;
conn->write_handler = func;
if (barrier)
conn->flags |= CONN_FLAG_WRITE_BARRIER;
else
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
if (!conn->write_handler)
aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
else
if (aeCreateFileEvent(server.el,conn->fd,AE_WRITABLE,
conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
return C_OK;
}
/* Register a read handler, to be called when the connection is readable.
* If NULL, the existing handler is removed.
*/
static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
if (func == conn->read_handler) return C_OK;
conn->read_handler = func;
if (!conn->read_handler)
aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
else
if (aeCreateFileEvent(server.el,conn->fd,
AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
return C_OK;
}
static const char *connSocketGetLastError(connection *conn) {
return strerror(conn->last_errno);
}
static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask)
{
UNUSED(el);
UNUSED(fd);
connection *conn = clientData;
if (conn->state == CONN_STATE_CONNECTING &&
(mask & AE_WRITABLE) && conn->conn_handler) {
if (connGetSocketError(conn)) {
conn->last_errno = errno;
conn->state = CONN_STATE_ERROR;
} else {
conn->state = CONN_STATE_CONNECTED;
}
if (!conn->write_handler) aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
if (!callHandler(conn, conn->conn_handler)) return;
conn->conn_handler = NULL;
}
/* Normally we execute the readable event first, and the writable
* event later. This is useful as sometimes we may be able
* to serve the reply of a query immediately after processing the
* query.
*
* However if WRITE_BARRIER is set in the mask, our application is
* asking us to do the reverse: never fire the writable event
* after the readable. In such a case, we invert the calls.
* This is useful when, for instance, we want to do things
* in the beforeSleep() hook, like fsync'ing a file to disk,
* before replying to a client. */
int invert = conn->flags & CONN_FLAG_WRITE_BARRIER;
int call_write = (mask & AE_WRITABLE) && conn->write_handler;
int call_read = (mask & AE_READABLE) && conn->read_handler;
/* Handle normal I/O flows */
if (!invert && call_read) {
if (!callHandler(conn, conn->read_handler)) return;
}
/* Fire the writable event. */
if (call_write) {
if (!callHandler(conn, conn->write_handler)) return;
}
/* If we have to invert the call, fire the readable event now
* after the writable one. */
if (invert && call_read) {
if (!callHandler(conn, conn->read_handler)) return;
}
}
static int connSocketBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
int fd = anetTcpNonBlockConnect(NULL,addr,port);
if (fd == -1) {
conn->state = CONN_STATE_ERROR;
conn->last_errno = errno;
return C_ERR;
}
if ((aeWait(fd, AE_WRITABLE, timeout) & AE_WRITABLE) == 0) {
conn->state = CONN_STATE_ERROR;
conn->last_errno = ETIMEDOUT;
}
conn->fd = fd;
conn->state = CONN_STATE_CONNECTED;
return C_OK;
}
/* Connection-based versions of syncio.c functions.
* NOTE: This should ideally be refactored out in favor of pure async work.
*/
static ssize_t connSocketSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
return syncWrite(conn->fd, ptr, size, timeout);
}
static ssize_t connSocketSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
return syncRead(conn->fd, ptr, size, timeout);
}
static ssize_t connSocketSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
return syncReadLine(conn->fd, ptr, size, timeout);
}
ConnectionType CT_Socket = {
.ae_handler = connSocketEventHandler,
.close = connSocketClose,
.write = connSocketWrite,
.read = connSocketRead,
.accept = connSocketAccept,
.connect = connSocketConnect,
.set_write_handler = connSocketSetWriteHandler,
.set_read_handler = connSocketSetReadHandler,
.get_last_error = connSocketGetLastError,
.blocking_connect = connSocketBlockingConnect,
.sync_write = connSocketSyncWrite,
.sync_read = connSocketSyncRead,
.sync_readline = connSocketSyncReadLine
};
int connGetSocketError(connection *conn) {
int sockerr = 0;
socklen_t errlen = sizeof(sockerr);
if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
sockerr = errno;
return sockerr;
}
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
return anetPeerToString(conn ? conn->fd : -1, ip, ip_len, port);
}
int connFormatPeer(connection *conn, char *buf, size_t buf_len) {
return anetFormatPeer(conn ? conn->fd : -1, buf, buf_len);
}
int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
return anetSockName(conn->fd, ip, ip_len, port);
}
int connBlock(connection *conn) {
if (conn->fd == -1) return C_ERR;
return anetBlock(NULL, conn->fd);
}
int connNonBlock(connection *conn) {
if (conn->fd == -1) return C_ERR;
return anetNonBlock(NULL, conn->fd);
}
int connEnableTcpNoDelay(connection *conn) {
if (conn->fd == -1) return C_ERR;
return anetEnableTcpNoDelay(NULL, conn->fd);
}
int connDisableTcpNoDelay(connection *conn) {
if (conn->fd == -1) return C_ERR;
return anetDisableTcpNoDelay(NULL, conn->fd);
}
int connKeepAlive(connection *conn, int interval) {
if (conn->fd == -1) return C_ERR;
return anetKeepAlive(NULL, conn->fd, interval);
}
int connSendTimeout(connection *conn, long long ms) {
return anetSendTimeout(NULL, conn->fd, ms);
}
int connRecvTimeout(connection *conn, long long ms) {
return anetRecvTimeout(NULL, conn->fd, ms);
}
int connGetState(connection *conn) {
return conn->state;
}
/* Return a text that describes the connection, suitable for inclusion
* in CLIENT LIST and similar outputs.
*
* For sockets, we always return "fd=<fdnum>" to maintain compatibility.
*/
const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
snprintf(buf, buf_len-1, "fd=%i", conn->fd);
return buf;
}
/*
* Copyright (c) 2019, Redis Labs
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __REDIS_CONNECTION_H
#define __REDIS_CONNECTION_H
#define CONN_INFO_LEN 32
struct aeEventLoop;
typedef struct connection connection;
typedef enum {
CONN_STATE_NONE = 0,
CONN_STATE_CONNECTING,
CONN_STATE_ACCEPTING,
CONN_STATE_CONNECTED,
CONN_STATE_CLOSED,
CONN_STATE_ERROR
} ConnectionState;
#define CONN_FLAG_IN_HANDLER (1<<0) /* A handler execution is in progress */
#define CONN_FLAG_CLOSE_SCHEDULED (1<<1) /* Closed scheduled by a handler */
#define CONN_FLAG_WRITE_BARRIER (1<<2) /* Write barrier requested */
typedef void (*ConnectionCallbackFunc)(struct connection *conn);
typedef struct ConnectionType {
void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask);
int (*connect)(struct connection *conn, const char *addr, int port, const char *source_addr, ConnectionCallbackFunc connect_handler);
int (*write)(struct connection *conn, const void *data, size_t data_len);
int (*read)(struct connection *conn, void *buf, size_t buf_len);
void (*close)(struct connection *conn);
int (*accept)(struct connection *conn, ConnectionCallbackFunc accept_handler);
int (*set_write_handler)(struct connection *conn, ConnectionCallbackFunc handler, int barrier);
int (*set_read_handler)(struct connection *conn, ConnectionCallbackFunc handler);
const char *(*get_last_error)(struct connection *conn);
int (*blocking_connect)(struct connection *conn, const char *addr, int port, long long timeout);
ssize_t (*sync_write)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
} ConnectionType;
struct connection {
ConnectionType *type;
ConnectionState state;
int flags;
int last_errno;
void *private_data;
ConnectionCallbackFunc conn_handler;
ConnectionCallbackFunc write_handler;
ConnectionCallbackFunc read_handler;
int fd;
};
/* The connection module does not deal with listening and accepting sockets,
* so we assume we have a socket when an incoming connection is created.
*
* The fd supplied should therefore be associated with an already accept()ed
* socket.
*
* connAccept() may directly call accept_handler(), or return and call it
* at a later time. This behavior is a bit awkward but aims to reduce the need
* to wait for the next event loop, if no additional handshake is required.
*/
static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
return conn->type->accept(conn, accept_handler);
}
/* Establish a connection. The connect_handler will be called when the connection
* is established, or if an error has occured.
*
* The connection handler will be responsible to set up any read/write handlers
* as needed.
*
* If C_ERR is returned, the operation failed and the connection handler shall
* not be expected.
*/
static inline int connConnect(connection *conn, const char *addr, int port, const char *src_addr,
ConnectionCallbackFunc connect_handler) {
return conn->type->connect(conn, addr, port, src_addr, connect_handler);
}
/* Blocking connect.
*
* NOTE: This is implemented in order to simplify the transition to the abstract
* connections, but should probably be refactored out of cluster.c and replication.c,
* in favor of a pure async implementation.
*/
static inline int connBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
return conn->type->blocking_connect(conn, addr, port, timeout);
}
/* Write to connection, behaves the same as write(2).
*
* Like write(2), a short write is possible. A -1 return indicates an error.
*
* The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
* connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
*/
static inline int connWrite(connection *conn, const void *data, size_t data_len) {
return conn->type->write(conn, data, data_len);
}
/* Read from the connection, behaves the same as read(2).
*
* Like read(2), a short read is possible. A return value of 0 will indicate the
* connection was closed, and -1 will indicate an error.
*
* The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
* connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
*/
static inline int connRead(connection *conn, void *buf, size_t buf_len) {
return conn->type->read(conn, buf, buf_len);
}
/* Register a write handler, to be called when the connection is writable.
* If NULL, the existing handler is removed.
*/
static inline int connSetWriteHandler(connection *conn, ConnectionCallbackFunc func) {
return conn->type->set_write_handler(conn, func, 0);
}
/* Register a read handler, to be called when the connection is readable.
* If NULL, the existing handler is removed.
*/
static inline int connSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
return conn->type->set_read_handler(conn, func);
}
/* Set a write handler, and possibly enable a write barrier, this flag is
* cleared when write handler is changed or removed.
* With barroer enabled, we never fire the event if the read handler already
* fired in the same event loop iteration. Useful when you want to persist
* things to disk before sending replies, and want to do that in a group fashion. */
static inline int connSetWriteHandlerWithBarrier(connection *conn, ConnectionCallbackFunc func, int barrier) {
return conn->type->set_write_handler(conn, func, barrier);
}
static inline void connClose(connection *conn) {
conn->type->close(conn);
}
/* Returns the last error encountered by the connection, as a string. If no error,
* a NULL is returned.
*/
static inline const char *connGetLastError(connection *conn) {
return conn->type->get_last_error(conn);
}
static inline ssize_t connSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
return conn->type->sync_write(conn, ptr, size, timeout);
}
static inline ssize_t connSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
return conn->type->sync_read(conn, ptr, size, timeout);
}
static inline ssize_t connSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
return conn->type->sync_readline(conn, ptr, size, timeout);
}
connection *connCreateSocket();
connection *connCreateAcceptedSocket(int fd);
connection *connCreateTLS();
connection *connCreateAcceptedTLS(int fd, int require_auth);
void connSetPrivateData(connection *conn, void *data);
void *connGetPrivateData(connection *conn);
int connGetState(connection *conn);
int connHasWriteHandler(connection *conn);
int connHasReadHandler(connection *conn);
int connGetSocketError(connection *conn);
/* anet-style wrappers to conns */
int connBlock(connection *conn);
int connNonBlock(connection *conn);
int connEnableTcpNoDelay(connection *conn);
int connDisableTcpNoDelay(connection *conn);
int connKeepAlive(connection *conn, int interval);
int connSendTimeout(connection *conn, long long ms);
int connRecvTimeout(connection *conn, long long ms);
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port);
int connFormatPeer(connection *conn, char *buf, size_t buf_len);
int connSockName(connection *conn, char *ip, size_t ip_len, int *port);
const char *connGetInfo(connection *conn, char *buf, size_t buf_len);
/* Helpers for tls special considerations */
int tlsHasPendingData();
void tlsProcessPendingData();
#endif /* __REDIS_CONNECTION_H */
/*
* Copyright (c) 2019, Redis Labs
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __REDIS_CONNHELPERS_H
#define __REDIS_CONNHELPERS_H
#include "connection.h"
/* These are helper functions that are common to different connection
* implementations (currently sockets in connection.c and TLS in tls.c).
*
* Currently helpers implement the mechanisms for invoking connection
* handlers, tracking in-handler states and dealing with deferred
* destruction (if invoked by a handler).
*/
/* Called whenever a handler is invoked on a connection and sets the
* CONN_FLAG_IN_HANDLER flag to indicate we're in a handler context.
*
* An attempt to close a connection while CONN_FLAG_IN_HANDLER is
* set will result with deferred close, i.e. setting the CONN_FLAG_CLOSE_SCHEDULED
* instead of destructing it.
*/
static inline void enterHandler(connection *conn) {
conn->flags |= CONN_FLAG_IN_HANDLER;
}
/* Called whenever a handler returns. This unsets the CONN_FLAG_IN_HANDLER
* flag and performs actual close/destruction if a deferred close was
* scheduled by the handler.
*/
static inline int exitHandler(connection *conn) {
conn->flags &= ~CONN_FLAG_IN_HANDLER;
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
connClose(conn);
return 0;
}
return 1;
}
/* Helper for connection implementations to call handlers:
* 1. Mark the handler in use.
* 2. Execute the handler (if set).
* 3. Mark the handler as NOT in use and perform deferred close if was
* requested by the handler at any time.
*/
static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
conn->flags |= CONN_FLAG_IN_HANDLER;
if (handler) handler(conn);
conn->flags &= ~CONN_FLAG_IN_HANDLER;
if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
connClose(conn);
return 0;
}
return 1;
}
#endif /* __REDIS_CONNHELPERS_H */
...@@ -60,10 +60,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) { ...@@ -60,10 +60,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
/* Update the access time for the ageing algorithm. /* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger * Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */ * a copy on write madness. */
if (server.rdb_child_pid == -1 && if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
server.aof_child_pid == -1 &&
!(flags & LOOKUP_NOTOUCH))
{
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) { if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
updateLFU(val); updateLFU(val);
} else { } else {
...@@ -154,9 +151,13 @@ robj *lookupKeyRead(redisDb *db, robj *key) { ...@@ -154,9 +151,13 @@ robj *lookupKeyRead(redisDb *db, robj *key) {
* *
* Returns the linked value object if the key exists or NULL if the key * Returns the linked value object if the key exists or NULL if the key
* does not exist in the specified DB. */ * does not exist in the specified DB. */
robj *lookupKeyWrite(redisDb *db, robj *key) { robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
expireIfNeeded(db,key); expireIfNeeded(db,key);
return lookupKey(db,key,LOOKUP_NONE); return lookupKey(db,key,flags);
}
robj *lookupKeyWrite(redisDb *db, robj *key) {
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
} }
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) { robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
...@@ -353,6 +354,12 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)( ...@@ -353,6 +354,12 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
return -1; return -1;
} }
/* Fire the flushdb modules event. */
RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum};
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
REDISMODULE_SUBEVENT_FLUSHDB_START,
&fi);
/* Make sure the WATCHed keys are affected by the FLUSH* commands. /* Make sure the WATCHed keys are affected by the FLUSH* commands.
* Note that we need to call the function while the keys are still * Note that we need to call the function while the keys are still
* there. */ * there. */
...@@ -383,6 +390,13 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)( ...@@ -383,6 +390,13 @@ long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(
} }
} }
if (dbnum == -1) flushSlaveKeysWithExpireList(); if (dbnum == -1) flushSlaveKeysWithExpireList();
/* Also fire the end event. Note that this event will fire almost
* immediately after the start event if the flush is asynchronous. */
moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
REDISMODULE_SUBEVENT_FLUSHDB_END,
&fi);
return removed; return removed;
} }
...@@ -451,6 +465,29 @@ int getFlushCommandFlags(client *c, int *flags) { ...@@ -451,6 +465,29 @@ int getFlushCommandFlags(client *c, int *flags) {
return C_OK; return C_OK;
} }
/* Flushes the whole server data set. */
void flushAllDataAndResetRDB(int flags) {
server.dirty += emptyDb(-1,flags,NULL);
if (server.rdb_child_pid != -1) killRDBChild();
if (server.saveparamslen > 0) {
/* Normally rdbSave() will reset dirty, but we don't want this here
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
int saved_dirty = server.dirty;
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
rdbSave(server.rdb_filename,rsiptr);
server.dirty = saved_dirty;
}
server.dirty++;
#if defined(USE_JEMALLOC)
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
* for large databases, flushdb blocks for long anyway, so a bit more won't
* harm and this way the flush and purge will be synchroneus. */
if (!(flags & EMPTYDB_ASYNC))
jemalloc_purge();
#endif
}
/* FLUSHDB [ASYNC] /* FLUSHDB [ASYNC]
* *
* Flushes the currently SELECTed Redis DB. */ * Flushes the currently SELECTed Redis DB. */
...@@ -460,6 +497,13 @@ void flushdbCommand(client *c) { ...@@ -460,6 +497,13 @@ void flushdbCommand(client *c) {
if (getFlushCommandFlags(c,&flags) == C_ERR) return; if (getFlushCommandFlags(c,&flags) == C_ERR) return;
server.dirty += emptyDb(c->db->id,flags,NULL); server.dirty += emptyDb(c->db->id,flags,NULL);
addReply(c,shared.ok); addReply(c,shared.ok);
#if defined(USE_JEMALLOC)
/* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
* for large databases, flushdb blocks for long anyway, so a bit more won't
* harm and this way the flush and purge will be synchroneus. */
if (!(flags & EMPTYDB_ASYNC))
jemalloc_purge();
#endif
} }
/* FLUSHALL [ASYNC] /* FLUSHALL [ASYNC]
...@@ -467,21 +511,9 @@ void flushdbCommand(client *c) { ...@@ -467,21 +511,9 @@ void flushdbCommand(client *c) {
* Flushes the whole server data set. */ * Flushes the whole server data set. */
void flushallCommand(client *c) { void flushallCommand(client *c) {
int flags; int flags;
if (getFlushCommandFlags(c,&flags) == C_ERR) return; if (getFlushCommandFlags(c,&flags) == C_ERR) return;
server.dirty += emptyDb(-1,flags,NULL); flushAllDataAndResetRDB(flags);
addReply(c,shared.ok); addReply(c,shared.ok);
if (server.rdb_child_pid != -1) killRDBChild();
if (server.saveparamslen > 0) {
/* Normally rdbSave() will reset dirty, but we don't want this here
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
int saved_dirty = server.dirty;
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
rdbSave(server.rdb_filename,rsiptr);
server.dirty = saved_dirty;
}
server.dirty++;
} }
/* This command implements DEL and LAZYDEL. */ /* This command implements DEL and LAZYDEL. */
......
...@@ -297,6 +297,56 @@ void computeDatasetDigest(unsigned char *final) { ...@@ -297,6 +297,56 @@ void computeDatasetDigest(unsigned char *final) {
} }
} }
#ifdef USE_JEMALLOC
void mallctl_int(client *c, robj **argv, int argc) {
int ret;
/* start with the biggest size (int64), and if that fails, try smaller sizes (int32, bool) */
int64_t old = 0, val;
if (argc > 1) {
long long ll;
if (getLongLongFromObjectOrReply(c, argv[1], &ll, NULL) != C_OK)
return;
val = ll;
}
size_t sz = sizeof(old);
while (sz > 0) {
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, argc > 1? &val: NULL, argc > 1?sz: 0))) {
if (ret==EINVAL) {
/* size might be wrong, try a smaller one */
sz /= 2;
#if BYTE_ORDER == BIG_ENDIAN
val <<= 8*sz;
#endif
continue;
}
addReplyErrorFormat(c,"%s", strerror(ret));
return;
} else {
#if BYTE_ORDER == BIG_ENDIAN
old >>= 64 - 8*sz;
#endif
addReplyLongLong(c, old);
return;
}
}
addReplyErrorFormat(c,"%s", strerror(EINVAL));
}
void mallctl_string(client *c, robj **argv, int argc) {
int ret;
char *old;
size_t sz = sizeof(old);
/* for strings, it seems we need to first get the old value, before overriding it. */
if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) {
addReplyErrorFormat(c,"%s", strerror(ret));
return;
}
addReplyBulkCString(c, old);
if(argc > 1)
je_mallctl(argv[0]->ptr, NULL, 0, &argv[1]->ptr, sizeof(char*));
}
#endif
void debugCommand(client *c) { void debugCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
...@@ -319,10 +369,15 @@ void debugCommand(client *c) { ...@@ -319,10 +369,15 @@ void debugCommand(client *c) {
"SDSLEN <key> -- Show low level SDS string info representing key and value.", "SDSLEN <key> -- Show low level SDS string info representing key and value.",
"SEGFAULT -- Crash the server with sigsegv.", "SEGFAULT -- Crash the server with sigsegv.",
"SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.", "SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
"AOF-FLUSH-SLEEP <microsec> -- Server will sleep before flushing the AOF, this is used for testing",
"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.", "SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
"STRUCTSIZE -- Return the size of different Redis core C structures.", "STRUCTSIZE -- Return the size of different Redis core C structures.",
"ZIPLIST <key> -- Show low level info about the ziplist encoding.", "ZIPLIST <key> -- Show low level info about the ziplist encoding.",
"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.", "STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
#ifdef USE_JEMALLOC
"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.",
"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.",
#endif
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
...@@ -362,7 +417,7 @@ NULL ...@@ -362,7 +417,7 @@ NULL
} }
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL); emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
protectClient(c); protectClient(c);
int ret = rdbLoad(server.rdb_filename,NULL); int ret = rdbLoad(server.rdb_filename,NULL,RDBFLAGS_NONE);
unprotectClient(c); unprotectClient(c);
if (ret != C_OK) { if (ret != C_OK) {
addReplyError(c,"Error trying to load the RDB dump"); addReplyError(c,"Error trying to load the RDB dump");
...@@ -595,6 +650,11 @@ NULL ...@@ -595,6 +650,11 @@ NULL
{ {
server.active_expire_enabled = atoi(c->argv[2]->ptr); server.active_expire_enabled = atoi(c->argv[2]->ptr);
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"aof-flush-sleep") &&
c->argc == 3)
{
server.aof_flush_sleep = atoi(c->argv[2]->ptr);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"lua-always-replicate-commands") && } else if (!strcasecmp(c->argv[1]->ptr,"lua-always-replicate-commands") &&
c->argc == 3) c->argc == 3)
{ {
...@@ -638,7 +698,8 @@ NULL ...@@ -638,7 +698,8 @@ NULL
dictGetStats(buf,sizeof(buf),server.db[dbid].expires); dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
stats = sdscat(stats,buf); stats = sdscat(stats,buf);
addReplyBulkSds(c,stats); addReplyVerbatim(c,stats,sdslen(stats),"txt");
sdsfree(stats);
} else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
robj *o; robj *o;
dict *ht = NULL; dict *ht = NULL;
...@@ -665,7 +726,7 @@ NULL ...@@ -665,7 +726,7 @@ NULL
} else { } else {
char buf[4096]; char buf[4096];
dictGetStats(buf,sizeof(buf),ht); dictGetStats(buf,sizeof(buf),ht);
addReplyBulkCString(c,buf); addReplyVerbatim(c,buf,strlen(buf),"txt");
} }
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id"); serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
...@@ -676,6 +737,14 @@ NULL ...@@ -676,6 +737,14 @@ NULL
{ {
stringmatchlen_fuzz_test(); stringmatchlen_fuzz_test();
addReplyStatus(c,"Apparently Redis did not crash: test passed"); addReplyStatus(c,"Apparently Redis did not crash: test passed");
#ifdef USE_JEMALLOC
} else if(!strcasecmp(c->argv[1]->ptr,"mallctl") && c->argc >= 3) {
mallctl_int(c, c->argv+2, c->argc-2);
return;
} else if(!strcasecmp(c->argv[1]->ptr,"mallctl-str") && c->argc >= 3) {
mallctl_string(c, c->argv+2, c->argc-2);
return;
#endif
} else { } else {
addReplySubcommandSyntaxError(c); addReplySubcommandSyntaxError(c);
return; return;
...@@ -699,11 +768,12 @@ void _serverAssert(const char *estr, const char *file, int line) { ...@@ -699,11 +768,12 @@ void _serverAssert(const char *estr, const char *file, int line) {
void _serverAssertPrintClientInfo(const client *c) { void _serverAssertPrintClientInfo(const client *c) {
int j; int j;
char conninfo[CONN_INFO_LEN];
bugReportStart(); bugReportStart();
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ==="); serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long)c->flags); serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long) c->flags);
serverLog(LL_WARNING,"client->fd = %d", c->fd); serverLog(LL_WARNING,"client->conn = %s", connGetInfo(c->conn, conninfo, sizeof(conninfo)));
serverLog(LL_WARNING,"client->argc = %d", c->argc); serverLog(LL_WARNING,"client->argc = %d", c->argc);
for (j=0; j < c->argc; j++) { for (j=0; j < c->argc; j++) {
char buf[128]; char buf[128];
...@@ -1110,6 +1180,33 @@ void logRegisters(ucontext_t *uc) { ...@@ -1110,6 +1180,33 @@ void logRegisters(ucontext_t *uc) {
(unsigned long) uc->uc_mcontext.mc_cs (unsigned long) uc->uc_mcontext.mc_cs
); );
logStackContent((void**)uc->uc_mcontext.mc_rsp); logStackContent((void**)uc->uc_mcontext.mc_rsp);
#elif defined(__aarch64__) /* Linux AArch64 */
serverLog(LL_WARNING,
"\n"
"X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
"X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
"X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
"X30:%016lx\n"
"pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
(unsigned long) uc->uc_mcontext.regs[18],
(unsigned long) uc->uc_mcontext.regs[19],
(unsigned long) uc->uc_mcontext.regs[20],
(unsigned long) uc->uc_mcontext.regs[21],
(unsigned long) uc->uc_mcontext.regs[22],
(unsigned long) uc->uc_mcontext.regs[23],
(unsigned long) uc->uc_mcontext.regs[24],
(unsigned long) uc->uc_mcontext.regs[25],
(unsigned long) uc->uc_mcontext.regs[26],
(unsigned long) uc->uc_mcontext.regs[27],
(unsigned long) uc->uc_mcontext.regs[28],
(unsigned long) uc->uc_mcontext.regs[29],
(unsigned long) uc->uc_mcontext.regs[30],
(unsigned long) uc->uc_mcontext.pc,
(unsigned long) uc->uc_mcontext.sp,
(unsigned long) uc->uc_mcontext.pstate,
(unsigned long) uc->uc_mcontext.fault_address
);
logStackContent((void**)uc->uc_mcontext.sp);
#else #else
serverLog(LL_WARNING, serverLog(LL_WARNING,
" Dumping of registers not supported for this OS/arch"); " Dumping of registers not supported for this OS/arch");
...@@ -1337,6 +1434,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) { ...@@ -1337,6 +1434,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
/* Log dump of processor registers */ /* Log dump of processor registers */
logRegisters(uc); logRegisters(uc);
/* Log Modules INFO */
serverLogRaw(LL_WARNING|LL_RAW, "\n------ MODULES INFO OUTPUT ------\n");
infostring = modulesCollectInfo(sdsempty(), NULL, 1, 0);
serverLogRaw(LL_WARNING|LL_RAW, infostring);
sdsfree(infostring);
#if defined(HAVE_PROC_MAPS) #if defined(HAVE_PROC_MAPS)
/* Test memory */ /* Test memory */
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n"); serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
......
...@@ -374,7 +374,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) { ...@@ -374,7 +374,7 @@ long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
if ((newele = activeDefragStringOb(ele, &defragged))) if ((newele = activeDefragStringOb(ele, &defragged)))
de->v.val = newele, defragged++; de->v.val = newele, defragged++;
} else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) { } else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
void *newptr, *ptr = ln->value; void *newptr, *ptr = dictGetVal(de);
if ((newptr = activeDefragAlloc(ptr))) if ((newptr = activeDefragAlloc(ptr)))
ln->value = newptr, defragged++; ln->value = newptr, defragged++;
} }
...@@ -1039,7 +1039,7 @@ void activeDefragCycle(void) { ...@@ -1039,7 +1039,7 @@ void activeDefragCycle(void) {
mstime_t latency; mstime_t latency;
int quit = 0; int quit = 0;
if (server.aof_child_pid!=-1 || server.rdb_child_pid!=-1) if (hasActiveChildProcess())
return; /* Defragging memory while there's a fork will just do damage. */ return; /* Defragging memory while there's a fork will just do damage. */
/* Once a second, check if we the fragmentation justfies starting a scan /* Once a second, check if we the fragmentation justfies starting a scan
......
...@@ -444,6 +444,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev ...@@ -444,6 +444,7 @@ int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *lev
* Otehrwise if we are over the memory limit, but not enough memory * Otehrwise if we are over the memory limit, but not enough memory
* was freed to return back under the limit, the function returns C_ERR. */ * was freed to return back under the limit, the function returns C_ERR. */
int freeMemoryIfNeeded(void) { int freeMemoryIfNeeded(void) {
int keys_freed = 0;
/* By default replicas should ignore maxmemory /* By default replicas should ignore maxmemory
* and just be masters exact copies. */ * and just be masters exact copies. */
if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK; if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
...@@ -467,7 +468,7 @@ int freeMemoryIfNeeded(void) { ...@@ -467,7 +468,7 @@ int freeMemoryIfNeeded(void) {
latencyStartMonitor(latency); latencyStartMonitor(latency);
while (mem_freed < mem_tofree) { while (mem_freed < mem_tofree) {
int j, k, i, keys_freed = 0; int j, k, i;
static unsigned int next_db = 0; static unsigned int next_db = 0;
sds bestkey = NULL; sds bestkey = NULL;
int bestdbid; int bestdbid;
...@@ -598,9 +599,7 @@ int freeMemoryIfNeeded(void) { ...@@ -598,9 +599,7 @@ int freeMemoryIfNeeded(void) {
mem_freed = mem_tofree; mem_freed = mem_tofree;
} }
} }
} } else {
if (!keys_freed) {
latencyEndMonitor(latency); latencyEndMonitor(latency);
latencyAddSampleIfNeeded("eviction-cycle",latency); latencyAddSampleIfNeeded("eviction-cycle",latency);
goto cant_free; /* nothing to free... */ goto cant_free; /* nothing to free... */
......
...@@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) { ...@@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) {
/* Look up the requested zset */ /* Look up the requested zset */
robj *zobj = NULL; robj *zobj = NULL;
if ((zobj = lookupKeyReadOrReply(c, key, shared.null[c->resp])) == NULL || if ((zobj = lookupKeyReadOrReply(c, key, shared.emptyarray)) == NULL ||
checkType(c, zobj, OBJ_ZSET)) { checkType(c, zobj, OBJ_ZSET)) {
return; return;
} }
...@@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) { ...@@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
/* If no matching results, the user gets an empty reply. */ /* If no matching results, the user gets an empty reply. */
if (ga->used == 0 && storekey == NULL) { if (ga->used == 0 && storekey == NULL) {
addReplyNull(c); addReply(c,shared.emptyarray);
geoArrayFree(ga); geoArrayFree(ga);
return; return;
} }
...@@ -734,14 +734,14 @@ void geohashCommand(client *c) { ...@@ -734,14 +734,14 @@ void geohashCommand(client *c) {
r[1].max = 90; r[1].max = 90;
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash); geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
char buf[12]; char buf[11];
int i; int i;
for (i = 0; i < 11; i++) { for (i = 0; i < 10; i++) {
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f; int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
buf[i] = geoalphabet[idx]; buf[i] = geoalphabet[idx];
} }
buf[11] = '\0'; buf[10] = '\0';
addReplyBulkCBuffer(c,buf,11); addReplyBulkCBuffer(c,buf,10);
} }
} }
} }
......
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