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
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
#include <sys/stat.h> #include <sys/stat.h>
void replicationDiscardCachedMaster(void); void replicationDiscardCachedMaster(void);
void replicationResurrectCachedMaster(int newfd); void replicationResurrectCachedMaster(connection *conn);
void replicationSendAck(void); void replicationSendAck(void);
void putSlaveOnline(client *slave); void putSlaveOnline(client *slave);
int cancelReplicationHandshake(void); int cancelReplicationHandshake(void);
...@@ -57,7 +57,7 @@ char *replicationGetSlaveName(client *c) { ...@@ -57,7 +57,7 @@ char *replicationGetSlaveName(client *c) {
ip[0] = '\0'; ip[0] = '\0';
buf[0] = '\0'; buf[0] = '\0';
if (c->slave_ip[0] != '\0' || if (c->slave_ip[0] != '\0' ||
anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1) connPeerToString(c->conn,ip,sizeof(ip),NULL) != -1)
{ {
/* Note that the 'ip' buffer is always larger than 'c->slave_ip' */ /* Note that the 'ip' buffer is always larger than 'c->slave_ip' */
if (c->slave_ip[0] != '\0') memcpy(ip,c->slave_ip,sizeof(c->slave_ip)); if (c->slave_ip[0] != '\0') memcpy(ip,c->slave_ip,sizeof(c->slave_ip));
...@@ -256,7 +256,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) { ...@@ -256,7 +256,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
client *slave = ln->value; client *slave = ln->value;
/* Don't feed slaves that are still waiting for BGSAVE to start */ /* Don't feed slaves that are still waiting for BGSAVE to start. */
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue; if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
/* Feed slaves that are waiting for the initial SYNC (so these commands /* Feed slaves that are waiting for the initial SYNC (so these commands
...@@ -295,7 +295,7 @@ void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t bufle ...@@ -295,7 +295,7 @@ void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t bufle
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
client *slave = ln->value; client *slave = ln->value;
/* Don't feed slaves that are still waiting for BGSAVE to start */ /* Don't feed slaves that are still waiting for BGSAVE to start. */
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue; if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
addReplyProto(slave,buf,buflen); addReplyProto(slave,buf,buflen);
} }
...@@ -432,7 +432,7 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) { ...@@ -432,7 +432,7 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) {
if (!(slave->flags & CLIENT_PRE_PSYNC)) { if (!(slave->flags & CLIENT_PRE_PSYNC)) {
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n", buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
server.replid,offset); server.replid,offset);
if (write(slave->fd,buf,buflen) != buflen) { if (connWrite(slave->conn,buf,buflen) != buflen) {
freeClientAsync(slave); freeClientAsync(slave);
return C_ERR; return C_ERR;
} }
...@@ -519,7 +519,7 @@ int masterTryPartialResynchronization(client *c) { ...@@ -519,7 +519,7 @@ int masterTryPartialResynchronization(client *c) {
} else { } else {
buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n"); buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
} }
if (write(c->fd,buf,buflen) != buflen) { if (connWrite(c->conn,buf,buflen) != buflen) {
freeClientAsync(c); freeClientAsync(c);
return C_OK; return C_OK;
} }
...@@ -533,6 +533,12 @@ int masterTryPartialResynchronization(client *c) { ...@@ -533,6 +533,12 @@ int masterTryPartialResynchronization(client *c) {
* has this state from the previous connection with the master. */ * has this state from the previous connection with the master. */
refreshGoodSlavesCount(); refreshGoodSlavesCount();
/* Fire the replica change modules event. */
moduleFireServerEvent(REDISMODULE_EVENT_REPLICA_CHANGE,
REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE,
NULL);
return C_OK; /* The caller can return, no full resync needed. */ return C_OK; /* The caller can return, no full resync needed. */
need_full_resync: need_full_resync:
...@@ -585,7 +591,7 @@ int startBgsaveForReplication(int mincapa) { ...@@ -585,7 +591,7 @@ int startBgsaveForReplication(int mincapa) {
} }
/* If we failed to BGSAVE, remove the slaves waiting for a full /* If we failed to BGSAVE, remove the slaves waiting for a full
* resynchorinization from the list of salves, inform them with * resynchronization from the list of slaves, inform them with
* an error about what happened, close the connection ASAP. */ * an error about what happened, close the connection ASAP. */
if (retval == C_ERR) { if (retval == C_ERR) {
serverLog(LL_WARNING,"BGSAVE for replication failed"); serverLog(LL_WARNING,"BGSAVE for replication failed");
...@@ -606,7 +612,7 @@ int startBgsaveForReplication(int mincapa) { ...@@ -606,7 +612,7 @@ int startBgsaveForReplication(int mincapa) {
} }
/* If the target is socket, rdbSaveToSlavesSockets() already setup /* If the target is socket, rdbSaveToSlavesSockets() already setup
* the salves for a full resync. Otherwise for disk target do it now.*/ * the slaves for a full resync. Otherwise for disk target do it now.*/
if (!socket_target) { if (!socket_target) {
listRewind(server.slaves,&li); listRewind(server.slaves,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
...@@ -685,7 +691,7 @@ void syncCommand(client *c) { ...@@ -685,7 +691,7 @@ void syncCommand(client *c) {
* paths will change the state if we handle the slave differently. */ * paths will change the state if we handle the slave differently. */
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START; c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
if (server.repl_disable_tcp_nodelay) if (server.repl_disable_tcp_nodelay)
anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */ connDisableTcpNoDelay(c->conn); /* Non critical if it fails. */
c->repldbfd = -1; c->repldbfd = -1;
c->flags |= CLIENT_SLAVE; c->flags |= CLIENT_SLAVE;
listAddNodeTail(server.slaves,c); listAddNodeTail(server.slaves,c);
...@@ -751,11 +757,11 @@ void syncCommand(client *c) { ...@@ -751,11 +757,11 @@ void syncCommand(client *c) {
/* Target is disk (or the slave is not capable of supporting /* Target is disk (or the slave is not capable of supporting
* diskless replication) and we don't have a BGSAVE in progress, * diskless replication) and we don't have a BGSAVE in progress,
* let's start one. */ * let's start one. */
if (server.aof_child_pid == -1) { if (!hasActiveChildProcess()) {
startBgsaveForReplication(c->slave_capa); startBgsaveForReplication(c->slave_capa);
} else { } else {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"No BGSAVE in progress, but an AOF rewrite is active. " "No BGSAVE in progress, but another BG operation is active. "
"BGSAVE for replication delayed"); "BGSAVE for replication delayed");
} }
} }
...@@ -862,21 +868,22 @@ void putSlaveOnline(client *slave) { ...@@ -862,21 +868,22 @@ void putSlaveOnline(client *slave) {
slave->replstate = SLAVE_STATE_ONLINE; slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 0; slave->repl_put_online_on_ack = 0;
slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */ slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, if (connSetWriteHandler(slave->conn, sendReplyToClient) == C_ERR) {
sendReplyToClient, slave) == AE_ERR) {
serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno)); serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno));
freeClient(slave); freeClient(slave);
return; return;
} }
refreshGoodSlavesCount(); refreshGoodSlavesCount();
/* Fire the replica change modules event. */
moduleFireServerEvent(REDISMODULE_EVENT_REPLICA_CHANGE,
REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE,
NULL);
serverLog(LL_NOTICE,"Synchronization with replica %s succeeded", serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
replicationGetSlaveName(slave)); replicationGetSlaveName(slave));
} }
void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) { void sendBulkToSlave(connection *conn) {
client *slave = privdata; client *slave = connGetPrivateData(conn);
UNUSED(el);
UNUSED(mask);
char buf[PROTO_IOBUF_LEN]; char buf[PROTO_IOBUF_LEN];
ssize_t nwritten, buflen; ssize_t nwritten, buflen;
...@@ -884,10 +891,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -884,10 +891,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
* replication process. Currently the preamble is just the bulk count of * replication process. Currently the preamble is just the bulk count of
* the file in the form "$<length>\r\n". */ * the file in the form "$<length>\r\n". */
if (slave->replpreamble) { if (slave->replpreamble) {
nwritten = write(fd,slave->replpreamble,sdslen(slave->replpreamble)); nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble));
if (nwritten == -1) { if (nwritten == -1) {
serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s", serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
strerror(errno)); connGetLastError(conn));
freeClient(slave); freeClient(slave);
return; return;
} }
...@@ -911,10 +918,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -911,10 +918,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
freeClient(slave); freeClient(slave);
return; return;
} }
if ((nwritten = write(fd,buf,buflen)) == -1) { if ((nwritten = connWrite(conn,buf,buflen)) == -1) {
if (errno != EAGAIN) { if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"Write error sending DB to replica: %s", serverLog(LL_WARNING,"Write error sending DB to replica: %s",
strerror(errno)); connGetLastError(conn));
freeClient(slave); freeClient(slave);
} }
return; return;
...@@ -924,11 +931,157 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -924,11 +931,157 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
if (slave->repldboff == slave->repldbsize) { if (slave->repldboff == slave->repldbsize) {
close(slave->repldbfd); close(slave->repldbfd);
slave->repldbfd = -1; slave->repldbfd = -1;
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE); connSetWriteHandler(slave->conn,NULL);
putSlaveOnline(slave); putSlaveOnline(slave);
} }
} }
/* Remove one write handler from the list of connections waiting to be writable
* during rdb pipe transfer. */
void rdbPipeWriteHandlerConnRemoved(struct connection *conn) {
if (!connHasWriteHandler(conn))
return;
connSetWriteHandler(conn, NULL);
server.rdb_pipe_numconns_writing--;
/* if there are no more writes for now for this conn, or write error: */
if (server.rdb_pipe_numconns_writing == 0) {
if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
}
}
}
/* Called in diskless master during transfer of data from the rdb pipe, when
* the replica becomes writable again. */
void rdbPipeWriteHandler(struct connection *conn) {
serverAssert(server.rdb_pipe_bufflen>0);
client *slave = connGetPrivateData(conn);
int nwritten;
if ((nwritten = connWrite(conn, server.rdb_pipe_buff + slave->repldboff,
server.rdb_pipe_bufflen - slave->repldboff)) == -1)
{
if (connGetState(conn) == CONN_STATE_CONNECTED)
return; /* equivalent to EAGAIN */
serverLog(LL_WARNING,"Write error sending DB to replica: %s",
connGetLastError(conn));
freeClient(slave);
return;
} else {
slave->repldboff += nwritten;
server.stat_net_output_bytes += nwritten;
if (slave->repldboff < server.rdb_pipe_bufflen)
return; /* more data to write.. */
}
rdbPipeWriteHandlerConnRemoved(conn);
}
/* When the the pipe serving diskless rdb transfer is drained (write end was
* closed), we can clean up all the temporary variables, and cleanup after the
* fork child. */
void RdbPipeCleanup() {
close(server.rdb_pipe_read);
zfree(server.rdb_pipe_conns);
server.rdb_pipe_conns = NULL;
server.rdb_pipe_numconns = 0;
server.rdb_pipe_numconns_writing = 0;
zfree(server.rdb_pipe_buff);
server.rdb_pipe_buff = NULL;
server.rdb_pipe_bufflen = 0;
/* Since we're avoiding to detect the child exited as long as the pipe is
* not drained, so now is the time to check. */
checkChildrenDone();
}
/* Called in diskless master, when there's data to read from the child's rdb pipe */
void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask) {
UNUSED(mask);
UNUSED(clientData);
UNUSED(eventLoop);
int i;
if (!server.rdb_pipe_buff)
server.rdb_pipe_buff = zmalloc(PROTO_IOBUF_LEN);
serverAssert(server.rdb_pipe_numconns_writing==0);
while (1) {
server.rdb_pipe_bufflen = read(fd, server.rdb_pipe_buff, PROTO_IOBUF_LEN);
if (server.rdb_pipe_bufflen < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
serverLog(LL_WARNING,"Diskless rdb transfer, read error sending DB to replicas: %s", strerror(errno));
for (i=0; i < server.rdb_pipe_numconns; i++) {
connection *conn = server.rdb_pipe_conns[i];
if (!conn)
continue;
client *slave = connGetPrivateData(conn);
freeClient(slave);
server.rdb_pipe_conns[i] = NULL;
}
killRDBChild();
return;
}
if (server.rdb_pipe_bufflen == 0) {
/* EOF - write end was closed. */
int stillUp = 0;
aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
for (i=0; i < server.rdb_pipe_numconns; i++)
{
connection *conn = server.rdb_pipe_conns[i];
if (!conn)
continue;
stillUp++;
}
serverLog(LL_WARNING,"Diskless rdb transfer, done reading from pipe, %d replicas still up.", stillUp);
RdbPipeCleanup();
return;
}
int stillAlive = 0;
for (i=0; i < server.rdb_pipe_numconns; i++)
{
int nwritten;
connection *conn = server.rdb_pipe_conns[i];
if (!conn)
continue;
client *slave = connGetPrivateData(conn);
if ((nwritten = connWrite(conn, server.rdb_pipe_buff, server.rdb_pipe_bufflen)) == -1) {
if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"Diskless rdb transfer, write error sending DB to replica: %s",
connGetLastError(conn));
freeClient(slave);
server.rdb_pipe_conns[i] = NULL;
continue;
}
/* An error and still in connected state, is equivalent to EAGAIN */
slave->repldboff = 0;
} else {
slave->repldboff = nwritten;
server.stat_net_output_bytes += nwritten;
}
/* If we were unable to write all the data to one of the replicas,
* setup write handler (and disable pipe read handler, below) */
if (nwritten != server.rdb_pipe_bufflen) {
server.rdb_pipe_numconns_writing++;
connSetWriteHandler(conn, rdbPipeWriteHandler);
}
stillAlive++;
}
if (stillAlive == 0) {
serverLog(LL_WARNING,"Diskless rdb transfer, last replica dropped, killing fork child.");
killRDBChild();
RdbPipeCleanup();
}
/* Remove the pipe read handler if at least one write handler was set. */
if (server.rdb_pipe_numconns_writing || stillAlive == 0) {
aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
break;
}
}
}
/* This function is called at the end of every background saving, /* This function is called at the end of every background saving,
* or when the replication RDB transfer strategy is modified from * or when the replication RDB transfer strategy is modified from
* disk to socket or the other way around. * disk to socket or the other way around.
...@@ -1015,8 +1168,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) { ...@@ -1015,8 +1168,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n", slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n",
(unsigned long long) slave->repldbsize); (unsigned long long) slave->repldbsize);
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE); connSetWriteHandler(slave->conn,NULL);
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) { if (connSetWriteHandler(slave->conn,sendBulkToSlave) == C_ERR) {
freeClient(slave); freeClient(slave);
continue; continue;
} }
...@@ -1084,9 +1237,8 @@ void replicationSendNewlineToMaster(void) { ...@@ -1084,9 +1237,8 @@ void replicationSendNewlineToMaster(void) {
static time_t newline_sent; static time_t newline_sent;
if (time(NULL) != newline_sent) { if (time(NULL) != newline_sent) {
newline_sent = time(NULL); newline_sent = time(NULL);
if (write(server.repl_transfer_s,"\n",1) == -1) {
/* Pinging back in this stage is best-effort. */ /* Pinging back in this stage is best-effort. */
} if (server.repl_transfer_s) connWrite(server.repl_transfer_s, "\n", 1);
} }
} }
...@@ -1100,8 +1252,10 @@ void replicationEmptyDbCallback(void *privdata) { ...@@ -1100,8 +1252,10 @@ void replicationEmptyDbCallback(void *privdata) {
/* Once we have a link with the master and the synchroniziation was /* Once we have a link with the master and the synchroniziation was
* performed, this function materializes the master client we store * performed, this function materializes the master client we store
* at server.master, starting from the specified file descriptor. */ * at server.master, starting from the specified file descriptor. */
void replicationCreateMasterClient(int fd, int dbid) { void replicationCreateMasterClient(connection *conn, int dbid) {
server.master = createClient(fd); server.master = createClient(conn);
if (conn)
connSetReadHandler(server.master->conn, readQueryFromClient);
server.master->flags |= CLIENT_MASTER; server.master->flags |= CLIENT_MASTER;
server.master->authenticated = 1; server.master->authenticated = 1;
server.master->reploff = server.master_initial_offset; server.master->reploff = server.master_initial_offset;
...@@ -1139,8 +1293,15 @@ void restartAOFAfterSYNC() { ...@@ -1139,8 +1293,15 @@ void restartAOFAfterSYNC() {
static int useDisklessLoad() { static int useDisklessLoad() {
/* compute boolean decision to use diskless load */ /* compute boolean decision to use diskless load */
return server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB || int enabled = server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
(server.repl_diskless_load == REPL_DISKLESS_LOAD_WHEN_DB_EMPTY && dbTotalServerKeyCount()==0); (server.repl_diskless_load == REPL_DISKLESS_LOAD_WHEN_DB_EMPTY && dbTotalServerKeyCount()==0);
/* Check all modules handle read errors, otherwise it's not safe to use diskless load. */
if (enabled && !moduleAllDatatypesHandleErrors()) {
serverLog(LL_WARNING,
"Skipping diskless-load because there are modules that don't handle read errors.");
enabled = 0;
}
return enabled;
} }
/* Helper function for readSyncBulkPayload() to make backups of the current /* Helper function for readSyncBulkPayload() to make backups of the current
...@@ -1189,7 +1350,7 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags ...@@ -1189,7 +1350,7 @@ void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags
/* Asynchronously read the SYNC payload we receive from a master */ /* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */ #define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { void readSyncBulkPayload(connection *conn) {
char buf[4096]; char buf[4096];
ssize_t nread, readlen, nwritten; ssize_t nread, readlen, nwritten;
int use_diskless_load; int use_diskless_load;
...@@ -1197,9 +1358,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1197,9 +1358,6 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC : int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
EMPTYDB_NO_FLAGS; EMPTYDB_NO_FLAGS;
off_t left; off_t left;
UNUSED(el);
UNUSED(privdata);
UNUSED(mask);
/* Static vars used to hold the EOF mark, and the last bytes received /* Static vars used to hold the EOF mark, and the last bytes received
* form the server: when they match, we reached the end of the transfer. */ * form the server: when they match, we reached the end of the transfer. */
...@@ -1210,7 +1368,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1210,7 +1368,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
/* If repl_transfer_size == -1 we still have to read the bulk length /* If repl_transfer_size == -1 we still have to read the bulk length
* from the master reply. */ * from the master reply. */
if (server.repl_transfer_size == -1) { if (server.repl_transfer_size == -1) {
if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout*1000) == -1) { if (connSyncReadLine(conn,buf,1024,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"I/O error reading bulk count from MASTER: %s", "I/O error reading bulk count from MASTER: %s",
strerror(errno)); strerror(errno));
...@@ -1275,7 +1433,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1275,7 +1433,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf); readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
} }
nread = read(fd,buf,readlen); nread = connRead(conn,buf,readlen);
if (nread <= 0) { if (nread <= 0) {
serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s", serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
(nread == -1) ? strerror(errno) : "connection lost"); (nread == -1) ? strerror(errno) : "connection lost");
...@@ -1383,27 +1541,27 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1383,27 +1541,27 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
* handler, otherwise it will get called recursively since * handler, otherwise it will get called recursively since
* rdbLoad() will call the event loop to process events from time to * rdbLoad() will call the event loop to process events from time to
* time for non blocking loading. */ * time for non blocking loading. */
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE); connSetReadHandler(conn, NULL);
serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory"); serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
if (use_diskless_load) { if (use_diskless_load) {
rio rdb; rio rdb;
rioInitWithFd(&rdb,fd,server.repl_transfer_size); rioInitWithConn(&rdb,conn,server.repl_transfer_size);
/* Put the socket in blocking mode to simplify RDB transfer. /* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the RDB is received. */ * We'll restore it when the RDB is received. */
anetBlock(NULL,fd); connBlock(conn);
anetRecvTimeout(NULL,fd,server.repl_timeout*1000); connRecvTimeout(conn, server.repl_timeout*1000);
startLoading(server.repl_transfer_size); startLoading(server.repl_transfer_size, RDBFLAGS_REPLICATION);
if (rdbLoadRio(&rdb,&rsi,0) != C_OK) { if (rdbLoadRio(&rdb,RDBFLAGS_REPLICATION,&rsi) != C_OK) {
/* RDB loading failed. */ /* RDB loading failed. */
stopLoading(); stopLoading(0);
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Failed trying to load the MASTER synchronization DB " "Failed trying to load the MASTER synchronization DB "
"from socket"); "from socket");
cancelReplicationHandshake(); cancelReplicationHandshake();
rioFreeFd(&rdb, NULL); rioFreeConn(&rdb, NULL);
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) { if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
/* Restore the backed up databases. */ /* Restore the backed up databases. */
disklessLoadRestoreBackups(diskless_load_backup,1, disklessLoadRestoreBackups(diskless_load_backup,1,
...@@ -1419,7 +1577,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1419,7 +1577,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
* gets promoted. */ * gets promoted. */
return; return;
} }
stopLoading(); stopLoading(1);
/* RDB loading succeeded if we reach this point. */ /* RDB loading succeeded if we reach this point. */
if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) { if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
...@@ -1436,16 +1594,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1436,16 +1594,16 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
{ {
serverLog(LL_WARNING,"Replication stream EOF marker is broken"); serverLog(LL_WARNING,"Replication stream EOF marker is broken");
cancelReplicationHandshake(); cancelReplicationHandshake();
rioFreeFd(&rdb, NULL); rioFreeConn(&rdb, NULL);
return; return;
} }
} }
/* Cleanup and restore the socket to the original state to continue /* Cleanup and restore the socket to the original state to continue
* with the normal replication. */ * with the normal replication. */
rioFreeFd(&rdb, NULL); rioFreeConn(&rdb, NULL);
anetNonBlock(NULL,fd); connNonBlock(conn);
anetRecvTimeout(NULL,fd,0); connRecvTimeout(conn,0);
} else { } else {
/* Ensure background save doesn't overwrite synced data */ /* Ensure background save doesn't overwrite synced data */
if (server.rdb_child_pid != -1) { if (server.rdb_child_pid != -1) {
...@@ -1466,7 +1624,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1466,7 +1624,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
cancelReplicationHandshake(); cancelReplicationHandshake();
return; return;
} }
if (rdbLoad(server.rdb_filename,&rsi) != C_OK) { if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_REPLICATION) != C_OK) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Failed trying to load the MASTER synchronization " "Failed trying to load the MASTER synchronization "
"DB from disk"); "DB from disk");
...@@ -1488,6 +1646,11 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1488,6 +1646,11 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
server.repl_state = REPL_STATE_CONNECTED; server.repl_state = REPL_STATE_CONNECTED;
server.repl_down_since = 0; server.repl_down_since = 0;
/* Fire the master link modules event. */
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
REDISMODULE_SUBEVENT_MASTER_LINK_UP,
NULL);
/* After a full resynchroniziation we use the replication ID and /* After a full resynchroniziation we use the replication ID and
* offset of the master. The secondary ID / offset are cleared since * offset of the master. The secondary ID / offset are cleared since
* we are starting a new history. */ * we are starting a new history. */
...@@ -1522,7 +1685,7 @@ error: ...@@ -1522,7 +1685,7 @@ error:
#define SYNC_CMD_READ (1<<0) #define SYNC_CMD_READ (1<<0)
#define SYNC_CMD_WRITE (1<<1) #define SYNC_CMD_WRITE (1<<1)
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE) #define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
char *sendSynchronousCommand(int flags, int fd, ...) { char *sendSynchronousCommand(int flags, connection *conn, ...) {
/* Create the command to send to the master, we use redis binary /* Create the command to send to the master, we use redis binary
* protocol to make sure correct arguments are sent. This function * protocol to make sure correct arguments are sent. This function
...@@ -1533,7 +1696,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) { ...@@ -1533,7 +1696,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
sds cmd = sdsempty(); sds cmd = sdsempty();
sds cmdargs = sdsempty(); sds cmdargs = sdsempty();
size_t argslen = 0; size_t argslen = 0;
va_start(ap,fd); va_start(ap,conn);
while(1) { while(1) {
arg = va_arg(ap, char*); arg = va_arg(ap, char*);
...@@ -1550,12 +1713,12 @@ char *sendSynchronousCommand(int flags, int fd, ...) { ...@@ -1550,12 +1713,12 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
sdsfree(cmdargs); sdsfree(cmdargs);
/* Transfer command to the server. */ /* Transfer command to the server. */
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000) if (connSyncWrite(conn,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
== -1) == -1)
{ {
sdsfree(cmd); sdsfree(cmd);
return sdscatprintf(sdsempty(),"-Writing to master: %s", return sdscatprintf(sdsempty(),"-Writing to master: %s",
strerror(errno)); connGetLastError(conn));
} }
sdsfree(cmd); sdsfree(cmd);
} }
...@@ -1564,7 +1727,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) { ...@@ -1564,7 +1727,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
if (flags & SYNC_CMD_READ) { if (flags & SYNC_CMD_READ) {
char buf[256]; char buf[256];
if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000) if (connSyncReadLine(conn,buf,sizeof(buf),server.repl_syncio_timeout*1000)
== -1) == -1)
{ {
return sdscatprintf(sdsempty(),"-Reading from master: %s", return sdscatprintf(sdsempty(),"-Reading from master: %s",
...@@ -1630,7 +1793,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) { ...@@ -1630,7 +1793,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
#define PSYNC_FULLRESYNC 3 #define PSYNC_FULLRESYNC 3
#define PSYNC_NOT_SUPPORTED 4 #define PSYNC_NOT_SUPPORTED 4
#define PSYNC_TRY_LATER 5 #define PSYNC_TRY_LATER 5
int slaveTryPartialResynchronization(int fd, int read_reply) { int slaveTryPartialResynchronization(connection *conn, int read_reply) {
char *psync_replid; char *psync_replid;
char psync_offset[32]; char psync_offset[32];
sds reply; sds reply;
...@@ -1655,18 +1818,18 @@ int slaveTryPartialResynchronization(int fd, int read_reply) { ...@@ -1655,18 +1818,18 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
} }
/* Issue the PSYNC command */ /* Issue the PSYNC command */
reply = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PSYNC",psync_replid,psync_offset,NULL); reply = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PSYNC",psync_replid,psync_offset,NULL);
if (reply != NULL) { if (reply != NULL) {
serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply); serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
sdsfree(reply); sdsfree(reply);
aeDeleteFileEvent(server.el,fd,AE_READABLE); connSetReadHandler(conn, NULL);
return PSYNC_WRITE_ERROR; return PSYNC_WRITE_ERROR;
} }
return PSYNC_WAIT_REPLY; return PSYNC_WAIT_REPLY;
} }
/* Reading half */ /* Reading half */
reply = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); reply = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
if (sdslen(reply) == 0) { if (sdslen(reply) == 0) {
/* The master may send empty newlines after it receives PSYNC /* The master may send empty newlines after it receives PSYNC
* and before to reply, just to keep the connection alive. */ * and before to reply, just to keep the connection alive. */
...@@ -1674,7 +1837,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) { ...@@ -1674,7 +1837,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
return PSYNC_WAIT_REPLY; return PSYNC_WAIT_REPLY;
} }
aeDeleteFileEvent(server.el,fd,AE_READABLE); connSetReadHandler(conn, NULL);
if (!strncmp(reply,"+FULLRESYNC",11)) { if (!strncmp(reply,"+FULLRESYNC",11)) {
char *replid = NULL, *offset = NULL; char *replid = NULL, *offset = NULL;
...@@ -1748,7 +1911,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) { ...@@ -1748,7 +1911,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* Setup the replication to continue. */ /* Setup the replication to continue. */
sdsfree(reply); sdsfree(reply);
replicationResurrectCachedMaster(fd); replicationResurrectCachedMaster(conn);
/* If this instance was restarted and we read the metadata to /* If this instance was restarted and we read the metadata to
* PSYNC from the persistence file, our replication backlog could * PSYNC from the persistence file, our replication backlog could
...@@ -1790,29 +1953,23 @@ int slaveTryPartialResynchronization(int fd, int read_reply) { ...@@ -1790,29 +1953,23 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* This handler fires when the non blocking connect was able to /* This handler fires when the non blocking connect was able to
* establish a connection with the master. */ * establish a connection with the master. */
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { void syncWithMaster(connection *conn) {
char tmpfile[256], *err = NULL; char tmpfile[256], *err = NULL;
int dfd = -1, maxtries = 5; int dfd = -1, maxtries = 5;
int sockerr = 0, psync_result; int psync_result;
socklen_t errlen = sizeof(sockerr);
UNUSED(el);
UNUSED(privdata);
UNUSED(mask);
/* If this event fired after the user turned the instance into a master /* If this event fired after the user turned the instance into a master
* with SLAVEOF NO ONE we must just return ASAP. */ * with SLAVEOF NO ONE we must just return ASAP. */
if (server.repl_state == REPL_STATE_NONE) { if (server.repl_state == REPL_STATE_NONE) {
close(fd); connClose(conn);
return; return;
} }
/* Check for errors in the socket: after a non blocking connect() we /* Check for errors in the socket: after a non blocking connect() we
* may find that the socket is in error state. */ * may find that the socket is in error state. */
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1) if (connGetState(conn) != CONN_STATE_CONNECTED) {
sockerr = errno;
if (sockerr) {
serverLog(LL_WARNING,"Error condition on socket for SYNC: %s", serverLog(LL_WARNING,"Error condition on socket for SYNC: %s",
strerror(sockerr)); connGetLastError(conn));
goto error; goto error;
} }
...@@ -1821,18 +1978,19 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1821,18 +1978,19 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event."); serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event.");
/* Delete the writable event so that the readable event remains /* Delete the writable event so that the readable event remains
* registered and we can wait for the PONG reply. */ * registered and we can wait for the PONG reply. */
aeDeleteFileEvent(server.el,fd,AE_WRITABLE); connSetReadHandler(conn, syncWithMaster);
connSetWriteHandler(conn, NULL);
server.repl_state = REPL_STATE_RECEIVE_PONG; server.repl_state = REPL_STATE_RECEIVE_PONG;
/* Send the PING, don't check for errors at all, we have the timeout /* Send the PING, don't check for errors at all, we have the timeout
* that will take care about this. */ * that will take care about this. */
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL); err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PING",NULL);
if (err) goto write_error; if (err) goto write_error;
return; return;
} }
/* Receive the PONG command. */ /* Receive the PONG command. */
if (server.repl_state == REPL_STATE_RECEIVE_PONG) { if (server.repl_state == REPL_STATE_RECEIVE_PONG) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* We accept only two replies as valid, a positive +PONG reply /* We accept only two replies as valid, a positive +PONG reply
* (we just check for "+") or an authentication error. * (we just check for "+") or an authentication error.
...@@ -1857,13 +2015,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1857,13 +2015,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* AUTH with the master if required. */ /* AUTH with the master if required. */
if (server.repl_state == REPL_STATE_SEND_AUTH) { if (server.repl_state == REPL_STATE_SEND_AUTH) {
if (server.masteruser && server.masterauth) { if (server.masteruser && server.masterauth) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH", err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",
server.masteruser,server.masterauth,NULL); server.masteruser,server.masterauth,NULL);
if (err) goto write_error; if (err) goto write_error;
server.repl_state = REPL_STATE_RECEIVE_AUTH; server.repl_state = REPL_STATE_RECEIVE_AUTH;
return; return;
} else if (server.masterauth) { } else if (server.masterauth) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",server.masterauth,NULL); err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",server.masterauth,NULL);
if (err) goto write_error; if (err) goto write_error;
server.repl_state = REPL_STATE_RECEIVE_AUTH; server.repl_state = REPL_STATE_RECEIVE_AUTH;
return; return;
...@@ -1874,7 +2032,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1874,7 +2032,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive AUTH reply. */ /* Receive AUTH reply. */
if (server.repl_state == REPL_STATE_RECEIVE_AUTH) { if (server.repl_state == REPL_STATE_RECEIVE_AUTH) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
if (err[0] == '-') { if (err[0] == '-') {
serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err); serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
sdsfree(err); sdsfree(err);
...@@ -1887,11 +2045,14 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1887,11 +2045,14 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Set the slave port, so that Master's INFO command can list the /* Set the slave port, so that Master's INFO command can list the
* slave listening port correctly. */ * slave listening port correctly. */
if (server.repl_state == REPL_STATE_SEND_PORT) { if (server.repl_state == REPL_STATE_SEND_PORT) {
sds port = sdsfromlonglong(server.slave_announce_port ? int port;
server.slave_announce_port : server.port); if (server.slave_announce_port) port = server.slave_announce_port;
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF", else if (server.tls_replication && server.tls_port) port = server.tls_port;
"listening-port",port, NULL); else port = server.port;
sdsfree(port); sds portstr = sdsfromlonglong(port);
err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"listening-port",portstr, NULL);
sdsfree(portstr);
if (err) goto write_error; if (err) goto write_error;
sdsfree(err); sdsfree(err);
server.repl_state = REPL_STATE_RECEIVE_PORT; server.repl_state = REPL_STATE_RECEIVE_PORT;
...@@ -1900,7 +2061,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1900,7 +2061,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF listening-port reply. */ /* Receive REPLCONF listening-port reply. */
if (server.repl_state == REPL_STATE_RECEIVE_PORT) { if (server.repl_state == REPL_STATE_RECEIVE_PORT) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support /* Ignore the error if any, not all the Redis versions support
* REPLCONF listening-port. */ * REPLCONF listening-port. */
if (err[0] == '-') { if (err[0] == '-') {
...@@ -1921,7 +2082,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1921,7 +2082,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Set the slave ip, so that Master's INFO command can list the /* Set the slave ip, so that Master's INFO command can list the
* slave IP address port correctly in case of port forwarding or NAT. */ * slave IP address port correctly in case of port forwarding or NAT. */
if (server.repl_state == REPL_STATE_SEND_IP) { if (server.repl_state == REPL_STATE_SEND_IP) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF", err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"ip-address",server.slave_announce_ip, NULL); "ip-address",server.slave_announce_ip, NULL);
if (err) goto write_error; if (err) goto write_error;
sdsfree(err); sdsfree(err);
...@@ -1931,7 +2092,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1931,7 +2092,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF ip-address reply. */ /* Receive REPLCONF ip-address reply. */
if (server.repl_state == REPL_STATE_RECEIVE_IP) { if (server.repl_state == REPL_STATE_RECEIVE_IP) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support /* Ignore the error if any, not all the Redis versions support
* REPLCONF listening-port. */ * REPLCONF listening-port. */
if (err[0] == '-') { if (err[0] == '-') {
...@@ -1949,7 +2110,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1949,7 +2110,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* *
* The master will ignore capabilities it does not understand. */ * The master will ignore capabilities it does not understand. */
if (server.repl_state == REPL_STATE_SEND_CAPA) { if (server.repl_state == REPL_STATE_SEND_CAPA) {
err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF", err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"capa","eof","capa","psync2",NULL); "capa","eof","capa","psync2",NULL);
if (err) goto write_error; if (err) goto write_error;
sdsfree(err); sdsfree(err);
...@@ -1959,7 +2120,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1959,7 +2120,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive CAPA reply. */ /* Receive CAPA reply. */
if (server.repl_state == REPL_STATE_RECEIVE_CAPA) { if (server.repl_state == REPL_STATE_RECEIVE_CAPA) {
err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL); err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support /* Ignore the error if any, not all the Redis versions support
* REPLCONF capa. */ * REPLCONF capa. */
if (err[0] == '-') { if (err[0] == '-') {
...@@ -1976,7 +2137,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1976,7 +2137,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* and the global offset, to try a partial resync at the next * and the global offset, to try a partial resync at the next
* reconnection attempt. */ * reconnection attempt. */
if (server.repl_state == REPL_STATE_SEND_PSYNC) { if (server.repl_state == REPL_STATE_SEND_PSYNC) {
if (slaveTryPartialResynchronization(fd,0) == PSYNC_WRITE_ERROR) { if (slaveTryPartialResynchronization(conn,0) == PSYNC_WRITE_ERROR) {
err = sdsnew("Write error sending the PSYNC command."); err = sdsnew("Write error sending the PSYNC command.");
goto write_error; goto write_error;
} }
...@@ -1992,7 +2153,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1992,7 +2153,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
goto error; goto error;
} }
psync_result = slaveTryPartialResynchronization(fd,1); psync_result = slaveTryPartialResynchronization(conn,1);
if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */ if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */
/* If the master is in an transient error, we should try to PSYNC /* If the master is in an transient error, we should try to PSYNC
...@@ -2021,7 +2182,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -2021,7 +2182,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* already populated. */ * already populated. */
if (psync_result == PSYNC_NOT_SUPPORTED) { if (psync_result == PSYNC_NOT_SUPPORTED) {
serverLog(LL_NOTICE,"Retrying with SYNC..."); serverLog(LL_NOTICE,"Retrying with SYNC...");
if (syncWrite(fd,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) { if (connSyncWrite(conn,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING,"I/O error writing to MASTER: %s", serverLog(LL_WARNING,"I/O error writing to MASTER: %s",
strerror(errno)); strerror(errno));
goto error; goto error;
...@@ -2046,12 +2207,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -2046,12 +2207,13 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
} }
/* Setup the non blocking download of the bulk file. */ /* Setup the non blocking download of the bulk file. */
if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL) if (connSetReadHandler(conn, readSyncBulkPayload)
== AE_ERR) == C_ERR)
{ {
char conninfo[CONN_INFO_LEN];
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Can't create readable event for SYNC: %s (fd=%d)", "Can't create readable event for SYNC: %s (%s)",
strerror(errno),fd); strerror(errno), connGetInfo(conn, conninfo, sizeof(conninfo)));
goto error; goto error;
} }
...@@ -2063,16 +2225,15 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -2063,16 +2225,15 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
return; return;
error: error:
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
if (dfd != -1) close(dfd); if (dfd != -1) close(dfd);
close(fd); connClose(conn);
server.repl_transfer_s = NULL;
if (server.repl_transfer_fd != -1) if (server.repl_transfer_fd != -1)
close(server.repl_transfer_fd); close(server.repl_transfer_fd);
if (server.repl_transfer_tmpfile) if (server.repl_transfer_tmpfile)
zfree(server.repl_transfer_tmpfile); zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL; server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1; server.repl_transfer_fd = -1;
server.repl_transfer_s = -1;
server.repl_state = REPL_STATE_CONNECT; server.repl_state = REPL_STATE_CONNECT;
return; return;
...@@ -2083,26 +2244,18 @@ write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */ ...@@ -2083,26 +2244,18 @@ write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
} }
int connectWithMaster(void) { int connectWithMaster(void) {
int fd; server.repl_transfer_s = server.tls_replication ? connCreateTLS() : connCreateSocket();
if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
fd = anetTcpNonBlockBestEffortBindConnect(NULL, NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {
server.masterhost,server.masterport,NET_FIRST_BIND_ADDR);
if (fd == -1) {
serverLog(LL_WARNING,"Unable to connect to MASTER: %s", serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
strerror(errno)); connGetLastError(server.repl_transfer_s));
connClose(server.repl_transfer_s);
server.repl_transfer_s = NULL;
return C_ERR; return C_ERR;
} }
if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==
AE_ERR)
{
close(fd);
serverLog(LL_WARNING,"Can't create readable event for SYNC");
return C_ERR;
}
server.repl_transfer_lastio = server.unixtime; server.repl_transfer_lastio = server.unixtime;
server.repl_transfer_s = fd;
server.repl_state = REPL_STATE_CONNECTING; server.repl_state = REPL_STATE_CONNECTING;
return C_OK; return C_OK;
} }
...@@ -2112,11 +2265,8 @@ int connectWithMaster(void) { ...@@ -2112,11 +2265,8 @@ int connectWithMaster(void) {
* Never call this function directly, use cancelReplicationHandshake() instead. * Never call this function directly, use cancelReplicationHandshake() instead.
*/ */
void undoConnectWithMaster(void) { void undoConnectWithMaster(void) {
int fd = server.repl_transfer_s; connClose(server.repl_transfer_s);
server.repl_transfer_s = NULL;
aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
close(fd);
server.repl_transfer_s = -1;
} }
/* Abort the async download of the bulk dataset while SYNC-ing with master. /* Abort the async download of the bulk dataset while SYNC-ing with master.
...@@ -2175,13 +2325,35 @@ void replicationSetMaster(char *ip, int port) { ...@@ -2175,13 +2325,35 @@ void replicationSetMaster(char *ip, int port) {
cancelReplicationHandshake(); cancelReplicationHandshake();
/* Before destroying our master state, create a cached master using /* Before destroying our master state, create a cached master using
* our own parameters, to later PSYNC with the new master. */ * our own parameters, to later PSYNC with the new master. */
if (was_master) replicationCacheMasterUsingMyself(); if (was_master) {
replicationDiscardCachedMaster();
replicationCacheMasterUsingMyself();
}
/* Fire the role change modules event. */
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA,
NULL);
/* Fire the master link modules event. */
if (server.repl_state == REPL_STATE_CONNECTED)
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN,
NULL);
server.repl_state = REPL_STATE_CONNECT; server.repl_state = REPL_STATE_CONNECT;
} }
/* Cancel replication, setting the instance as a master itself. */ /* Cancel replication, setting the instance as a master itself. */
void replicationUnsetMaster(void) { void replicationUnsetMaster(void) {
if (server.masterhost == NULL) return; /* Nothing to do. */ if (server.masterhost == NULL) return; /* Nothing to do. */
/* Fire the master link modules event. */
if (server.repl_state == REPL_STATE_CONNECTED)
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN,
NULL);
sdsfree(server.masterhost); sdsfree(server.masterhost);
server.masterhost = NULL; server.masterhost = NULL;
/* When a slave is turned into a master, the current replication ID /* When a slave is turned into a master, the current replication ID
...@@ -2210,11 +2382,22 @@ void replicationUnsetMaster(void) { ...@@ -2210,11 +2382,22 @@ void replicationUnsetMaster(void) {
* starting from now. Otherwise the backlog will be freed after a * starting from now. Otherwise the backlog will be freed after a
* failover if slaves do not connect immediately. */ * failover if slaves do not connect immediately. */
server.repl_no_slaves_since = server.unixtime; server.repl_no_slaves_since = server.unixtime;
/* Fire the role change modules event. */
moduleFireServerEvent(REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER,
NULL);
} }
/* This function is called when the slave lose the connection with the /* This function is called when the slave lose the connection with the
* master into an unexpected way. */ * master into an unexpected way. */
void replicationHandleMasterDisconnection(void) { void replicationHandleMasterDisconnection(void) {
/* Fire the master link modules event. */
if (server.repl_state == REPL_STATE_CONNECTED)
moduleFireServerEvent(REDISMODULE_EVENT_MASTER_LINK_CHANGE,
REDISMODULE_SUBEVENT_MASTER_LINK_DOWN,
NULL);
server.master = NULL; server.master = NULL;
server.repl_state = REPL_STATE_CONNECT; server.repl_state = REPL_STATE_CONNECT;
server.repl_down_since = server.unixtime; server.repl_down_since = server.unixtime;
...@@ -2301,7 +2484,7 @@ void roleCommand(client *c) { ...@@ -2301,7 +2484,7 @@ void roleCommand(client *c) {
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip; char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
if (slaveip[0] == '\0') { if (slaveip[0] == '\0') {
if (anetPeerToString(slave->fd,ip,sizeof(ip),NULL) == -1) if (connPeerToString(slave->conn,ip,sizeof(ip),NULL) == -1)
continue; continue;
slaveip = ip; slaveip = ip;
} }
...@@ -2423,7 +2606,7 @@ void replicationCacheMasterUsingMyself(void) { ...@@ -2423,7 +2606,7 @@ void replicationCacheMasterUsingMyself(void) {
/* The master client we create can be set to any DBID, because /* The master client we create can be set to any DBID, because
* the new master will start its replication stream with SELECT. */ * the new master will start its replication stream with SELECT. */
server.master_initial_offset = server.master_repl_offset; server.master_initial_offset = server.master_repl_offset;
replicationCreateMasterClient(-1,-1); replicationCreateMasterClient(NULL,-1);
/* Use our own ID / offset. */ /* Use our own ID / offset. */
memcpy(server.master->replid, server.replid, sizeof(server.replid)); memcpy(server.master->replid, server.replid, sizeof(server.replid));
...@@ -2452,10 +2635,11 @@ void replicationDiscardCachedMaster(void) { ...@@ -2452,10 +2635,11 @@ void replicationDiscardCachedMaster(void) {
* This function is called when successfully setup a partial resynchronization * This function is called when successfully setup a partial resynchronization
* so the stream of data that we'll receive will start from were this * so the stream of data that we'll receive will start from were this
* master left. */ * master left. */
void replicationResurrectCachedMaster(int newfd) { void replicationResurrectCachedMaster(connection *conn) {
server.master = server.cached_master; server.master = server.cached_master;
server.cached_master = NULL; server.cached_master = NULL;
server.master->fd = newfd; server.master->conn = conn;
connSetPrivateData(server.master->conn, server.master);
server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP); server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP);
server.master->authenticated = 1; server.master->authenticated = 1;
server.master->lastinteraction = server.unixtime; server.master->lastinteraction = server.unixtime;
...@@ -2464,8 +2648,7 @@ void replicationResurrectCachedMaster(int newfd) { ...@@ -2464,8 +2648,7 @@ void replicationResurrectCachedMaster(int newfd) {
/* Re-add to the list of clients. */ /* Re-add to the list of clients. */
linkClient(server.master); linkClient(server.master);
if (aeCreateFileEvent(server.el, newfd, AE_READABLE, if (connSetReadHandler(server.master->conn, readQueryFromClient)) {
readQueryFromClient, server.master)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno)); serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */ freeClientAsync(server.master); /* Close ASAP. */
} }
...@@ -2473,8 +2656,7 @@ void replicationResurrectCachedMaster(int newfd) { ...@@ -2473,8 +2656,7 @@ void replicationResurrectCachedMaster(int newfd) {
/* We may also need to install the write handler as well if there is /* We may also need to install the write handler as well if there is
* pending data in the write buffers. */ * pending data in the write buffers. */
if (clientHasPendingReplies(server.master)) { if (clientHasPendingReplies(server.master)) {
if (aeCreateFileEvent(server.el, newfd, AE_WRITABLE, if (connSetWriteHandler(server.master->conn, sendReplyToClient)) {
sendReplyToClient, server.master)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno)); serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */ freeClientAsync(server.master); /* Close ASAP. */
} }
...@@ -2844,9 +3026,7 @@ void replicationCron(void) { ...@@ -2844,9 +3026,7 @@ void replicationCron(void) {
server.rdb_child_type != RDB_CHILD_TYPE_SOCKET)); server.rdb_child_type != RDB_CHILD_TYPE_SOCKET));
if (is_presync) { if (is_presync) {
if (write(slave->fd, "\n", 1) == -1) { connWrite(slave->conn, "\n", 1);
/* Don't worry about socket errors, it's just a ping. */
}
} }
} }
...@@ -2923,7 +3103,7 @@ void replicationCron(void) { ...@@ -2923,7 +3103,7 @@ void replicationCron(void) {
* In case of diskless replication, we make sure to wait the specified * In case of diskless replication, we make sure to wait the specified
* number of seconds (according to configuration) so that other slaves * number of seconds (according to configuration) so that other slaves
* have the time to arrive before we start streaming. */ * have the time to arrive before we start streaming. */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) { if (!hasActiveChildProcess()) {
time_t idle, max_idle = 0; time_t idle, max_idle = 0;
int slaves_waiting = 0; int slaves_waiting = 0;
int mincapa = -1; int mincapa = -1;
......
...@@ -159,13 +159,13 @@ void rioInitWithFile(rio *r, FILE *fp) { ...@@ -159,13 +159,13 @@ void rioInitWithFile(rio *r, FILE *fp) {
r->io.file.autosync = 0; r->io.file.autosync = 0;
} }
/* ------------------- File descriptor implementation ------------------- /* ------------------- Connection implementation -------------------
* We use this RIO implemetnation when reading an RDB file directly from * We use this RIO implemetnation when reading an RDB file directly from
* the socket to the memory via rdbLoadRio(), thus this implementation * the connection to the memory via rdbLoadRio(), thus this implementation
* only implements reading from a file descriptor that is, normally, * only implements reading from a connection that is, normally,
* just a socket. */ * just a socket. */
static size_t rioFdWrite(rio *r, const void *buf, size_t len) { static size_t rioConnWrite(rio *r, const void *buf, size_t len) {
UNUSED(r); UNUSED(r);
UNUSED(buf); UNUSED(buf);
UNUSED(len); UNUSED(len);
...@@ -173,72 +173,72 @@ static size_t rioFdWrite(rio *r, const void *buf, size_t len) { ...@@ -173,72 +173,72 @@ static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
} }
/* Returns 1 or 0 for success/failure. */ /* Returns 1 or 0 for success/failure. */
static size_t rioFdRead(rio *r, void *buf, size_t len) { static size_t rioConnRead(rio *r, void *buf, size_t len) {
size_t avail = sdslen(r->io.fd.buf)-r->io.fd.pos; size_t avail = sdslen(r->io.conn.buf)-r->io.conn.pos;
/* If the buffer is too small for the entire request: realloc. */ /* If the buffer is too small for the entire request: realloc. */
if (sdslen(r->io.fd.buf) + sdsavail(r->io.fd.buf) < len) if (sdslen(r->io.conn.buf) + sdsavail(r->io.conn.buf) < len)
r->io.fd.buf = sdsMakeRoomFor(r->io.fd.buf, len - sdslen(r->io.fd.buf)); r->io.conn.buf = sdsMakeRoomFor(r->io.conn.buf, len - sdslen(r->io.conn.buf));
/* If the remaining unused buffer is not large enough: memmove so that we /* If the remaining unused buffer is not large enough: memmove so that we
* can read the rest. */ * can read the rest. */
if (len > avail && sdsavail(r->io.fd.buf) < len - avail) { if (len > avail && sdsavail(r->io.conn.buf) < len - avail) {
sdsrange(r->io.fd.buf, r->io.fd.pos, -1); sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
r->io.fd.pos = 0; r->io.conn.pos = 0;
} }
/* If we don't already have all the data in the sds, read more */ /* If we don't already have all the data in the sds, read more */
while (len > sdslen(r->io.fd.buf) - r->io.fd.pos) { while (len > sdslen(r->io.conn.buf) - r->io.conn.pos) {
size_t buffered = sdslen(r->io.fd.buf) - r->io.fd.pos; size_t buffered = sdslen(r->io.conn.buf) - r->io.conn.pos;
size_t toread = len - buffered; size_t toread = len - buffered;
/* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of /* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of
* the two. */ * the two. */
if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN; if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN;
if (toread > sdsavail(r->io.fd.buf)) toread = sdsavail(r->io.fd.buf); if (toread > sdsavail(r->io.conn.buf)) toread = sdsavail(r->io.conn.buf);
if (r->io.fd.read_limit != 0 && if (r->io.conn.read_limit != 0 &&
r->io.fd.read_so_far + buffered + toread > r->io.fd.read_limit) r->io.conn.read_so_far + buffered + toread > r->io.conn.read_limit)
{ {
if (r->io.fd.read_limit >= r->io.fd.read_so_far - buffered) if (r->io.conn.read_limit >= r->io.conn.read_so_far - buffered)
toread = r->io.fd.read_limit - r->io.fd.read_so_far - buffered; toread = r->io.conn.read_limit - r->io.conn.read_so_far - buffered;
else { else {
errno = EOVERFLOW; errno = EOVERFLOW;
return 0; return 0;
} }
} }
int retval = read(r->io.fd.fd, int retval = connRead(r->io.conn.conn,
(char*)r->io.fd.buf + sdslen(r->io.fd.buf), (char*)r->io.conn.buf + sdslen(r->io.conn.buf),
toread); toread);
if (retval <= 0) { if (retval <= 0) {
if (errno == EWOULDBLOCK) errno = ETIMEDOUT; if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
return 0; return 0;
} }
sdsIncrLen(r->io.fd.buf, retval); sdsIncrLen(r->io.conn.buf, retval);
} }
memcpy(buf, (char*)r->io.fd.buf + r->io.fd.pos, len); memcpy(buf, (char*)r->io.conn.buf + r->io.conn.pos, len);
r->io.fd.read_so_far += len; r->io.conn.read_so_far += len;
r->io.fd.pos += len; r->io.conn.pos += len;
return len; return len;
} }
/* Returns read/write position in file. */ /* Returns read/write position in file. */
static off_t rioFdTell(rio *r) { static off_t rioConnTell(rio *r) {
return r->io.fd.read_so_far; return r->io.conn.read_so_far;
} }
/* Flushes any buffer to target device if applicable. Returns 1 on success /* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */ * and 0 on failures. */
static int rioFdFlush(rio *r) { static int rioConnFlush(rio *r) {
/* Our flush is implemented by the write method, that recognizes a /* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */ * buffer set to NULL with a count of zero as a flush request. */
return rioFdWrite(r,NULL,0); return rioConnWrite(r,NULL,0);
} }
static const rio rioFdIO = { static const rio rioConnIO = {
rioFdRead, rioConnRead,
rioFdWrite, rioConnWrite,
rioFdTell, rioConnTell,
rioFdFlush, rioConnFlush,
NULL, /* update_checksum */ NULL, /* update_checksum */
0, /* current checksum */ 0, /* current checksum */
0, /* flags */ 0, /* flags */
...@@ -249,108 +249,90 @@ static const rio rioFdIO = { ...@@ -249,108 +249,90 @@ static const rio rioFdIO = {
/* Create an RIO that implements a buffered read from an fd /* Create an RIO that implements a buffered read from an fd
* read_limit argument stops buffering when the reaching the limit. */ * read_limit argument stops buffering when the reaching the limit. */
void rioInitWithFd(rio *r, int fd, size_t read_limit) { void rioInitWithConn(rio *r, connection *conn, size_t read_limit) {
*r = rioFdIO; *r = rioConnIO;
r->io.fd.fd = fd; r->io.conn.conn = conn;
r->io.fd.pos = 0; r->io.conn.pos = 0;
r->io.fd.read_limit = read_limit; r->io.conn.read_limit = read_limit;
r->io.fd.read_so_far = 0; r->io.conn.read_so_far = 0;
r->io.fd.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN); r->io.conn.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN);
sdsclear(r->io.fd.buf); sdsclear(r->io.conn.buf);
} }
/* Release the RIO tream. Optionally returns the unread buffered data /* Release the RIO tream. Optionally returns the unread buffered data
* when the SDS pointer 'remaining' is passed. */ * when the SDS pointer 'remaining' is passed. */
void rioFreeFd(rio *r, sds *remaining) { void rioFreeConn(rio *r, sds *remaining) {
if (remaining && (size_t)r->io.fd.pos < sdslen(r->io.fd.buf)) { if (remaining && (size_t)r->io.conn.pos < sdslen(r->io.conn.buf)) {
if (r->io.fd.pos > 0) sdsrange(r->io.fd.buf, r->io.fd.pos, -1); if (r->io.conn.pos > 0) sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
*remaining = r->io.fd.buf; *remaining = r->io.conn.buf;
} else { } else {
sdsfree(r->io.fd.buf); sdsfree(r->io.conn.buf);
if (remaining) *remaining = NULL; if (remaining) *remaining = NULL;
} }
r->io.fd.buf = NULL; r->io.conn.buf = NULL;
} }
/* ------------------- File descriptors set implementation ------------------ /* ------------------- File descriptor implementation ------------------
* This target is used to write the RDB file to N different replicas via * This target is used to write the RDB file to pipe, when the master just
* sockets, when the master just streams the data to the replicas without * streams the data to the replicas without creating an RDB on-disk image
* creating an RDB on-disk image (diskless replication option). * (diskless replication option).
* It only implements writes. */ * It only implements writes. */
/* Returns 1 or 0 for success/failure. /* Returns 1 or 0 for success/failure.
* The function returns success as long as we are able to correctly write
* to at least one file descriptor.
* *
* When buf is NULL and len is 0, the function performs a flush operation * When buf is NULL and len is 0, the function performs a flush operation
* if there is some pending buffer, so this function is also used in order * if there is some pending buffer, so this function is also used in order
* to implement rioFdsetFlush(). */ * to implement rioFdFlush(). */
static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) { static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
ssize_t retval; ssize_t retval;
int j;
unsigned char *p = (unsigned char*) buf; unsigned char *p = (unsigned char*) buf;
int doflush = (buf == NULL && len == 0); int doflush = (buf == NULL && len == 0);
/* To start we always append to our buffer. If it gets larger than /* For small writes, we rather keep the data in user-space buffer, and flush
* a given size, we actually write to the sockets. */ * it only when it grows. however for larger writes, we prefer to flush
if (len) { * any pre-existing buffer, and write the new one directly without reallocs
r->io.fdset.buf = sdscatlen(r->io.fdset.buf,buf,len); * and memory copying. */
len = 0; /* Prevent entering the while below if we don't flush. */ if (len > PROTO_IOBUF_LEN) {
if (sdslen(r->io.fdset.buf) > PROTO_IOBUF_LEN) doflush = 1; /* First, flush any pre-existing buffered data. */
if (sdslen(r->io.fd.buf)) {
if (rioFdWrite(r, NULL, 0) == 0)
return 0;
} }
/* Write the new data, keeping 'p' and 'len' from the input. */
if (doflush) { } else {
p = (unsigned char*) r->io.fdset.buf; if (len) {
len = sdslen(r->io.fdset.buf); r->io.fd.buf = sdscatlen(r->io.fd.buf,buf,len);
if (sdslen(r->io.fd.buf) > PROTO_IOBUF_LEN)
doflush = 1;
if (!doflush)
return 1;
} }
/* Flusing the buffered data. set 'p' and 'len' accordintly. */
/* Write in little chunchs so that when there are big writes we p = (unsigned char*) r->io.fd.buf;
* parallelize while the kernel is sending data in background to len = sdslen(r->io.fd.buf);
* the TCP socket. */
while(len) {
size_t count = len < 1024 ? len : 1024;
int broken = 0;
for (j = 0; j < r->io.fdset.numfds; j++) {
if (r->io.fdset.state[j] != 0) {
/* Skip FDs alraedy in error. */
broken++;
continue;
} }
/* Make sure to write 'count' bytes to the socket regardless
* of short writes. */
size_t nwritten = 0; size_t nwritten = 0;
while(nwritten != count) { while(nwritten != len) {
retval = write(r->io.fdset.fds[j],p+nwritten,count-nwritten); retval = write(r->io.fd.fd,p+nwritten,len-nwritten);
if (retval <= 0) { if (retval <= 0) {
/* With blocking sockets, which is the sole user of this /* With blocking io, which is the sole user of this
* rio target, EWOULDBLOCK is returned only because of * rio target, EWOULDBLOCK is returned only because of
* the SO_SNDTIMEO socket option, so we translate the error * the SO_SNDTIMEO socket option, so we translate the error
* into one more recognizable by the user. */ * into one more recognizable by the user. */
if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT; if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT;
break; return 0; /* error. */
} }
nwritten += retval; nwritten += retval;
} }
if (nwritten != count) { r->io.fd.pos += len;
/* Mark this FD as broken. */ sdsclear(r->io.fd.buf);
r->io.fdset.state[j] = errno;
if (r->io.fdset.state[j] == 0) r->io.fdset.state[j] = EIO;
}
}
if (broken == r->io.fdset.numfds) return 0; /* All the FDs in error. */
p += count;
len -= count;
r->io.fdset.pos += count;
}
if (doflush) sdsclear(r->io.fdset.buf);
return 1; return 1;
} }
/* Returns 1 or 0 for success/failure. */ /* Returns 1 or 0 for success/failure. */
static size_t rioFdsetRead(rio *r, void *buf, size_t len) { static size_t rioFdRead(rio *r, void *buf, size_t len) {
UNUSED(r); UNUSED(r);
UNUSED(buf); UNUSED(buf);
UNUSED(len); UNUSED(len);
...@@ -358,23 +340,23 @@ static size_t rioFdsetRead(rio *r, void *buf, size_t len) { ...@@ -358,23 +340,23 @@ static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
} }
/* Returns read/write position in file. */ /* Returns read/write position in file. */
static off_t rioFdsetTell(rio *r) { static off_t rioFdTell(rio *r) {
return r->io.fdset.pos; return r->io.fd.pos;
} }
/* Flushes any buffer to target device if applicable. Returns 1 on success /* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */ * and 0 on failures. */
static int rioFdsetFlush(rio *r) { static int rioFdFlush(rio *r) {
/* Our flush is implemented by the write method, that recognizes a /* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */ * buffer set to NULL with a count of zero as a flush request. */
return rioFdsetWrite(r,NULL,0); return rioFdWrite(r,NULL,0);
} }
static const rio rioFdsetIO = { static const rio rioFdIO = {
rioFdsetRead, rioFdRead,
rioFdsetWrite, rioFdWrite,
rioFdsetTell, rioFdTell,
rioFdsetFlush, rioFdFlush,
NULL, /* update_checksum */ NULL, /* update_checksum */
0, /* current checksum */ 0, /* current checksum */
0, /* flags */ 0, /* flags */
...@@ -383,24 +365,16 @@ static const rio rioFdsetIO = { ...@@ -383,24 +365,16 @@ static const rio rioFdsetIO = {
{ { NULL, 0 } } /* union for io-specific vars */ { { NULL, 0 } } /* union for io-specific vars */
}; };
void rioInitWithFdset(rio *r, int *fds, int numfds) { void rioInitWithFd(rio *r, int fd) {
int j; *r = rioFdIO;
r->io.fd.fd = fd;
*r = rioFdsetIO; r->io.fd.pos = 0;
r->io.fdset.fds = zmalloc(sizeof(int)*numfds); r->io.fd.buf = sdsempty();
r->io.fdset.state = zmalloc(sizeof(int)*numfds);
memcpy(r->io.fdset.fds,fds,sizeof(int)*numfds);
for (j = 0; j < numfds; j++) r->io.fdset.state[j] = 0;
r->io.fdset.numfds = numfds;
r->io.fdset.pos = 0;
r->io.fdset.buf = sdsempty();
} }
/* release the rio stream. */ /* release the rio stream. */
void rioFreeFdset(rio *r) { void rioFreeFd(rio *r) {
zfree(r->io.fdset.fds); sdsfree(r->io.fd.buf);
zfree(r->io.fdset.state);
sdsfree(r->io.fdset.buf);
} }
/* ---------------------------- Generic functions ---------------------------- */ /* ---------------------------- Generic functions ---------------------------- */
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include "sds.h" #include "sds.h"
#include "connection.h"
#define RIO_FLAG_READ_ERROR (1<<0) #define RIO_FLAG_READ_ERROR (1<<0)
#define RIO_FLAG_WRITE_ERROR (1<<1) #define RIO_FLAG_WRITE_ERROR (1<<1)
...@@ -76,22 +77,20 @@ struct _rio { ...@@ -76,22 +77,20 @@ struct _rio {
off_t buffered; /* Bytes written since last fsync. */ off_t buffered; /* Bytes written since last fsync. */
off_t autosync; /* fsync after 'autosync' bytes written. */ off_t autosync; /* fsync after 'autosync' bytes written. */
} file; } file;
/* file descriptor */ /* Connection object (used to read from socket) */
struct { struct {
int fd; /* File descriptor. */ connection *conn; /* Connection */
off_t pos; /* pos in buf that was returned */ off_t pos; /* pos in buf that was returned */
sds buf; /* buffered data */ sds buf; /* buffered data */
size_t read_limit; /* don't allow to buffer/read more than that */ size_t read_limit; /* don't allow to buffer/read more than that */
size_t read_so_far; /* amount of data read from the rio (not buffered) */ size_t read_so_far; /* amount of data read from the rio (not buffered) */
} fd; } conn;
/* Multiple FDs target (used to write to N sockets). */ /* FD target (used to write to pipe). */
struct { struct {
int *fds; /* File descriptors. */ int fd; /* File descriptor. */
int *state; /* Error state of each fd. 0 (if ok) or errno. */
int numfds;
off_t pos; off_t pos;
sds buf; sds buf;
} fdset; } fd;
} io; } io;
}; };
...@@ -159,11 +158,11 @@ static inline void rioClearErrors(rio *r) { ...@@ -159,11 +158,11 @@ static inline void rioClearErrors(rio *r) {
void rioInitWithFile(rio *r, FILE *fp); void rioInitWithFile(rio *r, FILE *fp);
void rioInitWithBuffer(rio *r, sds s); void rioInitWithBuffer(rio *r, sds s);
void rioInitWithFd(rio *r, int fd, size_t read_limit); void rioInitWithConn(rio *r, connection *conn, size_t read_limit);
void rioInitWithFdset(rio *r, int *fds, int numfds); void rioInitWithFd(rio *r, int fd);
void rioFreeFdset(rio *r); void rioFreeFd(rio *r);
void rioFreeFd(rio *r, sds* out_remainingBufferedData); void rioFreeConn(rio *r, sds* out_remainingBufferedData);
size_t rioWriteBulkCount(rio *r, char prefix, long count); size_t rioWriteBulkCount(rio *r, char prefix, long count);
size_t rioWriteBulkString(rio *r, const char *buf, size_t len); size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
......
...@@ -61,7 +61,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx); ...@@ -61,7 +61,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx);
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */ #define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
#define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */ #define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */
struct ldbState { struct ldbState {
int fd; /* Socket of the debugging client. */ connection *conn; /* Connection of the debugging client. */
int active; /* Are we debugging EVAL right now? */ int active; /* Are we debugging EVAL right now? */
int forked; /* Is this a fork()ed debugging session? */ int forked; /* Is this a fork()ed debugging session? */
list *logs; /* List of messages to send to the client. */ list *logs; /* List of messages to send to the client. */
...@@ -139,8 +139,8 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) { ...@@ -139,8 +139,8 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
case '%': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break; case '%': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
case '~': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break; case '~': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
case '_': p = redisProtocolToLuaType_Null(lua,reply); break; case '_': p = redisProtocolToLuaType_Null(lua,reply); break;
case '#': p = redisProtocolToLuaType_Bool(lua,reply,p[1]); case '#': p = redisProtocolToLuaType_Bool(lua,reply,p[1]); break;
case ',': p = redisProtocolToLuaType_Double(lua,reply); case ',': p = redisProtocolToLuaType_Double(lua,reply); break;
} }
return p; return p;
} }
...@@ -1083,6 +1083,7 @@ void scriptingInit(int setup) { ...@@ -1083,6 +1083,7 @@ void scriptingInit(int setup) {
if (setup) { if (setup) {
server.lua_client = NULL; server.lua_client = NULL;
server.lua_caller = NULL; server.lua_caller = NULL;
server.lua_cur_script = NULL;
server.lua_timedout = 0; server.lua_timedout = 0;
ldbInit(); ldbInit();
} }
...@@ -1242,7 +1243,7 @@ void scriptingInit(int setup) { ...@@ -1242,7 +1243,7 @@ void scriptingInit(int setup) {
* Note: there is no need to create it again when this function is called * Note: there is no need to create it again when this function is called
* by scriptingReset(). */ * by scriptingReset(). */
if (server.lua_client == NULL) { if (server.lua_client == NULL) {
server.lua_client = createClient(-1); server.lua_client = createClient(NULL);
server.lua_client->flags |= CLIENT_LUA; server.lua_client->flags |= CLIENT_LUA;
} }
...@@ -1407,7 +1408,11 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) { ...@@ -1407,7 +1408,11 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
/* Set the timeout condition if not already set and the maximum /* Set the timeout condition if not already set and the maximum
* execution time was reached. */ * execution time was reached. */
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) { if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed); serverLog(LL_WARNING,
"Lua slow script detected: still in execution after %lld milliseconds. "
"You can try killing the script using the SCRIPT KILL command. "
"Script SHA1 is: %s",
elapsed, server.lua_cur_script);
server.lua_timedout = 1; server.lua_timedout = 1;
/* Once the script timeouts we reenter the event loop to permit others /* Once the script timeouts we reenter the event loop to permit others
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason * to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
...@@ -1524,6 +1529,7 @@ void evalGenericCommand(client *c, int evalsha) { ...@@ -1524,6 +1529,7 @@ void evalGenericCommand(client *c, int evalsha) {
* If we are debugging, we set instead a "line" hook so that the * If we are debugging, we set instead a "line" hook so that the
* debugger is call-back at every line executed by the script. */ * debugger is call-back at every line executed by the script. */
server.lua_caller = c; server.lua_caller = c;
server.lua_cur_script = funcname + 2;
server.lua_time_start = mstime(); server.lua_time_start = mstime();
server.lua_kill = 0; server.lua_kill = 0;
if (server.lua_time_limit > 0 && ldb.active == 0) { if (server.lua_time_limit > 0 && ldb.active == 0) {
...@@ -1550,6 +1556,7 @@ void evalGenericCommand(client *c, int evalsha) { ...@@ -1550,6 +1556,7 @@ void evalGenericCommand(client *c, int evalsha) {
queueClientForReprocessing(server.master); queueClientForReprocessing(server.master);
} }
server.lua_caller = NULL; server.lua_caller = NULL;
server.lua_cur_script = NULL;
/* Call the Lua garbage collector from time to time to avoid a /* Call the Lua garbage collector from time to time to avoid a
* full cycle performed by Lua, which adds too latency. * full cycle performed by Lua, which adds too latency.
...@@ -1727,7 +1734,7 @@ NULL ...@@ -1727,7 +1734,7 @@ NULL
/* Initialize Lua debugger data structures. */ /* Initialize Lua debugger data structures. */
void ldbInit(void) { void ldbInit(void) {
ldb.fd = -1; ldb.conn = NULL;
ldb.active = 0; ldb.active = 0;
ldb.logs = listCreate(); ldb.logs = listCreate();
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree); listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
...@@ -1749,7 +1756,7 @@ void ldbFlushLog(list *log) { ...@@ -1749,7 +1756,7 @@ void ldbFlushLog(list *log) {
void ldbEnable(client *c) { void ldbEnable(client *c) {
c->flags |= CLIENT_LUA_DEBUG; c->flags |= CLIENT_LUA_DEBUG;
ldbFlushLog(ldb.logs); ldbFlushLog(ldb.logs);
ldb.fd = c->fd; ldb.conn = c->conn;
ldb.step = 1; ldb.step = 1;
ldb.bpcount = 0; ldb.bpcount = 0;
ldb.luabp = 0; ldb.luabp = 0;
...@@ -1804,7 +1811,7 @@ void ldbSendLogs(void) { ...@@ -1804,7 +1811,7 @@ void ldbSendLogs(void) {
proto = sdscatlen(proto,"\r\n",2); proto = sdscatlen(proto,"\r\n",2);
listDelNode(ldb.logs,ln); listDelNode(ldb.logs,ln);
} }
if (write(ldb.fd,proto,sdslen(proto)) == -1) { if (connWrite(ldb.conn,proto,sdslen(proto)) == -1) {
/* Avoid warning. We don't check the return value of write() /* Avoid warning. We don't check the return value of write()
* since the next read() will catch the I/O error and will * since the next read() will catch the I/O error and will
* close the debugging session. */ * close the debugging session. */
...@@ -1827,7 +1834,7 @@ void ldbSendLogs(void) { ...@@ -1827,7 +1834,7 @@ void ldbSendLogs(void) {
int ldbStartSession(client *c) { int ldbStartSession(client *c) {
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0; ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
if (ldb.forked) { if (ldb.forked) {
pid_t cp = fork(); pid_t cp = redisFork();
if (cp == -1) { if (cp == -1) {
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode."); addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
return 0; return 0;
...@@ -1844,7 +1851,6 @@ int ldbStartSession(client *c) { ...@@ -1844,7 +1851,6 @@ int ldbStartSession(client *c) {
* socket to make sure if the parent crashes a reset is sent * socket to make sure if the parent crashes a reset is sent
* to the clients. */ * to the clients. */
serverLog(LL_WARNING,"Redis forked for debugging eval"); serverLog(LL_WARNING,"Redis forked for debugging eval");
closeListeningSockets(0);
} else { } else {
/* Parent */ /* Parent */
listAddNodeTail(ldb.children,(void*)(unsigned long)cp); listAddNodeTail(ldb.children,(void*)(unsigned long)cp);
...@@ -1857,8 +1863,8 @@ int ldbStartSession(client *c) { ...@@ -1857,8 +1863,8 @@ int ldbStartSession(client *c) {
} }
/* Setup our debugging session. */ /* Setup our debugging session. */
anetBlock(NULL,ldb.fd); connBlock(ldb.conn);
anetSendTimeout(NULL,ldb.fd,5000); connSendTimeout(ldb.conn,5000);
ldb.active = 1; ldb.active = 1;
/* First argument of EVAL is the script itself. We split it into different /* First argument of EVAL is the script itself. We split it into different
...@@ -1885,7 +1891,7 @@ void ldbEndSession(client *c) { ...@@ -1885,7 +1891,7 @@ void ldbEndSession(client *c) {
/* If it's a fork()ed session, we just exit. */ /* If it's a fork()ed session, we just exit. */
if (ldb.forked) { if (ldb.forked) {
writeToClient(c->fd, c, 0); writeToClient(c,0);
serverLog(LL_WARNING,"Lua debugging session child exiting"); serverLog(LL_WARNING,"Lua debugging session child exiting");
exitFromChild(0); exitFromChild(0);
} else { } else {
...@@ -1894,8 +1900,8 @@ void ldbEndSession(client *c) { ...@@ -1894,8 +1900,8 @@ void ldbEndSession(client *c) {
} }
/* Otherwise let's restore client's state. */ /* Otherwise let's restore client's state. */
anetNonBlock(NULL,ldb.fd); connNonBlock(ldb.conn);
anetSendTimeout(NULL,ldb.fd,0); connSendTimeout(ldb.conn,0);
/* Close the client connectin after sending the final EVAL reply /* Close the client connectin after sending the final EVAL reply
* in order to signal the end of the debugging session. */ * in order to signal the end of the debugging session. */
...@@ -2532,7 +2538,7 @@ int ldbRepl(lua_State *lua) { ...@@ -2532,7 +2538,7 @@ int ldbRepl(lua_State *lua) {
while(1) { while(1) {
while((argv = ldbReplParseCommand(&argc)) == NULL) { while((argv = ldbReplParseCommand(&argc)) == NULL) {
char buf[1024]; char buf[1024];
int nread = read(ldb.fd,buf,sizeof(buf)); int nread = connRead(ldb.conn,buf,sizeof(buf));
if (nread <= 0) { if (nread <= 0) {
/* Make sure the script runs without user input since the /* Make sure the script runs without user input since the
* client is no longer connected. */ * client is no longer connected. */
......
...@@ -603,6 +603,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) { ...@@ -603,6 +603,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
long i; long i;
va_list ap; va_list ap;
/* To avoid continuous reallocations, let's start with a buffer that
* can hold at least two times the format string itself. It's not the
* best heuristic but seems to work in practice. */
s = sdsMakeRoomFor(s, initlen + strlen(fmt)*2);
va_start(ap,fmt); va_start(ap,fmt);
f = fmt; /* Next format specifier byte to process. */ f = fmt; /* Next format specifier byte to process. */
i = initlen; /* Position of the next byte to write to dest str. */ i = initlen; /* Position of the next byte to write to dest str. */
......
...@@ -30,6 +30,10 @@ ...@@ -30,6 +30,10 @@
#include "server.h" #include "server.h"
#include "hiredis.h" #include "hiredis.h"
#ifdef USE_OPENSSL
#include "openssl/ssl.h"
#include "hiredis_ssl.h"
#endif
#include "async.h" #include "async.h"
#include <ctype.h> #include <ctype.h>
...@@ -40,6 +44,10 @@ ...@@ -40,6 +44,10 @@
extern char **environ; extern char **environ;
#ifdef USE_OPENSSL
extern SSL_CTX *redis_tls_ctx;
#endif
#define REDIS_SENTINEL_PORT 26379 #define REDIS_SENTINEL_PORT 26379
/* ======================== Sentinel global state =========================== */ /* ======================== Sentinel global state =========================== */
...@@ -1995,6 +2003,19 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char ...@@ -1995,6 +2003,19 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char
} }
} }
static int instanceLinkNegotiateTLS(redisAsyncContext *context) {
#ifndef USE_OPENSSL
(void) context;
#else
if (!redis_tls_ctx) return C_ERR;
SSL *ssl = SSL_new(redis_tls_ctx);
if (!ssl) return C_ERR;
if (redisInitiateSSL(&context->c, ssl) == REDIS_ERR) return C_ERR;
#endif
return C_OK;
}
/* Create the async connections for the instance link if the link /* Create the async connections for the instance link if the link
* is disconnected. Note that link->disconnected is true even if just * is disconnected. Note that link->disconnected is true even if just
* one of the two links (commands and pub/sub) is missing. */ * one of the two links (commands and pub/sub) is missing. */
...@@ -2010,7 +2031,11 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) { ...@@ -2010,7 +2031,11 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Commands connection. */ /* Commands connection. */
if (link->cc == NULL) { if (link->cc == NULL) {
link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR); link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
if (link->cc->err) { if (!link->cc->err && server.tls_replication &&
(instanceLinkNegotiateTLS(link->cc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");
instanceLinkCloseConnection(link,link->cc);
} else if (link->cc->err) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s", sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
link->cc->errstr); link->cc->errstr);
instanceLinkCloseConnection(link,link->cc); instanceLinkCloseConnection(link,link->cc);
...@@ -2033,7 +2058,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) { ...@@ -2033,7 +2058,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Pub / Sub */ /* Pub / Sub */
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) { if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR); link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
if (link->pc->err) { if (!link->pc->err && server.tls_replication &&
(instanceLinkNegotiateTLS(link->pc) == C_ERR)) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");
} else if (link->pc->err) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s", sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
link->pc->errstr); link->pc->errstr);
instanceLinkCloseConnection(link,link->pc); instanceLinkCloseConnection(link,link->pc);
...@@ -2584,8 +2612,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) { ...@@ -2584,8 +2612,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
return C_ERR; return C_ERR;
announce_ip = ip; announce_ip = ip;
} }
announce_port = sentinel.announce_port ? if (sentinel.announce_port) announce_port = sentinel.announce_port;
sentinel.announce_port : server.port; else if (server.tls_replication && server.tls_port) announce_port = server.tls_port;
else announce_port = server.port;
/* Format and send the Hello message. */ /* Format and send the Hello message. */
snprintf(payload,sizeof(payload), snprintf(payload,sizeof(payload),
......
...@@ -1449,12 +1449,18 @@ int incrementallyRehash(int dbid) { ...@@ -1449,12 +1449,18 @@ int incrementallyRehash(int dbid) {
* for dict.c to resize the hash tables accordingly to the fact we have o not * for dict.c to resize the hash tables accordingly to the fact we have o not
* running childs. */ * running childs. */
void updateDictResizePolicy(void) { void updateDictResizePolicy(void) {
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) if (!hasActiveChildProcess())
dictEnableResize(); dictEnableResize();
else else
dictDisableResize(); dictDisableResize();
} }
int hasActiveChildProcess() {
return server.rdb_child_pid != -1 ||
server.aof_child_pid != -1 ||
server.module_child_pid != -1;
}
/* ======================= Cron: called every 100 ms ======================== */ /* ======================= Cron: called every 100 ms ======================== */
/* Add a sample to the operations per second array of samples. */ /* Add a sample to the operations per second array of samples. */
...@@ -1691,7 +1697,7 @@ void databasesCron(void) { ...@@ -1691,7 +1697,7 @@ void databasesCron(void) {
/* Perform hash tables rehashing if needed, but only if there are no /* Perform hash tables rehashing if needed, but only if there are no
* other processes saving the DB on disk. Otherwise rehashing is bad * other processes saving the DB on disk. Otherwise rehashing is bad
* as will cause a lot of copy-on-write of memory pages. */ * as will cause a lot of copy-on-write of memory pages. */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) { if (!hasActiveChildProcess()) {
/* We use global counters so if we stop the computation at a given /* We use global counters so if we stop the computation at a given
* DB we'll be able to start from the successive in the next * DB we'll be able to start from the successive in the next
* cron loop iteration. */ * cron loop iteration. */
...@@ -1746,6 +1752,62 @@ void updateCachedTime(void) { ...@@ -1746,6 +1752,62 @@ void updateCachedTime(void) {
server.daylight_active = tm.tm_isdst; server.daylight_active = tm.tm_isdst;
} }
void checkChildrenDone(void) {
int statloc;
pid_t pid;
/* If we have a diskless rdb child (note that we support only one concurrent
* child), we want to avoid collecting it's exit status and acting on it
* as long as we didn't finish to drain the pipe, since then we're at risk
* of starting a new fork and a new pipe before we're done with the previous
* one. */
if (server.rdb_child_pid != -1 && server.rdb_pipe_conns)
return;
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
int exitcode = WEXITSTATUS(statloc);
int bysignal = 0;
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
/* sigKillChildHandler catches the signal and calls exit(), but we
* must make sure not to flag lastbgsave_status, etc incorrectly.
* We could directly terminate the child process via SIGUSR1
* without handling it, but in this case Valgrind will log an
* annoying error. */
if (exitcode == SERVER_CHILD_NOERROR_RETVAL) {
bysignal = SIGUSR1;
exitcode = 1;
}
if (pid == -1) {
serverLog(LL_WARNING,"wait3() returned an error: %s. "
"rdb_child_pid = %d, aof_child_pid = %d, module_child_pid = %d",
strerror(errno),
(int) server.rdb_child_pid,
(int) server.aof_child_pid,
(int) server.module_child_pid);
} else if (pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.module_child_pid) {
ModuleForkDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else {
if (!ldbRemoveChild(pid)) {
serverLog(LL_WARNING,
"Warning, detected child with unmatched pid: %ld",
(long)pid);
}
}
updateDictResizePolicy();
closeChildInfoPipe();
}
}
/* This is our timer interrupt, called server.hz times per second. /* This is our timer interrupt, called server.hz times per second.
* Here is where we do a number of things that need to be done asynchronously. * Here is where we do a number of things that need to be done asynchronously.
* For instance: * For instance:
...@@ -1888,47 +1950,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -1888,47 +1950,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Start a scheduled AOF rewrite if this was requested by the user while /* Start a scheduled AOF rewrite if this was requested by the user while
* a BGSAVE was in progress. */ * a BGSAVE was in progress. */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 && if (!hasActiveChildProcess() &&
server.aof_rewrite_scheduled) server.aof_rewrite_scheduled)
{ {
rewriteAppendOnlyFileBackground(); rewriteAppendOnlyFileBackground();
} }
/* Check if a background saving or AOF rewrite in progress terminated. */ /* Check if a background saving or AOF rewrite in progress terminated. */
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 || if (hasActiveChildProcess() || ldbPendingChildren())
ldbPendingChildren())
{ {
int statloc; checkChildrenDone();
pid_t pid;
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
int exitcode = WEXITSTATUS(statloc);
int bysignal = 0;
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
if (pid == -1) {
serverLog(LL_WARNING,"wait3() returned an error: %s. "
"rdb_child_pid = %d, aof_child_pid = %d",
strerror(errno),
(int) server.rdb_child_pid,
(int) server.aof_child_pid);
} else if (pid == server.rdb_child_pid) {
backgroundSaveDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
if (!bysignal && exitcode == 0) receiveChildInfo();
} else {
if (!ldbRemoveChild(pid)) {
serverLog(LL_WARNING,
"Warning, detected child with unmatched pid: %ld",
(long)pid);
}
}
updateDictResizePolicy();
closeChildInfoPipe();
}
} else { } else {
/* If there is not a background saving/rewrite in progress check if /* If there is not a background saving/rewrite in progress check if
* we have to save/rewrite now. */ * we have to save/rewrite now. */
...@@ -1956,8 +1987,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -1956,8 +1987,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Trigger an AOF rewrite if needed. */ /* Trigger an AOF rewrite if needed. */
if (server.aof_state == AOF_ON && if (server.aof_state == AOF_ON &&
server.rdb_child_pid == -1 && !hasActiveChildProcess() &&
server.aof_child_pid == -1 &&
server.aof_rewrite_perc && server.aof_rewrite_perc &&
server.aof_current_size > server.aof_rewrite_min_size) server.aof_current_size > server.aof_rewrite_min_size)
{ {
...@@ -2015,7 +2045,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -2015,7 +2045,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
* Note: this code must be after the replicationCron() call above so * Note: this code must be after the replicationCron() call above so
* make sure when refactoring this file to keep this order. This is useful * make sure when refactoring this file to keep this order. This is useful
* because we want to give priority to RDB savings for replication. */ * because we want to give priority to RDB savings for replication. */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 && if (!hasActiveChildProcess() &&
server.rdb_bgsave_scheduled && server.rdb_bgsave_scheduled &&
(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || (server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
server.lastbgsave_status == C_OK)) server.lastbgsave_status == C_OK))
...@@ -2026,6 +2056,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -2026,6 +2056,12 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
server.rdb_bgsave_scheduled = 0; server.rdb_bgsave_scheduled = 0;
} }
/* Fire the cron loop modules event. */
RedisModuleCronLoopV1 ei = {REDISMODULE_CRON_LOOP_VERSION,server.hz};
moduleFireServerEvent(REDISMODULE_EVENT_CRON_LOOP,
0,
&ei);
server.cronloops++; server.cronloops++;
return 1000/server.hz; return 1000/server.hz;
} }
...@@ -2036,6 +2072,11 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { ...@@ -2036,6 +2072,11 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
void beforeSleep(struct aeEventLoop *eventLoop) { void beforeSleep(struct aeEventLoop *eventLoop) {
UNUSED(eventLoop); UNUSED(eventLoop);
/* Handle TLS pending data. (must be done before flushAppendOnlyFile) */
tlsProcessPendingData();
/* If tls still has pending unread data don't sleep at all. */
aeSetDontWait(server.el, tlsHasPendingData());
/* Call the Redis Cluster before sleep function. Note that this function /* Call the Redis Cluster before sleep function. Note that this function
* may change the state of Redis Cluster (from ok to fail or vice versa), * may change the state of Redis Cluster (from ok to fail or vice versa),
* so it's a good idea to call it before serving the unblocked clients * so it's a good idea to call it before serving the unblocked clients
...@@ -2069,7 +2110,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) { ...@@ -2069,7 +2110,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
/* Check if there are clients unblocked by modules that implement /* Check if there are clients unblocked by modules that implement
* blocking commands. */ * blocking commands. */
moduleHandleBlockedClients(); if (moduleCount()) moduleHandleBlockedClients();
/* Try to process pending commands for clients that were just unblocked. */ /* Try to process pending commands for clients that were just unblocked. */
if (listLength(server.unblocked_clients)) if (listLength(server.unblocked_clients))
...@@ -2229,11 +2270,13 @@ void initServerConfig(void) { ...@@ -2229,11 +2270,13 @@ void initServerConfig(void) {
server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ; server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
server.arch_bits = (sizeof(long) == 8) ? 64 : 32; server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
server.port = CONFIG_DEFAULT_SERVER_PORT; server.port = CONFIG_DEFAULT_SERVER_PORT;
server.tls_port = CONFIG_DEFAULT_SERVER_TLS_PORT;
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG; server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
server.bindaddr_count = 0; server.bindaddr_count = 0;
server.unixsocket = NULL; server.unixsocket = NULL;
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM; server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
server.ipfd_count = 0; server.ipfd_count = 0;
server.tlsfd_count = 0;
server.sofd = -1; server.sofd = -1;
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE; server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED; server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED;
...@@ -2242,6 +2285,7 @@ void initServerConfig(void) { ...@@ -2242,6 +2285,7 @@ void initServerConfig(void) {
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT; server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE; server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
server.active_expire_enabled = 1; server.active_expire_enabled = 1;
server.jemalloc_bg_thread = 1;
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG; server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES; server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER; server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER;
...@@ -2267,6 +2311,7 @@ void initServerConfig(void) { ...@@ -2267,6 +2311,7 @@ void initServerConfig(void) {
server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE; server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE;
server.aof_rewrite_base_size = 0; server.aof_rewrite_base_size = 0;
server.aof_rewrite_scheduled = 0; server.aof_rewrite_scheduled = 0;
server.aof_flush_sleep = 0;
server.aof_last_fsync = time(NULL); server.aof_last_fsync = time(NULL);
server.aof_rewrite_time_last = -1; server.aof_rewrite_time_last = -1;
server.aof_rewrite_time_start = -1; server.aof_rewrite_time_start = -1;
...@@ -2278,6 +2323,7 @@ void initServerConfig(void) { ...@@ -2278,6 +2323,7 @@ void initServerConfig(void) {
server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC; server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC; server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC;
server.rdb_key_save_delay = CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY; server.rdb_key_save_delay = CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY;
server.key_load_delay = CONFIG_DEFAULT_KEY_LOAD_DELAY;
server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED; server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED;
server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE; server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE;
server.pidfile = NULL; server.pidfile = NULL;
...@@ -2349,7 +2395,7 @@ void initServerConfig(void) { ...@@ -2349,7 +2395,7 @@ void initServerConfig(void) {
server.repl_state = REPL_STATE_NONE; server.repl_state = REPL_STATE_NONE;
server.repl_transfer_tmpfile = NULL; server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1; server.repl_transfer_fd = -1;
server.repl_transfer_s = -1; server.repl_transfer_s = NULL;
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT; server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA; server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY; server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
...@@ -2746,6 +2792,11 @@ void initServer(void) { ...@@ -2746,6 +2792,11 @@ void initServer(void) {
server.clients_paused = 0; server.clients_paused = 0;
server.system_memory_size = zmalloc_get_memory_size(); server.system_memory_size = zmalloc_get_memory_size();
if (server.tls_port && tlsConfigure(&server.tls_ctx_config) == C_ERR) {
serverLog(LL_WARNING, "Failed to configure TLS. Check logs for more info.");
exit(1);
}
createSharedObjects(); createSharedObjects();
adjustOpenFilesLimit(); adjustOpenFilesLimit();
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR); server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
...@@ -2761,6 +2812,9 @@ void initServer(void) { ...@@ -2761,6 +2812,9 @@ void initServer(void) {
if (server.port != 0 && if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR) listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
exit(1); exit(1);
if (server.tls_port != 0 &&
listenToPort(server.tls_port,server.tlsfd,&server.tlsfd_count) == C_ERR)
exit(1);
/* Open the listening Unix domain socket. */ /* Open the listening Unix domain socket. */
if (server.unixsocket != NULL) { if (server.unixsocket != NULL) {
...@@ -2775,7 +2829,7 @@ void initServer(void) { ...@@ -2775,7 +2829,7 @@ void initServer(void) {
} }
/* Abort if there are no listening sockets at all. */ /* Abort if there are no listening sockets at all. */
if (server.ipfd_count == 0 && server.sofd < 0) { if (server.ipfd_count == 0 && server.tlsfd_count == 0 && server.sofd < 0) {
serverLog(LL_WARNING, "Configured to not listen anywhere, exiting."); serverLog(LL_WARNING, "Configured to not listen anywhere, exiting.");
exit(1); exit(1);
} }
...@@ -2799,7 +2853,13 @@ void initServer(void) { ...@@ -2799,7 +2853,13 @@ void initServer(void) {
server.cronloops = 0; server.cronloops = 0;
server.rdb_child_pid = -1; server.rdb_child_pid = -1;
server.aof_child_pid = -1; server.aof_child_pid = -1;
server.module_child_pid = -1;
server.rdb_child_type = RDB_CHILD_TYPE_NONE; server.rdb_child_type = RDB_CHILD_TYPE_NONE;
server.rdb_pipe_conns = NULL;
server.rdb_pipe_numconns = 0;
server.rdb_pipe_numconns_writing = 0;
server.rdb_pipe_buff = NULL;
server.rdb_pipe_bufflen = 0;
server.rdb_bgsave_scheduled = 0; server.rdb_bgsave_scheduled = 0;
server.child_info_pipe[0] = -1; server.child_info_pipe[0] = -1;
server.child_info_pipe[1] = -1; server.child_info_pipe[1] = -1;
...@@ -2817,6 +2877,7 @@ void initServer(void) { ...@@ -2817,6 +2877,7 @@ void initServer(void) {
server.stat_peak_memory = 0; server.stat_peak_memory = 0;
server.stat_rdb_cow_bytes = 0; server.stat_rdb_cow_bytes = 0;
server.stat_aof_cow_bytes = 0; server.stat_aof_cow_bytes = 0;
server.stat_module_cow_bytes = 0;
server.cron_malloc_stats.zmalloc_used = 0; server.cron_malloc_stats.zmalloc_used = 0;
server.cron_malloc_stats.process_rss = 0; server.cron_malloc_stats.process_rss = 0;
server.cron_malloc_stats.allocator_allocated = 0; server.cron_malloc_stats.allocator_allocated = 0;
...@@ -2845,6 +2906,14 @@ void initServer(void) { ...@@ -2845,6 +2906,14 @@ void initServer(void) {
"Unrecoverable error creating server.ipfd file event."); "Unrecoverable error creating server.ipfd file event.");
} }
} }
for (j = 0; j < server.tlsfd_count; j++) {
if (aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,
acceptTLSHandler,NULL) == AE_ERR)
{
serverPanic(
"Unrecoverable error creating server.tlsfd file event.");
}
}
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE, if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event."); acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
...@@ -2884,8 +2953,17 @@ void initServer(void) { ...@@ -2884,8 +2953,17 @@ void initServer(void) {
scriptingInit(1); scriptingInit(1);
slowlogInit(); slowlogInit();
latencyMonitorInit(); latencyMonitorInit();
}
/* Some steps in server initialization need to be done last (after modules
* are loaded).
* Specifically, creation of threads due to a race bug in ld.so, in which
* Thread Local Storage initialization collides with dlopen call.
* see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329 */
void InitServerLast() {
bioInit(); bioInit();
initThreadedIO(); initThreadedIO();
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
server.initial_memory_usage = zmalloc_used_memory(); server.initial_memory_usage = zmalloc_used_memory();
} }
...@@ -3136,7 +3214,7 @@ void preventCommandReplication(client *c) { ...@@ -3136,7 +3214,7 @@ void preventCommandReplication(client *c) {
* CMD_CALL_STATS Populate command stats. * CMD_CALL_STATS Populate command stats.
* CMD_CALL_PROPAGATE_AOF Append command to AOF if it modified the dataset * CMD_CALL_PROPAGATE_AOF Append command to AOF if it modified the dataset
* or if the client flags are forcing propagation. * or if the client flags are forcing propagation.
* CMD_CALL_PROPAGATE_REPL Send command to salves if it modified the dataset * CMD_CALL_PROPAGATE_REPL Send command to slaves if it modified the dataset
* or if the client flags are forcing propagation. * or if the client flags are forcing propagation.
* CMD_CALL_PROPAGATE Alias for PROPAGATE_AOF|PROPAGATE_REPL. * CMD_CALL_PROPAGATE Alias for PROPAGATE_AOF|PROPAGATE_REPL.
* CMD_CALL_FULL Alias for SLOWLOG|STATS|PROPAGATE. * CMD_CALL_FULL Alias for SLOWLOG|STATS|PROPAGATE.
...@@ -3341,11 +3419,12 @@ int processCommand(client *c) { ...@@ -3341,11 +3419,12 @@ int processCommand(client *c) {
/* Check if the user is authenticated. This check is skipped in case /* Check if the user is authenticated. This check is skipped in case
* the default user is flagged as "nopass" and is active. */ * the default user is flagged as "nopass" and is active. */
int auth_required = !(DefaultUser->flags & USER_FLAG_NOPASS) && int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
DefaultUser->flags & USER_FLAG_DISABLED) &&
!c->authenticated; !c->authenticated;
if (auth_required || DefaultUser->flags & USER_FLAG_DISABLED) { if (auth_required) {
/* AUTH and HELLO are valid even in non authenticated state. */ /* AUTH and HELLO are valid even in non authenticated state. */
if (c->cmd->proc != authCommand || c->cmd->proc == helloCommand) { if (c->cmd->proc != authCommand && c->cmd->proc != helloCommand) {
flagTransaction(c); flagTransaction(c);
addReply(c,shared.noautherr); addReply(c,shared.noautherr);
return C_OK; return C_OK;
...@@ -3411,7 +3490,10 @@ int processCommand(client *c) { ...@@ -3411,7 +3490,10 @@ int processCommand(client *c) {
* is in MULTI/EXEC context? Error. */ * is in MULTI/EXEC context? Error. */
if (out_of_memory && if (out_of_memory &&
(c->cmd->flags & CMD_DENYOOM || (c->cmd->flags & CMD_DENYOOM ||
(c->flags & CLIENT_MULTI && c->cmd->proc != execCommand))) { (c->flags & CLIENT_MULTI &&
c->cmd->proc != execCommand &&
c->cmd->proc != discardCommand)))
{
flagTransaction(c); flagTransaction(c);
addReply(c, shared.oomerr); addReply(c, shared.oomerr);
return C_OK; return C_OK;
...@@ -3536,6 +3618,7 @@ void closeListeningSockets(int unlink_unix_socket) { ...@@ -3536,6 +3618,7 @@ void closeListeningSockets(int unlink_unix_socket) {
int j; int j;
for (j = 0; j < server.ipfd_count; j++) close(server.ipfd[j]); for (j = 0; j < server.ipfd_count; j++) close(server.ipfd[j]);
for (j = 0; j < server.tlsfd_count; j++) close(server.tlsfd[j]);
if (server.sofd != -1) close(server.sofd); if (server.sofd != -1) close(server.sofd);
if (server.cluster_enabled) if (server.cluster_enabled)
for (j = 0; j < server.cfd_count; j++) close(server.cfd[j]); for (j = 0; j < server.cfd_count; j++) close(server.cfd[j]);
...@@ -3562,6 +3645,12 @@ int prepareForShutdown(int flags) { ...@@ -3562,6 +3645,12 @@ int prepareForShutdown(int flags) {
killRDBChild(); killRDBChild();
} }
/* Kill module child if there is one. */
if (server.module_child_pid != -1) {
serverLog(LL_WARNING,"There is a module fork child. Killing it!");
TerminateModuleForkChild(server.module_child_pid,0);
}
if (server.aof_state != AOF_OFF) { if (server.aof_state != AOF_OFF) {
/* Kill the AOF saving child as the AOF we already have may be longer /* Kill the AOF saving child as the AOF we already have may be longer
* but contains the full dataset anyway. */ * but contains the full dataset anyway. */
...@@ -3599,6 +3688,9 @@ int prepareForShutdown(int flags) { ...@@ -3599,6 +3688,9 @@ int prepareForShutdown(int flags) {
} }
} }
/* Fire the shutdown modules event. */
moduleFireServerEvent(REDISMODULE_EVENT_SHUTDOWN,0,NULL);
/* Remove the pid file if possible and needed. */ /* Remove the pid file if possible and needed. */
if (server.daemonize || server.pidfile) { if (server.daemonize || server.pidfile) {
serverLog(LL_NOTICE,"Removing the pid file."); serverLog(LL_NOTICE,"Removing the pid file.");
...@@ -3831,12 +3923,15 @@ sds genRedisInfoString(char *section) { ...@@ -3831,12 +3923,15 @@ sds genRedisInfoString(char *section) {
time_t uptime = server.unixtime-server.stat_starttime; time_t uptime = server.unixtime-server.stat_starttime;
int j; int j;
struct rusage self_ru, c_ru; struct rusage self_ru, c_ru;
int allsections = 0, defsections = 0; int allsections = 0, defsections = 0, everything = 0, modules = 0;
int sections = 0; int sections = 0;
if (section == NULL) section = "default"; if (section == NULL) section = "default";
allsections = strcasecmp(section,"all") == 0; allsections = strcasecmp(section,"all") == 0;
defsections = strcasecmp(section,"default") == 0; defsections = strcasecmp(section,"default") == 0;
everything = strcasecmp(section,"everything") == 0;
modules = strcasecmp(section,"modules") == 0;
if (everything) allsections = 1;
getrusage(RUSAGE_SELF, &self_ru); getrusage(RUSAGE_SELF, &self_ru);
getrusage(RUSAGE_CHILDREN, &c_ru); getrusage(RUSAGE_CHILDREN, &c_ru);
...@@ -3859,32 +3954,32 @@ sds genRedisInfoString(char *section) { ...@@ -3859,32 +3954,32 @@ sds genRedisInfoString(char *section) {
call_uname = 0; call_uname = 0;
} }
info = sdscatprintf(info, info = sdscatfmt(info,
"# Server\r\n" "# Server\r\n"
"redis_version:%s\r\n" "redis_version:%s\r\n"
"redis_git_sha1:%s\r\n" "redis_git_sha1:%s\r\n"
"redis_git_dirty:%d\r\n" "redis_git_dirty:%i\r\n"
"redis_build_id:%llx\r\n" "redis_build_id:%s\r\n"
"redis_mode:%s\r\n" "redis_mode:%s\r\n"
"os:%s %s %s\r\n" "os:%s %s %s\r\n"
"arch_bits:%d\r\n" "arch_bits:%i\r\n"
"multiplexing_api:%s\r\n" "multiplexing_api:%s\r\n"
"atomicvar_api:%s\r\n" "atomicvar_api:%s\r\n"
"gcc_version:%d.%d.%d\r\n" "gcc_version:%i.%i.%i\r\n"
"process_id:%ld\r\n" "process_id:%I\r\n"
"run_id:%s\r\n" "run_id:%s\r\n"
"tcp_port:%d\r\n" "tcp_port:%i\r\n"
"uptime_in_seconds:%jd\r\n" "uptime_in_seconds:%I\r\n"
"uptime_in_days:%jd\r\n" "uptime_in_days:%I\r\n"
"hz:%d\r\n" "hz:%i\r\n"
"configured_hz:%d\r\n" "configured_hz:%i\r\n"
"lru_clock:%ld\r\n" "lru_clock:%u\r\n"
"executable:%s\r\n" "executable:%s\r\n"
"config_file:%s\r\n", "config_file:%s\r\n",
REDIS_VERSION, REDIS_VERSION,
redisGitSHA1(), redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0, strtol(redisGitDirty(),NULL,10) > 0,
(unsigned long long) redisBuildId(), redisBuildIdString(),
mode, mode,
name.sysname, name.release, name.machine, name.sysname, name.release, name.machine,
server.arch_bits, server.arch_bits,
...@@ -3895,14 +3990,14 @@ sds genRedisInfoString(char *section) { ...@@ -3895,14 +3990,14 @@ sds genRedisInfoString(char *section) {
#else #else
0,0,0, 0,0,0,
#endif #endif
(long) getpid(), (int64_t) getpid(),
server.runid, server.runid,
server.port, server.port ? server.port : server.tls_port,
(intmax_t)uptime, (int64_t)uptime,
(intmax_t)(uptime/(3600*24)), (int64_t)(uptime/(3600*24)),
server.hz, server.hz,
server.config_hz, server.config_hz,
(unsigned long) server.lruclock, server.lruclock,
server.executable ? server.executable : "", server.executable ? server.executable : "",
server.configfile ? server.configfile : ""); server.configfile ? server.configfile : "");
} }
...@@ -4028,8 +4123,11 @@ sds genRedisInfoString(char *section) { ...@@ -4028,8 +4123,11 @@ sds genRedisInfoString(char *section) {
mh->allocator_rss_bytes, mh->allocator_rss_bytes,
mh->rss_extra, mh->rss_extra,
mh->rss_extra_bytes, mh->rss_extra_bytes,
mh->total_frag, /* this is the total RSS overhead, including fragmentation, */ mh->total_frag, /* This is the total RSS overhead, including
mh->total_frag_bytes, /* named so for backwards compatibility */ fragmentation, but not just it. This field
(and the next one) is named like that just
for backward compatibility. */
mh->total_frag_bytes,
freeMemoryGetNotCountedMemory(), freeMemoryGetNotCountedMemory(),
mh->repl_backlog, mh->repl_backlog,
mh->clients_slaves, mh->clients_slaves,
...@@ -4062,7 +4160,9 @@ sds genRedisInfoString(char *section) { ...@@ -4062,7 +4160,9 @@ sds genRedisInfoString(char *section) {
"aof_current_rewrite_time_sec:%jd\r\n" "aof_current_rewrite_time_sec:%jd\r\n"
"aof_last_bgrewrite_status:%s\r\n" "aof_last_bgrewrite_status:%s\r\n"
"aof_last_write_status:%s\r\n" "aof_last_write_status:%s\r\n"
"aof_last_cow_size:%zu\r\n", "aof_last_cow_size:%zu\r\n"
"module_fork_in_progress:%d\r\n"
"module_fork_last_cow_size:%zu\r\n",
server.loading, server.loading,
server.dirty, server.dirty,
server.rdb_child_pid != -1, server.rdb_child_pid != -1,
...@@ -4080,7 +4180,9 @@ sds genRedisInfoString(char *section) { ...@@ -4080,7 +4180,9 @@ sds genRedisInfoString(char *section) {
-1 : time(NULL)-server.aof_rewrite_time_start), -1 : time(NULL)-server.aof_rewrite_time_start),
(server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err", (server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
(server.aof_last_write_status == C_OK) ? "ok" : "err", (server.aof_last_write_status == C_OK) ? "ok" : "err",
server.stat_aof_cow_bytes); server.stat_aof_cow_bytes,
server.module_child_pid != -1,
server.stat_module_cow_bytes);
if (server.aof_enabled) { if (server.aof_enabled) {
info = sdscatprintf(info, info = sdscatprintf(info,
...@@ -4238,7 +4340,7 @@ sds genRedisInfoString(char *section) { ...@@ -4238,7 +4340,7 @@ sds genRedisInfoString(char *section) {
if (server.repl_state != REPL_STATE_CONNECTED) { if (server.repl_state != REPL_STATE_CONNECTED) {
info = sdscatprintf(info, info = sdscatprintf(info,
"master_link_down_since_seconds:%jd\r\n", "master_link_down_since_seconds:%jd\r\n",
(intmax_t)server.unixtime-server.repl_down_since); (intmax_t)(server.unixtime-server.repl_down_since));
} }
info = sdscatprintf(info, info = sdscatprintf(info,
"slave_priority:%d\r\n" "slave_priority:%d\r\n"
...@@ -4274,7 +4376,7 @@ sds genRedisInfoString(char *section) { ...@@ -4274,7 +4376,7 @@ sds genRedisInfoString(char *section) {
long lag = 0; long lag = 0;
if (slaveip[0] == '\0') { if (slaveip[0] == '\0') {
if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) == -1) if (connPeerToString(slave->conn,ip,sizeof(ip),&port) == -1)
continue; continue;
slaveip = ip; slaveip = ip;
} }
...@@ -4336,6 +4438,13 @@ sds genRedisInfoString(char *section) { ...@@ -4336,6 +4438,13 @@ sds genRedisInfoString(char *section) {
(long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec); (long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec);
} }
/* Modules */
if (allsections || defsections || !strcasecmp(section,"modules")) {
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info,"# Modules\r\n");
info = genModulesInfoString(info);
}
/* Command statistics */ /* Command statistics */
if (allsections || !strcasecmp(section,"commandstats")) { if (allsections || !strcasecmp(section,"commandstats")) {
if (sections++) info = sdscat(info,"\r\n"); if (sections++) info = sdscat(info,"\r\n");
...@@ -4381,6 +4490,17 @@ sds genRedisInfoString(char *section) { ...@@ -4381,6 +4490,17 @@ sds genRedisInfoString(char *section) {
} }
} }
} }
/* Get info from modules.
* if user asked for "everything" or "modules", or a specific section
* that's not found yet. */
if (everything || modules ||
(!allsections && !defsections && sections==0)) {
info = modulesCollectInfo(info,
everything || modules ? NULL: section,
0, /* not a crash report */
sections);
}
return info; return info;
} }
...@@ -4391,7 +4511,9 @@ void infoCommand(client *c) { ...@@ -4391,7 +4511,9 @@ void infoCommand(client *c) {
addReply(c,shared.syntaxerr); addReply(c,shared.syntaxerr);
return; return;
} }
addReplyBulkSds(c, genRedisInfoString(section)); sds info = genRedisInfoString(section);
addReplyVerbatim(c,info,sdslen(info),"txt");
sdsfree(info);
} }
void monitorCommand(client *c) { void monitorCommand(client *c) {
...@@ -4508,7 +4630,7 @@ void redisAsciiArt(void) { ...@@ -4508,7 +4630,7 @@ void redisAsciiArt(void) {
if (!show_logo) { if (!show_logo) {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Running mode=%s, port=%d.", "Running mode=%s, port=%d.",
mode, server.port mode, server.port ? server.port : server.tls_port
); );
} else { } else {
snprintf(buf,1024*16,ascii_logo, snprintf(buf,1024*16,ascii_logo,
...@@ -4516,7 +4638,7 @@ void redisAsciiArt(void) { ...@@ -4516,7 +4638,7 @@ void redisAsciiArt(void) {
redisGitSHA1(), redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0, strtol(redisGitDirty(),NULL,10) > 0,
(sizeof(long) == 8) ? "64" : "32", (sizeof(long) == 8) ? "64" : "32",
mode, server.port, mode, server.port ? server.port : server.tls_port,
(long) getpid() (long) getpid()
); );
serverLogRaw(LL_NOTICE|LL_RAW,buf); serverLogRaw(LL_NOTICE|LL_RAW,buf);
...@@ -4578,6 +4700,61 @@ void setupSignalHandlers(void) { ...@@ -4578,6 +4700,61 @@ void setupSignalHandlers(void) {
return; return;
} }
/* This is the signal handler for children process. It is currently useful
* in order to track the SIGUSR1, that we send to a child in order to terminate
* it in a clean way, without the parent detecting an error and stop
* accepting writes because of a write error condition. */
static void sigKillChildHandler(int sig) {
UNUSED(sig);
serverLogFromHandler(LL_WARNING, "Received SIGUSR1 in child, exiting now.");
exitFromChild(SERVER_CHILD_NOERROR_RETVAL);
}
void setupChildSignalHandlers(void) {
struct sigaction act;
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
* Otherwise, sa_handler is used. */
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = sigKillChildHandler;
sigaction(SIGUSR1, &act, NULL);
return;
}
int redisFork() {
int childpid;
long long start = ustime();
if ((childpid = fork()) == 0) {
/* Child */
closeListeningSockets(0);
setupChildSignalHandlers();
} else {
/* Parent */
server.stat_fork_time = ustime()-start;
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
if (childpid == -1) {
return -1;
}
updateDictResizePolicy();
}
return childpid;
}
void sendChildCOWInfo(int ptype, char *pname) {
size_t private_dirty = zmalloc_get_private_dirty(-1);
if (private_dirty) {
serverLog(LL_NOTICE,
"%s: %zu MB of memory used by copy-on-write",
pname, private_dirty/(1024*1024));
}
server.child_info_data.cow_size = private_dirty;
sendChildInfo(ptype);
}
void memtest(size_t megabytes, int passes); void memtest(size_t megabytes, int passes);
/* Returns 1 if there is --sentinel among the arguments or if /* Returns 1 if there is --sentinel among the arguments or if
...@@ -4599,17 +4776,19 @@ void loadDataFromDisk(void) { ...@@ -4599,17 +4776,19 @@ void loadDataFromDisk(void) {
serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000); serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
} else { } else {
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT; rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
if (rdbLoad(server.rdb_filename,&rsi) == C_OK) { if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_NONE) == C_OK) {
serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds", serverLog(LL_NOTICE,"DB loaded from disk: %.3f seconds",
(float)(ustime()-start)/1000000); (float)(ustime()-start)/1000000);
/* Restore the replication ID / offset from the RDB file. */ /* Restore the replication ID / offset from the RDB file. */
if ((server.masterhost || (server.cluster_enabled && nodeIsSlave(server.cluster->myself)))&& if ((server.masterhost ||
(server.cluster_enabled &&
nodeIsSlave(server.cluster->myself))) &&
rsi.repl_id_is_set && rsi.repl_id_is_set &&
rsi.repl_offset != -1 && rsi.repl_offset != -1 &&
/* Note that older implementations may save a repl_stream_db /* Note that older implementations may save a repl_stream_db
* of -1 inside the RDB file in a wrong way, see more information * of -1 inside the RDB file in a wrong way, see more
* in function rdbPopulateSaveInfo. */ * information in function rdbPopulateSaveInfo. */
rsi.repl_stream_db != -1) rsi.repl_stream_db != -1)
{ {
memcpy(server.replid,rsi.repl_id,sizeof(server.replid)); memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
...@@ -4642,7 +4821,7 @@ void redisSetProcTitle(char *title) { ...@@ -4642,7 +4821,7 @@ void redisSetProcTitle(char *title) {
setproctitle("%s %s:%d%s", setproctitle("%s %s:%d%s",
title, title,
server.bindaddr_count ? server.bindaddr[0] : "*", server.bindaddr_count ? server.bindaddr[0] : "*",
server.port, server.port ? server.port : server.tls_port,
server_mode); server_mode);
#else #else
UNUSED(title); UNUSED(title);
...@@ -4785,14 +4964,15 @@ int main(int argc, char **argv) { ...@@ -4785,14 +4964,15 @@ int main(int argc, char **argv) {
srand(time(NULL)^getpid()); srand(time(NULL)^getpid());
gettimeofday(&tv,NULL); gettimeofday(&tv,NULL);
char hashseed[16]; uint8_t hashseed[16];
getRandomHexChars(hashseed,sizeof(hashseed)); getRandomBytes(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed((uint8_t*)hashseed); dictSetHashFunctionSeed(hashseed);
server.sentinel_mode = checkForSentinelMode(argc,argv); server.sentinel_mode = checkForSentinelMode(argc,argv);
initServerConfig(); initServerConfig();
ACLInit(); /* The ACL subsystem must be initialized ASAP because the ACLInit(); /* The ACL subsystem must be initialized ASAP because the
basic networking code and client creation depends on it. */ basic networking code and client creation depends on it. */
moduleInitModulesSystem(); moduleInitModulesSystem();
tlsInit();
/* Store the executable path and arguments in a safe place in order /* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */ * to be able to restart the server later. */
...@@ -4916,6 +5096,7 @@ int main(int argc, char **argv) { ...@@ -4916,6 +5096,7 @@ int main(int argc, char **argv) {
#endif #endif
moduleLoadFromQueue(); moduleLoadFromQueue();
ACLLoadUsersAtStartup(); ACLLoadUsersAtStartup();
InitServerLast();
loadDataFromDisk(); loadDataFromDisk();
if (server.cluster_enabled) { if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) { if (verifyClusterConfigWithData() == C_ERR) {
...@@ -4925,11 +5106,12 @@ int main(int argc, char **argv) { ...@@ -4925,11 +5106,12 @@ int main(int argc, char **argv) {
exit(1); exit(1);
} }
} }
if (server.ipfd_count > 0) if (server.ipfd_count > 0 || server.tlsfd_count > 0)
serverLog(LL_NOTICE,"Ready to accept connections"); serverLog(LL_NOTICE,"Ready to accept connections");
if (server.sofd > 0) if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket); serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
} else { } else {
InitServerLast();
sentinelIsRunning(); sentinelIsRunning();
} }
......
...@@ -66,6 +66,10 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -66,6 +66,10 @@ typedef long long mstime_t; /* millisecond time type. */
#include "quicklist.h" /* Lists are encoded as linked lists of #include "quicklist.h" /* Lists are encoded as linked lists of
N-elements flat arrays */ N-elements flat arrays */
#include "rax.h" /* Radix tree */ #include "rax.h" /* Radix tree */
#include "connection.h" /* Connection abstraction */
#define REDISMODULE_CORE 1
#include "redismodule.h" /* Redis modules API defines. */
/* Following includes allow test functions to be called from Redis main() */ /* Following includes allow test functions to be called from Redis main() */
#include "zipmap.h" #include "zipmap.h"
...@@ -84,6 +88,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -84,6 +88,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_MAX_HZ 500 #define CONFIG_MAX_HZ 500
#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */ #define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */ #define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
#define CONFIG_DEFAULT_SERVER_TLS_PORT 0 /* TCP port. */
#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */ #define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */ #define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
#define CONFIG_DEFAULT_DBNUM 16 #define CONFIG_DEFAULT_DBNUM 16
...@@ -133,6 +138,7 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -133,6 +138,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0 #define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5 #define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
#define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0 #define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0
#define CONFIG_DEFAULT_KEY_LOAD_DELAY 0
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1 #define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1 #define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1 #define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
...@@ -179,6 +185,14 @@ typedef long long mstime_t; /* millisecond time type. */ ...@@ -179,6 +185,14 @@ typedef long long mstime_t; /* millisecond time type. */
#define ACTIVE_EXPIRE_CYCLE_SLOW 0 #define ACTIVE_EXPIRE_CYCLE_SLOW 0
#define ACTIVE_EXPIRE_CYCLE_FAST 1 #define ACTIVE_EXPIRE_CYCLE_FAST 1
/* Children process will exit with this status code to signal that the
* process terminated without an error: this is useful in order to kill
* a saving child (RDB or AOF one), without triggering in the parent the
* write protection that is normally turned on on write errors.
* Usually children that are terminated with SIGUSR1 will exit with this
* special code. */
#define SERVER_CHILD_NOERROR_RETVAL 255
/* Instantaneous metrics tracking. */ /* Instantaneous metrics tracking. */
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */ #define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */ #define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
...@@ -816,9 +830,14 @@ typedef struct user { ...@@ -816,9 +830,14 @@ typedef struct user {
/* With multiplexing we need to take per-client state. /* With multiplexing we need to take per-client state.
* Clients are taken in a linked list. */ * Clients are taken in a linked list. */
#define CLIENT_ID_AOF (UINT64_MAX) /* Reserved ID for the AOF client. If you
need more reserved IDs use UINT64_MAX-1,
-2, ... and so forth. */
typedef struct client { typedef struct client {
uint64_t id; /* Client incremental unique ID. */ uint64_t id; /* Client incremental unique ID. */
int fd; /* Client socket. */ connection *conn;
int resp; /* RESP protocol version. Can be 2 or 3. */ int resp; /* RESP protocol version. Can be 2 or 3. */
redisDb *db; /* Pointer to currently SELECTed DB. */ redisDb *db; /* Pointer to currently SELECTed DB. */
robj *name; /* As set by CLIENT SETNAME. */ robj *name; /* As set by CLIENT SETNAME. */
...@@ -1026,6 +1045,22 @@ struct malloc_stats { ...@@ -1026,6 +1045,22 @@ struct malloc_stats {
size_t allocator_resident; size_t allocator_resident;
}; };
/*-----------------------------------------------------------------------------
* TLS Context Configuration
*----------------------------------------------------------------------------*/
typedef struct redisTLSContextConfig {
char *cert_file;
char *key_file;
char *dh_params_file;
char *ca_cert_file;
char *ca_cert_dir;
char *protocols;
char *ciphers;
char *ciphersuites;
int prefer_server_ciphers;
} redisTLSContextConfig;
/*----------------------------------------------------------------------------- /*-----------------------------------------------------------------------------
* Global server state * Global server state
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
...@@ -1041,6 +1076,7 @@ struct clusterState; ...@@ -1041,6 +1076,7 @@ struct clusterState;
#define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL #define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL
#define CHILD_INFO_TYPE_RDB 0 #define CHILD_INFO_TYPE_RDB 0
#define CHILD_INFO_TYPE_AOF 1 #define CHILD_INFO_TYPE_AOF 1
#define CHILD_INFO_TYPE_MODULE 3
struct redisServer { struct redisServer {
/* General */ /* General */
...@@ -1076,8 +1112,10 @@ struct redisServer { ...@@ -1076,8 +1112,10 @@ struct redisServer {
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
client blocked on a module command needs client blocked on a module command needs
to be processed. */ to be processed. */
pid_t module_child_pid; /* PID of module child */
/* Networking */ /* Networking */
int port; /* TCP listening port */ int port; /* TCP listening port */
int tls_port; /* TLS listening port */
int tcp_backlog; /* TCP listen() backlog */ int tcp_backlog; /* TCP listen() backlog */
char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */ char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */
int bindaddr_count; /* Number of addresses in server.bindaddr[] */ int bindaddr_count; /* Number of addresses in server.bindaddr[] */
...@@ -1085,6 +1123,8 @@ struct redisServer { ...@@ -1085,6 +1123,8 @@ struct redisServer {
mode_t unixsocketperm; /* UNIX socket permission */ mode_t unixsocketperm; /* UNIX socket permission */
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */ int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
int ipfd_count; /* Used slots in ipfd[] */ int ipfd_count; /* Used slots in ipfd[] */
int tlsfd[CONFIG_BINDADDR_MAX]; /* TLS socket file descriptors */
int tlsfd_count; /* Used slots in tlsfd[] */
int sofd; /* Unix socket file descriptor */ int sofd; /* Unix socket file descriptor */
int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */ int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */
int cfd_count; /* Used slots in cfd[] */ int cfd_count; /* Used slots in cfd[] */
...@@ -1149,6 +1189,7 @@ struct redisServer { ...@@ -1149,6 +1189,7 @@ struct redisServer {
_Atomic long long stat_net_output_bytes; /* Bytes written to network. */ _Atomic long long stat_net_output_bytes; /* Bytes written to network. */
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */ size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */ size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
/* The following two are used to track instantaneous metrics, like /* The following two are used to track instantaneous metrics, like
* number of operations per second, network traffic. */ * number of operations per second, network traffic. */
struct { struct {
...@@ -1163,6 +1204,7 @@ struct redisServer { ...@@ -1163,6 +1204,7 @@ struct redisServer {
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */ int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
int active_expire_enabled; /* Can be disabled for testing purposes. */ int active_expire_enabled; /* Can be disabled for testing purposes. */
int active_defrag_enabled; int active_defrag_enabled;
int jemalloc_bg_thread; /* Enable jemalloc background thread */
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */ size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
int active_defrag_threshold_lower; /* minimum percentage of fragmentation to start active defrag */ int active_defrag_threshold_lower; /* minimum percentage of fragmentation to start active defrag */
int active_defrag_threshold_upper; /* maximum percentage of fragmentation at which we use maximum effort */ int active_defrag_threshold_upper; /* maximum percentage of fragmentation at which we use maximum effort */
...@@ -1186,6 +1228,7 @@ struct redisServer { ...@@ -1186,6 +1228,7 @@ struct redisServer {
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */ off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */
off_t aof_current_size; /* AOF current size. */ off_t aof_current_size; /* AOF current size. */
off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */ off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
int aof_flush_sleep; /* Micros to sleep before flush. (used by tests) */
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */ int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
pid_t aof_child_pid; /* PID if rewriting process */ pid_t aof_child_pid; /* PID if rewriting process */
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */ list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
...@@ -1231,10 +1274,17 @@ struct redisServer { ...@@ -1231,10 +1274,17 @@ struct redisServer {
int rdb_child_type; /* Type of save by active child. */ int rdb_child_type; /* Type of save by active child. */
int lastbgsave_status; /* C_OK or C_ERR */ int lastbgsave_status; /* C_OK or C_ERR */
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */ int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */ int rdb_pipe_write; /* RDB pipes used to transfer the rdb */
int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */ int rdb_pipe_read; /* data to the parent process in diskless repl. */
connection **rdb_pipe_conns; /* Connections which are currently the */
int rdb_pipe_numconns; /* target of diskless rdb fork child. */
int rdb_pipe_numconns_writing; /* Number of rdb conns with pending writes. */
char *rdb_pipe_buff; /* In diskless replication, this buffer holds data */
int rdb_pipe_bufflen; /* that was read from the the rdb pipe. */
int rdb_key_save_delay; /* Delay in microseconds between keys while int rdb_key_save_delay; /* Delay in microseconds between keys while
* writing the RDB. (for testings) */ * writing the RDB. (for testings) */
int key_load_delay; /* Delay in microseconds between keys while
* loading aof or rdb. (for testings) */
/* Pipe and data structures for child -> parent info sharing. */ /* Pipe and data structures for child -> parent info sharing. */
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */ int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
struct { struct {
...@@ -1287,7 +1337,7 @@ struct redisServer { ...@@ -1287,7 +1337,7 @@ struct redisServer {
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */ off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
off_t repl_transfer_read; /* Amount of RDB read from master during sync. */ off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */ off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
int repl_transfer_s; /* Slave -> Master SYNC socket */ connection *repl_transfer_s; /* Slave -> Master SYNC connection */
int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */ int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */
char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */ char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */ time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
...@@ -1378,6 +1428,7 @@ struct redisServer { ...@@ -1378,6 +1428,7 @@ struct redisServer {
lua_State *lua; /* The Lua interpreter. We use just one for all clients */ lua_State *lua; /* The Lua interpreter. We use just one for all clients */
client *lua_client; /* The "fake client" to query Redis from Lua */ client *lua_client; /* The "fake client" to query Redis from Lua */
client *lua_caller; /* The client running EVAL right now, or NULL */ client *lua_caller; /* The client running EVAL right now, or NULL */
char* lua_cur_script; /* SHA1 of the script currently running, or NULL */
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */ unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
mstime_t lua_time_limit; /* Script timeout in milliseconds */ mstime_t lua_time_limit; /* Script timeout in milliseconds */
...@@ -1410,6 +1461,11 @@ struct redisServer { ...@@ -1410,6 +1461,11 @@ struct redisServer {
int watchdog_period; /* Software watchdog period in ms. 0 = off */ int watchdog_period; /* Software watchdog period in ms. 0 = off */
/* System hardware info */ /* System hardware info */
size_t system_memory_size; /* Total memory in system as reported by OS */ size_t system_memory_size; /* Total memory in system as reported by OS */
/* TLS Configuration */
int tls_cluster;
int tls_replication;
int tls_auth_clients;
redisTLSContextConfig tls_ctx_config;
}; };
typedef struct pubsubPattern { typedef struct pubsubPattern {
...@@ -1540,7 +1596,16 @@ void moduleAcquireGIL(void); ...@@ -1540,7 +1596,16 @@ void moduleAcquireGIL(void);
void moduleReleaseGIL(void); void moduleReleaseGIL(void);
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid); void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
void moduleCallCommandFilters(client *c); void moduleCallCommandFilters(client *c);
void ModuleForkDoneHandler(int exitcode, int bysignal);
int TerminateModuleForkChild(int child_pid, int wait);
ssize_t rdbSaveModulesAux(rio *rdb, int when); ssize_t rdbSaveModulesAux(rio *rdb, int when);
int moduleAllDatatypesHandleErrors();
sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections);
void moduleFireServerEvent(uint64_t eid, int subid, void *data);
void processModuleLoadingProgressEvent(int is_aof);
int moduleTryServeClientBlockedOnKey(client *c, robj *key);
void moduleUnblockClient(client *c);
int moduleClientIsBlockedOnKeys(client *c);
/* Utils */ /* Utils */
long long ustime(void); long long ustime(void);
...@@ -1553,12 +1618,12 @@ size_t redisPopcount(void *s, long count); ...@@ -1553,12 +1618,12 @@ size_t redisPopcount(void *s, long count);
void redisSetProcTitle(char *title); void redisSetProcTitle(char *title);
/* networking.c -- Networking and Client related operations */ /* networking.c -- Networking and Client related operations */
client *createClient(int fd); client *createClient(connection *conn);
void closeTimedoutClients(void); void closeTimedoutClients(void);
void freeClient(client *c); void freeClient(client *c);
void freeClientAsync(client *c); void freeClientAsync(client *c);
void resetClient(client *c); void resetClient(client *c);
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask); void sendReplyToClient(connection *conn);
void *addReplyDeferredLen(client *c); void *addReplyDeferredLen(client *c);
void setDeferredArrayLen(client *c, void *node, long length); void setDeferredArrayLen(client *c, void *node, long length);
void setDeferredMapLen(client *c, void *node, long length); void setDeferredMapLen(client *c, void *node, long length);
...@@ -1570,8 +1635,9 @@ void processInputBufferAndReplicate(client *c); ...@@ -1570,8 +1635,9 @@ void processInputBufferAndReplicate(client *c);
void processGopherRequest(client *c); void processGopherRequest(client *c);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask); void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask); void readQueryFromClient(connection *conn);
void addReplyNull(client *c); void addReplyNull(client *c);
void addReplyNullArray(client *c); void addReplyNullArray(client *c);
void addReplyBool(client *c, int b); void addReplyBool(client *c, int b);
...@@ -1629,7 +1695,7 @@ int handleClientsWithPendingReadsUsingThreads(void); ...@@ -1629,7 +1695,7 @@ int handleClientsWithPendingReadsUsingThreads(void);
int stopThreadedIOIfNeeded(void); int stopThreadedIOIfNeeded(void);
int clientHasPendingReplies(client *c); int clientHasPendingReplies(client *c);
void unlinkClient(client *c); void unlinkClient(client *c);
int writeToClient(int fd, client *c, int handler_installed); int writeToClient(client *c, int handler_installed);
void linkClient(client *c); void linkClient(client *c);
void protectClient(client *c); void protectClient(client *c);
void unprotectClient(client *c); void unprotectClient(client *c);
...@@ -1765,12 +1831,16 @@ void clearReplicationId2(void); ...@@ -1765,12 +1831,16 @@ void clearReplicationId2(void);
void chopReplicationBacklog(void); void chopReplicationBacklog(void);
void replicationCacheMasterUsingMyself(void); void replicationCacheMasterUsingMyself(void);
void feedReplicationBacklog(void *ptr, size_t len); void feedReplicationBacklog(void *ptr, size_t len);
void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
void rdbPipeWriteHandlerConnRemoved(struct connection *conn);
/* Generic persistence functions */ /* Generic persistence functions */
void startLoadingFile(FILE* fp, char* filename); void startLoadingFile(FILE* fp, char* filename, int rdbflags);
void startLoading(size_t size); void startLoading(size_t size, int rdbflags);
void loadingProgress(off_t pos); void loadingProgress(off_t pos);
void stopLoading(void); void stopLoading(int success);
void startSaving(int rdbflags);
void stopSaving(int success);
#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */ #define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */ #define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
...@@ -1779,7 +1849,6 @@ int writeCommandsDeniedByDiskError(void); ...@@ -1779,7 +1849,6 @@ int writeCommandsDeniedByDiskError(void);
/* RDB persistence */ /* RDB persistence */
#include "rdb.h" #include "rdb.h"
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi);
void killRDBChild(void); void killRDBChild(void);
/* AOF persistence */ /* AOF persistence */
...@@ -1795,6 +1864,7 @@ void aofRewriteBufferReset(void); ...@@ -1795,6 +1864,7 @@ void aofRewriteBufferReset(void);
unsigned long aofRewriteBufferSize(void); unsigned long aofRewriteBufferSize(void);
ssize_t aofReadDiffFromParent(void); ssize_t aofReadDiffFromParent(void);
void killAppendOnlyChild(void); void killAppendOnlyChild(void);
void restartAOFAfterSYNC();
/* Child info */ /* Child info */
void openChildInfoPipe(void); void openChildInfoPipe(void);
...@@ -1802,6 +1872,11 @@ void closeChildInfoPipe(void); ...@@ -1802,6 +1872,11 @@ void closeChildInfoPipe(void);
void sendChildInfo(int process_type); void sendChildInfo(int process_type);
void receiveChildInfo(void); void receiveChildInfo(void);
/* Fork helpers */
int redisFork();
int hasActiveChildProcess();
void sendChildCOWInfo(int ptype, char *pname);
/* acl.c -- Authentication related prototypes. */ /* acl.c -- Authentication related prototypes. */
extern rax *Users; extern rax *Users;
extern user *DefaultUser; extern user *DefaultUser;
...@@ -1902,6 +1977,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name); ...@@ -1902,6 +1977,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
void call(client *c, int flags); void call(client *c, int flags);
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags); void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target); void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
void redisOpArrayInit(redisOpArray *oa);
void redisOpArrayFree(redisOpArray *oa);
void forceCommandPropagation(client *c, int flags); void forceCommandPropagation(client *c, int flags);
void preventCommandPropagation(client *c); void preventCommandPropagation(client *c);
void preventCommandAOF(client *c); void preventCommandAOF(client *c);
...@@ -1930,6 +2007,7 @@ unsigned int LRU_CLOCK(void); ...@@ -1930,6 +2007,7 @@ unsigned int LRU_CLOCK(void);
const char *evictPolicyToString(void); const char *evictPolicyToString(void);
struct redisMemOverhead *getMemoryOverheadData(void); struct redisMemOverhead *getMemoryOverheadData(void);
void freeMemoryOverheadData(struct redisMemOverhead *mh); void freeMemoryOverheadData(struct redisMemOverhead *mh);
void checkChildrenDone(void);
#define RESTART_SERVER_NONE 0 #define RESTART_SERVER_NONE 0
#define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */ #define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */
...@@ -2007,9 +2085,10 @@ robj *lookupKeyWrite(redisDb *db, robj *key); ...@@ -2007,9 +2085,10 @@ robj *lookupKeyWrite(redisDb *db, robj *key);
robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply); robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply); robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags); robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags);
robj *objectCommandLookup(client *c, robj *key); robj *objectCommandLookup(client *c, robj *key);
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply); robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle, int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
long long lru_clock); long long lru_clock);
#define LOOKUP_NONE 0 #define LOOKUP_NONE 0
#define LOOKUP_NOTOUCH (1<<0) #define LOOKUP_NOTOUCH (1<<0)
...@@ -2026,6 +2105,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o); ...@@ -2026,6 +2105,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */ #define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
long long emptyDb(int dbnum, int flags, void(callback)(void*)); long long emptyDb(int dbnum, int flags, void(callback)(void*));
long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)); long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*));
void flushAllDataAndResetRDB(int flags);
long long dbTotalServerKeyCount(); long long dbTotalServerKeyCount();
int selectDb(client *c, int id); int selectDb(client *c, int id);
...@@ -2120,6 +2200,7 @@ void dictSdsDestructor(void *privdata, void *val); ...@@ -2120,6 +2200,7 @@ void dictSdsDestructor(void *privdata, void *val);
char *redisGitSHA1(void); char *redisGitSHA1(void);
char *redisGitDirty(void); char *redisGitDirty(void);
uint64_t redisBuildId(void); uint64_t redisBuildId(void);
char *redisBuildIdString(void);
/* Commands prototypes */ /* Commands prototypes */
void authCommand(client *c); void authCommand(client *c);
...@@ -2334,6 +2415,7 @@ void bugReportStart(void); ...@@ -2334,6 +2415,7 @@ void bugReportStart(void);
void serverLogObjectDebugInfo(const robj *o); void serverLogObjectDebugInfo(const robj *o);
void sigsegvHandler(int sig, siginfo_t *info, void *secret); void sigsegvHandler(int sig, siginfo_t *info, void *secret);
sds genRedisInfoString(char *section); sds genRedisInfoString(char *section);
sds genModulesInfoString(sds info);
void enableWatchdog(int period); void enableWatchdog(int period);
void disableWatchdog(void); void disableWatchdog(void);
void watchdogScheduleSignal(int period); void watchdogScheduleSignal(int period);
...@@ -2343,6 +2425,10 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len); ...@@ -2343,6 +2425,10 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len);
void xorDigest(unsigned char *digest, void *ptr, size_t len); void xorDigest(unsigned char *digest, void *ptr, size_t len);
int populateCommandTableParseFlags(struct redisCommand *c, char *strflags); int populateCommandTableParseFlags(struct redisCommand *c, char *strflags);
/* TLS stuff */
void tlsInit(void);
int tlsConfigure(redisTLSContextConfig *ctx_config);
#define redisDebug(fmt, ...) \ #define redisDebug(fmt, ...) \
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__) printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
#define redisDebugMark() \ #define redisDebugMark() \
......
...@@ -58,7 +58,8 @@ int siptlw(int c) { ...@@ -58,7 +58,8 @@ int siptlw(int c) {
/* Test of the CPU is Little Endian and supports not aligned accesses. /* Test of the CPU is Little Endian and supports not aligned accesses.
* Two interesting conditions to speedup the function that happen to be * Two interesting conditions to speedup the function that happen to be
* in most of x86 servers. */ * in most of x86 servers. */
#if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__) #if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__) \
|| defined (__aarch64__) || defined (__arm64__)
#define UNALIGNED_LE_CPU #define UNALIGNED_LE_CPU
#endif #endif
......
...@@ -88,7 +88,7 @@ typedef struct streamNACK { ...@@ -88,7 +88,7 @@ typedef struct streamNACK {
/* Stream propagation informations, passed to functions in order to propagate /* Stream propagation informations, passed to functions in order to propagate
* XCLAIM commands to AOF and slaves. */ * XCLAIM commands to AOF and slaves. */
typedef struct sreamPropInfo { typedef struct streamPropInfo {
robj *keyname; robj *keyname;
robj *groupname; robj *groupname;
} streamPropInfo; } streamPropInfo;
......
...@@ -242,17 +242,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_ ...@@ -242,17 +242,17 @@ int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_
* the current node is full. */ * the current node is full. */
if (lp != NULL) { if (lp != NULL) {
if (server.stream_node_max_bytes && if (server.stream_node_max_bytes &&
lp_bytes > server.stream_node_max_bytes) lp_bytes >= server.stream_node_max_bytes)
{ {
lp = NULL; lp = NULL;
} else if (server.stream_node_max_entries) { } else if (server.stream_node_max_entries) {
int64_t count = lpGetInteger(lpFirst(lp)); int64_t count = lpGetInteger(lpFirst(lp));
if (count > server.stream_node_max_entries) lp = NULL; if (count >= server.stream_node_max_entries) lp = NULL;
} }
} }
int flags = STREAM_ITEM_FLAG_NONE; int flags = STREAM_ITEM_FLAG_NONE;
if (lp == NULL || lp_bytes > server.stream_node_max_bytes) { if (lp == NULL || lp_bytes >= server.stream_node_max_bytes) {
master_id = id; master_id = id;
streamEncodeID(rax_key,&id); streamEncodeID(rax_key,&id);
/* Create the listpack having the master entry ID and fields. */ /* Create the listpack having the master entry ID and fields. */
......
/*
* 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"
#include "adlist.h"
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#define REDIS_TLS_PROTO_TLSv1 (1<<0)
#define REDIS_TLS_PROTO_TLSv1_1 (1<<1)
#define REDIS_TLS_PROTO_TLSv1_2 (1<<2)
#define REDIS_TLS_PROTO_TLSv1_3 (1<<3)
/* Use safe defaults */
#ifdef TLS1_3_VERSION
#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2|REDIS_TLS_PROTO_TLSv1_3)
#else
#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2)
#endif
extern ConnectionType CT_Socket;
SSL_CTX *redis_tls_ctx;
static int parseProtocolsConfig(const char *str) {
int i, count = 0;
int protocols = 0;
if (!str) return REDIS_TLS_PROTO_DEFAULT;
sds *tokens = sdssplitlen(str, strlen(str), " ", 1, &count);
if (!tokens) {
serverLog(LL_WARNING, "Invalid tls-protocols configuration string");
return -1;
}
for (i = 0; i < count; i++) {
if (!strcasecmp(tokens[i], "tlsv1")) protocols |= REDIS_TLS_PROTO_TLSv1;
else if (!strcasecmp(tokens[i], "tlsv1.1")) protocols |= REDIS_TLS_PROTO_TLSv1_1;
else if (!strcasecmp(tokens[i], "tlsv1.2")) protocols |= REDIS_TLS_PROTO_TLSv1_2;
else if (!strcasecmp(tokens[i], "tlsv1.3")) {
#ifdef TLS1_3_VERSION
protocols |= REDIS_TLS_PROTO_TLSv1_3;
#else
serverLog(LL_WARNING, "TLSv1.3 is specified in tls-protocols but not supported by OpenSSL.");
protocols = -1;
break;
#endif
} else {
serverLog(LL_WARNING, "Invalid tls-protocols specified. "
"Use a combination of 'TLSv1', 'TLSv1.1', 'TLSv1.2' and 'TLSv1.3'.");
protocols = -1;
break;
}
}
sdsfreesplitres(tokens, count);
return protocols;
}
/* list of connections with pending data already read from the socket, but not
* served to the reader yet. */
static list *pending_list = NULL;
void tlsInit(void) {
ERR_load_crypto_strings();
SSL_load_error_strings();
SSL_library_init();
if (!RAND_poll()) {
serverLog(LL_WARNING, "OpenSSL: Failed to seed random number generator.");
}
pending_list = listCreate();
/* Server configuration */
server.tls_auth_clients = 1; /* Secure by default */
}
/* Attempt to configure/reconfigure TLS. This operation is atomic and will
* leave the SSL_CTX unchanged if fails.
*/
int tlsConfigure(redisTLSContextConfig *ctx_config) {
char errbuf[256];
SSL_CTX *ctx = NULL;
if (!ctx_config->cert_file) {
serverLog(LL_WARNING, "No tls-cert-file configured!");
goto error;
}
if (!ctx_config->key_file) {
serverLog(LL_WARNING, "No tls-key-file configured!");
goto error;
}
if (!ctx_config->ca_cert_file && !ctx_config->ca_cert_dir) {
serverLog(LL_WARNING, "Either tls-ca-cert-file or tls-ca-cert-dir must be configured!");
goto error;
}
ctx = SSL_CTX_new(SSLv23_method());
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
#endif
int protocols = parseProtocolsConfig(ctx_config->protocols);
if (protocols == -1) goto error;
if (!(protocols & REDIS_TLS_PROTO_TLSv1))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
if (!(protocols & REDIS_TLS_PROTO_TLSv1_1))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
#ifdef SSL_OP_NO_TLSv1_2
if (!(protocols & REDIS_TLS_PROTO_TLSv1_2))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2);
#endif
#ifdef SSL_OP_NO_TLSv1_3
if (!(protocols & REDIS_TLS_PROTO_TLSv1_3))
SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3);
#endif
#ifdef SSL_OP_NO_COMPRESSION
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#endif
#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
#endif
if (ctx_config->prefer_server_ciphers)
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
SSL_CTX_set_ecdh_auto(ctx, 1);
if (SSL_CTX_use_certificate_file(ctx, ctx_config->cert_file, SSL_FILETYPE_PEM) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load certificate: %s: %s", ctx_config->cert_file, errbuf);
goto error;
}
if (SSL_CTX_use_PrivateKey_file(ctx, ctx_config->key_file, SSL_FILETYPE_PEM) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load private key: %s: %s", ctx_config->key_file, errbuf);
goto error;
}
if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, ctx_config->ca_cert_dir) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to configure CA certificate(s) file/directory: %s", errbuf);
goto error;
}
if (ctx_config->dh_params_file) {
FILE *dhfile = fopen(ctx_config->dh_params_file, "r");
DH *dh = NULL;
if (!dhfile) {
serverLog(LL_WARNING, "Failed to load %s: %s", ctx_config->dh_params_file, strerror(errno));
goto error;
}
dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL);
fclose(dhfile);
if (!dh) {
serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file);
goto error;
}
if (SSL_CTX_set_tmp_dh(ctx, dh) <= 0) {
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
serverLog(LL_WARNING, "Failed to load DH params file: %s: %s", ctx_config->dh_params_file, errbuf);
DH_free(dh);
goto error;
}
DH_free(dh);
}
if (ctx_config->ciphers && !SSL_CTX_set_cipher_list(ctx, ctx_config->ciphers)) {
serverLog(LL_WARNING, "Failed to configure ciphers: %s", ctx_config->ciphers);
goto error;
}
#ifdef TLS1_3_VERSION
if (ctx_config->ciphersuites && !SSL_CTX_set_ciphersuites(ctx, ctx_config->ciphersuites)) {
serverLog(LL_WARNING, "Failed to configure ciphersuites: %s", ctx_config->ciphersuites);
goto error;
}
#endif
SSL_CTX_free(redis_tls_ctx);
redis_tls_ctx = ctx;
return C_OK;
error:
if (ctx) SSL_CTX_free(ctx);
return C_ERR;
}
#ifdef TLS_DEBUGGING
#define TLSCONN_DEBUG(fmt, ...) \
serverLog(LL_DEBUG, "TLSCONN: " fmt, __VA_ARGS__)
#else
#define TLSCONN_DEBUG(fmt, ...)
#endif
ConnectionType CT_TLS;
/* Normal socket connections have a simple events/handler correlation.
*
* With TLS connections we need to handle cases where during a logical read
* or write operation, the SSL library asks to block for the opposite
* socket operation.
*
* When this happens, we need to do two things:
* 1. Make sure we register for the even.
* 2. Make sure we know which handler needs to execute when the
* event fires. That is, if we notify the caller of a write operation
* that it blocks, and SSL asks for a read, we need to trigger the
* write handler again on the next read event.
*
*/
typedef enum {
WANT_READ = 1,
WANT_WRITE
} WantIOType;
#define TLS_CONN_FLAG_READ_WANT_WRITE (1<<0)
#define TLS_CONN_FLAG_WRITE_WANT_READ (1<<1)
#define TLS_CONN_FLAG_FD_SET (1<<2)
typedef struct tls_connection {
connection c;
int flags;
SSL *ssl;
char *ssl_error;
listNode *pending_list_node;
} tls_connection;
connection *connCreateTLS(void) {
tls_connection *conn = zcalloc(sizeof(tls_connection));
conn->c.type = &CT_TLS;
conn->c.fd = -1;
conn->ssl = SSL_new(redis_tls_ctx);
return (connection *) conn;
}
connection *connCreateAcceptedTLS(int fd, int require_auth) {
tls_connection *conn = (tls_connection *) connCreateTLS();
conn->c.fd = fd;
conn->c.state = CONN_STATE_ACCEPTING;
if (!require_auth) {
/* We still verify certificates if provided, but don't require them.
*/
SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, NULL);
}
SSL_set_fd(conn->ssl, conn->c.fd);
SSL_set_accept_state(conn->ssl);
return (connection *) conn;
}
static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask);
/* Process the return code received from OpenSSL>
* Update the want parameter with expected I/O.
* Update the connection's error state if a real error has occured.
* Returns an SSL error code, or 0 if no further handling is required.
*/
static int handleSSLReturnCode(tls_connection *conn, int ret_value, WantIOType *want) {
if (ret_value <= 0) {
int ssl_err = SSL_get_error(conn->ssl, ret_value);
switch (ssl_err) {
case SSL_ERROR_WANT_WRITE:
*want = WANT_WRITE;
return 0;
case SSL_ERROR_WANT_READ:
*want = WANT_READ;
return 0;
case SSL_ERROR_SYSCALL:
conn->c.last_errno = errno;
if (conn->ssl_error) zfree(conn->ssl_error);
conn->ssl_error = errno ? zstrdup(strerror(errno)) : NULL;
break;
default:
/* Error! */
conn->c.last_errno = 0;
if (conn->ssl_error) zfree(conn->ssl_error);
conn->ssl_error = zmalloc(512);
ERR_error_string_n(ERR_get_error(), conn->ssl_error, 512);
break;
}
return ssl_err;
}
return 0;
}
void registerSSLEvent(tls_connection *conn, WantIOType want) {
int mask = aeGetFileEvents(server.el, conn->c.fd);
switch (want) {
case WANT_READ:
if (mask & AE_WRITABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
if (!(mask & AE_READABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE,
tlsEventHandler, conn);
break;
case WANT_WRITE:
if (mask & AE_READABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
if (!(mask & AE_WRITABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE,
tlsEventHandler, conn);
break;
default:
serverAssert(0);
break;
}
}
void updateSSLEvent(tls_connection *conn) {
int mask = aeGetFileEvents(server.el, conn->c.fd);
int need_read = conn->c.read_handler || (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ);
int need_write = conn->c.write_handler || (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE);
if (need_read && !(mask & AE_READABLE))
aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE, tlsEventHandler, conn);
if (!need_read && (mask & AE_READABLE))
aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
if (need_write && !(mask & AE_WRITABLE))
aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE, tlsEventHandler, conn);
if (!need_write && (mask & AE_WRITABLE))
aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
}
static void tlsHandleEvent(tls_connection *conn, int mask) {
int ret;
TLSCONN_DEBUG("tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d",
fd, conn->c.state, mask, conn->c.read_handler != NULL, conn->c.write_handler != NULL,
conn->flags);
ERR_clear_error();
switch (conn->c.state) {
case CONN_STATE_CONNECTING:
if (connGetSocketError((connection *) conn)) {
conn->c.last_errno = errno;
conn->c.state = CONN_STATE_ERROR;
} else {
if (!(conn->flags & TLS_CONN_FLAG_FD_SET)) {
SSL_set_fd(conn->ssl, conn->c.fd);
conn->flags |= TLS_CONN_FLAG_FD_SET;
}
ret = SSL_connect(conn->ssl);
if (ret <= 0) {
WantIOType want = 0;
if (!handleSSLReturnCode(conn, ret, &want)) {
registerSSLEvent(conn, want);
/* Avoid hitting UpdateSSLEvent, which knows nothing
* of what SSL_connect() wants and instead looks at our
* R/W handlers.
*/
return;
}
/* If not handled, it's an error */
conn->c.state = CONN_STATE_ERROR;
} else {
conn->c.state = CONN_STATE_CONNECTED;
}
}
if (!callHandler((connection *) conn, conn->c.conn_handler)) return;
conn->c.conn_handler = NULL;
break;
case CONN_STATE_ACCEPTING:
ret = SSL_accept(conn->ssl);
if (ret <= 0) {
WantIOType want = 0;
if (!handleSSLReturnCode(conn, ret, &want)) {
/* Avoid hitting UpdateSSLEvent, which knows nothing
* of what SSL_connect() wants and instead looks at our
* R/W handlers.
*/
registerSSLEvent(conn, want);
return;
}
/* If not handled, it's an error */
conn->c.state = CONN_STATE_ERROR;
} else {
conn->c.state = CONN_STATE_CONNECTED;
}
if (!callHandler((connection *) conn, conn->c.conn_handler)) return;
conn->c.conn_handler = NULL;
break;
case CONN_STATE_CONNECTED:
{
int call_read = ((mask & AE_READABLE) && conn->c.read_handler) ||
((mask & AE_WRITABLE) && (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE));
int call_write = ((mask & AE_WRITABLE) && conn->c.write_handler) ||
((mask & AE_READABLE) && (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ));
/* Normally we execute the readable event first, and the writable
* event laster. 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 fsynching a file to disk,
* before replying to a client. */
int invert = conn->c.flags & CONN_FLAG_WRITE_BARRIER;
if (!invert && call_read) {
conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
if (!callHandler((connection *) conn, conn->c.read_handler)) return;
}
/* Fire the writable event. */
if (call_write) {
conn->flags &= ~TLS_CONN_FLAG_WRITE_WANT_READ;
if (!callHandler((connection *) conn, conn->c.write_handler)) return;
}
/* If we have to invert the call, fire the readable event now
* after the writable one. */
if (invert && call_read) {
conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
if (!callHandler((connection *) conn, conn->c.read_handler)) return;
}
/* If SSL has pending that, already read from the socket, we're at
* risk of not calling the read handler again, make sure to add it
* to a list of pending connection that should be handled anyway. */
if ((mask & AE_READABLE)) {
if (SSL_pending(conn->ssl) > 0) {
if (!conn->pending_list_node) {
listAddNodeTail(pending_list, conn);
conn->pending_list_node = listLast(pending_list);
}
} else if (conn->pending_list_node) {
listDelNode(pending_list, conn->pending_list_node);
conn->pending_list_node = NULL;
}
}
break;
}
default:
break;
}
updateSSLEvent(conn);
}
static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask) {
UNUSED(el);
UNUSED(fd);
tls_connection *conn = clientData;
tlsHandleEvent(conn, mask);
}
static void connTLSClose(connection *conn_) {
tls_connection *conn = (tls_connection *) conn_;
if (conn->ssl) {
SSL_free(conn->ssl);
conn->ssl = NULL;
}
if (conn->ssl_error) {
zfree(conn->ssl_error);
conn->ssl_error = NULL;
}
if (conn->pending_list_node) {
listDelNode(pending_list, conn->pending_list_node);
conn->pending_list_node = NULL;
}
CT_Socket.close(conn_);
}
static int connTLSAccept(connection *_conn, ConnectionCallbackFunc accept_handler) {
tls_connection *conn = (tls_connection *) _conn;
int ret;
if (conn->c.state != CONN_STATE_ACCEPTING) return C_ERR;
ERR_clear_error();
/* Try to accept */
conn->c.conn_handler = accept_handler;
ret = SSL_accept(conn->ssl);
if (ret <= 0) {
WantIOType want = 0;
if (!handleSSLReturnCode(conn, ret, &want)) {
registerSSLEvent(conn, want); /* We'll fire back */
return C_OK;
} else {
conn->c.state = CONN_STATE_ERROR;
return C_ERR;
}
}
conn->c.state = CONN_STATE_CONNECTED;
if (!callHandler((connection *) conn, conn->c.conn_handler)) return C_OK;
conn->c.conn_handler = NULL;
return C_OK;
}
static int connTLSConnect(connection *conn_, const char *addr, int port, const char *src_addr, ConnectionCallbackFunc connect_handler) {
tls_connection *conn = (tls_connection *) conn_;
if (conn->c.state != CONN_STATE_NONE) return C_ERR;
ERR_clear_error();
/* Initiate Socket connection first */
if (CT_Socket.connect(conn_, addr, port, src_addr, connect_handler) == C_ERR) return C_ERR;
/* Return now, once the socket is connected we'll initiate
* TLS connection from the event handler.
*/
return C_OK;
}
static int connTLSWrite(connection *conn_, const void *data, size_t data_len) {
tls_connection *conn = (tls_connection *) conn_;
int ret, ssl_err;
if (conn->c.state != CONN_STATE_CONNECTED) return -1;
ERR_clear_error();
ret = SSL_write(conn->ssl, data, data_len);
if (ret <= 0) {
WantIOType want = 0;
if (!(ssl_err = handleSSLReturnCode(conn, ret, &want))) {
if (want == WANT_READ) conn->flags |= TLS_CONN_FLAG_WRITE_WANT_READ;
updateSSLEvent(conn);
errno = EAGAIN;
return -1;
} else {
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
((ssl_err == SSL_ERROR_SYSCALL && !errno))) {
conn->c.state = CONN_STATE_CLOSED;
return 0;
} else {
conn->c.state = CONN_STATE_ERROR;
return -1;
}
}
}
return ret;
}
static int connTLSRead(connection *conn_, void *buf, size_t buf_len) {
tls_connection *conn = (tls_connection *) conn_;
int ret;
int ssl_err;
if (conn->c.state != CONN_STATE_CONNECTED) return -1;
ERR_clear_error();
ret = SSL_read(conn->ssl, buf, buf_len);
if (ret <= 0) {
WantIOType want = 0;
if (!(ssl_err = handleSSLReturnCode(conn, ret, &want))) {
if (want == WANT_WRITE) conn->flags |= TLS_CONN_FLAG_READ_WANT_WRITE;
updateSSLEvent(conn);
errno = EAGAIN;
return -1;
} else {
if (ssl_err == SSL_ERROR_ZERO_RETURN ||
((ssl_err == SSL_ERROR_SYSCALL) && !errno)) {
conn->c.state = CONN_STATE_CLOSED;
return 0;
} else {
conn->c.state = CONN_STATE_ERROR;
return -1;
}
}
}
return ret;
}
static const char *connTLSGetLastError(connection *conn_) {
tls_connection *conn = (tls_connection *) conn_;
if (conn->ssl_error) return conn->ssl_error;
return NULL;
}
int connTLSSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
conn->write_handler = func;
if (barrier)
conn->flags |= CONN_FLAG_WRITE_BARRIER;
else
conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
updateSSLEvent((tls_connection *) conn);
return C_OK;
}
int connTLSSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
conn->read_handler = func;
updateSSLEvent((tls_connection *) conn);
return C_OK;
}
static void setBlockingTimeout(tls_connection *conn, long long timeout) {
anetBlock(NULL, conn->c.fd);
anetSendTimeout(NULL, conn->c.fd, timeout);
anetRecvTimeout(NULL, conn->c.fd, timeout);
}
static void unsetBlockingTimeout(tls_connection *conn) {
anetNonBlock(NULL, conn->c.fd);
anetSendTimeout(NULL, conn->c.fd, 0);
anetRecvTimeout(NULL, conn->c.fd, 0);
}
static int connTLSBlockingConnect(connection *conn_, const char *addr, int port, long long timeout) {
tls_connection *conn = (tls_connection *) conn_;
int ret;
if (conn->c.state != CONN_STATE_NONE) return C_ERR;
/* Initiate socket blocking connect first */
if (CT_Socket.blocking_connect(conn_, addr, port, timeout) == C_ERR) return C_ERR;
/* Initiate TLS connection now. We set up a send/recv timeout on the socket,
* which means the specified timeout will not be enforced accurately. */
SSL_set_fd(conn->ssl, conn->c.fd);
setBlockingTimeout(conn, timeout);
if ((ret = SSL_connect(conn->ssl)) <= 0) {
conn->c.state = CONN_STATE_ERROR;
return C_ERR;
}
unsetBlockingTimeout(conn);
conn->c.state = CONN_STATE_CONNECTED;
return C_OK;
}
static ssize_t connTLSSyncWrite(connection *conn_, char *ptr, ssize_t size, long long timeout) {
tls_connection *conn = (tls_connection *) conn_;
setBlockingTimeout(conn, timeout);
SSL_clear_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
int ret = SSL_write(conn->ssl, ptr, size);
SSL_set_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
unsetBlockingTimeout(conn);
return ret;
}
static ssize_t connTLSSyncRead(connection *conn_, char *ptr, ssize_t size, long long timeout) {
tls_connection *conn = (tls_connection *) conn_;
setBlockingTimeout(conn, timeout);
int ret = SSL_read(conn->ssl, ptr, size);
unsetBlockingTimeout(conn);
return ret;
}
static ssize_t connTLSSyncReadLine(connection *conn_, char *ptr, ssize_t size, long long timeout) {
tls_connection *conn = (tls_connection *) conn_;
ssize_t nread = 0;
setBlockingTimeout(conn, timeout);
size--;
while(size) {
char c;
if (SSL_read(conn->ssl,&c,1) <= 0) {
nread = -1;
goto exit;
}
if (c == '\n') {
*ptr = '\0';
if (nread && *(ptr-1) == '\r') *(ptr-1) = '\0';
goto exit;
} else {
*ptr++ = c;
*ptr = '\0';
nread++;
}
size--;
}
exit:
unsetBlockingTimeout(conn);
return nread;
}
ConnectionType CT_TLS = {
.ae_handler = tlsEventHandler,
.accept = connTLSAccept,
.connect = connTLSConnect,
.blocking_connect = connTLSBlockingConnect,
.read = connTLSRead,
.write = connTLSWrite,
.close = connTLSClose,
.set_write_handler = connTLSSetWriteHandler,
.set_read_handler = connTLSSetReadHandler,
.get_last_error = connTLSGetLastError,
.sync_write = connTLSSyncWrite,
.sync_read = connTLSSyncRead,
.sync_readline = connTLSSyncReadLine,
};
int tlsHasPendingData() {
if (!pending_list)
return 0;
return listLength(pending_list) > 0;
}
void tlsProcessPendingData() {
listIter li;
listNode *ln;
listRewind(pending_list,&li);
while((ln = listNext(&li))) {
tls_connection *conn = listNodeValue(ln);
tlsHandleEvent(conn, AE_READABLE);
}
}
#else /* USE_OPENSSL */
void tlsInit(void) {
}
int tlsConfigure(redisTLSContextConfig *ctx_config) {
UNUSED(ctx_config);
return C_OK;
}
connection *connCreateTLS(void) {
return NULL;
}
connection *connCreateAcceptedTLS(int fd, int require_auth) {
UNUSED(fd);
UNUSED(require_auth);
return NULL;
}
int tlsHasPendingData() {
return 0;
}
void tlsProcessPendingData() {
}
#endif
...@@ -326,6 +326,7 @@ size_t zmalloc_get_rss(void) { ...@@ -326,6 +326,7 @@ size_t zmalloc_get_rss(void) {
#endif #endif
#if defined(USE_JEMALLOC) #if defined(USE_JEMALLOC)
int zmalloc_get_allocator_info(size_t *allocated, int zmalloc_get_allocator_info(size_t *allocated,
size_t *active, size_t *active,
size_t *resident) { size_t *resident) {
...@@ -347,13 +348,44 @@ int zmalloc_get_allocator_info(size_t *allocated, ...@@ -347,13 +348,44 @@ int zmalloc_get_allocator_info(size_t *allocated,
je_mallctl("stats.allocated", allocated, &sz, NULL, 0); je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
return 1; return 1;
} }
void set_jemalloc_bg_thread(int enable) {
/* let jemalloc do purging asynchronously, required when there's no traffic
* after flushdb */
char val = !!enable;
je_mallctl("background_thread", NULL, 0, &val, 1);
}
int jemalloc_purge() {
/* return all unused (reserved) pages to the OS */
char tmp[32];
unsigned narenas = 0;
size_t sz = sizeof(unsigned);
if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
sprintf(tmp, "arena.%d.purge", narenas);
if (!je_mallctl(tmp, NULL, 0, NULL, 0))
return 0;
}
return -1;
}
#else #else
int zmalloc_get_allocator_info(size_t *allocated, int zmalloc_get_allocator_info(size_t *allocated,
size_t *active, size_t *active,
size_t *resident) { size_t *resident) {
*allocated = *resident = *active = 0; *allocated = *resident = *active = 0;
return 1; return 1;
} }
void set_jemalloc_bg_thread(int enable) {
((void)(enable));
}
int jemalloc_purge() {
return 0;
}
#endif #endif
/* Get the sum of the specified field (converted form kb to bytes) in /* Get the sum of the specified field (converted form kb to bytes) in
......
...@@ -86,6 +86,8 @@ size_t zmalloc_used_memory(void); ...@@ -86,6 +86,8 @@ size_t zmalloc_used_memory(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t)); void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
size_t zmalloc_get_rss(void); size_t zmalloc_get_rss(void);
int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident); int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
void set_jemalloc_bg_thread(int enable);
int jemalloc_purge();
size_t zmalloc_get_private_dirty(long pid); size_t zmalloc_get_private_dirty(long pid);
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid); size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
size_t zmalloc_get_memory_size(void); size_t zmalloc_get_memory_size(void);
......
...@@ -8,6 +8,7 @@ source ../instances.tcl ...@@ -8,6 +8,7 @@ source ../instances.tcl
source ../../support/cluster.tcl ; # Redis Cluster client. source ../../support/cluster.tcl ; # Redis Cluster client.
set ::instances_count 20 ; # How many instances we use at max. set ::instances_count 20 ; # How many instances we use at max.
set ::tlsdir "../../tls"
proc main {} { proc main {} {
parse_options parse_options
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
# are preseved across iterations. # are preseved across iterations.
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
test "Create a 5 nodes cluster" { test "Create a 5 nodes cluster" {
create_cluster 5 5 create_cluster 5 5
...@@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" { ...@@ -79,6 +80,7 @@ test "Cluster consistency during live resharding" {
--cluster-to $target \ --cluster-to $target \
--cluster-slots 100 \ --cluster-slots 100 \
--cluster-yes \ --cluster-yes \
{*}[rediscli_tls_config "../../../tests"] \
| [info nameofexecutable] \ | [info nameofexecutable] \
../tests/helpers/onlydots.tcl \ ../tests/helpers/onlydots.tcl \
&] 0] &] 0]
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
# other masters have slaves. # other masters have slaves.
source "../tests/includes/init-tests.tcl" source "../tests/includes/init-tests.tcl"
source "../../../tests/support/cli.tcl"
# Create a cluster with 5 master and 15 slaves, to make sure there are no # Create a cluster with 5 master and 15 slaves, to make sure there are no
# empty masters and make rebalancing simpler to handle during the test. # empty masters and make rebalancing simpler to handle during the test.
...@@ -33,7 +34,9 @@ test "Resharding all the master #0 slots away from it" { ...@@ -33,7 +34,9 @@ test "Resharding all the master #0 slots away from it" {
set output [exec \ set output [exec \
../../../src/redis-cli --cluster rebalance \ ../../../src/redis-cli --cluster rebalance \
127.0.0.1:[get_instance_attrib redis 0 port] \ 127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
--cluster-weight ${master0_id}=0 >@ stdout ] --cluster-weight ${master0_id}=0 >@ stdout ]
} }
test "Master #0 should lose its replicas" { test "Master #0 should lose its replicas" {
...@@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" { ...@@ -51,6 +54,7 @@ test "Resharding back some slot to master #0" {
set output [exec \ set output [exec \
../../../src/redis-cli --cluster rebalance \ ../../../src/redis-cli --cluster rebalance \
127.0.0.1:[get_instance_attrib redis 0 port] \ 127.0.0.1:[get_instance_attrib redis 0 port] \
{*}[rediscli_tls_config "../../../tests"] \
--cluster-weight ${master0_id}=.01 \ --cluster-weight ${master0_id}=.01 \
--cluster-use-empty-masters >@ stdout] --cluster-use-empty-masters >@ stdout]
} }
......
source tests/support/redis.tcl source tests/support/redis.tcl
source tests/support/util.tcl source tests/support/util.tcl
set ::tlsdir "tests/tls"
# This function sometimes writes sometimes blocking-reads from lists/sorted # This function sometimes writes sometimes blocking-reads from lists/sorted
# sets. There are multiple processes like this executing at the same time # sets. There are multiple processes like this executing at the same time
# so that we have some chance to trap some corner condition if there is # so that we have some chance to trap some corner condition if there is
...@@ -8,8 +10,8 @@ source tests/support/util.tcl ...@@ -8,8 +10,8 @@ source tests/support/util.tcl
# space to just a few elements, and balance the operations so that it is # space to just a few elements, and balance the operations so that it is
# unlikely that lists and zsets just get more data without ever causing # unlikely that lists and zsets just get more data without ever causing
# blocking. # blocking.
proc bg_block_op {host port db ops} { proc bg_block_op {host port db ops tls} {
set r [redis $host $port] set r [redis $host $port 0 $tls]
$r select $db $r select $db
for {set j 0} {$j < $ops} {incr j} { for {set j 0} {$j < $ops} {incr j} {
...@@ -49,4 +51,4 @@ proc bg_block_op {host port db ops} { ...@@ -49,4 +51,4 @@ proc bg_block_op {host port db ops} {
} }
} }
bg_block_op [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] bg_block_op [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
source tests/support/redis.tcl source tests/support/redis.tcl
source tests/support/util.tcl source tests/support/util.tcl
proc bg_complex_data {host port db ops} { set ::tlsdir "tests/tls"
set r [redis $host $port]
proc bg_complex_data {host port db ops tls} {
set r [redis $host $port 0 $tls]
$r select $db $r select $db
createComplexDataset $r $ops createComplexDataset $r $ops
} }
bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
source tests/support/redis.tcl source tests/support/redis.tcl
proc gen_write_load {host port seconds} { set ::tlsdir "tests/tls"
proc gen_write_load {host port seconds tls} {
set start_time [clock seconds] set start_time [clock seconds]
set r [redis $host $port 1] set r [redis $host $port 0 $tls]
$r select 9 $r select 9
while 1 { while 1 {
$r set [expr rand()] [expr rand()] $r set [expr rand()] [expr rand()]
...@@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} { ...@@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} {
} }
} }
gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
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