Skip to content

Commit b998ebe

Browse files
committed
REPLCONF internal command introduced.
The REPLCONF command is an internal command (not designed to be directly used by normal clients) that allows a slave to set some replication related state in the master before issuing SYNC to start the replication. The initial motivation for this command, and the only reason currently it is used by the implementation, is to let the slave instance communicate its listening port to the slave, so that the master can show all the slaves with their listening ports in the "replication" section of the INFO output. This allows clients to auto discover and query all the slaves attached into a master. Currently only a single option of the REPLCONF command is supported, and it is called "listening-port", so the slave now starts the replication process with something like the following chat: REPLCONF listening-prot 6380 SYNC Note that this works even if the master is an older version of Redis and does not understand REPLCONF, because the slave ignores the REPLCONF error. In the future REPLCONF can be used for partial replication and other replication related features where there is the need to exchange information between master and slave. NOTE: This commit also fixes a bug: the INFO outout already carried information about slaves, but the port was broken, and was obtained with getpeername(2), so it was actually just the ephemeral port used by the slave to connect to the master as a client.
1 parent a328c73 commit b998ebe

File tree

4 files changed

+110
-18
lines changed

4 files changed

+110
-18
lines changed

src/networking.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ redisClient *createClient(int fd) {
4848
c->lastinteraction = time(NULL);
4949
c->authenticated = 0;
5050
c->replstate = REDIS_REPL_NONE;
51+
c->slave_listening_port = 0;
5152
c->reply = listCreate();
5253
c->reply_bytes = 0;
5354
listSetFreeMethod(c->reply,decrRefCount);

src/redis.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ struct redisCommand readonlyCommandTable[] = {
173173
{"exec",execCommand,1,REDIS_CMD_DENYOOM,execBlockClientOnSwappedKeys,0,0,0},
174174
{"discard",discardCommand,1,0,NULL,0,0,0},
175175
{"sync",syncCommand,1,0,NULL,0,0,0},
176+
{"replconf",replconfCommand,-1,0,NULL,0,0,0},
176177
{"flushdb",flushdbCommand,1,0,NULL,0,0,0},
177178
{"flushall",flushallCommand,1,0,NULL,0,0,0},
178179
{"sort",sortCommand,-2,REDIS_CMD_DENYOOM,NULL,1,1,1},
@@ -1401,7 +1402,7 @@ sds genRedisInfoString(void) {
14011402
}
14021403
if (state == NULL) continue;
14031404
info = sdscatprintf(info,"slave%d:%s,%d,%s\r\n",
1404-
slaveid,ip,port,state);
1405+
slaveid,ip,slave->slave_listening_port,state);
14051406
slaveid++;
14061407
}
14071408
}

src/redis.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ typedef struct redisClient {
351351
int repldbfd; /* replication DB file descriptor */
352352
long repldboff; /* replication DB file offset */
353353
off_t repldbsize; /* replication DB file size */
354+
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
354355
multiState mstate; /* MULTI/EXEC state */
355356
blockingState bpop; /* blocking state */
356357
list *io_keys; /* Keys this client is waiting to be loaded from the
@@ -1071,6 +1072,7 @@ void watchCommand(redisClient *c);
10711072
void unwatchCommand(redisClient *c);
10721073
void objectCommand(redisClient *c);
10731074
void clientCommand(redisClient *c);
1075+
void replconfCommand(redisClient *c);
10741076

10751077
#if defined(__GNUC__)
10761078
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));

src/replication.c

Lines changed: 105 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,46 @@ void syncCommand(redisClient *c) {
139139
return;
140140
}
141141

142+
/* REPLCONF <option> <value> <option> <value> ...
143+
* This command is used by a slave in order to configure the replication
144+
* process before starting it with the SYNC command.
145+
*
146+
* Currently the only use of this command is to communicate to the master
147+
* what is the listening port of the Slave redis instance, so that the
148+
* master can accurately list slaves and their listening ports in
149+
* the INFO output.
150+
*
151+
* In the future the same command can be used in order to configure
152+
* the replication to initiate an incremental replication instead of a
153+
* full resync. */
154+
void replconfCommand(redisClient *c) {
155+
int j;
156+
157+
if ((c->argc % 2) == 0) {
158+
/* Number of arguments must be odd to make sure that every
159+
* option has a corresponding value. */
160+
addReply(c,shared.syntaxerr);
161+
return;
162+
}
163+
164+
/* Process every option-value pair. */
165+
for (j = 1; j < c->argc; j+=2) {
166+
if (!strcasecmp(c->argv[j]->ptr,"listening-port")) {
167+
long port;
168+
169+
if ((getLongFromObjectOrReply(c,c->argv[j+1],
170+
&port,NULL) != REDIS_OK))
171+
return;
172+
c->slave_listening_port = port;
173+
} else {
174+
addReplyErrorFormat(c,"Unrecognized REPLCONF option: %s",
175+
(char*)c->argv[j]->ptr);
176+
return;
177+
}
178+
}
179+
addReply(c,shared.ok);
180+
}
181+
142182
void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
143183
redisClient *slave = privdata;
144184
REDIS_NOTUSED(el);
@@ -193,6 +233,52 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
193233
}
194234
}
195235

