Commit fe63046e authored by Oran Agra's avatar Oran Agra
Browse files

diskless replication on slave side

parent c77081a4
...@@ -193,6 +193,20 @@ int anetSendTimeout(char *err, int fd, long long ms) { ...@@ -193,6 +193,20 @@ int anetSendTimeout(char *err, int fd, long long ms) {
return ANET_OK; return ANET_OK;
} }
/* Set the socket receive timeout (SO_RCVTIMEO socket option) to the specified
* number of milliseconds, or disable it if the 'ms' argument is zero. */
int anetRecvTimeout(char *err, int fd, long long ms) {
struct timeval tv;
tv.tv_sec = ms/1000;
tv.tv_usec = (ms%1000)*1000;
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
anetSetError(err, "setsockopt SO_RCVTIMEO: %s", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to /* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
* do the actual work. It resolves the hostname "host" and set the string * do the actual work. It resolves the hostname "host" and set the string
* representation of the IP address into the buffer pointed by "ipbuf". * representation of the IP address into the buffer pointed by "ipbuf".
......
...@@ -67,6 +67,7 @@ int anetEnableTcpNoDelay(char *err, int fd); ...@@ -67,6 +67,7 @@ int anetEnableTcpNoDelay(char *err, int fd);
int anetDisableTcpNoDelay(char *err, int fd); int anetDisableTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd); int anetTcpKeepAlive(char *err, int fd);
int anetSendTimeout(char *err, int fd, long long ms); int anetSendTimeout(char *err, int fd, long long ms);
int anetRecvTimeout(char *err, int fd, long long ms);
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port); int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
int anetKeepAlive(char *err, int fd, int interval); int anetKeepAlive(char *err, int fd, int interval);
int anetSockName(int fd, char *ip, size_t ip_len, int *port); int anetSockName(int fd, char *ip, size_t ip_len, int *port);
......
...@@ -620,7 +620,7 @@ int loadAppendOnlyFile(char *filename) { ...@@ -620,7 +620,7 @@ int loadAppendOnlyFile(char *filename) {
server.aof_state = REDIS_AOF_OFF; server.aof_state = REDIS_AOF_OFF;
fakeClient = createFakeClient(); fakeClient = createFakeClient();
startLoading(fp); startLoadingFile(fp);
while(1) { while(1) {
int argc, j; int argc, j;
......
...@@ -1187,18 +1187,19 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { ...@@ -1187,18 +1187,19 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
/* Mark that we are loading in the global state and setup the fields /* Mark that we are loading in the global state and setup the fields
* needed to provide loading stats. */ * needed to provide loading stats. */
void startLoading(FILE *fp) { void startLoading(size_t size) {
struct stat sb;
/* Load the DB */ /* Load the DB */
server.loading = 1; server.loading = 1;
server.loading_start_time = time(NULL); server.loading_start_time = time(NULL);
server.loading_loaded_bytes = 0; server.loading_loaded_bytes = 0;
if (fstat(fileno(fp), &sb) == -1) { server.loading_total_bytes = size;
server.loading_total_bytes = 0; }
} else {
server.loading_total_bytes = sb.st_size; void startLoadingFile(FILE *fp) {
} struct stat sb;
if (fstat(fileno(fp), &sb) == -1)
sb.st_size = 0;
startLoading(sb.st_size);
} }
/* Refresh the loading progress info */ /* Refresh the loading progress info */
...@@ -1233,66 +1234,74 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) { ...@@ -1233,66 +1234,74 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
} }
int rdbLoad(char *filename) { int rdbLoad(char *filename) {
FILE *fp;
int retval;
rio rdb;
struct stat sb;
if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
if (fstat(fileno(fp), &sb) == -1)
sb.st_size = 0;
rioInitWithFile(&rdb,fp);
retval = rdbLoadRio(&rdb, sb.st_size);
fclose(fp);
return retval;
}
int rdbLoadRio(rio *rdb, size_t size) {
uint32_t dbid; uint32_t dbid;
int type, rdbver; int type, rdbver;
redisDb *db = server.db+0; redisDb *db = server.db+0;
char buf[1024]; char buf[1024];
long long expiretime, now = mstime(); long long expiretime, now = mstime();
FILE *fp;
rio rdb;
if ((fp = fopen(filename,"r")) == NULL) return REDIS_ERR;
rioInitWithFile(&rdb,fp); rdb->update_cksum = rdbLoadProgressCallback;
rdb.update_cksum = rdbLoadProgressCallback; rdb->max_processing_chunk = server.loading_process_events_interval_bytes;
rdb.max_processing_chunk = server.loading_process_events_interval_bytes; if (rioRead(rdb,buf,9) == 0) goto eoferr;
if (rioRead(&rdb,buf,9) == 0) goto eoferr;
buf[9] = '\0'; buf[9] = '\0';
if (memcmp(buf,"REDIS",5) != 0) { if (memcmp(buf,"REDIS",5) != 0) {
fclose(fp);
redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file"); redisLog(REDIS_WARNING,"Wrong signature trying to load DB from file");
errno = EINVAL; errno = EINVAL;
return REDIS_ERR; return REDIS_ERR;
} }
rdbver = atoi(buf+5); rdbver = atoi(buf+5);
if (rdbver < 1 || rdbver > REDIS_RDB_VERSION) { if (rdbver < 1 || rdbver > REDIS_RDB_VERSION) {
fclose(fp);
redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver); redisLog(REDIS_WARNING,"Can't handle RDB format version %d",rdbver);
errno = EINVAL; errno = EINVAL;
return REDIS_ERR; return REDIS_ERR;
} }
startLoading(fp); startLoading(size);
while(1) { while(1) {
robj *key, *val; robj *key, *val;
expiretime = -1; expiretime = -1;
/* Read type. */ /* Read type. */
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr; if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
/* Handle special types. */ /* Handle special types. */
if (type == REDIS_RDB_OPCODE_EXPIRETIME) { if (type == REDIS_RDB_OPCODE_EXPIRETIME) {
/* EXPIRETIME: load an expire associated with the next key /* EXPIRETIME: load an expire associated with the next key
* to load. Note that after loading an expire we need to * to load. Note that after loading an expire we need to
* load the actual type, and continue. */ * load the actual type, and continue. */
if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr; if ((expiretime = rdbLoadTime(rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again. */ /* We read the time so we need to read the object type again. */
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr; if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
/* the EXPIRETIME opcode specifies time in seconds, so convert /* the EXPIRETIME opcode specifies time in seconds, so convert
* into milliseconds. */ * into milliseconds. */
expiretime *= 1000; expiretime *= 1000;
} else if (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) { } else if (type == REDIS_RDB_OPCODE_EXPIRETIME_MS) {
/* EXPIRETIME_MS: milliseconds precision expire times introduced /* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */ * with RDB v3. Like EXPIRETIME but no with more precision. */
if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr; if ((expiretime = rdbLoadMillisecondTime(rdb)) == -1) goto eoferr;
/* We read the time so we need to read the object type again. */ /* We read the time so we need to read the object type again. */
if ((type = rdbLoadType(&rdb)) == -1) goto eoferr; if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
} else if (type == REDIS_RDB_OPCODE_EOF) { } else if (type == REDIS_RDB_OPCODE_EOF) {
/* EOF: End of file, exit the main loop. */ /* EOF: End of file, exit the main loop. */
break; break;
} else if (type == REDIS_RDB_OPCODE_SELECTDB) { } else if (type == REDIS_RDB_OPCODE_SELECTDB) {
/* SELECTDB: Select the specified database. */ /* SELECTDB: Select the specified database. */
if ((dbid = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR) if ((dbid = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR)
goto eoferr; goto eoferr;
if (dbid >= (unsigned)server.dbnum) { if (dbid >= (unsigned)server.dbnum) {
redisLog(REDIS_WARNING, redisLog(REDIS_WARNING,
...@@ -1307,9 +1316,9 @@ int rdbLoad(char *filename) { ...@@ -1307,9 +1316,9 @@ int rdbLoad(char *filename) {
/* RESIZEDB: Hint about the size of the keys in the currently /* RESIZEDB: Hint about the size of the keys in the currently
* selected data base, in order to avoid useless rehashing. */ * selected data base, in order to avoid useless rehashing. */
uint32_t db_size, expires_size; uint32_t db_size, expires_size;
if ((db_size = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR) if ((db_size = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR)
goto eoferr; goto eoferr;
if ((expires_size = rdbLoadLen(&rdb,NULL)) == REDIS_RDB_LENERR) if ((expires_size = rdbLoadLen(rdb,NULL)) == REDIS_RDB_LENERR)
goto eoferr; goto eoferr;
dictExpand(db->dict,db_size); dictExpand(db->dict,db_size);
dictExpand(db->expires,expires_size); dictExpand(db->expires,expires_size);
...@@ -1321,8 +1330,8 @@ int rdbLoad(char *filename) { ...@@ -1321,8 +1330,8 @@ int rdbLoad(char *filename) {
* *
* An AUX field is composed of two strings: key and value. */ * An AUX field is composed of two strings: key and value. */
robj *auxkey, *auxval; robj *auxkey, *auxval;
if ((auxkey = rdbLoadStringObject(&rdb)) == NULL) goto eoferr; if ((auxkey = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
if ((auxval = rdbLoadStringObject(&rdb)) == NULL) goto eoferr; if ((auxval = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
if (((char*)auxkey->ptr)[0] == '%') { if (((char*)auxkey->ptr)[0] == '%') {
/* All the fields with a name staring with '%' are considered /* All the fields with a name staring with '%' are considered
...@@ -1344,9 +1353,9 @@ int rdbLoad(char *filename) { ...@@ -1344,9 +1353,9 @@ int rdbLoad(char *filename) {
} }
/* Read key */ /* Read key */
if ((key = rdbLoadStringObject(&rdb)) == NULL) goto eoferr; if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
/* Read value */ /* Read value */
if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr; if ((val = rdbLoadObject(type,rdb)) == NULL) goto eoferr;
/* Check if the key already expired. This function is used when loading /* Check if the key already expired. This function is used when loading
* an RDB file from disk, either at startup, or when an RDB was * an RDB file from disk, either at startup, or when an RDB was
* received from the master. In the latter case, the master is * received from the master. In the latter case, the master is
...@@ -1367,9 +1376,9 @@ int rdbLoad(char *filename) { ...@@ -1367,9 +1376,9 @@ int rdbLoad(char *filename) {
} }
/* Verify the checksum if RDB version is >= 5 */ /* Verify the checksum if RDB version is >= 5 */
if (rdbver >= 5 && server.rdb_checksum) { if (rdbver >= 5 && server.rdb_checksum) {
uint64_t cksum, expected = rdb.cksum; uint64_t cksum, expected = rdb->cksum;
if (rioRead(&rdb,&cksum,8) == 0) goto eoferr; if (rioRead(rdb,&cksum,8) == 0) goto eoferr;
memrev64ifbe(&cksum); memrev64ifbe(&cksum);
if (cksum == 0) { if (cksum == 0) {
redisLog(REDIS_WARNING,"RDB file was saved with checksum disabled: no check performed."); redisLog(REDIS_WARNING,"RDB file was saved with checksum disabled: no check performed.");
...@@ -1379,7 +1388,6 @@ int rdbLoad(char *filename) { ...@@ -1379,7 +1388,6 @@ int rdbLoad(char *filename) {
} }
} }
fclose(fp);
stopLoading(); stopLoading();
return REDIS_OK; return REDIS_OK;
...@@ -1572,7 +1580,7 @@ int rdbSaveToSlavesSockets(void) { ...@@ -1572,7 +1580,7 @@ int rdbSaveToSlavesSockets(void) {
clientids[numfds] = slave->id; clientids[numfds] = slave->id;
fds[numfds++] = slave->fd; fds[numfds++] = slave->fd;
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END; slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
/* Put the socket in non-blocking mode to simplify RDB transfer. /* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the children returns (since duped socket * We'll restore it when the children returns (since duped socket
* will share the O_NONBLOCK attribute with the parent). */ * will share the O_NONBLOCK attribute with the parent). */
anetBlock(NULL,slave->fd); anetBlock(NULL,slave->fd);
...@@ -1646,6 +1654,7 @@ int rdbSaveToSlavesSockets(void) { ...@@ -1646,6 +1654,7 @@ int rdbSaveToSlavesSockets(void) {
zfree(msg); zfree(msg);
} }
zfree(clientids); zfree(clientids);
rioFreeFdset(&slave_sockets);
exitFromChild((retval == REDIS_OK) ? 0 : 1); exitFromChild((retval == REDIS_OK) ? 0 : 1);
} else { } else {
/* Parent */ /* Parent */
......
...@@ -105,6 +105,7 @@ uint32_t rdbLoadLen(rio *rdb, int *isencoded); ...@@ -105,6 +105,7 @@ uint32_t rdbLoadLen(rio *rdb, int *isencoded);
int rdbSaveObjectType(rio *rdb, robj *o); int rdbSaveObjectType(rio *rdb, robj *o);
int rdbLoadObjectType(rio *rdb); int rdbLoadObjectType(rio *rdb);
int rdbLoad(char *filename); int rdbLoad(char *filename);
int rdbLoadRio(rio *rdb, size_t size);
int rdbSaveBackground(char *filename); int rdbSaveBackground(char *filename);
int rdbSaveToSlavesSockets(void); int rdbSaveToSlavesSockets(void);
void rdbRemoveTempFile(pid_t childpid); void rdbRemoveTempFile(pid_t childpid);
......
...@@ -1490,6 +1490,9 @@ void initServerConfig(void) { ...@@ -1490,6 +1490,9 @@ void initServerConfig(void) {
server.cached_master = NULL; server.cached_master = NULL;
server.repl_master_initial_offset = -1; server.repl_master_initial_offset = -1;
server.repl_state = REDIS_REPL_NONE; server.repl_state = REDIS_REPL_NONE;
server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1;
server.repl_transfer_s = -1;
server.repl_syncio_timeout = REDIS_REPL_SYNCIO_TIMEOUT; server.repl_syncio_timeout = REDIS_REPL_SYNCIO_TIMEOUT;
server.repl_serve_stale_data = REDIS_DEFAULT_SLAVE_SERVE_STALE_DATA; server.repl_serve_stale_data = REDIS_DEFAULT_SLAVE_SERVE_STALE_DATA;
server.repl_slave_ro = REDIS_DEFAULT_SLAVE_READ_ONLY; server.repl_slave_ro = REDIS_DEFAULT_SLAVE_READ_ONLY;
......
...@@ -1195,7 +1195,8 @@ long long replicationGetSlaveOffset(void); ...@@ -1195,7 +1195,8 @@ long long replicationGetSlaveOffset(void);
char *replicationGetSlaveName(redisClient *c); char *replicationGetSlaveName(redisClient *c);
/* Generic persistence functions */ /* Generic persistence functions */
void startLoading(FILE *fp); void startLoading(size_t size);
void startLoadingFile(FILE *fp);
void loadingProgress(off_t pos); void loadingProgress(off_t pos);
void stopLoading(void); void stopLoading(void);
......
...@@ -823,9 +823,13 @@ void replicationAbortSyncTransfer(void) { ...@@ -823,9 +823,13 @@ void replicationAbortSyncTransfer(void) {
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE); aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
close(server.repl_transfer_s); close(server.repl_transfer_s);
close(server.repl_transfer_fd); if (!server.repl_diskless_sync) {
unlink(server.repl_transfer_tmpfile); close(server.repl_transfer_fd);
zfree(server.repl_transfer_tmpfile); unlink(server.repl_transfer_tmpfile);
zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1;
}
server.repl_state = REDIS_REPL_CONNECT; server.repl_state = REDIS_REPL_CONNECT;
} }
...@@ -877,6 +881,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -877,6 +881,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
char buf[4096]; char buf[4096];
ssize_t nread, readlen; ssize_t nread, readlen;
off_t left; off_t left;
sds initialCommandStream = NULL;
REDIS_NOTUSED(el); REDIS_NOTUSED(el);
REDIS_NOTUSED(privdata); REDIS_NOTUSED(privdata);
REDIS_NOTUSED(mask); REDIS_NOTUSED(mask);
...@@ -942,118 +947,165 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -942,118 +947,165 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
return; return;
} }
/* Read bulk data */ if (!server.repl_diskless_sync) {
if (usemark) { /* read the data from the socket, store it to a file and search for the EOF */
readlen = sizeof(buf); if (usemark) {
} else { readlen = sizeof(buf);
left = server.repl_transfer_size - server.repl_transfer_read; } else {
readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf); left = server.repl_transfer_size - server.repl_transfer_read;
} readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
}
nread = read(fd,buf,readlen); nread = read(fd,buf,readlen);
if (nread <= 0) { if (nread <= 0) {
redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s", redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s",
(nread == -1) ? strerror(errno) : "connection lost"); (nread == -1) ? strerror(errno) : "connection lost");
replicationAbortSyncTransfer(); replicationAbortSyncTransfer();
return; return;
} }
server.stat_net_input_bytes += nread; server.stat_net_input_bytes += nread;
/* When a mark is used, we want to detect EOF asap in order to avoid /* When a mark is used, we want to detect EOF asap in order to avoid
* writing the EOF mark into the file... */ * writing the EOF mark into the file... */
int eof_reached = 0; int eof_reached = 0;
if (usemark) { if (usemark) {
/* Update the last bytes array, and check if it matches our delimiter.*/ /* Update the last bytes array, and check if it matches our delimiter.*/
if (nread >= REDIS_RUN_ID_SIZE) { if (nread >= REDIS_RUN_ID_SIZE) {
memcpy(lastbytes,buf+nread-REDIS_RUN_ID_SIZE,REDIS_RUN_ID_SIZE); memcpy(lastbytes,buf+nread-REDIS_RUN_ID_SIZE,REDIS_RUN_ID_SIZE);
} else { } else {
int rem = REDIS_RUN_ID_SIZE-nread; int rem = REDIS_RUN_ID_SIZE-nread;
memmove(lastbytes,lastbytes+nread,rem); memmove(lastbytes,lastbytes+nread,rem);
memcpy(lastbytes+rem,buf,nread); memcpy(lastbytes+rem,buf,nread);
}
if (memcmp(lastbytes,eofmark,REDIS_RUN_ID_SIZE) == 0) eof_reached = 1;
} }
if (memcmp(lastbytes,eofmark,REDIS_RUN_ID_SIZE) == 0) eof_reached = 1;
}
server.repl_transfer_lastio = server.unixtime; server.repl_transfer_lastio = server.unixtime;
if (write(server.repl_transfer_fd,buf,nread) != nread) { if (write(server.repl_transfer_fd,buf,nread) != nread) {
redisLog(REDIS_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s", strerror(errno)); redisLog(REDIS_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s", strerror(errno));
goto error;
}
server.repl_transfer_read += nread;
/* Delete the last 40 bytes from the file if we reached EOF. */
if (usemark && eof_reached) {
if (ftruncate(server.repl_transfer_fd,
server.repl_transfer_read - REDIS_RUN_ID_SIZE) == -1)
{
redisLog(REDIS_WARNING,"Error truncating the RDB file received from the master for SYNC: %s", strerror(errno));
goto error; goto error;
} }
} server.repl_transfer_read += nread;
/* Sync data on disk from time to time, otherwise at the end of the transfer /* Delete the last 40 bytes from the file if we reached EOF. */
* we may suffer a big delay as the memory buffers are copied into the if (usemark && eof_reached) {
* actual disk. */ if (ftruncate(server.repl_transfer_fd,
if (server.repl_transfer_read >= server.repl_transfer_read - REDIS_RUN_ID_SIZE) == -1)
server.repl_transfer_last_fsync_off + REPL_MAX_WRITTEN_BEFORE_FSYNC) {
{ redisLog(REDIS_WARNING,"Error truncating the RDB file received from the master for SYNC: %s", strerror(errno));
off_t sync_size = server.repl_transfer_read - goto error;
server.repl_transfer_last_fsync_off; }
rdb_fsync_range(server.repl_transfer_fd, }
server.repl_transfer_last_fsync_off, sync_size);
server.repl_transfer_last_fsync_off += sync_size;
}
/* Check if the transfer is now complete */ /* Sync data on disk from time to time, otherwise at the end of the transfer
if (!usemark) { * we may suffer a big delay as the memory buffers are copied into the
if (server.repl_transfer_read == server.repl_transfer_size) * actual disk. */
eof_reached = 1; if (server.repl_transfer_read >=
server.repl_transfer_last_fsync_off + REPL_MAX_WRITTEN_BEFORE_FSYNC)
{
off_t sync_size = server.repl_transfer_read -
server.repl_transfer_last_fsync_off;
rdb_fsync_range(server.repl_transfer_fd,
server.repl_transfer_last_fsync_off, sync_size);
server.repl_transfer_last_fsync_off += sync_size;
}
/* Check if the transfer is now complete */
if (!usemark) {
if (server.repl_transfer_read == server.repl_transfer_size)
eof_reached = 1;
}
if (!eof_reached)
return;
} }
if (eof_reached) { /* We reach here when the slave is using diskless replication,
* or when we are done reading from the socket to the rdb file. */
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Flushing old data");
signalFlushedDb(-1);
emptyDb(replicationEmptyDbCallback);
/* Before loading the DB into memory we need to delete the readable
* handler, otherwise it will get called recursively since
* rdbLoad() will call the event loop to process events from time to
* time for non blocking loading. */
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");
if (server.repl_diskless_sync) {
rio rdb;
rioInitWithFd(&rdb,fd);
/* Put the socket in blocking mode to simplify RDB transfer.
* We'll restore it when the RDB is received. */
anetBlock(NULL,fd);
anetRecvTimeout(NULL,fd,server.repl_timeout*1000);
if (rdbLoadRio(&rdb, server.repl_transfer_size) != REDIS_OK) {
redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
replicationAbortSyncTransfer();
rioFreeFd(&rdb, NULL);
return;
}
if (usemark) {
if (!rioRead(&rdb,buf,REDIS_RUN_ID_SIZE) || memcmp(buf,eofmark,REDIS_RUN_ID_SIZE) != 0) {
redisLog(REDIS_WARNING,"replication stream EOF marker is broken");
replicationAbortSyncTransfer();
rioFreeFd(&rdb, NULL);
return;
}
}
/* get the unread command stream from the rio buffer */
rioFreeFd(&rdb, &initialCommandStream);
/* Restore the socket as non-blocking. */
anetNonBlock(NULL,fd);
anetRecvTimeout(NULL,fd,0);
} else {
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) { if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
redisLog(REDIS_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> SLAVE synchronization: %s", strerror(errno)); redisLog(REDIS_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> SLAVE synchronization: %s", strerror(errno));
replicationAbortSyncTransfer(); replicationAbortSyncTransfer();
return; return;
} }
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Flushing old data");
signalFlushedDb(-1);
emptyDb(replicationEmptyDbCallback);
/* Before loading the DB into memory we need to delete the readable
* handler, otherwise it will get called recursively since
* rdbLoad() will call the event loop to process events from time to
* time for non blocking loading. */
aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");
if (rdbLoad(server.rdb_filename) != REDIS_OK) { if (rdbLoad(server.rdb_filename) != REDIS_OK) {
redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk"); redisLog(REDIS_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
replicationAbortSyncTransfer(); replicationAbortSyncTransfer();
return; return;
} }
/* Final setup of the connected slave <- master link */
zfree(server.repl_transfer_tmpfile); zfree(server.repl_transfer_tmpfile);
close(server.repl_transfer_fd); close(server.repl_transfer_fd);
replicationCreateMasterClient(server.repl_transfer_s); server.repl_transfer_fd = -1;
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success"); server.repl_transfer_tmpfile = NULL;
/* Restart the AOF subsystem now that we finished the sync. This }
* will trigger an AOF rewrite, and when done will start appending /* Final setup of the connected slave <- master link */
* to the new file. */ replicationCreateMasterClient(server.repl_transfer_s);
if (server.aof_state != REDIS_AOF_OFF) { /* feed the initial command stream into the client input buffer */
int retry = 10; if (initialCommandStream) {
int nread = sdslen(initialCommandStream);
stopAppendOnly(); server.current_client = server.master;
while (retry-- && startAppendOnly() == REDIS_ERR) { server.master->querybuf = sdscatsds(server.master->querybuf, initialCommandStream);
redisLog(REDIS_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second."); server.master->reploff += nread;
sleep(1); server.master->lastinteraction = server.unixtime;
} server.stat_net_input_bytes += nread;
if (!retry) { processInputBuffer(server.master);
redisLog(REDIS_WARNING,"FATAL: this slave instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now."); server.current_client = NULL;
exit(1); sdsfree(initialCommandStream);
} }
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
/* Restart the AOF subsystem now that we finished the sync. This
* will trigger an AOF rewrite, and when done will start appending
* to the new file. */
if (server.aof_state != REDIS_AOF_OFF) {
int retry = 10;
stopAppendOnly();
while (retry-- && startAppendOnly() == REDIS_ERR) {
redisLog(REDIS_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second.");
sleep(1);
}
if (!retry) {
redisLog(REDIS_WARNING,"FATAL: this slave instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
exit(1);
} }
} }
return; return;
error: error:
...@@ -1341,16 +1393,20 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1341,16 +1393,20 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
} }
/* Prepare a suitable temp file for bulk transfer */ /* Prepare a suitable temp file for bulk transfer */
while(maxtries--) { if (!server.repl_diskless_sync) {
snprintf(tmpfile,256, while(maxtries--) {
"temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid()); snprintf(tmpfile,256,
dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644); "temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid());
if (dfd != -1) break; dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
sleep(1); if (dfd != -1) break;
} sleep(1);
if (dfd == -1) { }
redisLog(REDIS_WARNING,"Opening the temp file needed for MASTER <-> SLAVE synchronization: %s",strerror(errno)); if (dfd == -1) {
goto error; redisLog(REDIS_WARNING,"Opening the temp file needed for MASTER <-> SLAVE synchronization: %s",strerror(errno));
goto error;
}
server.repl_transfer_tmpfile = zstrdup(tmpfile);
server.repl_transfer_fd = dfd;
} }
/* Setup the non blocking download of the bulk file. */ /* Setup the non blocking download of the bulk file. */
...@@ -1367,13 +1423,17 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1367,13 +1423,17 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
server.repl_transfer_size = -1; server.repl_transfer_size = -1;
server.repl_transfer_read = 0; server.repl_transfer_read = 0;
server.repl_transfer_last_fsync_off = 0; server.repl_transfer_last_fsync_off = 0;
server.repl_transfer_fd = dfd;
server.repl_transfer_lastio = server.unixtime; server.repl_transfer_lastio = server.unixtime;
server.repl_transfer_tmpfile = zstrdup(tmpfile);
return; return;
error: error:
close(fd); close(fd);
if (server.repl_transfer_fd != -1)
close(server.repl_transfer_fd);
if (server.repl_transfer_tmpfile)
zfree(server.repl_transfer_tmpfile);
server.repl_transfer_tmpfile = NULL;
server.repl_transfer_fd = -1;
server.repl_transfer_s = -1; server.repl_transfer_s = -1;
server.repl_state = REDIS_REPL_CONNECT; server.repl_state = REDIS_REPL_CONNECT;
return; return;
......
...@@ -157,13 +157,106 @@ void rioInitWithFile(rio *r, FILE *fp) { ...@@ -157,13 +157,106 @@ void rioInitWithFile(rio *r, FILE *fp) {
r->io.file.autosync = 0; r->io.file.autosync = 0;
} }
/* ------------------- File descriptor implementation ------------------- */
static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
REDIS_NOTUSED(r);
REDIS_NOTUSED(buf);
REDIS_NOTUSED(len);
return 0; /* Error, this target does not yet support writing. */
}
/* Returns 1 or 0 for success/failure. */
static size_t rioFdRead(rio *r, void *buf, size_t len) {
size_t avail = sdslen(r->io.fd.buf)-r->io.fd.pos;
/* if the buffer is too small for the entire request: realloc */
if (sdslen(r->io.fd.buf) + sdsavail(r->io.fd.buf) < len)
r->io.fd.buf = sdsMakeRoomFor(r->io.fd.buf, len - sdslen(r->io.fd.buf));
/* if the remaining unused buffer is not large enough: memmove so that we can read the rest */
if (len > avail && sdsavail(r->io.fd.buf) < len - avail) {
sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
r->io.fd.pos = 0;
}
/* 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) {
size_t toread = len - (sdslen(r->io.fd.buf) - r->io.fd.pos);
/* read either what's missing, or REDIS_IOBUF_LEN, the bigger of the two */
if (toread < REDIS_IOBUF_LEN)
toread = REDIS_IOBUF_LEN;
if (toread > sdsavail(r->io.fd.buf))
toread = sdsavail(r->io.fd.buf);
int retval = read(r->io.fd.fd, (char*)r->io.fd.buf + sdslen(r->io.fd.buf), toread);
if (retval == -1) {
if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
return 0;
}
sdsIncrLen(r->io.fd.buf, retval);
}
memcpy(buf, (char*)r->io.fd.buf + r->io.fd.pos, len);
r->io.fd.pos += len;
return len;
}
/* Returns read/write position in file. */
static off_t rioFdTell(rio *r) {
off_t pos = lseek(r->io.fd.fd, 0, SEEK_CUR);
return pos - sdslen(r->io.fd.buf) + r->io.fd.pos;
}
/* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */
static int rioFdFlush(rio *r) {
/* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */
return rioFdWrite(r,NULL,0);
}
static const rio rioFdIO = {
rioFdRead,
rioFdWrite,
rioFdTell,
rioFdFlush,
NULL, /* update_checksum */
0, /* current checksum */
0, /* bytes read or written */
0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */
};
void rioInitWithFd(rio *r, int fd) {
*r = rioFdIO;
r->io.fd.fd = fd;
r->io.fd.pos = 0;
r->io.fd.buf = sdsnewlen(NULL, REDIS_IOBUF_LEN);
sdsclear(r->io.fd.buf);
}
/* release the rio stream.
* optionally returns the unread buffered data. */
void rioFreeFd(rio *r, sds* out_remainingBufferedData) {
if(out_remainingBufferedData && (size_t)r->io.fd.pos < sdslen(r->io.fd.buf)) {
if (r->io.fd.pos > 0)
sdsrange(r->io.fd.buf, r->io.fd.pos, -1);
*out_remainingBufferedData = r->io.fd.buf;
} else {
sdsfree(r->io.fd.buf);
if (out_remainingBufferedData)
*out_remainingBufferedData = NULL;
}
r->io.fd.buf = NULL;
}
/* ------------------- File descriptors set implementation ------------------- */ /* ------------------- File descriptors set implementation ------------------- */
/* 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 * The function returns success as long as we are able to correctly write
* to at least one file descriptor. * to at least one file descriptor.
* *
* When buf is NULL adn 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 rioFdsetFlush(). */
static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) { static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
...@@ -176,7 +269,7 @@ static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) { ...@@ -176,7 +269,7 @@ static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
* a given size, we actually write to the sockets. */ * a given size, we actually write to the sockets. */
if (len) { if (len) {
r->io.fdset.buf = sdscatlen(r->io.fdset.buf,buf,len); r->io.fdset.buf = sdscatlen(r->io.fdset.buf,buf,len);
len = 0; /* Prevent entering the while belove if we don't flush. */ len = 0; /* Prevent entering the while below if we don't flush. */
if (sdslen(r->io.fdset.buf) > REDIS_IOBUF_LEN) doflush = 1; if (sdslen(r->io.fdset.buf) > REDIS_IOBUF_LEN) doflush = 1;
} }
...@@ -276,6 +369,7 @@ void rioInitWithFdset(rio *r, int *fds, int numfds) { ...@@ -276,6 +369,7 @@ void rioInitWithFdset(rio *r, int *fds, int numfds) {
r->io.fdset.buf = sdsempty(); r->io.fdset.buf = sdsempty();
} }
/* release the rio stream. */
void rioFreeFdset(rio *r) { void rioFreeFdset(rio *r) {
zfree(r->io.fdset.fds); zfree(r->io.fdset.fds);
zfree(r->io.fdset.state); zfree(r->io.fdset.state);
......
...@@ -73,6 +73,12 @@ struct _rio { ...@@ -73,6 +73,12 @@ 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 */
struct {
int fd; /* File descriptor. */
off_t pos;
sds buf;
} fd;
/* Multiple FDs target (used to write to N sockets). */ /* Multiple FDs target (used to write to N sockets). */
struct { struct {
int *fds; /* File descriptors. */ int *fds; /* File descriptors. */
...@@ -126,8 +132,12 @@ static inline int rioFlush(rio *r) { ...@@ -126,8 +132,12 @@ static inline int rioFlush(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);
void rioInitWithFdset(rio *r, int *fds, int numfds); void rioInitWithFdset(rio *r, int *fds, int numfds);
void rioFreeFdset(rio *r);
void rioFreeFd(rio *r, sds* out_remainingBufferedData);
size_t rioWriteBulkCount(rio *r, char prefix, int count); size_t rioWriteBulkCount(rio *r, char prefix, int count);
size_t rioWriteBulkString(rio *r, const char *buf, size_t len); size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
size_t rioWriteBulkLongLong(rio *r, long long l); size_t rioWriteBulkLongLong(rio *r, long long l);
......
proc start_bg_complex_data {host port db ops} {
set tclsh [info nameofexecutable]
exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
}
proc stop_bg_complex_data {handle} {
catch {exec /bin/kill -9 $handle}
}
start_server {tags {"repl"}} { start_server {tags {"repl"}} {
start_server {} { start_server {} {
......
proc start_bg_complex_data {host port db ops} {
set tclsh [info nameofexecutable]
exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
}
proc stop_bg_complex_data {handle} {
catch {exec /bin/kill -9 $handle}
}
# Creates a master-slave pair and breaks the link continuously to force # Creates a master-slave pair and breaks the link continuously to force
# partial resyncs attempts, all this while flooding the master with # partial resyncs attempts, all this while flooding the master with
# write queries. # write queries.
......
...@@ -94,85 +94,91 @@ start_server {tags {"repl"}} { ...@@ -94,85 +94,91 @@ start_server {tags {"repl"}} {
} }
} }
foreach dl {no yes} { foreach mdl {no yes} {
start_server {tags {"repl"}} { foreach sdl {no yes} {
set master [srv 0 client] start_server {tags {"repl"}} {
$master config set repl-diskless-sync $dl set master [srv 0 client]
set master_host [srv 0 host] $master config set repl-diskless-sync $mdl
set master_port [srv 0 port] $master config set repl-diskless-sync-delay 1
set slaves {} set master_host [srv 0 host]
set load_handle0 [start_write_load $master_host $master_port 3] set master_port [srv 0 port]
set load_handle1 [start_write_load $master_host $master_port 5] set slaves {}
set load_handle2 [start_write_load $master_host $master_port 20] set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000000]
set load_handle3 [start_write_load $master_host $master_port 8] set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000000]
set load_handle4 [start_write_load $master_host $master_port 4] set load_handle2 [start_bg_complex_data $master_host $master_port 12 100000000]
start_server {} { set load_handle3 [start_write_load $master_host $master_port 8]
lappend slaves [srv 0 client] set load_handle4 [start_write_load $master_host $master_port 4]
start_server {} { start_server {} {
lappend slaves [srv 0 client] lappend slaves [srv 0 client]
start_server {} { start_server {} {
lappend slaves [srv 0 client] lappend slaves [srv 0 client]
test "Connect multiple slaves at the same time (issue #141), diskless=$dl" { start_server {} {
# Send SALVEOF commands to slaves lappend slaves [srv 0 client]
[lindex $slaves 0] slaveof $master_host $master_port test "Connect multiple slaves at the same time (issue #141), master diskless=$mdl, slave diskless=$sdl" {
[lindex $slaves 1] slaveof $master_host $master_port # Send SALVEOF commands to slaves
[lindex $slaves 2] slaveof $master_host $master_port [lindex $slaves 0] config set repl-diskless-sync $sdl
[lindex $slaves 1] config set repl-diskless-sync $sdl
# Wait for all the three slaves to reach the "online" [lindex $slaves 2] config set repl-diskless-sync $sdl
# state from the POV of the master. [lindex $slaves 0] slaveof $master_host $master_port
set retry 500 [lindex $slaves 1] slaveof $master_host $master_port
while {$retry} { [lindex $slaves 2] slaveof $master_host $master_port
set info [r -3 info]
if {[string match {*slave0:*state=online*slave1:*state=online*slave2:*state=online*} $info]} { # Wait for all the three slaves to reach the "online"
break # state from the POV of the master.
set retry 500
while {$retry} {
set info [r -3 info]
if {[string match {*slave0:*state=online*slave1:*state=online*slave2:*state=online*} $info]} {
break
} else {
incr retry -1
after 100
}
}
if {$retry == 0} {
error "assertion:Slaves not correctly synchronized"
}
# Wait that slaves acknowledge they are online so
# we are sure that DBSIZE and DEBUG DIGEST will not
# fail because of timing issues.
wait_for_condition 500 100 {
[lindex [[lindex $slaves 0] role] 3] eq {connected} &&
[lindex [[lindex $slaves 1] role] 3] eq {connected} &&
[lindex [[lindex $slaves 2] role] 3] eq {connected}
} else { } else {
incr retry -1 fail "Slaves still not connected after some time"
after 100
} }
}
if {$retry == 0} {
error "assertion:Slaves not correctly synchronized"
}
# Wait that slaves acknowledge they are online so # Stop the write load
# we are sure that DBSIZE and DEBUG DIGEST will not stop_bg_complex_data $load_handle0
# fail because of timing issues. stop_bg_complex_data $load_handle1
wait_for_condition 500 100 { stop_bg_complex_data $load_handle2
[lindex [[lindex $slaves 0] role] 3] eq {connected} && stop_write_load $load_handle3
[lindex [[lindex $slaves 1] role] 3] eq {connected} && stop_write_load $load_handle4
[lindex [[lindex $slaves 2] role] 3] eq {connected}
} else { # Make sure that slaves and master have same
fail "Slaves still not connected after some time" # number of keys
} wait_for_condition 500 100 {
[$master dbsize] == [[lindex $slaves 0] dbsize] &&
[$master dbsize] == [[lindex $slaves 1] dbsize] &&
[$master dbsize] == [[lindex $slaves 2] dbsize]
} else {
fail "Different number of keys between masted and slave after too long time."
}
# Stop the write load # Check digests
stop_write_load $load_handle0 set digest [$master debug digest]
stop_write_load $load_handle1 set digest0 [[lindex $slaves 0] debug digest]
stop_write_load $load_handle2 set digest1 [[lindex $slaves 1] debug digest]
stop_write_load $load_handle3 set digest2 [[lindex $slaves 2] debug digest]
stop_write_load $load_handle4 assert {$digest ne 0000000000000000000000000000000000000000}
assert {$digest eq $digest0}
# Make sure that slaves and master have same assert {$digest eq $digest1}
# number of keys assert {$digest eq $digest2}
wait_for_condition 500 100 {
[$master dbsize] == [[lindex $slaves 0] dbsize] &&
[$master dbsize] == [[lindex $slaves 1] dbsize] &&
[$master dbsize] == [[lindex $slaves 2] dbsize]
} else {
fail "Different number of keys between masted and slave after too long time."
} }
}
# Check digests }
set digest [$master debug digest]
set digest0 [[lindex $slaves 0] debug digest]
set digest1 [[lindex $slaves 1] debug digest]
set digest2 [[lindex $slaves 2] debug digest]
assert {$digest ne 0000000000000000000000000000000000000000}
assert {$digest eq $digest0}
assert {$digest eq $digest1}
assert {$digest eq $digest2}
}
}
} }
} }
} }
......
...@@ -371,3 +371,15 @@ proc start_write_load {host port seconds} { ...@@ -371,3 +371,15 @@ proc start_write_load {host port seconds} {
proc stop_write_load {handle} { proc stop_write_load {handle} {
catch {exec /bin/kill -9 $handle} catch {exec /bin/kill -9 $handle}
} }
# Execute a background process writing complex data for the specified number
# of ops to the specified Redis instance.
proc start_bg_complex_data {host port db ops} {
set tclsh [info nameofexecutable]
exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
}
# Stop a process generating write load executed with start_bg_complex_data.
proc stop_bg_complex_data {handle} {
catch {exec /bin/kill -9 $handle}
}
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