Compare commits

..
1 Commits
Author SHA1 Message Date
John Sully 82358cbf4a Initial implementation of the BITOPRANGE command 2019-09-06 00:12:30 -04:00
56 changed files with 990 additions and 1301 deletions

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
{
"files.associations": {
"zmalloc.h": "c",
"stat.h": "c",
"array": "cpp",
"atomic": "cpp",
"*.tcc": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"list": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"fstream": "cpp",
"functional": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"memory": "cpp",
"mutex": "cpp",
"new": "cpp",
"numeric": "cpp",
"optional": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"scoped_allocator": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeinfo": "cpp",
"utility": "cpp",
"set": "cpp",
"algorithm": "cpp"
}
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
-13
View File
@@ -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.
+4 -2
View File
@@ -47,6 +47,8 @@ endif
USEASM?=true
ifneq ($(SANITIZE),)
CC=clang
CXX=clang++
CFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
CXXFLAGS+= -fsanitize=$(SANITIZE) -DSANITIZE
LDFLAGS+= -fsanitize=$(SANITIZE)
@@ -188,7 +190,7 @@ endif
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
REDIS_CXX=$(QUIET_CC)$(CC) $(FINAL_CXXFLAGS)
KEYDB_AS=$(QUIET_CC) as --64 -g
REDIS_NASM=$(QUIET_CC)nasm -felf64
REDIS_LD=$(QUIET_LINK)$(CXX) $(FINAL_LDFLAGS)
REDIS_INSTALL=$(QUIET_INSTALL)$(INSTALL)
@@ -293,7 +295,7 @@ dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c
$(REDIS_CXX) -c $<
%.o: %.asm .make-prerequisites
$(KEYDB_AS) $< -o $@
$(REDIS_NASM) $<
clean:
rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep dict-benchmark
+2 -2
View File
@@ -80,7 +80,7 @@ public:
mutex_wrapper g_lock;
#else
fastlock g_lock("AE (global)");
fastlock g_lock;
#endif
thread_local aeEventLoop *g_eventLoopThisThread = NULL;
@@ -327,7 +327,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
for (i = 0; i < setsize; i++)
eventLoop->events[i].mask = AE_NONE;
fastlock_init(&eventLoop->flock, "event loop");
fastlock_init(&eventLoop->flock);
int rgfd[2];
if (pipe(rgfd) < 0)
goto err;
-5
View File
@@ -61,11 +61,6 @@ public:
return m_fArmed;
}
void release()
{
m_fArmed = false;
}
~AeLocker()
{
if (m_fArmed)
+7 -11
View File
@@ -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
View File
@@ -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);
+11 -17
View File
@@ -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
@@ -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();
@@ -678,7 +680,7 @@ client *createFakeClient(void) {
c->puser = NULL;
listSetFreeMethod(c->reply,freeClientReplyValue);
listSetDupMethod(c->reply,dupClientReplyValue);
fastlock_init(&c->lock, "fake client");
fastlock_init(&c->lock);
fastlock_lock(&c->lock);
initClientMultiState(c);
return c;
@@ -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);
}
+272 -183
View File
@@ -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
View File
@@ -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. */
+1 -1
View File
@@ -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 {
+1 -3
View File
@@ -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;
}
+9 -44
View File
@@ -30,7 +30,6 @@
#include "server.h"
#include "cluster.h"
#include "atomicvar.h"
#include "aelocker.h"
#include <signal.h>
#include <ctype.h>
@@ -115,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. */
@@ -133,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.
@@ -344,7 +343,7 @@ robj *dbRandomKey(redisDb *db) {
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
@@ -1133,8 +1132,7 @@ 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,
@@ -1219,39 +1217,6 @@ int removeExpireCore(redisDb *db, robj *key, dictEntry *de) {
return 1;
}
int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey) {
dictEntry *de = dictFind(db->pdict,ptrFromObj(key));
serverAssertWithInfo(NULL,key,de != NULL);
robj *val = (robj*)dictGetVal(de);
if (!val->FExpires())
return 0;
auto itr = db->setexpire->find((sds)dictGetKey(de));
serverAssert(itr != db->setexpire->end());
serverAssert(itr->key() == (sds)dictGetKey(de));
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)
removeExpireCore(db, 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
@@ -1360,7 +1325,7 @@ expireEntry *getExpire(redisDb *db, 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) {
@@ -1418,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).
*
@@ -1434,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,
+3 -5
View File
@@ -55,7 +55,7 @@ typedef ucontext_t sigcontext_t;
#endif
#endif
int g_fInCrash = false;
bool g_fInCrash = false;
/* ================================= Debugging ============================== */
@@ -682,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;
+2 -2
View File
@@ -374,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) {
+29 -106
View File
@@ -130,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 == NULL) {
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:
@@ -175,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
@@ -352,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.
*
@@ -371,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.
@@ -434,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);
@@ -451,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 = {
@@ -493,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) {
@@ -531,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. */
@@ -589,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. */
* TTL value otherwise. */
expireEntry *pexpire = getExpire(c->db,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;
}
if (pexpire != nullptr)
pexpire->FGetPrimaryExpire(&expire);
if (expire != -1) {
ttl = expire-mstime();
@@ -640,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 (removeSubkeyExpire(c->db, 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);
+12 -95
View File
@@ -36,13 +36,10 @@
#include <assert.h>
#include <pthread.h>
#include <limits.h>
#include <map>
#ifdef __linux__
#include <linux/futex.h>
#endif
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#ifdef __APPLE__
#include <TargetConditionals.h>
@@ -60,7 +57,6 @@
#define UNUSED(x) ((void)x)
#endif
extern int g_fInCrash;
/****************************************************
*
@@ -129,80 +125,6 @@ extern int g_fInCrash;
#endif
#pragma weak _serverPanic
extern "C" __attribute__((weak)) void _serverPanic(const char * /*file*/, int /*line*/, const char * /*msg*/, ...)
{
*((char*)-1) = 'x';
}
#pragma weak serverLog
__attribute__((weak)) void serverLog(int , const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf("\n");
}
class DeadlockDetector
{
std::map<pid_t, fastlock *> m_mapwait;
fastlock m_lock { "deadlock detector" };
public:
void registerwait(fastlock *lock, pid_t thispid)
{
if (lock == &m_lock || g_fInCrash)
return;
fastlock_lock(&m_lock);
m_mapwait.insert(std::make_pair(thispid, lock));
// Detect cycles
pid_t pidCheck = thispid;
size_t cchecks = 0;
for (;;)
{
auto itr = m_mapwait.find(pidCheck);
if (itr == m_mapwait.end())
break;
pidCheck = itr->second->m_pidOwner;
if (pidCheck == thispid)
{
// Deadlock detected, printout some debugging info and crash
serverLog(3 /*LL_WARNING*/, "\n\n");
serverLog(3 /*LL_WARNING*/, "!!! ERROR: Deadlock detected !!!");
pidCheck = thispid;
for (;;)
{
auto itr = m_mapwait.find(pidCheck);
serverLog(3 /* LL_WARNING */, "\t%d: (%p) %s", pidCheck, itr->second, itr->second->szName);
pidCheck = itr->second->m_pidOwner;
if (pidCheck == thispid)
break;
}
serverLog(3 /*LL_WARNING*/, "!!! KeyDB Will Now Crash !!!");
_serverPanic(__FILE__, __LINE__, "Deadlock detected");
}
if (cchecks > m_mapwait.size())
break; // There is a cycle but we're not in it
++cchecks;
}
fastlock_unlock(&m_lock);
}
void clearwait(fastlock *lock, pid_t thispid)
{
if (lock == &m_lock || g_fInCrash)
return;
fastlock_lock(&m_lock);
m_mapwait.erase(thispid);
fastlock_unlock(&m_lock);
}
};
DeadlockDetector g_dlock;
static_assert(sizeof(pid_t) <= sizeof(fastlock::m_pidOwner), "fastlock::m_pidOwner not large enough");
uint64_t g_longwaits = 0;
@@ -213,6 +135,7 @@ uint64_t fastlock_getlongwaitcount()
return rval;
}
#ifndef ASM_SPINLOCK
#ifdef __linux__
static int futex(volatile unsigned *uaddr, int futex_op, int val,
const struct timespec *timeout, int val3)
@@ -221,6 +144,7 @@ static int futex(volatile unsigned *uaddr, int futex_op, int val,
timeout, uaddr, val3);
}
#endif
#endif
extern "C" pid_t gettid()
{
@@ -239,26 +163,13 @@ extern "C" pid_t gettid()
return pidCache;
}
extern "C" void fastlock_sleep(fastlock *lock, pid_t pid, unsigned wake, unsigned mask)
{
#ifdef __linux__
g_dlock.registerwait(lock, pid);
__atomic_fetch_or(&lock->futex, mask, __ATOMIC_ACQUIRE);
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, wake, nullptr, mask);
__atomic_fetch_and(&lock->futex, ~mask, __ATOMIC_RELEASE);
g_dlock.clearwait(lock, pid);
#endif
__atomic_fetch_add(&g_longwaits, 1, __ATOMIC_RELAXED);
}
extern "C" void fastlock_init(struct fastlock *lock, const char *name)
extern "C" void fastlock_init(struct fastlock *lock)
{
lock->m_ticket.m_active = 0;
lock->m_ticket.m_avail = 0;
lock->m_depth = 0;
lock->m_pidOwner = -1;
lock->futex = 0;
lock->szName = name;
ANNOTATE_RWLOCK_CREATE(lock);
}
@@ -273,12 +184,12 @@ extern "C" void fastlock_lock(struct fastlock *lock)
return;
}
int tid = gettid();
unsigned myticket = __atomic_fetch_add(&lock->m_ticket.m_avail, 1, __ATOMIC_RELEASE);
#ifdef __linux__
unsigned mask = (1U << (myticket % 32));
#endif
int cloops = 0;
ticket ticketT;
for (;;)
{
__atomic_load(&lock->m_ticket.u, &ticketT.u, __ATOMIC_ACQUIRE);
@@ -290,11 +201,17 @@ extern "C" void fastlock_lock(struct fastlock *lock)
#endif
if ((++cloops % 1024*1024) == 0)
{
fastlock_sleep(lock, tid, ticketT.u, mask);
#ifdef __linux__
__atomic_fetch_or(&lock->futex, mask, __ATOMIC_ACQUIRE);
futex(&lock->m_ticket.u, FUTEX_WAIT_BITSET_PRIVATE, ticketT.u, nullptr, mask);
__atomic_fetch_and(&lock->futex, ~mask, __ATOMIC_RELEASE);
#endif
__atomic_fetch_add(&g_longwaits, 1, __ATOMIC_RELAXED);
}
}
lock->m_depth = 1;
int tid = gettid();
__atomic_store(&lock->m_pidOwner, &tid, __ATOMIC_RELEASE);
ANNOTATE_RWLOCK_ACQUIRED(lock, true);
std::atomic_thread_fence(std::memory_order_acquire);
+6 -7
View File
@@ -7,7 +7,7 @@ extern "C" {
/* Begin C API */
struct fastlock;
void fastlock_init(struct fastlock *lock, const char *name);
void fastlock_init(struct fastlock *lock);
void fastlock_lock(struct fastlock *lock);
int fastlock_trylock(struct fastlock *lock, int fWeak);
void fastlock_unlock(struct fastlock *lock);
@@ -45,25 +45,24 @@ struct fastlock
volatile int m_pidOwner;
volatile int m_depth;
unsigned futex;
const char *szName;
#ifdef __cplusplus
fastlock(const char *name)
fastlock()
{
fastlock_init(this, name);
fastlock_init(this);
}
inline void lock()
void lock()
{
fastlock_lock(this);
}
inline bool try_lock(bool fWeak = false)
bool try_lock(bool fWeak = false)
{
return !!fastlock_trylock(this, fWeak);
}
inline void unlock()
void unlock()
{
fastlock_unlock(this);
}
+119 -123
View File
@@ -1,160 +1,156 @@
.intel_syntax noprefix
.text
section .text
.extern gettid
.extern fastlock_sleep
extern gettid
extern sched_yield
extern g_longwaits
# This is the first use of assembly in this codebase, a valid question is WHY?
# The spinlock we implement here is performance critical, and simply put GCC
# emits awful code. The original C code is left in fastlock.cpp for reference
# and x-plat.
; This is the first use of assembly in this codebase, a valid question is WHY?
; The spinlock we implement here is performance critical, and simply put GCC
; emits awful code. The original C code is left in fastlock.cpp for reference
; and x-plat.
.ALIGN 16
.global fastlock_lock
.type fastlock_lock,@function
ALIGN 16
global fastlock_lock
fastlock_lock:
.cfi_startproc
.cfi_def_cfa rsp, 8
# RDI points to the struct:
# uint16_t active
# uint16_t avail
# int32_t m_pidOwner
# int32_t m_depth
; RDI points to the struct:
; uint16_t active
; uint16_t avail
; int32_t m_pidOwner
; int32_t m_depth
# First get our TID and put it in ecx
push rdi # we need our struct pointer (also balance the stack for the call)
.cfi_adjust_cfa_offset 8
call gettid # get our thread ID (TLS is nasty in ASM so don't bother inlining)
mov esi, eax # back it up in esi
pop rdi # get our pointer back
.cfi_adjust_cfa_offset -8
; First get our TID and put it in ecx
push rdi ; we need our struct pointer (also balance the stack for the call)
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
mov esi, eax ; back it up in esi
pop rdi ; get our pointer back
cmp [rdi+4], esi # Is the TID we got back the owner of the lock?
je .LLocked # Don't spin in that case
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
je .LLocked ; Don't spin in that case
xor eax, eax # eliminate partial register dependency
inc eax # we want to add one
lock xadd [rdi+2], ax # do the xadd, ax contains the value before the addition
# ax now contains the ticket
# OK Start the wait loop
xor ecx, ecx
.ALIGN 16
xor eax, eax ; eliminate partial register dependency
inc eax ; we want to add one
lock xadd [rdi+2], ax ; do the xadd, ax contains the value before the addition
; ax now contains the ticket
ALIGN 16
.LLoop:
mov edx, [rdi]
cmp dx, ax # is our ticket up?
je .LLocked # leave the loop
cmp dx, ax ; is our ticket up?
je .LLocked ; leave the loop
pause
add ecx, 0x1000 # Have we been waiting a long time? (oflow if we have)
# 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
jnc .LLoop # If so, give up our timeslice to someone who's doing real work
# Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
# But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
# taking a long time to be released anyways. We optimize for the common case of short
# lock intervals. That's why we're using a spinlock in the first place
# If we get here we're going to sleep in the kernel with a futex
push rdi
add ecx, 1000h ; Have we been waiting a long time? (oflow if we have)
; 1000h is set so we overflow on the 1024*1024'th iteration (like the C code)
jnc .LLoop ; If so, give up our timeslice to someone who's doing real work
; Like the compiler, you're probably thinking: "Hey! I should take these pushs out of the loop"
; But the compiler doesn't know that we rarely hit this, and when we do we know the lock is
; taking a long time to be released anyways. We optimize for the common case of short
; lock intervals. That's why we're using a spinlock in the first place
; If we get here we're going to sleep in the kernel with a futex
push rsi
push rax
.cfi_adjust_cfa_offset 24
# Setup the syscall args
# rdi ARG1 futex (already in rdi)
# rsi ARG2 tid (already in esi)
# rdx ARG3 ticketT.u (already in edx)
bts ecx, eax # rcx ARG4 mask
call fastlock_sleep
# cleanup and continue
; Setup the syscall args
; rdi ARG1 futex (already in rdi)
mov esi, (9 | 128) ; rsi ARG2 FUTEX_WAIT_BITSET_PRIVATE
; rdx ARG3 ticketT.u (already in edx)
xor r10d, r10d ; r10 ARG4 NULL
mov r8, rdi ; r8 ARG5 dup rdi
xor r9d, r9d
bts r9d, eax ; r9 ARG6 mask
mov eax, 202 ; sys_futex
; Do the syscall
lock or [rdi+12], r9d ; inform the unlocking thread we're waiting
syscall ; wait for the futex
not r9d ; convert our flag into a mask of bits not to touch
lock and [rdi+12], r9d ; clear the flag in the futex control mask
; cleanup and continue
mov rcx, g_longwaits
inc qword [rcx] ; increment our long wait counter
pop rax
pop rsi
pop rdi
.cfi_adjust_cfa_offset -24
xor ecx, ecx # Reset our loop counter
jmp .LLoop # Get back in the game
.ALIGN 16
xor ecx, ecx ; Reset our loop counter
jmp .LLoop ; Get back in the game
ALIGN 16
.LLocked:
mov [rdi+4], esi # lock->m_pidOwner = gettid()
inc dword ptr [rdi+8] # lock->m_depth++
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
inc dword [rdi+8] ; lock->m_depth++
ret
.cfi_endproc
.ALIGN 16
.global fastlock_trylock
.type fastlock_trylock,@function
ALIGN 16
global fastlock_trylock
fastlock_trylock:
# RDI points to the struct:
# uint16_t active
# uint16_t avail
# int32_t m_pidOwner
# int32_t m_depth
; RDI points to the struct:
; uint16_t active
; uint16_t avail
; int32_t m_pidOwner
; int32_t m_depth
# First get our TID and put it in ecx
push rdi # we need our struct pointer (also balance the stack for the call)
call gettid # get our thread ID (TLS is nasty in ASM so don't bother inlining)
mov esi, eax # back it up in esi
pop rdi # get our pointer back
; First get our TID and put it in ecx
push rdi ; we need our struct pointer (also balance the stack for the call)
call gettid ; get our thread ID (TLS is nasty in ASM so don't bother inlining)
mov esi, eax ; back it up in esi
pop rdi ; get our pointer back
cmp [rdi+4], esi # Is the TID we got back the owner of the lock?
je .LRecursive # Don't spin in that case
cmp [rdi+4], esi ; Is the TID we got back the owner of the lock?
je .LRecursive ; Don't spin in that case
mov eax, [rdi] # get both active and avail counters
mov ecx, eax # duplicate in ecx
ror ecx, 16 # swap upper and lower 16-bits
cmp eax, ecx # are the upper and lower 16-bits the same?
jnz .LAlreadyLocked # If not return failure
mov eax, [rdi] ; get both active and avail counters
mov ecx, eax ; duplicate in ecx
ror ecx, 16 ; swap upper and lower 16-bits
cmp eax, ecx ; are the upper and lower 16-bits the same?
jnz .LAlreadyLocked ; If not return failure
# at this point we know eax+ecx have [avail][active] and they are both the same
add ecx, 0x10000 # increment avail, ecx is now our wanted value
lock cmpxchg [rdi], ecx # If rdi still contains the value in eax, put in ecx (inc avail)
jnz .LAlreadyLocked # If Z is not set then someone locked it while we were preparing
; at this point we know eax+ecx have [avail][active] and they are both the same
add ecx, 10000h ; increment avail, ecx is now our wanted value
lock cmpxchg [rdi], ecx ; If rdi still contains the value in eax, put in ecx (inc avail)
jnz .LAlreadyLocked ; If Z is not set then someone locked it while we were preparing
xor eax, eax
inc eax # return SUCCESS! (eax=1)
mov [rdi+4], esi # lock->m_pidOwner = gettid()
mov dword ptr [rdi+8], eax # lock->m_depth = 1
inc eax ; return SUCCESS! (eax=1)
mov [rdi+4], esi ; lock->m_pidOwner = gettid()
mov dword [rdi+8], eax ; lock->m_depth = 1
ret
.ALIGN 16
ALIGN 16
.LRecursive:
xor eax, eax
inc eax # return SUCCESS! (eax=1)
inc dword ptr [rdi+8] # lock->m_depth++
inc eax ; return SUCCESS! (eax=1)
inc dword [rdi+8] ; lock->m_depth++
ret
.ALIGN 16
ALIGN 16
.LAlreadyLocked:
xor eax, eax # return 0
xor eax, eax ; return 0;
ret
.ALIGN 16
.global fastlock_unlock
ALIGN 16
global fastlock_unlock
fastlock_unlock:
# RDI points to the struct:
# uint16_t active
# uint16_t avail
# int32_t m_pidOwner
# int32_t m_depth
; RDI points to the struct:
; uint16_t active
; uint16_t avail
; int32_t m_pidOwner
; int32_t m_depth
push r11
sub dword ptr [rdi+8], 1 # decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
jnz .LDone # if depth is non-zero this is a recursive unlock, and we still hold it
mov dword ptr [rdi+4], -1 # pidOwner = -1 (we don't own it anymore)
mov ecx, [rdi] # get current active (this one)
inc ecx # bump it to the next thread
mov [rdi], cx # give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
# At this point the lock is removed, however we must wake up any pending futexs
mov r9d, 1 # eax is the bitmask for 2 threads
rol r9d, cl # place the mask in the right spot for the next 2 threads
.ALIGN 16
sub dword [rdi+8], 1 ; decrement m_depth, don't use dec because it partially writes the flag register and we don't know its state
jnz .LDone ; if depth is non-zero this is a recursive unlock, and we still hold it
mov dword [rdi+4], -1 ; pidOwner = -1 (we don't own it anymore)
mov ecx, [rdi] ; get current active (this one)
inc ecx ; bump it to the next thread
mov [rdi], cx ; give up our ticket (note: lock is not required here because the spinlock itself guards this variable)
; At this point the lock is removed, however we must wake up any pending futexs
mov r9d, 1 ; eax is the bitmask for 2 threads
rol r9d, cl ; place the mask in the right spot for the next 2 threads
ALIGN 16
.LRetryWake:
mov r11d, [rdi+12] # load the futex mask
and r11d, r9d # are any threads waiting on a futex?
jz .LDone # if not we're done.
# we have to wake the futexs
# rdi ARG1 futex (already in rdi)
mov esi, (10 | 128) # rsi ARG2 FUTEX_WAKE_BITSET_PRIVATE
mov edx, 0x7fffffff # rdx ARG3 INT_MAX (number of threads to wake)
xor r10d, r10d # r10 ARG4 NULL
mov r8, rdi # r8 ARG5 dup rdi
# r9 ARG6 mask (already set above)
mov eax, 202 # sys_futex
mov r11d, [rdi+12] ; load the futex mask
and r11d, r9d ; are any threads waiting on a futex?
jz .LDone ; if not we're done.
; we have to wake the futexs
; rdi ARG1 futex (already in rdi)
mov esi, (10 | 128) ; rsi ARG2 FUTEX_WAKE_BITSET_PRIVATE
mov edx, 0x7fffffff ; rdx ARG3 INT_MAX (number of threads to wake)
xor r10d, r10d ; r10 ARG4 NULL
mov r8, rdi ; r8 ARG5 dup rdi
; r9 ARG6 mask (already set above)
mov eax, 202 ; sys_futex
syscall
cmp eax, 1 # did we wake as many as we expected?
cmp eax, 1 ; did we wake as many as we expected?
jnz .LRetryWake
.LDone:
pop r11
+13 -11
View File
@@ -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",
+4 -4
View File
@@ -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
*
@@ -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;
+2 -2
View File
@@ -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. */
+25 -37
View File
@@ -116,7 +116,7 @@ client *createClient(int fd, int iel) {
uint64_t client_id;
client_id = g_pserver->next_client_id.fetch_add(1);
c->iel = iel;
fastlock_init(&c->lock, "client");
fastlock_init(&c->lock);
c->id = client_id;
c->resp = 2;
c->fd = fd;
@@ -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,12 +247,8 @@ 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);
if (FCorrectThread(c)) {
serverAssert(c->fd <= 0 || c->lock.fOwnLock());
} else {
serverAssert(GlobalLocksAcquired());
}
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.
// do not install a write handler
@@ -446,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:
@@ -1218,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) &&
@@ -1303,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));
@@ -1337,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);
@@ -1356,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));
@@ -1498,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 ||
@@ -1513,6 +1509,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
} else {
serverLog(LL_VERBOSE,
"Error writing to client: %s", strerror(errno));
lock.unlock();
freeClientAsync(c);
return C_ERR;
@@ -1531,6 +1528,7 @@ int writeToClient(int fd, client *c, int handler_installed) {
/* Close connection after entire reply has been sent. */
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
lock.unlock();
freeClientAsync(c);
return C_ERR;
}
@@ -1807,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;
@@ -2040,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;
}
@@ -2062,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;
@@ -2912,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
@@ -2924,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);
}
}
}
@@ -3002,12 +3000,6 @@ int processEventsWhileBlocked(int iel) {
int iterations = 4; /* See the function top-comment. */
int count = 0;
client *c = serverTL->current_client;
if (c != nullptr)
{
serverAssert(c->flags & CLIENT_PROTECTED);
c->lock.unlock();
}
aeReleaseLock();
while (iterations--) {
int events = 0;
@@ -3016,11 +3008,7 @@ int processEventsWhileBlocked(int iel) {
if (!events) break;
count += events;
}
AeLocker locker;
if (c != nullptr)
c->lock.lock();
locker.arm(c);
locker.release();
aeAcquireLock();
return count;
}
-4
View File
@@ -1302,10 +1302,6 @@ NULL
* because we update the access time only
* when the key is read or overwritten. */
addReplyLongLong(c,LFUDecrAndReturn(o));
} else if (!strcasecmp(szFromObj(c->argv[1]), "lastmodified") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
== NULL) return;
addReplyLongLong(c, (g_pserver->mstime - (o->mvcc_tstamp >> MVCC_MS_SHIFT)) / 1000);
} else {
addReplySubcommandSyntaxError(c);
}
+4 -8
View File
@@ -301,11 +301,9 @@ int pubsubPublishMessage(robj *channel, robj *message) {
client *c = reinterpret_cast<client*>(ln->value);
if (c->flags & CLIENT_CLOSE_ASAP) // avoid blocking if the write will be ignored
continue;
if (FCorrectThread(c))
fastlock_lock(&c->lock);
fastlock_lock(&c->lock);
addReplyPubsubMessage(c,channel,message);
if (FCorrectThread(c))
fastlock_unlock(&c->lock);
fastlock_unlock(&c->lock);
receivers++;
}
}
@@ -323,12 +321,10 @@ int pubsubPublishMessage(robj *channel, robj *message) {
{
if (pat->pclient->flags & CLIENT_CLOSE_ASAP)
continue;
if (FCorrectThread(pat->pclient))
fastlock_lock(&pat->pclient->lock);
fastlock_lock(&pat->pclient->lock);
addReplyPubsubPatMessage(pat->pclient,
pat->pattern,channel,message);
if (FCorrectThread(pat->pclient))
fastlock_unlock(&pat->pclient->lock);
fastlock_unlock(&pat->pclient->lock);
receivers++;
}
}
+35 -40
View File
@@ -2107,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 */
@@ -2119,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
@@ -2141,7 +2136,6 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
else
{
decrRefCount(val);
val = nullptr;
}
}
@@ -2185,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;
@@ -2204,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;
@@ -2273,7 +2268,7 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
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.
*
@@ -2312,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);
}
}
}
@@ -2407,17 +2402,17 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
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) {
clientids[numfds] = replica->id;
fds[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);
}
}
@@ -2451,19 +2446,19 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
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
/* 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> <replica[0].id> <replica[0].error> ...
* <len> <slave[0].id> <slave[0].error> ...
*
* len, replica IDs, and replica errors, are all uint64_t integers,
* 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 replica's client ID, so that the master
* can match the report with a specific replica, and 'error' is
* 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);
@@ -2504,12 +2499,12 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
* replicationSetupSlaveForFullResync() turned it into BGSAVE_END */
listRewind(g_pserver->slaves,&li);
while((ln = listNext(&li))) {
client *replica = (client*)ln->value;
client *slave = (client*)ln->value;
int j;
for (j = 0; j < numfds; j++) {
if (replica->id == clientids[j]) {
replica->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
if (slave->id == clientids[j]) {
slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
break;
}
}
@@ -2603,17 +2598,17 @@ rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi) {
/* 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;
}
@@ -2624,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;
@@ -2632,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. */
-1
View File
@@ -136,7 +136,6 @@ 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(pid_t childpid);
+1 -3
View File
@@ -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;
@@ -173,8 +173,6 @@ typedef struct redisConfig {
sds appendonly;
} redisConfig;
int g_fInCrash = false;
/* Prototypes */
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
static void createMissingClients(client c);
+2 -2
View File
@@ -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
*
-2
View File
@@ -90,8 +90,6 @@ int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253
int *spectrum_palette;
int spectrum_palette_size;
int g_fInCrash = 0;
/*------------------------------------------------------------------------------
* Utility functions
*--------------------------------------------------------------------------- */
+1 -1
View File
@@ -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
View File
@@ -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)
+236 -322
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -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) {
+51 -71
View File
@@ -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},
@@ -2110,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]);
@@ -2515,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;
@@ -2708,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
@@ -2720,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)++;
@@ -2732,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)++;
@@ -2748,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,
@@ -2814,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
@@ -2878,7 +2873,7 @@ static void initServerThread(struct redisServerThreadVars *pvar, int fMain)
exit(1);
}
fastlock_init(&pvar->lockPendingWrite, "lockPendingWrite");
fastlock_init(&pvar->lockPendingWrite);
if (!fMain)
{
@@ -2925,11 +2920,12 @@ void initServer(void) {
signal(SIGPIPE, SIG_IGN);
setupSignalHandlers();
fastlock_init(&g_pserver->flock);
g_pserver->db = (redisDb*)zmalloc(sizeof(redisDb)*cserver.dbnum, MALLOC_LOCAL);
/* 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].pdict = dictCreate(&dbDictType,NULL);
g_pserver->db[j].setexpire = new(MALLOC_LOCAL) expireset();
g_pserver->db[j].expireitr = g_pserver->db[j].setexpire->end();
@@ -2963,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;
@@ -3354,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;
@@ -3590,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
@@ -3638,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) &&
@@ -3661,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 &&
@@ -3793,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();
@@ -4105,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,
@@ -4471,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";
@@ -4495,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++;
}
}
@@ -4610,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;
@@ -4837,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);
@@ -4857,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 = "";
@@ -4985,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);
@@ -4995,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);
}
}
@@ -5217,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) {
@@ -5270,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)
+66 -58
View File
@@ -95,7 +95,8 @@ extern int g_fTestMode;
struct redisObject;
class robj_roptr
{
const redisObject *m_ptr;
protected:
const redisObject *m_ptr = nullptr;
public:
robj_roptr()
@@ -144,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
@@ -331,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. */
@@ -395,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. */
@@ -421,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. */
@@ -434,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 */
@@ -543,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. */
@@ -722,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;
@@ -731,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;
@@ -802,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);
@@ -843,7 +863,6 @@ class expireEntry {
public:
class iter
{
friend class expireEntry;
expireEntry *m_pentry = nullptr;
size_t m_idx = 0;
@@ -979,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)
{
@@ -1043,9 +1054,6 @@ typedef struct clientReplyBlock {
* 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 {
redisDb()
: expireitr(nullptr)
{};
dict *pdict; /* The keyspace for this DB */
expireset *setexpire;
expireset::setiter expireitr;
@@ -1200,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. */
@@ -1209,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 */
@@ -1435,7 +1443,7 @@ struct redisServerThreadVars {
client blocked on a module command needs
to be processed. */
client *lua_client = nullptr; /* The "fake client" to query Redis from Lua */
struct fastlock lockPendingWrite { "thread pending write" };
struct fastlock lockPendingWrite;
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
long unsigned commandsExecuted = 0;
};
@@ -1453,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. */
@@ -1663,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 {
@@ -1683,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 */
@@ -1701,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 */
@@ -1772,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. */
@@ -1817,6 +1825,8 @@ struct redisServer {
int fActiveReplica; /* Can this replica also be a master? */
struct fastlock flock;
// Format:
// Lower 20 bits: a counter incrementing for each command executed in the same millisecond
// Upper 44 bits: mstime (least significant 44-bits) enough for ~500 years before rollover from date of addition
@@ -2039,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);
@@ -2193,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);
@@ -2433,7 +2442,6 @@ int rewriteConfig(char *path);
/* db.c -- Keyspace access API */
int removeExpire(redisDb *db, robj *key);
int removeExpireCore(redisDb *db, robj *key, dictEntry *de);
int removeSubkeyExpire(redisDb *db, robj *key, robj *subkey);
void propagateExpire(redisDb *db, robj *key, int lazy);
int expireIfNeeded(redisDb *db, robj *key);
expireEntry *getExpire(redisDb *db, robj_roptr key);
@@ -2634,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);
@@ -2720,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);
@@ -2795,7 +2803,7 @@ void xorDigest(unsigned char *digest, const void *ptr, size_t len);
int populateCommandTableParseFlags(struct redisCommand *c, const char *strflags);
int moduleGILAcquiredByModule(void);
extern int g_fInCrash;
extern bool g_fInCrash;
static inline int GlobalLocksAcquired(void) // Used in asserts to verify all global locks are correctly acquired for a server-thread to operate
{
return aeThreadOwnsLock() || moduleGILAcquiredByModule() || g_fInCrash;
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -1,3 +1,3 @@
#define KEYDB_REAL_VERSION "5.1.1"
#define KEYDB_REAL_VERSION "0.0.0"
extern const char *KEYDB_SET_VERSION; // Unlike real version, this can be overriden by the config
+1 -1
View File
@@ -6,7 +6,7 @@ if {$system_name eq {linux} || $system_name eq {darwin}} {
test "Server is able to generate a stack trace on selected systems" {
r config set watchdog-period 200
r debug sleep 1
set pattern "*watchdogSignalHandler*"
set pattern "*debugCommand*"
set retry 10
while {$retry} {
set result [exec tail -100 < [srv 0 stdout]]
-13
View File
@@ -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"
}
}
}
}
-1
View File
@@ -35,7 +35,6 @@ set ::all_tests {
unit/quit
unit/aofrw
unit/acl
unit/rreplay
integration/block-repl
integration/replication
integration/replication-2
-58
View File
@@ -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
-30
View File
@@ -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
}
+1 -1
View File
@@ -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