Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82358cbf4a |
No files matched your search
+1
-1
@@ -4,7 +4,7 @@ FROM ubuntu:18.04
|
||||
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -qqy \
|
||||
build-essential nasm autotools-dev autoconf libcurl4-openssl-dev libjemalloc-dev tcl tcl-dev uuid-dev \
|
||||
build-essential nasm autotools-dev autoconf libjemalloc-dev tcl tcl-dev uuid-dev \
|
||||
&& apt-get clean
|
||||
|
||||
CMD make
|
||||
@@ -18,7 +18,7 @@ On the same hardware KeyDB can perform twice as many queries per second as Redis
|
||||
Why fork Redis?
|
||||
---------------
|
||||
|
||||
KeyDB has a different philosophy on how the codebase should evolve. We feel that ease of use, high performance, and a "batteries included" approach is the best way to create a good user experience. While we have great respect for the Redis maintainers it is our opinion that the Redis approach focuses too much on simplicity of the code base at the expense of complexity for the user. This results in the need for external components and workarounds to solve common problems - resulting in more complexity overall.
|
||||
KeyDB has a different philosophy on how the codebase should evolve. We feel that ease of use, high performance, and a "batteries included" approach is the best way to create a good user experience. While we have great respect for the Redis maintainers it is our opinion that the Redis approach focusses too much on simplicity of the code base at the expense of complexity for the user. This results in the need for external components and workarounds to solve common problems - resulting in more complexity overall.
|
||||
|
||||
Because of this difference of opinion features which are right for KeyDB may not be appropriate for Redis. A fork allows us to explore this new development path and implement features which may never be a part of Redis. KeyDB keeps in sync with upstream Redis changes, and where applicable we upstream bug fixes and changes. It is our hope that the two projects can continue to grow and learn from each other.
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
="repl-stream-db"
|
||||
="repl-id"
|
||||
="repl-offset"
|
||||
="lua"
|
||||
="redis-ver"
|
||||
="ctime"
|
||||
="used-mem"
|
||||
="aof-preamble"
|
||||
="redis-bits"
|
||||
="mvcc-tstamp"
|
||||
="keydb-subexpire-key"
|
||||
="keydb-subexpire-when"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
|
||||
class IStorage
|
||||
{
|
||||
public:
|
||||
typedef std::function<void(const char *, size_t, const void *, size_t)> callback;
|
||||
|
||||
virtual void insert(const char *key, size_t cchKey, void *data, size_t cb) = 0;
|
||||
virtual void erase(const char *key, size_t cchKey) = 0;
|
||||
virtual void retrieve(const char *key, size_t cchKey, bool fDelete, callback fn) = 0;
|
||||
virtual size_t clear() = 0;
|
||||
virtual void enumerate(callback fn) = 0;
|
||||
};
|
||||
@@ -61,11 +61,6 @@ public:
|
||||
return m_fArmed;
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
m_fArmed = false;
|
||||
}
|
||||
|
||||
~AeLocker()
|
||||
{
|
||||
if (m_fArmed)
|
||||
|
||||
+7
-11
@@ -476,7 +476,7 @@ static int anetV6Only(char *err, int s) {
|
||||
return ANET_OK;
|
||||
}
|
||||
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort, int fFirstListen)
|
||||
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort)
|
||||
{
|
||||
int s = -1, rv;
|
||||
char _port[6]; /* strlen("65535") */
|
||||
@@ -498,12 +498,8 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
|
||||
|
||||
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
|
||||
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
|
||||
if (fReusePort && !fFirstListen && anetSetReusePort(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) {
|
||||
s = ANET_ERR;
|
||||
goto end;
|
||||
}
|
||||
if (fReusePort && fFirstListen && anetSetReusePort(err,s) == ANET_ERR) goto error;
|
||||
if (fReusePort && anetSetReusePort(err,s) == ANET_ERR) goto error;
|
||||
if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
|
||||
goto end;
|
||||
}
|
||||
if (p == NULL) {
|
||||
@@ -519,14 +515,14 @@ end:
|
||||
return s;
|
||||
}
|
||||
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort, fFirstListen);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort);
|
||||
}
|
||||
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen)
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort)
|
||||
{
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort, fFirstListen);
|
||||
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort);
|
||||
}
|
||||
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog)
|
||||
|
||||
+2
-2
@@ -62,8 +62,8 @@ int anetUnixNonBlockConnect(char *err, char *path);
|
||||
int anetRead(int fd, char *buf, int count);
|
||||
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
|
||||
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort);
|
||||
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort);
|
||||
int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
|
||||
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
|
||||
int anetUnixAccept(char *err, int serversock);
|
||||
|
||||
+45
-51
@@ -97,7 +97,6 @@ void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
aofrwblock *block;
|
||||
ssize_t nwritten;
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
serverAssert(el == g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el); // MUST run on main thread
|
||||
|
||||
UNUSED(el);
|
||||
UNUSED(fd);
|
||||
@@ -165,7 +164,10 @@ void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
|
||||
|
||||
/* Install a file event to send data to the rewrite child if there is
|
||||
* not one already. */
|
||||
aeCreateRemoteFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, g_pserver->aof_pipe_write_data_to_child, AE_WRITABLE, aofChildWriteDiffData, NULL, FALSE);
|
||||
if (aeGetFileEvents(serverTL->el,g_pserver->aof_pipe_write_data_to_child) == 0) {
|
||||
aeCreateFileEvent(serverTL->el, g_pserver->aof_pipe_write_data_to_child,
|
||||
AE_WRITABLE, aofChildWriteDiffData, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the buffer (possibly composed of multiple blocks) into the specified
|
||||
@@ -264,7 +266,7 @@ int startAppendOnly(void) {
|
||||
strerror(errno));
|
||||
return C_ERR;
|
||||
}
|
||||
if (g_pserver->FRdbSaveInProgress()) {
|
||||
if (g_pserver->rdb_child_pid != -1) {
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
|
||||
} else {
|
||||
@@ -397,7 +399,7 @@ void flushAppendOnlyFile(int force) {
|
||||
* useful for graphing / monitoring purposes. */
|
||||
if (sync_in_progress) {
|
||||
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
||||
} else if (g_pserver->aof_child_pid != -1 || g_pserver->FRdbSaveInProgress()) {
|
||||
} else if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) {
|
||||
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
||||
} else {
|
||||
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
||||
@@ -494,7 +496,7 @@ try_fsync:
|
||||
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
|
||||
* children doing I/O in the background. */
|
||||
if (g_pserver->aof_no_fsync_on_rewrite &&
|
||||
(g_pserver->aof_child_pid != -1 || g_pserver->FRdbSaveInProgress()))
|
||||
(g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1))
|
||||
return;
|
||||
|
||||
/* Perform the fsync if needed. */
|
||||
@@ -666,7 +668,7 @@ client *createFakeClient(void) {
|
||||
c->flags = 0;
|
||||
c->fPendingAsyncWrite = FALSE;
|
||||
c->btype = BLOCKED_NONE;
|
||||
/* We set the fake client as a replica waiting for the synchronization
|
||||
/* We set the fake client as a slave waiting for the synchronization
|
||||
* so that Redis will not try to send replies to this client. */
|
||||
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
||||
c->reply = listCreate();
|
||||
@@ -1300,46 +1302,52 @@ ssize_t aofReadDiffFromParent(void) {
|
||||
|
||||
int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
size_t processed = 0;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
|
||||
redisDb *db = g_pserver->db+j;
|
||||
if (db->size() == 0) continue;
|
||||
dict *d = db->pdict;
|
||||
if (dictSize(d) == 0) continue;
|
||||
di = dictGetSafeIterator(d);
|
||||
|
||||
/* SELECT the new DB */
|
||||
if (rioWrite(aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkLongLong(aof,j) == 0) goto werr;
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
bool fComplete = db->iterate([&](const char *keystr, robj *o)->bool{
|
||||
robj key;
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds keystr;
|
||||
robj key, *o;
|
||||
|
||||
initStaticStringObject(key,(sds)keystr);
|
||||
keystr = (sds)dictGetKey(de);
|
||||
o = (robj*)dictGetVal(de);
|
||||
initStaticStringObject(key,keystr);
|
||||
|
||||
expireEntry *pexpire = db->getExpire(&key);
|
||||
expireEntry *pexpire = getExpire(db,&key);
|
||||
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
/* Emit a SET command */
|
||||
char cmd[]="*3\r\n$3\r\nSET\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) return false;
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
/* Key and value */
|
||||
if (rioWriteBulkObject(aof,&key) == 0) return false;
|
||||
if (rioWriteBulkObject(aof,o) == 0) return false;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,o) == 0) goto werr;
|
||||
} else if (o->type == OBJ_LIST) {
|
||||
if (rewriteListObject(aof,&key,o) == 0) return false;
|
||||
if (rewriteListObject(aof,&key,o) == 0) goto werr;
|
||||
} else if (o->type == OBJ_SET) {
|
||||
if (rewriteSetObject(aof,&key,o) == 0) return false;
|
||||
if (rewriteSetObject(aof,&key,o) == 0) goto werr;
|
||||
} else if (o->type == OBJ_ZSET) {
|
||||
if (rewriteSortedSetObject(aof,&key,o) == 0) return false;
|
||||
if (rewriteSortedSetObject(aof,&key,o) == 0) goto werr;
|
||||
} else if (o->type == OBJ_HASH) {
|
||||
if (rewriteHashObject(aof,&key,o) == 0) return false;
|
||||
if (rewriteHashObject(aof,&key,o) == 0) goto werr;
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
if (rewriteStreamObject(aof,&key,o) == 0) return false;
|
||||
if (rewriteStreamObject(aof,&key,o) == 0) goto werr;
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
if (rewriteModuleObject(aof,&key,o) == 0) return false;
|
||||
if (rewriteModuleObject(aof,&key,o) == 0) goto werr;
|
||||
} else {
|
||||
serverPanic("Unknown object type");
|
||||
}
|
||||
@@ -1349,17 +1357,17 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
if (subExpire.subkey() == nullptr)
|
||||
{
|
||||
char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) return false;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) return false;
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
|
||||
}
|
||||
else
|
||||
{
|
||||
char cmd[]="*4\r\n$12\r\nEXPIREMEMBER\r\n";
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) return false;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) return false;
|
||||
if (rioWrite(aof,subExpire.subkey(),sdslen(subExpire.subkey())) == 0) return false;
|
||||
if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkObject(aof,&key) == 0) goto werr;
|
||||
if (rioWrite(aof,subExpire.subkey(),sdslen(subExpire.subkey())) == 0) goto werr;
|
||||
}
|
||||
if (rioWriteBulkLongLong(aof,subExpire.when()) == 0) return false; // common
|
||||
if (rioWriteBulkLongLong(aof,subExpire.when()) == 0) goto werr; // common
|
||||
}
|
||||
}
|
||||
/* Read some diff from the parent process from time to time. */
|
||||
@@ -1367,10 +1375,9 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
processed = aof->processed_bytes;
|
||||
aofReadDiffFromParent();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!fComplete)
|
||||
goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
di = NULL;
|
||||
}
|
||||
return C_OK;
|
||||
|
||||
@@ -1411,12 +1418,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
|
||||
if (g_pserver->aof_use_rdb_preamble) {
|
||||
int error;
|
||||
std::vector<redisDbPersistentData*> vecpdb;
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
{
|
||||
vecpdb.push_back(&g_pserver->db[idb]);
|
||||
}
|
||||
if (rdbSaveRio(&aof,vecpdb.data(),&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||
if (rdbSaveRio(&aof,&error,RDB_SAVE_AOF_PREAMBLE,NULL) == C_ERR) {
|
||||
errno = error;
|
||||
goto werr;
|
||||
}
|
||||
@@ -1517,7 +1519,7 @@ void aofChildPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
}
|
||||
/* Remove the handler since this can be called only one time during a
|
||||
* rewrite. */
|
||||
aeDeleteFileEvent(el,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
|
||||
aeDeleteFileEventAsync(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
|
||||
}
|
||||
|
||||
/* Create the pipes used for parent - child process IPC during rewrite.
|
||||
@@ -1555,20 +1557,12 @@ error:
|
||||
}
|
||||
|
||||
void aofClosePipes(void) {
|
||||
int fdAofAckPipe = g_pserver->aof_pipe_read_ack_from_child;
|
||||
aePostFunction(g_pserver->el_alf_pip_read_ack_from_child, [fdAofAckPipe]{
|
||||
aeDeleteFileEventAsync(serverTL->el,fdAofAckPipe,AE_READABLE);
|
||||
close (fdAofAckPipe);
|
||||
});
|
||||
|
||||
int fdAofWritePipe = g_pserver->aof_pipe_write_data_to_child;
|
||||
aePostFunction(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, [fdAofWritePipe]{
|
||||
aeDeleteFileEventAsync(serverTL->el,fdAofWritePipe,AE_WRITABLE);
|
||||
close(fdAofWritePipe);
|
||||
});
|
||||
|
||||
aeDeleteFileEventAsync(g_pserver->el_alf_pip_read_ack_from_child,g_pserver->aof_pipe_read_ack_from_child,AE_READABLE);
|
||||
aeDeleteFileEventAsync(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el,g_pserver->aof_pipe_write_data_to_child,AE_WRITABLE);
|
||||
close(g_pserver->aof_pipe_write_data_to_child);
|
||||
close(g_pserver->aof_pipe_read_data_from_parent);
|
||||
close(g_pserver->aof_pipe_write_ack_to_parent);
|
||||
close(g_pserver->aof_pipe_read_ack_from_child);
|
||||
close(g_pserver->aof_pipe_write_ack_to_child);
|
||||
close(g_pserver->aof_pipe_read_ack_from_parent);
|
||||
}
|
||||
@@ -1593,7 +1587,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
pid_t childpid;
|
||||
long long start;
|
||||
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->FRdbSaveInProgress()) return C_ERR;
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) return C_ERR;
|
||||
if (aofCreatePipes() != C_OK) return C_ERR;
|
||||
openChildInfoPipe();
|
||||
start = ustime();
|
||||
@@ -1652,7 +1646,7 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
void bgrewriteaofCommand(client *c) {
|
||||
if (g_pserver->aof_child_pid != -1) {
|
||||
addReplyError(c,"Background append only file rewriting already in progress");
|
||||
} else if (g_pserver->FRdbSaveInProgress()) {
|
||||
} else if (g_pserver->rdb_child_pid != -1) {
|
||||
g_pserver->aof_rewrite_scheduled = 1;
|
||||
addReplyStatus(c,"Background append only file rewriting scheduled");
|
||||
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
|
||||
|
||||
+272
-183
@@ -396,8 +396,7 @@ void printBits(unsigned char *p, unsigned long count) {
|
||||
#define BITOP_OR 1
|
||||
#define BITOP_XOR 2
|
||||
#define BITOP_NOT 3
|
||||
#define BITOP_LSHIFT 4
|
||||
#define BITOP_RSHIFT 5
|
||||
#define BITOP_INVALID 4
|
||||
|
||||
#define BITFIELDOP_GET 0
|
||||
#define BITFIELDOP_SET 1
|
||||
@@ -589,34 +588,149 @@ void getbitCommand(client *c) {
|
||||
addReply(c, bitval ? shared.cone : shared.czero);
|
||||
}
|
||||
|
||||
/* BITOP op_name target_key src_key1 src_key2 src_key3 ... src_keyN */
|
||||
void bitopCommand(client *c) {
|
||||
unsigned char *bitopCore(unsigned long op, unsigned long minlen, unsigned long maxlen, unsigned long numkeys, unsigned char **src, unsigned long *len)
|
||||
{
|
||||
unsigned char *res = NULL; /* Resulting string. */
|
||||
unsigned long j;
|
||||
|
||||
/* Compute the bit operation, if at least one string is not empty. */
|
||||
if (maxlen) {
|
||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||
unsigned char output, byte;
|
||||
unsigned long i;
|
||||
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. On ARM we skip the fast path since it will
|
||||
* result in GCC compiling the code using multiple-words load/store
|
||||
* operations that are not supported even in ARM >= v6. */
|
||||
j = 0;
|
||||
#ifndef USE_ALIGNED_ACCESS
|
||||
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
|
||||
unsigned long *lp[16];
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
|
||||
/* Note: sds pointer is always aligned to 8 byte boundary. */
|
||||
memcpy(lp,src,sizeof(unsigned long*)*numkeys);
|
||||
memcpy(res,src[0],minlen);
|
||||
|
||||
/* Different branches per different operations for speed (sorry). */
|
||||
if (op == BITOP_AND) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] &= lp[i][0];
|
||||
lres[1] &= lp[i][1];
|
||||
lres[2] &= lp[i][2];
|
||||
lres[3] &= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_OR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] |= lp[i][0];
|
||||
lres[1] |= lp[i][1];
|
||||
lres[2] |= lp[i][2];
|
||||
lres[3] |= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_XOR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] ^= lp[i][0];
|
||||
lres[1] ^= lp[i][1];
|
||||
lres[2] ^= lp[i][2];
|
||||
lres[3] ^= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_NOT) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
lres[0] = ~lres[0];
|
||||
lres[1] = ~lres[1];
|
||||
lres[2] = ~lres[2];
|
||||
lres[3] = ~lres[3];
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* j is set to the next byte to process by the previous loop. */
|
||||
for (; j < maxlen; j++) {
|
||||
output = (len[0] <= j) ? 0 : src[0][j];
|
||||
if (op == BITOP_NOT) output = ~output;
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
byte = (len[i] <= j) ? 0 : src[i][j];
|
||||
switch(op) {
|
||||
case BITOP_AND: output &= byte; break;
|
||||
case BITOP_OR: output |= byte; break;
|
||||
case BITOP_XOR: output ^= byte; break;
|
||||
}
|
||||
}
|
||||
res[j] = output;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
unsigned bitopFromStr(const char *opname)
|
||||
{
|
||||
if ((opname[0] == 'a' || opname[0] == 'A') && !strcasecmp(opname,"and"))
|
||||
return BITOP_AND;
|
||||
else if((opname[0] == 'o' || opname[0] == 'O') && !strcasecmp(opname,"or"))
|
||||
return BITOP_OR;
|
||||
else if((opname[0] == 'x' || opname[0] == 'X') && !strcasecmp(opname,"xor"))
|
||||
return BITOP_XOR;
|
||||
else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
|
||||
return BITOP_NOT;
|
||||
return BITOP_INVALID;
|
||||
}
|
||||
|
||||
// Format (long):(long)
|
||||
std::pair<long long, long long> parseRange(robj *o)
|
||||
{
|
||||
const char *sz = szFromObj(o);
|
||||
|
||||
const char *pchCheck = sz;
|
||||
while (*pchCheck != '\0' && *pchCheck != ':')
|
||||
++pchCheck;
|
||||
|
||||
if (*pchCheck != ':')
|
||||
throw shared.syntaxerr;
|
||||
|
||||
long long min,max;
|
||||
if (!string2ll(sz, pchCheck - sz, &min))
|
||||
throw shared.syntaxerr;
|
||||
|
||||
if (!string2ll(pchCheck+1, strlen(pchCheck+1), &max))
|
||||
throw shared.syntaxerr;
|
||||
|
||||
return std::make_pair(min, max);
|
||||
}
|
||||
|
||||
void bitopRangeCommand(client *c)
|
||||
{
|
||||
char *opname = szFromObj(c->argv[1]);
|
||||
robj *targetkey = c->argv[2];
|
||||
robj_roptr o;
|
||||
int op;
|
||||
unsigned long j, numkeys;
|
||||
robj_roptr *objects; /* Array of source objects. */
|
||||
unsigned char **src; /* Array of source strings pointers. */
|
||||
unsigned long *len, maxlen = 0; /* Array of length of src strings,
|
||||
and max len. */
|
||||
unsigned long minlen = 0; /* Min len among the input keys. */
|
||||
unsigned char *res = NULL; /* Resulting string. */
|
||||
unsigned long minlen = 0, maxlen = 0;
|
||||
|
||||
/* Parse the operation name. */
|
||||
if ((opname[0] == 'a' || opname[0] == 'A') && !strcasecmp(opname,"and"))
|
||||
op = BITOP_AND;
|
||||
else if((opname[0] == 'o' || opname[0] == 'O') && !strcasecmp(opname,"or"))
|
||||
op = BITOP_OR;
|
||||
else if((opname[0] == 'x' || opname[0] == 'X') && !strcasecmp(opname,"xor"))
|
||||
op = BITOP_XOR;
|
||||
else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not"))
|
||||
op = BITOP_NOT;
|
||||
else if (!strcasecmp(opname, "lshift"))
|
||||
op = BITOP_LSHIFT;
|
||||
else if (!strcasecmp(opname, "rshift"))
|
||||
op = BITOP_RSHIFT;
|
||||
else {
|
||||
unsigned long op = bitopFromStr(opname);
|
||||
if (op == BITOP_INVALID) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
@@ -627,29 +741,141 @@ void bitopCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool fShiftOp = (op == BITOP_LSHIFT) || (op == BITOP_RSHIFT);
|
||||
long long shift = 0;
|
||||
|
||||
/* Sanity check: SHIFTS only accept a single arg and an integer */
|
||||
if (fShiftOp) {
|
||||
if (c->argc != 5) {
|
||||
addReplyError(c,"BITOP SHIFT must be called with a single source key and an integer shift.");
|
||||
return;
|
||||
}
|
||||
if (getLongLongFromObject(c->argv[4], &shift) != C_OK) {
|
||||
addReplyError(c, "BITOP SHIFT's last parameter must be an integer");
|
||||
return;
|
||||
}
|
||||
if (op == BITOP_RSHIFT)
|
||||
shift = -shift;
|
||||
if ((c->argc % 2) == 0)
|
||||
{ // did someone forget a range?
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lookup keys, and store pointers to the string objects into an array. */
|
||||
numkeys = c->argc - (fShiftOp ? 4 : 3);
|
||||
unsigned numkeys = (c->argc - 3) / 2;
|
||||
std::vector<unsigned char*> vecsrc(numkeys);
|
||||
std::vector<unsigned long> veclen(numkeys);
|
||||
std::vector<robj_rorefptr> vecobjects(numkeys);
|
||||
std::vector<std::unique_ptr<const char, void(*)(const char*)>> vecspsz; // temp storage so we free allocated strings
|
||||
|
||||
for (unsigned long j = 0; j < numkeys; j++) {
|
||||
size_t iarg = (j*2)+3;
|
||||
robj_roptr o = lookupKeyRead(c->db,c->argv[iarg]);
|
||||
/* Handle non-existing keys as empty strings. */
|
||||
if (o == nullptr) {
|
||||
vecobjects[j] = nullptr;
|
||||
vecsrc[j] = NULL;
|
||||
veclen[j] = 0;
|
||||
minlen = 0;
|
||||
continue;
|
||||
}
|
||||
/* Return an error if one of the keys is not a string. */
|
||||
if (checkType(c,o,OBJ_STRING))
|
||||
return;
|
||||
|
||||
vecobjects[j] = getDecodedObject(o);
|
||||
|
||||
auto range = parseRange(c->argv[iarg+1]);
|
||||
if (range.first < 0 || (range.second < range.first && range.second != -1))
|
||||
throw shared.syntaxerr;
|
||||
|
||||
vecsrc[j] = (unsigned char*)ptrFromObj(vecobjects[j]);
|
||||
veclen[j] = sdslen(szFromObj(vecobjects[j]));
|
||||
|
||||
// Handle the offset
|
||||
if ((size_t)range.first >= (veclen[j]*CHAR_BIT))
|
||||
{ // the start position is longer than the string
|
||||
vecsrc[j] = nullptr;
|
||||
veclen[j] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
vecsrc[j] += range.first/CHAR_BIT;
|
||||
veclen[j] -= range.first/CHAR_BIT;
|
||||
int bitshift = range.first % CHAR_BIT;
|
||||
if (bitshift)
|
||||
{
|
||||
sds newstr = sdsnewlen(nullptr, veclen[j]);
|
||||
for (size_t ich = 0; ich < veclen[j]; ++ich)
|
||||
newstr[ich] = static_cast<unsigned char>(vecsrc[j][ich]) >> bitshift;
|
||||
vecsrc[j] = (unsigned char*)newstr;
|
||||
auto spT = std::unique_ptr<const char, void(*)(const char*)>(newstr, sdsfree);
|
||||
vecspsz.emplace_back(std::move(spT));
|
||||
}
|
||||
}
|
||||
|
||||
// Now the max char
|
||||
if (range.second != -1)
|
||||
{
|
||||
if (range.second < 0)
|
||||
throw shared.syntaxerr;
|
||||
range.second -= range.first; // make relative
|
||||
int mod = range.second % CHAR_BIT;
|
||||
if ((size_t)range.second < (veclen[j]*CHAR_BIT))
|
||||
{
|
||||
veclen[j] = range.second/CHAR_BIT;
|
||||
if (mod)
|
||||
veclen[j]++;
|
||||
}
|
||||
if (mod && veclen[j] > 0)
|
||||
{
|
||||
sds newstr = sdsnewlen(nullptr, veclen[j]);
|
||||
memcpy(newstr, vecsrc[j], veclen[j]);
|
||||
newstr[sdslen(newstr)-1] &= ((1U << mod)-1);
|
||||
vecsrc[j] = (unsigned char*)newstr;
|
||||
auto spT = std::unique_ptr<const char, void(*)(const char*)>(newstr, sdsfree);
|
||||
vecspsz.emplace_back(std::move(spT));
|
||||
}
|
||||
}
|
||||
|
||||
if (veclen[j] > maxlen) maxlen = veclen[j];
|
||||
if (j == 0 || veclen[j] < minlen) minlen = veclen[j];
|
||||
}
|
||||
|
||||
unsigned char *res = bitopCore(op, minlen, maxlen, numkeys, vecsrc.data(), veclen.data());
|
||||
|
||||
/* Store the computed value into the target key */
|
||||
if (maxlen) {
|
||||
robj *o = createObject(OBJ_STRING,res);
|
||||
setKey(c->db,targetkey,o);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
|
||||
decrRefCount(o);
|
||||
} else if (dbDelete(c->db,targetkey)) {
|
||||
signalModifiedKey(c->db,targetkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
|
||||
}
|
||||
g_pserver->dirty++;
|
||||
addReplyLongLong(c,maxlen); /* Return the output string length in bytes. */
|
||||
}
|
||||
|
||||
/* BITOP op_name target_key src_key1 src_key2 src_key3 ... src_keyN */
|
||||
void bitopCommand(client *c) {
|
||||
char *opname = szFromObj(c->argv[1]);
|
||||
robj *targetkey = c->argv[2];
|
||||
robj_roptr o;
|
||||
unsigned long op, numkeys;
|
||||
robj_roptr *objects; /* Array of source objects. */
|
||||
unsigned char **src; /* Array of source strings pointers. */
|
||||
unsigned long *len, maxlen = 0; /* Array of length of src strings,
|
||||
and max len. */
|
||||
unsigned long minlen = 0; /* Min len among the input keys. */
|
||||
|
||||
/* Parse the operation name. */
|
||||
op = bitopFromStr(opname);
|
||||
if (op == BITOP_INVALID) {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Sanity check: NOT accepts only a single key argument. */
|
||||
if (op == BITOP_NOT && c->argc != 4) {
|
||||
addReplyError(c,"BITOP NOT must be called with a single source key.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Lookup keys, and store pointers to the string objects into an array. */
|
||||
numkeys = c->argc - 3;
|
||||
src = (unsigned char**)zmalloc(sizeof(unsigned char*) * numkeys, MALLOC_LOCAL);
|
||||
len = (unsigned long*)zmalloc(sizeof(long) * numkeys, MALLOC_LOCAL);
|
||||
objects = (robj_roptr*)zmalloc(sizeof(robj_roptr) * numkeys, MALLOC_LOCAL);
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
|
||||
for (unsigned long j = 0; j < numkeys; j++) {
|
||||
o = lookupKeyRead(c->db,c->argv[j+3]);
|
||||
/* Handle non-existing keys as empty strings. */
|
||||
if (o == nullptr) {
|
||||
@@ -678,146 +904,9 @@ void bitopCommand(client *c) {
|
||||
if (j == 0 || len[j] < minlen) minlen = len[j];
|
||||
}
|
||||
|
||||
if (fShiftOp)
|
||||
{
|
||||
long newlen = (long)maxlen + shift/CHAR_BIT;
|
||||
if (shift > 0 && (shift % CHAR_BIT) != 0)
|
||||
newlen++;
|
||||
|
||||
if (newlen < 0)
|
||||
newlen = 0;
|
||||
|
||||
if (newlen)
|
||||
{
|
||||
res = (unsigned char*) sdsnewlen(NULL,newlen);
|
||||
if (shift >= 0)
|
||||
{ // left shift
|
||||
long byteoffset = shift/CHAR_BIT;
|
||||
memset(res, 0, byteoffset);
|
||||
long srcLen = newlen - byteoffset - ((shift % CHAR_BIT) ? 1 : 0);
|
||||
|
||||
// now the bitshift+copy
|
||||
unsigned bitshift = shift % CHAR_BIT;
|
||||
unsigned char carry = 0;
|
||||
for (long iSrc = 0; iSrc < srcLen; ++iSrc)
|
||||
{
|
||||
res[byteoffset+iSrc] = (src[0][iSrc] << bitshift) | carry;
|
||||
carry = src[0][iSrc] >> (CHAR_BIT - bitshift);
|
||||
}
|
||||
if (bitshift)
|
||||
res[newlen-1] = carry;
|
||||
}
|
||||
else
|
||||
{ // right shift
|
||||
long byteoffset = -shift/CHAR_BIT;
|
||||
unsigned bitshift = -shift % CHAR_BIT;
|
||||
if (bitshift)
|
||||
++byteoffset;
|
||||
res[0] = (src[0][byteoffset] << (CHAR_BIT-bitshift));
|
||||
if (byteoffset > 0)
|
||||
res[0] |= (src[0][byteoffset-1] >> bitshift);
|
||||
for (long idx = 1; idx < newlen; ++idx)
|
||||
{
|
||||
res[idx] = (src[0][byteoffset+idx] << (CHAR_BIT-bitshift)) | (src[0][byteoffset+idx-1] >> bitshift);
|
||||
}
|
||||
}
|
||||
}
|
||||
maxlen = newlen; // this is to ensure we DEL below if newlen was 0
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Compute the bit operation, if at least one string is not empty. */
|
||||
if (maxlen) {
|
||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||
unsigned char output, byte;
|
||||
unsigned long i;
|
||||
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. On ARM we skip the fast path since it will
|
||||
* result in GCC compiling the code using multiple-words load/store
|
||||
* operations that are not supported even in ARM >= v6. */
|
||||
j = 0;
|
||||
#ifndef USE_ALIGNED_ACCESS
|
||||
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
|
||||
unsigned long *lp[16];
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
|
||||
/* Note: sds pointer is always aligned to 8 byte boundary. */
|
||||
memcpy(lp,src,sizeof(unsigned long*)*numkeys);
|
||||
memcpy(res,src[0],minlen);
|
||||
|
||||
/* Different branches per different operations for speed (sorry). */
|
||||
if (op == BITOP_AND) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] &= lp[i][0];
|
||||
lres[1] &= lp[i][1];
|
||||
lres[2] &= lp[i][2];
|
||||
lres[3] &= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_OR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] |= lp[i][0];
|
||||
lres[1] |= lp[i][1];
|
||||
lres[2] |= lp[i][2];
|
||||
lres[3] |= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_XOR) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
lres[0] ^= lp[i][0];
|
||||
lres[1] ^= lp[i][1];
|
||||
lres[2] ^= lp[i][2];
|
||||
lres[3] ^= lp[i][3];
|
||||
lp[i]+=4;
|
||||
}
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
} else if (op == BITOP_NOT) {
|
||||
while(minlen >= sizeof(unsigned long)*4) {
|
||||
lres[0] = ~lres[0];
|
||||
lres[1] = ~lres[1];
|
||||
lres[2] = ~lres[2];
|
||||
lres[3] = ~lres[3];
|
||||
lres+=4;
|
||||
j += sizeof(unsigned long)*4;
|
||||
minlen -= sizeof(unsigned long)*4;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* j is set to the next byte to process by the previous loop. */
|
||||
for (; j < maxlen; j++) {
|
||||
output = (len[0] <= j) ? 0 : src[0][j];
|
||||
if (op == BITOP_NOT) output = ~output;
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
byte = (len[i] <= j) ? 0 : src[i][j];
|
||||
switch(op) {
|
||||
case BITOP_AND: output &= byte; break;
|
||||
case BITOP_OR: output |= byte; break;
|
||||
case BITOP_XOR: output ^= byte; break;
|
||||
}
|
||||
}
|
||||
res[j] = output;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (j = 0; j < numkeys; j++) {
|
||||
unsigned char *res = bitopCore(op, minlen, maxlen, numkeys, src, len);
|
||||
|
||||
for (unsigned long j = 0; j < numkeys; j++) {
|
||||
if (objects[j])
|
||||
decrRefCount(objects[j]);
|
||||
}
|
||||
|
||||
+2
-2
@@ -221,8 +221,8 @@ void replyToBlockedClientTimedOut(client *c) {
|
||||
|
||||
/* Mass-unblock clients because something changed in the instance that makes
|
||||
* blocking no longer safe. For example clients blocked in list operations
|
||||
* in an instance which turns from master to replica is unsafe, so this function
|
||||
* is called when a master turns into a replica.
|
||||
* in an instance which turns from master to slave is unsafe, so this function
|
||||
* is called when a master turns into a slave.
|
||||
*
|
||||
* The semantics is to send an -UNBLOCKED error to the client, disconnecting
|
||||
* it at the same time. */
|
||||
|
||||
+6
-25
@@ -499,7 +499,7 @@ void clusterInit(void) {
|
||||
}
|
||||
|
||||
if (listenToPort(g_pserver->port+CLUSTER_PORT_INCR,
|
||||
g_pserver->cfd,&g_pserver->cfd_count, 0 /*fReusePort*/, 0 /*fFirstListen*/) == C_ERR)
|
||||
g_pserver->cfd,&g_pserver->cfd_count, 0 /*fReusePort*/) == C_ERR)
|
||||
{
|
||||
exit(1);
|
||||
} else {
|
||||
@@ -3910,7 +3910,7 @@ int verifyClusterConfigWithData(void) {
|
||||
|
||||
/* Make sure we only have keys in DB0. */
|
||||
for (j = 1; j < cserver.dbnum; j++) {
|
||||
if (g_pserver->db[j].size()) return C_ERR;
|
||||
if (dictSize(g_pserver->db[j].pdict)) return C_ERR;
|
||||
}
|
||||
|
||||
/* Check that all the slots we see populated memory have a corresponding
|
||||
@@ -4286,7 +4286,7 @@ NULL
|
||||
clusterReplyMultiBulkSlots(c);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"flushslots") && c->argc == 2) {
|
||||
/* CLUSTER FLUSHSLOTS */
|
||||
if (g_pserver->db[0].size() != 0) {
|
||||
if (dictSize(g_pserver->db[0].pdict) != 0) {
|
||||
addReplyError(c,"DB must be empty to perform CLUSTER FLUSHSLOTS.");
|
||||
return;
|
||||
}
|
||||
@@ -4621,7 +4621,7 @@ NULL
|
||||
* slots nor keys to accept to replicate some other node.
|
||||
* Slaves can switch to another master without issues. */
|
||||
if (nodeIsMaster(myself) &&
|
||||
(myself->numslots != 0 || g_pserver->db[0].size() != 0)) {
|
||||
(myself->numslots != 0 || dictSize(g_pserver->db[0].pdict) != 0)) {
|
||||
addReplyError(c,
|
||||
"To set a master the node must be empty and "
|
||||
"without assigned slots.");
|
||||
@@ -4778,7 +4778,7 @@ NULL
|
||||
|
||||
/* Slaves can be reset while containing data, but not master nodes
|
||||
* that must be empty. */
|
||||
if (nodeIsMaster(myself) && c->db->size() != 0) {
|
||||
if (nodeIsMaster(myself) && dictSize(c->db->pdict) != 0) {
|
||||
addReplyError(c,"CLUSTER RESET can't be called with "
|
||||
"master nodes containing keys");
|
||||
return;
|
||||
@@ -4794,7 +4794,6 @@ NULL
|
||||
/* -----------------------------------------------------------------------------
|
||||
* DUMP, RESTORE and MIGRATE commands
|
||||
* -------------------------------------------------------------------------- */
|
||||
ssize_t rdbSaveAuxFieldStrStr(rio *rdb, const char *key, const char *val);
|
||||
|
||||
/* Generates a DUMP-format representation of the object 'o', adding it to the
|
||||
* io stream pointed by 'rio'. This function can't fail. */
|
||||
@@ -4807,9 +4806,6 @@ void createDumpPayload(rio *payload, robj_roptr o, robj *key) {
|
||||
rioInitWithBuffer(payload,sdsempty());
|
||||
serverAssert(rdbSaveObjectType(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o,key));
|
||||
char szT[32];
|
||||
snprintf(szT, 32, "%" PRIu64, o->mvcc_tstamp);
|
||||
serverAssert(rdbSaveAuxFieldStrStr(payload,"mvcc-tstamp", szT) != -1);
|
||||
|
||||
/* Write the footer, this is how it looks like:
|
||||
* ----------------+---------------------+---------------+
|
||||
@@ -4945,21 +4941,6 @@ void restoreCommand(client *c) {
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
}
|
||||
if (rdbLoadType(&payload) == RDB_OPCODE_AUX)
|
||||
{
|
||||
robj *auxkey, *auxval;
|
||||
if ((auxkey = rdbLoadStringObject(&payload)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(&payload)) == NULL) {
|
||||
decrRefCount(auxkey);
|
||||
goto eoferr;
|
||||
}
|
||||
if (strcasecmp(szFromObj(auxkey), "mvcc-tstamp") == 0) {
|
||||
obj->mvcc_tstamp = strtoull(szFromObj(auxval), nullptr, 10);
|
||||
}
|
||||
decrRefCount(auxkey);
|
||||
decrRefCount(auxval);
|
||||
}
|
||||
eoferr:
|
||||
|
||||
/* Remove the old key if needed. */
|
||||
if (replace) dbDelete(c->db,c->argv[1]);
|
||||
@@ -5213,7 +5194,7 @@ try_again:
|
||||
/* Create RESTORE payload and generate the protocol to call the command. */
|
||||
for (j = 0; j < num_keys; j++) {
|
||||
long long ttl = 0;
|
||||
expireEntry *pexpire = c->db->getExpire(kv[j]);
|
||||
expireEntry *pexpire = getExpire(c->db,kv[j]);
|
||||
long long expireat = -1;
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expireat);
|
||||
|
||||
+2
-10
@@ -30,16 +30,7 @@ public:
|
||||
zfree(m_data);
|
||||
}
|
||||
|
||||
compactvector(const compactvector &src)
|
||||
{
|
||||
m_celem = src.m_celem;
|
||||
m_max = src.m_max;
|
||||
m_data = (T*)zmalloc(sizeof(T) * m_max, MALLOC_LOCAL);
|
||||
for (size_t ielem = 0; ielem < m_celem; ++ielem)
|
||||
{
|
||||
new (m_data+ielem) T(src[ielem]);
|
||||
}
|
||||
}
|
||||
compactvector(compactvector &) noexcept = delete;
|
||||
|
||||
compactvector(compactvector &&src) noexcept
|
||||
{
|
||||
@@ -51,6 +42,7 @@ public:
|
||||
src.m_max = 0;
|
||||
}
|
||||
|
||||
compactvector &operator=(const compactvector&) noexcept = delete;
|
||||
compactvector &operator=(compactvector &&src) noexcept
|
||||
{
|
||||
zfree(m_data);
|
||||
|
||||
+1
-3
@@ -96,7 +96,7 @@ configEnum aof_fsync_enum[] = {
|
||||
/* Output buffer limits presets. */
|
||||
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
|
||||
{0, 0, 0}, /* normal */
|
||||
{1024*1024*256, 1024*1024*64, 60}, /* replica */
|
||||
{1024*1024*256, 1024*1024*64, 60}, /* slave */
|
||||
{1024*1024*32, 1024*1024*8, 60} /* pubsub */
|
||||
};
|
||||
|
||||
@@ -800,8 +800,6 @@ void loadServerConfigFromString(char *config) {
|
||||
serverLog(LL_WARNING, "Warning version is overriden to: %s\n", KEYDB_SET_VERSION);
|
||||
} else if (!strcasecmp(argv[0],"testmode") && argc == 2){
|
||||
g_fTestMode = yesnotoi(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"rdbfuzz-mode")) {
|
||||
// NOP, handled in main
|
||||
} else {
|
||||
err = "Bad directive or wrong number of arguments"; goto loaderr;
|
||||
}
|
||||
|
||||
+150
-469
@@ -30,7 +30,6 @@
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
#include "atomicvar.h"
|
||||
#include "aelocker.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <ctype.h>
|
||||
@@ -41,6 +40,7 @@
|
||||
|
||||
int keyIsExpired(redisDb *db, robj *key);
|
||||
int expireIfNeeded(redisDb *db, robj *key, robj *o);
|
||||
void dbOverwriteCore(redisDb *db, dictEntry *de, robj *key, robj *val, bool fUpdateMvcc, bool fRemoveExpire);
|
||||
|
||||
/* Update LFU when an object is accessed.
|
||||
* Firstly, decrement the counter if the decrement time is reached.
|
||||
@@ -56,7 +56,8 @@ void updateExpire(redisDb *db, sds key, robj *valOld, robj *valNew)
|
||||
serverAssert(valOld->FExpires());
|
||||
serverAssert(!valNew->FExpires());
|
||||
|
||||
serverAssert(db->FKeyExpires((const char*)key));
|
||||
auto itr = db->setexpire->find(key);
|
||||
serverAssert(itr != db->setexpire->end());
|
||||
|
||||
valNew->SetFExpires(true);
|
||||
valOld->SetFExpires(false);
|
||||
@@ -68,13 +69,14 @@ void updateExpire(redisDb *db, sds key, robj *valOld, robj *valNew)
|
||||
* implementations that should instead rely on lookupKeyRead(),
|
||||
* lookupKeyWrite() and lookupKeyReadWithFlags(). */
|
||||
static robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
auto itr = db->find(key);
|
||||
if (itr) {
|
||||
robj *val = itr.val();
|
||||
dictEntry *de = dictFind(db->pdict,ptrFromObj(key));
|
||||
if (de) {
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
|
||||
/* Update the access time for the ageing algorithm.
|
||||
* Don't do it if we have a saving child, as this will trigger
|
||||
* a copy on write madness. */
|
||||
if (!g_pserver->FRdbSaveInProgress() &&
|
||||
if (g_pserver->rdb_child_pid == -1 &&
|
||||
g_pserver->aof_child_pid == -1 &&
|
||||
!(flags & LOOKUP_NOTOUCH))
|
||||
{
|
||||
@@ -87,7 +89,6 @@ static robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
|
||||
if (flags & LOOKUP_UPDATEMVCC) {
|
||||
val->mvcc_tstamp = getMvccTstamp();
|
||||
db->trackkey(key);
|
||||
}
|
||||
return val;
|
||||
} else {
|
||||
@@ -113,7 +114,7 @@ static robj *lookupKey(redisDb *db, robj *key, int flags) {
|
||||
* LOOKUP_NOTOUCH: don't alter the last access time of the key.
|
||||
*
|
||||
* Note: this function also returns NULL if the key is logically expired
|
||||
* but still existing, in case this is a replica, since this API is called only
|
||||
* but still existing, in case this is a slave, since this API is called only
|
||||
* for read operations. Even if the key expiry is master-driven, we can
|
||||
* correctly report a key is expired on slaves even if the master is lagging
|
||||
* expiring our key via DELs in the replication link. */
|
||||
@@ -131,7 +132,7 @@ robj_roptr lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* However if we are in the context of a replica, expireIfNeeded() will
|
||||
/* However if we are in the context of a slave, expireIfNeeded() will
|
||||
* not really try to expire the key, it only returns information
|
||||
* about the "logical" status of the key: key expiring is up to the
|
||||
* master in order to have a consistent view of master's data set.
|
||||
@@ -193,14 +194,13 @@ robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply) {
|
||||
return o;
|
||||
}
|
||||
|
||||
bool dbAddCore(redisDb *db, robj *key, robj *val) {
|
||||
int dbAddCore(redisDb *db, robj *key, robj *val) {
|
||||
serverAssert(!val->FExpires());
|
||||
sds copy = sdsdup(szFromObj(key));
|
||||
bool fInserted = db->insert(copy, val);
|
||||
if (g_pserver->fActiveReplica)
|
||||
val->mvcc_tstamp = key->mvcc_tstamp = getMvccTstamp();
|
||||
int retval = dictAdd(db->pdict, copy, val);
|
||||
val->mvcc_tstamp = key->mvcc_tstamp = getMvccTstamp();
|
||||
|
||||
if (fInserted)
|
||||
if (retval == DICT_OK)
|
||||
{
|
||||
if (val->type == OBJ_LIST ||
|
||||
val->type == OBJ_ZSET)
|
||||
@@ -212,7 +212,7 @@ bool dbAddCore(redisDb *db, robj *key, robj *val) {
|
||||
sdsfree(copy);
|
||||
}
|
||||
|
||||
return fInserted;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Add the key to the DB. It's up to the caller to increment the reference
|
||||
@@ -221,22 +221,23 @@ bool dbAddCore(redisDb *db, robj *key, robj *val) {
|
||||
* The program is aborted if the key already exists. */
|
||||
void dbAdd(redisDb *db, robj *key, robj *val)
|
||||
{
|
||||
bool fInserted = dbAddCore(db, key, val);
|
||||
serverAssertWithInfo(NULL,key,fInserted);
|
||||
int retval = dbAddCore(db, key, val);
|
||||
serverAssertWithInfo(NULL,key,retval == DICT_OK);
|
||||
}
|
||||
|
||||
void redisDb::dbOverwriteCore(redisDb::iter itr, robj *key, robj *val, bool fUpdateMvcc, bool fRemoveExpire)
|
||||
void dbOverwriteCore(redisDb *db, dictEntry *de, robj *key, robj *val, bool fUpdateMvcc, bool fRemoveExpire)
|
||||
{
|
||||
robj *old = itr.val();
|
||||
dictEntry auxentry = *de;
|
||||
robj *old = (robj*)dictGetVal(de);
|
||||
|
||||
if (old->FExpires()) {
|
||||
if (fRemoveExpire) {
|
||||
::removeExpire(this, key);
|
||||
removeExpire(db, key);
|
||||
}
|
||||
else {
|
||||
if (val->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT)
|
||||
val = dupStringObject(val);
|
||||
::updateExpire(this, itr.key(), old, val);
|
||||
updateExpire(db, (sds)dictGetKey(de), old, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,12 +250,14 @@ void redisDb::dbOverwriteCore(redisDb::iter itr, robj *key, robj *val, bool fUpd
|
||||
val->mvcc_tstamp = getMvccTstamp();
|
||||
}
|
||||
|
||||
if (g_pserver->lazyfree_lazy_server_del)
|
||||
freeObjAsync(itr.val());
|
||||
else
|
||||
decrRefCount(itr.val());
|
||||
dictSetVal(db->pdict, de, val);
|
||||
|
||||
updateValue(itr, val);
|
||||
if (g_pserver->lazyfree_lazy_server_del) {
|
||||
freeObjAsync(old);
|
||||
dictSetVal(db->pdict, &auxentry, NULL);
|
||||
}
|
||||
|
||||
dictFreeVal(db->pdict, &auxentry);
|
||||
}
|
||||
|
||||
/* Overwrite an existing key with a new value. Incrementing the reference
|
||||
@@ -263,10 +266,10 @@ void redisDb::dbOverwriteCore(redisDb::iter itr, robj *key, robj *val, bool fUpd
|
||||
*
|
||||
* The program is aborted if the key was not already present. */
|
||||
void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
auto itr = db->find(key);
|
||||
dictEntry *de = dictFind(db->pdict,ptrFromObj(key));
|
||||
|
||||
serverAssertWithInfo(NULL,key,itr != nullptr);
|
||||
db->dbOverwriteCore(itr, key, val, !!g_pserver->fActiveReplica, false);
|
||||
serverAssertWithInfo(NULL,key,de != NULL);
|
||||
dbOverwriteCore(db, de, key, val, !!g_pserver->fActiveReplica, false);
|
||||
}
|
||||
|
||||
/* Insert a key, handling duplicate keys according to fReplace */
|
||||
@@ -274,14 +277,14 @@ int dbMerge(redisDb *db, robj *key, robj *val, int fReplace)
|
||||
{
|
||||
if (fReplace)
|
||||
{
|
||||
auto itr = db->find(key);
|
||||
if (itr == nullptr)
|
||||
return (dbAddCore(db, key, val) == true);
|
||||
dictEntry *de = dictFind(db->pdict, ptrFromObj(key));
|
||||
if (de == nullptr)
|
||||
return (dbAddCore(db, key, val) == DICT_OK);
|
||||
|
||||
robj *old = itr.val();
|
||||
robj *old = (robj*)dictGetVal(de);
|
||||
if (old->mvcc_tstamp <= val->mvcc_tstamp)
|
||||
{
|
||||
db->dbOverwriteCore(itr, key, val, false, true);
|
||||
dbOverwriteCore(db, de, key, val, false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -289,7 +292,7 @@ int dbMerge(redisDb *db, robj *key, robj *val, int fReplace)
|
||||
}
|
||||
else
|
||||
{
|
||||
return (dbAddCore(db, key, val) == true);
|
||||
return (dbAddCore(db, key, val) == DICT_OK);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,18 +305,18 @@ int dbMerge(redisDb *db, robj *key, robj *val, int fReplace)
|
||||
*
|
||||
* All the new keys in the database should be created via this interface. */
|
||||
void setKey(redisDb *db, robj *key, robj *val) {
|
||||
auto itr = db->find(key);
|
||||
if (itr == NULL) {
|
||||
dictEntry *de = dictFind(db->pdict, ptrFromObj(key));
|
||||
if (de == NULL) {
|
||||
dbAdd(db,key,val);
|
||||
} else {
|
||||
db->dbOverwriteCore(itr,key,val,!!g_pserver->fActiveReplica,true);
|
||||
dbOverwriteCore(db,de,key,val,!!g_pserver->fActiveReplica,true);
|
||||
}
|
||||
incrRefCount(val);
|
||||
signalModifiedKey(db,key);
|
||||
}
|
||||
|
||||
int dbExists(redisDb *db, robj *key) {
|
||||
return (db->find(key) != nullptr);
|
||||
return dictFind(db->pdict,ptrFromObj(key)) != NULL;
|
||||
}
|
||||
|
||||
/* Return a random key, in form of a Redis object.
|
||||
@@ -321,25 +324,26 @@ int dbExists(redisDb *db, robj *key) {
|
||||
*
|
||||
* The function makes sure to return keys not already expired. */
|
||||
robj *dbRandomKey(redisDb *db) {
|
||||
dictEntry *de;
|
||||
int maxtries = 100;
|
||||
bool allvolatile = db->expireSize() == db->size();
|
||||
int allvolatile = dictSize(db->pdict) == db->setexpire->size();
|
||||
|
||||
while(1) {
|
||||
sds key;
|
||||
robj *keyobj;
|
||||
|
||||
auto itr = db->random();
|
||||
if (itr == nullptr) return NULL;
|
||||
de = dictGetRandomKey(db->pdict);
|
||||
if (de == NULL) return NULL;
|
||||
|
||||
key = itr.key();
|
||||
key = (sds)dictGetKey(de);
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
if (itr.val()->FExpires())
|
||||
if (((robj*)dictGetVal(de))->FExpires())
|
||||
{
|
||||
if (allvolatile && listLength(g_pserver->masters) && --maxtries == 0) {
|
||||
/* If the DB is composed only of keys with an expire set,
|
||||
* it could happen that all the keys are already logically
|
||||
* expired in the replica, so the function cannot stop because
|
||||
* expired in the slave, so the function cannot stop because
|
||||
* expireIfNeeded() is false, nor it can stop because
|
||||
* dictGetRandomKey() returns NULL (there are keys to return).
|
||||
* To prevent the infinite loop we do some tries, but if there
|
||||
@@ -349,7 +353,7 @@ robj *dbRandomKey(redisDb *db) {
|
||||
}
|
||||
}
|
||||
|
||||
if (itr.val()->FExpires())
|
||||
if (((robj*)dictGetVal(de))->FExpires())
|
||||
{
|
||||
if (expireIfNeeded(db,keyobj)) {
|
||||
decrRefCount(keyobj);
|
||||
@@ -361,16 +365,15 @@ robj *dbRandomKey(redisDb *db) {
|
||||
}
|
||||
}
|
||||
|
||||
bool redisDbPersistentData::syncDelete(robj *key)
|
||||
{
|
||||
/* Delete a key, value, and associated expiration entry if any, from the DB */
|
||||
int dbSyncDelete(redisDb *db, robj *key) {
|
||||
/* Deleting an entry from the expires dict will not free the sds of
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
|
||||
auto itr = find(szFromObj(key));
|
||||
trackkey(szFromObj(key));
|
||||
if (itr != nullptr && itr.val()->FExpires())
|
||||
removeExpire(key, itr);
|
||||
if (dictDelete(m_pdict,ptrFromObj(key)) == DICT_OK) {
|
||||
dictEntry *de = dictFind(db->pdict, szFromObj(key));
|
||||
if (de != nullptr && ((robj*)dictGetVal(de))->FExpires())
|
||||
removeExpireCore(db, key, de);
|
||||
if (dictDelete(db->pdict,ptrFromObj(key)) == DICT_OK) {
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(key);
|
||||
return 1;
|
||||
} else {
|
||||
@@ -378,11 +381,6 @@ bool redisDbPersistentData::syncDelete(robj *key)
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete a key, value, and associated expiration entry if any, from the DB */
|
||||
int dbSyncDelete(redisDb *db, robj *key) {
|
||||
return db->syncDelete(key);
|
||||
}
|
||||
|
||||
/* This is a wrapper whose behavior depends on the Redis lazy free
|
||||
* configuration. Deletes the key synchronously or asynchronously. */
|
||||
int dbDelete(redisDb *db, robj *key) {
|
||||
@@ -428,7 +426,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
|
||||
return o;
|
||||
}
|
||||
|
||||
/* Remove all keys from all the databases in a Redis DB
|
||||
/* Remove all keys from all the databases in a Redis g_pserver->
|
||||
* If callback is given the function is called from time to time to
|
||||
* signal that work is in progress.
|
||||
*
|
||||
@@ -460,7 +458,15 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
|
||||
}
|
||||
|
||||
for (int j = startdb; j <= enddb; j++) {
|
||||
removed += g_pserver->db[j].clear(!!async, callback);
|
||||
removed += dictSize(g_pserver->db[j].pdict);
|
||||
if (async) {
|
||||
emptyDbAsync(&g_pserver->db[j]);
|
||||
} else {
|
||||
dictEmpty(g_pserver->db[j].pdict,callback);
|
||||
delete g_pserver->db[j].setexpire;
|
||||
g_pserver->db[j].setexpire = new (MALLOC_LOCAL) expireset();
|
||||
g_pserver->db[j].expireitr = g_pserver->db[j].setexpire->end();
|
||||
}
|
||||
}
|
||||
if (g_pserver->cluster_enabled) {
|
||||
if (async) {
|
||||
@@ -546,14 +552,14 @@ void flushallCommand(client *c) {
|
||||
signalFlushedDb(-1);
|
||||
g_pserver->dirty += emptyDb(-1,flags,NULL);
|
||||
addReply(c,shared.ok);
|
||||
if (g_pserver->FRdbSaveInProgress()) killRDBChild();
|
||||
if (g_pserver->rdb_child_pid != -1) killRDBChild();
|
||||
if (g_pserver->saveparamslen > 0) {
|
||||
/* Normally rdbSave() will reset dirty, but we don't want this here
|
||||
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
|
||||
int saved_dirty = g_pserver->dirty;
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
rdbSave(nullptr, rsiptr);
|
||||
rdbSave(rsiptr);
|
||||
g_pserver->dirty = saved_dirty;
|
||||
}
|
||||
g_pserver->dirty++;
|
||||
@@ -628,35 +634,18 @@ void randomkeyCommand(client *c) {
|
||||
decrRefCount(key);
|
||||
}
|
||||
|
||||
|
||||
bool redisDbPersistentData::iterate(std::function<bool(const char*, robj*)> fn)
|
||||
{
|
||||
dictIterator *di = dictGetSafeIterator(m_pdict);
|
||||
dictEntry *de = nullptr;
|
||||
bool fResult = true;
|
||||
while((de = dictNext(di)) != nullptr)
|
||||
{
|
||||
ensure(de);
|
||||
if (!fn((const char*)dictGetKey(de), (robj*)dictGetVal(de)))
|
||||
{
|
||||
fResult = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return fResult;
|
||||
}
|
||||
|
||||
void keysCommand(client *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
sds pattern = szFromObj(c->argv[1]);
|
||||
int plen = sdslen(pattern), allkeys;
|
||||
unsigned long numkeys = 0;
|
||||
void *replylen = addReplyDeferredLen(c);
|
||||
|
||||
aeReleaseLock();
|
||||
|
||||
di = dictGetSafeIterator(c->db->pdict);
|
||||
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
|
||||
c->db->iterate([&](const char *key, robj *)->bool {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key = (sds)dictGetKey(de);
|
||||
robj *keyobj;
|
||||
|
||||
if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
|
||||
@@ -667,15 +656,9 @@ void keysCommand(client *c) {
|
||||
}
|
||||
decrRefCount(keyobj);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
setDeferredArrayLen(c,replylen,numkeys);
|
||||
|
||||
fastlock_unlock(&c->db->lock); // we must release the DB lock before acquiring the AE lock to prevent deadlocks
|
||||
AeLocker lock;
|
||||
lock.arm(c);
|
||||
fastlock_lock(&c->db->lock); // we still need the DB lock
|
||||
lock.release();
|
||||
}
|
||||
|
||||
/* This callback is used by scanGenericCommand in order to collect elements
|
||||
@@ -803,7 +786,7 @@ void scanGenericCommand(client *c, robj_roptr o, unsigned long cursor) {
|
||||
/* Handle the case of a hash table. */
|
||||
ht = NULL;
|
||||
if (o == nullptr) {
|
||||
ht = c->db->dictUnsafeKeyOnly();
|
||||
ht = c->db->pdict;
|
||||
} else if (o->type == OBJ_SET && o->encoding == OBJ_ENCODING_HT) {
|
||||
ht = (dict*)ptrFromObj(o);
|
||||
} else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT) {
|
||||
@@ -936,7 +919,7 @@ void scanCommand(client *c) {
|
||||
}
|
||||
|
||||
void dbsizeCommand(client *c) {
|
||||
addReplyLongLong(c,c->db->size());
|
||||
addReplyLongLong(c,dictSize(c->db->pdict));
|
||||
}
|
||||
|
||||
void lastsaveCommand(client *c) {
|
||||
@@ -1019,7 +1002,7 @@ void renameGenericCommand(client *c, int nx) {
|
||||
std::unique_ptr<expireEntry> spexpire;
|
||||
|
||||
{ // scope pexpireOld since it will be invalid soon
|
||||
expireEntry *pexpireOld = c->db->getExpire(c->argv[1]);
|
||||
expireEntry *pexpireOld = getExpire(c->db,c->argv[1]);
|
||||
if (pexpireOld != nullptr)
|
||||
spexpire = std::make_unique<expireEntry>(std::move(*pexpireOld));
|
||||
}
|
||||
@@ -1097,7 +1080,7 @@ void moveCommand(client *c) {
|
||||
|
||||
std::unique_ptr<expireEntry> spexpire;
|
||||
{ // scope pexpireOld
|
||||
expireEntry *pexpireOld = c->db->getExpire(c->argv[1]);
|
||||
expireEntry *pexpireOld = getExpire(c->db,c->argv[1]);
|
||||
if (pexpireOld != nullptr)
|
||||
spexpire = std::make_unique<expireEntry>(std::move(*pexpireOld));
|
||||
}
|
||||
@@ -1149,21 +1132,23 @@ int dbSwapDatabases(int id1, int id2) {
|
||||
if (id1 < 0 || id1 >= cserver.dbnum ||
|
||||
id2 < 0 || id2 >= cserver.dbnum) return C_ERR;
|
||||
if (id1 == id2) return C_OK;
|
||||
redisDb aux;
|
||||
memcpy(&aux, &g_pserver->db[id1], sizeof(redisDb));
|
||||
redisDb aux = g_pserver->db[id1];
|
||||
redisDb *db1 = &g_pserver->db[id1], *db2 = &g_pserver->db[id2];
|
||||
|
||||
/* Swap hash tables. Note that we don't swap blocking_keys,
|
||||
* ready_keys and watched_keys, since we want clients to
|
||||
* remain in the same DB they were. */
|
||||
redisDbPersistentData::swap(db1, db2);
|
||||
db1->pdict = db2->pdict;
|
||||
db1->setexpire = db2->setexpire;
|
||||
db1->expireitr = db2->expireitr;
|
||||
db1->avg_ttl = db2->avg_ttl;
|
||||
db1->last_expire_set = db2->last_expire_set;
|
||||
db1->expireitr = db2->expireitr;
|
||||
|
||||
db2->pdict = aux.pdict;
|
||||
db2->setexpire = aux.setexpire;
|
||||
db2->expireitr = aux.expireitr;
|
||||
db2->avg_ttl = aux.avg_ttl;
|
||||
db2->last_expire_set = aux.last_expire_set;
|
||||
db2->expireitr = aux.expireitr;
|
||||
|
||||
/* Now we need to handle clients blocked on lists: as an effect
|
||||
* of swapping the two DBs, a client that was waiting for list
|
||||
@@ -1212,82 +1197,74 @@ void swapdbCommand(client *c) {
|
||||
* Expires API
|
||||
*----------------------------------------------------------------------------*/
|
||||
int removeExpire(redisDb *db, robj *key) {
|
||||
auto itr = db->find(key);
|
||||
return db->removeExpire(key, itr);
|
||||
dictEntry *de = dictFind(db->pdict,ptrFromObj(key));
|
||||
return removeExpireCore(db, key, de);
|
||||
}
|
||||
int redisDbPersistentData::removeExpire(robj *key, dict_iter itr) {
|
||||
int removeExpireCore(redisDb *db, robj *key, dictEntry *de) {
|
||||
/* An expire may only be removed if there is a corresponding entry in the
|
||||
* main dict. Otherwise, the key will never be freed. */
|
||||
serverAssertWithInfo(NULL,key,itr != nullptr);
|
||||
serverAssertWithInfo(NULL,key,de != NULL);
|
||||
|
||||
robj *val = itr.val();
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
if (!val->FExpires())
|
||||
return 0;
|
||||
|
||||
trackkey(key);
|
||||
auto itrExpire = m_setexpire->find(itr.key());
|
||||
serverAssert(itrExpire != m_setexpire->end());
|
||||
serverAssert(itrExpire->key() == itr.key());
|
||||
m_setexpire->erase(itrExpire);
|
||||
auto itr = db->setexpire->find((sds)dictGetKey(de));
|
||||
serverAssert(itr != db->setexpire->end());
|
||||
serverAssert(itr->key() == (sds)dictGetKey(de));
|
||||
db->setexpire->erase(itr);
|
||||
val->SetFExpires(false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int redisDbPersistentData::removeSubkeyExpire(robj *key, robj *subkey) {
|
||||
auto de = find(szFromObj(key));
|
||||
serverAssertWithInfo(NULL,key,de != nullptr);
|
||||
|
||||
robj *val = de.val();
|
||||
if (!val->FExpires())
|
||||
return 0;
|
||||
|
||||
auto itr = m_setexpire->find(de.key());
|
||||
serverAssert(itr != m_setexpire->end());
|
||||
serverAssert(itr->key() == de.key());
|
||||
if (!itr->FFat())
|
||||
return 0;
|
||||
|
||||
int found = 0;
|
||||
for (auto subitr : *itr)
|
||||
{
|
||||
if (subitr.subkey() == nullptr)
|
||||
continue;
|
||||
if (sdscmp((sds)subitr.subkey(), szFromObj(subkey)) == 0)
|
||||
{
|
||||
itr->erase(subitr);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (itr->pfatentry()->size() == 0)
|
||||
this->removeExpire(key, de);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/* Set an expire to the specified key. If the expire is set in the context
|
||||
* of an user calling a command 'c' is the client, otherwise 'c' is set
|
||||
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
|
||||
* after which the key will no longer be considered valid. */
|
||||
void setExpire(client *c, redisDb *db, robj *key, robj *subkey, long long when) {
|
||||
dictEntry *kde;
|
||||
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
|
||||
/* Reuse the sds from the main dict in the expire dict */
|
||||
kde = dictFind(db->pdict,ptrFromObj(key));
|
||||
serverAssertWithInfo(NULL,key,kde != NULL);
|
||||
|
||||
if (((robj*)dictGetVal(kde))->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT)
|
||||
{
|
||||
// shared objects cannot have the expire bit set, create a real object
|
||||
dictSetVal(db->pdict, kde, dupStringObject((robj*)dictGetVal(kde)));
|
||||
}
|
||||
|
||||
/* Update TTL stats (exponential moving average) */
|
||||
/* Note: We never have to update this on expiry since we reduce it by the current elapsed time here */
|
||||
long long now = g_pserver->mstime;
|
||||
db->avg_ttl -= (now - db->last_expire_set); // reduce the TTL by the time that has elapsed
|
||||
if (db->expireSize() == 0)
|
||||
if (db->setexpire->empty())
|
||||
db->avg_ttl = 0;
|
||||
else
|
||||
db->avg_ttl -= db->avg_ttl / db->expireSize(); // slide one entry out the window
|
||||
db->avg_ttl -= db->avg_ttl / db->setexpire->size(); // slide one entry out the window
|
||||
if (db->avg_ttl < 0)
|
||||
db->avg_ttl = 0; // TTLs are never negative
|
||||
db->avg_ttl += (double)(when-now) / (db->expireSize()+1); // add the new entry
|
||||
db->avg_ttl += (double)(when-now) / (db->setexpire->size()+1); // add the new entry
|
||||
db->last_expire_set = now;
|
||||
|
||||
/* Update the expire set */
|
||||
db->setExpire(key, subkey, when);
|
||||
const char *szSubKey = (subkey != nullptr) ? szFromObj(subkey) : nullptr;
|
||||
if (((robj*)dictGetVal(kde))->FExpires()) {
|
||||
auto itr = db->setexpire->find((sds)dictGetKey(kde));
|
||||
serverAssert(itr != db->setexpire->end());
|
||||
expireEntry eNew(std::move(*itr));
|
||||
eNew.update(szSubKey, when);
|
||||
db->setexpire->erase(itr);
|
||||
db->setexpire->insert(eNew);
|
||||
}
|
||||
else
|
||||
{
|
||||
expireEntry e((sds)dictGetKey(kde), szSubKey, when);
|
||||
((robj*)dictGetVal(kde))->SetFExpires(true);
|
||||
db->setexpire->insert(e);
|
||||
}
|
||||
|
||||
int writable_slave = listLength(g_pserver->masters) && g_pserver->repl_slave_ro == 0;
|
||||
if (c && writable_slave && !(c->flags & CLIENT_MASTER))
|
||||
@@ -1296,24 +1273,26 @@ void setExpire(client *c, redisDb *db, robj *key, robj *subkey, long long when)
|
||||
|
||||
void setExpire(client *c, redisDb *db, robj *key, expireEntry &&e)
|
||||
{
|
||||
dictEntry *kde;
|
||||
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
|
||||
/* Reuse the sds from the main dict in the expire dict */
|
||||
auto kde = db->find(key);
|
||||
kde = dictFind(db->pdict,ptrFromObj(key));
|
||||
serverAssertWithInfo(NULL,key,kde != NULL);
|
||||
|
||||
if (kde.val()->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT)
|
||||
if (((robj*)dictGetVal(kde))->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT)
|
||||
{
|
||||
// shared objects cannot have the expire bit set, create a real object
|
||||
db->updateValue(kde, dupStringObject(kde.val()));
|
||||
dictSetVal(db->pdict, kde, dupStringObject((robj*)dictGetVal(kde)));
|
||||
}
|
||||
|
||||
if (kde.val()->FExpires())
|
||||
if (((robj*)dictGetVal(kde))->FExpires())
|
||||
removeExpire(db, key);
|
||||
|
||||
e.setKeyUnsafe(kde.key());
|
||||
db->setExpire(std::move(e));
|
||||
kde.val()->SetFExpires(true);
|
||||
e.setKeyUnsafe((sds)dictGetKey(kde));
|
||||
db->setexpire->insert(e);
|
||||
((robj*)dictGetVal(kde))->SetFExpires(true);
|
||||
|
||||
|
||||
int writable_slave = listLength(g_pserver->masters) && g_pserver->repl_slave_ro == 0;
|
||||
@@ -1323,19 +1302,22 @@ void setExpire(client *c, redisDb *db, robj *key, expireEntry &&e)
|
||||
|
||||
/* Return the expire time of the specified key, or null if no expire
|
||||
* is associated with this key (i.e. the key is non volatile) */
|
||||
expireEntry *redisDbPersistentData::getExpire(robj_roptr key) {
|
||||
expireEntry *getExpire(redisDb *db, robj_roptr key) {
|
||||
dictEntry *de;
|
||||
|
||||
/* No expire? return ASAP */
|
||||
if (expireSize() == 0)
|
||||
if (db->setexpire->size() == 0)
|
||||
return nullptr;
|
||||
|
||||
auto itr = find(szFromObj(key));
|
||||
if (itr == nullptr)
|
||||
de = dictFind(db->pdict, ptrFromObj(key));
|
||||
if (de == NULL)
|
||||
return nullptr;
|
||||
if (!itr.val()->FExpires())
|
||||
robj *obj = (robj*)dictGetVal(de);
|
||||
if (!obj->FExpires())
|
||||
return nullptr;
|
||||
|
||||
auto itrExpire = findExpire(itr.key());
|
||||
return itrExpire.operator->();
|
||||
auto itr = db->setexpire->find((sds)dictGetKey(de));
|
||||
return itr.operator->();
|
||||
}
|
||||
|
||||
/* Propagate expires into slaves and the AOF file.
|
||||
@@ -1343,7 +1325,7 @@ expireEntry *redisDbPersistentData::getExpire(robj_roptr key) {
|
||||
* to all the slaves and the AOF file if enabled.
|
||||
*
|
||||
* This way the key expiry is centralized in one place, and since both
|
||||
* AOF and the master->replica link guarantee operation ordering, everything
|
||||
* AOF and the master->slave link guarantee operation ordering, everything
|
||||
* will be consistent even if we allow write operations against expiring
|
||||
* keys. */
|
||||
void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
@@ -1365,7 +1347,7 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
|
||||
|
||||
/* Check if the key is expired. Note, this does not check subexpires */
|
||||
int keyIsExpired(redisDb *db, robj *key) {
|
||||
expireEntry *pexpire = db->getExpire(key);
|
||||
expireEntry *pexpire = getExpire(db,key);
|
||||
|
||||
if (pexpire == nullptr) return 0; /* No expire for this key */
|
||||
|
||||
@@ -1401,10 +1383,10 @@ int keyIsExpired(redisDb *db, robj *key) {
|
||||
* is via lookupKey*() family of functions.
|
||||
*
|
||||
* The behavior of the function depends on the replication role of the
|
||||
* instance, because replica instances do not expire keys, they wait
|
||||
* instance, because slave instances do not expire keys, they wait
|
||||
* for DELs from the master for consistency matters. However even
|
||||
* slaves will try to have a coherent return value for the function,
|
||||
* so that read commands executed in the replica side will be able to
|
||||
* so that read commands executed in the slave side will be able to
|
||||
* behave like if the key is expired even if still present (because the
|
||||
* master has yet to propagate the DEL).
|
||||
*
|
||||
@@ -1417,9 +1399,9 @@ int keyIsExpired(redisDb *db, robj *key) {
|
||||
int expireIfNeeded(redisDb *db, robj *key) {
|
||||
if (!keyIsExpired(db,key)) return 0;
|
||||
|
||||
/* If we are running in the context of a replica, instead of
|
||||
/* If we are running in the context of a slave, instead of
|
||||
* evicting the expired key from the database, we return ASAP:
|
||||
* the replica key expiration is controlled by the master that will
|
||||
* the slave key expiration is controlled by the master that will
|
||||
* send us synthesized DEL operations for expired keys.
|
||||
*
|
||||
* Still we try to return the right information to the caller,
|
||||
@@ -1797,304 +1779,3 @@ unsigned int delKeysInSlot(unsigned int hashslot) {
|
||||
unsigned int countKeysInSlot(unsigned int hashslot) {
|
||||
return g_pserver->cluster->slots_keys_count[hashslot];
|
||||
}
|
||||
|
||||
void redisDbPersistentData::initialize()
|
||||
{
|
||||
m_pdict = dictCreate(&dbDictType,this);
|
||||
m_setexpire = new(MALLOC_LOCAL) expireset();
|
||||
m_fAllChanged = false;
|
||||
m_fTrackingChanges = 0;
|
||||
}
|
||||
|
||||
void redisDb::initialize(int id)
|
||||
{
|
||||
redisDbPersistentData::initialize();
|
||||
this->expireitr = setexpire()->end();
|
||||
this->blocking_keys = dictCreate(&keylistDictType,NULL);
|
||||
this->ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
|
||||
this->watched_keys = dictCreate(&keylistDictType,NULL);
|
||||
this->id = id;
|
||||
this->avg_ttl = 0;
|
||||
this->last_expire_set = 0;
|
||||
this->defrag_later = listCreate();
|
||||
}
|
||||
|
||||
bool redisDbPersistentData::insert(char *key, robj *o)
|
||||
{
|
||||
int res = dictAdd(m_pdict, key, o);
|
||||
if (res == DICT_OK)
|
||||
trackkey(key);
|
||||
return (res == DICT_OK);
|
||||
}
|
||||
|
||||
void redisDbPersistentData::tryResize()
|
||||
{
|
||||
if (htNeedsResize(m_pdict))
|
||||
dictResize(m_pdict);
|
||||
}
|
||||
|
||||
size_t redisDb::clear(bool fAsync, void(callback)(void*))
|
||||
{
|
||||
size_t removed = size();
|
||||
if (fAsync) {
|
||||
redisDbPersistentData::emptyDbAsync();
|
||||
} else {
|
||||
redisDbPersistentData::clear(callback);
|
||||
}
|
||||
expireitr = setexpire()->end();
|
||||
return removed;
|
||||
}
|
||||
|
||||
void redisDbPersistentData::clear(void(callback)(void*))
|
||||
{
|
||||
dictEmpty(m_pdict,callback);
|
||||
if (m_fTrackingChanges)
|
||||
m_fAllChanged = true;
|
||||
delete m_setexpire;
|
||||
m_setexpire = new (MALLOC_LOCAL) expireset();
|
||||
if (m_pstorage != nullptr)
|
||||
m_pstorage->clear();
|
||||
}
|
||||
|
||||
/* static */ void redisDbPersistentData::swap(redisDbPersistentData *db1, redisDbPersistentData *db2)
|
||||
{
|
||||
redisDbPersistentData aux = *db1;
|
||||
db1->m_pdict = db2->m_pdict;
|
||||
db1->m_fTrackingChanges = db2->m_fTrackingChanges;
|
||||
db1->m_fAllChanged = db2->m_fAllChanged;
|
||||
db1->m_setexpire = db2->m_setexpire;
|
||||
db1->m_pstorage = db2->m_pstorage;
|
||||
db1->m_spdbSnapshot = db2->m_spdbSnapshot;
|
||||
|
||||
db2->m_pdict = aux.m_pdict;
|
||||
db2->m_fTrackingChanges = aux.m_fTrackingChanges;
|
||||
db2->m_fAllChanged = aux.m_fAllChanged;
|
||||
db2->m_setexpire = aux.m_setexpire;
|
||||
db2->m_pstorage = aux.m_pstorage;
|
||||
db2->m_spdbSnapshot = aux.m_spdbSnapshot;
|
||||
|
||||
db1->m_pdict->privdata = static_cast<redisDbPersistentData*>(db1);
|
||||
db2->m_pdict->privdata = static_cast<redisDbPersistentData*>(db2);
|
||||
}
|
||||
|
||||
void redisDbPersistentData::setExpire(robj *key, robj *subkey, long long when)
|
||||
{
|
||||
/* Reuse the sds from the main dict in the expire dict */
|
||||
dictEntry *kde = dictFind(m_pdict,ptrFromObj(key));
|
||||
serverAssertWithInfo(NULL,key,kde != NULL);
|
||||
trackkey(key);
|
||||
|
||||
if (((robj*)dictGetVal(kde))->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT)
|
||||
{
|
||||
// shared objects cannot have the expire bit set, create a real object
|
||||
dictSetVal(m_pdict, kde, dupStringObject((robj*)dictGetVal(kde)));
|
||||
}
|
||||
|
||||
const char *szSubKey = (subkey != nullptr) ? szFromObj(subkey) : nullptr;
|
||||
if (((robj*)dictGetVal(kde))->FExpires()) {
|
||||
auto itr = m_setexpire->find((sds)dictGetKey(kde));
|
||||
serverAssert(itr != m_setexpire->end());
|
||||
expireEntry eNew(std::move(*itr));
|
||||
eNew.update(szSubKey, when);
|
||||
m_setexpire->erase(itr);
|
||||
m_setexpire->insert(eNew);
|
||||
}
|
||||
else
|
||||
{
|
||||
expireEntry e((sds)dictGetKey(kde), szSubKey, when);
|
||||
((robj*)dictGetVal(kde))->SetFExpires(true);
|
||||
m_setexpire->insert(e);
|
||||
}
|
||||
}
|
||||
|
||||
void redisDbPersistentData::setExpire(expireEntry &&e)
|
||||
{
|
||||
trackkey(e.key());
|
||||
m_setexpire->insert(e);
|
||||
}
|
||||
|
||||
bool redisDb::FKeyExpires(const char *key)
|
||||
{
|
||||
return setexpireUnsafe()->find(key) != setexpire()->end();
|
||||
}
|
||||
|
||||
void redisDbPersistentData::updateValue(dict_iter itr, robj *val)
|
||||
{
|
||||
trackkey(itr.key());
|
||||
dictSetVal(m_pdict, itr.de, val);
|
||||
}
|
||||
|
||||
void redisDbPersistentData::ensure(const char *key)
|
||||
{
|
||||
dictEntry *de = dictFind(m_pdict, key);
|
||||
ensure(de);
|
||||
}
|
||||
|
||||
void redisDbPersistentData::ensure(dictEntry *de)
|
||||
{
|
||||
if (de != nullptr && dictGetVal(de) == nullptr)
|
||||
{
|
||||
if (m_spdbSnapshot != nullptr)
|
||||
{
|
||||
auto itr = m_spdbSnapshot->find((const char*)dictGetKey(de));
|
||||
serverAssert(itr != m_spdbSnapshot->end());
|
||||
if (itr.val()->getrefcount(std::memory_order_relaxed) == OBJ_SHARED_REFCOUNT)
|
||||
{
|
||||
dictSetVal(m_pdict, de, itr.val());
|
||||
}
|
||||
else
|
||||
{
|
||||
sds strT = serializeStoredObject(itr.val());
|
||||
robj *objNew = deserializeStoredObject(strT, sdslen(strT));
|
||||
sdsfree(strT);
|
||||
dictSetVal(m_pdict, de, objNew);
|
||||
serverAssert(objNew->getrefcount(std::memory_order_relaxed) == 1);
|
||||
serverAssert(objNew->mvcc_tstamp == itr.val()->mvcc_tstamp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
serverAssert(m_pstorage != nullptr);
|
||||
sds key = (sds)dictGetKey(de);
|
||||
m_pstorage->retrieve(key, sdslen(key), true, [&](const char *, size_t, const void *data, size_t cb){
|
||||
robj *o = deserializeStoredObject(data, cb);
|
||||
serverAssert(o != nullptr);
|
||||
dictSetVal(m_pdict, de, o);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void redisDbPersistentData::storeKey(const char *szKey, size_t cchKey, robj *o)
|
||||
{
|
||||
sds temp = serializeStoredObject(o);
|
||||
m_pstorage->insert(szKey, cchKey, temp, sdslen(temp));
|
||||
sdsfree(temp);
|
||||
}
|
||||
|
||||
void redisDbPersistentData::storeDatabase()
|
||||
{
|
||||
dictIterator *di = dictGetIterator(m_pdict);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
sds key = (sds)dictGetKey(de);
|
||||
robj *o = (robj*)dictGetVal(de);
|
||||
storeKey(key, sdslen(key), o);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
void redisDbPersistentData::processChanges()
|
||||
{
|
||||
--m_fTrackingChanges;
|
||||
serverAssert(m_fTrackingChanges >= 0);
|
||||
|
||||
if (m_pstorage != nullptr)
|
||||
{
|
||||
if (m_fTrackingChanges == 0)
|
||||
{
|
||||
if (m_fAllChanged)
|
||||
{
|
||||
m_pstorage->clear();
|
||||
storeDatabase();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto &str : m_setchanged)
|
||||
{
|
||||
sds sdsKey = sdsnewlen(str.data(), str.size());
|
||||
robj *o = find(sdsKey);
|
||||
if (o != nullptr)
|
||||
{
|
||||
storeKey(str.data(), str.size(), o);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pstorage->erase(str.data(), str.size());
|
||||
}
|
||||
sdsfree(sdsKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_setchanged.clear();
|
||||
}
|
||||
|
||||
std::shared_ptr<redisDbPersistentData> redisDbPersistentData::createSnapshot()
|
||||
{
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
serverAssert(m_spdbSnapshot == nullptr);
|
||||
auto spdb = std::make_shared<redisDbPersistentData>();
|
||||
spdb->m_pdict = dictCreate(&dbDictType,spdb.get());
|
||||
spdb->m_fAllChanged = false;
|
||||
spdb->m_fTrackingChanges = 0;
|
||||
spdb->m_pdict->rehashidx = m_pdict->rehashidx;
|
||||
spdb->m_pdict->iterators++; // fake an iterator so it doesn't rehash
|
||||
if (m_setexpire != nullptr)
|
||||
spdb->m_setexpire = new (MALLOC_LOCAL) expireset(*m_setexpire);
|
||||
|
||||
for (unsigned iht = 0; iht < 2; ++iht)
|
||||
{
|
||||
spdb->m_pdict->ht[iht] = m_pdict->ht[iht];
|
||||
if (m_pdict->ht[iht].size)
|
||||
m_pdict->ht[iht].table = (dictEntry**)zcalloc(m_pdict->ht[iht].size*sizeof(dictEntry*), MALLOC_SHARED);
|
||||
else
|
||||
m_pdict->ht[iht].table = nullptr;
|
||||
|
||||
for (size_t idx = 0; idx < m_pdict->ht[iht].size; ++idx)
|
||||
{
|
||||
const dictEntry *deSrc = spdb->m_pdict->ht[iht].table[idx];
|
||||
dictEntry **pdeDst = &m_pdict->ht[iht].table[idx];
|
||||
while (deSrc != nullptr)
|
||||
{
|
||||
*pdeDst = (dictEntry*)zmalloc(sizeof(dictEntry), MALLOC_SHARED);
|
||||
(*pdeDst)->key = deSrc->key;
|
||||
(*pdeDst)->v.val = nullptr;
|
||||
(*pdeDst)->next = nullptr;
|
||||
pdeDst = &(*pdeDst)->next;
|
||||
deSrc = deSrc->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_spdbSnapshot = std::move(spdb);
|
||||
return m_spdbSnapshot;
|
||||
}
|
||||
|
||||
void redisDbPersistentData::endSnapshot(const redisDbPersistentData *psnapshot)
|
||||
{
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
serverAssert(m_spdbSnapshot.get() == psnapshot);
|
||||
|
||||
dictIterator *di = dictGetIterator(m_pdict);
|
||||
dictEntry *de;
|
||||
while ((de = dictNext(di)) != NULL)
|
||||
{
|
||||
dictEntry *deSnapshot = dictFind(m_spdbSnapshot->m_pdict, dictGetKey(de));
|
||||
if (dictGetVal(de) == nullptr)
|
||||
{
|
||||
if (deSnapshot != nullptr)
|
||||
{
|
||||
de->v.val = deSnapshot->v.val;
|
||||
deSnapshot->v.val = nullptr;
|
||||
}
|
||||
}
|
||||
if (deSnapshot && (dictGetKey(deSnapshot) == dictGetKey(de)))
|
||||
{
|
||||
// The key is owned by the parent snapshot, so we modify the DB key dtor
|
||||
// to ensure the key is not free'd during the delete
|
||||
m_spdbSnapshot->m_pdict->type = &dbSnapshotDictType;
|
||||
dictDelete(m_spdbSnapshot->m_pdict, dictGetKey(de));
|
||||
m_spdbSnapshot->m_pdict->type = &dbDictType;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
m_spdbSnapshot->m_pdict->iterators--;
|
||||
m_spdbSnapshot = nullptr;
|
||||
}
|
||||
|
||||
redisDbPersistentData::~redisDbPersistentData()
|
||||
{
|
||||
dictRelease(m_pdict);
|
||||
delete m_setexpire;
|
||||
}
|
||||
+36
-27
@@ -126,7 +126,7 @@ void mixStringObjectDigest(unsigned char *digest, robj_roptr o) {
|
||||
void xorObjectDigest(redisDb *db, robj_roptr keyobj, unsigned char *digest, robj_roptr o) {
|
||||
uint32_t aux = htonl(o->type);
|
||||
mixDigest(digest,&aux,sizeof(aux));
|
||||
expireEntry *pexpire = db->getExpire(keyobj);
|
||||
expireEntry *pexpire = getExpire(db,keyobj);
|
||||
long long expiretime = -1;
|
||||
char buf[128];
|
||||
|
||||
@@ -266,6 +266,8 @@ void xorObjectDigest(redisDb *db, robj_roptr keyobj, unsigned char *digest, robj
|
||||
* a different digest. */
|
||||
void computeDatasetDigest(unsigned char *final) {
|
||||
unsigned char digest[20];
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
uint32_t aux;
|
||||
|
||||
@@ -274,7 +276,8 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
redisDb *db = g_pserver->db+j;
|
||||
|
||||
if (db->size() == 0) continue;
|
||||
if (dictSize(db->pdict) == 0) continue;
|
||||
di = dictGetSafeIterator(db->pdict);
|
||||
|
||||
/* hash the DB id, so the same dataset moved in a different
|
||||
* DB will lead to a different digest */
|
||||
@@ -282,21 +285,24 @@ void computeDatasetDigest(unsigned char *final) {
|
||||
mixDigest(final,&aux,sizeof(aux));
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
db->iterate([&](const char *key, robj *o)->bool {
|
||||
robj *keyobj;
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds key;
|
||||
robj *keyobj, *o;
|
||||
|
||||
memset(digest,0,20); /* This key-val digest */
|
||||
key = (sds)dictGetKey(de);
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
|
||||
mixDigest(digest,key,sdslen(key));
|
||||
|
||||
o = (robj*)dictGetVal(de);
|
||||
xorObjectDigest(db,keyobj,digest,o);
|
||||
|
||||
/* We can finally xor the key-val digest to the final digest */
|
||||
xorDigest(final,digest,20);
|
||||
decrRefCount(keyobj);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +365,7 @@ NULL
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"reload")) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(nullptr, rsiptr) != C_OK) {
|
||||
if (rdbSave(rsiptr) != C_OK) {
|
||||
addReply(c,shared.err);
|
||||
return;
|
||||
}
|
||||
@@ -388,14 +394,15 @@ NULL
|
||||
serverLog(LL_WARNING,"Append Only File loaded by DEBUG LOADAOF");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"object") && c->argc == 3) {
|
||||
dictEntry *de;
|
||||
robj *val;
|
||||
const char *strenc;
|
||||
|
||||
val = c->db->find(c->argv[2]);
|
||||
if (val == NULL) {
|
||||
if ((de = dictFind(c->db->pdict,ptrFromObj(c->argv[2]))) == NULL) {
|
||||
addReply(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
val = (robj*)dictGetVal(de);
|
||||
strenc = strEncoding(val->encoding);
|
||||
|
||||
char extra[138] = {0};
|
||||
@@ -439,14 +446,16 @@ NULL
|
||||
strenc, rdbSavedObjectLen(val),
|
||||
val->lru, estimateObjectIdleTime(val)/1000, extra);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"sdslen") && c->argc == 3) {
|
||||
auto itr = c->db->find(c->argv[2]);
|
||||
robj *val = itr.val();
|
||||
const char *key = itr.key();
|
||||
dictEntry *de;
|
||||
robj *val;
|
||||
sds key;
|
||||
|
||||
if (val == NULL) {
|
||||
if ((de = dictFind(c->db->pdict,ptrFromObj(c->argv[2]))) == NULL) {
|
||||
addReply(c,shared.nokeyerr);
|
||||
return;
|
||||
}
|
||||
val = (robj*)dictGetVal(de);
|
||||
key = (sds)dictGetKey(de);
|
||||
|
||||
if (val->type != OBJ_STRING || !sdsEncodedObject(val)) {
|
||||
addReplyError(c,"Not an sds encoded string.");
|
||||
@@ -456,16 +465,16 @@ NULL
|
||||
"val_sds_len:%lld, val_sds_avail:%lld, val_zmalloc: %lld",
|
||||
(long long) sdslen(key),
|
||||
(long long) sdsavail(key),
|
||||
(long long) sdsZmallocSize((sds)key),
|
||||
(long long) sdsZmallocSize(key),
|
||||
(long long) sdslen(szFromObj(val)),
|
||||
(long long) sdsavail(szFromObj(val)),
|
||||
(long long) getStringObjectSdsUsedMemory(val));
|
||||
}
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"ziplist") && c->argc == 3) {
|
||||
robj_roptr o;
|
||||
robj *o;
|
||||
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr))
|
||||
== nullptr) return;
|
||||
== NULL) return;
|
||||
|
||||
if (o->encoding != OBJ_ENCODING_ZIPLIST) {
|
||||
addReplyError(c,"Not an sds encoded string.");
|
||||
@@ -481,7 +490,7 @@ NULL
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != C_OK)
|
||||
return;
|
||||
c->db->expand(keys);
|
||||
dictExpand(c->db->pdict,keys);
|
||||
for (j = 0; j < keys; j++) {
|
||||
long valsize = 0;
|
||||
snprintf(buf,sizeof(buf),"%s:%lu",
|
||||
@@ -632,20 +641,20 @@ NULL
|
||||
}
|
||||
|
||||
stats = sdscatprintf(stats,"[Dictionary HT]\n");
|
||||
g_pserver->db[dbid].getStats(buf,sizeof(buf));
|
||||
dictGetStats(buf,sizeof(buf),g_pserver->db[dbid].pdict);
|
||||
stats = sdscat(stats,buf);
|
||||
|
||||
stats = sdscatprintf(stats,"[Expires set]\n");
|
||||
g_pserver->db[dbid].getExpireStats(buf, sizeof(buf));
|
||||
g_pserver->db[dbid].setexpire->getstats(buf, sizeof(buf));
|
||||
stats = sdscat(stats, buf);
|
||||
|
||||
addReplyBulkSds(c,stats);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"htstats-key") && c->argc == 3) {
|
||||
robj_roptr o;
|
||||
robj *o;
|
||||
dict *ht = NULL;
|
||||
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr))
|
||||
== nullptr) return;
|
||||
== NULL) return;
|
||||
|
||||
/* Get the hash table reference from the object, if possible. */
|
||||
switch (o->encoding) {
|
||||
@@ -673,12 +682,10 @@ NULL
|
||||
changeReplicationId();
|
||||
clearReplicationId2();
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"stringmatch-test") && c->argc == 2) {
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"stringmatch-test") && c->argc == 2)
|
||||
{
|
||||
stringmatchlen_fuzz_test();
|
||||
addReplyStatus(c,"Apparently Redis did not crash: test passed");
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]), "force-master") && c->argc == 2) {
|
||||
c->flags |= CLIENT_MASTER | CLIENT_MASTER_FORCE_REPLY;
|
||||
addReply(c, shared.ok);
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@@ -1247,10 +1254,12 @@ void logCurrentClient(void) {
|
||||
* selected DB, and if so print info about the associated object. */
|
||||
if (cc->argc >= 1) {
|
||||
robj *val, *key;
|
||||
dictEntry *de;
|
||||
|
||||
key = getDecodedObject(cc->argv[1]);
|
||||
val = cc->db->find(key);
|
||||
if (val) {
|
||||
de = dictFind(cc->db->pdict, ptrFromObj(key));
|
||||
if (de) {
|
||||
val = (robj*)dictGetVal(de);
|
||||
serverLog(LL_WARNING,"key '%s' found in DB containing the following object:", (char*)ptrFromObj(key));
|
||||
serverLogObjectDebugInfo(val);
|
||||
}
|
||||
|
||||
+10
-12
@@ -781,14 +781,12 @@ long defragKey(redisDb *db, dictEntry *de) {
|
||||
newsds = activeDefragSds(keysds);
|
||||
if (newsds)
|
||||
defragged++, de->key = newsds;
|
||||
if (!db->setexpire()->empty()) {
|
||||
replaceSateliteOSetKeyPtr(*const_cast<expireset*>(db->setexpire()), keysds, newsds);
|
||||
if (!db->setexpire->empty()) {
|
||||
replaceSateliteOSetKeyPtr(*db->setexpire, keysds, newsds);
|
||||
}
|
||||
|
||||
/* Try to defrag robj and / or string value. */
|
||||
ob = (robj*)dictGetVal(de);
|
||||
if (ob == nullptr)
|
||||
return defragged;
|
||||
if ((newob = activeDefragStringOb(ob, &defragged))) {
|
||||
de->v.val = newob;
|
||||
ob = newob;
|
||||
@@ -855,7 +853,7 @@ void defragScanCallback(void *privdata, const dictEntry *de) {
|
||||
g_pserver->stat_active_defrag_scanned++;
|
||||
}
|
||||
|
||||
/* Defrag scan callback for each hash table bucket,
|
||||
/* Defrag scan callback for each hash table bicket,
|
||||
* used in order to defrag the dictEntry allocations. */
|
||||
void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
|
||||
UNUSED(privdata); /* NOTE: this function is also used by both activeDefragCycle and scanLaterHash, etc. don't use privdata */
|
||||
@@ -904,8 +902,9 @@ long defragOtherGlobals() {
|
||||
|
||||
/* returns 0 more work may or may not be needed (see non-zero cursor),
|
||||
* and 1 if time is up and more work is needed. */
|
||||
int defragLaterItem(robj *ob, unsigned long *cursor, long long endtime) {
|
||||
if (ob) {
|
||||
int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
|
||||
if (de) {
|
||||
robj *ob = (robj*)dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
g_pserver->stat_active_defrag_hits += scanLaterList(ob);
|
||||
*cursor = 0; /* list has no scan, we must finish it in one go */
|
||||
@@ -960,11 +959,11 @@ int defragLaterStep(redisDb *db, long long endtime) {
|
||||
}
|
||||
|
||||
/* each time we enter this function we need to fetch the key from the dict again (if it still exists) */
|
||||
robj *o = db->find(current_key);
|
||||
dictEntry *de = dictFind(db->pdict, current_key);
|
||||
key_defragged = g_pserver->stat_active_defrag_hits;
|
||||
do {
|
||||
int quit = 0;
|
||||
if (defragLaterItem(o, &cursor, endtime))
|
||||
if (defragLaterItem(de, &cursor, endtime))
|
||||
quit = 1; /* time is up, we didn't finish all the work */
|
||||
|
||||
/* Don't start a new BIG key in this loop, this is because the
|
||||
@@ -1046,7 +1045,7 @@ void activeDefragCycle(void) {
|
||||
mstime_t latency;
|
||||
int quit = 0;
|
||||
|
||||
if (g_pserver->aof_child_pid!=-1 || g_pserver->FRdbSaveInProgress())
|
||||
if (g_pserver->aof_child_pid!=-1 || g_pserver->rdb_child_pid!=-1)
|
||||
return; /* Defragging memory while there's a fork will just do damage. */
|
||||
|
||||
/* Once a second, check if we the fragmentation justfies starting a scan
|
||||
@@ -1113,8 +1112,7 @@ void activeDefragCycle(void) {
|
||||
break; /* this will exit the function and we'll continue on the next cycle */
|
||||
}
|
||||
|
||||
// we actually look at the objects too but defragScanCallback can handle missing values
|
||||
cursor = dictScan(db->dictUnsafeKeyOnly(), cursor, defragScanCallback, defragDictBucketCallback, db);
|
||||
cursor = dictScan(db->pdict, cursor, defragScanCallback, defragDictBucketCallback, db);
|
||||
|
||||
/* Once in 16 scan iterations, 512 pointer reallocations. or 64 keys
|
||||
* (if we have a lot of pointers in one hash bucket or rehasing),
|
||||
|
||||
+34
-36
@@ -87,7 +87,7 @@ unsigned int LRU_CLOCK(void) {
|
||||
|
||||
/* Given an object returns the min number of milliseconds the object was never
|
||||
* requested, using an approximated LRU algorithm. */
|
||||
unsigned long long estimateObjectIdleTime(robj_roptr o) {
|
||||
unsigned long long estimateObjectIdleTime(robj *o) {
|
||||
unsigned long long lruclock = LRU_CLOCK();
|
||||
if (lruclock >= o->lru) {
|
||||
return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;
|
||||
@@ -252,20 +252,19 @@ struct visitFunctor
|
||||
return count < g_pserver->maxmemory_samples;
|
||||
}
|
||||
};
|
||||
void evictionPoolPopulate(int dbid, redisDb *db, expireset *setexpire, struct evictionPoolEntry *pool)
|
||||
void evictionPoolPopulate(int dbid, dict *dbdict, expireset *setexpire, struct evictionPoolEntry *pool)
|
||||
{
|
||||
if (setexpire != nullptr)
|
||||
{
|
||||
visitFunctor visitor { dbid, db->dictUnsafeKeyOnly(), pool, 0 };
|
||||
visitFunctor visitor { dbid, dbdict, pool, 0 };
|
||||
setexpire->random_visit(visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
dictEntry **samples = (dictEntry**)alloca(g_pserver->maxmemory_samples * sizeof(dictEntry*));
|
||||
int count = dictGetSomeKeys(db->dictUnsafeKeyOnly(),samples,g_pserver->maxmemory_samples);
|
||||
int count = dictGetSomeKeys(dbdict,samples,g_pserver->maxmemory_samples);
|
||||
for (int j = 0; j < count; j++) {
|
||||
robj *o = (robj*)dictGetVal(samples[j]);
|
||||
serverAssert(o != nullptr); // BUG!!! We have to get the info we need here without permanently rehydrating the obj
|
||||
processEvictionCandidate(dbid, (sds)dictGetKey(samples[j]), o, nullptr, pool);
|
||||
}
|
||||
}
|
||||
@@ -375,8 +374,8 @@ size_t freeMemoryGetNotCountedMemory(void) {
|
||||
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)listNodeValue(ln);
|
||||
overhead += getClientOutputBufferMemoryUsage(replica);
|
||||
client *slave = (client*)listNodeValue(ln);
|
||||
overhead += getClientOutputBufferMemoryUsage(slave);
|
||||
}
|
||||
}
|
||||
if (g_pserver->aof_state != AOF_OFF) {
|
||||
@@ -505,16 +504,16 @@ int freeMemoryIfNeeded(void) {
|
||||
db = g_pserver->db+i;
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS)
|
||||
{
|
||||
if ((keys = db->size()) != 0) {
|
||||
evictionPoolPopulate(i, db, nullptr, pool);
|
||||
if ((keys = dictSize(db->pdict)) != 0) {
|
||||
evictionPoolPopulate(i, db->pdict, nullptr, pool);
|
||||
total_keys += keys;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
keys = db->expireSize();
|
||||
keys = db->setexpire->size();
|
||||
if (keys != 0)
|
||||
evictionPoolPopulate(i, db, db->setexpireUnsafe(), pool);
|
||||
evictionPoolPopulate(i, db->pdict, db->setexpire, pool);
|
||||
total_keys += keys;
|
||||
}
|
||||
}
|
||||
@@ -526,9 +525,9 @@ int freeMemoryIfNeeded(void) {
|
||||
bestdbid = pool[k].dbid;
|
||||
sds key = nullptr;
|
||||
|
||||
auto itr = g_pserver->db[pool[k].dbid].find(pool[k].key);
|
||||
if (itr != nullptr && (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS || itr.val()->FExpires()))
|
||||
key = itr.key();
|
||||
dictEntry *de = dictFind(g_pserver->db[pool[k].dbid].pdict,pool[k].key);
|
||||
if (de != nullptr && (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS || ((robj*)dictGetVal(de))->FExpires()))
|
||||
key = (sds)dictGetKey(de);
|
||||
|
||||
/* Remove the entry from the pool. */
|
||||
if (pool[k].key != pool[k].cached)
|
||||
@@ -560,18 +559,18 @@ int freeMemoryIfNeeded(void) {
|
||||
db = g_pserver->db+j;
|
||||
if (g_pserver->maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM)
|
||||
{
|
||||
if (db->size() != 0) {
|
||||
auto itr = db->random();
|
||||
bestkey = itr.key();
|
||||
if (dictSize(db->pdict) != 0) {
|
||||
dictEntry *de = dictGetRandomKey(db->pdict);
|
||||
bestkey = (sds)dictGetKey(de);
|
||||
bestdbid = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (db->expireSize())
|
||||
if (!db->setexpire->empty())
|
||||
{
|
||||
bestkey = (sds)db->random_expire().key();
|
||||
bestkey = (sds)db->setexpire->random_value().key();
|
||||
bestdbid = j;
|
||||
break;
|
||||
}
|
||||
@@ -582,17 +581,16 @@ int freeMemoryIfNeeded(void) {
|
||||
/* Finally remove the selected key. */
|
||||
if (bestkey) {
|
||||
db = g_pserver->db+bestdbid;
|
||||
|
||||
robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
|
||||
propagateExpire(db,keyobj,g_pserver->lazyfree_lazy_eviction);
|
||||
/* We compute the amount of memory freed by db*Delete() alone.
|
||||
* It is possible that actually the memory needed to propagate
|
||||
* the DEL in AOF and replication link is greater than the one
|
||||
* we are freeing removing the key, but we can't account for
|
||||
* that otherwise we would never exit the loop.
|
||||
*
|
||||
* AOF and Output buffer memory will be freed eventually so
|
||||
* we only care about memory used by the key space. */
|
||||
* It is possible that actually the memory needed to propagate
|
||||
* the DEL in AOF and replication link is greater than the one
|
||||
* we are freeing removing the key, but we can't account for
|
||||
* that otherwise we would never exit the loop.
|
||||
*
|
||||
* AOF and Output buffer memory will be freed eventually so
|
||||
* we only care about memory used by the key space. */
|
||||
delta = (long long) zmalloc_used_memory();
|
||||
latencyStartMonitor(eviction_latency);
|
||||
if (g_pserver->lazyfree_lazy_eviction)
|
||||
@@ -611,18 +609,18 @@ int freeMemoryIfNeeded(void) {
|
||||
keys_freed++;
|
||||
|
||||
/* When the memory to free starts to be big enough, we may
|
||||
* start spending so much time here that is impossible to
|
||||
* deliver data to the slaves fast enough, so we force the
|
||||
* transmission here inside the loop. */
|
||||
* start spending so much time here that is impossible to
|
||||
* deliver data to the slaves fast enough, so we force the
|
||||
* transmission here inside the loop. */
|
||||
if (slaves) flushSlavesOutputBuffers();
|
||||
|
||||
/* Normally our stop condition is the ability to release
|
||||
* a fixed, pre-computed amount of memory. However when we
|
||||
* are deleting objects in another thread, it's better to
|
||||
* check, from time to time, if we already reached our target
|
||||
* memory, since the "mem_freed" amount is computed only
|
||||
* across the dbAsyncDelete() call, while the thread can
|
||||
* release the memory all the time. */
|
||||
* a fixed, pre-computed amount of memory. However when we
|
||||
* are deleting objects in another thread, it's better to
|
||||
* check, from time to time, if we already reached our target
|
||||
* memory, since the "mem_freed" amount is computed only
|
||||
* across the dbAsyncDelete() call, while the thread can
|
||||
* release the memory all the time. */
|
||||
if (g_pserver->lazyfree_lazy_eviction && !(keys_freed % 16)) {
|
||||
if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
|
||||
/* Let's satisfy our stop condition. */
|
||||
|
||||
+42
-118
@@ -74,7 +74,8 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
}
|
||||
|
||||
expireEntryFat *pfat = e.pfatentry();
|
||||
robj *val = db->find(e.key());
|
||||
dictEntry *de = dictFind(db->pdict, e.key());
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
int deleted = 0;
|
||||
while (!pfat->FEmpty())
|
||||
{
|
||||
@@ -129,44 +130,28 @@ void activeExpireCycleExpire(redisDb *db, expireEntry &e, long long now) {
|
||||
}
|
||||
}
|
||||
|
||||
int parseUnitString(const char *sz)
|
||||
void expireMemberCommand(client *c)
|
||||
{
|
||||
if (strcasecmp(sz, "s") == 0)
|
||||
return UNIT_SECONDS;
|
||||
if (strcasecmp(sz, "ms") == 0)
|
||||
return UNIT_MILLISECONDS;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, long long when, int unit)
|
||||
{
|
||||
switch (unit)
|
||||
{
|
||||
case UNIT_SECONDS:
|
||||
when *= 1000;
|
||||
case UNIT_MILLISECONDS:
|
||||
break;
|
||||
|
||||
default:
|
||||
addReplyError(c, "Invalid unit arg");
|
||||
long long when;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
|
||||
return;
|
||||
}
|
||||
|
||||
when += basetime;
|
||||
|
||||
when *= 1000;
|
||||
when += mstime();
|
||||
|
||||
/* No key, return zero. */
|
||||
robj *val = lookupKeyWriteOrReply(c, key, shared.czero);
|
||||
if (val == nullptr) {
|
||||
dictEntry *de = dictFind(c->db->pdict, szFromObj(c->argv[1]));
|
||||
if (de == NULL) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
|
||||
switch (val->type)
|
||||
{
|
||||
case OBJ_SET:
|
||||
if (!setTypeIsMember(val, szFromObj(subkey))) {
|
||||
addReply(c,shared.czero);
|
||||
return;
|
||||
}
|
||||
// these types are safe
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -174,40 +159,11 @@ void expireMemberCore(client *c, robj *key, robj *subkey, long long basetime, lo
|
||||
return;
|
||||
}
|
||||
|
||||
setExpire(c, c->db, key, subkey, when);
|
||||
setExpire(c, c->db, c->argv[1], c->argv[2], when);
|
||||
|
||||
addReply(c, shared.cone);
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
|
||||
void expireMemberCommand(client *c)
|
||||
{
|
||||
long long when;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
if (c->argc > 5) {
|
||||
addReplyError(c, "Invalid number of arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
int unit = UNIT_SECONDS;
|
||||
if (c->argc == 5) {
|
||||
unit = parseUnitString(szFromObj(c->argv[4]));
|
||||
}
|
||||
|
||||
expireMemberCore(c, c->argv[1], c->argv[2], mstime(), when, unit);
|
||||
}
|
||||
|
||||
void expireMemberAtCommand(client *c)
|
||||
{
|
||||
long long when;
|
||||
if (getLongLongFromObjectOrReply(c, c->argv[3], &when, NULL) != C_OK)
|
||||
return;
|
||||
|
||||
expireMemberCore(c, c->argv[1], c->argv[2], 0, when, UNIT_SECONDS);
|
||||
}
|
||||
|
||||
|
||||
/* Try to expire a few timed out keys. The algorithm used is adaptive and
|
||||
* will use few CPU cycles if there are few expiring keys, otherwise
|
||||
* it will get more aggressive to avoid that too much memory is used by
|
||||
@@ -295,7 +251,7 @@ void activeExpireCycle(int type) {
|
||||
now = mstime();
|
||||
|
||||
/* If there is nothing to expire try next DB ASAP. */
|
||||
if (db->setexpireUnsafe()->empty())
|
||||
if (db->setexpire->empty())
|
||||
{
|
||||
db->avg_ttl = 0;
|
||||
db->last_expire_set = now;
|
||||
@@ -305,7 +261,7 @@ void activeExpireCycle(int type) {
|
||||
size_t expired = 0;
|
||||
size_t tried = 0;
|
||||
long long check = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; // assume a check is roughly 1us. It isn't but good enough
|
||||
db->expireitr = db->setexpireUnsafe()->enumerate(db->expireitr, now, [&](expireEntry &e) __attribute__((always_inline)) {
|
||||
db->expireitr = db->setexpire->enumerate(db->expireitr, now, [&](expireEntry &e) __attribute__((always_inline)) {
|
||||
if (e.when() < now)
|
||||
{
|
||||
activeExpireCycleExpire(db, e, now);
|
||||
@@ -351,17 +307,17 @@ void activeExpireCycle(int type) {
|
||||
*
|
||||
* Normally slaves do not process expires: they wait the masters to synthesize
|
||||
* DEL operations in order to retain consistency. However writable slaves are
|
||||
* an exception: if a key is created in the replica and an expire is assigned
|
||||
* an exception: if a key is created in the slave and an expire is assigned
|
||||
* to it, we need a way to expire such a key, since the master does not know
|
||||
* anything about such a key.
|
||||
*
|
||||
* In order to do so, we track keys created in the replica side with an expire
|
||||
* In order to do so, we track keys created in the slave side with an expire
|
||||
* set, and call the expireSlaveKeys() function from time to time in order to
|
||||
* reclaim the keys if they already expired.
|
||||
*
|
||||
* Note that the use case we are trying to cover here, is a popular one where
|
||||
* slaves are put in writable mode in order to compute slow operations in
|
||||
* the replica side that are mostly useful to actually read data in a more
|
||||
* the slave side that are mostly useful to actually read data in a more
|
||||
* processed way. Think at sets intersections in a tmp key, with an expire so
|
||||
* that it is also used as a cache to avoid intersecting every time.
|
||||
*
|
||||
@@ -370,7 +326,7 @@ void activeExpireCycle(int type) {
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* The dictionary where we remember key names and database ID of keys we may
|
||||
* want to expire from the replica. Since this function is not often used we
|
||||
* want to expire from the slave. Since this function is not often used we
|
||||
* don't even care to initialize the database at startup. We'll do it once
|
||||
* the feature is used the first time, that is, when rememberSlaveKeyWithExpire()
|
||||
* is called.
|
||||
@@ -405,16 +361,16 @@ void expireSlaveKeys(void) {
|
||||
redisDb *db = g_pserver->db+dbid;
|
||||
|
||||
// the expire is hashed based on the key pointer, so we need the point in the main db
|
||||
auto itrDB = db->find(keyname);
|
||||
auto itrExpire = db->setexpire()->end();
|
||||
if (itrDB != nullptr)
|
||||
itrExpire = db->setexpireUnsafe()->find(itrDB.key());
|
||||
dictEntry *deMain = dictFind(db->pdict, keyname);
|
||||
auto itr = db->setexpire->end();
|
||||
if (deMain != nullptr)
|
||||
itr = db->setexpire->find((sds)dictGetKey(deMain));
|
||||
int expired = 0;
|
||||
|
||||
if (itrExpire != db->setexpire()->end())
|
||||
if (itr != db->setexpire->end())
|
||||
{
|
||||
if (itrExpire->when() < start) {
|
||||
activeExpireCycleExpire(g_pserver->db+dbid,*itrExpire,start);
|
||||
if (itr->when() < start) {
|
||||
activeExpireCycleExpire(g_pserver->db+dbid,*itr,start);
|
||||
expired = 1;
|
||||
}
|
||||
}
|
||||
@@ -423,7 +379,7 @@ void expireSlaveKeys(void) {
|
||||
* corresponding bit in the new bitmap we set as value.
|
||||
* At the end of the loop if the bitmap is zero, it means we
|
||||
* no longer need to keep track of this key. */
|
||||
if (itrExpire != db->setexpire()->end() && !expired) {
|
||||
if (itr != db->setexpire->end() && !expired) {
|
||||
noexpire++;
|
||||
new_dbids |= (uint64_t)1 << dbid;
|
||||
}
|
||||
@@ -433,7 +389,7 @@ void expireSlaveKeys(void) {
|
||||
}
|
||||
|
||||
/* Set the new bitmap as value of the key, in the dictionary
|
||||
* of keys with an expire set directly in the writable replica. Otherwise
|
||||
* of keys with an expire set directly in the writable slave. Otherwise
|
||||
* if the bitmap is zero, we no longer need to keep track of it. */
|
||||
if (new_dbids)
|
||||
dictSetUnsignedIntegerVal(de,new_dbids);
|
||||
@@ -450,7 +406,7 @@ void expireSlaveKeys(void) {
|
||||
}
|
||||
|
||||
/* Track keys that received an EXPIRE or similar command in the context
|
||||
* of a writable replica. */
|
||||
* of a writable slave. */
|
||||
void rememberSlaveKeyWithExpire(redisDb *db, robj *key) {
|
||||
if (slaveKeysWithExpire == NULL) {
|
||||
static dictType dt = {
|
||||
@@ -492,7 +448,7 @@ size_t getSlaveKeyWithExpireCount(void) {
|
||||
*
|
||||
* Note: technically we should handle the case of a single DB being flushed
|
||||
* but it is not worth it since anyway race conditions using the same set
|
||||
* of key names in a wriatable replica and in its master will lead to
|
||||
* of key names in a wriatable slave and in its master will lead to
|
||||
* inconsistencies. This is just a best-effort thing we do. */
|
||||
void flushSlaveKeysWithExpireList(void) {
|
||||
if (slaveKeysWithExpire) {
|
||||
@@ -530,7 +486,7 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
|
||||
/* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past
|
||||
* should never be executed as a DEL when load the AOF or in the context
|
||||
* of a replica instance.
|
||||
* of a slave instance.
|
||||
*
|
||||
* Instead we take the other branch of the IF statement setting an expire
|
||||
* (possibly in the past) and wait for an explicit DEL from the master. */
|
||||
@@ -588,32 +544,11 @@ void ttlGenericCommand(client *c, int output_ms) {
|
||||
addReplyLongLong(c,-2);
|
||||
return;
|
||||
}
|
||||
|
||||
/* The key exists. Return -1 if it has no expire, or the actual
|
||||
* TTL value otherwise. */
|
||||
expireEntry *pexpire = c->db->getExpire(c->argv[1]);
|
||||
|
||||
if (c->argc == 2) {
|
||||
// primary expire
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expire);
|
||||
} else if (c->argc == 3) {
|
||||
// We want a subkey expire
|
||||
if (pexpire && pexpire->FFat()) {
|
||||
for (auto itr : *pexpire) {
|
||||
if (itr.subkey() == nullptr)
|
||||
continue;
|
||||
if (sdscmp((sds)itr.subkey(), szFromObj(c->argv[2])) == 0) {
|
||||
expire = itr.when();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addReplyError(c, "Invalid arguments");
|
||||
return;
|
||||
}
|
||||
|
||||
* TTL value otherwise. */
|
||||
expireEntry *pexpire = getExpire(c->db,c->argv[1]);
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expire);
|
||||
|
||||
if (expire != -1) {
|
||||
ttl = expire-mstime();
|
||||
@@ -639,22 +574,11 @@ void pttlCommand(client *c) {
|
||||
/* PERSIST key */
|
||||
void persistCommand(client *c) {
|
||||
if (lookupKeyWrite(c->db,c->argv[1])) {
|
||||
if (c->argc == 2) {
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else if (c->argc == 3) {
|
||||
if (c->db->removeSubkeyExpire(c->argv[1], c->argv[2])) {
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
if (removeExpire(c->db,c->argv[1])) {
|
||||
addReply(c,shared.cone);
|
||||
g_pserver->dirty++;
|
||||
} else {
|
||||
addReplyError(c, "Invalid arguments");
|
||||
addReply(c,shared.czero);
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.czero);
|
||||
|
||||
@@ -46,13 +46,13 @@
|
||||
#define ECCENT (sqrt(1.0 - (RATIO *RATIO)))
|
||||
#define COM (0.5 * ECCENT)
|
||||
|
||||
/// @brief The usual PI/180 constant
|
||||
const double DEG_TO_RAD = 0.017453292519943295769236907684886;
|
||||
/// @brief Earth's quatratic mean radius for WGS-84
|
||||
const double EARTH_RADIUS_IN_METERS = 6372797.560856;
|
||||
|
||||
const double MERCATOR_MAX = 20037726.37;
|
||||
#if 0 //unused
|
||||
const double MERCATOR_MIN = -20037726.37;
|
||||
#endif
|
||||
|
||||
static inline double deg_rad(double ang) { return ang * D_R; }
|
||||
static inline double rad_deg(double ang) { return ang / D_R; }
|
||||
|
||||
+13
-11
@@ -63,6 +63,11 @@ struct commandHelp {
|
||||
"Perform bitwise operations between strings",
|
||||
1,
|
||||
"2.6.0" },
|
||||
{ "BITOPRANGE",
|
||||
"operation destkey key start:end [key ...]",
|
||||
"Perform bitwise operations between strings with offset and range",
|
||||
1,
|
||||
"5.1.0" },
|
||||
{ "BITPOS",
|
||||
"key bit [start] [end]",
|
||||
"Find first bit set or clear in a string",
|
||||
@@ -344,11 +349,8 @@ struct commandHelp {
|
||||
0,
|
||||
"1.2.0" },
|
||||
{ "EXPIREMEMBER",
|
||||
"key subkey delay [Unit: s,ms]",
|
||||
"set a subkey's time to live in seconds (or milliseconds)"},
|
||||
{ "EXPIREMEMBERAT",
|
||||
"key subkey timestamp",
|
||||
"Set the expiration for a subkey as a UNIX timestamp"},
|
||||
"key subkey seconds",
|
||||
"set a subkey's time to live in seconds"},
|
||||
{ "FLUSHALL",
|
||||
"[ASYNC]",
|
||||
"Remove all keys from all databases",
|
||||
@@ -635,8 +637,8 @@ struct commandHelp {
|
||||
0,
|
||||
"2.2.3" },
|
||||
{ "PERSIST",
|
||||
"key [subkey]",
|
||||
"Remove the expiration from a key or subkey",
|
||||
"key",
|
||||
"Remove the expiration from a key",
|
||||
0,
|
||||
"2.2.0" },
|
||||
{ "PEXPIRE",
|
||||
@@ -680,8 +682,8 @@ struct commandHelp {
|
||||
6,
|
||||
"2.0.0" },
|
||||
{ "PTTL",
|
||||
"key [subkey]",
|
||||
"Get the time to live for a key or subkey in milliseconds",
|
||||
"key",
|
||||
"Get the time to live for a key in milliseconds",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "PUBLISH",
|
||||
@@ -955,8 +957,8 @@ struct commandHelp {
|
||||
0,
|
||||
"3.2.1" },
|
||||
{ "TTL",
|
||||
"key [subkey]",
|
||||
"Get the time to live for a key or subkey",
|
||||
"key",
|
||||
"Get the time to live for a key",
|
||||
0,
|
||||
"1.0.0" },
|
||||
{ "TYPE",
|
||||
|
||||
+13
-20
@@ -51,18 +51,18 @@ size_t lazyfreeGetFreeEffort(robj *obj) {
|
||||
* a lazy free list instead of being freed synchronously. The lazy free list
|
||||
* will be reclaimed in a different bio.c thread. */
|
||||
#define LAZYFREE_THRESHOLD 64
|
||||
bool redisDbPersistentData::asyncDelete(robj *key) {
|
||||
int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
/* If the value is composed of a few allocations, to free in a lazy way
|
||||
* is actually just slower... So under a certain limit we just free
|
||||
* the object synchronously. */
|
||||
dictEntry *de = dictUnlink(m_pdict,ptrFromObj(key));
|
||||
dictEntry *de = dictUnlink(db->pdict,ptrFromObj(key));
|
||||
if (de) {
|
||||
robj *val = (robj*)dictGetVal(de);
|
||||
if (val->FExpires())
|
||||
{
|
||||
/* Deleting an entry from the expires dict will not free the sds of
|
||||
* the key, because it is shared with the main dictionary. */
|
||||
removeExpire(key,dict_iter(de));
|
||||
removeExpireCore(db,key,de);
|
||||
}
|
||||
|
||||
size_t free_effort = lazyfreeGetFreeEffort(val);
|
||||
@@ -78,25 +78,21 @@ bool redisDbPersistentData::asyncDelete(robj *key) {
|
||||
if (free_effort > LAZYFREE_THRESHOLD && val->getrefcount(std::memory_order_relaxed) == 1) {
|
||||
atomicIncr(lazyfree_objects,1);
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,val,NULL,NULL);
|
||||
dictSetVal(m_pdict,de,NULL);
|
||||
dictSetVal(db->pdict,de,NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Release the key-val pair, or just the key if we set the val
|
||||
* field to NULL in order to lazy free it later. */
|
||||
if (de) {
|
||||
dictFreeUnlinkedEntry(m_pdict,de);
|
||||
dictFreeUnlinkedEntry(db->pdict,de);
|
||||
if (g_pserver->cluster_enabled) slotToKeyDel(key);
|
||||
return true;
|
||||
return 1;
|
||||
} else {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int dbAsyncDelete(redisDb *db, robj *key) {
|
||||
return db->asyncDelete(key);
|
||||
}
|
||||
|
||||
/* Free an object, if the object is huge enough, free it in async way. */
|
||||
void freeObjAsync(robj *o) {
|
||||
size_t free_effort = lazyfreeGetFreeEffort(o);
|
||||
@@ -111,15 +107,12 @@ void freeObjAsync(robj *o) {
|
||||
/* Empty a Redis DB asynchronously. What the function does actually is to
|
||||
* create a new empty set of hash tables and scheduling the old ones for
|
||||
* lazy freeing. */
|
||||
void redisDbPersistentData::emptyDbAsync() {
|
||||
dict *oldht1 = m_pdict;
|
||||
auto *set = m_setexpire;
|
||||
m_setexpire = new (MALLOC_LOCAL) expireset();
|
||||
m_pdict = dictCreate(&dbDictType,this);
|
||||
if (m_pstorage != nullptr)
|
||||
m_pstorage->clear();
|
||||
if (m_fTrackingChanges)
|
||||
m_fAllChanged = true;
|
||||
void emptyDbAsync(redisDb *db) {
|
||||
dict *oldht1 = db->pdict;
|
||||
auto *set = db->setexpire;
|
||||
db->setexpire = new (MALLOC_LOCAL) expireset();
|
||||
db->expireitr = db->setexpire->end();
|
||||
db->pdict = dictCreate(&dbDictType,NULL);
|
||||
atomicIncr(lazyfree_objects,dictSize(oldht1));
|
||||
bioCreateBackgroundJob(BIO_LAZY_FREE,NULL,oldht1,set);
|
||||
}
|
||||
|
||||
+5
-5
@@ -1439,7 +1439,7 @@ int RM_GetSelectedDb(RedisModuleCtx *ctx) {
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_MASTER: The Redis instance is a master
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a replica
|
||||
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a slave
|
||||
*
|
||||
* * REDISMODULE_CTX_FLAGS_READONLY: The Redis instance is read-only
|
||||
*
|
||||
@@ -1644,7 +1644,7 @@ int RM_UnlinkKey(RedisModuleKey *key) {
|
||||
* If no TTL is associated with the key or if the key is empty,
|
||||
* REDISMODULE_NO_EXPIRE is returned. */
|
||||
mstime_t RM_GetExpire(RedisModuleKey *key) {
|
||||
expireEntry *pexpire = key->db->getExpire(key->key);
|
||||
expireEntry *pexpire = getExpire(key->db,key->key);
|
||||
mstime_t expire = -1;
|
||||
if (pexpire != nullptr)
|
||||
pexpire->FGetPrimaryExpire(&expire);
|
||||
@@ -4289,7 +4289,7 @@ size_t RM_GetClusterSize(void) {
|
||||
*
|
||||
* The arguments ip, master_id, port and flags can be NULL in case we don't
|
||||
* need to populate back certain info. If an ip and master_id (only populated
|
||||
* if the instance is a replica) are specified, they point to buffers holding
|
||||
* if the instance is a slave) are specified, they point to buffers holding
|
||||
* at least REDISMODULE_NODE_ID_LEN bytes. The strings written back as ip
|
||||
* and master_id are not null terminated.
|
||||
*
|
||||
@@ -4300,7 +4300,7 @@ size_t RM_GetClusterSize(void) {
|
||||
* * REDISMODULE_NODE_SLAVE The node is a replica
|
||||
* * REDISMODULE_NODE_PFAIL We see the node as failing
|
||||
* * REDISMODULE_NODE_FAIL The cluster agrees the node is failing
|
||||
* * REDISMODULE_NODE_NOFAILOVER The replica is configured to never failover
|
||||
* * REDISMODULE_NODE_NOFAILOVER The slave is configured to never failover
|
||||
*/
|
||||
|
||||
clusterNode *clusterLookupNode(const char *name); /* We need access to internals */
|
||||
@@ -5181,7 +5181,7 @@ void moduleInitModulesSystem(void) {
|
||||
* The function aborts the server on errors, since to start with missing
|
||||
* modules is not considered sane: clients may rely on the existence of
|
||||
* given commands, loading AOF also may need some modules to exist, and
|
||||
* if this instance is a replica, it must understand commands from master. */
|
||||
* if this instance is a slave, it must understand commands from master. */
|
||||
void moduleLoadFromQueue(void) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
|
||||
+3
-3
@@ -142,7 +142,7 @@ void execCommand(client *c) {
|
||||
}
|
||||
|
||||
/* If there are write commands inside the transaction, and this is a read
|
||||
* only replica, we want to send an error. This happens when the transaction
|
||||
* only slave, we want to send an error. This happens when the transaction
|
||||
* was initiated when the instance was a master or a writable replica and
|
||||
* then the configuration changed (for example instance was turned into
|
||||
* a replica). */
|
||||
@@ -195,7 +195,7 @@ void execCommand(client *c) {
|
||||
int is_master = listLength(g_pserver->masters) == 0;
|
||||
g_pserver->dirty++;
|
||||
/* If inside the MULTI/EXEC block this instance was suddenly
|
||||
* switched from master to replica (using the SLAVEOF command), the
|
||||
* switched from master to slave (using the SLAVEOF command), the
|
||||
* initial MULTI was propagated into the replication backlog, but the
|
||||
* rest was not. We need to make sure to at least terminate the
|
||||
* backlog with the final EXEC. */
|
||||
@@ -333,7 +333,7 @@ void touchWatchedKeysOnFlush(int dbid) {
|
||||
* key exists, mark the client as dirty, as the key will be
|
||||
* removed. */
|
||||
if (dbid == -1 || wk->db->id == dbid) {
|
||||
if (wk->db->find(wk->key) != NULL)
|
||||
if (dictFind(wk->db->pdict, ptrFromObj(wk->key)) != NULL)
|
||||
c->flags |= CLIENT_DIRTY_CAS;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-20
@@ -195,7 +195,7 @@ client *createClient(int fd, int iel) {
|
||||
* buffers can hold, then we'll really install the handler. */
|
||||
void clientInstallWriteHandler(client *c) {
|
||||
/* Schedule the client to write the output buffers to the socket only
|
||||
* if not already done and, for slaves, if the replica can actually receive
|
||||
* if not already done and, for slaves, if the slave can actually receive
|
||||
* writes at this stage. */
|
||||
if (!(c->flags & CLIENT_PENDING_WRITE) &&
|
||||
(c->replstate == REPL_STATE_NONE ||
|
||||
@@ -239,7 +239,7 @@ void clientInstallAsyncWriteHandler(client *c) {
|
||||
*
|
||||
* 1) The event handler should already be installed since the output buffer
|
||||
* already contains something.
|
||||
* 2) The client is a replica but not yet online, so we want to just accumulate
|
||||
* 2) The client is a slave but not yet online, so we want to just accumulate
|
||||
* writes in the buffer but not actually sending them yet.
|
||||
*
|
||||
* Typically gets called every time a reply is built, before adding more
|
||||
@@ -247,7 +247,7 @@ void clientInstallAsyncWriteHandler(client *c) {
|
||||
* data should be appended to the output buffers. */
|
||||
int prepareClientToWrite(client *c, bool fAsync) {
|
||||
fAsync = fAsync && !FCorrectThread(c); // Not async if we're on the right thread
|
||||
serverAssert(FCorrectThread(c) || fAsync);
|
||||
serverAssert(!fAsync || GlobalLocksAcquired());
|
||||
serverAssert(c->fd <= 0 || c->lock.fOwnLock());
|
||||
|
||||
if (c->flags & CLIENT_FORCE_REPLY) return C_OK; // FORCE REPLY means we're doing something else with the buffer.
|
||||
@@ -442,7 +442,7 @@ void addReplyErrorLengthCore(client *c, const char *s, size_t len, bool fAsync)
|
||||
addReplyProtoCore(c,s,len,fAsync);
|
||||
addReplyProtoCore(c,"\r\n",2,fAsync);
|
||||
|
||||
/* Sometimes it could be normal that a replica replies to a master with
|
||||
/* Sometimes it could be normal that a slave replies to a master with
|
||||
* an error and this function gets called. Actually the error will never
|
||||
* be sent because addReply*() against master clients has no effect...
|
||||
* A notable example is:
|
||||
@@ -1214,7 +1214,7 @@ void unlinkClient(client *c) {
|
||||
|
||||
/* In the case of diskless replication the fork is writing to the
|
||||
* sockets and just closing the fd isn't enough, if we don't also
|
||||
* shutdown the socket the fork will continue to write to the replica
|
||||
* shutdown the socket the fork will continue to write to the slave
|
||||
* and the salve will only find out that it was disconnected when
|
||||
* it will finish reading the rdb. */
|
||||
if ((c->flags & CLIENT_SLAVE) &&
|
||||
@@ -1299,7 +1299,7 @@ bool freeClient(client *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Log link disconnection with replica */
|
||||
/* Log link disconnection with slave */
|
||||
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
|
||||
serverLog(LL_WARNING,"Connection with replica %s lost.",
|
||||
replicationGetSlaveName(c));
|
||||
@@ -1333,8 +1333,8 @@ bool freeClient(client *c) {
|
||||
* places where active clients may be referenced. */
|
||||
unlinkClient(c);
|
||||
|
||||
/* Master/replica cleanup Case 1:
|
||||
* we lost the connection with a replica. */
|
||||
/* Master/slave cleanup Case 1:
|
||||
* we lost the connection with a slave. */
|
||||
if (c->flags & CLIENT_SLAVE) {
|
||||
if (c->replstate == SLAVE_STATE_SEND_BULK) {
|
||||
if (c->repldbfd != -1) close(c->repldbfd);
|
||||
@@ -1352,7 +1352,7 @@ bool freeClient(client *c) {
|
||||
refreshGoodSlavesCount();
|
||||
}
|
||||
|
||||
/* Master/replica cleanup Case 2:
|
||||
/* Master/slave cleanup Case 2:
|
||||
* we lost the connection with the master. */
|
||||
if (c->flags & CLIENT_MASTER) replicationHandleMasterDisconnection(MasterInfoFromClient(c));
|
||||
|
||||
@@ -1494,7 +1494,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
|
||||
* just deliver as much data as it is possible to deliver.
|
||||
*
|
||||
* Moreover, we also send as much as possible if the client is
|
||||
* a replica (otherwise, on high-speed traffic, the replication
|
||||
* a slave (otherwise, on high-speed traffic, the replication
|
||||
* buffer will grow indefinitely) */
|
||||
if (totwritten > NET_MAX_WRITES_PER_EVENT &&
|
||||
(g_pserver->maxmemory == 0 ||
|
||||
@@ -1805,7 +1805,7 @@ int processInlineBuffer(client *c) {
|
||||
}
|
||||
|
||||
/* Newline from slaves can be used to refresh the last ACK time.
|
||||
* This is useful for a replica to ping back while loading a big
|
||||
* This is useful for a slave to ping back while loading a big
|
||||
* RDB file. */
|
||||
if (querylen == 0 && c->flags & CLIENT_SLAVE)
|
||||
c->repl_ack_time = g_pserver->unixtime;
|
||||
@@ -2038,8 +2038,8 @@ int processCommandAndResetClient(client *c, int flags) {
|
||||
}
|
||||
if (serverTL->current_client == NULL) deadclient = 1;
|
||||
serverTL->current_client = NULL;
|
||||
/* freeMemoryIfNeeded may flush replica output buffers. This may
|
||||
* result into a replica, that may be the active client, to be
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may
|
||||
* result into a slave, that may be the active client, to be
|
||||
* freed. */
|
||||
return deadclient ? C_ERR : C_OK;
|
||||
}
|
||||
@@ -2060,7 +2060,7 @@ void processInputBuffer(client *c, int callFlags) {
|
||||
if (c->flags & CLIENT_BLOCKED) break;
|
||||
|
||||
/* Don't process input from the master while there is a busy script
|
||||
* condition on the replica. We want just to accumulate the replication
|
||||
* condition on the slave. We want just to accumulate the replication
|
||||
* stream (instead of replying -BUSY like we do with other clients) and
|
||||
* later resume the processing. */
|
||||
if (g_pserver->lua_timedout && c->flags & CLIENT_MASTER) break;
|
||||
@@ -2910,10 +2910,10 @@ void flushSlavesOutputBuffers(void) {
|
||||
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)listNodeValue(ln);
|
||||
client *slave = (client*)listNodeValue(ln);
|
||||
int events;
|
||||
|
||||
if (!FCorrectThread(replica))
|
||||
if (!FCorrectThread(slave))
|
||||
continue; // we cannot synchronously flush other thread's clients
|
||||
|
||||
/* Note that the following will not flush output buffers of slaves
|
||||
@@ -2922,12 +2922,12 @@ void flushSlavesOutputBuffers(void) {
|
||||
* of put_online_on_ack is to postpone the moment it is installed.
|
||||
* This is what we want since slaves in this state should not receive
|
||||
* writes before the first ACK. */
|
||||
events = aeGetFileEvents(g_pserver->rgthreadvar[replica->iel].el,replica->fd);
|
||||
events = aeGetFileEvents(g_pserver->rgthreadvar[slave->iel].el,slave->fd);
|
||||
if (events & AE_WRITABLE &&
|
||||
replica->replstate == SLAVE_STATE_ONLINE &&
|
||||
clientHasPendingReplies(replica))
|
||||
slave->replstate == SLAVE_STATE_ONLINE &&
|
||||
clientHasPendingReplies(slave))
|
||||
{
|
||||
writeToClient(replica->fd,replica,0);
|
||||
writeToClient(slave->fd,slave,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+24
-133
@@ -789,7 +789,7 @@ size_t streamRadixTreeMemoryUsage(rax *rax) {
|
||||
* case of aggregated data types where only "sample_size" elements
|
||||
* are checked and averaged to estimate the total size. */
|
||||
#define OBJ_COMPUTE_SIZE_DEF_SAMPLES 5 /* Default sample size. */
|
||||
size_t objectComputeSize(robj_roptr o, size_t sample_size) {
|
||||
size_t objectComputeSize(robj *o, size_t sample_size) {
|
||||
sds ele, ele2;
|
||||
dict *d;
|
||||
dictIterator *di;
|
||||
@@ -800,7 +800,7 @@ size_t objectComputeSize(robj_roptr o, size_t sample_size) {
|
||||
if(o->encoding == OBJ_ENCODING_INT) {
|
||||
asize = sizeof(*o);
|
||||
} else if(o->encoding == OBJ_ENCODING_RAW) {
|
||||
asize = sdsAllocSize((sds)szFromObj(o))+sizeof(*o);
|
||||
asize = sdsAllocSize(szFromObj(o))+sizeof(*o);
|
||||
} else if(o->encoding == OBJ_ENCODING_EMBSTR) {
|
||||
asize = sdslen(szFromObj(o))+2+sizeof(*o);
|
||||
} else {
|
||||
@@ -1054,20 +1054,20 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
|
||||
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
redisDb *db = g_pserver->db+j;
|
||||
long long keyscount = db->size();
|
||||
long long keyscount = dictSize(db->pdict);
|
||||
if (keyscount==0) continue;
|
||||
|
||||
mh->total_keys += keyscount;
|
||||
mh->db = (decltype(mh->db))zrealloc(mh->db,sizeof(mh->db[0])*(mh->num_dbs+1), MALLOC_LOCAL);
|
||||
mh->db[mh->num_dbs].dbid = j;
|
||||
|
||||
mem = db->size() * sizeof(dictEntry) +
|
||||
db->slots() * sizeof(dictEntry*) +
|
||||
db->size() * sizeof(robj);
|
||||
mem = dictSize(db->pdict) * sizeof(dictEntry) +
|
||||
dictSlots(db->pdict) * sizeof(dictEntry*) +
|
||||
dictSize(db->pdict) * sizeof(robj);
|
||||
mh->db[mh->num_dbs].overhead_ht_main = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
mem = db->setexpire()->bytes_used();
|
||||
mem = db->setexpire->bytes_used();
|
||||
mh->db[mh->num_dbs].overhead_ht_expires = mem;
|
||||
mem_total+=mem;
|
||||
|
||||
@@ -1246,12 +1246,15 @@ void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
|
||||
/* This is a helper function for the OBJECT command. We need to lookup keys
|
||||
* without any modification of LRU or other parameters. */
|
||||
robj_roptr objectCommandLookup(client *c, robj *key) {
|
||||
return c->db->find(key);
|
||||
robj *objectCommandLookup(client *c, robj *key) {
|
||||
dictEntry *de;
|
||||
|
||||
if ((de = dictFind(c->db->pdict,ptrFromObj(key))) == NULL) return NULL;
|
||||
return (robj*) dictGetVal(de);
|
||||
}
|
||||
|
||||
robj_roptr objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
robj_roptr o = objectCommandLookup(c,key);
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
robj *o = objectCommandLookup(c,key);
|
||||
|
||||
if (!o) addReply(c, reply);
|
||||
return o;
|
||||
@@ -1260,7 +1263,7 @@ robj_roptr objectCommandLookupOrReply(client *c, robj *key, robj *reply) {
|
||||
/* Object command allows to inspect the internals of an Redis Object.
|
||||
* Usage: OBJECT <refcount|encoding|idletime|freq> <key> */
|
||||
void objectCommand(client *c) {
|
||||
robj_roptr o;
|
||||
robj *o;
|
||||
|
||||
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
|
||||
const char *help[] = {
|
||||
@@ -1273,15 +1276,15 @@ NULL
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"refcount") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== nullptr) return;
|
||||
== NULL) return;
|
||||
addReplyLongLong(c,o->getrefcount(std::memory_order_relaxed));
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"encoding") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== nullptr) return;
|
||||
== NULL) return;
|
||||
addReplyBulkCString(c,strEncoding(o->encoding));
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"idletime") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== nullptr) return;
|
||||
== NULL) return;
|
||||
if (g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU) {
|
||||
addReplyError(c,"An LFU maxmemory policy is selected, idle time not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
return;
|
||||
@@ -1289,7 +1292,7 @@ NULL
|
||||
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"freq") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== nullptr) return;
|
||||
== NULL) return;
|
||||
if (!(g_pserver->maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
|
||||
addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
|
||||
return;
|
||||
@@ -1298,11 +1301,7 @@ NULL
|
||||
* in case of the key has not been accessed for a long time,
|
||||
* because we update the access time only
|
||||
* when the key is read or overwritten. */
|
||||
addReplyLongLong(c,LFUDecrAndReturn(o.unsafe_robjcast()));
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]), "lastmodified") && c->argc == 3) {
|
||||
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
|
||||
== nullptr) return;
|
||||
addReplyLongLong(c, (g_pserver->mstime - (o->mvcc_tstamp >> MVCC_MS_SHIFT)) / 1000);
|
||||
addReplyLongLong(c,LFUDecrAndReturn(o));
|
||||
} else {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
}
|
||||
@@ -1324,6 +1323,7 @@ NULL
|
||||
};
|
||||
addReplyHelp(c, help);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"usage") && c->argc >= 3) {
|
||||
dictEntry *de;
|
||||
long long samples = OBJ_COMPUTE_SIZE_DEF_SAMPLES;
|
||||
for (int j = 3; j < c->argc; j++) {
|
||||
if (!strcasecmp(szFromObj(c->argv[j]),"samples") &&
|
||||
@@ -1342,14 +1342,12 @@ NULL
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto itr = c->db->find(c->argv[2]);
|
||||
if (itr == nullptr) {
|
||||
if ((de = dictFind(c->db->pdict,ptrFromObj(c->argv[2]))) == NULL) {
|
||||
addReplyNull(c, shared.nullbulk);
|
||||
return;
|
||||
}
|
||||
size_t usage = objectComputeSize(itr.val(),samples);
|
||||
usage += sdsAllocSize(itr.key());
|
||||
size_t usage = objectComputeSize((robj*)dictGetVal(de),samples);
|
||||
usage += sdsAllocSize((sds)dictGetKey(de));
|
||||
usage += sizeof(dictEntry);
|
||||
addReplyLongLong(c,usage);
|
||||
} else if (!strcasecmp(szFromObj(c->argv[1]),"stats") && c->argc == 2) {
|
||||
@@ -1493,110 +1491,3 @@ void redisObject::setrefcount(unsigned ref)
|
||||
serverAssert(!FExpires());
|
||||
refcount.store(ref, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
sds serializeStoredStringObject(sds str, robj_roptr o)
|
||||
{
|
||||
str = sdscatlen(str, &(*o), sizeof(robj));
|
||||
switch (o->encoding)
|
||||
{
|
||||
case OBJ_ENCODING_RAW:
|
||||
str = sdscatsds(str, (sds)szFromObj(o));
|
||||
break;
|
||||
|
||||
case OBJ_ENCODING_INT:
|
||||
break; //nop
|
||||
|
||||
case OBJ_ENCODING_EMBSTR:
|
||||
size_t cb = zmalloc_size(o.unsafe_robjcast());
|
||||
if (cb > sizeof(robj))
|
||||
{
|
||||
str = sdscatlen(str, o.unsafe_robjcast() + 1, cb - sizeof(robj));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
robj *deserializeStoredStringObject(const char *data, size_t cb)
|
||||
{
|
||||
const robj *oT = (const robj*)data;
|
||||
robj *newObject = nullptr;
|
||||
switch (oT->encoding)
|
||||
{
|
||||
case OBJ_ENCODING_INT:
|
||||
serverAssert(cb == sizeof(robj));
|
||||
[[fallthrough]];
|
||||
case OBJ_ENCODING_EMBSTR:
|
||||
newObject = (robj*)zmalloc(cb, MALLOC_LOCAL);
|
||||
memcpy(newObject, data, cb);
|
||||
newObject->setrefcount(1);
|
||||
return newObject;
|
||||
|
||||
case OBJ_ENCODING_RAW:
|
||||
newObject = (robj*)zmalloc(sizeof(robj), MALLOC_SHARED);
|
||||
memcpy(newObject, data, sizeof(robj));
|
||||
newObject->m_ptr = sdsnewlen(SDS_NOINIT,cb-sizeof(robj));
|
||||
memcpy(newObject->m_ptr, data+sizeof(robj), cb-sizeof(robj));
|
||||
newObject->setrefcount(1);
|
||||
return newObject;
|
||||
}
|
||||
serverPanic("Unknown string object encoding from storage");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
robj *deserializeStoredObject(const void *data, size_t cb)
|
||||
{
|
||||
switch (((char*)data)[0])
|
||||
{
|
||||
case RDB_TYPE_STRING:
|
||||
return deserializeStoredStringObject(((char*)data)+1, cb-1);
|
||||
|
||||
default:
|
||||
rio payload;
|
||||
int type;
|
||||
robj *obj;
|
||||
rioInitWithConstBuffer(&payload,data,cb);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,nullptr, OBJ_MVCC_INVALID)) == nullptr))
|
||||
{
|
||||
serverPanic("Bad data format");
|
||||
}
|
||||
if (rdbLoadType(&payload) == RDB_OPCODE_AUX)
|
||||
{
|
||||
robj *auxkey, *auxval;
|
||||
if ((auxkey = rdbLoadStringObject(&payload)) == NULL) goto eoferr;
|
||||
if ((auxval = rdbLoadStringObject(&payload)) == NULL) {
|
||||
decrRefCount(auxkey);
|
||||
goto eoferr;
|
||||
}
|
||||
if (strcasecmp(szFromObj(auxkey), "mvcc-tstamp") == 0) {
|
||||
obj->mvcc_tstamp = strtoull(szFromObj(auxval), nullptr, 10);
|
||||
}
|
||||
decrRefCount(auxkey);
|
||||
decrRefCount(auxval);
|
||||
}
|
||||
eoferr:
|
||||
return obj;
|
||||
}
|
||||
serverPanic("Unknown object type loading from storage");
|
||||
}
|
||||
|
||||
sds serializeStoredObject(robj_roptr o)
|
||||
{
|
||||
switch (o->type)
|
||||
{
|
||||
case OBJ_STRING:
|
||||
{
|
||||
sds sdsT = sdsnewlen(nullptr, 1);
|
||||
sdsT[0] = RDB_TYPE_STRING;
|
||||
return serializeStoredStringObject(sdsT, o);
|
||||
}
|
||||
|
||||
default:
|
||||
rio rdb;
|
||||
createDumpPayload(&rdb,o,nullptr);
|
||||
return (sds)rdb.io.buffer.ptr;
|
||||
}
|
||||
serverPanic("Attempting to store unknown object type");
|
||||
}
|
||||
@@ -705,7 +705,7 @@ int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **
|
||||
nodesize = sizeof(raxNode)+trimmedlen+raxPadding(trimmedlen)+
|
||||
sizeof(raxNode*);
|
||||
if (h->iskey && !h->isnull) nodesize += sizeof(void*);
|
||||
trimmed = zcalloc(nodesize, MALLOC_LOCAL);//rax_malloc(nodesize);
|
||||
trimmed = rax_malloc(nodesize);
|
||||
}
|
||||
|
||||
if (postfixlen) {
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ extern "C" {
|
||||
#include <sys/wait.h>
|
||||
|
||||
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
|
||||
int rdbSaveS3(char *s3bucket, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi)
|
||||
int rdbSaveS3(char *s3bucket, rdbSaveInfo *rsi)
|
||||
{
|
||||
int status = EXIT_FAILURE;
|
||||
int fd[2];
|
||||
@@ -40,7 +40,7 @@ int rdbSaveS3(char *s3bucket, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi)
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (rdbSaveFp(fp, rgpdb, rsi) != C_OK)
|
||||
if (rdbSaveFp(fp, rsi) != C_OK)
|
||||
{
|
||||
fclose(fp);
|
||||
return C_ERR;
|
||||
|
||||
+221
-314
@@ -42,9 +42,6 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <thread>
|
||||
#include <future>
|
||||
#include "aelocker.h"
|
||||
|
||||
#define rdbExitReportCorruptRDB(...) rdbCheckThenExit(__LINE__,__VA_ARGS__)
|
||||
|
||||
@@ -1106,21 +1103,21 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
|
||||
if (rsi) {
|
||||
if (rdbSaveAuxFieldStrInt(rdb,"repl-stream-db",rsi->repl_stream_db)
|
||||
== -1) return -1;
|
||||
if (rdbSaveAuxFieldStrStr(rdb,"repl-id",rsi->repl_id)
|
||||
if (rdbSaveAuxFieldStrStr(rdb,"repl-id",g_pserver->replid)
|
||||
== -1) return -1;
|
||||
if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",rsi->master_repl_offset)
|
||||
if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",g_pserver->master_repl_offset)
|
||||
== -1) return -1;
|
||||
}
|
||||
if (rdbSaveAuxFieldStrInt(rdb,"aof-preamble",aof_preamble) == -1) return -1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int saveKey(rio *rdb, redisDbPersistentData *db, int flags, size_t *processed, const char *keystr, robj *o)
|
||||
int saveKey(rio *rdb, redisDb *db, int flags, size_t *processed, const char *keystr, robj *o)
|
||||
{
|
||||
robj key;
|
||||
|
||||
initStaticStringObject(key,(char*)keystr);
|
||||
expireEntry *pexpire = db->getExpire(&key);
|
||||
expireEntry *pexpire = getExpire(db, &key);
|
||||
|
||||
if (rdbSaveKeyValuePair(rdb,&key,o,pexpire) == -1)
|
||||
return 0;
|
||||
@@ -1145,9 +1142,9 @@ int saveKey(rio *rdb, redisDbPersistentData *db, int flags, size_t *processed, c
|
||||
* When the function returns C_ERR and if 'error' is not NULL, the
|
||||
* integer pointed by 'error' is set to the value of errno just after the I/O
|
||||
* error. */
|
||||
int rdbSaveRio(rio *rdb, redisDbPersistentData **rgpdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
dictEntry *de;
|
||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
|
||||
dictIterator *di = NULL;
|
||||
dictEntry *de;
|
||||
char magic[10];
|
||||
int j;
|
||||
uint64_t cksum;
|
||||
@@ -1157,12 +1154,13 @@ int rdbSaveRio(rio *rdb, redisDbPersistentData **rgpdb, int *error, int flags, r
|
||||
rdb->update_cksum = rioGenericUpdateChecksum;
|
||||
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
|
||||
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
|
||||
|
||||
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
|
||||
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
redisDbPersistentData *db = rgpdb[j];
|
||||
if (db->size() == 0) continue;
|
||||
redisDb *db = g_pserver->db+j;
|
||||
dict *d = db->pdict;
|
||||
if (dictSize(d) == 0) continue;
|
||||
di = dictGetSafeIterator(d);
|
||||
|
||||
/* Write the SELECT DB opcode */
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
|
||||
@@ -1173,47 +1171,43 @@ int rdbSaveRio(rio *rdb, redisDbPersistentData **rgpdb, int *error, int flags, r
|
||||
* However this does not limit the actual size of the DB to load since
|
||||
* these sizes are just hints to resize the hash tables. */
|
||||
uint64_t db_size, expires_size;
|
||||
db_size = db->size();
|
||||
expires_size = db->expireSize();
|
||||
db_size = dictSize(db->pdict);
|
||||
expires_size = db->setexpire->size();
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;
|
||||
if (rdbSaveLen(rdb,db_size) == -1) goto werr;
|
||||
if (rdbSaveLen(rdb,expires_size) == -1) goto werr;
|
||||
|
||||
/* Iterate this DB writing every entry */
|
||||
size_t ckeysExpired = 0;
|
||||
bool fSavedAll = db->iterate([&](const char *keystr, robj *o)->bool {
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sds keystr = (sds)dictGetKey(de);
|
||||
robj *o = (robj*)dictGetVal(de);
|
||||
|
||||
if (o->FExpires())
|
||||
++ckeysExpired;
|
||||
|
||||
if (!saveKey(rdb, db, flags, &processed, keystr, o))
|
||||
return false;
|
||||
return !g_pserver->rdbThreadVars.fRdbThreadCancel;
|
||||
});
|
||||
if (!fSavedAll)
|
||||
goto werr;
|
||||
serverAssert(ckeysExpired == db->expireSize());
|
||||
goto werr;
|
||||
}
|
||||
serverAssert(ckeysExpired == db->setexpire->size());
|
||||
dictReleaseIterator(di);
|
||||
di = NULL; /* So that we don't release it again on error. */
|
||||
}
|
||||
|
||||
/* If we are storing the replication information on disk, persist
|
||||
* the script cache as well: on successful PSYNC after a restart, we need
|
||||
* to be able to process any EVALSHA inside the replication backlog the
|
||||
* master will send us. */
|
||||
{
|
||||
AeLocker lock;
|
||||
lock.arm(nullptr);
|
||||
if (rsi && dictSize(g_pserver->lua_scripts)) {
|
||||
di = dictGetIterator(g_pserver->lua_scripts);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *body = (robj*)dictGetVal(de);
|
||||
if (rdbSaveAuxField(rdb,"lua",3,szFromObj(body),sdslen(szFromObj(body))) == -1)
|
||||
goto werr;
|
||||
if (g_pserver->rdbThreadVars.fRdbThreadCancel)
|
||||
goto werr;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
di = NULL; /* So that we don't release it again on error. */
|
||||
}
|
||||
} // AeLocker end scope
|
||||
|
||||
/* EOF opcode */
|
||||
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
|
||||
@@ -1239,7 +1233,7 @@ werr:
|
||||
* While the suffix is the 40 bytes hex string we announced in the prefix.
|
||||
* This way processes receiving the payload can understand when it ends
|
||||
* without doing any processing of the content. */
|
||||
int rdbSaveRioWithEOFMark(rio *rdb, redisDbPersistentData **rgpdb, int *error, rdbSaveInfo *rsi) {
|
||||
int rdbSaveRioWithEOFMark(rio *rdb, int *error, rdbSaveInfo *rsi) {
|
||||
char eofmark[RDB_EOF_MARK_SIZE];
|
||||
|
||||
getRandomHexChars(eofmark,RDB_EOF_MARK_SIZE);
|
||||
@@ -1247,7 +1241,7 @@ int rdbSaveRioWithEOFMark(rio *rdb, redisDbPersistentData **rgpdb, int *error, r
|
||||
if (rioWrite(rdb,"$EOF:",5) == 0) goto werr;
|
||||
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
||||
if (rioWrite(rdb,"\r\n",2) == 0) goto werr;
|
||||
if (rdbSaveRio(rdb,rgpdb,error,RDB_SAVE_NONE,rsi) == C_ERR) goto werr;
|
||||
if (rdbSaveRio(rdb,error,RDB_SAVE_NONE,rsi) == C_ERR) goto werr;
|
||||
if (rioWrite(rdb,eofmark,RDB_EOF_MARK_SIZE) == 0) goto werr;
|
||||
return C_OK;
|
||||
|
||||
@@ -1257,7 +1251,7 @@ werr: /* Write error. */
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
int rdbSaveFp(FILE *fp, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi)
|
||||
int rdbSaveFp(FILE *fp, rdbSaveInfo *rsi)
|
||||
{
|
||||
int error = 0;
|
||||
rio rdb;
|
||||
@@ -1267,41 +1261,31 @@ int rdbSaveFp(FILE *fp, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi)
|
||||
if (g_pserver->rdb_save_incremental_fsync)
|
||||
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
|
||||
|
||||
if (rdbSaveRio(&rdb,rgpdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) {
|
||||
if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) {
|
||||
errno = error;
|
||||
return C_ERR;
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
int rdbSave(redisDbPersistentData **rgpdb, rdbSaveInfo *rsi)
|
||||
int rdbSave(rdbSaveInfo *rsi)
|
||||
{
|
||||
std::vector<redisDbPersistentData*> vecdb;
|
||||
if (rgpdb == nullptr)
|
||||
{
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
{
|
||||
vecdb.push_back(&g_pserver->db[idb]);
|
||||
}
|
||||
rgpdb = vecdb.data();
|
||||
}
|
||||
|
||||
int err = C_OK;
|
||||
if (g_pserver->rdb_filename != NULL)
|
||||
err = rdbSaveFile(g_pserver->rdb_filename, rgpdb, rsi);
|
||||
err = rdbSaveFile(g_pserver->rdb_filename, rsi);
|
||||
|
||||
if (err == C_OK && g_pserver->rdb_s3bucketpath != NULL)
|
||||
err = rdbSaveS3(g_pserver->rdb_s3bucketpath, rgpdb, rsi);
|
||||
err = rdbSaveS3(g_pserver->rdb_s3bucketpath, rsi);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Save the DB on disk. Return C_ERR on error, C_OK on success. */
|
||||
int rdbSaveFile(char *filename, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi) {
|
||||
int rdbSaveFile(char *filename, rdbSaveInfo *rsi) {
|
||||
char tmpfile[256];
|
||||
char cwd[MAXPATHLEN]; /* Current working dir path for error messages. */
|
||||
FILE *fp;
|
||||
|
||||
snprintf(tmpfile,256,"temp-%d.rdb", g_pserver->rdbThreadVars.tmpfileNum);
|
||||
snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
|
||||
fp = fopen(tmpfile,"w");
|
||||
if (!fp) {
|
||||
char *cwdp = getcwd(cwd,MAXPATHLEN);
|
||||
@@ -1314,7 +1298,7 @@ int rdbSaveFile(char *filename, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi)
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
if (rdbSaveFp(fp, rgpdb, rsi) == C_ERR){
|
||||
if (rdbSaveFp(fp, rsi) == C_ERR){
|
||||
goto werr;
|
||||
}
|
||||
|
||||
@@ -1339,12 +1323,9 @@ int rdbSaveFile(char *filename, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi)
|
||||
}
|
||||
|
||||
serverLog(LL_NOTICE,"DB saved on disk");
|
||||
if (serverTL != nullptr)
|
||||
{
|
||||
g_pserver->dirty = 0;
|
||||
g_pserver->lastsave = time(NULL);
|
||||
g_pserver->lastbgsave_status = C_OK;
|
||||
}
|
||||
g_pserver->dirty = 0;
|
||||
g_pserver->lastsave = time(NULL);
|
||||
g_pserver->lastbgsave_status = C_OK;
|
||||
return C_OK;
|
||||
|
||||
werr:
|
||||
@@ -1354,99 +1335,63 @@ werr:
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
struct rdbSaveThreadArgs
|
||||
{
|
||||
rdbSaveInfo rsi;
|
||||
redisDbPersistentData *rgpdb[1]; // NOTE: Variable Length
|
||||
};
|
||||
|
||||
void *rdbSaveThread(void *vargs)
|
||||
{
|
||||
rdbSaveThreadArgs *args = reinterpret_cast<rdbSaveThreadArgs*>(vargs);
|
||||
serverAssert(serverTL == nullptr);
|
||||
int retval = rdbSave(args->rgpdb, &args->rsi);
|
||||
if (retval == C_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||
|
||||
if (private_dirty) {
|
||||
serverLog(LL_NOTICE,
|
||||
"RDB: %zu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
|
||||
g_pserver->child_info_data.cow_size = private_dirty;
|
||||
sendChildInfo(CHILD_INFO_TYPE_RDB);
|
||||
}
|
||||
|
||||
aeAcquireLock();
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
g_pserver->db[idb].endSnapshot(args->rgpdb[idb]);
|
||||
aeReleaseLock();
|
||||
zfree(args);
|
||||
return (retval == C_OK) ? (void*)0 : (void*)1;
|
||||
}
|
||||
|
||||
int launchRdbSaveThread(pthread_t &child, rdbSaveInfo *rsi)
|
||||
{
|
||||
rdbSaveThreadArgs *args = (rdbSaveThreadArgs*)zmalloc(sizeof(rdbSaveThreadArgs) + ((cserver.dbnum-1)*sizeof(redisDbPersistentData*)), MALLOC_LOCAL);
|
||||
rdbSaveInfo rsiT = RDB_SAVE_INFO_INIT;
|
||||
if (rsi == nullptr)
|
||||
rsi = &rsiT;
|
||||
memcpy(&args->rsi, rsi, sizeof(rdbSaveInfo));
|
||||
memcpy(&args->rsi.repl_id, g_pserver->replid, sizeof(g_pserver->replid));
|
||||
args->rsi.master_repl_offset = g_pserver->master_repl_offset;
|
||||
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
args->rgpdb[idb] = g_pserver->db[idb].createSnapshot().get();
|
||||
|
||||
g_pserver->rdbThreadVars.tmpfileNum++;
|
||||
g_pserver->rdbThreadVars.fRdbThreadCancel = false;
|
||||
if (pthread_create(&child, NULL, rdbSaveThread, args)) {
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
g_pserver->db[idb].endSnapshot(args->rgpdb[idb]);
|
||||
zfree(args);
|
||||
return C_ERR;
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
int rdbSaveBackground(rdbSaveInfo *rsi) {
|
||||
pthread_t child;
|
||||
pid_t childpid;
|
||||
long long start;
|
||||
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->FRdbSaveInProgress()) return C_ERR;
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) return C_ERR;
|
||||
|
||||
g_pserver->dirty_before_bgsave = g_pserver->dirty;
|
||||
g_pserver->lastbgsave_try = time(NULL);
|
||||
openChildInfoPipe();
|
||||
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
int retval;
|
||||
|
||||
|
||||
g_pserver->stat_fork_time = ustime()-start;
|
||||
g_pserver->stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / g_pserver->stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000);
|
||||
if (launchRdbSaveThread(child, rsi) != C_OK) {
|
||||
closeChildInfoPipe();
|
||||
g_pserver->lastbgsave_status = C_ERR;
|
||||
serverLog(LL_WARNING,"Can't save in background: fork: %s",
|
||||
strerror(errno));
|
||||
return C_ERR;
|
||||
/* Child */
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("keydb-rdb-bgsave");
|
||||
retval = rdbSave(rsi);
|
||||
if (retval == C_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||
|
||||
if (private_dirty) {
|
||||
serverLog(LL_NOTICE,
|
||||
"RDB: %zu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
|
||||
g_pserver->child_info_data.cow_size = private_dirty;
|
||||
sendChildInfo(CHILD_INFO_TYPE_RDB);
|
||||
}
|
||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||
} else {
|
||||
/* Parent */
|
||||
g_pserver->stat_fork_time = ustime()-start;
|
||||
g_pserver->stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / g_pserver->stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000);
|
||||
if (childpid == -1) {
|
||||
closeChildInfoPipe();
|
||||
g_pserver->lastbgsave_status = C_ERR;
|
||||
serverLog(LL_WARNING,"Can't save in background: fork: %s",
|
||||
strerror(errno));
|
||||
return C_ERR;
|
||||
}
|
||||
serverLog(LL_NOTICE,"Background saving started by pid %d",childpid);
|
||||
g_pserver->rdb_save_time_start = time(NULL);
|
||||
g_pserver->rdb_child_pid = childpid;
|
||||
g_pserver->rdb_child_type = RDB_CHILD_TYPE_DISK;
|
||||
updateDictResizePolicy();
|
||||
return C_OK;
|
||||
}
|
||||
serverLog(LL_NOTICE,"Background saving started");
|
||||
g_pserver->rdb_save_time_start = time(NULL);
|
||||
g_pserver->rdbThreadVars.fRdbThreadActive = true;
|
||||
g_pserver->rdbThreadVars.rdb_child_thread = child;
|
||||
g_pserver->rdb_child_type = RDB_CHILD_TYPE_DISK;
|
||||
updateDictResizePolicy();
|
||||
|
||||
return C_OK;
|
||||
return C_OK; /* unreached */
|
||||
}
|
||||
|
||||
void rdbRemoveTempFile(int tmpfileNum) {
|
||||
void rdbRemoveTempFile(pid_t childpid) {
|
||||
char tmpfile[256];
|
||||
|
||||
snprintf(tmpfile,sizeof(tmpfile),"temp-%d.rdb", tmpfileNum);
|
||||
snprintf(tmpfile,sizeof(tmpfile),"temp-%d.rdb", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
@@ -2053,7 +1998,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
goto eoferr;
|
||||
if ((expires_size = rdbLoadLen(rdb,NULL)) == RDB_LENERR)
|
||||
goto eoferr;
|
||||
db->expand(db_size);
|
||||
dictExpand(db->pdict,db_size);
|
||||
continue; /* Read next opcode. */
|
||||
} else if (type == RDB_OPCODE_AUX) {
|
||||
/* AUX: generic string-string fields. Use to add state to RDB
|
||||
@@ -2162,10 +2107,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
|
||||
/* Read key */
|
||||
if (key != nullptr)
|
||||
{
|
||||
decrRefCount(key);
|
||||
key = nullptr;
|
||||
}
|
||||
|
||||
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
|
||||
/* Read value */
|
||||
@@ -2174,12 +2116,10 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
* an RDB file from disk, either at startup, or when an RDB was
|
||||
* received from the master. In the latter case, the master is
|
||||
* responsible for key expiry. If we would expire keys here, the
|
||||
* snapshot taken by the master may not be reflected on the replica. */
|
||||
* snapshot taken by the master may not be reflected on the slave. */
|
||||
if (listLength(g_pserver->masters) == 0 && !loading_aof && expiretime != -1 && expiretime < now) {
|
||||
decrRefCount(key);
|
||||
key = nullptr;
|
||||
decrRefCount(val);
|
||||
val = nullptr;
|
||||
} else {
|
||||
/* Add the new object in the hash table */
|
||||
int fInserted = dbMerge(db, key, val, rsi->fForceSetKey); // Note: dbMerge will incrRef
|
||||
@@ -2196,7 +2136,6 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
|
||||
else
|
||||
{
|
||||
decrRefCount(val);
|
||||
val = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2240,6 +2179,7 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
|
||||
return C_ERR; /* Just to avoid warning */
|
||||
}
|
||||
|
||||
int rdbLoadFile(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbLoad(rdbSaveInfo *rsi)
|
||||
{
|
||||
int err = C_ERR;
|
||||
@@ -2259,7 +2199,7 @@ int rdbLoad(rdbSaveInfo *rsi)
|
||||
*
|
||||
* If you pass an 'rsi' structure initialied with RDB_SAVE_OPTION_INIT, the
|
||||
* loading code will fiil the information fields in the structure. */
|
||||
int rdbLoadFile(const char *filename, rdbSaveInfo *rsi) {
|
||||
int rdbLoadFile(char *filename, rdbSaveInfo *rsi) {
|
||||
FILE *fp;
|
||||
rio rdb;
|
||||
int retval;
|
||||
@@ -2291,7 +2231,7 @@ void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
|
||||
serverLog(LL_WARNING,
|
||||
"Background saving terminated by signal %d", bysignal);
|
||||
latencyStartMonitor(latency);
|
||||
rdbRemoveTempFile(g_pserver->rdbThreadVars.tmpfileNum);
|
||||
rdbRemoveTempFile(g_pserver->rdb_child_pid);
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("rdb-unlink-temp-file",latency);
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
@@ -2299,7 +2239,7 @@ void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
|
||||
if (bysignal != SIGUSR1)
|
||||
g_pserver->lastbgsave_status = C_ERR;
|
||||
}
|
||||
g_pserver->rdbThreadVars.fRdbThreadActive = false;
|
||||
g_pserver->rdb_child_pid = -1;
|
||||
g_pserver->rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||
g_pserver->rdb_save_time_last = time(NULL)-g_pserver->rdb_save_time_start;
|
||||
g_pserver->rdb_save_time_start = -1;
|
||||
@@ -2324,11 +2264,11 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||
serverLog(LL_WARNING,
|
||||
"Background transfer terminated by signal %d", bysignal);
|
||||
}
|
||||
g_pserver->rdbThreadVars.fRdbThreadActive = false;
|
||||
g_pserver->rdb_child_pid = -1;
|
||||
g_pserver->rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||
g_pserver->rdb_save_time_start = -1;
|
||||
|
||||
/* If the child returns an OK exit code, read the set of replica client
|
||||
/* If the child returns an OK exit code, read the set of slave client
|
||||
* IDs and the associated status code. We'll terminate all the slaves
|
||||
* in error state.
|
||||
*
|
||||
@@ -2367,35 +2307,35 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
|
||||
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)ln->value;
|
||||
client *slave = (client*)ln->value;
|
||||
|
||||
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
|
||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
|
||||
uint64_t j;
|
||||
int errorcode = 0;
|
||||
|
||||
/* Search for the replica ID in the reply. In order for a replica to
|
||||
/* Search for the slave ID in the reply. In order for a slave to
|
||||
* continue the replication process, we need to find it in the list,
|
||||
* and it must have an error code set to 0 (which means success). */
|
||||
for (j = 0; j < ok_slaves[0]; j++) {
|
||||
if (replica->id == ok_slaves[2*j+1]) {
|
||||
if (slave->id == ok_slaves[2*j+1]) {
|
||||
errorcode = ok_slaves[2*j+2];
|
||||
break; /* Found in slaves list. */
|
||||
}
|
||||
}
|
||||
if (j == ok_slaves[0] || errorcode != 0) {
|
||||
serverLog(LL_WARNING,
|
||||
"Closing replica %s: child->replica RDB transfer failed: %s",
|
||||
replicationGetSlaveName(replica),
|
||||
"Closing slave %s: child->slave RDB transfer failed: %s",
|
||||
replicationGetSlaveName(slave),
|
||||
(errorcode == 0) ? "RDB transfer child aborted"
|
||||
: strerror(errorcode));
|
||||
freeClient(replica);
|
||||
freeClient(slave);
|
||||
} else {
|
||||
serverLog(LL_WARNING,
|
||||
"Slave %s correctly received the streamed RDB file.",
|
||||
replicationGetSlaveName(replica));
|
||||
replicationGetSlaveName(slave));
|
||||
/* Restore the socket as non-blocking. */
|
||||
anetNonBlock(NULL,replica->fd);
|
||||
anetSendTimeout(NULL,replica->fd,0);
|
||||
anetNonBlock(NULL,slave->fd);
|
||||
anetSendTimeout(NULL,slave->fd,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2423,114 +2363,26 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
* the child did not exit for an error, but because we wanted), and performs
|
||||
* the cleanup needed. */
|
||||
void killRDBChild(void) {
|
||||
g_pserver->rdbThreadVars.fRdbThreadCancel = true;
|
||||
void *rval;
|
||||
pthread_join(g_pserver->rdbThreadVars.rdb_child_thread,&rval);
|
||||
g_pserver->rdbThreadVars.fRdbThreadActive = false;
|
||||
g_pserver->rdbThreadVars.fRdbThreadCancel = false;
|
||||
rdbRemoveTempFile(g_pserver->rdbThreadVars.tmpfileNum);
|
||||
kill(g_pserver->rdb_child_pid,SIGUSR1);
|
||||
rdbRemoveTempFile(g_pserver->rdb_child_pid);
|
||||
closeChildInfoPipe();
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
|
||||
struct rdbSaveSocketThreadArgs
|
||||
{
|
||||
rdbSaveInfo rsi;
|
||||
int *fds;
|
||||
int numfds;
|
||||
uint64_t *clientids;
|
||||
redisDbPersistentData *rgpdb[1];
|
||||
};
|
||||
void *rdbSaveToSlavesSocketsThread(void *vargs)
|
||||
{
|
||||
/* Child */
|
||||
serverAssert(serverTL == nullptr);
|
||||
rdbSaveSocketThreadArgs *args = (rdbSaveSocketThreadArgs*)vargs;
|
||||
int retval;
|
||||
rio slave_sockets;
|
||||
|
||||
rioInitWithFdset(&slave_sockets,args->fds,args->numfds);
|
||||
zfree(args->fds);
|
||||
args->fds = nullptr;
|
||||
|
||||
retval = rdbSaveRioWithEOFMark(&slave_sockets,args->rgpdb,NULL,&args->rsi);
|
||||
if (retval == C_OK && rioFlush(&slave_sockets) == 0)
|
||||
retval = C_ERR;
|
||||
|
||||
if (retval == C_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||
|
||||
if (private_dirty) {
|
||||
serverLog(LL_NOTICE,
|
||||
"RDB: %zu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
|
||||
g_pserver->child_info_data.cow_size = private_dirty;
|
||||
sendChildInfo(CHILD_INFO_TYPE_RDB);
|
||||
|
||||
/* If we are returning OK, at least one replica was served
|
||||
* with the RDB file as expected, so we need to send a report
|
||||
* to the parent via the pipe. The format of the message is:
|
||||
*
|
||||
* <len> <replica[0].id> <replica[0].error> ...
|
||||
*
|
||||
* len, replica IDs, and replica errors, are all uint64_t integers,
|
||||
* so basically the reply is composed of 64 bits for the len field
|
||||
* plus 2 additional 64 bit integers for each entry, for a total
|
||||
* of 'len' entries.
|
||||
*
|
||||
* The 'id' represents the replica's client ID, so that the master
|
||||
* can match the report with a specific replica, and 'error' is
|
||||
* set to 0 if the replication process terminated with a success
|
||||
* or the error code if an error occurred. */
|
||||
void *msg = zmalloc(sizeof(uint64_t)*(1+2*args->numfds), MALLOC_LOCAL);
|
||||
uint64_t *len = (uint64_t*)msg;
|
||||
uint64_t *ids = len+1;
|
||||
int j, msglen;
|
||||
|
||||
*len = args->numfds;
|
||||
for (j = 0; j < args->numfds; j++) {
|
||||
*ids++ = args->clientids[j];
|
||||
*ids++ = slave_sockets.io.fdset.state[j];
|
||||
}
|
||||
|
||||
/* Write the message to the parent. If we have no good slaves or
|
||||
* we are unable to transfer the message to the parent, we exit
|
||||
* with an error so that the parent will abort the replication
|
||||
* process with all the childre that were waiting. */
|
||||
msglen = sizeof(uint64_t)*(1+2*args->numfds);
|
||||
if (*len == 0 ||
|
||||
write(g_pserver->rdb_pipe_write_result_to_parent,msg,msglen)
|
||||
!= msglen)
|
||||
{
|
||||
retval = C_ERR;
|
||||
}
|
||||
zfree(msg);
|
||||
}
|
||||
aeAcquireLock();
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
g_pserver->db[idb].endSnapshot(args->rgpdb[idb]);
|
||||
aeReleaseLock();
|
||||
zfree(args->clientids);
|
||||
zfree(args);
|
||||
rioFreeFdset(&slave_sockets);
|
||||
|
||||
return (retval == C_OK) ? (void*)0 : (void*)1;
|
||||
}
|
||||
|
||||
/* Spawn an RDB child that writes the RDB to the sockets of the slaves
|
||||
* that are currently in SLAVE_STATE_WAIT_BGSAVE_START state. */
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
int *fds;
|
||||
uint64_t *clientids;
|
||||
int numfds;
|
||||
listNode *ln;
|
||||
listIter li;
|
||||
pthread_t child;
|
||||
pid_t childpid;
|
||||
long long start;
|
||||
int pipefds[2];
|
||||
rdbSaveSocketThreadArgs *args = nullptr;
|
||||
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->FRdbSaveInProgress()) return C_ERR;
|
||||
if (g_pserver->aof_child_pid != -1 || g_pserver->rdb_child_pid != -1) return C_ERR;
|
||||
|
||||
/* Before to fork, create a pipe that will be used in order to
|
||||
* send back to the parent the IDs of the slaves that successfully
|
||||
@@ -2539,96 +2391,154 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
|
||||
g_pserver->rdb_pipe_read_result_from_child = pipefds[0];
|
||||
g_pserver->rdb_pipe_write_result_to_parent = pipefds[1];
|
||||
|
||||
args = (rdbSaveSocketThreadArgs*)zmalloc(sizeof(rdbSaveSocketThreadArgs) + sizeof(redisDbPersistentData*)*(cserver.dbnum-1), MALLOC_LOCAL);
|
||||
|
||||
/* Collect the file descriptors of the slaves we want to transfer
|
||||
* the RDB to, which are i WAIT_BGSAVE_START state. */
|
||||
args->fds = (int*)zmalloc(sizeof(int)*listLength(g_pserver->slaves), MALLOC_LOCAL);
|
||||
fds = (int*)zmalloc(sizeof(int)*listLength(g_pserver->slaves), MALLOC_LOCAL);
|
||||
/* We also allocate an array of corresponding client IDs. This will
|
||||
* be useful for the child process in order to build the report
|
||||
* (sent via unix pipe) that will be sent to the parent. */
|
||||
args->clientids = (uint64_t*)zmalloc(sizeof(uint64_t)*listLength(g_pserver->slaves), MALLOC_LOCAL);
|
||||
args->numfds = 0;
|
||||
memcpy(&args->rsi, rsi, sizeof(rdbSaveInfo));
|
||||
memcpy(&args->rsi.repl_id, g_pserver->replid, sizeof(g_pserver->replid));
|
||||
args->rsi.master_repl_offset = g_pserver->master_repl_offset;
|
||||
clientids = (uint64_t*)zmalloc(sizeof(uint64_t)*listLength(g_pserver->slaves), MALLOC_LOCAL);
|
||||
numfds = 0;
|
||||
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)ln->value;
|
||||
client *slave = (client*)ln->value;
|
||||
|
||||
if (replica->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
|
||||
args->clientids[args->numfds] = replica->id;
|
||||
args->fds[args->numfds++] = replica->fd;
|
||||
replicationSetupSlaveForFullResync(replica,getPsyncInitialOffset());
|
||||
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
|
||||
clientids[numfds] = slave->id;
|
||||
fds[numfds++] = slave->fd;
|
||||
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
|
||||
/* Put the socket in blocking mode to simplify RDB transfer.
|
||||
* We'll restore it when the children returns (since duped socket
|
||||
* will share the O_NONBLOCK attribute with the parent). */
|
||||
anetBlock(NULL,replica->fd);
|
||||
anetSendTimeout(NULL,replica->fd,g_pserver->repl_timeout*1000);
|
||||
anetBlock(NULL,slave->fd);
|
||||
anetSendTimeout(NULL,slave->fd,g_pserver->repl_timeout*1000);
|
||||
}
|
||||
}
|
||||
|
||||
/* Create the child process. */
|
||||
openChildInfoPipe();
|
||||
start = ustime();
|
||||
if ((childpid = fork()) == 0) {
|
||||
/* Child */
|
||||
int retval;
|
||||
rio slave_sockets;
|
||||
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
args->rgpdb[idb] = g_pserver->db[idb].createSnapshot().get();
|
||||
rioInitWithFdset(&slave_sockets,fds,numfds);
|
||||
zfree(fds);
|
||||
|
||||
g_pserver->rdbThreadVars.tmpfileNum++;
|
||||
g_pserver->rdbThreadVars.fRdbThreadCancel = false;
|
||||
if (pthread_create(&child, nullptr, rdbSaveToSlavesSocketsThread, args)) {
|
||||
serverLog(LL_WARNING,"Can't save in background: fork: %s",
|
||||
strerror(errno));
|
||||
closeListeningSockets(0);
|
||||
redisSetProcTitle("keydb-rdb-to-slaves");
|
||||
|
||||
/* Undo the state change. The caller will perform cleanup on
|
||||
* all the slaves in BGSAVE_START state, but an early call to
|
||||
* replicationSetupSlaveForFullResync() turned it into BGSAVE_END */
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)ln->value;
|
||||
int j;
|
||||
retval = rdbSaveRioWithEOFMark(&slave_sockets,NULL,rsi);
|
||||
if (retval == C_OK && rioFlush(&slave_sockets) == 0)
|
||||
retval = C_ERR;
|
||||
|
||||
for (j = 0; j < args->numfds; j++) {
|
||||
if (replica->id == args->clientids[j]) {
|
||||
replica->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
||||
break;
|
||||
if (retval == C_OK) {
|
||||
size_t private_dirty = zmalloc_get_private_dirty(-1);
|
||||
|
||||
if (private_dirty) {
|
||||
serverLog(LL_NOTICE,
|
||||
"RDB: %zu MB of memory used by copy-on-write",
|
||||
private_dirty/(1024*1024));
|
||||
}
|
||||
|
||||
g_pserver->child_info_data.cow_size = private_dirty;
|
||||
sendChildInfo(CHILD_INFO_TYPE_RDB);
|
||||
|
||||
/* If we are returning OK, at least one slave was served
|
||||
* with the RDB file as expected, so we need to send a report
|
||||
* to the parent via the pipe. The format of the message is:
|
||||
*
|
||||
* <len> <slave[0].id> <slave[0].error> ...
|
||||
*
|
||||
* len, slave IDs, and slave errors, are all uint64_t integers,
|
||||
* so basically the reply is composed of 64 bits for the len field
|
||||
* plus 2 additional 64 bit integers for each entry, for a total
|
||||
* of 'len' entries.
|
||||
*
|
||||
* The 'id' represents the slave's client ID, so that the master
|
||||
* can match the report with a specific slave, and 'error' is
|
||||
* set to 0 if the replication process terminated with a success
|
||||
* or the error code if an error occurred. */
|
||||
void *msg = zmalloc(sizeof(uint64_t)*(1+2*numfds), MALLOC_LOCAL);
|
||||
uint64_t *len = (uint64_t*)msg;
|
||||
uint64_t *ids = len+1;
|
||||
int j, msglen;
|
||||
|
||||
*len = numfds;
|
||||
for (j = 0; j < numfds; j++) {
|
||||
*ids++ = clientids[j];
|
||||
*ids++ = slave_sockets.io.fdset.state[j];
|
||||
}
|
||||
|
||||
/* Write the message to the parent. If we have no good slaves or
|
||||
* we are unable to transfer the message to the parent, we exit
|
||||
* with an error so that the parent will abort the replication
|
||||
* process with all the childre that were waiting. */
|
||||
msglen = sizeof(uint64_t)*(1+2*numfds);
|
||||
if (*len == 0 ||
|
||||
write(g_pserver->rdb_pipe_write_result_to_parent,msg,msglen)
|
||||
!= msglen)
|
||||
{
|
||||
retval = C_ERR;
|
||||
}
|
||||
zfree(msg);
|
||||
}
|
||||
zfree(clientids);
|
||||
rioFreeFdset(&slave_sockets);
|
||||
exitFromChild((retval == C_OK) ? 0 : 1);
|
||||
} else {
|
||||
/* Parent */
|
||||
if (childpid == -1) {
|
||||
serverLog(LL_WARNING,"Can't save in background: fork: %s",
|
||||
strerror(errno));
|
||||
|
||||
/* Undo the state change. The caller will perform cleanup on
|
||||
* all the slaves in BGSAVE_START state, but an early call to
|
||||
* replicationSetupSlaveForFullResync() turned it into BGSAVE_END */
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *slave = (client*)ln->value;
|
||||
int j;
|
||||
|
||||
for (j = 0; j < numfds; j++) {
|
||||
if (slave->id == clientids[j]) {
|
||||
slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(pipefds[0]);
|
||||
close(pipefds[1]);
|
||||
closeChildInfoPipe();
|
||||
} else {
|
||||
g_pserver->stat_fork_time = ustime()-start;
|
||||
g_pserver->stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / g_pserver->stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000);
|
||||
|
||||
serverLog(LL_NOTICE,"Background RDB transfer started by pid %d",
|
||||
childpid);
|
||||
g_pserver->rdb_save_time_start = time(NULL);
|
||||
g_pserver->rdb_child_pid = childpid;
|
||||
g_pserver->rdb_child_type = RDB_CHILD_TYPE_SOCKET;
|
||||
updateDictResizePolicy();
|
||||
}
|
||||
close(pipefds[0]);
|
||||
close(pipefds[1]);
|
||||
closeChildInfoPipe();
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
g_pserver->db[idb].endSnapshot(args->rgpdb[idb]);
|
||||
zfree(args->clientids);
|
||||
zfree(args->fds);
|
||||
zfree(args);
|
||||
return C_ERR;
|
||||
zfree(clientids);
|
||||
zfree(fds);
|
||||
return (childpid == -1) ? C_ERR : C_OK;
|
||||
}
|
||||
|
||||
g_pserver->stat_fork_time = ustime()-start;
|
||||
g_pserver->stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / g_pserver->stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",g_pserver->stat_fork_time/1000);
|
||||
|
||||
serverLog(LL_NOTICE,"Background RDB transfer started");
|
||||
g_pserver->rdb_save_time_start = time(NULL);
|
||||
g_pserver->rdbThreadVars.rdb_child_thread = child;
|
||||
g_pserver->rdbThreadVars.fRdbThreadActive = true;
|
||||
g_pserver->rdb_child_type = RDB_CHILD_TYPE_SOCKET;
|
||||
updateDictResizePolicy();
|
||||
return C_OK; /* Unreached. */
|
||||
}
|
||||
|
||||
void saveCommand(client *c) {
|
||||
if (g_pserver->FRdbSaveInProgress()) {
|
||||
if (g_pserver->rdb_child_pid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
return;
|
||||
}
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(nullptr, rsiptr) == C_OK) {
|
||||
if (rdbSave(rsiptr) == C_OK) {
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReply(c,shared.err);
|
||||
@@ -2653,7 +2563,7 @@ void bgsaveCommand(client *c) {
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
|
||||
if (g_pserver->FRdbSaveInProgress()) {
|
||||
if (g_pserver->rdb_child_pid != -1) {
|
||||
addReplyError(c,"Background save already in progress");
|
||||
} else if (g_pserver->aof_child_pid != -1) {
|
||||
if (schedule) {
|
||||
@@ -2685,23 +2595,20 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
|
||||
rdbSaveInfo rsi_init = RDB_SAVE_INFO_INIT;
|
||||
*rsi = rsi_init;
|
||||
|
||||
memcpy(rsi->repl_id, g_pserver->replid, sizeof(g_pserver->replid));
|
||||
rsi->master_repl_offset = g_pserver->master_repl_offset;
|
||||
|
||||
/* If the instance is a master, we can populate the replication info
|
||||
* only when repl_backlog is not NULL. If the repl_backlog is NULL,
|
||||
* it means that the instance isn't in any replication chains. In this
|
||||
* scenario the replication info is useless, because when a replica
|
||||
* scenario the replication info is useless, because when a slave
|
||||
* connects to us, the NULL repl_backlog will trigger a full
|
||||
* synchronization, at the same time we will use a new replid and clear
|
||||
* replid2. */
|
||||
if (g_pserver->fActiveReplica || (!listLength(g_pserver->masters) && g_pserver->repl_backlog)) {
|
||||
/* Note that when g_pserver->replicaseldb is -1, it means that this master
|
||||
/* Note that when g_pserver->slaveseldb is -1, it means that this master
|
||||
* didn't apply any write commands after a full synchronization.
|
||||
* So we can let repl_stream_db be 0, this allows a restarted replica
|
||||
* So we can let repl_stream_db be 0, this allows a restarted slave
|
||||
* to reload replication ID/offset, it's safe because the next write
|
||||
* command must generate a SELECT statement. */
|
||||
rsi->repl_stream_db = g_pserver->replicaseldb == -1 ? 0 : g_pserver->replicaseldb;
|
||||
rsi->repl_stream_db = g_pserver->slaveseldb == -1 ? 0 : g_pserver->slaveseldb;
|
||||
return rsi;
|
||||
}
|
||||
|
||||
@@ -2712,7 +2619,7 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
|
||||
}
|
||||
struct redisMaster *miFirst = (redisMaster*)(listLength(g_pserver->masters) ? listNodeValue(listFirst(g_pserver->masters)) : NULL);
|
||||
|
||||
/* If the instance is a replica we need a connected master
|
||||
/* If the instance is a slave we need a connected master
|
||||
* in order to fetch the currently selected DB. */
|
||||
if (miFirst && miFirst->master) {
|
||||
rsi->repl_stream_db = miFirst->master->db->id;
|
||||
@@ -2720,7 +2627,7 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
|
||||
}
|
||||
|
||||
/* If we have a cached master we can use it in order to populate the
|
||||
* replication selected DB info inside the RDB file: the replica can
|
||||
* replication selected DB info inside the RDB file: the slave can
|
||||
* increment the master_repl_offset only from data arriving from the
|
||||
* master, so if we are disconnected the offset in the cached master
|
||||
* is valid. */
|
||||
|
||||
@@ -136,14 +136,13 @@ int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
|
||||
int rdbSaveObjectType(rio *rdb, robj_roptr o);
|
||||
int rdbLoadObjectType(rio *rdb);
|
||||
int rdbLoad(rdbSaveInfo *rsi);
|
||||
int rdbLoadFile(const char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveBackground(rdbSaveInfo *rsi);
|
||||
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(int childpid);
|
||||
int rdbSave(redisDbPersistentData **rgpdb, rdbSaveInfo *rsi);
|
||||
int rdbSaveFile(char *filename, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi);
|
||||
int rdbSaveFp(FILE *pf, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi);
|
||||
int rdbSaveS3(char *path, redisDbPersistentData **rgpdb, rdbSaveInfo *rsi);
|
||||
void rdbRemoveTempFile(pid_t childpid);
|
||||
int rdbSave(rdbSaveInfo *rsi);
|
||||
int rdbSaveFile(char *filename, rdbSaveInfo *rsi);
|
||||
int rdbSaveFp(FILE *pf, rdbSaveInfo *rsi);
|
||||
int rdbSaveS3(char *path, rdbSaveInfo *rsi);
|
||||
int rdbLoadS3(char *path, rdbSaveInfo *rsi);
|
||||
ssize_t rdbSaveObject(rio *rdb, robj_roptr o, robj *key);
|
||||
size_t rdbSavedObjectLen(robj *o);
|
||||
|
||||
@@ -152,7 +152,7 @@ typedef struct clusterNode {
|
||||
int port;
|
||||
sds name;
|
||||
int flags;
|
||||
sds replicate; /* Master ID if node is a replica */
|
||||
sds replicate; /* Master ID if node is a slave */
|
||||
int *slots;
|
||||
int slots_count;
|
||||
int current_slot_index;
|
||||
|
||||
@@ -124,8 +124,8 @@ extern "C" void freeClusterManager(void) {
|
||||
*
|
||||
* The score is calculated as follows:
|
||||
*
|
||||
* SAME_AS_MASTER = 10000 * each replica in the same IP of its master.
|
||||
* SAME_AS_SLAVE = 1 * each replica having the same IP as another replica
|
||||
* SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
|
||||
* SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
|
||||
of the same master.
|
||||
* FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
|
||||
*
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ typedef struct clusterManagerNode {
|
||||
time_t ping_recv;
|
||||
int flags;
|
||||
list *flags_str; /* Flags string representations */
|
||||
sds replicate; /* Master ID if node is a replica */
|
||||
sds replicate; /* Master ID if node is a slave */
|
||||
int dirty; /* Node has changes that can be flushed */
|
||||
uint8_t slots[CLUSTER_MANAGER_SLOTS];
|
||||
int slots_count;
|
||||
|
||||
+2
-2
@@ -71,9 +71,9 @@ extern "C" {
|
||||
#define REDISMODULE_CTX_FLAGS_MULTI (1<<1)
|
||||
/* The instance is a master */
|
||||
#define REDISMODULE_CTX_FLAGS_MASTER (1<<2)
|
||||
/* The instance is a replica */
|
||||
/* The instance is a slave */
|
||||
#define REDISMODULE_CTX_FLAGS_SLAVE (1<<3)
|
||||
/* The instance is read-only (usually meaning it's a replica as well) */
|
||||
/* The instance is read-only (usually meaning it's a slave as well) */
|
||||
#define REDISMODULE_CTX_FLAGS_READONLY (1<<4)
|
||||
/* The instance is running in cluster mode */
|
||||
#define REDISMODULE_CTX_FLAGS_CLUSTER (1<<5)
|
||||
|
||||
+241
-303
File diff suppressed because it is too large.
Load diff
-28
@@ -73,14 +73,6 @@ static size_t rioBufferRead(rio *r, void *buf, size_t len) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t rioConstBufferRead(rio *r, void *buf, size_t len) {
|
||||
if (r->io.buffer.len-r->io.buffer.pos < (off_t)len)
|
||||
return 0; /* not enough buffer to return len bytes. */
|
||||
memcpy(buf,r->io.buffer.ptr+r->io.buffer.pos,len);
|
||||
r->io.buffer.pos += len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Returns read/write position in buffer. */
|
||||
static off_t rioBufferTell(rio *r) {
|
||||
return r->io.buffer.pos;
|
||||
@@ -105,32 +97,12 @@ static const rio rioBufferIO = {
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
static const rio rioConstBufferIO = {
|
||||
rioConstBufferRead,
|
||||
nullptr,
|
||||
rioBufferTell,
|
||||
rioBufferFlush,
|
||||
NULL, /* update_checksum */
|
||||
0, /* current checksum */
|
||||
0, /* bytes read or written */
|
||||
0, /* read/write chunk size */
|
||||
{ { NULL, 0 } } /* union for io-specific vars */
|
||||
};
|
||||
|
||||
void rioInitWithBuffer(rio *r, sds s) {
|
||||
*r = rioBufferIO;
|
||||
r->io.buffer.ptr = s;
|
||||
r->io.buffer.pos = 0;
|
||||
}
|
||||
|
||||
void rioInitWithConstBuffer(rio *r, const void *buf, size_t cb)
|
||||
{
|
||||
*r = rioConstBufferIO;
|
||||
r->io.buffer.ptr = (sds)buf;
|
||||
r->io.buffer.pos = 0;
|
||||
r->io.buffer.len = cb;
|
||||
}
|
||||
|
||||
/* --------------------- Stdio file pointer implementation ------------------- */
|
||||
|
||||
/* Returns 1 or 0 for success/failure. */
|
||||
|
||||
@@ -70,7 +70,6 @@ struct _rio {
|
||||
struct {
|
||||
sds ptr;
|
||||
off_t pos;
|
||||
off_t len; // For const buffers only
|
||||
} buffer;
|
||||
/* Stdio file pointer target. */
|
||||
struct {
|
||||
@@ -131,7 +130,6 @@ static inline int rioFlush(rio *r) {
|
||||
|
||||
void rioInitWithFile(rio *r, FILE *fp);
|
||||
void rioInitWithBuffer(rio *r, sds s);
|
||||
void rioInitWithConstBuffer(rio *r, const void *rgch, size_t cch);
|
||||
void rioInitWithFdset(rio *r, int *fds, int numfds);
|
||||
|
||||
void rioFreeFdset(rio *r);
|
||||
|
||||
+1
-1
@@ -1479,7 +1479,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
* To do so we use a cache of SHA1s of scripts that we already propagated
|
||||
* as full EVAL, that's called the Replication Script Cache.
|
||||
*
|
||||
* For repliation, everytime a new replica attaches to the master, we need to
|
||||
* For repliation, everytime a new slave attaches to the master, we need to
|
||||
* flush our cache of scripts that can be replicated as EVALSHA, while
|
||||
* for AOF we need to do so every time we rewrite the AOF file. */
|
||||
if (evalsha && !g_pserver->lua_replicate_commands) {
|
||||
|
||||
@@ -93,9 +93,9 @@ public:
|
||||
return end();
|
||||
}
|
||||
|
||||
setiter end() const
|
||||
setiter end()
|
||||
{
|
||||
setiter itr(const_cast<semiorderedset<T,T_KEY,MEMMOVE_SAFE>*>(this));
|
||||
setiter itr(this);
|
||||
itr.idxPrimary = m_data.size();
|
||||
return itr;
|
||||
}
|
||||
|
||||
+89
-143
@@ -59,7 +59,6 @@
|
||||
#include <sys/socket.h>
|
||||
#include <algorithm>
|
||||
#include <uuid/uuid.h>
|
||||
#include <mutex>
|
||||
#include "aelocker.h"
|
||||
|
||||
int g_fTestMode = false;
|
||||
@@ -154,7 +153,7 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
|
||||
*
|
||||
* ok-loading: Allow the command while loading the database.
|
||||
*
|
||||
* ok-stale: Allow the command while a replica has stale data but is not
|
||||
* ok-stale: Allow the command while a slave has stale data but is not
|
||||
* allowed to serve this data. Normally no command is accepted
|
||||
* in this condition but just a few.
|
||||
*
|
||||
@@ -229,10 +228,6 @@ struct redisCommand redisCommandTable[] = {
|
||||
"write @keyspace",
|
||||
0,NULL,1,-1,1,0,0,0},
|
||||
|
||||
{"expdel",delCommand,-2,
|
||||
"write @keyspace",
|
||||
0,NULL,1,-1,1,0,0,0},
|
||||
|
||||
{"unlink",unlinkCommand,-2,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,-1,1,0,0,0},
|
||||
@@ -623,11 +618,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"expiremember", expireMemberCommand, -4,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"expirememberat", expireMemberAtCommand, 4,
|
||||
{"expiremember", expireMemberCommand, 4,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
@@ -734,7 +725,7 @@ struct redisCommand redisCommandTable[] = {
|
||||
"admin no-script",
|
||||
0,NULL,0,0,0,0,0,0},
|
||||
|
||||
{"ttl",ttlCommand,-2,
|
||||
{"ttl",ttlCommand,2,
|
||||
"read-only fast random @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
@@ -742,11 +733,11 @@ struct redisCommand redisCommandTable[] = {
|
||||
"read-only fast @keyspace",
|
||||
0,NULL,1,-1,1,0,0,0},
|
||||
|
||||
{"pttl",pttlCommand,-2,
|
||||
{"pttl",pttlCommand,2,
|
||||
"read-only fast random @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
{"persist",persistCommand,-2,
|
||||
{"persist",persistCommand,2,
|
||||
"write fast @keyspace",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
|
||||
@@ -876,6 +867,10 @@ struct redisCommand redisCommandTable[] = {
|
||||
"write use-memory @bitmap",
|
||||
0,NULL,2,-1,1,0,0,0},
|
||||
|
||||
{"bitoprange",bitopRangeCommand,-5,
|
||||
"write use-memory @bitmap",
|
||||
0,NULL,2,-1,1,0,0,0},
|
||||
|
||||
{"bitcount",bitcountCommand,-2,
|
||||
"read-only @bitmap",
|
||||
0,NULL,1,1,1,0,0,0},
|
||||
@@ -1179,21 +1174,6 @@ int dictSdsKeyCompare(void *privdata, const void *key1,
|
||||
return memcmp(key1, key2, l1) == 0;
|
||||
}
|
||||
|
||||
void dictSdsNOPDestructor(void *, void *) {}
|
||||
|
||||
void dictDbKeyDestructor(void *privdata, void *key)
|
||||
{
|
||||
redisDbPersistentData *owner = (redisDbPersistentData*)privdata;
|
||||
serverAssert(owner != nullptr);
|
||||
if (owner->m_spdbSnapshot != nullptr)
|
||||
{
|
||||
dictEntry *deSnapshot = dictFind(owner->m_spdbSnapshot->m_pdict, key);
|
||||
if (deSnapshot && (key == dictGetKey(deSnapshot)))
|
||||
return; // don't free, it's now owned by the snapshot
|
||||
}
|
||||
sdsfree((sds)key);
|
||||
}
|
||||
|
||||
/* A case insensitive version used for the command lookup table and other
|
||||
* places where case insensitive non binary-safe comparison is needed. */
|
||||
int dictSdsKeyCaseCompare(void *privdata, const void *key1,
|
||||
@@ -1329,19 +1309,10 @@ dictType dbDictType = {
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictSdsKeyCompare, /* key compare */
|
||||
dictDbKeyDestructor, /* key destructor */
|
||||
dictSdsDestructor, /* key destructor */
|
||||
dictObjectDestructor /* val destructor */
|
||||
};
|
||||
|
||||
dictType dbSnapshotDictType = {
|
||||
dictSdsHash,
|
||||
NULL,
|
||||
NULL,
|
||||
dictSdsKeyCompare,
|
||||
dictSdsNOPDestructor,
|
||||
dictObjectDestructor,
|
||||
};
|
||||
|
||||
/* g_pserver->lua_scripts sha (as sds string) -> scripts (as robj) cache. */
|
||||
dictType shaScriptObjectDictType = {
|
||||
dictSdsCaseHash, /* hash function */
|
||||
@@ -1463,7 +1434,8 @@ int htNeedsResize(dict *dict) {
|
||||
/* If the percentage of used slots in the HT reaches HASHTABLE_MIN_FILL
|
||||
* we resize the hash table to save memory */
|
||||
void tryResizeHashTables(int dbid) {
|
||||
g_pserver->db[dbid].tryResize();
|
||||
if (htNeedsResize(g_pserver->db[dbid].pdict))
|
||||
dictResize(g_pserver->db[dbid].pdict);
|
||||
}
|
||||
|
||||
/* Our hash table implementation performs rehashing incrementally while
|
||||
@@ -1473,10 +1445,10 @@ void tryResizeHashTables(int dbid) {
|
||||
*
|
||||
* The function returns 1 if some rehashing was performed, otherwise 0
|
||||
* is returned. */
|
||||
int redisDbPersistentData::incrementallyRehash() {
|
||||
int incrementallyRehash(int dbid) {
|
||||
/* Keys dictionary */
|
||||
if (dictIsRehashing(m_pdict)) {
|
||||
dictRehashMilliseconds(m_pdict,1);
|
||||
if (dictIsRehashing(g_pserver->db[dbid].pdict)) {
|
||||
dictRehashMilliseconds(g_pserver->db[dbid].pdict,1);
|
||||
return 1; /* already used our millisecond for this loop... */
|
||||
}
|
||||
return 0;
|
||||
@@ -1489,7 +1461,7 @@ int redisDbPersistentData::incrementallyRehash() {
|
||||
* for dict.c to resize the hash tables accordingly to the fact we have o not
|
||||
* running childs. */
|
||||
void updateDictResizePolicy(void) {
|
||||
if (!g_pserver->FRdbSaveInProgress() && g_pserver->aof_child_pid == -1)
|
||||
if (g_pserver->rdb_child_pid == -1 && g_pserver->aof_child_pid == -1)
|
||||
dictEnableResize();
|
||||
else
|
||||
dictDisableResize();
|
||||
@@ -1736,7 +1708,7 @@ void databasesCron(void) {
|
||||
/* Perform hash tables rehashing if needed, but only if there are no
|
||||
* other processes saving the DB on disk. Otherwise rehashing is bad
|
||||
* as will cause a lot of copy-on-write of memory pages. */
|
||||
if (!g_pserver->FRdbSaveInProgress() && g_pserver->aof_child_pid == -1) {
|
||||
if (g_pserver->rdb_child_pid == -1 && g_pserver->aof_child_pid == -1) {
|
||||
/* 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
|
||||
* cron loop iteration. */
|
||||
@@ -1757,7 +1729,7 @@ void databasesCron(void) {
|
||||
/* Rehash */
|
||||
if (g_pserver->activerehashing) {
|
||||
for (j = 0; j < dbs_per_call; j++) {
|
||||
int work_done = g_pserver->db[rehash_db].incrementallyRehash();
|
||||
int work_done = incrementallyRehash(rehash_db);
|
||||
if (work_done) {
|
||||
/* If the function did some work, stop here, we'll do
|
||||
* more at the next cron loop. */
|
||||
@@ -1916,9 +1888,9 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
long long size, used, vkeys;
|
||||
|
||||
size = g_pserver->db[j].slots();
|
||||
used = g_pserver->db[j].size();
|
||||
vkeys = g_pserver->db[j].expireSize();
|
||||
size = dictSlots(g_pserver->db[j].pdict);
|
||||
used = dictSize(g_pserver->db[j].pdict);
|
||||
vkeys = g_pserver->db[j].setexpire->size();
|
||||
if (used || vkeys) {
|
||||
serverLog(LL_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size);
|
||||
/* dictPrintStats(g_pserver->dict); */
|
||||
@@ -1945,37 +1917,20 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
|
||||
/* Start a scheduled AOF rewrite if this was requested by the user while
|
||||
* a BGSAVE was in progress. */
|
||||
if (!g_pserver->FRdbSaveInProgress() && g_pserver->aof_child_pid == -1 &&
|
||||
if (g_pserver->rdb_child_pid == -1 && g_pserver->aof_child_pid == -1 &&
|
||||
g_pserver->aof_rewrite_scheduled)
|
||||
{
|
||||
rewriteAppendOnlyFileBackground();
|
||||
}
|
||||
|
||||
/* Check if a background saving or AOF rewrite in progress terminated. */
|
||||
if (g_pserver->FRdbSaveInProgress() || g_pserver->aof_child_pid != -1 ||
|
||||
if (g_pserver->rdb_child_pid != -1 || g_pserver->aof_child_pid != -1 ||
|
||||
ldbPendingChildren())
|
||||
{
|
||||
int statloc;
|
||||
pid_t pid;
|
||||
|
||||
if (g_pserver->FRdbSaveInProgress())
|
||||
{
|
||||
void *rval = nullptr;
|
||||
if (pthread_tryjoin_np(g_pserver->rdbThreadVars.rdb_child_thread, &rval))
|
||||
{
|
||||
if (errno != EBUSY && errno != EAGAIN)
|
||||
serverLog(LL_WARNING, "Error joining the background RDB save thread: %s\n", strerror(errno));
|
||||
}
|
||||
else
|
||||
{
|
||||
int exitcode = (int)reinterpret_cast<ptrdiff_t>(rval);
|
||||
backgroundSaveDoneHandler(exitcode,0);
|
||||
if (exitcode == 0) receiveChildInfo();
|
||||
updateDictResizePolicy();
|
||||
closeChildInfoPipe();
|
||||
}
|
||||
}
|
||||
else if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
|
||||
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = 0;
|
||||
|
||||
@@ -1983,9 +1938,13 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
|
||||
if (pid == -1) {
|
||||
serverLog(LL_WARNING,"wait3() returned an error: %s. "
|
||||
"aof_child_pid = %d",
|
||||
"rdb_child_pid = %d, aof_child_pid = %d",
|
||||
strerror(errno),
|
||||
(int) g_pserver->rdb_child_pid,
|
||||
(int) g_pserver->aof_child_pid);
|
||||
} else if (pid == g_pserver->rdb_child_pid) {
|
||||
backgroundSaveDoneHandler(exitcode,bysignal);
|
||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||
} else if (pid == g_pserver->aof_child_pid) {
|
||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
||||
if (!bysignal && exitcode == 0) receiveChildInfo();
|
||||
@@ -2026,7 +1985,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
|
||||
/* Trigger an AOF rewrite if needed. */
|
||||
if (g_pserver->aof_state == AOF_ON &&
|
||||
!g_pserver->FRdbSaveInProgress() &&
|
||||
g_pserver->rdb_child_pid == -1 &&
|
||||
g_pserver->aof_child_pid == -1 &&
|
||||
g_pserver->aof_rewrite_perc &&
|
||||
g_pserver->aof_current_size > g_pserver->aof_rewrite_min_size)
|
||||
@@ -2082,7 +2041,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
* Note: this code must be after the replicationCron() call above so
|
||||
* make sure when refactoring this file to keep this order. This is useful
|
||||
* because we want to give priority to RDB savings for replication. */
|
||||
if (!g_pserver->FRdbSaveInProgress() && g_pserver->aof_child_pid == -1 &&
|
||||
if (g_pserver->rdb_child_pid == -1 && g_pserver->aof_child_pid == -1 &&
|
||||
g_pserver->rdb_bgsave_scheduled &&
|
||||
(g_pserver->unixtime-g_pserver->lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
|
||||
g_pserver->lastbgsave_status == C_OK))
|
||||
@@ -2146,7 +2105,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
|
||||
argv[0] = createStringObject("REPLCONF",8);
|
||||
argv[1] = createStringObject("GETACK",6);
|
||||
argv[2] = createStringObject("*",1); /* Not used argument. */
|
||||
replicationFeedSlaves(g_pserver->slaves, g_pserver->replicaseldb, argv, 3);
|
||||
replicationFeedSlaves(g_pserver->slaves, g_pserver->slaveseldb, argv, 3);
|
||||
decrRefCount(argv[0]);
|
||||
decrRefCount(argv[1]);
|
||||
decrRefCount(argv[2]);
|
||||
@@ -2551,7 +2510,7 @@ void initServerConfig(void) {
|
||||
|
||||
/* By default we want scripts to be always replicated by effects
|
||||
* (single commands executed by the script), and not by sending the
|
||||
* script to the replica / AOF. This is the new way starting from
|
||||
* script to the slave / AOF. This is the new way starting from
|
||||
* Redis 5. However it is possible to revert it via redis.conf. */
|
||||
g_pserver->lua_always_replicate_commands = 1;
|
||||
|
||||
@@ -2744,7 +2703,7 @@ void checkTcpBacklogSettings(void) {
|
||||
* impossible to bind, or no bind addresses were specified in the server
|
||||
* configuration but the function is not able to bind * for at least
|
||||
* one of the IPv4 or IPv6 protocols. */
|
||||
int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListen) {
|
||||
int listenToPort(int port, int *fds, int *count, int fReusePort) {
|
||||
int j;
|
||||
|
||||
/* Force binding of 0.0.0.0 if no bind address is specified, always
|
||||
@@ -2756,7 +2715,7 @@ int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListe
|
||||
/* Bind * for both IPv6 and IPv4, we enter here only if
|
||||
* g_pserver->bindaddr_count == 0. */
|
||||
fds[*count] = anetTcp6Server(serverTL->neterr,port,NULL,
|
||||
g_pserver->tcp_backlog, fReusePort, fFirstListen);
|
||||
g_pserver->tcp_backlog, fReusePort);
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
@@ -2768,7 +2727,7 @@ int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListe
|
||||
if (*count == 1 || unsupported) {
|
||||
/* Bind the IPv4 address as well. */
|
||||
fds[*count] = anetTcpServer(serverTL->neterr,port,NULL,
|
||||
g_pserver->tcp_backlog, fReusePort, fFirstListen);
|
||||
g_pserver->tcp_backlog, fReusePort);
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
@@ -2784,11 +2743,11 @@ int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListe
|
||||
} else if (strchr(g_pserver->bindaddr[j],':')) {
|
||||
/* Bind IPv6 address. */
|
||||
fds[*count] = anetTcp6Server(serverTL->neterr,port,g_pserver->bindaddr[j],
|
||||
g_pserver->tcp_backlog, fReusePort, fFirstListen);
|
||||
g_pserver->tcp_backlog, fReusePort);
|
||||
} else {
|
||||
/* Bind IPv4 address. */
|
||||
fds[*count] = anetTcpServer(serverTL->neterr,port,g_pserver->bindaddr[j],
|
||||
g_pserver->tcp_backlog, fReusePort, fFirstListen);
|
||||
g_pserver->tcp_backlog, fReusePort);
|
||||
}
|
||||
if (fds[*count] == ANET_ERR) {
|
||||
serverLog(LL_WARNING,
|
||||
@@ -2850,7 +2809,7 @@ static void initNetworkingThread(int iel, int fReusePort)
|
||||
if (fReusePort || (iel == IDX_EVENT_LOOP_MAIN))
|
||||
{
|
||||
if (g_pserver->port != 0 &&
|
||||
listenToPort(g_pserver->port,g_pserver->rgthreadvar[iel].ipfd,&g_pserver->rgthreadvar[iel].ipfd_count, fReusePort, (iel == IDX_EVENT_LOOP_MAIN)) == C_ERR)
|
||||
listenToPort(g_pserver->port,g_pserver->rgthreadvar[iel].ipfd,&g_pserver->rgthreadvar[iel].ipfd_count, fReusePort) == C_ERR)
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
@@ -2967,8 +2926,16 @@ void initServer(void) {
|
||||
|
||||
/* Create the Redis databases, and initialize other internal state. */
|
||||
for (int j = 0; j < cserver.dbnum; j++) {
|
||||
new (&g_pserver->db[j]) redisDb;
|
||||
g_pserver->db[j].initialize(j);
|
||||
g_pserver->db[j].pdict = dictCreate(&dbDictType,NULL);
|
||||
g_pserver->db[j].setexpire = new(MALLOC_LOCAL) expireset();
|
||||
g_pserver->db[j].expireitr = g_pserver->db[j].setexpire->end();
|
||||
g_pserver->db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
|
||||
g_pserver->db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
|
||||
g_pserver->db[j].watched_keys = dictCreate(&keylistDictType,NULL);
|
||||
g_pserver->db[j].id = j;
|
||||
g_pserver->db[j].avg_ttl = 0;
|
||||
g_pserver->db[j].last_expire_set = 0;
|
||||
g_pserver->db[j].defrag_later = listCreate();
|
||||
}
|
||||
|
||||
/* Fixup Master Client Database */
|
||||
@@ -2992,7 +2959,7 @@ void initServer(void) {
|
||||
cserver.pid = getpid();
|
||||
g_pserver->clients_index = raxNew();
|
||||
g_pserver->clients_to_close = listCreate();
|
||||
g_pserver->replicaseldb = -1; /* Force to emit the first SELECT command. */
|
||||
g_pserver->slaveseldb = -1; /* Force to emit the first SELECT command. */
|
||||
g_pserver->ready_keys = listCreate();
|
||||
g_pserver->clients_waiting_acks = listCreate();
|
||||
g_pserver->get_ack_from_slaves = 0;
|
||||
@@ -3008,7 +2975,7 @@ void initServer(void) {
|
||||
listSetFreeMethod(g_pserver->pubsub_patterns,freePubsubPattern);
|
||||
listSetMatchMethod(g_pserver->pubsub_patterns,listMatchPubsubPattern);
|
||||
g_pserver->cronloops = 0;
|
||||
g_pserver->rdbThreadVars.fRdbThreadActive = false;
|
||||
g_pserver->rdb_child_pid = -1;
|
||||
g_pserver->aof_child_pid = -1;
|
||||
g_pserver->rdb_child_type = RDB_CHILD_TYPE_NONE;
|
||||
g_pserver->rdb_bgsave_scheduled = 0;
|
||||
@@ -3383,7 +3350,16 @@ void call(client *c, int flags) {
|
||||
/* Call the command. */
|
||||
dirty = g_pserver->dirty;
|
||||
start = ustime();
|
||||
c->cmd->proc(c);
|
||||
try
|
||||
{
|
||||
c->cmd->proc(c);
|
||||
}
|
||||
catch (robj *obj)
|
||||
{
|
||||
addReply(c, obj);
|
||||
decrRefCount(obj);
|
||||
}
|
||||
|
||||
serverTL->commandsExecuted++;
|
||||
duration = ustime()-start;
|
||||
dirty = g_pserver->dirty-dirty;
|
||||
@@ -3619,8 +3595,8 @@ int processCommand(client *c, int callFlags) {
|
||||
* propagation of DELs due to eviction. */
|
||||
if (g_pserver->maxmemory && !g_pserver->lua_timedout) {
|
||||
int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
|
||||
/* freeMemoryIfNeeded may flush replica output buffers. This may result
|
||||
* into a replica, that may be the active client, to be freed. */
|
||||
/* freeMemoryIfNeeded may flush slave output buffers. This may result
|
||||
* into a slave, that may be the active client, to be freed. */
|
||||
if (serverTL->current_client == NULL) return C_ERR;
|
||||
|
||||
/* It was impossible to free enough memory, and the command the client
|
||||
@@ -3667,7 +3643,7 @@ int processCommand(client *c, int callFlags) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* Don't accept write commands if this is a read only replica. But
|
||||
/* Don't accept write commands if this is a read only slave. But
|
||||
* accept write commands if this is our master. */
|
||||
if (listLength(g_pserver->masters) && g_pserver->repl_slave_ro &&
|
||||
!(c->flags & CLIENT_MASTER) &&
|
||||
@@ -3690,7 +3666,7 @@ int processCommand(client *c, int callFlags) {
|
||||
}
|
||||
|
||||
/* Only allow commands with flag "t", such as INFO, SLAVEOF and so on,
|
||||
* when replica-serve-stale-data is no and we are a replica with a broken
|
||||
* when slave-serve-stale-data is no and we are a slave with a broken
|
||||
* link with master. */
|
||||
if (FBrokenLinkToMaster() &&
|
||||
g_pserver->repl_serve_stale_data == 0 &&
|
||||
@@ -3733,12 +3709,7 @@ int processCommand(client *c, int callFlags) {
|
||||
queueMultiCommand(c);
|
||||
addReply(c,shared.queued);
|
||||
} else {
|
||||
std::unique_lock<decltype(c->db->lock)> ulock(c->db->lock);
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
g_pserver->db[idb].trackChanges();
|
||||
call(c,callFlags);
|
||||
for (int idb = 0; idb < cserver.dbnum; ++idb)
|
||||
g_pserver->db[idb].processChanges();
|
||||
c->woff = g_pserver->master_repl_offset;
|
||||
if (listLength(g_pserver->ready_keys))
|
||||
handleClientsBlockedOnKeys();
|
||||
@@ -3779,7 +3750,7 @@ int prepareForShutdown(int flags) {
|
||||
/* Kill the saving child if there is a background saving in progress.
|
||||
We want to avoid race conditions, for instance our saving child may
|
||||
overwrite the synchronous saving did by SHUTDOWN. */
|
||||
if (g_pserver->FRdbSaveInProgress()) {
|
||||
if (g_pserver->rdb_child_pid != -1) {
|
||||
serverLog(LL_WARNING,"There is a child saving an .rdb. Killing it!");
|
||||
killRDBChild();
|
||||
}
|
||||
@@ -3810,7 +3781,7 @@ int prepareForShutdown(int flags) {
|
||||
/* Snapshotting. Perform a SYNC SAVE and exit */
|
||||
rdbSaveInfo rsi, *rsiptr;
|
||||
rsiptr = rdbPopulateSaveInfo(&rsi);
|
||||
if (rdbSave(nullptr, rsiptr) != C_OK) {
|
||||
if (rdbSave(rsiptr) != C_OK) {
|
||||
/* Ooops.. error saving! The best we can do is to continue
|
||||
* operating. Note that if there was a background saving process,
|
||||
* in the next cron() Redis will be notified that the background
|
||||
@@ -3827,7 +3798,7 @@ int prepareForShutdown(int flags) {
|
||||
unlink(cserver.pidfile);
|
||||
}
|
||||
|
||||
/* Best effort flush of replica output buffers, so that we hopefully
|
||||
/* Best effort flush of slave output buffers, so that we hopefully
|
||||
* send them pending writes. */
|
||||
flushSlavesOutputBuffers();
|
||||
|
||||
@@ -4139,12 +4110,10 @@ sds genRedisInfoString(const char *section) {
|
||||
"connected_clients:%lu\r\n"
|
||||
"client_recent_max_input_buffer:%zu\r\n"
|
||||
"client_recent_max_output_buffer:%zu\r\n"
|
||||
"blocked_clients:%d\r\n"
|
||||
"current_client_thread:%d\r\n",
|
||||
"blocked_clients:%d\r\n",
|
||||
listLength(g_pserver->clients)-listLength(g_pserver->slaves),
|
||||
maxin, maxout,
|
||||
g_pserver->blocked_clients,
|
||||
static_cast<int>(serverTL - g_pserver->rgthreadvar));
|
||||
g_pserver->blocked_clients);
|
||||
for (int ithread = 0; ithread < cserver.cthreads; ++ithread)
|
||||
{
|
||||
info = sdscatprintf(info,
|
||||
@@ -4293,12 +4262,12 @@ sds genRedisInfoString(const char *section) {
|
||||
"aof_last_cow_size:%zu\r\n",
|
||||
g_pserver->loading,
|
||||
g_pserver->dirty,
|
||||
g_pserver->FRdbSaveInProgress(),
|
||||
g_pserver->rdb_child_pid != -1,
|
||||
(intmax_t)g_pserver->lastsave,
|
||||
(g_pserver->lastbgsave_status == C_OK) ? "ok" : "err",
|
||||
(intmax_t)g_pserver->rdb_save_time_last,
|
||||
(intmax_t)(g_pserver->FRdbSaveInProgress() ?
|
||||
time(NULL)-g_pserver->rdb_save_time_start : -1),
|
||||
(intmax_t)((g_pserver->rdb_child_pid == -1) ?
|
||||
-1 : time(NULL)-g_pserver->rdb_save_time_start),
|
||||
g_pserver->stat_rdb_cow_bytes,
|
||||
g_pserver->aof_state != AOF_OFF,
|
||||
g_pserver->aof_child_pid != -1,
|
||||
@@ -4505,18 +4474,18 @@ sds genRedisInfoString(const char *section) {
|
||||
|
||||
listRewind(g_pserver->slaves,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
client *replica = (client*)listNodeValue(ln);
|
||||
client *slave = (client*)listNodeValue(ln);
|
||||
const char *state = NULL;
|
||||
char ip[NET_IP_STR_LEN], *slaveip = replica->slave_ip;
|
||||
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
|
||||
int port;
|
||||
long lag = 0;
|
||||
|
||||
if (slaveip[0] == '\0') {
|
||||
if (anetPeerToString(replica->fd,ip,sizeof(ip),&port) == -1)
|
||||
if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) == -1)
|
||||
continue;
|
||||
slaveip = ip;
|
||||
}
|
||||
switch(replica->replstate) {
|
||||
switch(slave->replstate) {
|
||||
case SLAVE_STATE_WAIT_BGSAVE_START:
|
||||
case SLAVE_STATE_WAIT_BGSAVE_END:
|
||||
state = "wait_bgsave";
|
||||
@@ -4529,14 +4498,14 @@ sds genRedisInfoString(const char *section) {
|
||||
break;
|
||||
}
|
||||
if (state == NULL) continue;
|
||||
if (replica->replstate == SLAVE_STATE_ONLINE)
|
||||
lag = time(NULL) - replica->repl_ack_time;
|
||||
if (slave->replstate == SLAVE_STATE_ONLINE)
|
||||
lag = time(NULL) - slave->repl_ack_time;
|
||||
|
||||
info = sdscatprintf(info,
|
||||
"slave%d:ip=%s,port=%d,state=%s,"
|
||||
"offset=%lld,lag=%ld\r\n",
|
||||
slaveid,slaveip,replica->slave_listening_port,state,
|
||||
(replica->repl_ack_off + replica->reploff_skipped), lag);
|
||||
slaveid,slaveip,slave->slave_listening_port,state,
|
||||
(slave->repl_ack_off + slave->reploff_skipped), lag);
|
||||
slaveid++;
|
||||
}
|
||||
}
|
||||
@@ -4614,8 +4583,8 @@ sds genRedisInfoString(const char *section) {
|
||||
for (j = 0; j < cserver.dbnum; j++) {
|
||||
long long keys, vkeys;
|
||||
|
||||
keys = g_pserver->db[j].size();
|
||||
vkeys = g_pserver->db[j].expireSize();
|
||||
keys = dictSize(g_pserver->db[j].pdict);
|
||||
vkeys = g_pserver->db[j].setexpire->size();
|
||||
|
||||
// Adjust TTL by the current time
|
||||
g_pserver->db[j].avg_ttl -= (g_pserver->mstime - g_pserver->db[j].last_expire_set);
|
||||
@@ -4644,7 +4613,7 @@ void infoCommand(client *c) {
|
||||
}
|
||||
|
||||
void monitorCommand(client *c) {
|
||||
/* ignore MONITOR if already replica or in monitor mode */
|
||||
/* ignore MONITOR if already slave or in monitor mode */
|
||||
serverAssert(GlobalLocksAcquired());
|
||||
if (c->flags & CLIENT_SLAVE) return;
|
||||
|
||||
@@ -4794,7 +4763,7 @@ static void sigShutdownHandler(int sig) {
|
||||
* on disk. */
|
||||
if (g_pserver->shutdown_asap && sig == SIGINT) {
|
||||
serverLogFromHandler(LL_WARNING, "You insist... exiting now.");
|
||||
rdbRemoveTempFile(g_pserver->rdbThreadVars.tmpfileNum);
|
||||
rdbRemoveTempFile(getpid());
|
||||
exit(1); /* Exit with an error since this was not a clean shutdown. */
|
||||
} else if (g_pserver->loading) {
|
||||
serverLogFromHandler(LL_WARNING, "Received shutdown signal during loading, exiting now.");
|
||||
@@ -4871,7 +4840,7 @@ void loadDataFromDisk(void) {
|
||||
while ((ln = listNext(&li)))
|
||||
{
|
||||
redisMaster *mi = (redisMaster*)listNodeValue(ln);
|
||||
/* If we are a replica, create a cached master from this
|
||||
/* If we are a slave, create a cached master from this
|
||||
* information, in order to allow partial resynchronizations
|
||||
* with masters. */
|
||||
replicationCacheMasterUsingMyself(mi);
|
||||
@@ -4891,12 +4860,6 @@ void redisOutOfMemoryHandler(size_t allocation_size) {
|
||||
serverPanic("Redis aborting for OUT OF MEMORY");
|
||||
}
|
||||
|
||||
void fuzzOutOfMemoryHandler(size_t allocation_size) {
|
||||
serverLog(LL_WARNING,"Out Of Memory allocating %zu bytes!",
|
||||
allocation_size);
|
||||
exit(EXIT_FAILURE); // don't crash because it causes false positives
|
||||
}
|
||||
|
||||
void redisSetProcTitle(const char *title) {
|
||||
#ifdef USE_SETPROCTITLE
|
||||
const char *server_mode = "";
|
||||
@@ -5019,7 +4982,7 @@ void incrementMvccTstamp()
|
||||
{
|
||||
uint64_t msPrev;
|
||||
__atomic_load(&g_pserver->mvcc_tstamp, &msPrev, __ATOMIC_ACQUIRE);
|
||||
msPrev >>= MVCC_MS_SHIFT; // convert to milliseconds
|
||||
msPrev >>= 20; // convert to milliseconds
|
||||
|
||||
long long mst;
|
||||
__atomic_load(&g_pserver->mstime, &mst, __ATOMIC_RELAXED);
|
||||
@@ -5029,7 +4992,7 @@ void incrementMvccTstamp()
|
||||
}
|
||||
else
|
||||
{
|
||||
atomicSet(g_pserver->mvcc_tstamp, ((uint64_t)g_pserver->mstime) << MVCC_MS_SHIFT);
|
||||
atomicSet(g_pserver->mvcc_tstamp, ((uint64_t)g_pserver->mstime) << 20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5251,23 +5214,6 @@ int main(int argc, char **argv) {
|
||||
#endif
|
||||
moduleLoadFromQueue();
|
||||
ACLLoadUsersAtStartup();
|
||||
|
||||
// special case of FUZZING load from stdin then quit
|
||||
if (argc > 1 && strstr(argv[1],"rdbfuzz-mode") != NULL)
|
||||
{
|
||||
zmalloc_set_oom_handler(fuzzOutOfMemoryHandler);
|
||||
#ifdef __AFL_HAVE_MANUAL_CONTROL
|
||||
__AFL_INIT();
|
||||
#endif
|
||||
rio rdb;
|
||||
rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
|
||||
startLoading(stdin);
|
||||
rioInitWithFile(&rdb,stdin);
|
||||
rdbLoadRio(&rdb,&rsi,0);
|
||||
stopLoading();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
loadDataFromDisk();
|
||||
if (g_pserver->cluster_enabled) {
|
||||
if (verifyClusterConfigWithData() == C_ERR) {
|
||||
@@ -5304,7 +5250,7 @@ int main(int argc, char **argv) {
|
||||
|
||||
aeReleaseLock(); //Finally we can dump the lock
|
||||
moduleReleaseGIL(true);
|
||||
|
||||
|
||||
serverAssert(cserver.cthreads > 0 && cserver.cthreads <= MAX_EVENT_LOOPS);
|
||||
pthread_t rgthread[MAX_EVENT_LOOPS];
|
||||
for (int iel = 0; iel < cserver.cthreads; ++iel)
|
||||
|
||||
+74
-281
@@ -42,7 +42,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <cmath>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
@@ -55,7 +54,6 @@
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
@@ -91,14 +89,14 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#include "sha1.h"
|
||||
#include "endianconv.h"
|
||||
#include "crc64.h"
|
||||
#include "IStorage.h"
|
||||
|
||||
extern int g_fTestMode;
|
||||
|
||||
struct redisObject;
|
||||
class robj_roptr
|
||||
{
|
||||
const redisObject *m_ptr;
|
||||
protected:
|
||||
const redisObject *m_ptr = nullptr;
|
||||
|
||||
public:
|
||||
robj_roptr()
|
||||
@@ -132,11 +130,6 @@ public:
|
||||
return m_ptr;
|
||||
}
|
||||
|
||||
const redisObject& operator*() const
|
||||
{
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
bool operator!() const
|
||||
{
|
||||
return !m_ptr;
|
||||
@@ -152,6 +145,45 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void incrRefCount(robj_roptr o);
|
||||
void decrRefCount(robj_roptr o);
|
||||
class robj_rorefptr : public robj_roptr
|
||||
{
|
||||
public:
|
||||
robj_rorefptr() = default;
|
||||
robj_rorefptr(const redisObject *ref)
|
||||
: robj_roptr(ref)
|
||||
{}
|
||||
|
||||
robj_rorefptr(const robj_rorefptr& other)
|
||||
: robj_roptr(other)
|
||||
{
|
||||
if (m_ptr)
|
||||
incrRefCount(m_ptr);
|
||||
}
|
||||
robj_rorefptr(robj_rorefptr&& other)
|
||||
: robj_roptr(other)
|
||||
{}
|
||||
|
||||
~robj_rorefptr()
|
||||
{
|
||||
if (m_ptr)
|
||||
decrRefCount(m_ptr);
|
||||
}
|
||||
|
||||
robj_rorefptr &operator=(robj_rorefptr &other)
|
||||
{
|
||||
robj_roptr::operator=(other.m_ptr);
|
||||
if (m_ptr)
|
||||
incrRefCount(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
using robj_roptr::operator=;
|
||||
using robj_roptr::operator==;
|
||||
using robj_roptr::operator->;
|
||||
};
|
||||
|
||||
/* Error codes */
|
||||
#define C_OK 0
|
||||
#define C_ERR -1
|
||||
@@ -339,7 +371,7 @@ public:
|
||||
/* Client flags */
|
||||
#define CLIENT_SLAVE (1<<0) /* This client is a repliaca */
|
||||
#define CLIENT_MASTER (1<<1) /* This client is a master */
|
||||
#define CLIENT_MONITOR (1<<2) /* This client is a replica monitor, see MONITOR */
|
||||
#define CLIENT_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */
|
||||
#define CLIENT_MULTI (1<<3) /* This client is in a MULTI context */
|
||||
#define CLIENT_BLOCKED (1<<4) /* The client is waiting in a blocking operation */
|
||||
#define CLIENT_DIRTY_CAS (1<<5) /* Watched keys modified. EXEC will fail. */
|
||||
@@ -403,7 +435,7 @@ public:
|
||||
#define CLIENT_TYPE_MASTER 3 /* Master. */
|
||||
#define CLIENT_TYPE_OBUF_COUNT 3 /* Number of clients to expose to output
|
||||
buffer configuration. Just the first
|
||||
three: normal, replica, pubsub. */
|
||||
three: normal, slave, pubsub. */
|
||||
|
||||
/* Slave replication state. Used in g_pserver->repl_state for slaves to remember
|
||||
* what to do next. */
|
||||
@@ -429,12 +461,12 @@ public:
|
||||
#define REPL_STATE_CONNECTED 17 /* Connected to master */
|
||||
|
||||
/* State of slaves from the POV of the master. Used in client->replstate.
|
||||
* In SEND_BULK and ONLINE state the replica receives new updates
|
||||
* In SEND_BULK and ONLINE state the slave receives new updates
|
||||
* in its output queue. In the WAIT_BGSAVE states instead the server is waiting
|
||||
* to start the next background saving in order to send updates to it. */
|
||||
#define SLAVE_STATE_WAIT_BGSAVE_START 6 /* We need to produce a new RDB file. */
|
||||
#define SLAVE_STATE_WAIT_BGSAVE_END 7 /* Waiting RDB file creation to finish. */
|
||||
#define SLAVE_STATE_SEND_BULK 8 /* Sending RDB file to replica. */
|
||||
#define SLAVE_STATE_SEND_BULK 8 /* Sending RDB file to slave. */
|
||||
#define SLAVE_STATE_ONLINE 9 /* RDB file transmitted, sending just updates. */
|
||||
|
||||
/* Slave capabilities. */
|
||||
@@ -442,7 +474,7 @@ public:
|
||||
#define SLAVE_CAPA_EOF (1<<0) /* Can parse the RDB EOF streaming format. */
|
||||
#define SLAVE_CAPA_PSYNC2 (1<<1) /* Supports PSYNC2 protocol. */
|
||||
|
||||
/* Synchronous read timeout - replica side */
|
||||
/* Synchronous read timeout - slave side */
|
||||
#define CONFIG_REPL_SYNCIO_TIMEOUT 5
|
||||
|
||||
/* List related stuff */
|
||||
@@ -551,7 +583,7 @@ public:
|
||||
/* RDB active child save type. */
|
||||
#define RDB_CHILD_TYPE_NONE 0
|
||||
#define RDB_CHILD_TYPE_DISK 1 /* RDB is written to disk. */
|
||||
#define RDB_CHILD_TYPE_SOCKET 2 /* RDB is written to replica socket. */
|
||||
#define RDB_CHILD_TYPE_SOCKET 2 /* RDB is written to slave socket. */
|
||||
|
||||
/* Keyspace changes notification classes. Every class is associated with a
|
||||
* character for configuration purposes. */
|
||||
@@ -730,8 +762,6 @@ typedef struct RedisModuleDigest {
|
||||
#define OBJ_SHARED_REFCOUNT (0x7FFFFFFF)
|
||||
#define OBJ_MVCC_INVALID (0xFFFFFFFFFFFFFFFFULL)
|
||||
|
||||
#define MVCC_MS_SHIFT 20
|
||||
|
||||
typedef struct redisObject {
|
||||
unsigned type:4;
|
||||
unsigned encoding:4;
|
||||
@@ -739,7 +769,7 @@ typedef struct redisObject {
|
||||
* LFU data (least significant 8 bits frequency
|
||||
* and most significant 16 bits access time). */
|
||||
private:
|
||||
mutable std::atomic<unsigned> refcount {0};
|
||||
mutable std::atomic<unsigned> refcount;
|
||||
public:
|
||||
uint64_t mvcc_tstamp;
|
||||
void *m_ptr;
|
||||
@@ -791,16 +821,6 @@ public:
|
||||
: when(when), spsubkey(subkey, sdsfree)
|
||||
{}
|
||||
|
||||
subexpireEntry(const subexpireEntry &e)
|
||||
: when(e.when), spsubkey(nullptr, sdsfree)
|
||||
{
|
||||
if (e.spsubkey)
|
||||
spsubkey = std::unique_ptr<const char, void(*)(const char*)>((const char*)sdsdup((sds)e.spsubkey.get()), sdsfree);
|
||||
}
|
||||
|
||||
subexpireEntry(subexpireEntry &&e) = default;
|
||||
subexpireEntry& operator=(subexpireEntry &&e) = default;
|
||||
|
||||
bool operator<(long long when) const noexcept { return this->when < when; }
|
||||
bool operator<(const subexpireEntry &se) { return this->when < se.when; }
|
||||
};
|
||||
@@ -813,10 +833,6 @@ public:
|
||||
expireEntryFat(sds keyPrimary)
|
||||
: m_keyPrimary(keyPrimary)
|
||||
{}
|
||||
|
||||
expireEntryFat(const expireEntryFat &e) = default;
|
||||
expireEntryFat(expireEntryFat &&e) = default;
|
||||
|
||||
long long when() const noexcept { return m_vecexpireEntries.front().when; }
|
||||
const char *key() const noexcept { return m_keyPrimary; }
|
||||
|
||||
@@ -824,24 +840,6 @@ public:
|
||||
|
||||
void expireSubKey(const char *szSubkey, long long when)
|
||||
{
|
||||
// First check if the subkey already has an expiration
|
||||
for (auto &entry : m_vecexpireEntries)
|
||||
{
|
||||
if (szSubkey != nullptr)
|
||||
{
|
||||
// if this is a subkey expiry then its not a match if the expireEntry is either for the
|
||||
// primary key or a different subkey
|
||||
if (entry.spsubkey == nullptr || sdscmp((sds)entry.spsubkey.get(), (sds)szSubkey) != 0)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entry.spsubkey != nullptr)
|
||||
continue;
|
||||
}
|
||||
m_vecexpireEntries.erase(m_vecexpireEntries.begin() + (&entry - m_vecexpireEntries.data()));
|
||||
break;
|
||||
}
|
||||
auto itrInsert = std::lower_bound(m_vecexpireEntries.begin(), m_vecexpireEntries.end(), when);
|
||||
const char *subkey = (szSubkey) ? sdsdup(szSubkey) : nullptr;
|
||||
m_vecexpireEntries.emplace(itrInsert, when, subkey);
|
||||
@@ -865,7 +863,6 @@ class expireEntry {
|
||||
public:
|
||||
class iter
|
||||
{
|
||||
friend class expireEntry;
|
||||
expireEntry *m_pentry = nullptr;
|
||||
size_t m_idx = 0;
|
||||
|
||||
@@ -918,14 +915,6 @@ public:
|
||||
m_when = LLONG_MIN;
|
||||
}
|
||||
|
||||
expireEntry(const expireEntry &e)
|
||||
{
|
||||
u.m_key = e.u.m_key;
|
||||
m_when = e.m_when;
|
||||
if (e.FFat())
|
||||
u.m_pfatentry = new (MALLOC_LOCAL) expireEntryFat(*e.u.m_pfatentry);
|
||||
}
|
||||
|
||||
expireEntry(expireEntry &&e)
|
||||
{
|
||||
u.m_key = e.u.m_key;
|
||||
@@ -1009,14 +998,6 @@ public:
|
||||
return iter(this, u.m_pfatentry->size());
|
||||
return iter(this, 1);
|
||||
}
|
||||
|
||||
void erase(iter &itr)
|
||||
{
|
||||
if (!FFat())
|
||||
throw -1; // assert
|
||||
pfatentry()->m_vecexpireEntries.erase(
|
||||
pfatentry()->m_vecexpireEntries.begin() + itr.m_idx);
|
||||
}
|
||||
|
||||
bool FGetPrimaryExpire(long long *pwhen)
|
||||
{
|
||||
@@ -1069,183 +1050,14 @@ typedef struct clientReplyBlock {
|
||||
#endif
|
||||
} clientReplyBlock;
|
||||
|
||||
struct dictEntry;
|
||||
class dict_const_iter
|
||||
{
|
||||
friend struct redisDb;
|
||||
friend class redisDbPersistentData;
|
||||
protected:
|
||||
dictEntry *de;
|
||||
public:
|
||||
explicit dict_const_iter(dictEntry *de)
|
||||
: de(de)
|
||||
{}
|
||||
|
||||
const char *key() const { return de ? (const char*)dictGetKey(de) : nullptr; }
|
||||
robj_roptr val() const { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
const robj* operator->() const { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
operator robj_roptr() const { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
|
||||
bool operator==(std::nullptr_t) const { return de == nullptr; }
|
||||
bool operator!=(std::nullptr_t) const { return de != nullptr; }
|
||||
bool operator==(const dict_const_iter &other) { return de == other.de; }
|
||||
};
|
||||
class dict_iter : public dict_const_iter
|
||||
{
|
||||
public:
|
||||
explicit dict_iter(dictEntry *de)
|
||||
: dict_const_iter(de)
|
||||
{}
|
||||
sds key() { return de ? (sds)dictGetKey(de) : nullptr; }
|
||||
robj *val() { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
robj *operator->() { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
operator robj*() const { return de ? (robj*)dictGetVal(de) : nullptr; }
|
||||
};
|
||||
|
||||
class redisDbPersistentData
|
||||
{
|
||||
friend void dictDbKeyDestructor(void *privdata, void *key);
|
||||
public:
|
||||
~redisDbPersistentData();
|
||||
|
||||
static void swap(redisDbPersistentData *db1, redisDbPersistentData *db2);
|
||||
|
||||
size_t slots() const { return dictSlots(m_pdict); }
|
||||
size_t size() const { return dictSize(m_pdict); }
|
||||
void expand(uint64_t slots) { dictExpand(m_pdict, slots); }
|
||||
|
||||
void trackkey(robj_roptr o)
|
||||
{
|
||||
trackkey(szFromObj(o));
|
||||
}
|
||||
|
||||
void trackkey(const char *key)
|
||||
{
|
||||
if (m_fTrackingChanges && !m_fAllChanged)
|
||||
m_setchanged.insert(std::string(key, sdslen(key)));
|
||||
}
|
||||
|
||||
dict_iter find(const char *key)
|
||||
{
|
||||
dictEntry *de = dictFind(m_pdict, key);
|
||||
ensure(de);
|
||||
return dict_iter(de);
|
||||
}
|
||||
|
||||
dict_iter find(robj_roptr key)
|
||||
{
|
||||
return find(szFromObj(key));
|
||||
}
|
||||
|
||||
dict_iter random()
|
||||
{
|
||||
dictEntry *de = dictGetRandomKey(m_pdict);
|
||||
ensure(de);
|
||||
return dict_iter(de);
|
||||
}
|
||||
|
||||
const expireEntry &random_expire()
|
||||
{
|
||||
return m_setexpire->random_value();
|
||||
}
|
||||
|
||||
auto findExpire(const char *key)
|
||||
{
|
||||
return m_setexpire->find(key);
|
||||
}
|
||||
|
||||
dict_iter end() { return dict_iter(nullptr); }
|
||||
dict_const_iter end() const { return dict_const_iter(nullptr); }
|
||||
|
||||
void getStats(char *buf, size_t bufsize) { dictGetStats(buf, bufsize, m_pdict); }
|
||||
void getExpireStats(char *buf, size_t bufsize) { m_setexpire->getstats(buf, bufsize); }
|
||||
|
||||
bool insert(char *k, robj *o);
|
||||
void tryResize();
|
||||
int incrementallyRehash();
|
||||
void updateValue(dict_iter itr, robj *val);
|
||||
bool syncDelete(robj *key);
|
||||
bool asyncDelete(robj *key);
|
||||
size_t expireSize() const { return m_setexpire->size(); }
|
||||
int removeExpire(robj *key, dict_iter itr);
|
||||
int removeSubkeyExpire(robj *key, robj *subkey);
|
||||
void clear(void(callback)(void*));
|
||||
void emptyDbAsync();
|
||||
bool iterate(std::function<bool(const char*, robj*)> fn);
|
||||
void setExpire(robj *key, robj *subkey, long long when);
|
||||
void setExpire(expireEntry &&e);
|
||||
expireEntry *getExpire(robj_roptr key);
|
||||
void initialize();
|
||||
|
||||
void trackChanges() { m_fTrackingChanges++; }
|
||||
void processChanges();
|
||||
|
||||
// This should only be used if you look at the key, we do not fixup
|
||||
// objects stored elsewhere
|
||||
dict *dictUnsafeKeyOnly() { return m_pdict; }
|
||||
|
||||
expireset *setexpireUnsafe() { return m_setexpire; }
|
||||
const expireset *setexpire() { return m_setexpire; }
|
||||
|
||||
std::shared_ptr<redisDbPersistentData> createSnapshot();
|
||||
void endSnapshot(const redisDbPersistentData *psnapshot);
|
||||
|
||||
private:
|
||||
void ensure(const char *key);
|
||||
void ensure(dictEntry *de);
|
||||
void storeDatabase();
|
||||
void storeKey(const char *key, size_t cchKey, robj *o);
|
||||
|
||||
// Keyspace
|
||||
dict *m_pdict = nullptr; /* The keyspace for this DB */
|
||||
int m_fTrackingChanges = 0; // Note: Stack based
|
||||
bool m_fAllChanged = false;
|
||||
std::set<std::string> m_setchanged;
|
||||
IStorage *m_pstorage = nullptr;
|
||||
|
||||
// Expire
|
||||
expireset *m_setexpire = nullptr;
|
||||
|
||||
std::shared_ptr<redisDbPersistentData> m_spdbSnapshot;
|
||||
};
|
||||
|
||||
/* Redis database representation. There are multiple databases identified
|
||||
* by integers from 0 (the default database) up to the max configured
|
||||
* database. The database number is the 'id' field in the structure. */
|
||||
typedef struct redisDb : public redisDbPersistentData
|
||||
{
|
||||
// Legacy C API, Do not add more
|
||||
friend void tryResizeHashTables(int);
|
||||
friend int dbSyncDelete(redisDb *db, robj *key);
|
||||
friend int dbAsyncDelete(redisDb *db, robj *key);
|
||||
friend long long emptyDb(int dbnum, int flags, void(callback)(void*));
|
||||
friend void scanGenericCommand(struct client *c, robj_roptr o, unsigned long cursor);
|
||||
friend int dbSwapDatabases(int id1, int id2);
|
||||
friend int removeExpire(redisDb *db, robj *key);
|
||||
friend void setExpire(struct client *c, redisDb *db, robj *key, robj *subkey, long long when);
|
||||
friend void setExpire(client *c, redisDb *db, robj *key, expireEntry &&e);
|
||||
friend void evictionPoolPopulate(int dbid, redisDb *db, expireset *setexpire, struct evictionPoolEntry *pool);
|
||||
friend void activeDefragCycle(void);
|
||||
friend int freeMemoryIfNeeded(void);
|
||||
friend void activeExpireCycle(int);
|
||||
friend void expireSlaveKeys(void);
|
||||
|
||||
typedef ::dict_const_iter const_iter;
|
||||
typedef ::dict_iter iter;
|
||||
|
||||
redisDb()
|
||||
: expireitr(nullptr)
|
||||
{}
|
||||
void initialize(int id);
|
||||
|
||||
void dbOverwriteCore(redisDb::iter itr, robj *key, robj *val, bool fUpdateMvcc, bool fRemoveExpire);
|
||||
|
||||
|
||||
bool FKeyExpires(const char *key);
|
||||
size_t clear(bool fAsync, void(callback)(void*));
|
||||
|
||||
public:
|
||||
typedef struct redisDb {
|
||||
dict *pdict; /* The keyspace for this DB */
|
||||
expireset *setexpire;
|
||||
expireset::setiter expireitr;
|
||||
|
||||
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
|
||||
dict *ready_keys; /* Blocked keys that received a PUSH */
|
||||
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
|
||||
@@ -1253,8 +1065,6 @@ public:
|
||||
long long last_expire_set; /* when the last expire was set */
|
||||
double avg_ttl; /* Average TTL, just for stats */
|
||||
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
|
||||
|
||||
fastlock lock;
|
||||
} redisDb;
|
||||
|
||||
/* Client MULTI/EXEC state */
|
||||
@@ -1398,8 +1208,8 @@ typedef struct client {
|
||||
int casyncOpsPending;
|
||||
int fPendingAsyncWrite; /* NOTE: Not a flag because it is written to outside of the client lock (locked by the global lock instead) */
|
||||
int authenticated; /* Needed when the default user requires auth. */
|
||||
int replstate; /* Replication state if this is a replica. */
|
||||
int repl_put_online_on_ack; /* Install replica write handler on ACK. */
|
||||
int replstate; /* Replication state if this is a slave. */
|
||||
int repl_put_online_on_ack; /* Install slave write handler on ACK. */
|
||||
int repldbfd; /* Replication DB file descriptor. */
|
||||
off_t repldboff; /* Replication DB file offset. */
|
||||
off_t repldbsize; /* Replication DB file size. */
|
||||
@@ -1407,10 +1217,10 @@ typedef struct client {
|
||||
long long read_reploff; /* Read replication offset if this is a master. */
|
||||
long long reploff; /* Applied replication offset if this is a master. */
|
||||
long long reploff_skipped; /* Repl backlog we did not send to this client */
|
||||
long long repl_ack_off; /* Replication ack offset, if this is a replica. */
|
||||
long long repl_ack_time;/* Replication ack time, if this is a replica. */
|
||||
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
|
||||
long long repl_ack_time;/* Replication ack time, if this is a slave. */
|
||||
long long psync_initial_offset; /* FULLRESYNC reply offset other slaves
|
||||
copying this replica output buffer
|
||||
copying this slave output buffer
|
||||
should use. */
|
||||
char replid[CONFIG_RUN_ID_SIZE+1]; /* Master replication ID (if master). */
|
||||
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
|
||||
@@ -1588,9 +1398,6 @@ typedef struct rdbSaveInfo {
|
||||
char repl_id[CONFIG_RUN_ID_SIZE+1]; /* Replication ID. */
|
||||
long long repl_offset; /* Replication offset. */
|
||||
int fForceSetKey;
|
||||
|
||||
/* Used In Save */
|
||||
long long master_repl_offset;
|
||||
} rdbSaveInfo;
|
||||
|
||||
#define RDB_SAVE_INFO_INIT {-1,0,"000000000000000000000000000000",-1, TRUE}
|
||||
@@ -1654,7 +1461,7 @@ struct redisMaster {
|
||||
char master_replid[CONFIG_RUN_ID_SIZE+1]; /* Master PSYNC runid. */
|
||||
long long master_initial_offset; /* Master PSYNC offset. */
|
||||
|
||||
int repl_state; /* Replication status if the instance is a replica */
|
||||
int repl_state; /* Replication status if the instance is a slave */
|
||||
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_last_fsync_off; /* Offset when we fsync-ed last time. */
|
||||
@@ -1848,13 +1655,7 @@ struct redisServer {
|
||||
/* RDB persistence */
|
||||
long long dirty; /* Changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
||||
struct _rdbThreadVars
|
||||
{
|
||||
bool fRdbThreadActive = false;
|
||||
volatile bool fRdbThreadCancel = false;
|
||||
pthread_t rdb_child_thread; /* PID of RDB saving child */
|
||||
int tmpfileNum = 0;
|
||||
} rdbThreadVars;
|
||||
pid_t rdb_child_pid; /* PID of RDB saving child */
|
||||
struct saveparam *saveparams; /* Save points array for RDB */
|
||||
int saveparamslen; /* Number of saving points */
|
||||
char *rdb_filename; /* Name of RDB file */
|
||||
@@ -1870,7 +1671,7 @@ struct redisServer {
|
||||
int lastbgsave_status; /* C_OK or C_ERR */
|
||||
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_read_result_from_child; /* of each replica in diskless SYNC. */
|
||||
int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */
|
||||
/* Pipe and data structures for child -> parent info sharing. */
|
||||
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
|
||||
struct {
|
||||
@@ -1890,8 +1691,8 @@ struct redisServer {
|
||||
char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from master*/
|
||||
long long master_repl_offset; /* My current replication offset */
|
||||
long long second_replid_offset; /* Accept offsets up to this for replid2. */
|
||||
int replicaseldb; /* Last SELECTed DB in replication output */
|
||||
int repl_ping_slave_period; /* Master pings the replica every N seconds */
|
||||
int slaveseldb; /* Last SELECTed DB in replication output */
|
||||
int repl_ping_slave_period; /* Master pings the slave every N seconds */
|
||||
char *repl_backlog; /* Replication backlog for partial syncs */
|
||||
long long repl_backlog_size; /* Backlog circular buffer size */
|
||||
long long repl_backlog_histlen; /* Backlog actual data length */
|
||||
@@ -1908,7 +1709,7 @@ struct redisServer {
|
||||
int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */
|
||||
int repl_diskless_sync; /* Send RDB to slaves sockets directly. */
|
||||
int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */
|
||||
/* Replication (replica) */
|
||||
/* Replication (slave) */
|
||||
list *masters;
|
||||
int enable_multimaster;
|
||||
int repl_timeout; /* Timeout after N seconds of master idle */
|
||||
@@ -1979,7 +1780,7 @@ struct redisServer {
|
||||
int cluster_slave_validity_factor; /* Slave max data age for failover. */
|
||||
int cluster_require_full_coverage; /* If true, put the cluster down if
|
||||
there is at least an uncovered slot.*/
|
||||
int cluster_slave_no_failover; /* Prevent replica from starting a failover
|
||||
int cluster_slave_no_failover; /* Prevent slave from starting a failover
|
||||
if the master is in failure state. */
|
||||
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
|
||||
int cluster_announce_port; /* base port to announce on cluster bus. */
|
||||
@@ -2033,8 +1834,6 @@ struct redisServer {
|
||||
|
||||
/* System hardware info */
|
||||
size_t system_memory_size; /* Total memory in system as reported by OS */
|
||||
|
||||
bool FRdbSaveInProgress() const { return rdbThreadVars.fRdbThreadActive; }
|
||||
};
|
||||
|
||||
typedef struct pubsubPattern {
|
||||
@@ -2140,11 +1939,9 @@ extern dictType zsetDictType;
|
||||
extern dictType clusterNodesDictType;
|
||||
extern dictType clusterNodesBlackListDictType;
|
||||
extern dictType dbDictType;
|
||||
extern dictType dbSnapshotDictType;
|
||||
extern dictType shaScriptObjectDictType;
|
||||
extern double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
extern dictType hashDictType;
|
||||
extern dictType keylistDictType;
|
||||
extern dictType replScriptCacheDictType;
|
||||
extern dictType keyptrDictType;
|
||||
extern dictType modulesDictType;
|
||||
@@ -2252,7 +2049,7 @@ const char *getClientTypeName(int cclass);
|
||||
void flushSlavesOutputBuffers(void);
|
||||
void disconnectSlaves(void);
|
||||
void disconnectSlavesExcept(unsigned char *uuid);
|
||||
int listenToPort(int port, int *fds, int *count, int fReusePort, int fFirstListen);
|
||||
int listenToPort(int port, int *fds, int *count, int fReusePort);
|
||||
void pauseClients(mstime_t duration);
|
||||
int clientsArePaused(void);
|
||||
int processEventsWhileBlocked(int iel);
|
||||
@@ -2373,12 +2170,8 @@ const char *strEncoding(int encoding);
|
||||
int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long long estimateObjectIdleTime(robj_roptr o);
|
||||
unsigned long long estimateObjectIdleTime(robj *o);
|
||||
void trimStringObjectIfNeeded(robj *o);
|
||||
|
||||
robj *deserializeStoredObject(const void *data, size_t cb);
|
||||
sds serializeStoredObject(robj_roptr o);
|
||||
|
||||
#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
|
||||
|
||||
/* Synchronous I/O with timeout */
|
||||
@@ -2410,10 +2203,9 @@ void replicationSendNewlineToMaster(struct redisMaster *mi);
|
||||
long long replicationGetSlaveOffset(struct redisMaster *mi);
|
||||
char *replicationGetSlaveName(client *c);
|
||||
long long getPsyncInitialOffset(void);
|
||||
int replicationSetupSlaveForFullResync(client *replica, long long offset);
|
||||
int replicationSetupSlaveForFullResync(client *slave, long long offset);
|
||||
void changeReplicationId(void);
|
||||
void clearReplicationId2(void);
|
||||
void mergeReplicationId(const char *);
|
||||
void chopReplicationBacklog(void);
|
||||
void replicationCacheMasterUsingMyself(struct redisMaster *mi);
|
||||
void feedReplicationBacklog(const void *ptr, size_t len);
|
||||
@@ -2431,7 +2223,7 @@ int writeCommandsDeniedByDiskError(void);
|
||||
|
||||
/* RDB persistence */
|
||||
#include "rdb.h"
|
||||
int rdbSaveRio(rio *rdb, redisDbPersistentData **rgpdb, int *error, int flags, rdbSaveInfo *rsi);
|
||||
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi);
|
||||
void killRDBChild(void);
|
||||
|
||||
/* AOF persistence */
|
||||
@@ -2649,9 +2441,10 @@ int rewriteConfig(char *path);
|
||||
|
||||
/* db.c -- Keyspace access API */
|
||||
int removeExpire(redisDb *db, robj *key);
|
||||
int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey);
|
||||
int removeExpireCore(redisDb *db, robj *key, dictEntry *de);
|
||||
void propagateExpire(redisDb *db, robj *key, int lazy);
|
||||
int expireIfNeeded(redisDb *db, robj *key);
|
||||
expireEntry *getExpire(redisDb *db, robj_roptr key);
|
||||
void setExpire(client *c, redisDb *db, robj *key, robj *subkey, long long when);
|
||||
void setExpire(client *c, redisDb *db, robj *key, expireEntry &&entry);
|
||||
robj_roptr lookupKeyRead(redisDb *db, robj *key);
|
||||
@@ -2659,8 +2452,8 @@ robj *lookupKeyWrite(redisDb *db, robj *key);
|
||||
robj_roptr lookupKeyReadOrReply(client *c, robj *key, robj *reply);
|
||||
robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
|
||||
robj_roptr lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
|
||||
robj_roptr objectCommandLookup(client *c, robj *key);
|
||||
robj_roptr objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
robj *objectCommandLookup(client *c, robj *key);
|
||||
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
|
||||
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
|
||||
long long lru_clock);
|
||||
#define LOOKUP_NONE 0
|
||||
@@ -2693,6 +2486,7 @@ void slotToKeyAdd(robj *key);
|
||||
void slotToKeyDel(robj *key);
|
||||
void slotToKeyFlush(void);
|
||||
int dbAsyncDelete(redisDb *db, robj *key);
|
||||
void emptyDbAsync(redisDb *db);
|
||||
void slotToKeyFlushAsync(void);
|
||||
size_t lazyfreeGetPendingObjectsCount(void);
|
||||
void freeObjAsync(robj *o);
|
||||
@@ -2716,7 +2510,6 @@ void clusterPropagatePublish(robj *channel, robj *message);
|
||||
void migrateCloseTimedoutSockets(void);
|
||||
void clusterBeforeSleep(void);
|
||||
int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, unsigned char *payload, uint32_t len);
|
||||
void createDumpPayload(rio *payload, robj_roptr o, robj *key);
|
||||
|
||||
/* Sentinel */
|
||||
void initSentinelConfig(void);
|
||||
@@ -2849,7 +2642,6 @@ void monitorCommand(client *c);
|
||||
void expireCommand(client *c);
|
||||
void expireatCommand(client *c);
|
||||
void expireMemberCommand(client *c);
|
||||
void expireMemberAtCommand(client *c);
|
||||
void pexpireCommand(client *c);
|
||||
void pexpireatCommand(client *c);
|
||||
void getsetCommand(client *c);
|
||||
@@ -2935,6 +2727,7 @@ void evalShaCommand(client *c);
|
||||
void scriptCommand(client *c);
|
||||
void timeCommand(client *c);
|
||||
void bitopCommand(client *c);
|
||||
void bitopRangeCommand(client *c);
|
||||
void bitcountCommand(client *c);
|
||||
void bitposCommand(client *c);
|
||||
void replconfCommand(client *c);
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
/* ----------------- Blocking sockets I/O with timeouts --------------------- */
|
||||
|
||||
/* Redis performs most of the I/O in a nonblocking way, with the exception
|
||||
* of the SYNC command where the replica does it in a blocking way, and
|
||||
* of the SYNC command where the slave does it in a blocking way, and
|
||||
* the MIGRATE command that must be blocking in order to be atomic from the
|
||||
* point of view of the two instances (one migrating the key and one receiving
|
||||
* the key). This is why need the following blocking I/O functions.
|
||||
|
||||
+2
-2
@@ -219,9 +219,9 @@ int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) {
|
||||
return setobj->encoding;
|
||||
}
|
||||
|
||||
int setTypeRandomElement(robj_roptr setobj, const char **sdsele, int64_t *llele)
|
||||
int setTypeRandomElement(robj_roptr setobj, const char **sdsele, const int64_t *llele)
|
||||
{
|
||||
return setTypeRandomElement(setobj.unsafe_robjcast(), (sds*)sdsele, llele);
|
||||
return setTypeRandomElement(setobj.unsafe_robjcast(), (sds*)sdsele, const_cast<int64_t*>(llele));
|
||||
}
|
||||
|
||||
unsigned long setTypeSize(robj_roptr subject) {
|
||||
|
||||
+1
-1
@@ -803,7 +803,7 @@ void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupnam
|
||||
* RETRYCOUNT <count> FORCE JUSTID LASTID <id>.
|
||||
*
|
||||
* Note that JUSTID is useful in order to avoid that XCLAIM will do
|
||||
* useless work in the replica side, trying to fetch the stream item. */
|
||||
* useless work in the slave side, trying to fetch the stream item. */
|
||||
robj *argv[14];
|
||||
argv[0] = createStringObject("XCLAIM",6);
|
||||
argv[1] = key;
|
||||
|
||||
@@ -93,18 +93,5 @@ start_server {tags {"active-repl"} overrides {active-replica yes}} {
|
||||
assert_equal {0} [$master del testkey1]
|
||||
assert_equal {0} [$slave del testkey1]
|
||||
}
|
||||
|
||||
test {Active replica different databases} {
|
||||
$master select 3
|
||||
$master set testkey abcd
|
||||
$master select 2
|
||||
$master del testkey
|
||||
$slave select 3
|
||||
wait_for_condition 50 1000 {
|
||||
[string match abcd [$slave get testkey]]
|
||||
} else {
|
||||
fail "Replication failed to propogate DB 3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,6 @@ set ::all_tests {
|
||||
unit/quit
|
||||
unit/aofrw
|
||||
unit/acl
|
||||
unit/rreplay
|
||||
integration/block-repl
|
||||
integration/replication
|
||||
integration/replication-2
|
||||
|
||||
@@ -214,64 +214,6 @@ start_server {tags {"bitops"}} {
|
||||
r bitop or x a b
|
||||
} {32}
|
||||
|
||||
test {BITOP lshift size} {
|
||||
r set a " "
|
||||
r bitop lshift x a 1
|
||||
} {2}
|
||||
|
||||
test {BITOP rshift size} {
|
||||
r set a " "
|
||||
r bitop rshift x a 1
|
||||
} {1}
|
||||
|
||||
test {BITOP rshift 0 byte} {
|
||||
r set a " "
|
||||
r bitop rshift x a 8
|
||||
} {0}
|
||||
|
||||
test {BITOP rshift underflow} {
|
||||
r set a " "
|
||||
r bitop rshift x a 65
|
||||
} {0}
|
||||
|
||||
test {BITOP lshift string} {
|
||||
r set a "abcdefg"
|
||||
r bitop lshift x a 8
|
||||
r get x
|
||||
} "\x00abcdefg"
|
||||
|
||||
test {BITOP lshift char} {
|
||||
r set a "\xAA"
|
||||
r bitop lshift x a 4
|
||||
r get x
|
||||
} "\xA0\x0A"
|
||||
|
||||
test {BITOP rshift char} {
|
||||
r set a "\xAA"
|
||||
r bitop rshift x a 3
|
||||
r get x
|
||||
} "\x15"
|
||||
|
||||
test {BITOP lshift carry} {
|
||||
r set a "\xFF"
|
||||
r bitop lshift x a 1
|
||||
r get x
|
||||
} "\xFE\x01"
|
||||
|
||||
test {BITOP rshift carry} {
|
||||
r set a "\x00\xFF"
|
||||
r bitop rshift x a 1
|
||||
r get x
|
||||
} "\x80\x7F"
|
||||
|
||||
test {BITOP rshift reciprocal} {
|
||||
r flushdb
|
||||
r set a "abcdefg"
|
||||
r bitop lshift b a 14
|
||||
r bitop rshift res b 14
|
||||
r get res
|
||||
} "abcdefg\x00"
|
||||
|
||||
test {BITPOS bit=0 with empty key returns 0} {
|
||||
r del str
|
||||
r bitpos str 0
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
start_server {tags {"rreplay"}} {
|
||||
|
||||
test {RREPLAY use current db} {
|
||||
r debug force-master
|
||||
r select 4
|
||||
r set dbnum invalid
|
||||
r rreplay "f4d5b2b5-4f07-4ee5-a4f2-5dc98507dfce" "*3\r\n\$3\r\nSET\r\n\$5\r\ndbnum\r\n\$4\r\nfour\r\n"
|
||||
r get dbnum
|
||||
} {four}
|
||||
reconnect
|
||||
|
||||
test {RREPLAY db different} {
|
||||
r debug force-master
|
||||
r select 4
|
||||
r set testkey four
|
||||
r rreplay "f4d5b2b5-4f07-4ee5-a4f2-5dc98507dfce" "*3\r\n\$3\r\nSET\r\n\$7\r\ntestkey\r\n\$4\r\nbebe\r\n" 2
|
||||
r select 4
|
||||
assert { [r get testkey] == "four" }
|
||||
r select 2
|
||||
r get testkey
|
||||
} {bebe}
|
||||
|
||||
reconnect
|
||||
|
||||
test {RREPLAY not master} {
|
||||
assert_error "*master*" {r rreplay "f4d5b2b5-4f07-4ee5-a4f2-5dc98507dfce" "*3\r\n\$3\r\nSET\r\n\$7\r\ntestkey\r\n\$4\r\nbebe\r\n" 2}
|
||||
}
|
||||
|
||||
r flushdb
|
||||
}
|
||||
@@ -202,7 +202,7 @@ REDIS_CHKCONFIG_INFO=\
|
||||
# chkconfig: - 58 74\n
|
||||
# description: redis_${REDIS_PORT} is the redis daemon.\n
|
||||
### BEGIN INIT INFO\n
|
||||
# Provides: redis_${REDIS_PORT}\n
|
||||
# Provides: redis_6379\n
|
||||
# Required-Start: \$network \$local_fs \$remote_fs\n
|
||||
# Required-Stop: \$network \$local_fs \$remote_fs\n
|
||||
# Default-Start: 2 3 4 5\n
|
||||
|
||||
Reference in new issue
Block a user