Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33b5f22e31 | ||
|
|
61671a0dbe | ||
|
|
a4961a9a1d | ||
|
|
eed8495bf1 | ||
|
|
9acac9ceb0 | ||
|
|
1c894f5989 | ||
|
|
e2871e21fd | ||
|
|
8046756f3b | ||
|
|
f159f8034a | ||
|
|
f7501fb1d3 | ||
|
|
2a7da8a736 |
+4
-59
@@ -563,51 +563,6 @@ lua-time-limit 5000
|
||||
#
|
||||
# cluster-node-timeout 15000
|
||||
|
||||
# A slave of a failing master will avoid to start a failover if its data
|
||||
# looks too old.
|
||||
#
|
||||
# There is no simple way for a slave to actually have a exact measure of
|
||||
# its "data age", so the following two checks are performed:
|
||||
#
|
||||
# 1) If there are multiple slaves able to failover, they exchange messages
|
||||
# in order to try to give an advantage to the slave with the best
|
||||
# replication offset (more data from the master processed).
|
||||
# Slaves will try to get their rank by offset, and apply to the start
|
||||
# of the failover a delay proportional to their rank.
|
||||
#
|
||||
# 2) Every single slave computes the time of the last interaction with
|
||||
# its master. This can be the last ping or command received (if the master
|
||||
# is still in the "connected" state), or the time that elapsed since the
|
||||
# disconnection with the master (if the replication link is currently down).
|
||||
# If the last interaction is too old, the slave will not try to failover
|
||||
# at all.
|
||||
#
|
||||
# The point "2" can be tuned by user. Specifically a slave will not perform
|
||||
# the failover if, since the last interaction with the master, the time
|
||||
# elapsed is greater than:
|
||||
#
|
||||
# (node-timeout * slave-validity-factor) + repl-ping-slave-period
|
||||
#
|
||||
# So for example if node-timeout is 30 seconds, and the slave-validity-factor
|
||||
# is 10, and assuming a default repl-ping-slave-period of 10 seconds, the
|
||||
# slave will not try to failover if it was not able to talk with the master
|
||||
# for longer than 310 seconds.
|
||||
#
|
||||
# A large slave-validity-factor may allow slaves with too old data to failover
|
||||
# a master, while a too small value may prevent the cluster from being able to
|
||||
# elect a slave at all.
|
||||
#
|
||||
# For maximum availability, it is possible to set the slave-validity-factor
|
||||
# to a value of 0, which means, that slaves will always try to failover the
|
||||
# master regardless of the last time they interacted with the master.
|
||||
# (However they'll always try to apply a delay proportional to their
|
||||
# offset rank).
|
||||
#
|
||||
# Zero is the only value able to guarantee that when all the partitions heal
|
||||
# the cluster will always be able to continue.
|
||||
#
|
||||
# cluster-slave-validity-factor 10
|
||||
|
||||
# Cluster slaves are able to migrate to orphaned masters, that are masters
|
||||
# that are left without working slaves. This improves the cluster ability
|
||||
# to resist to failures as otherwise an orphaned master can't be failed over
|
||||
@@ -727,20 +682,6 @@ set-max-intset-entries 512
|
||||
zset-max-ziplist-entries 128
|
||||
zset-max-ziplist-value 64
|
||||
|
||||
# HyperLogLog sparse representation bytes limit. The limit includes the
|
||||
# 16 bytes header. When an HyperLogLog using the sparse representation crosses
|
||||
# this limit, it is converted into the dense representation.
|
||||
#
|
||||
# A value greater than 16000 is totally useless, since at that point the
|
||||
# dense representation is more memory efficient.
|
||||
#
|
||||
# The suggested value is ~ 3000 in order to have the benefits of
|
||||
# the space efficient encoding without slowing down too much PFADD,
|
||||
# which is O(N) with the sparse encoding. The value can be raised to
|
||||
# ~ 10000 when CPU is not a concern, but space is, and the data set is
|
||||
# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.
|
||||
hll-sparse-max-bytes 3000
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||
@@ -821,3 +762,7 @@ hz 10
|
||||
# big latency spikes.
|
||||
aof-rewrite-incremental-fsync yes
|
||||
|
||||
# Transparently compress string objects in memory using LZF.
|
||||
# For default this is set to no since it may affect performances, however
|
||||
# when caching things like HTML fragments this may result into a big win.
|
||||
memcompression no
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
|
||||
done
|
||||
|
||||
if [ -z $TCLSH ]
|
||||
then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test"
|
||||
exit 1
|
||||
fi
|
||||
$TCLSH tests/cluster/run.tcl $*
|
||||
+1
-1
@@ -11,4 +11,4 @@ then
|
||||
echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test"
|
||||
exit 1
|
||||
fi
|
||||
$TCLSH tests/sentinel/run.tcl $*
|
||||
$TCLSH tests/sentinel.tcl $*
|
||||
|
||||
@@ -4,13 +4,6 @@
|
||||
# The port that this sentinel instance will run on
|
||||
port 26379
|
||||
|
||||
# dir <working-directory>
|
||||
# Every long running process should have a well-defined working directory.
|
||||
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
|
||||
# for the process to don't interferer with administrative tasks such as
|
||||
# unmounting filesystems.
|
||||
dir /tmp
|
||||
|
||||
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
|
||||
#
|
||||
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
|
||||
@@ -20,12 +13,6 @@ dir /tmp
|
||||
# be elected by the majority of the known Sentinels in order to
|
||||
# start a failover, so no failover can be performed in minority.
|
||||
#
|
||||
# Slaves are auto-discovered, so you don't need to specify slaves in
|
||||
# any way. Sentinel itself will rewrite this configuration file adding
|
||||
# the slaves using additional configuration options.
|
||||
# Also note that the configuration file is rewritten when a
|
||||
# slave is promoted to master.
|
||||
#
|
||||
# Note: master name should not include special characters or spaces.
|
||||
# The valid charset is A-z 0-9 and the three characters ".-_".
|
||||
sentinel monitor mymaster 127.0.0.1 6379 2
|
||||
|
||||
+2
-2
@@ -496,7 +496,7 @@ int anetTcpAccept(char *err, int s, char *ip, size_t ip_len, int *port) {
|
||||
int fd;
|
||||
struct sockaddr_storage sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
if (sa.ss_family == AF_INET) {
|
||||
@@ -515,7 +515,7 @@ int anetUnixAccept(char *err, int s) {
|
||||
int fd;
|
||||
struct sockaddr_un sa;
|
||||
socklen_t salen = sizeof(sa);
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == -1)
|
||||
if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)
|
||||
return ANET_ERR;
|
||||
|
||||
return fd;
|
||||
|
||||
@@ -507,7 +507,6 @@ struct redisClient *createFakeClient(void) {
|
||||
c->reply_bytes = 0;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
c->watched_keys = listCreate();
|
||||
c->peerid = NULL;
|
||||
listSetFreeMethod(c->reply,decrRefCountVoid);
|
||||
listSetDupMethod(c->reply,dupClientReplyValue);
|
||||
initClientMultiState(c);
|
||||
@@ -561,7 +560,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
/* Serve the clients from time to time */
|
||||
if (!(loops++ % 1000)) {
|
||||
loadingProgress(ftello(fp));
|
||||
processEventsWhileBlocked();
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
}
|
||||
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
|
||||
@@ -261,6 +261,7 @@ void getbitCommand(redisClient *c) {
|
||||
|
||||
byte = bitoffset >> 3;
|
||||
bit = 7 - (bitoffset & 0x7);
|
||||
if (lzfEncodedObject(o)) o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
if (sdsEncodedObject(o)) {
|
||||
if (byte < sdslen(o->ptr))
|
||||
bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit);
|
||||
@@ -457,6 +458,7 @@ void bitcountCommand(redisClient *c) {
|
||||
|
||||
/* Set the 'p' pointer to the string, that can be just a stack allocated
|
||||
* array if our string was integer encoded. */
|
||||
if (lzfEncodedObject(o)) o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
p = (unsigned char*) llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
||||
@@ -526,6 +528,7 @@ void bitposCommand(redisClient *c) {
|
||||
|
||||
/* Set the 'p' pointer to the string, that can be just a stack allocated
|
||||
* array if our string was integer encoded. */
|
||||
if (lzfEncodedObject(o)) o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
p = (unsigned char*) llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
||||
|
||||
+67
-317
@@ -39,7 +39,6 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
/* A global reference to myself is handy to make code more clear.
|
||||
* Myself always points to server.cluster->myself, that is, the clusterNode
|
||||
@@ -70,8 +69,6 @@ void clusterDoBeforeSleep(int flags);
|
||||
void clusterSendUpdate(clusterLink *link, clusterNode *node);
|
||||
void resetManualFailover(void);
|
||||
void clusterCloseAllSlots(void);
|
||||
void clusterSetNodeAsMaster(clusterNode *n);
|
||||
void clusterDelNode(clusterNode *delnode);
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Initialization
|
||||
@@ -93,35 +90,12 @@ uint64_t clusterGetMaxEpoch(void) {
|
||||
return max;
|
||||
}
|
||||
|
||||
/* Load the cluster config from 'filename'.
|
||||
*
|
||||
* If the file does not exist or is zero-length (this may happen because
|
||||
* when we lock the nodes.conf file, we create a zero-length one for the
|
||||
* sake of locking if it does not already exist), REDIS_ERR is returned.
|
||||
* If the configuration was loaded from the file, REDIS_OK is returned. */
|
||||
int clusterLoadConfig(char *filename) {
|
||||
FILE *fp = fopen(filename,"r");
|
||||
struct stat sb;
|
||||
char *line;
|
||||
int maxline, j;
|
||||
|
||||
if (fp == NULL) {
|
||||
if (errno == ENOENT) {
|
||||
return REDIS_ERR;
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Loading the cluster node config from %s: %s",
|
||||
filename, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the file is zero-length: if so return REDIS_ERR to signal
|
||||
* we have to write the config. */
|
||||
if (fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
|
||||
fclose(fp);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (fp == NULL) return REDIS_ERR;
|
||||
|
||||
/* Parse the file. Note that single liens of the cluster config file can
|
||||
* be really long as they include all the hash slots of the node.
|
||||
@@ -306,8 +280,6 @@ int clusterSaveConfig(int do_fsync) {
|
||||
struct stat sb;
|
||||
int fd;
|
||||
|
||||
server.cluster->todo_before_sleep &= ~CLUSTER_TODO_SAVE_CONFIG;
|
||||
|
||||
/* Get the nodes description and concatenate our "vars" directive to
|
||||
* save currentEpoch and lastVoteEpoch. */
|
||||
ci = clusterGenNodesDescription(REDIS_NODE_HANDSHAKE);
|
||||
@@ -315,7 +287,7 @@ int clusterSaveConfig(int do_fsync) {
|
||||
(unsigned long long) server.cluster->currentEpoch,
|
||||
(unsigned long long) server.cluster->lastVoteEpoch);
|
||||
content_size = sdslen(ci);
|
||||
|
||||
|
||||
if ((fd = open(server.cluster_configfile,O_WRONLY|O_CREAT,0644))
|
||||
== -1) goto err;
|
||||
|
||||
@@ -327,10 +299,7 @@ int clusterSaveConfig(int do_fsync) {
|
||||
}
|
||||
}
|
||||
if (write(fd,ci,sdslen(ci)) != (ssize_t)sdslen(ci)) goto err;
|
||||
if (do_fsync) {
|
||||
server.cluster->todo_before_sleep &= ~CLUSTER_TODO_FSYNC_CONFIG;
|
||||
fsync(fd);
|
||||
}
|
||||
if (do_fsync) fsync(fd);
|
||||
|
||||
/* Truncate the file if needed to remove the final \n padding that
|
||||
* is just garbage. */
|
||||
@@ -354,46 +323,6 @@ void clusterSaveConfigOrDie(int do_fsync) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Lock the cluster config using flock(), and leaks the file descritor used to
|
||||
* acquire the lock so that the file will be locked forever.
|
||||
*
|
||||
* This works because we always update nodes.conf with a new version
|
||||
* in-place, reopening the file, and writing to it in place (later adjusting
|
||||
* the length with ftruncate()).
|
||||
*
|
||||
* On success REDIS_OK is returned, otherwise an error is logged and
|
||||
* the function returns REDIS_ERR to signal a lock was not acquired. */
|
||||
int clusterLockConfig(char *filename) {
|
||||
/* To lock it, we need to open the file in a way it is created if
|
||||
* it does not exist, otherwise there is a race condition with other
|
||||
* processes. */
|
||||
int fd = open(filename,O_WRONLY|O_CREAT,0644);
|
||||
if (fd == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Can't open %s in order to acquire a lock: %s",
|
||||
filename, strerror(errno));
|
||||
return REDIS_ERR;
|
||||
}
|
||||
|
||||
if (flock(fd,LOCK_EX|LOCK_NB) == -1) {
|
||||
if (errno == EWOULDBLOCK) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Sorry, the cluster configuration file %s is already used "
|
||||
"by a different Redis Cluster node. Please make sure that "
|
||||
"different nodes use different cluster configuration "
|
||||
"files.", filename);
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Impossible to lock %s: %s", filename, strerror(errno));
|
||||
}
|
||||
close(fd);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
/* Lock acquired: leak the 'fd' by not closing it, so that we'll retain the
|
||||
* lock to the file as long as the process exists. */
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
void clusterInit(void) {
|
||||
int saveconf = 0;
|
||||
|
||||
@@ -415,13 +344,6 @@ void clusterInit(void) {
|
||||
server.cluster->stats_bus_messages_received = 0;
|
||||
memset(server.cluster->slots,0, sizeof(server.cluster->slots));
|
||||
clusterCloseAllSlots();
|
||||
|
||||
/* Lock the cluster config file to make sure every node uses
|
||||
* its own nodes.conf. */
|
||||
if (clusterLockConfig(server.cluster_configfile) == REDIS_ERR)
|
||||
exit(1);
|
||||
|
||||
/* Load or create a new nodes configuration. */
|
||||
if (clusterLoadConfig(server.cluster_configfile) == REDIS_ERR) {
|
||||
/* No configuration found. We will just use the random name provided
|
||||
* by the createClusterNode() function. */
|
||||
@@ -469,65 +391,6 @@ void clusterInit(void) {
|
||||
resetManualFailover();
|
||||
}
|
||||
|
||||
/* Reset a node performing a soft or hard reset:
|
||||
*
|
||||
* 1) All other nodes are forget.
|
||||
* 2) All the assigned / open slots are released.
|
||||
* 3) If the node is a slave, it turns into a master.
|
||||
* 5) Only for hard reset: a new Node ID is generated.
|
||||
* 6) Only for hard reset: currentEpoch and configEpoch are set to 0.
|
||||
* 7) The new configuration is saved and the cluster state updated. */
|
||||
void clusterReset(int hard) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int j;
|
||||
|
||||
/* Turn into master. */
|
||||
if (nodeIsSlave(myself)) {
|
||||
clusterSetNodeAsMaster(myself);
|
||||
replicationUnsetMaster();
|
||||
}
|
||||
|
||||
/* Close slots, reset manual failover state. */
|
||||
clusterCloseAllSlots();
|
||||
resetManualFailover();
|
||||
|
||||
/* Unassign all the slots. */
|
||||
for (j = 0; j < REDIS_CLUSTER_SLOTS; j++) clusterDelSlot(j);
|
||||
|
||||
/* Forget all the nodes, but myself. */
|
||||
di = dictGetSafeIterator(server.cluster->nodes);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
clusterNode *node = dictGetVal(de);
|
||||
|
||||
if (node == myself) continue;
|
||||
clusterDelNode(node);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
/* Hard reset only: set epochs to 0, change node ID. */
|
||||
if (hard) {
|
||||
sds oldname;
|
||||
|
||||
server.cluster->currentEpoch = 0;
|
||||
server.cluster->lastVoteEpoch = 0;
|
||||
myself->configEpoch = 0;
|
||||
|
||||
/* To change the Node ID we need to remove the old name from the
|
||||
* nodes table, change the ID, and re-add back with new name. */
|
||||
oldname = sdsnewlen(myself->name, REDIS_CLUSTER_NAMELEN);
|
||||
dictDelete(server.cluster->nodes,oldname);
|
||||
sdsfree(oldname);
|
||||
getRandomHexChars(myself->name, REDIS_CLUSTER_NAMELEN);
|
||||
clusterAddNode(myself);
|
||||
}
|
||||
|
||||
/* Make sure to persist the new config and update the state. */
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
|
||||
CLUSTER_TODO_UPDATE_STATE|
|
||||
CLUSTER_TODO_FSYNC_CONFIG);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* CLUSTER communication link
|
||||
* -------------------------------------------------------------------------- */
|
||||
@@ -558,42 +421,31 @@ void freeClusterLink(clusterLink *link) {
|
||||
zfree(link);
|
||||
}
|
||||
|
||||
#define MAX_CLUSTER_ACCEPTS_PER_CALL 1000
|
||||
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cport, cfd;
|
||||
int max = MAX_CLUSTER_ACCEPTS_PER_CALL;
|
||||
char cip[REDIS_IP_STR_LEN];
|
||||
clusterLink *link;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
/* If the server is starting up, don't accept cluster connections:
|
||||
* UPDATE messages may interact with the database content. */
|
||||
if (server.masterhost == NULL && server.loading) return;
|
||||
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (errno != EWOULDBLOCK)
|
||||
redisLog(REDIS_VERBOSE,
|
||||
"Accepting cluster node: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
anetNonBlock(NULL,cfd);
|
||||
anetEnableTcpNoDelay(NULL,cfd);
|
||||
|
||||
/* Use non-blocking I/O for cluster messages. */
|
||||
redisLog(REDIS_VERBOSE,"Accepted cluster node %s:%d", cip, cport);
|
||||
/* Create a link object we use to handle the connection.
|
||||
* It gets passed to the readable handler when data is available.
|
||||
* Initiallly the link->node pointer is set to NULL as we don't know
|
||||
* which node is, but the right node is references once we know the
|
||||
* node identity. */
|
||||
link = createClusterLink(NULL);
|
||||
link->fd = cfd;
|
||||
aeCreateFileEvent(server.el,cfd,AE_READABLE,clusterReadHandler,link);
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
redisLog(REDIS_VERBOSE,"Accepting cluster node: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
anetNonBlock(NULL,cfd);
|
||||
anetEnableTcpNoDelay(NULL,cfd);
|
||||
|
||||
/* Use non-blocking I/O for cluster messages. */
|
||||
/* IPV6: might want to wrap a v6 address in [] */
|
||||
redisLog(REDIS_VERBOSE,"Accepted cluster node %s:%d", cip, cport);
|
||||
/* We need to create a temporary node in order to read the incoming
|
||||
* packet in a valid contest. This node will be released once we
|
||||
* read the packet and reply. */
|
||||
link = createClusterLink(NULL);
|
||||
link->fd = cfd;
|
||||
aeCreateFileEvent(server.el,cfd,AE_READABLE,clusterReadHandler,link);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
@@ -817,7 +669,7 @@ void freeClusterNode(clusterNode *n) {
|
||||
/* Add a node to the nodes hash table */
|
||||
int clusterAddNode(clusterNode *node) {
|
||||
int retval;
|
||||
|
||||
|
||||
retval = dictAdd(server.cluster->nodes,
|
||||
sdsnewlen(node->name,REDIS_CLUSTER_NAMELEN), node);
|
||||
return (retval == DICT_OK) ? REDIS_OK : REDIS_ERR;
|
||||
@@ -880,7 +732,7 @@ clusterNode *clusterLookupNode(char *name) {
|
||||
void clusterRenameNode(clusterNode *node, char *newname) {
|
||||
int retval;
|
||||
sds s = sdsnewlen(node->name, REDIS_CLUSTER_NAMELEN);
|
||||
|
||||
|
||||
redisLog(REDIS_DEBUG,"Renaming node %.40s into %.40s",
|
||||
node->name, newname);
|
||||
retval = dictDelete(server.cluster->nodes, s);
|
||||
@@ -1282,15 +1134,6 @@ void clusterSetNodeAsMaster(clusterNode *n) {
|
||||
void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoch, unsigned char *slots) {
|
||||
int j;
|
||||
clusterNode *curmaster, *newmaster = NULL;
|
||||
/* The dirty slots list is a list of slots for which we lose the ownership
|
||||
* while having still keys inside. This usually happens after a failover
|
||||
* or after a manual cluster reconfiguration operated by the admin.
|
||||
*
|
||||
* If the update message is not able to demote a master to slave (in this
|
||||
* case we'll resync with the master updating the whole key space), we
|
||||
* need to delete all the keys in the slots we lost ownership. */
|
||||
uint16_t dirty_slots[REDIS_CLUSTER_SLOTS];
|
||||
int dirty_slots_count = 0;
|
||||
|
||||
/* Here we set curmaster to this node or the node this node
|
||||
* replicates to if it's a slave. In the for loop we are
|
||||
@@ -1320,14 +1163,24 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
|
||||
if (server.cluster->slots[j] == NULL ||
|
||||
server.cluster->slots[j]->configEpoch < senderConfigEpoch)
|
||||
{
|
||||
/* Was this slot mine, and still contains keys? Mark it as
|
||||
* a dirty slot. */
|
||||
/* Was this slot mine, and still contains keys? Something
|
||||
* odd happened, put the slot in importing state so that
|
||||
* redis-trib fix can detect the condition (and no further
|
||||
* updates will be processed before the slot gets fixed). */
|
||||
if (server.cluster->slots[j] == myself &&
|
||||
countKeysInSlot(j) &&
|
||||
sender != myself)
|
||||
{
|
||||
dirty_slots[dirty_slots_count] = j;
|
||||
dirty_slots_count++;
|
||||
redisLog(REDIS_WARNING,
|
||||
"I received an update for slot %d. "
|
||||
"%.40s claims it with config %llu, "
|
||||
"I've it assigned to myself with config %llu. "
|
||||
"I've still keys about this slot! "
|
||||
"Putting the slot in IMPORTING state. "
|
||||
"Please run the 'redis-trib fix' command.",
|
||||
j, sender->name, senderConfigEpoch,
|
||||
myself->configEpoch);
|
||||
server.cluster->importing_slots_from[j] = sender;
|
||||
}
|
||||
|
||||
if (server.cluster->slots[j] == curmaster)
|
||||
@@ -1356,16 +1209,6 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
|
||||
CLUSTER_TODO_UPDATE_STATE|
|
||||
CLUSTER_TODO_FSYNC_CONFIG);
|
||||
} else if (dirty_slots_count) {
|
||||
/* If we are here, we received an update message which removed
|
||||
* ownership for certain slots we still have keys about, but still
|
||||
* we are serving some slots, so this master node was not demoted to
|
||||
* a slave.
|
||||
*
|
||||
* In order to maintain a consistent state between keys and slots
|
||||
* we need to remove all the keys from the slots we lost. */
|
||||
for (j = 0; j < dirty_slots_count; j++)
|
||||
delKeysInSlot(dirty_slots[j]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1849,7 +1692,7 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
/* This function is called when we detect the link with this node is lost.
|
||||
We set the node as no longer connected. The Cluster Cron will detect
|
||||
this connection and will try to get it connected again.
|
||||
|
||||
|
||||
Instead if the node is a temporary node used to accept a query, we
|
||||
completely free the node on error. */
|
||||
void handleLinkIOError(clusterLink *link) {
|
||||
@@ -1959,7 +1802,7 @@ void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
|
||||
|
||||
/* Send a message to all the nodes that are part of the cluster having
|
||||
* a connected link.
|
||||
*
|
||||
*
|
||||
* It is guaranteed that this function will never have as a side effect
|
||||
* some node->link to be invalidated, so it is safe to call this function
|
||||
* from event handlers that will do stuff with node links later. */
|
||||
@@ -2053,7 +1896,7 @@ void clusterSendPing(clusterLink *link, int type) {
|
||||
if (link->node && type == CLUSTERMSG_TYPE_PING)
|
||||
link->node->ping_sent = mstime();
|
||||
clusterBuildMessageHdr(hdr,type);
|
||||
|
||||
|
||||
/* Populate the gossip fields */
|
||||
while(freshnodes > 0 && gossipcount < 3) {
|
||||
dictEntry *de = dictGetRandomKey(server.cluster->nodes);
|
||||
@@ -2372,8 +2215,6 @@ void clusterHandleSlaveFailover(void) {
|
||||
int j;
|
||||
mstime_t auth_timeout, auth_retry_time;
|
||||
|
||||
server.cluster->todo_before_sleep &= ~CLUSTER_TODO_HANDLE_FAILOVER;
|
||||
|
||||
/* Compute the failover timeout (the max time we have to send votes
|
||||
* and wait for replies), and the failover retry time (the time to wait
|
||||
* before waiting again.
|
||||
@@ -2385,8 +2226,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
if (auth_timeout < 2000) auth_timeout = 2000;
|
||||
auth_retry_time = auth_timeout*2;
|
||||
|
||||
/* Pre conditions to run the function, that must be met both in case
|
||||
* of an automatic or manual failover:
|
||||
/* Pre conditions to run the function:
|
||||
* 1) We are a slave.
|
||||
* 2) Our master is flagged as FAIL, or this is a manual failover.
|
||||
* 3) It is serving slots. */
|
||||
@@ -2398,10 +2238,9 @@ void clusterHandleSlaveFailover(void) {
|
||||
/* Set data_age to the number of seconds we are disconnected from
|
||||
* the master. */
|
||||
if (server.repl_state == REDIS_REPL_CONNECTED) {
|
||||
data_age = (mstime_t)(server.unixtime - server.master->lastinteraction)
|
||||
* 1000;
|
||||
data_age = (server.unixtime - server.master->lastinteraction) * 1000;
|
||||
} else {
|
||||
data_age = (mstime_t)(server.unixtime - server.repl_down_since) * 1000;
|
||||
data_age = (server.unixtime - server.repl_down_since) * 1000;
|
||||
}
|
||||
|
||||
/* Remove the node timeout from the data age as it is fine that we are
|
||||
@@ -2410,17 +2249,13 @@ void clusterHandleSlaveFailover(void) {
|
||||
if (data_age > server.cluster_node_timeout)
|
||||
data_age -= server.cluster_node_timeout;
|
||||
|
||||
/* Check if our data is recent enough according to the slave validity
|
||||
* factor configured by the user.
|
||||
*
|
||||
* Check bypassed for manual failovers. */
|
||||
if (server.cluster_slave_validity_factor &&
|
||||
data_age >
|
||||
(((mstime_t)server.repl_ping_slave_period * 1000) +
|
||||
(server.cluster_node_timeout * server.cluster_slave_validity_factor)))
|
||||
{
|
||||
if (!manual_failover) return;
|
||||
}
|
||||
/* Check if our data is recent enough. For now we just use a fixed
|
||||
* constant of ten times the node timeout since the cluster should
|
||||
* react much faster to a master down. */
|
||||
if (data_age >
|
||||
(server.repl_ping_slave_period * 1000) +
|
||||
(server.cluster_node_timeout * REDIS_CLUSTER_SLAVE_VALIDITY_MULT))
|
||||
return;
|
||||
|
||||
/* If the previous failover attempt timedout and the retry time has
|
||||
* elapsed, we can setup a new one. */
|
||||
@@ -2456,9 +2291,7 @@ void clusterHandleSlaveFailover(void) {
|
||||
|
||||
/* It is possible that we received more updated offsets from other
|
||||
* slaves for the same master since we computed our election delay.
|
||||
* Update the delay if our rank changed.
|
||||
*
|
||||
* Not performed if this is a manual failover. */
|
||||
* Update the delay if our rank changed. */
|
||||
if (server.cluster->failover_auth_sent == 0 &&
|
||||
server.cluster->mf_end == 0)
|
||||
{
|
||||
@@ -2504,7 +2337,10 @@ void clusterHandleSlaveFailover(void) {
|
||||
* this slave to a master.
|
||||
*
|
||||
* 1) Turn this node into a master. */
|
||||
clusterSetNodeAsMaster(myself);
|
||||
clusterNodeRemoveSlave(myself->slaveof, myself);
|
||||
myself->flags &= ~REDIS_NODE_SLAVE;
|
||||
myself->flags |= REDIS_NODE_MASTER;
|
||||
myself->slaveof = NULL;
|
||||
replicationUnsetMaster();
|
||||
|
||||
/* 2) Claim all the slots assigned to our master. */
|
||||
@@ -2948,8 +2784,7 @@ void clusterBeforeSleep(void) {
|
||||
clusterSaveConfigOrDie(fsync);
|
||||
}
|
||||
|
||||
/* Reset our flags (not strictly needed since every single function
|
||||
* called for flags set should be able to clear its flag). */
|
||||
/* Reset our flags. */
|
||||
server.cluster->todo_before_sleep = 0;
|
||||
}
|
||||
|
||||
@@ -3066,8 +2901,6 @@ void clusterUpdateState(void) {
|
||||
static mstime_t among_minority_time;
|
||||
static mstime_t first_call_time = 0;
|
||||
|
||||
server.cluster->todo_before_sleep &= ~CLUSTER_TODO_UPDATE_STATE;
|
||||
|
||||
/* If this is a master node, wait some time before turning the state
|
||||
* into OK, since it is not a good idea to rejoin the cluster as a writable
|
||||
* master, after a reboot, without giving the cluster a chance to
|
||||
@@ -3076,7 +2909,6 @@ void clusterUpdateState(void) {
|
||||
* to don't count the DB loading time. */
|
||||
if (first_call_time == 0) first_call_time = mstime();
|
||||
if (nodeIsMaster(myself) &&
|
||||
server.cluster->state == REDIS_CLUSTER_FAIL &&
|
||||
mstime() - first_call_time < REDIS_CLUSTER_WRITABLE_DELAY) return;
|
||||
|
||||
/* Start assuming the state is OK. We'll turn it into FAIL if there
|
||||
@@ -3121,7 +2953,7 @@ void clusterUpdateState(void) {
|
||||
* of the netsplit because of lack of majority. */
|
||||
{
|
||||
int needed_quorum = (server.cluster->size / 2) + 1;
|
||||
|
||||
|
||||
if (unreachable_masters >= needed_quorum) {
|
||||
new_state = REDIS_CLUSTER_FAIL;
|
||||
among_minority_time = mstime();
|
||||
@@ -3298,7 +3130,7 @@ sds clusterGenNodeDescription(clusterNode *node) {
|
||||
if (start == -1) start = j;
|
||||
}
|
||||
if (start != -1 && (!bit || j == REDIS_CLUSTER_SLOTS-1)) {
|
||||
if (bit && j == REDIS_CLUSTER_SLOTS-1) j++;
|
||||
if (j == REDIS_CLUSTER_SLOTS-1) j++;
|
||||
|
||||
if (start == j-1) {
|
||||
ci = sdscatprintf(ci," %d",start);
|
||||
@@ -3376,19 +3208,17 @@ void clusterCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
if (!strcasecmp(c->argv[1]->ptr,"meet") && c->argc == 4) {
|
||||
long long port;
|
||||
long port;
|
||||
|
||||
if (getLongLongFromObject(c->argv[3], &port) != REDIS_OK) {
|
||||
addReplyErrorFormat(c,"Invalid TCP port specified: %s",
|
||||
(char*)c->argv[3]->ptr);
|
||||
if (getLongFromObjectOrReply(c, c->argv[3], &port, NULL) != REDIS_OK) {
|
||||
addReplyError(c,"Invalid TCP port specified");
|
||||
return;
|
||||
}
|
||||
|
||||
if (clusterStartHandshake(c->argv[2]->ptr,port) == 0 &&
|
||||
errno == EINVAL)
|
||||
{
|
||||
addReplyErrorFormat(c,"Invalid node address specified: %s:%s",
|
||||
(char*)c->argv[2]->ptr, (char*)c->argv[3]->ptr);
|
||||
addReplyError(c,"Invalid node address specified");
|
||||
} else {
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
@@ -3728,27 +3558,12 @@ void clusterCommand(redisClient *c) {
|
||||
addReplyBulkCString(c,ni);
|
||||
sdsfree(ni);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"failover") &&
|
||||
(c->argc == 2 || c->argc == 3))
|
||||
{
|
||||
/* CLUSTER FAILOVER [FORCE] */
|
||||
int force = 0;
|
||||
|
||||
if (c->argc == 3) {
|
||||
if (!strcasecmp(c->argv[2]->ptr,"force")) {
|
||||
force = 1;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"failover") && c->argc == 2) {
|
||||
if (nodeIsMaster(myself)) {
|
||||
addReplyError(c,"You should send CLUSTER FAILOVER to a slave");
|
||||
return;
|
||||
} else if (!force &&
|
||||
(myself->slaveof == NULL || nodeFailed(myself->slaveof) ||
|
||||
myself->slaveof->link == NULL))
|
||||
} else if (myself->slaveof == NULL || nodeFailed(myself->slaveof) ||
|
||||
myself->slaveof->link == NULL)
|
||||
{
|
||||
addReplyError(c,"Master is down or failed, "
|
||||
"please use CLUSTER FAILOVER FORCE");
|
||||
@@ -3756,74 +3571,9 @@ void clusterCommand(redisClient *c) {
|
||||
}
|
||||
resetManualFailover();
|
||||
server.cluster->mf_end = mstime() + REDIS_CLUSTER_MF_TIMEOUT;
|
||||
|
||||
/* If this is a forced failover, we don't need to talk with our master
|
||||
* to agree about the offset. We just failover taking over it without
|
||||
* coordination. */
|
||||
if (force) {
|
||||
server.cluster->mf_can_start = 1;
|
||||
} else {
|
||||
clusterSendMFStart(myself->slaveof);
|
||||
}
|
||||
clusterSendMFStart(myself->slaveof);
|
||||
redisLog(REDIS_WARNING,"Manual failover user request accepted.");
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"set-config-epoch") && c->argc == 3)
|
||||
{
|
||||
/* CLUSTER SET-CONFIG-EPOCH <epoch>
|
||||
*
|
||||
* The user is allowed to set the config epoch only when a node is
|
||||
* totally fresh: no config epoch, no other known node, and so forth.
|
||||
* This happens at cluster creation time to start with a cluster where
|
||||
* every node has a different node ID, without to rely on the conflicts
|
||||
* resolution system which is too slow when a big cluster is created. */
|
||||
long long epoch;
|
||||
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&epoch,NULL) != REDIS_OK)
|
||||
return;
|
||||
|
||||
if (epoch < 0) {
|
||||
addReplyErrorFormat(c,"Invalid config epoch specified: %lld",epoch);
|
||||
} else if (dictSize(server.cluster->nodes) > 1) {
|
||||
addReplyError(c,"The user can assign a config epoch only when the "
|
||||
"node does not know any other node.");
|
||||
} else if (myself->configEpoch != 0) {
|
||||
addReplyError(c,"Node config epoch is already non-zero");
|
||||
} else {
|
||||
myself->configEpoch = epoch;
|
||||
/* No need to fsync the config here since in the unlucky event
|
||||
* of a failure to persist the config, the conflict resolution code
|
||||
* will assign an unique config to this node. */
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|
|
||||
CLUSTER_TODO_SAVE_CONFIG);
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reset") &&
|
||||
(c->argc == 2 || c->argc == 3))
|
||||
{
|
||||
/* CLUSTER RESET [SOFT|HARD] */
|
||||
int hard = 0;
|
||||
|
||||
/* Parse soft/hard argument. Default is soft. */
|
||||
if (c->argc == 3) {
|
||||
if (!strcasecmp(c->argv[2]->ptr,"hard")) {
|
||||
hard = 1;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"soft")) {
|
||||
hard = 0;
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Slaves can be reset while containing data, but not master nodes
|
||||
* that must be empty. */
|
||||
if (nodeIsMaster(myself) && dictSize(c->db->dict) != 0) {
|
||||
addReplyError(c,"CLUSTER RESET can't be called with "
|
||||
"master nodes containing keys");
|
||||
return;
|
||||
}
|
||||
clusterReset(hard);
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyError(c,"Wrong CLUSTER subcommand or number of arguments");
|
||||
}
|
||||
@@ -3929,7 +3679,7 @@ void restoreCommand(redisClient *c) {
|
||||
|
||||
/* Make sure this key does not already exist here... */
|
||||
if (!replace && lookupKeyWrite(c->db,c->argv[1]) != NULL) {
|
||||
addReply(c,shared.busykeyerr);
|
||||
addReplyError(c,"Target key name is busy.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -4124,7 +3874,7 @@ try_again:
|
||||
addReplySds(c,sdsnew("+NOKEY\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Connect */
|
||||
fd = migrateGetSocket(c,c->argv[1],c->argv[2],timeout);
|
||||
if (fd == -1) return; /* error sent to the client by migrateGetSocket() */
|
||||
|
||||
+1
-1
@@ -14,10 +14,10 @@
|
||||
/* The following defines are amunt of time, sometimes expressed as
|
||||
* multiplicators of the node timeout value (when ending with MULT). */
|
||||
#define REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT 15000
|
||||
#define REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY 10 /* Slave max data age factor. */
|
||||
#define REDIS_CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
|
||||
#define REDIS_CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if master is back. */
|
||||
#define REDIS_CLUSTER_FAIL_UNDO_TIME_ADD 10 /* Some additional time. */
|
||||
#define REDIS_CLUSTER_SLAVE_VALIDITY_MULT 10 /* Slave data validity. */
|
||||
#define REDIS_CLUSTER_FAILOVER_DELAY 5 /* Seconds */
|
||||
#define REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER 1
|
||||
#define REDIS_CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */
|
||||
|
||||
+12
-23
@@ -297,6 +297,10 @@ void loadServerConfigFromString(char *config) {
|
||||
if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"memcompression") && argc == 2) {
|
||||
if ((server.mem_compression = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"rdbchecksum") && argc == 2) {
|
||||
if ((server.rdb_checksum = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
@@ -391,8 +395,6 @@ void loadServerConfigFromString(char *config) {
|
||||
server.zset_max_ziplist_entries = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"zset-max-ziplist-value") && argc == 2) {
|
||||
server.zset_max_ziplist_value = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"hll-sparse-max-bytes") && argc == 2) {
|
||||
server.hll_sparse_max_bytes = memtoll(argv[1], NULL);
|
||||
} else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
|
||||
struct redisCommand *cmd = lookupCommand(argv[1]);
|
||||
int retval;
|
||||
@@ -434,15 +436,7 @@ void loadServerConfigFromString(char *config) {
|
||||
{
|
||||
server.cluster_migration_barrier = atoi(argv[1]);
|
||||
if (server.cluster_migration_barrier < 0) {
|
||||
err = "cluster migration barrier must zero or positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"cluster-slave-validity-factor")
|
||||
&& argc == 2)
|
||||
{
|
||||
server.cluster_slave_validity_factor = atoi(argv[1]);
|
||||
if (server.cluster_slave_validity_factor < 0) {
|
||||
err = "cluster slave validity factor must be zero or positive";
|
||||
err = "cluster migration barrier must be positive";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
|
||||
@@ -775,9 +769,6 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"zset-max-ziplist-value")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.zset_max_ziplist_value = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"hll-sparse-max-bytes")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.hll_sparse_max_bytes = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"lua-time-limit")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.lua_time_limit = ll;
|
||||
@@ -873,6 +864,11 @@ void configSetCommand(redisClient *c) {
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.rdb_compression = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"memcompression")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.mem_compression = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"notify-keyspace-events")) {
|
||||
int flags = keyspaceEventsStringToFlags(o->ptr);
|
||||
|
||||
@@ -905,10 +901,6 @@ void configSetCommand(redisClient *c) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.cluster_migration_barrier = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"cluster-slave-validity-factor")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR ||
|
||||
ll < 0) goto badfmt;
|
||||
server.cluster_slave_validity_factor = ll;
|
||||
} else {
|
||||
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
|
||||
(char*)c->argv[2]->ptr);
|
||||
@@ -991,8 +983,6 @@ void configGetCommand(redisClient *c) {
|
||||
server.zset_max_ziplist_entries);
|
||||
config_get_numerical_field("zset-max-ziplist-value",
|
||||
server.zset_max_ziplist_value);
|
||||
config_get_numerical_field("hll-sparse-max-bytes",
|
||||
server.hll_sparse_max_bytes);
|
||||
config_get_numerical_field("lua-time-limit",server.lua_time_limit);
|
||||
config_get_numerical_field("slowlog-log-slower-than",
|
||||
server.slowlog_log_slower_than);
|
||||
@@ -1013,7 +1003,6 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_numerical_field("hz",server.hz);
|
||||
config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
|
||||
config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
|
||||
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
|
||||
|
||||
/* Bool (yes/no) values */
|
||||
config_get_bool_field("no-appendfsync-on-rewrite",
|
||||
@@ -1026,6 +1015,7 @@ void configGetCommand(redisClient *c) {
|
||||
server.stop_writes_on_bgsave_err);
|
||||
config_get_bool_field("daemonize", server.daemonize);
|
||||
config_get_bool_field("rdbcompression", server.rdb_compression);
|
||||
config_get_bool_field("memcompression", server.mem_compression);
|
||||
config_get_bool_field("rdbchecksum", server.rdb_checksum);
|
||||
config_get_bool_field("activerehashing", server.activerehashing);
|
||||
config_get_bool_field("repl-disable-tcp-nodelay",
|
||||
@@ -1741,6 +1731,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigNumericalOption(state,"databases",server.dbnum,REDIS_DEFAULT_DBNUM);
|
||||
rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err,REDIS_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR);
|
||||
rewriteConfigYesNoOption(state,"rdbcompression",server.rdb_compression,REDIS_DEFAULT_RDB_COMPRESSION);
|
||||
rewriteConfigYesNoOption(state,"memcompression",server.mem_compression,REDIS_DEFAULT_MEM_COMPRESSION);
|
||||
rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,REDIS_DEFAULT_RDB_CHECKSUM);
|
||||
rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,REDIS_DEFAULT_RDB_FILENAME);
|
||||
rewriteConfigDirOption(state);
|
||||
@@ -1783,7 +1774,6 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,REDIS_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT);
|
||||
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER);
|
||||
rewriteConfigNumericalOption(state,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY);
|
||||
rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,REDIS_SLOWLOG_LOG_SLOWER_THAN);
|
||||
rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,REDIS_SLOWLOG_MAX_LEN);
|
||||
rewriteConfigNotifykeyspaceeventsOption(state);
|
||||
@@ -1794,7 +1784,6 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,REDIS_SET_MAX_INTSET_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,REDIS_ZSET_MAX_ZIPLIST_ENTRIES);
|
||||
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,REDIS_ZSET_MAX_ZIPLIST_VALUE);
|
||||
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES);
|
||||
rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,REDIS_DEFAULT_ACTIVE_REHASHING);
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ void setproctitle(const char *fmt, ...);
|
||||
|
||||
#if (__i386 || __amd64) && __GNUC__
|
||||
#define GNUC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
|
||||
#if (GNUC_VERSION >= 40100) || defined(__clang__)
|
||||
#if GNUC_VERSION >= 40100
|
||||
#define HAVE_ATOMIC
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -280,7 +280,6 @@ void delCommand(redisClient *c) {
|
||||
int deleted = 0, j;
|
||||
|
||||
for (j = 1; j < c->argc; j++) {
|
||||
expireIfNeeded(c->db,c->argv[j]);
|
||||
if (dbDelete(c->db,c->argv[j])) {
|
||||
signalModifiedKey(c->db,c->argv[j]);
|
||||
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
|
||||
@@ -1143,8 +1142,8 @@ unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int coun
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
n = zslFirstInRange(server.cluster->slots_to_keys, &range);
|
||||
|
||||
n = zslFirstInRange(server.cluster->slots_to_keys, range);
|
||||
while(n && n->score == hashslot && count--) {
|
||||
keys[j++] = n->obj;
|
||||
n = n->level[0].forward;
|
||||
@@ -1152,28 +1151,6 @@ unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int coun
|
||||
return j;
|
||||
}
|
||||
|
||||
/* Remove all the keys in the specified hash slot.
|
||||
* The number of removed items is returned. */
|
||||
unsigned int delKeysInSlot(unsigned int hashslot) {
|
||||
zskiplistNode *n;
|
||||
zrangespec range;
|
||||
int j = 0;
|
||||
|
||||
range.min = range.max = hashslot;
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
n = zslFirstInRange(server.cluster->slots_to_keys, &range);
|
||||
while(n && n->score == hashslot) {
|
||||
robj *key = n->obj;
|
||||
n = n->level[0].forward; /* Go to the next item before freeing it. */
|
||||
incrRefCount(key); /* Protect the object while freeing it. */
|
||||
dbDelete(&server.db[0],key);
|
||||
decrRefCount(key);
|
||||
j++;
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
unsigned int countKeysInSlot(unsigned int hashslot) {
|
||||
zskiplist *zsl = server.cluster->slots_to_keys;
|
||||
zskiplistNode *zn;
|
||||
@@ -1184,7 +1161,7 @@ unsigned int countKeysInSlot(unsigned int hashslot) {
|
||||
range.minex = range.maxex = 0;
|
||||
|
||||
/* Find first element in range */
|
||||
zn = zslFirstInRange(zsl, &range);
|
||||
zn = zslFirstInRange(zsl, range);
|
||||
|
||||
/* Use rank of first element, if any, to determine preliminary count */
|
||||
if (zn != NULL) {
|
||||
@@ -1192,7 +1169,7 @@ unsigned int countKeysInSlot(unsigned int hashslot) {
|
||||
count = (zsl->length - (rank - 1));
|
||||
|
||||
/* Find last element in range */
|
||||
zn = zslLastInRange(zsl, &range);
|
||||
zn = zslLastInRange(zsl, range);
|
||||
|
||||
/* Use rank of last element, if any, to determine the actual count */
|
||||
if (zn != NULL) {
|
||||
|
||||
+7
-6
@@ -308,8 +308,10 @@ void debugCommand(redisClient *c) {
|
||||
val = dictGetVal(de);
|
||||
key = dictGetKey(de);
|
||||
|
||||
if (val->type != REDIS_STRING || !sdsEncodedObject(val)) {
|
||||
addReplyError(c,"Not an sds encoded string.");
|
||||
if (val->type != REDIS_STRING ||
|
||||
(!sdsEncodedObject(val) && val->encoding != REDIS_ENCODING_LZF))
|
||||
{
|
||||
addReplyError(c,"Not an sds/lzf encoded string.");
|
||||
} else {
|
||||
addReplyStatusFormat(c,
|
||||
"key_sds_len:%lld, key_sds_avail:%lld, "
|
||||
@@ -326,7 +328,6 @@ void debugCommand(redisClient *c) {
|
||||
|
||||
if (getLongFromObjectOrReply(c, c->argv[2], &keys, NULL) != REDIS_OK)
|
||||
return;
|
||||
dictExpand(c->db->dict,keys);
|
||||
for (j = 0; j < keys; j++) {
|
||||
snprintf(buf,sizeof(buf),"key:%lu",j);
|
||||
key = createStringObject(buf,strlen(buf));
|
||||
@@ -702,7 +703,7 @@ void logCurrentClient(void) {
|
||||
int j;
|
||||
|
||||
redisLog(REDIS_WARNING, "--- CURRENT CLIENT INFO");
|
||||
client = catClientInfoString(sdsempty(),cc);
|
||||
client = getClientInfoString(cc);
|
||||
redisLog(REDIS_WARNING,"client: %s", client);
|
||||
sdsfree(client);
|
||||
for (j = 0; j < cc->argc; j++) {
|
||||
@@ -864,7 +865,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
|
||||
"\n=== REDIS BUG REPORT END. Make sure to include from START to END. ===\n\n"
|
||||
" Please report the crash opening an issue on github:\n\n"
|
||||
" http://github.com/antirez/redis/issues\n\n"
|
||||
" Suspect RAM error? Use redis-server --test-memory to verify it.\n\n"
|
||||
" Suspect RAM error? Use redis-server --test-memory to veryfy it.\n\n"
|
||||
);
|
||||
/* free(messages); Don't call free() with possibly corrupted memory. */
|
||||
if (server.daemonize) unlink(server.pidfile);
|
||||
@@ -947,7 +948,7 @@ void enableWatchdog(int period) {
|
||||
/* Watchdog was actually disabled, so we have to setup the signal
|
||||
* handler. */
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_ONSTACK | SA_SIGINFO;
|
||||
act.sa_flags = SA_NODEFER | SA_ONSTACK | SA_SIGINFO;
|
||||
act.sa_sigaction = watchdogSignalHandler;
|
||||
sigaction(SIGALRM, &act, NULL);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ typedef struct dictEntry {
|
||||
void *val;
|
||||
uint64_t u64;
|
||||
int64_t s64;
|
||||
double d;
|
||||
} v;
|
||||
struct dictEntry *next;
|
||||
} dictEntry;
|
||||
@@ -115,9 +114,6 @@ typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
#define dictSetUnsignedIntegerVal(entry, _val_) \
|
||||
do { entry->v.u64 = _val_; } while(0)
|
||||
|
||||
#define dictSetDoubleVal(entry, _val_) \
|
||||
do { entry->v.d = _val_; } while(0)
|
||||
|
||||
#define dictFreeKey(d, entry) \
|
||||
if ((d)->type->keyDestructor) \
|
||||
(d)->type->keyDestructor((d)->privdata, (entry)->key)
|
||||
@@ -139,7 +135,6 @@ typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
#define dictGetVal(he) ((he)->v.val)
|
||||
#define dictGetSignedIntegerVal(he) ((he)->v.s64)
|
||||
#define dictGetUnsignedIntegerVal(he) ((he)->v.u64)
|
||||
#define dictGetDoubleVal(he) ((he)->v.d)
|
||||
#define dictSlots(d) ((d)->ht[0].size+(d)->ht[1].size)
|
||||
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
|
||||
#define dictIsRehashing(ht) ((ht)->rehashidx != -1)
|
||||
|
||||
+2
-73
@@ -14,8 +14,7 @@ static char *commandGroups[] = {
|
||||
"transactions",
|
||||
"connection",
|
||||
"server",
|
||||
"scripting",
|
||||
"hyperloglog"
|
||||
"scripting"
|
||||
};
|
||||
|
||||
struct commandHelp {
|
||||
@@ -55,11 +54,6 @@ struct commandHelp {
|
||||
"Perform bitwise operations between strings",
|
||||
1,
|
||||
"2.6.0" },
|
||||
{ "BITPOS",
|
||||
"key bit [start] [end]",
|
||||
"Find first bit set or clear in a string",
|
||||
1,
|
||||
"2.8.7" },
|
||||
{ "BLPOP",
|
||||
"key [key ...] timeout",
|
||||
"Remove and get the first element in a list, or block until one is available",
|
||||
@@ -90,11 +84,6 @@ struct commandHelp {
|
||||
"Get the list of client connections",
|
||||
9,
|
||||
"2.4.0" },
|
||||
{ "CLIENT PAUSE",
|
||||
"timeout",
|
||||
"Stop processing commands from clients for some time",
|
||||
9,
|
||||
"2.9.50" },
|
||||
{ "CLIENT SETNAME",
|
||||
"connection-name",
|
||||
"Set the current connection name",
|
||||
@@ -110,11 +99,6 @@ struct commandHelp {
|
||||
"Reset the stats returned by INFO",
|
||||
9,
|
||||
"2.0.0" },
|
||||
{ "CONFIG REWRITE",
|
||||
"-",
|
||||
"Rewrite the configuration file with the in memory configuration",
|
||||
9,
|
||||
"2.8.0" },
|
||||
{ "CONFIG SET",
|
||||
"parameter value",
|
||||
"Set a configuration parameter to the given value",
|
||||
@@ -275,11 +259,6 @@ struct commandHelp {
|
||||
"Set multiple hash fields to multiple values",
|
||||
5,
|
||||
"2.0.0" },
|
||||
{ "HSCAN",
|
||||
"key cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate hash fields and associated values",
|
||||
5,
|
||||
"2.8.0" },
|
||||
{ "HSET",
|
||||
"key field value",
|
||||
"Set the string value of a hash field",
|
||||
@@ -381,7 +360,7 @@ struct commandHelp {
|
||||
1,
|
||||
"1.0.0" },
|
||||
{ "MIGRATE",
|
||||
"host port key destination-db timeout [COPY] [REPLACE]",
|
||||
"host port key destination-db timeout",
|
||||
"Atomically transfer a key from a Redis instance to another one.",
|
||||
0,
|
||||
"2.6.0" },
|
||||
@@ -430,21 +409,6 @@ struct commandHelp {
|
||||
"Set the expiration for a key as a UNIX timestamp specified in milliseconds",
|
||||
0,
|
||||
"2.6.0" },
|
||||
{ "PFADD",
|
||||
"key element [element ...]",
|
||||
"Adds the specified elements to the specified HyperLogLog.",
|
||||
11,
|
||||
"2.8.9" },
|
||||
{ "PFCOUNT",
|
||||
"key [key ...]",
|
||||
"Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).",
|
||||
11,
|
||||
"2.8.9" },
|
||||
{ "PFMERGE",
|
||||
"destkey sourcekey [sourcekey ...]",
|
||||
"Merge N different HyperLogLogs into a single one.",
|
||||
11,
|
||||
"2.8.9" },
|
||||
{ "PING",
|
||||
"-",
|
||||
"Ping the server",
|
||||
@@ -470,11 +434,6 @@ struct commandHelp {
|
||||
"Post a message to a channel",
|
||||
6,
|
||||
"2.0.0" },
|
||||
{ "PUBSUB",
|
||||
"subcommand [argument [argument ...]]",
|
||||
"Inspect the state of the Pub/Sub subsystem",
|
||||
6,
|
||||
"2.8.0" },
|
||||
{ "PUNSUBSCRIBE",
|
||||
"[pattern [pattern ...]]",
|
||||
"Stop listening for messages posted to channels matching the given patterns",
|
||||
@@ -535,11 +494,6 @@ struct commandHelp {
|
||||
"Synchronously save the dataset to disk",
|
||||
9,
|
||||
"1.0.0" },
|
||||
{ "SCAN",
|
||||
"cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate the keys space",
|
||||
0,
|
||||
"2.8.0" },
|
||||
{ "SCARD",
|
||||
"key",
|
||||
"Get the number of members in a set",
|
||||
@@ -665,11 +619,6 @@ struct commandHelp {
|
||||
"Remove one or more members from a set",
|
||||
3,
|
||||
"1.0.0" },
|
||||
{ "SSCAN",
|
||||
"key cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate Set elements",
|
||||
3,
|
||||
"2.8.0" },
|
||||
{ "STRLEN",
|
||||
"key",
|
||||
"Get the length of the value stored in a key",
|
||||
@@ -750,21 +699,11 @@ struct commandHelp {
|
||||
"Intersect multiple sorted sets and store the resulting sorted set in a new key",
|
||||
4,
|
||||
"2.0.0" },
|
||||
{ "ZLEXCOUNT",
|
||||
"key min max",
|
||||
"Count the number of members in a sorted set between a given lexicographical range",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZRANGE",
|
||||
"key start stop [WITHSCORES]",
|
||||
"Return a range of members in a sorted set, by index",
|
||||
4,
|
||||
"1.2.0" },
|
||||
{ "ZRANGEBYLEX",
|
||||
"key min max [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by lexicographical range",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZRANGEBYSCORE",
|
||||
"key min max [WITHSCORES] [LIMIT offset count]",
|
||||
"Return a range of members in a sorted set, by score",
|
||||
@@ -780,11 +719,6 @@ struct commandHelp {
|
||||
"Remove one or more members from a sorted set",
|
||||
4,
|
||||
"1.2.0" },
|
||||
{ "ZREMRANGEBYLEX",
|
||||
"key min max",
|
||||
"Remove all members in a sorted set between the given lexicographical range",
|
||||
4,
|
||||
"2.8.9" },
|
||||
{ "ZREMRANGEBYRANK",
|
||||
"key start stop",
|
||||
"Remove all members in a sorted set within the given indexes",
|
||||
@@ -810,11 +744,6 @@ struct commandHelp {
|
||||
"Determine the index of a member in a sorted set, with scores ordered from high to low",
|
||||
4,
|
||||
"2.0.0" },
|
||||
{ "ZSCAN",
|
||||
"key cursor [MATCH pattern] [COUNT count]",
|
||||
"Incrementally iterate sorted sets elements and associated scores",
|
||||
4,
|
||||
"2.8.0" },
|
||||
{ "ZSCORE",
|
||||
"key member",
|
||||
"Get the score associated with the given member in a sorted set",
|
||||
|
||||
+169
-1003
File diff suppressed because it is too large
Load Diff
+52
-89
@@ -118,7 +118,6 @@ redisClient *createClient(int fd) {
|
||||
c->watched_keys = listCreate();
|
||||
c->pubsub_channels = dictCreate(&setDictType,NULL);
|
||||
c->pubsub_patterns = listCreate();
|
||||
c->peerid = NULL;
|
||||
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
|
||||
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
|
||||
if (fd != -1) listAddNodeTail(server.clients,c);
|
||||
@@ -320,6 +319,11 @@ void addReply(redisClient *c, robj *obj) {
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
|
||||
_addReplyObjectToList(c,obj);
|
||||
decrRefCount(obj);
|
||||
} else if (obj->encoding == REDIS_ENCODING_LZF) {
|
||||
obj = getDecodedObject(obj);
|
||||
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
|
||||
_addReplyObjectToList(c,obj);
|
||||
decrRefCount(obj);
|
||||
} else {
|
||||
redisPanic("Wrong obj->encoding in addReply()");
|
||||
}
|
||||
@@ -489,7 +493,7 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
|
||||
|
||||
if (sdsEncodedObject(obj)) {
|
||||
len = sdslen(obj->ptr);
|
||||
} else {
|
||||
} else if (obj->encoding == REDIS_ENCODING_INT) {
|
||||
long n = (long)obj->ptr;
|
||||
|
||||
/* Compute how many bytes will take this integer as a radix 10 string */
|
||||
@@ -501,6 +505,9 @@ void addReplyBulkLen(redisClient *c, robj *obj) {
|
||||
while((n = n/10) != 0) {
|
||||
len++;
|
||||
}
|
||||
} else {
|
||||
/* LZF and others not handled explicitly. */
|
||||
len = stringObjectLen(obj);
|
||||
}
|
||||
|
||||
if (len < REDIS_SHARED_BULKHDR_LEN)
|
||||
@@ -552,7 +559,6 @@ void copyClientOutputBuffer(redisClient *dst, redisClient *src) {
|
||||
dst->reply_bytes = src->reply_bytes;
|
||||
}
|
||||
|
||||
#define MAX_ACCEPTS_PER_CALL 1000
|
||||
static void acceptCommonHandler(int fd, int flags) {
|
||||
redisClient *c;
|
||||
if ((c = createClient(fd)) == NULL) {
|
||||
@@ -582,44 +588,37 @@ static void acceptCommonHandler(int fd, int flags) {
|
||||
}
|
||||
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
|
||||
int cport, cfd;
|
||||
char cip[REDIS_IP_STR_LEN];
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (errno != EWOULDBLOCK)
|
||||
redisLog(REDIS_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(cfd,0);
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);
|
||||
acceptCommonHandler(cfd,0);
|
||||
}
|
||||
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
int cfd, max = MAX_ACCEPTS_PER_CALL;
|
||||
int cfd;
|
||||
REDIS_NOTUSED(el);
|
||||
REDIS_NOTUSED(mask);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
while(max--) {
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (errno != EWOULDBLOCK)
|
||||
redisLog(REDIS_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
acceptCommonHandler(cfd,REDIS_UNIX_SOCKET);
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == AE_ERR) {
|
||||
redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);
|
||||
return;
|
||||
}
|
||||
redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket);
|
||||
acceptCommonHandler(cfd,REDIS_UNIX_SOCKET);
|
||||
}
|
||||
|
||||
|
||||
static void freeClientArgv(redisClient *c) {
|
||||
int j;
|
||||
for (j = 0; j < c->argc; j++)
|
||||
@@ -764,7 +763,6 @@ void freeClient(redisClient *c) {
|
||||
if (c->name) decrRefCount(c->name);
|
||||
zfree(c->argv);
|
||||
freeClientMultiState(c);
|
||||
sdsfree(c->peerid);
|
||||
zfree(c);
|
||||
}
|
||||
|
||||
@@ -948,7 +946,7 @@ int processInlineBuffer(redisClient *c) {
|
||||
* multi bulk requests idempotent. */
|
||||
static void setProtocolError(redisClient *c, int pos) {
|
||||
if (server.verbosity >= REDIS_VERBOSE) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
sds client = getClientInfoString(c);
|
||||
redisLog(REDIS_VERBOSE,
|
||||
"Protocol error from client: %s", client);
|
||||
sdsfree(client);
|
||||
@@ -1010,10 +1008,8 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
newline = strchr(c->querybuf+pos,'\r');
|
||||
if (newline == NULL) {
|
||||
if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) {
|
||||
addReplyError(c,
|
||||
"Protocol error: too big bulk count string");
|
||||
addReplyError(c,"Protocol error: too big bulk count string");
|
||||
setProtocolError(c,0);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1186,7 +1182,7 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
return;
|
||||
}
|
||||
if (sdslen(c->querybuf) > server.client_max_querybuf_len) {
|
||||
sds ci = catClientInfoString(sdsempty(),c), bytes = sdsempty();
|
||||
sds ci = getClientInfoString(c), bytes = sdsempty();
|
||||
|
||||
bytes = sdscatrepr(bytes,c->querybuf,64);
|
||||
redisLog(REDIS_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);
|
||||
@@ -1217,7 +1213,7 @@ void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
*biggest_input_buffer = bib;
|
||||
}
|
||||
|
||||
/* This is a helper function for genClientPeerId().
|
||||
/* This is a helper function for getClientPeerId().
|
||||
* It writes the specified ip/port to "peerid" as a null termiated string
|
||||
* in the form ip:port if ip does not contain ":" itself, otherwise
|
||||
* [ip]:port format is used (for IPv6 addresses basically). */
|
||||
@@ -1241,7 +1237,7 @@ void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port) {
|
||||
* On failure the function still populates 'peerid' with the "?:0" string
|
||||
* in case you want to relax error checking or need to display something
|
||||
* anyway (see anetPeerToString implementation for more info). */
|
||||
int genClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
int port;
|
||||
|
||||
@@ -1257,26 +1253,12 @@ int genClientPeerId(redisClient *client, char *peerid, size_t peerid_len) {
|
||||
}
|
||||
}
|
||||
|
||||
/* This function returns the client peer id, by creating and caching it
|
||||
* if client->perrid is NULL, otherwise returning the cached value.
|
||||
* The Peer ID never changes during the life of the client, however it
|
||||
* is expensive to compute. */
|
||||
char *getClientPeerId(redisClient *c) {
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
|
||||
if (c->peerid == NULL) {
|
||||
genClientPeerId(c,peerid,sizeof(peerid));
|
||||
c->peerid = sdsnew(peerid);
|
||||
}
|
||||
return c->peerid;
|
||||
}
|
||||
|
||||
/* Concatenate a string representing the state of a client in an human
|
||||
* readable format, into the sds string 's'. */
|
||||
sds catClientInfoString(sds s, redisClient *client) {
|
||||
char flags[16], events[3], *p;
|
||||
/* Turn a Redis client into an sds string representing its state. */
|
||||
sds getClientInfoString(redisClient *client) {
|
||||
char peerid[REDIS_PEER_ID_LEN], flags[16], events[3], *p;
|
||||
int emask;
|
||||
|
||||
getClientPeerId(client,peerid,sizeof(peerid));
|
||||
p = flags;
|
||||
if (client->flags & REDIS_SLAVE) {
|
||||
if (client->flags & REDIS_MONITOR)
|
||||
@@ -1301,23 +1283,23 @@ sds catClientInfoString(sds s, redisClient *client) {
|
||||
if (emask & AE_READABLE) *p++ = 'r';
|
||||
if (emask & AE_WRITABLE) *p++ = 'w';
|
||||
*p = '\0';
|
||||
return sdscatfmt(s,
|
||||
"addr=%s fd=%i name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U obl=%U oll=%U omem=%U events=%s cmd=%s",
|
||||
getClientPeerId(client),
|
||||
return sdscatprintf(sdsempty(),
|
||||
"addr=%s fd=%d name=%s age=%ld idle=%ld flags=%s db=%d sub=%d psub=%d multi=%d qbuf=%lu qbuf-free=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s",
|
||||
peerid,
|
||||
client->fd,
|
||||
client->name ? (char*)client->name->ptr : "",
|
||||
(long long)(server.unixtime - client->ctime),
|
||||
(long long)(server.unixtime - client->lastinteraction),
|
||||
(long)(server.unixtime - client->ctime),
|
||||
(long)(server.unixtime - client->lastinteraction),
|
||||
flags,
|
||||
client->db->id,
|
||||
(int) dictSize(client->pubsub_channels),
|
||||
(int) listLength(client->pubsub_patterns),
|
||||
(client->flags & REDIS_MULTI) ? client->mstate.count : -1,
|
||||
(unsigned long long) sdslen(client->querybuf),
|
||||
(unsigned long long) sdsavail(client->querybuf),
|
||||
(unsigned long long) client->bufpos,
|
||||
(unsigned long long) listLength(client->reply),
|
||||
(unsigned long long) getClientOutputBufferMemoryUsage(client),
|
||||
(unsigned long) sdslen(client->querybuf),
|
||||
(unsigned long) sdsavail(client->querybuf),
|
||||
(unsigned long) client->bufpos,
|
||||
(unsigned long) listLength(client->reply),
|
||||
getClientOutputBufferMemoryUsage(client),
|
||||
events,
|
||||
client->lastcmd ? client->lastcmd->name : "NULL");
|
||||
}
|
||||
@@ -1328,11 +1310,14 @@ sds getAllClientsInfoString(void) {
|
||||
redisClient *client;
|
||||
sds o = sdsempty();
|
||||
|
||||
o = sdsMakeRoomFor(o,200*listLength(server.clients));
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds cs;
|
||||
|
||||
client = listNodeValue(ln);
|
||||
o = catClientInfoString(o,client);
|
||||
cs = getClientInfoString(client);
|
||||
o = sdscatsds(o,cs);
|
||||
sdsfree(cs);
|
||||
o = sdscatlen(o,"\n",1);
|
||||
}
|
||||
return o;
|
||||
@@ -1350,10 +1335,11 @@ void clientCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) {
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
char *peerid;
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
|
||||
client = listNodeValue(ln);
|
||||
peerid = getClientPeerId(client);
|
||||
if (getClientPeerId(client,peerid,sizeof(peerid)) == REDIS_ERR)
|
||||
continue;
|
||||
if (strcmp(peerid,c->argv[2]->ptr) == 0) {
|
||||
addReply(c,shared.ok);
|
||||
if (c == client) {
|
||||
@@ -1559,7 +1545,7 @@ void asyncCloseClientOnOutputBufferLimitReached(redisClient *c) {
|
||||
redisAssert(c->reply_bytes < ULONG_MAX-(1024*64));
|
||||
if (c->reply_bytes == 0 || c->flags & REDIS_CLOSE_ASAP) return;
|
||||
if (checkClientOutputBufferLimits(c)) {
|
||||
sds client = catClientInfoString(sdsempty(),c);
|
||||
sds client = getClientInfoString(c);
|
||||
|
||||
freeClientAsync(c);
|
||||
redisLog(REDIS_WARNING,"Client %s scheduled to be closed ASAP for overcoming of output buffer limits.", client);
|
||||
@@ -1633,26 +1619,3 @@ int clientsArePaused(void) {
|
||||
}
|
||||
return server.clients_paused;
|
||||
}
|
||||
|
||||
/* This function is called by Redis in order to process a few events from
|
||||
* time to time while blocked into some not interruptible operation.
|
||||
* This allows to reply to clients with the -LOADING error while loading the
|
||||
* data set at startup or after a full resynchronization with the master
|
||||
* and so forth.
|
||||
*
|
||||
* It calls the event loop in order to process a few events. Specifically we
|
||||
* try to call the event loop for times as long as we receive acknowledge that
|
||||
* some event was processed, in order to go forward with the accept, read,
|
||||
* write, close sequence needed to serve a client.
|
||||
*
|
||||
* The function returns the total number of events processed. */
|
||||
int processEventsWhileBlocked(void) {
|
||||
int iterations = 4; /* See the function top-comment. */
|
||||
int count = 0;
|
||||
while (iterations--) {
|
||||
int events = aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
if (!events) break;
|
||||
count += events;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
+112
-16
@@ -29,6 +29,7 @@
|
||||
*/
|
||||
|
||||
#include "redis.h"
|
||||
#include "lzf.h" /* LZF compression library */
|
||||
#include <math.h>
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -76,11 +77,8 @@ robj *createEmbeddedStringObject(char *ptr, size_t len) {
|
||||
|
||||
/* Create a string object with EMBSTR encoding if it is smaller than
|
||||
* REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
|
||||
* used.
|
||||
*
|
||||
* The current limit of 39 is chosen so that the biggest string object
|
||||
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
|
||||
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
|
||||
* used. */
|
||||
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 32
|
||||
robj *createStringObject(char *ptr, size_t len) {
|
||||
if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
|
||||
return createEmbeddedStringObject(ptr,len);
|
||||
@@ -152,6 +150,10 @@ robj *dupStringObject(robj *o) {
|
||||
d->encoding = REDIS_ENCODING_INT;
|
||||
d->ptr = o->ptr;
|
||||
return d;
|
||||
case REDIS_ENCODING_LZF:
|
||||
d = createObject(REDIS_STRING,sdsdup(o->ptr));
|
||||
d->encoding = REDIS_ENCODING_LZF;
|
||||
return d;
|
||||
default:
|
||||
redisPanic("Wrong encoding.");
|
||||
break;
|
||||
@@ -213,7 +215,8 @@ robj *createZsetZiplistObject(void) {
|
||||
}
|
||||
|
||||
void freeStringObject(robj *o) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW) {
|
||||
if (o->encoding == REDIS_ENCODING_RAW ||
|
||||
o->encoding == REDIS_ENCODING_LZF) {
|
||||
sdsfree(o->ptr);
|
||||
}
|
||||
}
|
||||
@@ -330,15 +333,24 @@ int checkType(redisClient *c, robj *o, int type) {
|
||||
|
||||
int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
if (intEncodedObject(o)) {
|
||||
if (llval) *llval = (long) o->ptr;
|
||||
return REDIS_OK;
|
||||
} else {
|
||||
} else if (sdsEncodedObject(o)) {
|
||||
return string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR;
|
||||
} else if (lzfEncodedObject(o)) {
|
||||
int retval;
|
||||
robj *decoded = getDecodedObject(o);
|
||||
retval = string2ll(o->ptr,sdslen(o->ptr),llval) ? REDIS_OK : REDIS_ERR;
|
||||
decrRefCount(decoded);
|
||||
return retval;
|
||||
} else {
|
||||
redisPanic("Unknown encoding.");
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to encode a string object in order to save space */
|
||||
/* Try to encode a string object in order to save space. */
|
||||
#define LZF_COMPR_STATIC_BUF (1024*32)
|
||||
robj *tryObjectEncoding(robj *o) {
|
||||
long value;
|
||||
sds s = o->ptr;
|
||||
@@ -397,6 +409,45 @@ robj *tryObjectEncoding(robj *o) {
|
||||
return emb;
|
||||
}
|
||||
|
||||
/* Try LZF compression for objects up to server.mem_compression_max_size
|
||||
* and greater than REDIS_ENCODING_EMBSTR_SIZE_LIMIT. */
|
||||
if (server.mem_compression && len <= server.mem_compression_max_size) {
|
||||
/* Allocate four more bytes in our buffer since we need to store
|
||||
* the size of the compressed string as header. */
|
||||
unsigned char compr_static[LZF_COMPR_STATIC_BUF];
|
||||
unsigned char *compr = compr_static;
|
||||
size_t comprlen, outlen;
|
||||
|
||||
/* Save want to save at least 25% of memory for this to make sense. */
|
||||
outlen = len-4-(len/4);
|
||||
|
||||
/* Use an heap allocated buffer if the output is too big for our
|
||||
* static buffer. We use the trick to directly allocating an empty
|
||||
* SDS string so we can use it directly to create the object later. */
|
||||
if (outlen+4 > LZF_COMPR_STATIC_BUF)
|
||||
compr = (unsigned char*) sdsnewlen(NULL,outlen+4);
|
||||
comprlen = lzf_compress(s,len,compr+4,outlen);
|
||||
if (comprlen != 0) {
|
||||
/* Object successfully compressed within the required space. */
|
||||
compr[0] = len & 0xff;
|
||||
compr[1] = (len >> 8) & 0xff;
|
||||
compr[2] = (len >> 16) & 0xff;
|
||||
compr[3] = (len >> 24) & 0xff;
|
||||
if (o->encoding == REDIS_ENCODING_RAW) sdsfree(o->ptr);
|
||||
o->encoding = REDIS_ENCODING_LZF;
|
||||
if (compr == compr_static) {
|
||||
/* When compressing to the static buffer we have to
|
||||
* generate the SDS string here. */
|
||||
o->ptr = sdsnewlen(compr,comprlen+4);
|
||||
} else {
|
||||
/* Already an SDS, use it. */
|
||||
o->ptr = compr;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
if (compr != compr_static) sdsfree((char*)compr);
|
||||
}
|
||||
|
||||
/* We can't encode the object...
|
||||
*
|
||||
* Do the last try, and at least optimize the SDS string inside
|
||||
@@ -425,12 +476,20 @@ robj *getDecodedObject(robj *o) {
|
||||
incrRefCount(o);
|
||||
return o;
|
||||
}
|
||||
if (o->type == REDIS_STRING && o->encoding == REDIS_ENCODING_INT) {
|
||||
if (o->type == REDIS_STRING && intEncodedObject(o)) {
|
||||
char buf[32];
|
||||
|
||||
ll2string(buf,32,(long)o->ptr);
|
||||
dec = createStringObject(buf,strlen(buf));
|
||||
return dec;
|
||||
} else if (o->type == REDIS_STRING && lzfEncodedObject(o)) {
|
||||
int origlen = stringObjectLen(o);
|
||||
sds orig = sdsnewlen(NULL,origlen);
|
||||
unsigned char *p = o->ptr;
|
||||
|
||||
if (lzf_decompress(p+4,sdslen(o->ptr)-4,orig,origlen) == 0)
|
||||
redisPanic("LZF error during object decoding.");
|
||||
return createObject(REDIS_STRING,orig);
|
||||
} else {
|
||||
redisPanic("Unknown encoding type");
|
||||
}
|
||||
@@ -451,8 +510,15 @@ int compareStringObjectsWithFlags(robj *a, robj *b, int flags) {
|
||||
redisAssertWithInfo(NULL,a,a->type == REDIS_STRING && b->type == REDIS_STRING);
|
||||
char bufa[128], bufb[128], *astr, *bstr;
|
||||
size_t alen, blen, minlen;
|
||||
int decr = 0;
|
||||
|
||||
if (a == b) return 0;
|
||||
|
||||
if (lzfEncodedObject(a) || lzfEncodedObject(b)) {
|
||||
a = getDecodedObject(a);
|
||||
b = getDecodedObject(b);
|
||||
decr = 1;
|
||||
}
|
||||
if (sdsEncodedObject(a)) {
|
||||
astr = a->ptr;
|
||||
alen = sdslen(astr);
|
||||
@@ -477,6 +543,10 @@ int compareStringObjectsWithFlags(robj *a, robj *b, int flags) {
|
||||
if (cmp == 0) return alen-blen;
|
||||
return cmp;
|
||||
}
|
||||
if (decr) {
|
||||
decrRefCount(a);
|
||||
decrRefCount(b);
|
||||
}
|
||||
}
|
||||
|
||||
/* Wrapper for compareStringObjectsWithFlags() using binary comparison. */
|
||||
@@ -494,8 +564,7 @@ int collateStringObjects(robj *a, robj *b) {
|
||||
* this function is faster then checking for (compareStringObject(a,b) == 0)
|
||||
* because it can perform some more optimization. */
|
||||
int equalStringObjects(robj *a, robj *b) {
|
||||
if (a->encoding == REDIS_ENCODING_INT &&
|
||||
b->encoding == REDIS_ENCODING_INT){
|
||||
if (intEncodedObject(a) && intEncodedObject(b)) {
|
||||
/* If both strings are integer encoded just check if the stored
|
||||
* long is the same. */
|
||||
return a->ptr == b->ptr;
|
||||
@@ -504,13 +573,21 @@ int equalStringObjects(robj *a, robj *b) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the original (uncompressed) size of an LZF encoded object.
|
||||
* Only called by stringObjectLen() that should be the main interface. */
|
||||
size_t stringObjectUncompressedLen(robj *o) {
|
||||
unsigned char *p = o->ptr;
|
||||
return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
|
||||
}
|
||||
|
||||
size_t stringObjectLen(robj *o) {
|
||||
redisAssertWithInfo(NULL,o,o->type == REDIS_STRING);
|
||||
if (sdsEncodedObject(o)) {
|
||||
return sdslen(o->ptr);
|
||||
} else if (o->encoding == REDIS_ENCODING_LZF) {
|
||||
return stringObjectUncompressedLen(o);
|
||||
} else {
|
||||
char buf[32];
|
||||
|
||||
return ll2string(buf,32,(long)o->ptr);
|
||||
}
|
||||
}
|
||||
@@ -533,8 +610,14 @@ int getDoubleFromObject(robj *o, double *target) {
|
||||
errno == EINVAL ||
|
||||
isnan(value))
|
||||
return REDIS_ERR;
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
} else if (intEncodedObject(o)) {
|
||||
value = (long)o->ptr;
|
||||
} else if (lzfEncodedObject(o)) {
|
||||
int retval;
|
||||
o = getDecodedObject(o);
|
||||
retval = getDoubleFromObject(o,target);
|
||||
decrRefCount(o);
|
||||
return retval;
|
||||
} else {
|
||||
redisPanic("Unknown string encoding");
|
||||
}
|
||||
@@ -571,8 +654,14 @@ int getLongDoubleFromObject(robj *o, long double *target) {
|
||||
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
|
||||
errno == ERANGE || isnan(value))
|
||||
return REDIS_ERR;
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
} else if (intEncodedObject(o)) {
|
||||
value = (long)o->ptr;
|
||||
} else if (lzfEncodedObject(o)) {
|
||||
int retval;
|
||||
o = getDecodedObject(o);
|
||||
retval = getLongDoubleFromObject(o,target);
|
||||
decrRefCount(o);
|
||||
return retval;
|
||||
} else {
|
||||
redisPanic("Unknown string encoding");
|
||||
}
|
||||
@@ -609,8 +698,14 @@ int getLongLongFromObject(robj *o, long long *target) {
|
||||
if (isspace(((char*)o->ptr)[0]) || eptr[0] != '\0' ||
|
||||
errno == ERANGE)
|
||||
return REDIS_ERR;
|
||||
} else if (o->encoding == REDIS_ENCODING_INT) {
|
||||
} else if (intEncodedObject(o)) {
|
||||
value = (long)o->ptr;
|
||||
} else if (lzfEncodedObject(o)) {
|
||||
int retval;
|
||||
o = getDecodedObject(o);
|
||||
retval = getLongLongFromObject(o,target);
|
||||
decrRefCount(o);
|
||||
return retval;
|
||||
} else {
|
||||
redisPanic("Unknown string encoding");
|
||||
}
|
||||
@@ -659,6 +754,7 @@ char *strEncoding(int encoding) {
|
||||
case REDIS_ENCODING_INTSET: return "intset";
|
||||
case REDIS_ENCODING_SKIPLIST: return "skiplist";
|
||||
case REDIS_ENCODING_EMBSTR: return "embstr";
|
||||
case REDIS_ENCODING_LZF: return "lzf";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,11 +209,41 @@ int rdbTryIntegerEncoding(char *s, size_t len, unsigned char *enc) {
|
||||
return rdbEncodeInteger(value,enc);
|
||||
}
|
||||
|
||||
int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
|
||||
size_t comprlen, outlen;
|
||||
/* Save an already compressed object in LZF encoding.
|
||||
*
|
||||
* On success the length of the strored object is returned, otherwise
|
||||
* 0 is returned. */
|
||||
int rdbSaveLzfStringObject(rio *rdb, unsigned char *out, size_t len, size_t comprlen) {
|
||||
unsigned char byte;
|
||||
int n, nwritten = 0;
|
||||
|
||||
/* Data compressed! Let's save it on disk */
|
||||
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
|
||||
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveLen(rdb,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
if ((n = rdbSaveLen(rdb,len)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
if ((n = rdbWriteRaw(rdb,out,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
return nwritten;
|
||||
|
||||
writeerr:
|
||||
zfree(out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Try to compress the string at 's' for 'len' bytes using LZF.
|
||||
* If successful save the object with LZF encoding, otherwise
|
||||
* returns 0 if the string can't be compressed, or -1 if the
|
||||
* compressed string can't be saved.
|
||||
*
|
||||
* On success the number of bytes used is returned. */
|
||||
int rdbTrySaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
|
||||
size_t comprlen, outlen;
|
||||
void *out;
|
||||
int retval;
|
||||
|
||||
/* We require at least four bytes compression for this to be worth it */
|
||||
if (len <= 4) return 0;
|
||||
@@ -224,26 +254,9 @@ int rdbSaveLzfStringObject(rio *rdb, unsigned char *s, size_t len) {
|
||||
zfree(out);
|
||||
return 0;
|
||||
}
|
||||
/* Data compressed! Let's save it on disk */
|
||||
byte = (REDIS_RDB_ENCVAL<<6)|REDIS_RDB_ENC_LZF;
|
||||
if ((n = rdbWriteRaw(rdb,&byte,1)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(rdb,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbSaveLen(rdb,len)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
if ((n = rdbWriteRaw(rdb,out,comprlen)) == -1) goto writeerr;
|
||||
nwritten += n;
|
||||
|
||||
retval = rdbSaveLzfStringObject(rdb,out,len,comprlen);
|
||||
zfree(out);
|
||||
return nwritten;
|
||||
|
||||
writeerr:
|
||||
zfree(out);
|
||||
return -1;
|
||||
return retval;
|
||||
}
|
||||
|
||||
robj *rdbLoadLzfStringObject(rio *rdb) {
|
||||
@@ -283,7 +296,7 @@ int rdbSaveRawString(rio *rdb, unsigned char *s, size_t len) {
|
||||
/* Try LZF compression - under 20 bytes it's unable to compress even
|
||||
* aaaaaaaaaaaaaaaaaa so skip it */
|
||||
if (server.rdb_compression && len > 20) {
|
||||
n = rdbSaveLzfStringObject(rdb,s,len);
|
||||
n = rdbTrySaveLzfStringObject(rdb,s,len);
|
||||
if (n == -1) return -1;
|
||||
if (n > 0) return n;
|
||||
/* Return value of 0 means data can't be compressed, save the old way */
|
||||
@@ -324,6 +337,11 @@ int rdbSaveStringObject(rio *rdb, robj *obj) {
|
||||
* object is already integer encoded. */
|
||||
if (obj->encoding == REDIS_ENCODING_INT) {
|
||||
return rdbSaveLongLongAsStringObject(rdb,(long)obj->ptr);
|
||||
} else if (obj->encoding == REDIS_ENCODING_LZF) {
|
||||
/* Data is already compressed, save it with LZF encoding. */
|
||||
int len = stringObjectLen(obj);
|
||||
unsigned char *p = obj->ptr;
|
||||
return rdbSaveLzfStringObject(rdb,p+4,len,sdslen(obj->ptr)-4);
|
||||
} else {
|
||||
redisAssertWithInfo(NULL,obj,sdsEncodedObject(obj));
|
||||
return rdbSaveRawString(rdb,obj->ptr,sdslen(obj->ptr));
|
||||
@@ -398,7 +416,7 @@ int rdbSaveDoubleValue(rio *rdb, double val) {
|
||||
double min = -4503599627370495; /* (2^52)-1 */
|
||||
double max = 4503599627370496; /* -(2^52) */
|
||||
if (val > min && val < max && val == ((double)((long long)val)))
|
||||
ll2string((char*)buf+1,sizeof(buf)-1,(long long)val);
|
||||
ll2string((char*)buf+1,sizeof(buf),(long long)val);
|
||||
else
|
||||
#endif
|
||||
snprintf((char*)buf+1,sizeof(buf)-1,"%.17g",val);
|
||||
@@ -410,7 +428,7 @@ int rdbSaveDoubleValue(rio *rdb, double val) {
|
||||
|
||||
/* For information about double serialization check rdbSaveDoubleValue() */
|
||||
int rdbLoadDoubleValue(rio *rdb, double *val) {
|
||||
char buf[256];
|
||||
char buf[128];
|
||||
unsigned char len;
|
||||
|
||||
if (rioRead(rdb,&len,1) == 0) return -1;
|
||||
@@ -1072,7 +1090,7 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
|
||||
if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER)
|
||||
replicationSendNewlineToMaster();
|
||||
loadingProgress(r->processed_bytes);
|
||||
processEventsWhileBlocked();
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1832,7 +1832,7 @@ static void intrinsicLatencyMode(void) {
|
||||
if (end > test_end) {
|
||||
printf("\n%lld total runs (avg %lld microseconds per run).\n",
|
||||
runs, run_time/runs);
|
||||
printf("Worst run took %.02fx times the average.\n",
|
||||
printf("Worst run took %.02fx times the avarege.\n",
|
||||
(double) max_latency / (run_time/runs));
|
||||
exit(0);
|
||||
}
|
||||
|
||||
+18
-234
@@ -139,10 +139,10 @@ class ClusterNode
|
||||
if s[0..0] == '['
|
||||
if s.index("->-") # Migrating
|
||||
slot,dst = s[1..-1].split("->-")
|
||||
@info[:migrating][slot.to_i] = dst
|
||||
@info[:migrating][slot] = dst
|
||||
elsif s.index("-<-") # Importing
|
||||
slot,src = s[1..-1].split("-<-")
|
||||
@info[:importing][slot.to_i] = src
|
||||
@info[:importing][slot] = src
|
||||
end
|
||||
elsif s.index("-")
|
||||
start,stop = s.split("-")
|
||||
@@ -445,35 +445,10 @@ class RedisTrib
|
||||
end
|
||||
end
|
||||
|
||||
# Return the owner of the specified slot
|
||||
def get_slot_owner(slot)
|
||||
@nodes.each{|n|
|
||||
n.slots.each{|s,_|
|
||||
return n if s == slot
|
||||
}
|
||||
}
|
||||
nil
|
||||
end
|
||||
|
||||
# Slot 'slot' was found to be in importing or migrating state in one or
|
||||
# more nodes. This function fixes this condition by migrating keys where
|
||||
# it seems more sensible.
|
||||
def fix_open_slot(slot)
|
||||
puts ">>> Fixing open slot #{slot}"
|
||||
|
||||
# Try to obtain the current slot owner, according to the current
|
||||
# nodes configuration.
|
||||
owner = get_slot_owner(slot)
|
||||
|
||||
# If there is no slot owner, set as owner the slot with the biggest
|
||||
# number of keys, among the set of migrating / importing nodes.
|
||||
if !owner
|
||||
xputs "*** Fix me, some work to do here."
|
||||
# Select owner...
|
||||
# Use ADDSLOTS to assign the slot.
|
||||
exit 1
|
||||
end
|
||||
|
||||
migrating = []
|
||||
importing = []
|
||||
@nodes.each{|n|
|
||||
@@ -482,26 +457,24 @@ class RedisTrib
|
||||
migrating << n
|
||||
elsif n.info[:importing][slot]
|
||||
importing << n
|
||||
elsif n.r.cluster("countkeysinslot",slot) > 0 && n != owner
|
||||
elsif n.r.cluster("countkeysinslot",slot) > 0
|
||||
xputs "*** Found keys about slot #{slot} in node #{n}!"
|
||||
importing << n
|
||||
end
|
||||
}
|
||||
puts ">>> Fixing open slot #{slot}"
|
||||
puts "Set as migrating in: #{migrating.join(",")}"
|
||||
puts "Set as importing in: #{importing.join(",")}"
|
||||
|
||||
# Case 1: The slot is in migrating state in one slot, and in
|
||||
# importing state in 1 slot. That's trivial to address.
|
||||
if migrating.length == 1 && importing.length == 1
|
||||
move_slot(migrating[0],importing[0],slot,:verbose=>true,:fix=>true)
|
||||
elsif migrating.length == 0 && importing.length > 0
|
||||
xputs ">>> Moving all the #{slot} slot keys to its owner #{owner}"
|
||||
importing.each {|node|
|
||||
next if node == owner
|
||||
move_slot(node,owner,slot,:verbose=>true,:fix=>true,:cold=>true)
|
||||
xputs ">>> Setting #{slot} as STABLE in #{node}"
|
||||
node.r.cluster("setslot",slot,"stable")
|
||||
}
|
||||
move_slot(migrating[0],importing[0],slot,:verbose=>true)
|
||||
elsif migrating.length == 1 && importing.length == 0
|
||||
xputs ">>> Setting #{slot} as STABLE"
|
||||
migrating[0].r.cluster("setslot",slot,"stable")
|
||||
elsif migrating.length == 0 && importing.length == 1
|
||||
xputs ">>> Setting #{slot} as STABLE"
|
||||
importing[0].r.cluster("setslot",slot,"stable")
|
||||
else
|
||||
xputs "[ERR] Sorry, Redis-trib can't fix this slot yet (work in progress)"
|
||||
end
|
||||
@@ -636,22 +609,6 @@ class RedisTrib
|
||||
}
|
||||
end
|
||||
|
||||
# Redis Cluster config epoch collision resolution code is able to eventually
|
||||
# set a different epoch to each node after a new cluster is created, but
|
||||
# it is slow compared to assign a progressive config epoch to each node
|
||||
# before joining the cluster. However we do just a best-effort try here
|
||||
# since if we fail is not a problem.
|
||||
def assign_config_epoch
|
||||
config_epoch = 1
|
||||
@nodes.each{|n|
|
||||
begin
|
||||
n.r.cluster("set-config-epoch",config_epoch)
|
||||
rescue
|
||||
end
|
||||
config_epoch += 1
|
||||
}
|
||||
end
|
||||
|
||||
def join_cluster
|
||||
# We use a brute force approach to make sure the node will meet
|
||||
# each other, that is, sending CLUSTER MEET messages to all the nodes
|
||||
@@ -754,51 +711,29 @@ class RedisTrib
|
||||
}
|
||||
end
|
||||
|
||||
# Move slots between source and target nodes using MIGRATE.
|
||||
#
|
||||
# Options:
|
||||
# :verbose -- Print a dot for every moved key.
|
||||
# :fix -- We are moving in the context of a fix. Use REPLACE.
|
||||
# :cold -- Move keys without opening / reconfiguring the nodes.
|
||||
def move_slot(source,target,slot,o={})
|
||||
# We start marking the slot as importing in the destination node,
|
||||
# and the slot as migrating in the target host. Note that the order of
|
||||
# the operations is important, as otherwise a client may be redirected
|
||||
# to the target node that does not yet know it is importing this slot.
|
||||
print "Moving slot #{slot} from #{source} to #{target}: "; STDOUT.flush
|
||||
if !o[:cold]
|
||||
target.r.cluster("setslot",slot,"importing",source.info[:name])
|
||||
source.r.cluster("setslot",slot,"migrating",target.info[:name])
|
||||
end
|
||||
target.r.cluster("setslot",slot,"importing",source.info[:name])
|
||||
source.r.cluster("setslot",slot,"migrating",target.info[:name])
|
||||
# Migrate all the keys from source to target using the MIGRATE command
|
||||
while true
|
||||
keys = source.r.cluster("getkeysinslot",slot,10)
|
||||
break if keys.length == 0
|
||||
keys.each{|key|
|
||||
begin
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],key,0,15000])
|
||||
rescue => e
|
||||
if o[:fix] && e.to_s =~ /BUSYKEY/
|
||||
xputs "*** Target key #{key} exists. Replace it for FIX."
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],key,0,15000,:replace])
|
||||
else
|
||||
puts ""
|
||||
xputs "[ERR] #{e}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
source.r.client.call(["migrate",target.info[:host],target.info[:port],key,0,1000])
|
||||
print "." if o[:verbose]
|
||||
STDOUT.flush
|
||||
}
|
||||
end
|
||||
|
||||
puts
|
||||
# Set the new node as the owner of the slot in all the known nodes.
|
||||
if !o[:cold]
|
||||
@nodes.each{|n|
|
||||
n.r.cluster("setslot",slot,"node",target.info[:name])
|
||||
}
|
||||
end
|
||||
@nodes.each{|n|
|
||||
n.r.cluster("setslot",slot,"node",target.info[:name])
|
||||
}
|
||||
end
|
||||
|
||||
# redis-trib subcommands implementations
|
||||
@@ -915,8 +850,6 @@ class RedisTrib
|
||||
yes_or_die "Can I set the above configuration?"
|
||||
flush_nodes_config
|
||||
xputs ">>> Nodes configuration updated"
|
||||
xputs ">>> Assign a different config epoch to each node"
|
||||
assign_config_epoch
|
||||
xputs ">>> Sending CLUSTER MEET messages to join the cluster"
|
||||
join_cluster
|
||||
# Give one second for the join to start, in order to avoid that
|
||||
@@ -1055,54 +988,6 @@ class RedisTrib
|
||||
}
|
||||
end
|
||||
|
||||
def import_cluster_cmd(argv,opt)
|
||||
source_addr = opt['from']
|
||||
xputs ">>> Importing data from #{source_addr} to cluster #{argv[1]}"
|
||||
|
||||
# Check the existing cluster.
|
||||
load_cluster_info_from_node(argv[0])
|
||||
check_cluster
|
||||
|
||||
# Connect to the source node.
|
||||
xputs ">>> Connecting to the source Redis instance"
|
||||
src_host,src_port = source_addr.split(":")
|
||||
source = Redis.new(:host =>src_host, :port =>src_port)
|
||||
if source.info['cluster_enabled'].to_i == 1
|
||||
xputs "[ERR] The source node should not be a cluster node."
|
||||
end
|
||||
xputs "*** Importing #{source.dbsize} keys from DB 0"
|
||||
|
||||
# Build a slot -> node map
|
||||
slots = {}
|
||||
@nodes.each{|n|
|
||||
n.slots.each{|s,_|
|
||||
slots[s] = n
|
||||
}
|
||||
}
|
||||
|
||||
# Use SCAN to iterate over the keys, migrating to the
|
||||
# right node as needed.
|
||||
cursor = nil
|
||||
while cursor != 0
|
||||
cursor,keys = source.scan(cursor,:count,1000)
|
||||
cursor = cursor.to_i
|
||||
keys.each{|k|
|
||||
# Migrate keys using the MIGRATE command.
|
||||
slot = key_to_slot(k)
|
||||
target = slots[slot]
|
||||
print "Migrating #{k} to #{target}: "
|
||||
STDOUT.flush
|
||||
begin
|
||||
source.client.call(["migrate",target.info[:host],target.info[:port],k,0,15000])
|
||||
rescue => e
|
||||
puts e
|
||||
else
|
||||
puts "OK"
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def help_cluster_cmd(argv,opt)
|
||||
show_help
|
||||
exit 0
|
||||
@@ -1134,109 +1019,10 @@ class RedisTrib
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Enforce mandatory options
|
||||
if ALLOWED_OPTIONS[cmd]
|
||||
ALLOWED_OPTIONS[cmd].each {|option,val|
|
||||
if !options[option] && val == :required
|
||||
puts "Option '--#{option}' is required "+ \
|
||||
"for subcommand '#{cmd}'"
|
||||
exit 1
|
||||
end
|
||||
}
|
||||
end
|
||||
return options,idx
|
||||
end
|
||||
end
|
||||
|
||||
#################################################################################
|
||||
# Libraries
|
||||
#
|
||||
# We try to don't depend on external libs since this is a critical part
|
||||
# of Redis Cluster.
|
||||
#################################################################################
|
||||
|
||||
# This is the CRC16 algorithm used by Redis Cluster to hash keys.
|
||||
# Implementation according to CCITT standards.
|
||||
#
|
||||
# This is actually the XMODEM CRC 16 algorithm, using the
|
||||
# following parameters:
|
||||
#
|
||||
# Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
|
||||
# Width : 16 bit
|
||||
# Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
|
||||
# Initialization : 0000
|
||||
# Reflect Input byte : False
|
||||
# Reflect Output CRC : False
|
||||
# Xor constant to output CRC : 0000
|
||||
# Output for "123456789" : 31C3
|
||||
|
||||
module RedisClusterCRC16
|
||||
def RedisClusterCRC16.crc16(bytes)
|
||||
crc = 0
|
||||
bytes.each_byte{|b|
|
||||
crc = ((crc<<8) & 0xffff) ^ XMODEMCRC16Lookup[((crc>>8)^b) & 0xff]
|
||||
}
|
||||
crc
|
||||
end
|
||||
|
||||
private
|
||||
XMODEMCRC16Lookup = [
|
||||
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
|
||||
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
|
||||
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
|
||||
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
|
||||
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
|
||||
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
|
||||
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
|
||||
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
|
||||
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
|
||||
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
|
||||
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
|
||||
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
|
||||
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
|
||||
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
|
||||
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
|
||||
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
|
||||
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
|
||||
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
|
||||
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
|
||||
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
|
||||
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
|
||||
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
|
||||
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
|
||||
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
|
||||
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
|
||||
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
|
||||
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
|
||||
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
|
||||
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
|
||||
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
|
||||
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
|
||||
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
|
||||
]
|
||||
end
|
||||
|
||||
# Turn a key name into the corrisponding Redis Cluster slot.
|
||||
def key_to_slot(key)
|
||||
# Only hash what is inside {...} if there is such a pattern in the key.
|
||||
# Note that the specification requires the content that is between
|
||||
# the first { and the first } after the first {. If we found {} without
|
||||
# nothing in the middle, the whole key is hashed as usually.
|
||||
s = key.index "{"
|
||||
if s
|
||||
e = key.index "}",s+1
|
||||
if e && e != s+1
|
||||
key = key[s+1..e-1]
|
||||
end
|
||||
end
|
||||
RedisClusterCRC16.crc16(key) % 16384
|
||||
end
|
||||
|
||||
#################################################################################
|
||||
# Definition of commands
|
||||
#################################################################################
|
||||
|
||||
COMMANDS={
|
||||
"create" => ["create_cluster_cmd", -2, "host1:port1 ... hostN:portN"],
|
||||
"check" => ["check_cluster_cmd", 2, "host:port"],
|
||||
@@ -1246,14 +1032,12 @@ COMMANDS={
|
||||
"del-node" => ["delnode_cluster_cmd", 3, "host:port node_id"],
|
||||
"set-timeout" => ["set_timeout_cluster_cmd", 3, "host:port milliseconds"],
|
||||
"call" => ["call_cluster_cmd", -3, "host:port command arg arg .. arg"],
|
||||
"import" => ["import_cluster_cmd", 2, "host:port"],
|
||||
"help" => ["help_cluster_cmd", 1, "(show this help)"]
|
||||
}
|
||||
|
||||
ALLOWED_OPTIONS={
|
||||
"create" => {"replicas" => true},
|
||||
"add-node" => {"slave" => false, "master-id" => true},
|
||||
"import" => {"from" => :required}
|
||||
"add-node" => {"slave" => false, "master-id" => true}
|
||||
}
|
||||
|
||||
def show_help
|
||||
|
||||
+15
-50
@@ -171,16 +171,12 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"zrem",zremCommand,-3,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebyscore",zremrangebyscoreCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebyrank",zremrangebyrankCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebylex",zremrangebylexCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zunionstore",zunionstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"zinterstore",zinterstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"zrange",zrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrangebyscore",zrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrangebyscore",zrevrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrangebylex",zrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrangebylex",zrevrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcount",zcountCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zlexcount",zlexcountCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrange",zrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcard",zcardCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"zscore",zscoreCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
@@ -274,9 +270,9 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"wait",waitCommand,3,"rs",0,NULL,0,0,0,0,0},
|
||||
{"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"pfadd",pfaddCommand,-2,"wm",0,NULL,1,1,1,0,0},
|
||||
{"pfcount",pfcountCommand,-2,"w",0,NULL,1,1,1,0,0},
|
||||
{"pfcount",pfcountCommand,2,"w",0,NULL,1,1,1,0,0},
|
||||
{"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
|
||||
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0}
|
||||
{"pfgetreg",pfgetregCommand,2,"r",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
|
||||
struct evictionPoolEntry *evictionPoolAlloc(void);
|
||||
@@ -304,21 +300,11 @@ void redisLogRaw(int level, const char *msg) {
|
||||
} else {
|
||||
int off;
|
||||
struct timeval tv;
|
||||
int role_char;
|
||||
pid_t pid = getpid();
|
||||
|
||||
gettimeofday(&tv,NULL);
|
||||
off = strftime(buf,sizeof(buf),"%d %b %H:%M:%S.",localtime(&tv.tv_sec));
|
||||
snprintf(buf+off,sizeof(buf)-off,"%03d",(int)tv.tv_usec/1000);
|
||||
if (server.sentinel_mode) {
|
||||
role_char = 'X'; /* Sentinel. */
|
||||
} else if (pid != server.pid) {
|
||||
role_char = 'C'; /* RDB / AOF writing child. */
|
||||
} else {
|
||||
role_char = (server.masterhost ? 'S':'M'); /* Slave or Master. */
|
||||
}
|
||||
fprintf(fp,"%d:%c %s %c %s\n",
|
||||
(int)getpid(),role_char, buf,c[level],msg);
|
||||
fprintf(fp,"[%d] %s %c %s\n",(int)getpid(),buf,c[level],msg);
|
||||
}
|
||||
fflush(fp);
|
||||
|
||||
@@ -359,8 +345,9 @@ void redisLogFromHandler(int level, const char *msg) {
|
||||
open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644);
|
||||
if (fd == -1) return;
|
||||
ll2string(buf,sizeof(buf),getpid());
|
||||
if (write(fd,"[",1) == -1) goto err;
|
||||
if (write(fd,buf,strlen(buf)) == -1) goto err;
|
||||
if (write(fd,":signal-handler (",17) == -1) goto err;
|
||||
if (write(fd," | signal handler] (",20) == -1) goto err;
|
||||
ll2string(buf,sizeof(buf),time(NULL));
|
||||
if (write(fd,buf,strlen(buf)) == -1) goto err;
|
||||
if (write(fd,") ",2) == -1) goto err;
|
||||
@@ -1325,8 +1312,6 @@ void createSharedObjects(void) {
|
||||
"-EXECABORT Transaction discarded because of previous errors.\r\n"));
|
||||
shared.noreplicaserr = createObject(REDIS_STRING,sdsnew(
|
||||
"-NOREPLICAS Not enough good slaves to write.\r\n"));
|
||||
shared.busykeyerr = createObject(REDIS_STRING,sdsnew(
|
||||
"-BUSYKEY Target key name already exists.\r\n"));
|
||||
shared.space = createObject(REDIS_STRING,sdsnew(" "));
|
||||
shared.colon = createObject(REDIS_STRING,sdsnew(":"));
|
||||
shared.plus = createObject(REDIS_STRING,sdsnew("+"));
|
||||
@@ -1361,12 +1346,6 @@ void createSharedObjects(void) {
|
||||
shared.bulkhdr[j] = createObject(REDIS_STRING,
|
||||
sdscatprintf(sdsempty(),"$%d\r\n",j));
|
||||
}
|
||||
/* The following two shared objects, minstring and maxstrings, are not
|
||||
* actually used for their value but as a special object meaning
|
||||
* respectively the minimum possible string and the maximum possible
|
||||
* string in string comparisons for the ZRANGEBYLEX command. */
|
||||
shared.minstring = createStringObject("minstring",9);
|
||||
shared.maxstring = createStringObject("maxstring",9);
|
||||
}
|
||||
|
||||
void initServerConfig() {
|
||||
@@ -1418,6 +1397,8 @@ void initServerConfig() {
|
||||
server.aof_filename = zstrdup(REDIS_DEFAULT_AOF_FILENAME);
|
||||
server.requirepass = NULL;
|
||||
server.rdb_compression = REDIS_DEFAULT_RDB_COMPRESSION;
|
||||
server.mem_compression = REDIS_DEFAULT_MEM_COMPRESSION;
|
||||
server.mem_compression_max_size = REDIS_DEFAULT_MEM_COMPRESSION_MAX_SIZE;
|
||||
server.rdb_checksum = REDIS_DEFAULT_RDB_CHECKSUM;
|
||||
server.stop_writes_on_bgsave_err = REDIS_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR;
|
||||
server.activerehashing = REDIS_DEFAULT_ACTIVE_REHASHING;
|
||||
@@ -1434,7 +1415,6 @@ void initServerConfig() {
|
||||
server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
|
||||
server.zset_max_ziplist_entries = REDIS_ZSET_MAX_ZIPLIST_ENTRIES;
|
||||
server.zset_max_ziplist_value = REDIS_ZSET_MAX_ZIPLIST_VALUE;
|
||||
server.hll_sparse_max_bytes = REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES;
|
||||
server.shutdown_asap = 0;
|
||||
server.repl_ping_slave_period = REDIS_REPL_PING_SLAVE_PERIOD;
|
||||
server.repl_timeout = REDIS_REPL_TIMEOUT;
|
||||
@@ -1443,7 +1423,6 @@ void initServerConfig() {
|
||||
server.cluster_enabled = 0;
|
||||
server.cluster_node_timeout = REDIS_CLUSTER_DEFAULT_NODE_TIMEOUT;
|
||||
server.cluster_migration_barrier = REDIS_CLUSTER_DEFAULT_MIGRATION_BARRIER;
|
||||
server.cluster_slave_validity_factor = REDIS_CLUSTER_DEFAULT_SLAVE_VALIDITY;
|
||||
server.cluster_configfile = zstrdup(REDIS_DEFAULT_CLUSTER_CONFIG_FILE);
|
||||
server.lua_caller = NULL;
|
||||
server.lua_time_limit = REDIS_LUA_TIME_LIMIT;
|
||||
@@ -1570,28 +1549,24 @@ void adjustOpenFilesLimit(void) {
|
||||
redisLog(REDIS_WARNING,"Your current 'ulimit -n' "
|
||||
"of %llu is not enough for Redis to start. "
|
||||
"Please increase your open file limit to at least "
|
||||
"%llu. Exiting.",
|
||||
(unsigned long long) oldlimit,
|
||||
(unsigned long long) maxfiles);
|
||||
"%llu. Exiting.", oldlimit, maxfiles);
|
||||
exit(1);
|
||||
}
|
||||
redisLog(REDIS_WARNING,"You requested maxclients of %d "
|
||||
"requiring at least %llu max file descriptors.",
|
||||
old_maxclients,
|
||||
(unsigned long long) maxfiles);
|
||||
old_maxclients, maxfiles);
|
||||
redisLog(REDIS_WARNING,"Redis can't set maximum open files "
|
||||
"to %llu because of OS error: %s.",
|
||||
(unsigned long long) maxfiles, strerror(setrlimit_error));
|
||||
maxfiles, strerror(setrlimit_error));
|
||||
redisLog(REDIS_WARNING,"Current maximum open files is %llu. "
|
||||
"maxclients has been reduced to %d to compensate for "
|
||||
"low ulimit. "
|
||||
"If you need higher maxclients increase 'ulimit -n'.",
|
||||
(unsigned long long) oldlimit, server.maxclients);
|
||||
oldlimit, server.maxclients);
|
||||
} else {
|
||||
redisLog(REDIS_NOTICE,"Increased maximum number of open files "
|
||||
"to %llu (it was originally set to %llu).",
|
||||
(unsigned long long) maxfiles,
|
||||
(unsigned long long) oldlimit);
|
||||
maxfiles, oldlimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1627,16 +1602,10 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
* server.bindaddr_count == 0. */
|
||||
fds[*count] = anetTcp6Server(server.neterr,port,NULL,
|
||||
server.tcp_backlog);
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
}
|
||||
if (fds[*count] != ANET_ERR) (*count)++;
|
||||
fds[*count] = anetTcpServer(server.neterr,port,NULL,
|
||||
server.tcp_backlog);
|
||||
if (fds[*count] != ANET_ERR) {
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
}
|
||||
if (fds[*count] != ANET_ERR) (*count)++;
|
||||
/* Exit the loop if we were able to bind * on IPv4 or IPv6,
|
||||
* otherwise fds[*count] will be ANET_ERR and we'll print an
|
||||
* error and return to the caller with an error. */
|
||||
@@ -1657,7 +1626,6 @@ int listenToPort(int port, int *fds, int *count) {
|
||||
port, server.neterr);
|
||||
return REDIS_ERR;
|
||||
}
|
||||
anetNonBlock(NULL,fds[*count]);
|
||||
(*count)++;
|
||||
}
|
||||
return REDIS_OK;
|
||||
@@ -1696,7 +1664,6 @@ void initServer() {
|
||||
server.syslog_facility);
|
||||
}
|
||||
|
||||
server.pid = getpid();
|
||||
server.current_client = NULL;
|
||||
server.clients = listCreate();
|
||||
server.clients_to_close = listCreate();
|
||||
@@ -1728,7 +1695,6 @@ void initServer() {
|
||||
redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
|
||||
exit(1);
|
||||
}
|
||||
anetNonBlock(NULL,server.sofd);
|
||||
}
|
||||
|
||||
/* Abort if there are no listening sockets at all. */
|
||||
@@ -2170,8 +2136,7 @@ int processCommand(redisClient *c) {
|
||||
|
||||
/* Don't accept write commands if there are not enough good slaves and
|
||||
* user configured the min-slaves-to-write option. */
|
||||
if (server.masterhost == NULL &&
|
||||
server.repl_min_slaves_to_write &&
|
||||
if (server.repl_min_slaves_to_write &&
|
||||
server.repl_min_slaves_max_lag &&
|
||||
c->cmd->flags & REDIS_CMD_WRITE &&
|
||||
server.repl_good_slaves_count < server.repl_min_slaves_to_write)
|
||||
|
||||
+18
-29
@@ -107,6 +107,8 @@
|
||||
#define REDIS_DEFAULT_SYSLOG_ENABLED 0
|
||||
#define REDIS_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
|
||||
#define REDIS_DEFAULT_RDB_COMPRESSION 1
|
||||
#define REDIS_DEFAULT_MEM_COMPRESSION 0
|
||||
#define REDIS_DEFAULT_MEM_COMPRESSION_MAX_SIZE (1024*64)
|
||||
#define REDIS_DEFAULT_RDB_CHECKSUM 1
|
||||
#define REDIS_DEFAULT_RDB_FILENAME "dump.rdb"
|
||||
#define REDIS_DEFAULT_SLAVE_SERVE_STALE_DATA 1
|
||||
@@ -172,7 +174,7 @@
|
||||
|
||||
/* Objects encoding. Some kind of objects like Strings and Hashes can be
|
||||
* internally represented in multiple ways. The 'encoding' field of the object
|
||||
* is set to one of this fields for this object. */
|
||||
* is set to one of this values. */
|
||||
#define REDIS_ENCODING_RAW 0 /* Raw representation */
|
||||
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
|
||||
#define REDIS_ENCODING_HT 2 /* Encoded as hash table */
|
||||
@@ -182,6 +184,7 @@
|
||||
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
|
||||
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
|
||||
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
|
||||
#define REDIS_ENCODING_LZF 9 /* LZF compressed string. */
|
||||
|
||||
/* Defines related to the dump file format. To store 32 bits lengths for short
|
||||
* keys requires a lot of space, so we check the most significant 2 bits of
|
||||
@@ -312,9 +315,6 @@
|
||||
#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128
|
||||
#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64
|
||||
|
||||
/* HyperLogLog defines */
|
||||
#define REDIS_DEFAULT_HLL_SPARSE_MAX_BYTES 3000
|
||||
|
||||
/* Sets operations codes */
|
||||
#define REDIS_OP_UNION 0
|
||||
#define REDIS_OP_DIFF 1
|
||||
@@ -530,7 +530,6 @@ typedef struct redisClient {
|
||||
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
|
||||
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
|
||||
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
|
||||
/* Response buffer */
|
||||
int bufpos;
|
||||
@@ -548,9 +547,9 @@ struct sharedObjectsStruct {
|
||||
*emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
|
||||
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
|
||||
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
|
||||
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
|
||||
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop,
|
||||
*lpush, *emptyscan, *minstring, *maxstring,
|
||||
*lpush, *emptyscan,
|
||||
*select[REDIS_SHARED_SELECT_CMDS],
|
||||
*integers[REDIS_SHARED_INTEGERS],
|
||||
*mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
|
||||
@@ -619,7 +618,6 @@ struct clusterState;
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
pid_t pid; /* Main process pid. */
|
||||
char *configfile; /* Absolute config file path, or NULL */
|
||||
int hz; /* serverCron() calls frequency in hertz */
|
||||
redisDb *db;
|
||||
@@ -814,9 +812,10 @@ struct redisServer {
|
||||
size_t set_max_intset_entries;
|
||||
size_t zset_max_ziplist_entries;
|
||||
size_t zset_max_ziplist_value;
|
||||
size_t hll_sparse_max_bytes;
|
||||
int mem_compression; /* In memory LZF compression. */
|
||||
size_t mem_compression_max_size; /* Try to compress up to this size. */
|
||||
time_t unixtime; /* Unix time sampled every cron cycle. */
|
||||
long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
|
||||
long long mstime; /* Like unixtime but in milliseconds. */
|
||||
/* Pubsub */
|
||||
dict *pubsub_channels; /* Map channels to list of subscribed clients */
|
||||
list *pubsub_patterns; /* A list of pubsub_patterns */
|
||||
@@ -828,7 +827,6 @@ struct redisServer {
|
||||
char *cluster_configfile; /* Cluster auto-generated config file name. */
|
||||
struct clusterState *cluster; /* State of the cluster */
|
||||
int cluster_migration_barrier; /* Cluster replicas migration barrier. */
|
||||
int cluster_slave_validity_factor; /* Slave max data age for failover. */
|
||||
/* Scripting */
|
||||
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
|
||||
redisClient *lua_client; /* The "fake client" to query Redis from Lua */
|
||||
@@ -994,8 +992,8 @@ void *dupClientReplyValue(void *o);
|
||||
void getClientsMaxBuffers(unsigned long *longest_output_list,
|
||||
unsigned long *biggest_input_buffer);
|
||||
void formatPeerId(char *peerid, size_t peerid_len, char *ip, int port);
|
||||
char *getClientPeerId(redisClient *client);
|
||||
sds catClientInfoString(sds s, redisClient *client);
|
||||
int getClientPeerId(redisClient *client, char *peerid, size_t peerid_len);
|
||||
sds getClientInfoString(redisClient *client);
|
||||
sds getAllClientsInfoString(void);
|
||||
void rewriteClientCommandVector(redisClient *c, int argc, ...);
|
||||
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval);
|
||||
@@ -1009,7 +1007,6 @@ void disconnectSlaves(void);
|
||||
int listenToPort(int port, int *fds, int *count);
|
||||
void pauseClients(mstime_t duration);
|
||||
int clientsArePaused(void);
|
||||
int processEventsWhileBlocked(void);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
|
||||
@@ -1088,6 +1085,9 @@ int compareStringObjects(robj *a, robj *b);
|
||||
int collateStringObjects(robj *a, robj *b);
|
||||
int equalStringObjects(robj *a, robj *b);
|
||||
unsigned long long estimateObjectIdleTime(robj *o);
|
||||
#define rawEncodedObject(objptr) (objptr->encoding == REDIS_ENCODING_RAW)
|
||||
#define lzfEncodedObject(objptr) (objptr->encoding == REDIS_ENCODING_LZF)
|
||||
#define intEncodedObject(objptr) (objptr->encoding == REDIS_ENCODING_INT)
|
||||
#define sdsEncodedObject(objptr) (objptr->encoding == REDIS_ENCODING_RAW || objptr->encoding == REDIS_ENCODING_EMBSTR)
|
||||
|
||||
/* Synchronous I/O with timeout */
|
||||
@@ -1138,25 +1138,19 @@ unsigned long aofRewriteBufferSize(void);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
/* Struct to hold a inclusive/exclusive range spec by score comparison. */
|
||||
/* Struct to hold a inclusive/exclusive range spec. */
|
||||
typedef struct {
|
||||
double min, max;
|
||||
int minex, maxex; /* are min or max exclusive? */
|
||||
} zrangespec;
|
||||
|
||||
/* Struct to hold an inclusive/exclusive range spec by lexicographic comparison. */
|
||||
typedef struct {
|
||||
robj *min, *max; /* May be set to shared.(minstring|maxstring) */
|
||||
int minex, maxex; /* are min or max exclusive? */
|
||||
} zlexrangespec;
|
||||
|
||||
zskiplist *zslCreate(void);
|
||||
void zslFree(zskiplist *zsl);
|
||||
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj);
|
||||
unsigned char *zzlInsert(unsigned char *zl, robj *ele, double score);
|
||||
int zslDelete(zskiplist *zsl, double score, robj *obj);
|
||||
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range);
|
||||
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec *range);
|
||||
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec range);
|
||||
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec range);
|
||||
double zzlGetScore(unsigned char *sptr);
|
||||
void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
|
||||
@@ -1273,7 +1267,6 @@ void signalModifiedKey(redisDb *db, robj *key);
|
||||
void signalFlushedDb(int dbid);
|
||||
unsigned int getKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count);
|
||||
unsigned int countKeysInSlot(unsigned int hashslot);
|
||||
unsigned int delKeysInSlot(unsigned int hashslot);
|
||||
int verifyClusterConfigWithData(void);
|
||||
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor);
|
||||
int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor);
|
||||
@@ -1402,16 +1395,12 @@ void zincrbyCommand(redisClient *c);
|
||||
void zrangeCommand(redisClient *c);
|
||||
void zrangebyscoreCommand(redisClient *c);
|
||||
void zrevrangebyscoreCommand(redisClient *c);
|
||||
void zrangebylexCommand(redisClient *c);
|
||||
void zrevrangebylexCommand(redisClient *c);
|
||||
void zcountCommand(redisClient *c);
|
||||
void zlexcountCommand(redisClient *c);
|
||||
void zrevrangeCommand(redisClient *c);
|
||||
void zcardCommand(redisClient *c);
|
||||
void zremCommand(redisClient *c);
|
||||
void zscoreCommand(redisClient *c);
|
||||
void zremrangebyscoreCommand(redisClient *c);
|
||||
void zremrangebylexCommand(redisClient *c);
|
||||
void multiCommand(redisClient *c);
|
||||
void execCommand(redisClient *c);
|
||||
void discardCommand(redisClient *c);
|
||||
@@ -1471,7 +1460,7 @@ void pfselftestCommand(redisClient *c);
|
||||
void pfaddCommand(redisClient *c);
|
||||
void pfcountCommand(redisClient *c);
|
||||
void pfmergeCommand(redisClient *c);
|
||||
void pfdebugCommand(redisClient *c);
|
||||
void pfgetregCommand(redisClient *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
||||
+3
-7
@@ -239,6 +239,7 @@ void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **
|
||||
int j;
|
||||
sds cmdrepr = sdsnew("+");
|
||||
robj *cmdobj;
|
||||
char peerid[REDIS_PEER_ID_LEN];
|
||||
struct timeval tv;
|
||||
|
||||
gettimeofday(&tv,NULL);
|
||||
@@ -248,7 +249,8 @@ void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **
|
||||
} else if (c->flags & REDIS_UNIX_SOCKET) {
|
||||
cmdrepr = sdscatprintf(cmdrepr,"[%d unix:%s] ",dictid,server.unixsocket);
|
||||
} else {
|
||||
cmdrepr = sdscatprintf(cmdrepr,"[%d %s] ",dictid,getClientPeerId(c));
|
||||
getClientPeerId(c,peerid,sizeof(peerid));
|
||||
cmdrepr = sdscatprintf(cmdrepr,"[%d %s] ",dictid,peerid);
|
||||
}
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
@@ -1385,12 +1387,6 @@ void replicationCacheMaster(redisClient *c) {
|
||||
/* Set fd to -1 so that we can safely call freeClient(c) later. */
|
||||
c->fd = -1;
|
||||
|
||||
/* Invalidate the Peer ID cache. */
|
||||
if (c->peerid) {
|
||||
sdsfree(c->peerid);
|
||||
c->peerid = NULL;
|
||||
}
|
||||
|
||||
/* Caching the master happens instead of the actual freeClient() call,
|
||||
* so make sure to adjust the replication state. This function will
|
||||
* also set server.master to NULL. */
|
||||
|
||||
+25
-113
@@ -200,20 +200,13 @@ void luaSortArray(lua_State *lua) {
|
||||
lua_pop(lua,1); /* Stack: array (sorted) */
|
||||
}
|
||||
|
||||
#define LUA_CMD_OBJCACHE_SIZE 32
|
||||
#define LUA_CMD_OBJCACHE_MAX_LEN 64
|
||||
int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
int j, argc = lua_gettop(lua);
|
||||
struct redisCommand *cmd;
|
||||
robj **argv;
|
||||
redisClient *c = server.lua_client;
|
||||
sds reply;
|
||||
|
||||
/* Cached across calls. */
|
||||
static robj **argv = NULL;
|
||||
static int argv_size = 0;
|
||||
static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
|
||||
/* Require at least one argument */
|
||||
if (argc == 0) {
|
||||
luaPushError(lua,
|
||||
@@ -222,47 +215,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
}
|
||||
|
||||
/* Build the arguments vector */
|
||||
if (!argv) {
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
} else if (argv_size < argc) {
|
||||
argv = zrealloc(argv,sizeof(robj*)*argc);
|
||||
argv_size = argc;
|
||||
}
|
||||
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
char *obj_s;
|
||||
size_t obj_len;
|
||||
char dbuf[64];
|
||||
|
||||
if (lua_isnumber(lua,j+1)) {
|
||||
/* We can't use lua_tolstring() for number -> string conversion
|
||||
* since Lua uses a format specifier that loses precision. */
|
||||
lua_Number num = lua_tonumber(lua,j+1);
|
||||
|
||||
obj_len = snprintf(dbuf,sizeof(dbuf),"%.17g",(double)num);
|
||||
obj_s = dbuf;
|
||||
} else {
|
||||
obj_s = (char*)lua_tolstring(lua,j+1,&obj_len);
|
||||
if (obj_s == NULL) break; /* Not a string. */
|
||||
}
|
||||
|
||||
/* Try to use a cached object. */
|
||||
if (j < LUA_CMD_OBJCACHE_SIZE && cached_objects[j] &&
|
||||
cached_objects_len[j] >= obj_len)
|
||||
{
|
||||
char *s = cached_objects[j]->ptr;
|
||||
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
|
||||
|
||||
argv[j] = cached_objects[j];
|
||||
cached_objects[j] = NULL;
|
||||
memcpy(s,obj_s,obj_len+1);
|
||||
sh->free += sh->len - obj_len;
|
||||
sh->len = obj_len;
|
||||
} else {
|
||||
argv[j] = createStringObject(obj_s, obj_len);
|
||||
}
|
||||
if (!lua_isstring(lua,j+1)) break;
|
||||
argv[j] = createStringObject((char*)lua_tostring(lua,j+1),
|
||||
lua_strlen(lua,j+1));
|
||||
}
|
||||
|
||||
|
||||
/* Check if one of the arguments passed by the Lua script
|
||||
* is not a string or an integer (lua_isstring() return true for
|
||||
* integers as well). */
|
||||
@@ -272,6 +231,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
decrRefCount(argv[j]);
|
||||
j--;
|
||||
}
|
||||
zfree(argv);
|
||||
luaPushError(lua,
|
||||
"Lua redis() command arguments must be strings or integers");
|
||||
return 1;
|
||||
@@ -346,22 +306,16 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
/* Convert the result of the Redis command into a suitable Lua type.
|
||||
* The first thing we need is to create a single string from the client
|
||||
* output buffers. */
|
||||
if (listLength(c->reply) == 0 && c->bufpos < REDIS_REPLY_CHUNK_BYTES) {
|
||||
/* This is a fast path for the common case of a reply inside the
|
||||
* client static buffer. Don't create an SDS string but just use
|
||||
* the client buffer directly. */
|
||||
c->buf[c->bufpos] = '\0';
|
||||
reply = c->buf;
|
||||
reply = sdsempty();
|
||||
if (c->bufpos) {
|
||||
reply = sdscatlen(reply,c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
} else {
|
||||
reply = sdsnewlen(c->buf,c->bufpos);
|
||||
c->bufpos = 0;
|
||||
while(listLength(c->reply)) {
|
||||
robj *o = listNodeValue(listFirst(c->reply));
|
||||
}
|
||||
while(listLength(c->reply)) {
|
||||
robj *o = listNodeValue(listFirst(c->reply));
|
||||
|
||||
reply = sdscatlen(reply,o->ptr,sdslen(o->ptr));
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
reply = sdscatlen(reply,o->ptr,sdslen(o->ptr));
|
||||
listDelNode(c->reply,listFirst(c->reply));
|
||||
}
|
||||
if (raise_error && reply[0] != '-') raise_error = 0;
|
||||
redisProtocolToLuaType(lua,reply);
|
||||
@@ -371,38 +325,15 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
(reply[0] == '*' && reply[1] != '-')) {
|
||||
luaSortArray(lua);
|
||||
}
|
||||
if (reply != c->buf) sdsfree(reply);
|
||||
sdsfree(reply);
|
||||
c->reply_bytes = 0;
|
||||
|
||||
cleanup:
|
||||
/* Clean up. Command code may have changed argv/argc so we use the
|
||||
* argv/argc of the client instead of the local variables. */
|
||||
for (j = 0; j < c->argc; j++) {
|
||||
robj *o = c->argv[j];
|
||||
|
||||
/* Try to cache the object in the cached_objects array.
|
||||
* The object must be small, SDS-encoded, and with refcount = 1
|
||||
* (we must be the only owner) for us to cache it. */
|
||||
if (j < LUA_CMD_OBJCACHE_SIZE &&
|
||||
o->refcount == 1 &&
|
||||
(o->encoding == REDIS_ENCODING_RAW ||
|
||||
o->encoding == REDIS_ENCODING_EMBSTR) &&
|
||||
sdslen(o->ptr) <= LUA_CMD_OBJCACHE_MAX_LEN)
|
||||
{
|
||||
struct sdshdr *sh = (void*)(((char*)(o->ptr))-(sizeof(struct sdshdr)));
|
||||
|
||||
if (cached_objects[j]) decrRefCount(cached_objects[j]);
|
||||
cached_objects[j] = o;
|
||||
cached_objects_len[j] = sh->free + sh->len;
|
||||
} else {
|
||||
decrRefCount(o);
|
||||
}
|
||||
}
|
||||
|
||||
if (c->argv != argv) {
|
||||
zfree(c->argv);
|
||||
argv = NULL;
|
||||
}
|
||||
for (j = 0; j < c->argc; j++)
|
||||
decrRefCount(c->argv[j]);
|
||||
zfree(c->argv);
|
||||
|
||||
if (raise_error) {
|
||||
/* If we are here we should have an error in the stack, in the
|
||||
@@ -521,7 +452,8 @@ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||
* here when the EVAL command will return. */
|
||||
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
|
||||
}
|
||||
if (server.lua_timedout) processEventsWhileBlocked();
|
||||
if (server.lua_timedout)
|
||||
aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
|
||||
if (server.lua_kill) {
|
||||
redisLog(REDIS_WARNING,"Lua script killed by user with SCRIPT KILL.");
|
||||
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
|
||||
@@ -544,7 +476,7 @@ void luaLoadLibraries(lua_State *lua) {
|
||||
luaLoadLib(lua, LUA_TABLIBNAME, luaopen_table);
|
||||
luaLoadLib(lua, LUA_STRLIBNAME, luaopen_string);
|
||||
luaLoadLib(lua, LUA_MATHLIBNAME, luaopen_math);
|
||||
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
|
||||
luaLoadLib(lua, LUA_DBLIBNAME, luaopen_debug);
|
||||
luaLoadLib(lua, "cjson", luaopen_cjson);
|
||||
luaLoadLib(lua, "struct", luaopen_struct);
|
||||
luaLoadLib(lua, "cmsgpack", luaopen_cmsgpack);
|
||||
@@ -924,12 +856,8 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
int j;
|
||||
char *sha = c->argv[1]->ptr;
|
||||
|
||||
/* Convert to lowercase. We don't use tolower since the function
|
||||
* managed to always show up in the profiler output consuming
|
||||
* a non trivial amount of time. */
|
||||
for (j = 0; j < 40; j++)
|
||||
funcname[j+2] = (sha[j] >= 'A' && sha[j] <= 'Z') ?
|
||||
sha[j]+('a'-'A') : sha[j];
|
||||
funcname[j+2] = tolower(sha[j]);
|
||||
funcname[42] = '\0';
|
||||
}
|
||||
|
||||
@@ -966,7 +894,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
|
||||
/* Select the right DB in the context of the Lua client */
|
||||
selectDb(server.lua_client,c->db->id);
|
||||
|
||||
|
||||
/* Set a hook in order to be able to stop the script execution if it
|
||||
* is running for too much time.
|
||||
* We set the hook only if the time limit is enabled as the hook will
|
||||
@@ -995,23 +923,7 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
}
|
||||
server.lua_caller = NULL;
|
||||
selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */
|
||||
|
||||
/* Call the Lua garbage collector from time to time to avoid a
|
||||
* full cycle performed by Lua, which adds too latency.
|
||||
*
|
||||
* The call is performed every LUA_GC_CYCLE_PERIOD executed commands
|
||||
* (and for LUA_GC_CYCLE_PERIOD collection steps) because calling it
|
||||
* for every command uses too much CPU. */
|
||||
#define LUA_GC_CYCLE_PERIOD 50
|
||||
{
|
||||
static long gc_count = 0;
|
||||
|
||||
gc_count++;
|
||||
if (gc_count == LUA_GC_CYCLE_PERIOD) {
|
||||
lua_gc(lua,LUA_GCSTEP,LUA_GC_CYCLE_PERIOD);
|
||||
gc_count = 0;
|
||||
}
|
||||
}
|
||||
lua_gc(lua,LUA_GCSTEP,1);
|
||||
|
||||
if (err) {
|
||||
addReplyErrorFormat(c,"Error running script (call to %s): %s\n",
|
||||
|
||||
@@ -289,84 +289,6 @@ sds sdscpy(sds s, const char *t) {
|
||||
return sdscpylen(s, t, strlen(t));
|
||||
}
|
||||
|
||||
/* Helper for sdscatlonglong() doing the actual number -> string
|
||||
* conversion. 's' must point to a string with room for at least
|
||||
* SDS_LLSTR_SIZE bytes.
|
||||
*
|
||||
* The function returns the lenght of the null-terminated string
|
||||
* representation stored at 's'. */
|
||||
#define SDS_LLSTR_SIZE 21
|
||||
int sdsll2str(char *s, long long value) {
|
||||
char *p, aux;
|
||||
unsigned long long v;
|
||||
size_t l;
|
||||
|
||||
/* Generate the string representation, this method produces
|
||||
* an reversed string. */
|
||||
v = (value < 0) ? -value : value;
|
||||
p = s;
|
||||
do {
|
||||
*p++ = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p++ = '-';
|
||||
|
||||
/* Compute length and add null term. */
|
||||
l = p-s;
|
||||
*p = '\0';
|
||||
|
||||
/* Reverse the string. */
|
||||
p--;
|
||||
while(s < p) {
|
||||
aux = *s;
|
||||
*s = *p;
|
||||
*p = aux;
|
||||
s++;
|
||||
p--;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Identical sdsll2str(), but for unsigned long long type. */
|
||||
int sdsull2str(char *s, unsigned long long v) {
|
||||
char *p, aux;
|
||||
size_t l;
|
||||
|
||||
/* Generate the string representation, this method produces
|
||||
* an reversed string. */
|
||||
p = s;
|
||||
do {
|
||||
*p++ = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
|
||||
/* Compute length and add null term. */
|
||||
l = p-s;
|
||||
*p = '\0';
|
||||
|
||||
/* Reverse the string. */
|
||||
p--;
|
||||
while(s < p) {
|
||||
aux = *s;
|
||||
*s = *p;
|
||||
*p = aux;
|
||||
s++;
|
||||
p--;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
/* Create an sds string from a long long value. It is much faster than:
|
||||
*
|
||||
* sdscatprintf(sdsempty(),"%lld\n", value);
|
||||
*/
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
int len = sdsll2str(buf,value);
|
||||
|
||||
return sdsnewlen(buf,len);
|
||||
}
|
||||
|
||||
/* Like sdscatpritf() but gets va_list instead of being variadic. */
|
||||
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
va_list cpy;
|
||||
@@ -429,122 +351,6 @@ sds sdscatprintf(sds s, const char *fmt, ...) {
|
||||
return t;
|
||||
}
|
||||
|
||||
/* This function is similar to sdscatprintf, but much faster as it does
|
||||
* not rely on sprintf() family functions implemented by the libc that
|
||||
* are often very slow. Moreover directly handling the sds string as
|
||||
* new data is concatenated provides a performance improvement.
|
||||
*
|
||||
* However this function only handles an incompatible subset of printf-alike
|
||||
* format specifiers:
|
||||
*
|
||||
* %s - C String
|
||||
* %S - SDS string
|
||||
* %i - signed int
|
||||
* %I - 64 bit signed integer (long long, int64_t)
|
||||
* %u - unsigned int
|
||||
* %U - 64 bit unsigned integer (unsigned long long, uint64_t)
|
||||
* %% - Verbatim "%" character.
|
||||
*/
|
||||
sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
size_t initlen = sdslen(s);
|
||||
const char *f = fmt;
|
||||
int i;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap,fmt);
|
||||
f = fmt; /* Next format specifier byte to process. */
|
||||
i = initlen; /* Position of the next byte to write to dest str. */
|
||||
while(*f) {
|
||||
char next, *str;
|
||||
size_t l;
|
||||
long long num;
|
||||
unsigned long long unum;
|
||||
|
||||
/* Make sure there is always space for at least 1 char. */
|
||||
if (sh->free == 0) {
|
||||
s = sdsMakeRoomFor(s,1);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
|
||||
switch(*f) {
|
||||
case '%':
|
||||
next = *(f+1);
|
||||
f++;
|
||||
switch(next) {
|
||||
case 's':
|
||||
case 'S':
|
||||
str = va_arg(ap,char*);
|
||||
l = (next == 's') ? strlen(str) : sdslen(str);
|
||||
if (sh->free < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
memcpy(s+i,str,l);
|
||||
sh->len += l;
|
||||
sh->free -= l;
|
||||
i += l;
|
||||
break;
|
||||
case 'i':
|
||||
case 'I':
|
||||
if (next == 'i')
|
||||
num = va_arg(ap,int);
|
||||
else
|
||||
num = va_arg(ap,long long);
|
||||
{
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
l = sdsll2str(buf,num);
|
||||
if (sh->free < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sh->len += l;
|
||||
sh->free -= l;
|
||||
i += l;
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
case 'U':
|
||||
if (next == 'u')
|
||||
unum = va_arg(ap,unsigned int);
|
||||
else
|
||||
unum = va_arg(ap,unsigned long long);
|
||||
{
|
||||
char buf[SDS_LLSTR_SIZE];
|
||||
l = sdsull2str(buf,unum);
|
||||
if (sh->free < l) {
|
||||
s = sdsMakeRoomFor(s,l);
|
||||
sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
}
|
||||
memcpy(s+i,buf,l);
|
||||
sh->len += l;
|
||||
sh->free -= l;
|
||||
i += l;
|
||||
}
|
||||
break;
|
||||
default: /* Handle %% and generally %<unknown>. */
|
||||
s[i++] = next;
|
||||
sh->len += 1;
|
||||
sh->free -= 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
s[i++] = *f;
|
||||
sh->len += 1;
|
||||
sh->free -= 1;
|
||||
break;
|
||||
}
|
||||
f++;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
/* Add null-term */
|
||||
s[i] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Remove the part of the string from left and from right composed just of
|
||||
* contiguous characters found in 'cset', that is a null terminted C string.
|
||||
*
|
||||
@@ -732,6 +538,25 @@ void sdsfreesplitres(sds *tokens, int count) {
|
||||
zfree(tokens);
|
||||
}
|
||||
|
||||
/* Create an sds string from a long long value. It is much faster than:
|
||||
*
|
||||
* sdscatprintf(sdsempty(),"%lld\n", value);
|
||||
*/
|
||||
sds sdsfromlonglong(long long value) {
|
||||
char buf[32], *p;
|
||||
unsigned long long v;
|
||||
|
||||
v = (value < 0) ? -value : value;
|
||||
p = buf+31; /* point to the last character */
|
||||
do {
|
||||
*p-- = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p-- = '-';
|
||||
p++;
|
||||
return sdsnewlen(p,32-(p-buf));
|
||||
}
|
||||
|
||||
/* Append to the sds string "s" an escaped string representation where
|
||||
* all the non-printable characters (tested with isprint()) are turned into
|
||||
* escapes in the form "\n\r\a...." or "\x<hex-number>".
|
||||
@@ -962,7 +787,6 @@ sds sdsjoin(char **argv, int argc, char *sep) {
|
||||
#ifdef SDS_TEST_MAIN
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
#include "limits.h"
|
||||
|
||||
int main(void) {
|
||||
{
|
||||
@@ -993,61 +817,39 @@ int main(void) {
|
||||
sdsfree(x);
|
||||
x = sdscatprintf(sdsempty(),"%d",123);
|
||||
test_cond("sdscatprintf() seems working in the base case",
|
||||
sdslen(x) == 3 && memcmp(x,"123\0",4) == 0)
|
||||
sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("--");
|
||||
x = sdscatfmt(x, "Hello %s World %I,%I--", "Hi!", LLONG_MIN,LLONG_MAX);
|
||||
test_cond("sdscatfmt() seems working in the base case",
|
||||
sdslen(x) == 60 &&
|
||||
memcmp(x,"--Hello Hi! World -9223372036854775808,"
|
||||
"9223372036854775807--",60) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("--");
|
||||
x = sdscatfmt(x, "%u,%U--", UINT_MAX, ULLONG_MAX);
|
||||
test_cond("sdscatfmt() seems working with unsigned numbers",
|
||||
sdslen(x) == 35 &&
|
||||
memcmp(x,"--4294967295,18446744073709551615--",35) == 0)
|
||||
|
||||
sdsfree(x);
|
||||
x = sdsnew("xxciaoyyy");
|
||||
sdstrim(x,"xy");
|
||||
x = sdstrim(sdsnew("xxciaoyyy"),"xy");
|
||||
test_cond("sdstrim() correctly trims characters",
|
||||
sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
|
||||
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,1,1);
|
||||
y = sdsrange(sdsdup(x),1,1);
|
||||
test_cond("sdsrange(...,1,1)",
|
||||
sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,1,-1);
|
||||
y = sdsrange(sdsdup(x),1,-1);
|
||||
test_cond("sdsrange(...,1,-1)",
|
||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,-2,-1);
|
||||
y = sdsrange(sdsdup(x),-2,-1);
|
||||
test_cond("sdsrange(...,-2,-1)",
|
||||
sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,2,1);
|
||||
y = sdsrange(sdsdup(x),2,1);
|
||||
test_cond("sdsrange(...,2,1)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,1,100);
|
||||
y = sdsrange(sdsdup(x),1,100);
|
||||
test_cond("sdsrange(...,1,100)",
|
||||
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
|
||||
|
||||
sdsfree(y);
|
||||
y = sdsdup(x);
|
||||
sdsrange(y,100,100);
|
||||
y = sdsrange(sdsdup(x),100,100);
|
||||
test_cond("sdsrange(...,100,100)",
|
||||
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
|
||||
|
||||
@@ -1069,13 +871,6 @@ int main(void) {
|
||||
y = sdsnew("bar");
|
||||
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
|
||||
|
||||
sdsfree(y);
|
||||
sdsfree(x);
|
||||
x = sdsnewlen("\a\n\0foo\r",7);
|
||||
y = sdscatrepr(sdsempty(),x,sdslen(x));
|
||||
test_cond("sdscatrepr(...data...)",
|
||||
memcmp(y,"\"\\a\\n\\x00foo\\r\"",15) == 0)
|
||||
|
||||
{
|
||||
int oldfree;
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ sds sdscatprintf(sds s, const char *fmt, ...)
|
||||
sds sdscatprintf(sds s, const char *fmt, ...);
|
||||
#endif
|
||||
|
||||
sds sdscatfmt(sds s, char const *fmt, ...);
|
||||
sds sdstrim(sds s, const char *cset);
|
||||
void sdsrange(sds s, int start, int end);
|
||||
void sdsupdatelen(sds s);
|
||||
|
||||
+12
-31
@@ -88,7 +88,7 @@ typedef struct sentinelAddr {
|
||||
|
||||
/* Failover machine different states. */
|
||||
#define SENTINEL_FAILOVER_STATE_NONE 0 /* No failover in progress. */
|
||||
#define SENTINEL_FAILOVER_STATE_WAIT_START 1 /* Wait for failover_start_time*/
|
||||
#define SENTINEL_FAILOVER_STATE_WAIT_START 1 /* Wait for failover_start_time*/
|
||||
#define SENTINEL_FAILOVER_STATE_SELECT_SLAVE 2 /* Select slave to promote */
|
||||
#define SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE 3 /* Slave -> Master */
|
||||
#define SENTINEL_FAILOVER_STATE_WAIT_PROMOTION 4 /* Wait slave to change role */
|
||||
@@ -183,8 +183,6 @@ typedef struct sentinelRedisInstance {
|
||||
mstime_t failover_state_change_time;
|
||||
mstime_t failover_start_time; /* Last failover attempt start time. */
|
||||
mstime_t failover_timeout; /* Max time to refresh failover state. */
|
||||
mstime_t failover_delay_logged; /* For what failover_start_time value we
|
||||
logged the failover delay. */
|
||||
struct sentinelRedisInstance *promoted_slave; /* Promoted slave instance. */
|
||||
/* Scripts executed to notify admin or reconfigure clients: when they
|
||||
* are set to NULL no script is executed. */
|
||||
@@ -493,7 +491,7 @@ int sentinelAddrIsEqual(sentinelAddr *a, sentinelAddr *b) {
|
||||
/* =========================== Events notification ========================== */
|
||||
|
||||
/* Send an event to log, pub/sub, user notification script.
|
||||
*
|
||||
*
|
||||
* 'level' is the log level for logging. Only REDIS_WARNING events will trigger
|
||||
* the execution of the user notification script.
|
||||
*
|
||||
@@ -616,7 +614,7 @@ void sentinelScheduleScriptExecution(char *path, ...) {
|
||||
}
|
||||
va_end(ap);
|
||||
argv[0] = sdsnew(path);
|
||||
|
||||
|
||||
sj = zmalloc(sizeof(*sj));
|
||||
sj->flags = SENTINEL_SCRIPT_NONE;
|
||||
sj->retry_num = 0;
|
||||
@@ -742,7 +740,7 @@ void sentinelCollectTerminatedScripts(void) {
|
||||
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
||||
sentinelEvent(REDIS_DEBUG,"-script-child",NULL,"%ld %d %d",
|
||||
(long)pid, exitcode, bysignal);
|
||||
|
||||
|
||||
ln = sentinelGetScriptListNodeByPid(pid);
|
||||
if (ln == NULL) {
|
||||
redisLog(REDIS_WARNING,"wait3() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid);
|
||||
@@ -969,7 +967,6 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
|
||||
ri->failover_state_change_time = 0;
|
||||
ri->failover_start_time = 0;
|
||||
ri->failover_timeout = SENTINEL_DEFAULT_FAILOVER_TIMEOUT;
|
||||
ri->failover_delay_logged = 0;
|
||||
ri->promoted_slave = NULL;
|
||||
ri->notification_script = NULL;
|
||||
ri->client_reconfig_script = NULL;
|
||||
@@ -1020,7 +1017,7 @@ sentinelRedisInstance *sentinelRedisInstanceLookupSlave(
|
||||
{
|
||||
sds key;
|
||||
sentinelRedisInstance *slave;
|
||||
|
||||
|
||||
redisAssert(ri->flags & SRI_MASTER);
|
||||
key = sdscatprintf(sdsempty(),
|
||||
strchr(ip,':') ? "[%s]:%d" : "%s:%d",
|
||||
@@ -1040,7 +1037,7 @@ const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri) {
|
||||
|
||||
/* This function removes all the instances found in the dictionary of
|
||||
* sentinels in the specified 'master', having either:
|
||||
*
|
||||
*
|
||||
* 1) The same ip/port as specified.
|
||||
* 2) The same runid.
|
||||
*
|
||||
@@ -1234,7 +1231,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip,
|
||||
slave->addr->port);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
|
||||
/* If we are switching to a different address, include the old address
|
||||
* as a slave as well, so that we'll be able to sense / reconfigure
|
||||
* the old master. */
|
||||
@@ -1847,7 +1844,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
ri->slave_conf_change_time = mstime();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* master_link_status:<status> */
|
||||
if (sdslen(l) >= 19 && !memcmp(l,"master_link_status:",19)) {
|
||||
ri->slave_master_link_status =
|
||||
@@ -2130,7 +2127,6 @@ void sentinelProcessHelloMessage(char *hello, int hello_len) {
|
||||
{
|
||||
sentinelAddr *old_addr;
|
||||
|
||||
sentinelEvent(REDIS_WARNING,"+config-update-from",si,"%@");
|
||||
sentinelEvent(REDIS_WARNING,"+switch-master",
|
||||
master,"%s %s %d %s %d",
|
||||
master->name,
|
||||
@@ -2168,7 +2164,7 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
|
||||
* receive our messages as well this timestamp can be used to detect
|
||||
* if the link is probably disconnected even if it seems otherwise. */
|
||||
ri->pc_last_activity = mstime();
|
||||
|
||||
|
||||
/* Sanity check in the reply we expect, so that the code that follows
|
||||
* can avoid to check for details. */
|
||||
if (r->type != REDIS_REPLY_ARRAY ||
|
||||
@@ -2875,7 +2871,7 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
|
||||
if (ri->last_ping_time)
|
||||
elapsed = mstime() - ri->last_ping_time;
|
||||
|
||||
/* Check if we are in need for a reconnection of one of the
|
||||
/* Check if we are in need for a reconnection of one of the
|
||||
* links, because we are detecting low activity.
|
||||
*
|
||||
* 1) Check if the command link seems connected, was connected not less
|
||||
@@ -3241,7 +3237,7 @@ void sentinelStartFailover(sentinelRedisInstance *master) {
|
||||
* 1) Master must be in ODOWN condition.
|
||||
* 2) No failover already in progress.
|
||||
* 3) No failover already attempted recently.
|
||||
*
|
||||
*
|
||||
* We still don't know if we'll win the election so it is possible that we
|
||||
* start the failover but that we'll not be able to act.
|
||||
*
|
||||
@@ -3255,22 +3251,7 @@ int sentinelStartFailoverIfNeeded(sentinelRedisInstance *master) {
|
||||
|
||||
/* Last failover attempt started too little time ago? */
|
||||
if (mstime() - master->failover_start_time <
|
||||
master->failover_timeout*2)
|
||||
{
|
||||
if (master->failover_delay_logged != master->failover_start_time) {
|
||||
time_t clock = (master->failover_start_time +
|
||||
master->failover_timeout*2) / 1000;
|
||||
char ctimebuf[26];
|
||||
|
||||
ctime_r(&clock,ctimebuf);
|
||||
ctimebuf[24] = '\0'; /* Remove newline. */
|
||||
master->failover_delay_logged = master->failover_start_time;
|
||||
redisLog(REDIS_WARNING,
|
||||
"Next failover delay: I will not start a failover before %s",
|
||||
ctimebuf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
master->failover_timeout*2) return 0;
|
||||
|
||||
sentinelStartFailover(master);
|
||||
return 1;
|
||||
|
||||
+6
-4
@@ -63,15 +63,17 @@ slowlogEntry *slowlogCreateEntry(robj **argv, int argc, long long duration) {
|
||||
} else {
|
||||
/* Trim too long strings as well... */
|
||||
if (argv[j]->type == REDIS_STRING &&
|
||||
sdsEncodedObject(argv[j]) &&
|
||||
sdslen(argv[j]->ptr) > SLOWLOG_ENTRY_MAX_STRING)
|
||||
(sdsEncodedObject(argv[j]) || lzfEncodedObject(argv[j])) &&
|
||||
stringObjectLen(argv[j]) > SLOWLOG_ENTRY_MAX_STRING)
|
||||
{
|
||||
sds s = sdsnewlen(argv[j]->ptr, SLOWLOG_ENTRY_MAX_STRING);
|
||||
robj *o = getDecodedObject(argv[j]);
|
||||
sds s = sdsnewlen(o->ptr, SLOWLOG_ENTRY_MAX_STRING);
|
||||
|
||||
s = sdscatprintf(s,"... (%lu more bytes)",
|
||||
(unsigned long)
|
||||
sdslen(argv[j]->ptr) - SLOWLOG_ENTRY_MAX_STRING);
|
||||
stringObjectLen(argv[j]) - SLOWLOG_ENTRY_MAX_STRING);
|
||||
se->argv[j] = createObject(REDIS_STRING,s);
|
||||
decrRefCount(o);
|
||||
} else {
|
||||
se->argv[j] = argv[j];
|
||||
incrRefCount(argv[j]);
|
||||
|
||||
@@ -242,6 +242,7 @@ void getrangeCommand(redisClient *c) {
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
|
||||
checkType(c,o,REDIS_STRING)) return;
|
||||
|
||||
if (lzfEncodedObject(o)) o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
if (o->encoding == REDIS_ENCODING_INT) {
|
||||
str = llbuf;
|
||||
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
|
||||
|
||||
+128
-767
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -402,7 +402,7 @@ void getRandomHexChars(char *p, unsigned int len) {
|
||||
/* Turn it into hex digits taking just 4 bits out of 8 for every byte. */
|
||||
for (j = 0; j < len; j++)
|
||||
p[j] = charset[p[j] & 0x0F];
|
||||
if (fp) fclose(fp);
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
/* Given the filename, return the absolute path as an SDS string, or NULL
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
# Cluster-specific test functions.
|
||||
#
|
||||
# Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
# Returns a parsed CLUSTER NODES output as a list of dictionaries.
|
||||
proc get_cluster_nodes id {
|
||||
set lines [split [R $id cluster nodes] "\r\n"]
|
||||
set nodes {}
|
||||
foreach l $lines {
|
||||
set l [string trim $l]
|
||||
if {$l eq {}} continue
|
||||
set args [split $l]
|
||||
set node [dict create \
|
||||
id [lindex $args 0] \
|
||||
addr [lindex $args 1] \
|
||||
flags [split [lindex $args 2] ,] \
|
||||
slaveof [lindex $args 3] \
|
||||
ping_sent [lindex $args 4] \
|
||||
pong_recv [lindex $args 5] \
|
||||
config_epoch [lindex $args 6] \
|
||||
linkstate [lindex $args 7] \
|
||||
slots [lrange $args 8 -1] \
|
||||
]
|
||||
lappend nodes $node
|
||||
}
|
||||
return $nodes
|
||||
}
|
||||
|
||||
# Test node for flag.
|
||||
proc has_flag {node flag} {
|
||||
expr {[lsearch -exact [dict get $node flags] $flag] != -1}
|
||||
}
|
||||
|
||||
# Returns the parsed myself node entry as a dictionary.
|
||||
proc get_myself id {
|
||||
set nodes [get_cluster_nodes $id]
|
||||
foreach n $nodes {
|
||||
if {[has_flag $n myself]} {return $n}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
# Return the value of the specified CLUSTER INFO field.
|
||||
proc CI {n field} {
|
||||
get_info_field [R $n cluster info] $field
|
||||
}
|
||||
|
||||
# Assuming nodes are reest, this function performs slots allocation.
|
||||
# Only the first 'n' nodes are used.
|
||||
proc cluster_allocate_slots {n} {
|
||||
set slot 16383
|
||||
while {$slot >= 0} {
|
||||
# Allocate successive slots to random nodes.
|
||||
set node [randomInt $n]
|
||||
lappend slots_$node $slot
|
||||
incr slot -1
|
||||
}
|
||||
for {set j 0} {$j < $n} {incr j} {
|
||||
R $j cluster addslots {*}[set slots_${j}]
|
||||
}
|
||||
}
|
||||
|
||||
# Check that cluster nodes agree about "state", or raise an error.
|
||||
proc assert_cluster_state {state} {
|
||||
foreach_redis_id id {
|
||||
if {[instance_is_killed redis $id]} continue
|
||||
wait_for_condition 1000 50 {
|
||||
[CI $id cluster_state] eq $state
|
||||
} else {
|
||||
fail "Cluster node $id cluster_state:[CI $id cluster_state]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Search the first node starting from ID $first that is not
|
||||
# already configured as a slave.
|
||||
proc cluster_find_available_slave {first} {
|
||||
foreach_redis_id id {
|
||||
if {$id < $first} continue
|
||||
if {[instance_is_killed redis $id]} continue
|
||||
set me [get_myself $id]
|
||||
if {[dict get $me slaveof] eq {-}} {return $id}
|
||||
}
|
||||
fail "No available slaves"
|
||||
}
|
||||
|
||||
# Add 'slaves' slaves to a cluster composed of 'masters' masters.
|
||||
# It assumes that masters are allocated sequentially from instance ID 0
|
||||
# to N-1.
|
||||
proc cluster_allocate_slaves {masters slaves} {
|
||||
for {set j 0} {$j < $slaves} {incr j} {
|
||||
set master_id [expr {$j % $masters}]
|
||||
set slave_id [cluster_find_available_slave $masters]
|
||||
set master_myself [get_myself $master_id]
|
||||
R $slave_id cluster replicate [dict get $master_myself id]
|
||||
}
|
||||
}
|
||||
|
||||
# Create a cluster composed of the specified number of masters and slaves.
|
||||
proc create_cluster {masters slaves} {
|
||||
cluster_allocate_slots $masters
|
||||
if {$slaves} {
|
||||
cluster_allocate_slaves $masters $slaves
|
||||
}
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
# Set the cluster node-timeout to all the reachalbe nodes.
|
||||
proc set_cluster_node_timeout {to} {
|
||||
foreach_redis_id id {
|
||||
catch {R $id CONFIG SET cluster-node-timeout $to}
|
||||
}
|
||||
}
|
||||
|
||||
# Check if the cluster is writable and readable. Use node "id"
|
||||
# as a starting point to talk with the cluster.
|
||||
proc cluster_write_test {id} {
|
||||
set prefix [randstring 20 20 alpha]
|
||||
set port [get_instance_attrib redis $id port]
|
||||
set cluster [redis_cluster 127.0.0.1:$port]
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
$cluster set key.$j $prefix.$j
|
||||
}
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
assert {[$cluster get key.$j] eq "$prefix.$j"}
|
||||
}
|
||||
$cluster close
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
# Cluster test suite. Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
cd tests/cluster
|
||||
source cluster.tcl
|
||||
source ../instances.tcl
|
||||
source ../../support/cluster.tcl ; # Redis Cluster client.
|
||||
|
||||
set ::instances_count 20 ; # How many instances we use at max.
|
||||
|
||||
proc main {} {
|
||||
parse_options
|
||||
spawn_instance redis $::redis_base_port $::instances_count {
|
||||
"cluster-enabled yes"
|
||||
"appendonly yes"
|
||||
}
|
||||
run_tests
|
||||
cleanup
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
cleanup
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
if {$::simulate_error} {
|
||||
test "This test will fail" {
|
||||
fail "Simulated error"
|
||||
}
|
||||
}
|
||||
|
||||
test "Different nodes have different IDs" {
|
||||
set ids {}
|
||||
set numnodes 0
|
||||
foreach_redis_id id {
|
||||
incr numnodes
|
||||
# Every node should just know itself.
|
||||
set nodeid [dict get [get_myself $id] id]
|
||||
assert {$nodeid ne {}}
|
||||
lappend ids $nodeid
|
||||
}
|
||||
set numids [llength [lsort -unique $ids]]
|
||||
assert {$numids == $numnodes}
|
||||
}
|
||||
|
||||
test "It is possible to perform slot allocation" {
|
||||
cluster_allocate_slots 5
|
||||
}
|
||||
|
||||
test "After the join, every node gets a different config epoch" {
|
||||
set trynum 60
|
||||
while {[incr trynum -1] != 0} {
|
||||
# We check that this condition is true for *all* the nodes.
|
||||
set ok 1 ; # Will be set to 0 every time a node is not ok.
|
||||
foreach_redis_id id {
|
||||
set epochs {}
|
||||
foreach n [get_cluster_nodes $id] {
|
||||
lappend epochs [dict get $n config_epoch]
|
||||
}
|
||||
if {[lsort $epochs] != [lsort -unique $epochs]} {
|
||||
set ok 0 ; # At least one collision!
|
||||
}
|
||||
}
|
||||
if {$ok} break
|
||||
after 1000
|
||||
puts -nonewline .
|
||||
flush stdout
|
||||
}
|
||||
if {$trynum == 0} {
|
||||
fail "Config epoch conflict resolution is not working."
|
||||
}
|
||||
}
|
||||
|
||||
test "Nodes should report cluster_state is ok now" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "It is possible to write and read from the cluster" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 5
|
||||
}
|
||||
|
||||
test "Cluster should start ok" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Killing two slave nodes" {
|
||||
kill_instance redis 5
|
||||
kill_instance redis 6
|
||||
}
|
||||
|
||||
test "Cluster should be still up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Killing one master node" {
|
||||
kill_instance redis 0
|
||||
}
|
||||
|
||||
# Note: the only slave of instance 0 is already down so no
|
||||
# failover is possible, that would change the state back to ok.
|
||||
test "Cluster should be down now" {
|
||||
assert_cluster_state fail
|
||||
}
|
||||
|
||||
test "Restarting master node" {
|
||||
restart_instance redis 0
|
||||
}
|
||||
|
||||
test "Cluster should be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
|
||||
test "Create a 5 nodes cluster" {
|
||||
create_cluster 5 5
|
||||
}
|
||||
|
||||
test "Cluster is up" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 0
|
||||
}
|
||||
|
||||
test "Instance #5 is a slave" {
|
||||
assert {[RI 5 role] eq {slave}}
|
||||
}
|
||||
|
||||
test "Killing one master node" {
|
||||
kill_instance redis 0
|
||||
}
|
||||
|
||||
test "Cluster should eventually be up again" {
|
||||
assert_cluster_state ok
|
||||
}
|
||||
|
||||
test "Cluster is writable" {
|
||||
cluster_write_test 1
|
||||
}
|
||||
|
||||
test "Instance #5 is now a master" {
|
||||
assert {[RI 5 role] eq {master}}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
# Initialization tests -- most units will start including this.
|
||||
|
||||
test "(init) Restart killed instances" {
|
||||
foreach type {redis} {
|
||||
foreach_${type}_id id {
|
||||
if {[get_instance_attrib $type $id pid] == -1} {
|
||||
puts -nonewline "$type/$id "
|
||||
flush stdout
|
||||
restart_instance $type $id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster nodes are reachable" {
|
||||
foreach_redis_id id {
|
||||
# Every node should just know itself.
|
||||
assert {[R $id ping] eq {PONG}}
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster nodes hard reset" {
|
||||
foreach_redis_id id {
|
||||
catch {R $id flushall} ; # May fail for readonly slaves.
|
||||
R $id cluster reset hard
|
||||
R $id config set cluster-node-timeout 3000
|
||||
}
|
||||
}
|
||||
|
||||
test "Cluster Join and auto-discovery test" {
|
||||
# Join node 0 with 1, 1 with 2, ... and so forth.
|
||||
# If auto-discovery works all nodes will know every other node
|
||||
# eventually.
|
||||
set ids {}
|
||||
foreach_redis_id id {lappend ids $id}
|
||||
for {set j 0} {$j < [expr [llength $ids]-1]} {incr j} {
|
||||
set a [lindex $ids $j]
|
||||
set b [lindex $ids [expr $j+1]]
|
||||
set b_port [get_instance_attrib redis $b port]
|
||||
R $a cluster meet 127.0.0.1 $b_port
|
||||
}
|
||||
|
||||
foreach_redis_id id {
|
||||
wait_for_condition 1000 50 {
|
||||
[llength [get_cluster_nodes $id]] == [llength $ids]
|
||||
} else {
|
||||
fail "Cluster failed to join into a full mesh."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "Before slots allocation, all nodes report cluster failure" {
|
||||
assert_cluster_state fail
|
||||
}
|
||||
@@ -91,13 +91,8 @@ tags {"aof"} {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
|
||||
test "Fixed AOF: Keyspace should contain values that were parsable" {
|
||||
test "Fixed AOF: Keyspace should contain values that were parsable" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
assert_equal "hello" [$client get foo]
|
||||
assert_equal "" [$client get bar]
|
||||
}
|
||||
@@ -117,11 +112,6 @@ tags {"aof"} {
|
||||
|
||||
test "AOF+SPOP: Set should have 1 member" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
assert_equal 1 [$client scard set]
|
||||
}
|
||||
}
|
||||
@@ -140,11 +130,6 @@ tags {"aof"} {
|
||||
|
||||
test "AOF+EXPIRE: List should be empty" {
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
wait_for_condition 50 100 {
|
||||
[catch {$client ping} e] == 0
|
||||
} else {
|
||||
fail "Loading DB is taking too much time."
|
||||
}
|
||||
assert_equal 0 [$client llen list]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,66 +6,6 @@ start_server {tags {"repl"}} {
|
||||
s -1 role
|
||||
} {slave}
|
||||
|
||||
test {If min-slaves-to-write is honored, write is accepted} {
|
||||
r config set min-slaves-to-write 1
|
||||
r config set min-slaves-max-lag 10
|
||||
r set foo 12345
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get foo] eq {12345}
|
||||
} else {
|
||||
fail "Write did not reached slave"
|
||||
}
|
||||
}
|
||||
|
||||
test {No write if min-slaves-to-write is < attached slaves} {
|
||||
r config set min-slaves-to-write 2
|
||||
r config set min-slaves-max-lag 10
|
||||
catch {r set foo 12345} err
|
||||
set err
|
||||
} {NOREPLICAS*}
|
||||
|
||||
test {If min-slaves-to-write is honored, write is accepted (again)} {
|
||||
r config set min-slaves-to-write 1
|
||||
r config set min-slaves-max-lag 10
|
||||
r set foo 12345
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get foo] eq {12345}
|
||||
} else {
|
||||
fail "Write did not reached slave"
|
||||
}
|
||||
}
|
||||
|
||||
test {No write if min-slaves-max-lag is > of the slave lag} {
|
||||
r -1 deferred 1
|
||||
r config set min-slaves-to-write 1
|
||||
r config set min-slaves-max-lag 2
|
||||
r -1 debug sleep 6
|
||||
assert {[r set foo 12345] eq {OK}}
|
||||
after 4000
|
||||
catch {r set foo 12345} err
|
||||
assert {[r -1 read] eq {OK}}
|
||||
r -1 deferred 0
|
||||
set err
|
||||
} {NOREPLICAS*}
|
||||
|
||||
test {min-slaves-to-write is ignored by slaves} {
|
||||
r config set min-slaves-to-write 1
|
||||
r config set min-slaves-max-lag 10
|
||||
r -1 config set min-slaves-to-write 1
|
||||
r -1 config set min-slaves-max-lag 10
|
||||
r set foo aaabbb
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get foo] eq {aaabbb}
|
||||
} else {
|
||||
fail "Write did not reached slave"
|
||||
}
|
||||
}
|
||||
|
||||
# Fix parameters for the next test to work
|
||||
r config set min-slaves-to-write 0
|
||||
r -1 config set min-slaves-to-write 0
|
||||
r flushall
|
||||
|
||||
test {MASTER and SLAVE dataset should be identical after complex ops} {
|
||||
createComplexDataset r 10000
|
||||
after 500
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Check the basic monitoring and failover capabilities.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
source "../sentinel-tests/includes/init-tests.tcl"
|
||||
|
||||
if {$::simulate_error} {
|
||||
test "This test will fail" {
|
||||
@@ -1,6 +1,6 @@
|
||||
# Test Sentinel configuration consistency after partitions heal.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
source "../sentinel-tests/includes/init-tests.tcl"
|
||||
|
||||
test "We can failover with Sentinel 1 crashed" {
|
||||
set old_port [RI $master_id tcp_port]
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
# 2) That partitioned slaves point to new master when they are partitioned
|
||||
# away during failover and return at a latter time.
|
||||
|
||||
source "../tests/includes/init-tests.tcl"
|
||||
source "../sentinel-tests/includes/init-tests.tcl"
|
||||
|
||||
proc 03_test_slaves_replication {} {
|
||||
uplevel 1 {
|
||||
@@ -1,19 +1,14 @@
|
||||
# Multi-instance test framework.
|
||||
# This is used in order to test Sentinel and Redis Cluster, and provides
|
||||
# basic capabilities for spawning and handling N parallel Redis / Sentinel
|
||||
# instances.
|
||||
#
|
||||
# Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# Sentinel test suite. Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
package require Tcl 8.5
|
||||
|
||||
set tcl_precision 17
|
||||
source ../support/redis.tcl
|
||||
source ../support/util.tcl
|
||||
source ../support/server.tcl
|
||||
source ../support/test.tcl
|
||||
source tests/support/redis.tcl
|
||||
source tests/support/util.tcl
|
||||
source tests/support/server.tcl
|
||||
source tests/support/test.tcl
|
||||
|
||||
set ::verbose 0
|
||||
set ::pause_on_error 0
|
||||
@@ -22,50 +17,45 @@ set ::sentinel_instances {}
|
||||
set ::redis_instances {}
|
||||
set ::sentinel_base_port 20000
|
||||
set ::redis_base_port 30000
|
||||
set ::instances_count 5 ; # How many Sentinels / Instances we use at max
|
||||
set ::pids {} ; # We kill everything at exit
|
||||
set ::dirs {} ; # We remove all the temp dirs at exit
|
||||
set ::run_matching {} ; # If non empty, only tests matching pattern are run.
|
||||
|
||||
if {[catch {cd tmp}]} {
|
||||
puts "tmp directory not found."
|
||||
if {[catch {cd tests/sentinel-tmp}]} {
|
||||
puts "tests/sentinel-tmp directory not found."
|
||||
puts "Please run this test from the Redis source root."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Spawn a redis or sentinel instance, depending on 'type'.
|
||||
proc spawn_instance {type base_port count {conf {}}} {
|
||||
proc spawn_instance {type base_port count} {
|
||||
for {set j 0} {$j < $count} {incr j} {
|
||||
set port [find_available_port $base_port]
|
||||
incr base_port
|
||||
puts "Starting $type #$j at port $port"
|
||||
|
||||
# Create a directory for this instance.
|
||||
# Create a directory for this Sentinel.
|
||||
set dirname "${type}_${j}"
|
||||
lappend ::dirs $dirname
|
||||
catch {exec rm -rf $dirname}
|
||||
file mkdir $dirname
|
||||
|
||||
# Write the instance config file.
|
||||
# Write the Sentinel config file.
|
||||
set cfgfile [file join $dirname $type.conf]
|
||||
set cfg [open $cfgfile w]
|
||||
puts $cfg "port $port"
|
||||
puts $cfg "dir ./$dirname"
|
||||
puts $cfg "logfile log.txt"
|
||||
# Add additional config files
|
||||
foreach directive $conf {
|
||||
puts $cfg $directive
|
||||
}
|
||||
close $cfg
|
||||
|
||||
# Finally exec it and remember the pid for later cleanup.
|
||||
if {$type eq "redis"} {
|
||||
set prgname redis-server
|
||||
} elseif {$type eq "sentinel"} {
|
||||
set prgname redis-sentinel
|
||||
} else {
|
||||
error "Unknown instance type."
|
||||
set prgname redis-sentinel
|
||||
}
|
||||
set pid [exec ../../../src/${prgname} $cfgfile &]
|
||||
set pid [exec ../../src/${prgname} $cfgfile &]
|
||||
lappend ::pids $pid
|
||||
|
||||
# Check availability
|
||||
@@ -126,6 +116,14 @@ proc parse_options {} {
|
||||
}
|
||||
}
|
||||
|
||||
proc main {} {
|
||||
parse_options
|
||||
spawn_instance sentinel $::sentinel_base_port $::instances_count
|
||||
spawn_instance redis $::redis_base_port $::instances_count
|
||||
run_tests
|
||||
cleanup
|
||||
}
|
||||
|
||||
# If --pause-on-error option was passed at startup this function is called
|
||||
# on error in order to give the developer a chance to understand more about
|
||||
# the error condition while the instances are still running.
|
||||
@@ -220,7 +218,7 @@ proc test {descr code} {
|
||||
}
|
||||
|
||||
proc run_tests {} {
|
||||
set tests [lsort [glob ../tests/*]]
|
||||
set tests [lsort [glob ../sentinel-tests/*]]
|
||||
foreach test $tests {
|
||||
if {$::run_matching ne {} && [string match $::run_matching $test] == 0} {
|
||||
continue
|
||||
@@ -363,7 +361,7 @@ proc kill_instance {type id} {
|
||||
# Return true of the instance of the specified type/id is killed.
|
||||
proc instance_is_killed {type id} {
|
||||
set pid [get_instance_attrib $type $id pid]
|
||||
expr {$pid == -1}
|
||||
return $pid == -1
|
||||
}
|
||||
|
||||
# Restart an instance previously killed by kill_instance
|
||||
@@ -379,7 +377,7 @@ proc restart_instance {type id} {
|
||||
} else {
|
||||
set prgname redis-sentinel
|
||||
}
|
||||
set pid [exec ../../../src/${prgname} $cfgfile &]
|
||||
set pid [exec ../../src/${prgname} $cfgfile &]
|
||||
set_instance_attrib $type $id pid $pid
|
||||
lappend ::pids $pid
|
||||
|
||||
@@ -392,3 +390,7 @@ proc restart_instance {type id} {
|
||||
set_instance_attrib $type $id link [redis 127.0.0.1 $port]
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
cleanup
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Sentinel test suite. Copyright (C) 2014 Salvatore Sanfilippo antirez@gmail.com
|
||||
# This softare is released under the BSD License. See the COPYING file for
|
||||
# more information.
|
||||
|
||||
cd tests/sentinel
|
||||
source ../instances.tcl
|
||||
|
||||
set ::instances_count 5 ; # How many instances we use at max.
|
||||
|
||||
proc main {} {
|
||||
parse_options
|
||||
spawn_instance sentinel $::sentinel_base_port $::instances_count
|
||||
spawn_instance redis $::redis_base_port $::instances_count
|
||||
run_tests
|
||||
cleanup
|
||||
}
|
||||
|
||||
if {[catch main e]} {
|
||||
puts $::errorInfo
|
||||
cleanup
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
redis_*
|
||||
sentinel_*
|
||||
@@ -1,303 +0,0 @@
|
||||
# Tcl redis cluster client as a wrapper of redis.rb.
|
||||
# Copyright (C) 2014 Salvatore Sanfilippo
|
||||
# Released under the BSD license like Redis itself
|
||||
#
|
||||
# Example usage:
|
||||
#
|
||||
# set c [redis_cluster 127.0.0.1 6379 127.0.0.1 6380]
|
||||
# $c set foo
|
||||
# $c get foo
|
||||
# $c close
|
||||
|
||||
package require Tcl 8.5
|
||||
package provide redis_cluster 0.1
|
||||
|
||||
namespace eval redis_cluster {}
|
||||
set ::redis_cluster::id 0
|
||||
array set ::redis_cluster::startup_nodes {}
|
||||
array set ::redis_cluster::nodes {}
|
||||
array set ::redis_cluster::slots {}
|
||||
|
||||
# List of "plain" commands, which are commands where the sole key is always
|
||||
# the first argument.
|
||||
set ::redis_cluster::plain_commands {
|
||||
get set setnx setex psetex append strlen exists setbit getbit
|
||||
setrange getrange substr incr decr rpush lpush rpushx lpushx
|
||||
linsert rpop lpop brpop llen lindex lset lrange ltrim lrem
|
||||
sadd srem sismember scard spop srandmember smembers sscan zadd
|
||||
zincrby zrem zremrangebyscore zremrangebyrank zremrangebylex zrange
|
||||
zrangebyscore zrevrangebyscore zrangebylex zrevrangebylex zcount
|
||||
zlexcount zrevrange zcard zscore zrank zrevrank zscan hset hsetnx
|
||||
hget hmset hmget hincrby hincrbyfloat hdel hlen hkeys hvals
|
||||
hgetall hexists hscan incrby decrby incrbyfloat getset move
|
||||
expire expireat pexpire pexpireat type ttl pttl persist restore
|
||||
dump bitcount bitpos pfadd pfcount
|
||||
}
|
||||
|
||||
proc redis_cluster {nodes} {
|
||||
set id [incr ::redis_cluster::id]
|
||||
set ::redis_cluster::startup_nodes($id) $nodes
|
||||
set ::redis_cluster::nodes($id) {}
|
||||
set ::redis_cluster::slots($id) {}
|
||||
set handle [interp alias {} ::redis_cluster::instance$id {} ::redis_cluster::__dispatch__ $id]
|
||||
$handle refresh_nodes_map
|
||||
return $handle
|
||||
}
|
||||
|
||||
# Totally reset the slots / nodes state for the client, calls
|
||||
# CLUSTER NODES in the first startup node available, populates the
|
||||
# list of nodes ::redis_cluster::nodes($id) with an hash mapping node
|
||||
# ip:port to a representation of the node (another hash), and finally
|
||||
# maps ::redis_cluster::slots($id) with an hash mapping slot numbers
|
||||
# to node IDs.
|
||||
#
|
||||
# This function is called when a new Redis Cluster client is initialized
|
||||
# and every time we get a -MOVED redirection error.
|
||||
proc ::redis_cluster::__method__refresh_nodes_map {id} {
|
||||
# Contact the first responding startup node.
|
||||
set idx 0; # Index of the node that will respond.
|
||||
set errmsg {}
|
||||
foreach start_node $::redis_cluster::startup_nodes($id) {
|
||||
lassign [split $start_node :] start_host start_port
|
||||
if {[catch {
|
||||
set r {}
|
||||
set r [redis $start_host $start_port]
|
||||
set nodes_descr [$r cluster nodes]
|
||||
$r close
|
||||
} e]} {
|
||||
if {$r ne {}} {catch {$r close}}
|
||||
incr idx
|
||||
if {[string length $errmsg] < 200} {
|
||||
append errmsg " $start_node: $e"
|
||||
}
|
||||
continue ; # Try next.
|
||||
} else {
|
||||
break; # Good node found.
|
||||
}
|
||||
}
|
||||
|
||||
if {$idx == [llength $::redis_cluster::startup_nodes($id)]} {
|
||||
error "No good startup node found. $errmsg"
|
||||
}
|
||||
|
||||
# Put the node that responded as first in the list if it is not
|
||||
# already the first.
|
||||
if {$idx != 0} {
|
||||
set l $::redis_cluster::startup_nodes($id)
|
||||
set left [lrange $l 0 [expr {$idx-1}]]
|
||||
set right [lrange $l [expr {$idx+1}] end]
|
||||
set l [concat [lindex $l $idx] $left $right]
|
||||
set ::redis_cluster::startup_nodes($id) $l
|
||||
}
|
||||
|
||||
# Parse CLUSTER NODES output to populate the nodes description.
|
||||
set nodes {} ; # addr -> node description hash.
|
||||
foreach line [split $nodes_descr "\n"] {
|
||||
set line [string trim $line]
|
||||
if {$line eq {}} continue
|
||||
set args [split $line " "]
|
||||
lassign $args nodeid addr flags slaveof pingsent pongrecv configepoch linkstate
|
||||
set slots [lrange $args 8 end]
|
||||
if {$addr eq {:0}} {
|
||||
set addr $start_host:$start_port
|
||||
}
|
||||
lassign [split $addr :] host port
|
||||
|
||||
# Connect to the node
|
||||
set link {}
|
||||
catch {set link [redis $host $port]}
|
||||
|
||||
# Build this node description as an hash.
|
||||
set node [dict create \
|
||||
id $nodeid \
|
||||
addr $addr \
|
||||
host $host \
|
||||
port $port \
|
||||
flags $flags \
|
||||
slaveof $slaveof \
|
||||
slots $slots \
|
||||
link $link \
|
||||
]
|
||||
dict set nodes $addr $node
|
||||
lappend ::redis_cluster::startup_nodes($id) $addr
|
||||
}
|
||||
|
||||
# Close all the existing links in the old nodes map, and set the new
|
||||
# map as current.
|
||||
foreach n $::redis_cluster::nodes($id) {
|
||||
catch {
|
||||
[dict get $n link] close
|
||||
}
|
||||
}
|
||||
set ::redis_cluster::nodes($id) $nodes
|
||||
|
||||
# Populates the slots -> nodes map.
|
||||
dict for {addr node} $nodes {
|
||||
foreach slotrange [dict get $node slots] {
|
||||
lassign [split $slotrange -] start end
|
||||
if {$end == {}} {set end $start}
|
||||
for {set j $start} {$j <= $end} {incr j} {
|
||||
dict set ::redis_cluster::slots($id) $j $addr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Only retain unique entries in the startup nodes list
|
||||
set ::redis_cluster::startup_nodes($id) [lsort -unique $::redis_cluster::startup_nodes($id)]
|
||||
}
|
||||
|
||||
# Free a redis_cluster handle.
|
||||
proc ::redis_cluster::__method__close {id} {
|
||||
catch {
|
||||
set nodes $::redis_cluster::nodes($id)
|
||||
dict for {addr node} $nodes {
|
||||
catch {
|
||||
[dict get $node link] close
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {unset ::redis_cluster::startup_nodes($id)}
|
||||
catch {unset ::redis_cluster::nodes($id)}
|
||||
catch {unset ::redis_cluster::slots($id)}
|
||||
catch {interp alias {} ::redis_cluster::instance$id {}}
|
||||
}
|
||||
|
||||
proc ::redis_cluster::__dispatch__ {id method args} {
|
||||
if {[info command ::redis_cluster::__method__$method] eq {}} {
|
||||
# Get the keys from the command.
|
||||
set keys [::redis_cluster::get_keys_from_command $method $args]
|
||||
if {$keys eq {}} {
|
||||
error "Redis command '$method' is not supported by redis_cluster."
|
||||
}
|
||||
|
||||
# Resolve the keys in the corresponding hash slot they hash to.
|
||||
set slot [::redis_cluster::get_slot_from_keys $keys]
|
||||
if {$slot eq {}} {
|
||||
error "Invalid command: multiple keys not hashing to the same slot."
|
||||
}
|
||||
|
||||
# Get the node mapped to this slot.
|
||||
set node_addr [dict get $::redis_cluster::slots($id) $slot]
|
||||
if {$node_addr eq {}} {
|
||||
error "No mapped node for slot $slot."
|
||||
}
|
||||
|
||||
# Execute the command in the node we think is the slot owner.
|
||||
set retry 100
|
||||
while {[incr retry -1]} {
|
||||
if {$retry < 5} {after 100}
|
||||
set node [dict get $::redis_cluster::nodes($id) $node_addr]
|
||||
set link [dict get $node link]
|
||||
if {[catch {$link $method {*}$args} e]} {
|
||||
if {$link eq {} || \
|
||||
[string range $e 0 4] eq {MOVED} || \
|
||||
[string range $e 0 2] eq {I/O} \
|
||||
} {
|
||||
# MOVED redirection.
|
||||
::redis_cluster::__method__refresh_nodes_map $id
|
||||
set node_addr [dict get $::redis_cluster::slots($id) $slot]
|
||||
continue
|
||||
} elseif {[string range $e 0 2] eq {ASK}} {
|
||||
# ASK redirection.
|
||||
set node_addr [lindex $e 2]
|
||||
continue
|
||||
} else {
|
||||
# Non redirecting error.
|
||||
error $e $::errorInfo $::errorCode
|
||||
}
|
||||
} else {
|
||||
# OK query went fine
|
||||
return $e
|
||||
}
|
||||
}
|
||||
error "Too many redirections or failures contacting Redis Cluster."
|
||||
} else {
|
||||
uplevel 1 [list ::redis_cluster::__method__$method $id] $args
|
||||
}
|
||||
}
|
||||
|
||||
proc ::redis_cluster::get_keys_from_command {cmd argv} {
|
||||
set cmd [string tolower $cmd]
|
||||
# Most Redis commands get just one key as first argument.
|
||||
if {[lsearch -exact $::redis_cluster::plain_commands $cmd] != -1} {
|
||||
return [list [lindex $argv 0]]
|
||||
}
|
||||
|
||||
# Special handling for other commands
|
||||
switch -exact $cmd {
|
||||
mget {return $argv}
|
||||
}
|
||||
|
||||
# All the remaining commands are not handled.
|
||||
return {}
|
||||
}
|
||||
|
||||
# Returns the CRC16 of the specified string.
|
||||
# The CRC parameters are described in the Redis Cluster specification.
|
||||
set ::redis_cluster::XMODEMCRC16Lookup {
|
||||
0x0000 0x1021 0x2042 0x3063 0x4084 0x50a5 0x60c6 0x70e7
|
||||
0x8108 0x9129 0xa14a 0xb16b 0xc18c 0xd1ad 0xe1ce 0xf1ef
|
||||
0x1231 0x0210 0x3273 0x2252 0x52b5 0x4294 0x72f7 0x62d6
|
||||
0x9339 0x8318 0xb37b 0xa35a 0xd3bd 0xc39c 0xf3ff 0xe3de
|
||||
0x2462 0x3443 0x0420 0x1401 0x64e6 0x74c7 0x44a4 0x5485
|
||||
0xa56a 0xb54b 0x8528 0x9509 0xe5ee 0xf5cf 0xc5ac 0xd58d
|
||||
0x3653 0x2672 0x1611 0x0630 0x76d7 0x66f6 0x5695 0x46b4
|
||||
0xb75b 0xa77a 0x9719 0x8738 0xf7df 0xe7fe 0xd79d 0xc7bc
|
||||
0x48c4 0x58e5 0x6886 0x78a7 0x0840 0x1861 0x2802 0x3823
|
||||
0xc9cc 0xd9ed 0xe98e 0xf9af 0x8948 0x9969 0xa90a 0xb92b
|
||||
0x5af5 0x4ad4 0x7ab7 0x6a96 0x1a71 0x0a50 0x3a33 0x2a12
|
||||
0xdbfd 0xcbdc 0xfbbf 0xeb9e 0x9b79 0x8b58 0xbb3b 0xab1a
|
||||
0x6ca6 0x7c87 0x4ce4 0x5cc5 0x2c22 0x3c03 0x0c60 0x1c41
|
||||
0xedae 0xfd8f 0xcdec 0xddcd 0xad2a 0xbd0b 0x8d68 0x9d49
|
||||
0x7e97 0x6eb6 0x5ed5 0x4ef4 0x3e13 0x2e32 0x1e51 0x0e70
|
||||
0xff9f 0xefbe 0xdfdd 0xcffc 0xbf1b 0xaf3a 0x9f59 0x8f78
|
||||
0x9188 0x81a9 0xb1ca 0xa1eb 0xd10c 0xc12d 0xf14e 0xe16f
|
||||
0x1080 0x00a1 0x30c2 0x20e3 0x5004 0x4025 0x7046 0x6067
|
||||
0x83b9 0x9398 0xa3fb 0xb3da 0xc33d 0xd31c 0xe37f 0xf35e
|
||||
0x02b1 0x1290 0x22f3 0x32d2 0x4235 0x5214 0x6277 0x7256
|
||||
0xb5ea 0xa5cb 0x95a8 0x8589 0xf56e 0xe54f 0xd52c 0xc50d
|
||||
0x34e2 0x24c3 0x14a0 0x0481 0x7466 0x6447 0x5424 0x4405
|
||||
0xa7db 0xb7fa 0x8799 0x97b8 0xe75f 0xf77e 0xc71d 0xd73c
|
||||
0x26d3 0x36f2 0x0691 0x16b0 0x6657 0x7676 0x4615 0x5634
|
||||
0xd94c 0xc96d 0xf90e 0xe92f 0x99c8 0x89e9 0xb98a 0xa9ab
|
||||
0x5844 0x4865 0x7806 0x6827 0x18c0 0x08e1 0x3882 0x28a3
|
||||
0xcb7d 0xdb5c 0xeb3f 0xfb1e 0x8bf9 0x9bd8 0xabbb 0xbb9a
|
||||
0x4a75 0x5a54 0x6a37 0x7a16 0x0af1 0x1ad0 0x2ab3 0x3a92
|
||||
0xfd2e 0xed0f 0xdd6c 0xcd4d 0xbdaa 0xad8b 0x9de8 0x8dc9
|
||||
0x7c26 0x6c07 0x5c64 0x4c45 0x3ca2 0x2c83 0x1ce0 0x0cc1
|
||||
0xef1f 0xff3e 0xcf5d 0xdf7c 0xaf9b 0xbfba 0x8fd9 0x9ff8
|
||||
0x6e17 0x7e36 0x4e55 0x5e74 0x2e93 0x3eb2 0x0ed1 0x1ef0
|
||||
}
|
||||
|
||||
proc ::redis_cluster::crc16 {s} {
|
||||
set s [encoding convertto ascii $s]
|
||||
set crc 0
|
||||
foreach char [split $s {}] {
|
||||
scan $char %c byte
|
||||
set crc [expr {(($crc<<8)&0xffff) ^ [lindex $::redis_cluster::XMODEMCRC16Lookup [expr {(($crc>>8)^$byte) & 0xff}]]}]
|
||||
}
|
||||
return $crc
|
||||
}
|
||||
|
||||
# Hash a single key returning the slot it belongs to, Implemented hash
|
||||
# tags as described in the Redis Cluster specification.
|
||||
proc ::redis_cluster::hash {key} {
|
||||
# TODO: Handle hash slots.
|
||||
expr {[::redis_cluster::crc16 $key] & 16383}
|
||||
}
|
||||
|
||||
# Return the slot the specified keys hash to.
|
||||
# If the keys hash to multiple slots, an empty string is returned to
|
||||
# signal that the command can't be run in Redis Cluster.
|
||||
proc ::redis_cluster::get_slot_from_keys {keys} {
|
||||
set slot {}
|
||||
foreach k $keys {
|
||||
set s [::redis_cluster::hash $k]
|
||||
if {$slot eq {}} {
|
||||
set slot $s
|
||||
} elseif {$slot != $s} {
|
||||
return {} ; # Error
|
||||
}
|
||||
}
|
||||
return $slot
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Tcl client library - used by the Redis test
|
||||
# Copyright (C) 2009-2014 Salvatore Sanfilippo
|
||||
# Tcl clinet library - used by test-redis.tcl script for now
|
||||
# Copyright (C) 2009 Salvatore Sanfilippo
|
||||
# Released under the BSD license like Redis itself
|
||||
#
|
||||
# Example usage:
|
||||
@@ -170,10 +170,7 @@ proc ::redis::redis_read_reply fd {
|
||||
- {return -code error [redis_read_line $fd]}
|
||||
$ {redis_bulk_read $fd}
|
||||
* {redis_multi_bulk_read $fd}
|
||||
default {
|
||||
if {$type eq {}} {return -code error "I/O error reading reply"}
|
||||
return -code error "Bad protocol, '$type' as reply type byte"
|
||||
}
|
||||
default {return -code error "Bad protocol, '$type' as reply type byte"}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,7 @@ proc kill_server config {
|
||||
test "Check for memory leaks (pid $pid)" {
|
||||
set output {0 leaks}
|
||||
catch {exec leaks $pid} output
|
||||
if {[string match {*process does not exist*} $output] ||
|
||||
[string match {*cannot examine*} $output]} {
|
||||
if {[string match {*process does not exist*} $output]} {
|
||||
# In a few tests we kill the server process.
|
||||
set output "0 leaks"
|
||||
}
|
||||
@@ -236,7 +235,7 @@ proc start_server {options {code undefined}} {
|
||||
|
||||
# find out the pid
|
||||
while {![info exists pid]} {
|
||||
regexp {PID:\s(\d+)} [exec cat $stdout] _ pid
|
||||
regexp {\[(\d+)\]} [exec cat $stdout] _ pid
|
||||
after 100
|
||||
}
|
||||
|
||||
|
||||
@@ -261,14 +261,6 @@ start_server {tags {"basic"}} {
|
||||
assert_equal 20 [r get x]
|
||||
}
|
||||
|
||||
test "DEL against expired key" {
|
||||
r debug set-active-expire 0
|
||||
r setex keyExpire 1 valExpire
|
||||
after 1100
|
||||
assert_equal 0 [r del keyExpire]
|
||||
r debug set-active-expire 1
|
||||
}
|
||||
|
||||
test {EXISTS} {
|
||||
set res {}
|
||||
r set newkey test
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ start_server {tags {"dump"}} {
|
||||
set e {}
|
||||
catch {r restore foo 0 "..."} e
|
||||
set e
|
||||
} {*BUSYKEY*}
|
||||
} {*is busy*}
|
||||
|
||||
test {RESTORE can overwrite an existing key with REPLACE} {
|
||||
r set foo bar1
|
||||
|
||||
+2
-104
@@ -39,82 +39,6 @@ start_server {tags {"hll"}} {
|
||||
set res
|
||||
} {5 10}
|
||||
|
||||
test {HyperLogLogs are promote from sparse to dense} {
|
||||
r del hll
|
||||
r config set hll-sparse-max-bytes 3000
|
||||
set n 0
|
||||
while {$n < 100000} {
|
||||
set elements {}
|
||||
for {set j 0} {$j < 100} {incr j} {lappend elements [expr rand()]}
|
||||
incr n 100
|
||||
r pfadd hll {*}$elements
|
||||
set card [r pfcount hll]
|
||||
set err [expr {abs($card-$n)}]
|
||||
assert {$err < (double($card)/100)*5}
|
||||
if {$n < 1000} {
|
||||
assert {[r pfdebug encoding hll] eq {sparse}}
|
||||
} elseif {$n > 10000} {
|
||||
assert {[r pfdebug encoding hll] eq {dense}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {HyperLogLog sparse encoding stress test} {
|
||||
for {set x 0} {$x < 1000} {incr x} {
|
||||
r del hll1 hll2
|
||||
set numele [randomInt 100]
|
||||
set elements {}
|
||||
for {set j 0} {$j < $numele} {incr j} {
|
||||
lappend elements [expr rand()]
|
||||
}
|
||||
# Force dense representation of hll2
|
||||
r pfadd hll2
|
||||
r pfdebug todense hll2
|
||||
r pfadd hll1 {*}$elements
|
||||
r pfadd hll2 {*}$elements
|
||||
assert {[r pfdebug encoding hll1] eq {sparse}}
|
||||
assert {[r pfdebug encoding hll2] eq {dense}}
|
||||
# Cardinality estimated should match exactly.
|
||||
assert {[r pfcount hll1] eq [r pfcount hll2]}
|
||||
}
|
||||
}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs are detected: Additionl at tail} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r append hll "hello"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*INVALIDOBJ*}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs are detected: Broken magic} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r setrange hll 0 "0123"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs are detected: Invalid encoding} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r setrange hll 4 "x"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Corrupted dense HyperLogLogs are detected: Wrong length} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r setrange hll 4 "\x00"
|
||||
set e {}
|
||||
catch {r pfcount hll} e
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {PFADD, PFCOUNT, PFMERGE type checking works} {
|
||||
r set foo bar
|
||||
catch {r pfadd foo 1} e
|
||||
@@ -136,35 +60,9 @@ start_server {tags {"hll"}} {
|
||||
r pfcount hll
|
||||
} {5}
|
||||
|
||||
test {PFCOUNT multiple-keys merge returns cardinality of union} {
|
||||
r del hll1 hll2 hll3
|
||||
for {set x 1} {$x < 10000} {incr x} {
|
||||
# Force dense representation of hll2
|
||||
r pfadd hll1 "foo-$x"
|
||||
r pfadd hll2 "bar-$x"
|
||||
r pfadd hll3 "zap-$x"
|
||||
|
||||
set card [r pfcount hll1 hll2 hll3]
|
||||
set realcard [expr {$x*3}]
|
||||
set err [expr {abs($card-$realcard)}]
|
||||
assert {$err < (double($card)/100)*5}
|
||||
}
|
||||
}
|
||||
|
||||
test {PFDEBUG GETREG returns the HyperLogLog raw registers} {
|
||||
test {PFGETREG returns the HyperLogLog raw registers} {
|
||||
r del hll
|
||||
r pfadd hll 1 2 3
|
||||
llength [r pfdebug getreg hll]
|
||||
llength [r pfgetreg hll]
|
||||
} {16384}
|
||||
|
||||
test {PFADD / PFCOUNT cache invalidation works} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
r pfcount hll
|
||||
assert {[r getrange hll 15 15] eq "\x00"}
|
||||
r pfadd hll a b c
|
||||
assert {[r getrange hll 15 15] eq "\x00"}
|
||||
r pfadd hll 1 2 3
|
||||
assert {[r getrange hll 15 15] eq "\x80"}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-62
@@ -40,15 +40,15 @@ start_server {tags {"scripting"}} {
|
||||
|
||||
test {EVAL - is Lua able to call Redis API?} {
|
||||
r set mykey myval
|
||||
r eval {return redis.call('get',KEYS[1])} 1 mykey
|
||||
r eval {return redis.call('get','mykey')} 0
|
||||
} {myval}
|
||||
|
||||
test {EVALSHA - Can we call a SHA1 if already defined?} {
|
||||
r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey
|
||||
r evalsha 9bd632c7d33e571e9f24556ebed26c3479a87129 0
|
||||
} {myval}
|
||||
|
||||
test {EVALSHA - Can we call a SHA1 in uppercase?} {
|
||||
r evalsha FD758D1589D044DD850A6F05D52F2EEFD27F033F 1 mykey
|
||||
r evalsha 9BD632C7D33E571E9F24556EBED26C3479A87129 0
|
||||
} {myval}
|
||||
|
||||
test {EVALSHA - Do we get an error on invalid SHA1?} {
|
||||
@@ -175,18 +175,18 @@ start_server {tags {"scripting"}} {
|
||||
set e {}
|
||||
r set foo bar
|
||||
catch {
|
||||
r eval {redis.call('lpush',KEYS[1],'val')} 1 foo
|
||||
r eval "redis.call('lpush','foo','val')" 0
|
||||
} e
|
||||
set e
|
||||
} {*against a key*}
|
||||
|
||||
test {SCRIPTING FLUSH - is able to clear the scripts cache?} {
|
||||
r set mykey myval
|
||||
set v [r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey]
|
||||
set v [r evalsha 9bd632c7d33e571e9f24556ebed26c3479a87129 0]
|
||||
assert_equal $v myval
|
||||
set e ""
|
||||
r script flush
|
||||
catch {r evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 mykey} e
|
||||
catch {r evalsha 9bd632c7d33e571e9f24556ebed26c3479a87129 0} e
|
||||
set e
|
||||
} {NOSCRIPT*}
|
||||
|
||||
@@ -204,25 +204,25 @@ start_server {tags {"scripting"}} {
|
||||
test "In the context of Lua the output of random commands gets ordered" {
|
||||
r del myset
|
||||
r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
|
||||
r eval {return redis.call('smembers',KEYS[1])} 1 myset
|
||||
r eval {return redis.call('smembers','myset')} 0
|
||||
} {a aa aaa azz b c d e f g h i l m n o p q r s t u v z}
|
||||
|
||||
test "SORT is normally not alpha re-ordered for the scripting engine" {
|
||||
r del myset
|
||||
r sadd myset 1 2 3 4 10
|
||||
r eval {return redis.call('sort',KEYS[1],'desc')} 1 myset
|
||||
r eval {return redis.call('sort','myset','desc')} 0
|
||||
} {10 4 3 2 1}
|
||||
|
||||
test "SORT BY <constant> output gets ordered for scripting" {
|
||||
r del myset
|
||||
r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
|
||||
r eval {return redis.call('sort',KEYS[1],'by','_')} 1 myset
|
||||
r eval {return redis.call('sort','myset','by','_')} 0
|
||||
} {a aa aaa azz b c d e f g h i l m n o p q r s t u v z}
|
||||
|
||||
test "SORT BY <constant> with GET gets ordered for scripting" {
|
||||
r del myset
|
||||
r sadd myset a b c
|
||||
r eval {return redis.call('sort',KEYS[1],'by','_','get','#','get','_:*')} 1 myset
|
||||
r eval {return redis.call('sort','myset','by','_','get','#','get','_:*')} 0
|
||||
} {a {} b {} c {}}
|
||||
|
||||
test "redis.sha1hex() implementation" {
|
||||
@@ -300,9 +300,9 @@ start_server {tags {"scripting"}} {
|
||||
test {EVAL processes writes from AOF in read-only slaves} {
|
||||
r flushall
|
||||
r config set appendonly yes
|
||||
r eval {redis.call("set",KEYS[1],"100")} 1 foo
|
||||
r eval {redis.call("incr",KEYS[1])} 1 foo
|
||||
r eval {redis.call("incr",KEYS[1])} 1 foo
|
||||
r eval {redis.call("set","foo","100")} 0
|
||||
r eval {redis.call("incr","foo")} 0
|
||||
r eval {redis.call("incr","foo")} 0
|
||||
wait_for_condition 50 100 {
|
||||
[s aof_rewrite_in_progress] == 0
|
||||
} else {
|
||||
@@ -311,42 +311,8 @@ start_server {tags {"scripting"}} {
|
||||
r config set slave-read-only yes
|
||||
r slaveof 127.0.0.1 0
|
||||
r debug loadaof
|
||||
set res [r get foo]
|
||||
r slaveof no one
|
||||
set res
|
||||
r get foo
|
||||
} {102}
|
||||
|
||||
test {We can call scripts rewriting client->argv from Lua} {
|
||||
r del myset
|
||||
r sadd myset a b c
|
||||
r mset a 1 b 2 c 3 d 4
|
||||
assert {[r spop myset] ne {}}
|
||||
assert {[r spop myset] ne {}}
|
||||
assert {[r spop myset] ne {}}
|
||||
assert {[r mget a b c d] eq {1 2 3 4}}
|
||||
assert {[r spop myset] eq {}}
|
||||
}
|
||||
|
||||
test {Call Redis command with many args from Lua (issue #1764)} {
|
||||
r eval {
|
||||
local i
|
||||
local x={}
|
||||
redis.call('del','mylist')
|
||||
for i=1,100 do
|
||||
table.insert(x,i)
|
||||
end
|
||||
redis.call('rpush','mylist',unpack(x))
|
||||
return redis.call('lrange','mylist',0,-1)
|
||||
} 0
|
||||
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100}
|
||||
|
||||
test {Number conversion precision test (issue #1118)} {
|
||||
r eval {
|
||||
local value = 9007199254740991
|
||||
redis.call("set","foo",value)
|
||||
return redis.call("get","foo")
|
||||
} 0
|
||||
} {9007199254740991}
|
||||
}
|
||||
|
||||
# Start a new server since the last test in this stanza will kill the
|
||||
@@ -360,7 +326,6 @@ start_server {tags {"scripting"}} {
|
||||
catch {r ping} e
|
||||
assert_match {BUSY*} $e
|
||||
r script kill
|
||||
after 200 ; # Give some time to Lua to call the hook again...
|
||||
assert_equal [r ping] "PONG"
|
||||
}
|
||||
|
||||
@@ -373,7 +338,7 @@ start_server {tags {"scripting"}} {
|
||||
test {Timedout scripts that modified data can't be killed by SCRIPT KILL} {
|
||||
set rd [redis_deferring_client]
|
||||
r config set lua-time-limit 10
|
||||
$rd eval {redis.call('set',KEYS[1],'y'); while true do end} 1 x
|
||||
$rd eval {redis.call('set','x','y'); while true do end} 0
|
||||
after 200
|
||||
catch {r ping} e
|
||||
assert_match {BUSY*} $e
|
||||
@@ -400,13 +365,13 @@ start_server {tags {"scripting repl"}} {
|
||||
start_server {} {
|
||||
test {Before the slave connects we issue two EVAL commands} {
|
||||
# One with an error, but still executing a command.
|
||||
# SHA is: 67164fc43fa971f76fd1aaeeaf60c1c178d25876
|
||||
# SHA is: 6e8bd6bdccbe78899e3cc06b31b6dbf4324c2e56
|
||||
catch {
|
||||
r eval {redis.call('incr',KEYS[1]); redis.call('nonexisting')} 1 x
|
||||
r eval {redis.call('incr','x'); redis.call('nonexisting')} 0
|
||||
}
|
||||
# One command is correct:
|
||||
# SHA is: 6f5ade10a69975e903c6d07b10ea44c6382381a5
|
||||
r eval {return redis.call('incr',KEYS[1])} 1 x
|
||||
# SHA is: ae3477e27be955de7e1bc9adfdca626b478d3cb2
|
||||
r eval {return redis.call('incr','x')} 0
|
||||
} {2}
|
||||
|
||||
test {Connect a slave to the main instance} {
|
||||
@@ -423,9 +388,9 @@ start_server {tags {"scripting repl"}} {
|
||||
# The server should replicate successful and unsuccessful
|
||||
# commands as EVAL instead of EVALSHA.
|
||||
catch {
|
||||
r evalsha 67164fc43fa971f76fd1aaeeaf60c1c178d25876 1 x
|
||||
r evalsha 6e8bd6bdccbe78899e3cc06b31b6dbf4324c2e56 0
|
||||
}
|
||||
r evalsha 6f5ade10a69975e903c6d07b10ea44c6382381a5 1 x
|
||||
r evalsha ae3477e27be955de7e1bc9adfdca626b478d3cb2 0
|
||||
} {4}
|
||||
|
||||
test {If EVALSHA was replicated as EVAL, 'x' should be '4'} {
|
||||
@@ -440,9 +405,9 @@ start_server {tags {"scripting repl"}} {
|
||||
set rd [redis_deferring_client]
|
||||
$rd brpop a 0
|
||||
r eval {
|
||||
redis.call("lpush",KEYS[1],"1");
|
||||
redis.call("lpush",KEYS[1],"2");
|
||||
} 1 a
|
||||
redis.call("lpush","a","1");
|
||||
redis.call("lpush","a","2");
|
||||
} 0
|
||||
set res [$rd read]
|
||||
$rd close
|
||||
wait_for_condition 50 100 {
|
||||
@@ -455,9 +420,9 @@ start_server {tags {"scripting repl"}} {
|
||||
|
||||
test {EVALSHA replication when first call is readonly} {
|
||||
r del x
|
||||
r eval {if tonumber(ARGV[1]) > 0 then redis.call('incr', KEYS[1]) end} 1 x 0
|
||||
r evalsha 6e0e2745aa546d0b50b801a20983b70710aef3ce 1 x 0
|
||||
r evalsha 6e0e2745aa546d0b50b801a20983b70710aef3ce 1 x 1
|
||||
r eval {if tonumber(KEYS[1]) > 0 then redis.call('incr', 'x') end} 1 0
|
||||
r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 0
|
||||
r evalsha 38fe3ddf5284a1d48f37f824b4c4e826879f3cb9 1 1
|
||||
wait_for_condition 50 100 {
|
||||
[r -1 get x] eq {1}
|
||||
} else {
|
||||
|
||||
+3
-3
@@ -154,9 +154,9 @@ start_server {
|
||||
r zadd zset 10 d
|
||||
r zadd zset 3 e
|
||||
r eval {
|
||||
return {redis.call('sort',KEYS[1],'by','nosort','asc'),
|
||||
redis.call('sort',KEYS[1],'by','nosort','desc')}
|
||||
} 1 zset
|
||||
return {redis.call('sort','zset','by','nosort','asc'),
|
||||
redis.call('sort','zset','by','nosort','desc')}
|
||||
} 0
|
||||
} {{a c e b d} {d b e c a}}
|
||||
|
||||
test "SORT sorted set: +inf and -inf handling" {
|
||||
|
||||
@@ -296,62 +296,6 @@ start_server {tags {"zset"}} {
|
||||
assert_error "*not*float*" {r zrangebyscore fooz 1 NaN}
|
||||
}
|
||||
|
||||
proc create_default_lex_zset {} {
|
||||
create_zset zset {0 alpha 0 bar 0 cool 0 down
|
||||
0 elephant 0 foo 0 great 0 hill
|
||||
0 omega}
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZCOUNT basics" {
|
||||
create_default_lex_zset
|
||||
|
||||
# inclusive range
|
||||
assert_equal {alpha bar cool} [r zrangebylex zset - \[cool]
|
||||
assert_equal {bar cool down} [r zrangebylex zset \[bar \[down]
|
||||
assert_equal {great hill omega} [r zrangebylex zset \[g +]
|
||||
assert_equal {cool bar alpha} [r zrevrangebylex zset \[cool -]
|
||||
assert_equal {down cool bar} [r zrevrangebylex zset \[down \[bar]
|
||||
assert_equal {omega hill great foo elephant down} [r zrevrangebylex zset + \[d]
|
||||
assert_equal 3 [r zlexcount zset \[ele \[h]
|
||||
|
||||
# exclusive range
|
||||
assert_equal {alpha bar} [r zrangebylex zset - (cool]
|
||||
assert_equal {cool} [r zrangebylex zset (bar (down]
|
||||
assert_equal {hill omega} [r zrangebylex zset (great +]
|
||||
assert_equal {bar alpha} [r zrevrangebylex zset (cool -]
|
||||
assert_equal {cool} [r zrevrangebylex zset (down (bar]
|
||||
assert_equal {omega hill} [r zrevrangebylex zset + (great]
|
||||
assert_equal 2 [r zlexcount zset (ele (great]
|
||||
|
||||
# inclusive and exclusive
|
||||
assert_equal {} [r zrangebylex zset (az (b]
|
||||
assert_equal {} [r zrangebylex zset (z +]
|
||||
assert_equal {} [r zrangebylex zset - \[aaaa]
|
||||
assert_equal {} [r zrevrangebylex zset \[elez \[elex]
|
||||
assert_equal {} [r zrevrangebylex zset (hill (omega]
|
||||
}
|
||||
|
||||
test "ZRANGEBYSLEX with LIMIT" {
|
||||
create_default_lex_zset
|
||||
assert_equal {alpha bar} [r zrangebylex zset - \[cool LIMIT 0 2]
|
||||
assert_equal {bar cool} [r zrangebylex zset - \[cool LIMIT 1 2]
|
||||
assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 0 0]
|
||||
assert_equal {} [r zrangebylex zset \[bar \[down LIMIT 2 0]
|
||||
assert_equal {bar} [r zrangebylex zset \[bar \[down LIMIT 0 1]
|
||||
assert_equal {cool} [r zrangebylex zset \[bar \[down LIMIT 1 1]
|
||||
assert_equal {bar cool down} [r zrangebylex zset \[bar \[down LIMIT 0 100]
|
||||
assert_equal {omega hill great foo elephant} [r zrevrangebylex zset + \[d LIMIT 0 5]
|
||||
assert_equal {omega hill great foo} [r zrevrangebylex zset + \[d LIMIT 0 4]
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX with invalid lex range specifiers" {
|
||||
assert_error "*not*string*" {r zrangebylex fooz foo bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz \[foo bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz foo \[bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz +x \[bar}
|
||||
assert_error "*not*string*" {r zrangebylex fooz -x \[bar}
|
||||
}
|
||||
|
||||
test "ZREMRANGEBYSCORE basics" {
|
||||
proc remrangebyscore {min max} {
|
||||
create_zset zset {1 a 2 b 3 c 4 d 5 e}
|
||||
@@ -764,111 +708,6 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {} $err
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
|
||||
set lexset {}
|
||||
r del zset
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
set e [randstring 0 30 alpha]
|
||||
lappend lexset $e
|
||||
r zadd zset 0 $e
|
||||
}
|
||||
set lexset [lsort -unique $lexset]
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
set min [randstring 0 30 alpha]
|
||||
set max [randstring 0 30 alpha]
|
||||
set mininc [randomInt 2]
|
||||
set maxinc [randomInt 2]
|
||||
if {$mininc} {set cmin "\[$min"} else {set cmin "($min"}
|
||||
if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"}
|
||||
set rev [randomInt 2]
|
||||
if {$rev} {
|
||||
set cmd zrevrangebylex
|
||||
} else {
|
||||
set cmd zrangebylex
|
||||
}
|
||||
|
||||
# Make sure data is the same in both sides
|
||||
assert {[r zrange zset 0 -1] eq $lexset}
|
||||
|
||||
# Get the Redis output
|
||||
set output [r $cmd zset $cmin $cmax]
|
||||
if {$rev} {
|
||||
set outlen [r zlexcount zset $cmax $cmin]
|
||||
} else {
|
||||
set outlen [r zlexcount zset $cmin $cmax]
|
||||
}
|
||||
|
||||
# Compute the same output via Tcl
|
||||
set o {}
|
||||
set copy $lexset
|
||||
if {(!$rev && [string compare $min $max] > 0) ||
|
||||
($rev && [string compare $max $min] > 0)} {
|
||||
# Empty output when ranges are inverted.
|
||||
} else {
|
||||
if {$rev} {
|
||||
# Invert the Tcl array using Redis itself.
|
||||
set copy [r zrevrange zset 0 -1]
|
||||
# Invert min / max as well
|
||||
lassign [list $min $max $mininc $maxinc] \
|
||||
max min maxinc mininc
|
||||
}
|
||||
foreach e $copy {
|
||||
set mincmp [string compare $e $min]
|
||||
set maxcmp [string compare $e $max]
|
||||
if {
|
||||
($mininc && $mincmp >= 0 || !$mininc && $mincmp > 0)
|
||||
&&
|
||||
($maxinc && $maxcmp <= 0 || !$maxinc && $maxcmp < 0)
|
||||
} {
|
||||
lappend o $e
|
||||
}
|
||||
}
|
||||
}
|
||||
assert {$o eq $output}
|
||||
assert {$outlen eq [llength $output]}
|
||||
}
|
||||
}
|
||||
|
||||
test "ZREMRANGEBYLEX fuzzy test, 100 ranges in $elements element sorted set - $encoding" {
|
||||
set lexset {}
|
||||
r del zset zsetcopy
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
set e [randstring 0 30 alpha]
|
||||
lappend lexset $e
|
||||
r zadd zset 0 $e
|
||||
}
|
||||
set lexset [lsort -unique $lexset]
|
||||
for {set j 0} {$j < 100} {incr j} {
|
||||
# Copy...
|
||||
r zunionstore zsetcopy 1 zset
|
||||
set lexsetcopy $lexset
|
||||
|
||||
set min [randstring 0 30 alpha]
|
||||
set max [randstring 0 30 alpha]
|
||||
set mininc [randomInt 2]
|
||||
set maxinc [randomInt 2]
|
||||
if {$mininc} {set cmin "\[$min"} else {set cmin "($min"}
|
||||
if {$maxinc} {set cmax "\[$max"} else {set cmax "($max"}
|
||||
|
||||
# Make sure data is the same in both sides
|
||||
assert {[r zrange zset 0 -1] eq $lexset}
|
||||
|
||||
# Get the range we are going to remove
|
||||
set torem [r zrangebylex zset $cmin $cmax]
|
||||
set toremlen [r zlexcount zset $cmin $cmax]
|
||||
r zremrangebylex zsetcopy $cmin $cmax
|
||||
set output [r zrange zsetcopy 0 -1]
|
||||
|
||||
# Remove the range with Tcl from the original list
|
||||
if {$toremlen} {
|
||||
set first [lsearch -exact $lexsetcopy [lindex $torem 0]]
|
||||
set last [expr {$first+$toremlen-1}]
|
||||
set lexsetcopy [lreplace $lexsetcopy $first $last]
|
||||
}
|
||||
assert {$lexsetcopy eq $output}
|
||||
}
|
||||
}
|
||||
|
||||
test "ZSETs skiplist implementation backlink consistency test - $encoding" {
|
||||
set diff 0
|
||||
for {set j 0} {$j < $elements} {incr j} {
|
||||
|
||||
@@ -11,8 +11,7 @@ GROUPS = [
|
||||
"transactions",
|
||||
"connection",
|
||||
"server",
|
||||
"scripting",
|
||||
"hyperloglog"
|
||||
"scripting"
|
||||
].freeze
|
||||
|
||||
GROUPS_BY_NAME = Hash[*
|
||||
|
||||
Reference in New Issue
Block a user