236+
/* Send a synchronous command to the master. Used to send AUTH and
237+
* REPLCONF commadns before starting the replication with SYNC.
238+
*
239+
* On success NULL is returned.
240+
* On error an sds string describing the error is returned.
241+
*/
242+
char *sendSynchronousCommand(int fd, ...) {
243+
va_list ap;
244+
sds cmd = sdsempty();
245+
char *arg, buf[256];
246+
247+
/* Create the command to send to the master, we use simple inline
248+
* protocol for simplicity as currently we only send simple strings. */
249+
va_start(ap,fd);
250+
while(1) {
251+
arg = va_arg(ap, char*);
252+
if (arg == NULL) break;
253+
254+
if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1);
255+
cmd = sdscat(cmd,arg);
256+
}
257+
cmd = sdscatlen(cmd,"\r\n",2);
258+
259+
/* Transfer command to the server. */
260+
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000) == -1) {
261+
sdsfree(cmd);
262+
return sdscatprintf(sdsempty(),"Writing to master: %s",
263+
strerror(errno));
264+
}
265+
sdsfree(cmd);
266+
267+
/* Read the reply from the server. */
268+
if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout) == -1)
269+
{
270+
return sdscatprintf(sdsempty(),"Reading from master: %s",
271+
strerror(errno));
272+
}
273+
274+
/* Check for errors from the server. */
275+
if (buf[0] != '+') {
276+
return sdscatprintf(sdsempty(),"Error from master: %s", buf);
277+
}
278+
279+
return NULL; /* No errors. */
280+
}
281+
196282
/* This function is called at the end of every backgrond saving.
197283
* The argument bgsaveerr is REDIS_OK if the background saving succeeded
198284
* otherwise REDIS_ERR is passed to the function.
@@ -360,7 +446,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
360446
}
361447

362448
void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
363-
char buf[1024], tmpfile[256];
449+
char tmpfile[256], *err;
364450
int dfd, maxtries = 5;
365451
REDIS_NOTUSED(el);
366452
REDIS_NOTUSED(privdata);
@@ -381,24 +467,26 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
381467

382468
/* AUTH with the master if required. */
383469
if(server.masterauth) {
384-
char authcmd[1024];
385-
size_t authlen;
386-
387-
authlen = snprintf(authcmd,sizeof(authcmd),"AUTH %s\r\n",server.masterauth);
388-
if (syncWrite(fd,authcmd,authlen,server.repl_syncio_timeout) == -1) {
389-
redisLog(REDIS_WARNING,"Unable to AUTH to MASTER: %s",
390-
strerror(errno));
391-
goto error;
392-
}
393-
/* Read the AUTH result. */
394-
if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout) == -1) {
395-
redisLog(REDIS_WARNING,"I/O error reading auth result from MASTER: %s",
396-
strerror(errno));
470+
err = sendSynchronousCommand(fd,"AUTH",server.masterauth,NULL);
471+
if (err) {
472+
redisLog(REDIS_WARNING,"Unable to AUTH to MASTER: %s",err);
473+
sdsfree(err);
397474
goto error;
398475
}
399-
if (buf[0] != '+') {
400-
redisLog(REDIS_WARNING,"Cannot AUTH to MASTER, is the masterauth password correct?");
401-
goto error;
476+
}
477+
478+
/* Set the slave port, so that Master's INFO command can list the
479+
* slave listening port correctly. */
480+
{
481+
sds port = sdsfromlonglong(server.port);
482+
err = sendSynchronousCommand(fd,"REPLCONF","listening-port",port,
483+
NULL);
484+
sdsfree(port);
485+
/* Ignore the error if any, not all the Redis versions support
486+
* REPLCONF listening-port. */
487+
if (err) {
488+
redisLog(REDIS_NOTICE,"(non critical): Master does not understand REPLCONF listening-port: %s", err);
489+
sdsfree(err);
402490
}
403491
}
404492

0 commit comments

Comments
 (0)