Compare commits

..
11 Commits
Author SHA1 Message Date
antirez 33b5f22e31 Non-LZF aware object.c APIs fixed. 2014-04-04 17:09:52 +02:00
antirez 61671a0dbe GETRANGE fixed for LZF encoded objects. 2014-04-04 16:41:56 +02:00
antirez a4961a9a1d No hard limits to LZF compression max size. 2014-04-04 16:36:44 +02:00
antirez eed8495bf1 Make LZF in-memory compression actually configurable. 2014-04-04 16:10:00 +02:00
antirez 9acac9ceb0 Add server.mem_compression to enable/disable the feature. 2014-04-04 16:06:28 +02:00
antirez 1c894f5989 Make SLOWLOG argument truncation play well with LZF encoding. 2014-04-04 15:37:01 +02:00
antirez e2871e21fd More encoding checking macros in redis.h. 2014-04-04 15:31:09 +02:00
antirez 8046756f3b PFCOUNT: always unshare/decode the object.
This will be a non-op most of the times since the object will be
unshared / decoded, however it is more technically correct to start this
way since the object may be decoded even in the read-only code path.
2014-04-04 15:29:32 +02:00
antirez f159f8034a BITCOUNT/BITPOS fixed to work with LZF encoded objects. 2014-04-04 12:41:09 +02:00
antirez f7501fb1d3 Transparent LZF compression initial implementation.
This commit shapes the main ideas for the implementation but doesn't
fix all the command implementations, nor handles loading of LZF
compressed objects in a way able to perserve the compression.
2014-04-04 12:11:48 +02:00
antirez 2a7da8a736 tryObjectEncoding() refactoring.
We also avoid to re-create an object that is already in EMBSTR encoding.
2014-04-04 10:28:34 +02:00
60 changed files with 807 additions and 4269 deletions
+4 -59
View File
@@ -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
-14
View File
@@ -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
View File
@@ -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 $*
-13
View File
@@ -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
View File
@@ -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;
+1 -2
View File
@@ -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) {
+3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+4 -27
View File
@@ -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
View File
@@ -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);
}
-5
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+52 -89
View File
@@ -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
View File
@@ -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";
}
}
+43 -25
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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",
+27 -232
View File
@@ -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;
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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]);
+1
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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
-130
View File
@@ -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
}
-25
View File
@@ -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
}
-59
View File
@@ -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
}
-38
View File
@@ -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
}
-35
View File
@@ -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
}
+1 -16
View File
@@ -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]
}
}
-60
View File
@@ -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]
@@ -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 {
+28 -26
View File
@@ -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
}
-21
View File
@@ -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
}
-2
View File
@@ -1,2 +0,0 @@
redis_*
sentinel_*
-303
View File
@@ -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
}
+3 -6
View File
@@ -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"}
}
}
+2 -3
View File
@@ -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
}
-8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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" {
-161
View File
@@ -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} {
+1 -2
View File
@@ -11,8 +11,7 @@ GROUPS = [
"transactions",
"connection",
"server",
"scripting",
"hyperloglog"
"scripting"
].freeze
GROUPS_BY_NAME = Hash[*