Unverified Commit cbb2ac07 authored by chendianqiang's avatar chendianqiang Committed by GitHub
Browse files

Merge branch 'unstable' into pending-querybuf

parents 7de1ada0 2edcafb3
...@@ -112,7 +112,7 @@ void activeExpireCycle(int type) { ...@@ -112,7 +112,7 @@ void activeExpireCycle(int type) {
if (type == ACTIVE_EXPIRE_CYCLE_FAST) { if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
/* Don't start a fast cycle if the previous cycle did not exit /* Don't start a fast cycle if the previous cycle did not exit
* for time limt. Also don't repeat a fast cycle for the same period * for time limit. Also don't repeat a fast cycle for the same period
* as the fast cycle total duration itself. */ * as the fast cycle total duration itself. */
if (!timelimit_exit) return; if (!timelimit_exit) return;
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return; if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
......
...@@ -144,8 +144,8 @@ int geohashEncode(const GeoHashRange *long_range, const GeoHashRange *lat_range, ...@@ -144,8 +144,8 @@ int geohashEncode(const GeoHashRange *long_range, const GeoHashRange *lat_range,
(longitude - long_range->min) / (long_range->max - long_range->min); (longitude - long_range->min) / (long_range->max - long_range->min);
/* convert to fixed point based on the step size */ /* convert to fixed point based on the step size */
lat_offset *= (1 << step); lat_offset *= (1ULL << step);
long_offset *= (1 << step); long_offset *= (1ULL << step);
hash->bits = interleave64(lat_offset, long_offset); hash->bits = interleave64(lat_offset, long_offset);
return 1; return 1;
} }
......
/* Automatically generated by utils/generate-command-help.rb, do not edit. */ /* Automatically generated by generate-command-help.rb, do not edit. */
#ifndef __REDIS_HELP_H #ifndef __REDIS_HELP_H
#define __REDIS_HELP_H #define __REDIS_HELP_H
...@@ -17,7 +17,8 @@ static char *commandGroups[] = { ...@@ -17,7 +17,8 @@ static char *commandGroups[] = {
"scripting", "scripting",
"hyperloglog", "hyperloglog",
"cluster", "cluster",
"geo" "geo",
"stream"
}; };
struct commandHelp { struct commandHelp {
...@@ -82,6 +83,16 @@ struct commandHelp { ...@@ -82,6 +83,16 @@ struct commandHelp {
"Pop a value from a list, push it to another list and return it; or block until one is available", "Pop a value from a list, push it to another list and return it; or block until one is available",
2, 2,
"2.2.0" }, "2.2.0" },
{ "BZPOPMAX",
"key [key ...] timeout",
"Remove and return the member with the highest score from one or more sorted sets, or block until one is available",
4,
"5.0.0" },
{ "BZPOPMIN",
"key [key ...] timeout",
"Remove and return the member with the lowest score from one or more sorted sets, or block until one is available",
4,
"5.0.0" },
{ "CLIENT GETNAME", { "CLIENT GETNAME",
"-", "-",
"Get the current connection name", "Get the current connection name",
...@@ -318,12 +329,12 @@ struct commandHelp { ...@@ -318,12 +329,12 @@ struct commandHelp {
0, 0,
"1.2.0" }, "1.2.0" },
{ "FLUSHALL", { "FLUSHALL",
"-", "[ASYNC]",
"Remove all keys from all databases", "Remove all keys from all databases",
9, 9,
"1.0.0" }, "1.0.0" },
{ "FLUSHDB", { "FLUSHDB",
"-", "[ASYNC]",
"Remove all keys from the current database", "Remove all keys from the current database",
9, 9,
"1.0.0" }, "1.0.0" },
...@@ -532,6 +543,36 @@ struct commandHelp { ...@@ -532,6 +543,36 @@ struct commandHelp {
"Trim a list to the specified range", "Trim a list to the specified range",
2, 2,
"1.0.0" }, "1.0.0" },
{ "MEMORY DOCTOR",
"-",
"Outputs memory problems report",
9,
"4.0.0" },
{ "MEMORY HELP",
"-",
"Show helpful text about the different subcommands",
9,
"4.0.0" },
{ "MEMORY MALLOC-STATS",
"-",
"Show allocator internal stats",
9,
"4.0.0" },
{ "MEMORY PURGE",
"-",
"Ask the allocator to release memory",
9,
"4.0.0" },
{ "MEMORY STATS",
"-",
"Show memory usage details",
9,
"4.0.0" },
{ "MEMORY USAGE",
"key [SAMPLES count]",
"Estimate the memory usage of a key",
9,
"4.0.0" },
{ "MGET", { "MGET",
"key [key ...]", "key [key ...]",
"Get the values of all the given keys", "Get the values of all the given keys",
...@@ -723,7 +764,7 @@ struct commandHelp { ...@@ -723,7 +764,7 @@ struct commandHelp {
10, 10,
"3.2.0" }, "3.2.0" },
{ "SCRIPT EXISTS", { "SCRIPT EXISTS",
"script [script ...]", "sha1 [sha1 ...]",
"Check existence of scripts in the script cache.", "Check existence of scripts in the script cache.",
10, 10,
"2.6.0" }, "2.6.0" },
...@@ -758,7 +799,7 @@ struct commandHelp { ...@@ -758,7 +799,7 @@ struct commandHelp {
8, 8,
"1.0.0" }, "1.0.0" },
{ "SET", { "SET",
"key value [EX seconds] [PX milliseconds] [NX|XX]", "key value [expiration EX seconds|PX milliseconds] [NX|XX]",
"Set the string value of a key", "Set the string value of a key",
1, 1,
"1.0.0" }, "1.0.0" },
...@@ -867,6 +908,11 @@ struct commandHelp { ...@@ -867,6 +908,11 @@ struct commandHelp {
"Add multiple sets and store the resulting set in a key", "Add multiple sets and store the resulting set in a key",
3, 3,
"1.0.0" }, "1.0.0" },
{ "SWAPDB",
"index index",
"Swaps two Redis databases",
8,
"4.0.0" },
{ "SYNC", { "SYNC",
"-", "-",
"Internal command used for replication", "Internal command used for replication",
...@@ -877,6 +923,11 @@ struct commandHelp { ...@@ -877,6 +923,11 @@ struct commandHelp {
"Return the current server time", "Return the current server time",
9, 9,
"2.6.0" }, "2.6.0" },
{ "TOUCH",
"key [key ...]",
"Alters the last access time of a key(s). Returns the number of existing keys specified.",
0,
"3.2.1" },
{ "TTL", { "TTL",
"key", "key",
"Get the time to live for a key", "Get the time to live for a key",
...@@ -887,6 +938,11 @@ struct commandHelp { ...@@ -887,6 +938,11 @@ struct commandHelp {
"Determine the type stored at key", "Determine the type stored at key",
0, 0,
"1.0.0" }, "1.0.0" },
{ "UNLINK",
"key [key ...]",
"Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.",
0,
"4.0.0" },
{ "UNSUBSCRIBE", { "UNSUBSCRIBE",
"[channel [channel ...]]", "[channel [channel ...]]",
"Stop listening for messages posted to the given channels", "Stop listening for messages posted to the given channels",
...@@ -907,6 +963,41 @@ struct commandHelp { ...@@ -907,6 +963,41 @@ struct commandHelp {
"Watch the given keys to determine execution of the MULTI/EXEC block", "Watch the given keys to determine execution of the MULTI/EXEC block",
7, 7,
"2.2.0" }, "2.2.0" },
{ "XADD",
"key ID field string [field string ...]",
"Appends a new entry to a stream",
14,
"5.0.0" },
{ "XLEN",
"key",
"Return the number of entires in a stream",
14,
"5.0.0" },
{ "XPENDING",
"key group [start end count] [consumer]",
"Return information and entries from a stream consumer group pending entries list, that are messages fetched but never acknowledged.",
14,
"5.0.0" },
{ "XRANGE",
"key start end [COUNT count]",
"Return a range of elements in a stream, with IDs matching the specified IDs interval",
14,
"5.0.0" },
{ "XREAD",
"[COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
"Return never seen elements in multiple streams, with IDs greater than the ones reported by the caller for each stream. Can block.",
14,
"5.0.0" },
{ "XREADGROUP",
"GROUP group consumer [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
"Return new entries from a stream using a consumer group, or access the history of the pending entries for a given consumer. Can block.",
14,
"5.0.0" },
{ "XREVRANGE",
"key end start [COUNT count]",
"Return a range of elements in a stream, with IDs matching the specified IDs interval, in reverse order (from greater to smaller IDs) compared to XRANGE",
14,
"5.0.0" },
{ "ZADD", { "ZADD",
"key [NX|XX] [CH] [INCR] score member [score member ...]", "key [NX|XX] [CH] [INCR] score member [score member ...]",
"Add one or more members to a sorted set, or update its score if it already exists", "Add one or more members to a sorted set, or update its score if it already exists",
...@@ -937,6 +1028,16 @@ struct commandHelp { ...@@ -937,6 +1028,16 @@ struct commandHelp {
"Count the number of members in a sorted set between a given lexicographical range", "Count the number of members in a sorted set between a given lexicographical range",
4, 4,
"2.8.9" }, "2.8.9" },
{ "ZPOPMAX",
"key [count]",
"Remove and return members with the highest scores in a sorted set",
4,
"5.0.0" },
{ "ZPOPMIN",
"key [count]",
"Remove and return members with the lowest scores in a sorted set",
4,
"5.0.0" },
{ "ZRANGE", { "ZRANGE",
"key start stop [WITHSCORES]", "key start stop [WITHSCORES]",
"Return a range of members in a sorted set, by index", "Return a range of members in a sorted set, by index",
......
...@@ -429,14 +429,14 @@ uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) { ...@@ -429,14 +429,14 @@ uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) {
} }
switch(len & 7) { switch(len & 7) {
case 7: h ^= (uint64_t)data[6] << 48; case 7: h ^= (uint64_t)data[6] << 48; /* fall-thru */
case 6: h ^= (uint64_t)data[5] << 40; case 6: h ^= (uint64_t)data[5] << 40; /* fall-thru */
case 5: h ^= (uint64_t)data[4] << 32; case 5: h ^= (uint64_t)data[4] << 32; /* fall-thru */
case 4: h ^= (uint64_t)data[3] << 24; case 4: h ^= (uint64_t)data[3] << 24; /* fall-thru */
case 3: h ^= (uint64_t)data[2] << 16; case 3: h ^= (uint64_t)data[2] << 16; /* fall-thru */
case 2: h ^= (uint64_t)data[1] << 8; case 2: h ^= (uint64_t)data[1] << 8; /* fall-thru */
case 1: h ^= (uint64_t)data[0]; case 1: h ^= (uint64_t)data[0];
h *= m; h *= m; /* fall-thru */
}; };
h ^= h >> r; h ^= h >> r;
......
...@@ -86,6 +86,8 @@ lzf_decompress (const void *const in_data, unsigned int in_len, ...@@ -86,6 +86,8 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
#ifdef lzf_movsb #ifdef lzf_movsb
lzf_movsb (op, ip, ctrl); lzf_movsb (op, ip, ctrl);
#else #else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
switch (ctrl) switch (ctrl)
{ {
case 32: *op++ = *ip++; case 31: *op++ = *ip++; case 30: *op++ = *ip++; case 29: *op++ = *ip++; case 32: *op++ = *ip++; case 31: *op++ = *ip++; case 30: *op++ = *ip++; case 29: *op++ = *ip++;
...@@ -97,6 +99,7 @@ lzf_decompress (const void *const in_data, unsigned int in_len, ...@@ -97,6 +99,7 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
case 8: *op++ = *ip++; case 7: *op++ = *ip++; case 6: *op++ = *ip++; case 5: *op++ = *ip++; case 8: *op++ = *ip++; case 7: *op++ = *ip++; case 6: *op++ = *ip++; case 5: *op++ = *ip++;
case 4: *op++ = *ip++; case 3: *op++ = *ip++; case 2: *op++ = *ip++; case 1: *op++ = *ip++; case 4: *op++ = *ip++; case 3: *op++ = *ip++; case 2: *op++ = *ip++; case 1: *op++ = *ip++;
} }
#pragma GCC diagnostic pop
#endif #endif
} }
else /* back reference */ else /* back reference */
...@@ -163,17 +166,17 @@ lzf_decompress (const void *const in_data, unsigned int in_len, ...@@ -163,17 +166,17 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
break; break;
case 9: *op++ = *ref++; case 9: *op++ = *ref++; /* fall-thru */
case 8: *op++ = *ref++; case 8: *op++ = *ref++; /* fall-thru */
case 7: *op++ = *ref++; case 7: *op++ = *ref++; /* fall-thru */
case 6: *op++ = *ref++; case 6: *op++ = *ref++; /* fall-thru */
case 5: *op++ = *ref++; case 5: *op++ = *ref++; /* fall-thru */
case 4: *op++ = *ref++; case 4: *op++ = *ref++; /* fall-thru */
case 3: *op++ = *ref++; case 3: *op++ = *ref++; /* fall-thru */
case 2: *op++ = *ref++; case 2: *op++ = *ref++; /* fall-thru */
case 1: *op++ = *ref++; case 1: *op++ = *ref++; /* fall-thru */
case 0: *op++ = *ref++; /* two octets more */ case 0: *op++ = *ref++; /* two octets more */
*op++ = *ref++; *op++ = *ref++; /* fall-thru */
} }
#endif #endif
} }
......
...@@ -2239,6 +2239,9 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) { ...@@ -2239,6 +2239,9 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
* to avoid a useless copy. */ * to avoid a useless copy. */
if (flags & REDISMODULE_HASH_CFIELDS) if (flags & REDISMODULE_HASH_CFIELDS)
low_flags |= HASH_SET_TAKE_FIELD; low_flags |= HASH_SET_TAKE_FIELD;
robj *argv[2] = {field,value};
hashTypeTryConversion(key->value,argv,0,1);
updated += hashTypeSet(key->value, field->ptr, value->ptr, low_flags); updated += hashTypeSet(key->value, field->ptr, value->ptr, low_flags);
/* If CFIELDS is active, SDS string ownership is now of hashTypeSet(), /* If CFIELDS is active, SDS string ownership is now of hashTypeSet(),
...@@ -3396,7 +3399,7 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li ...@@ -3396,7 +3399,7 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
* *
* If the specified log level is invalid, verbose is used by default. * If the specified log level is invalid, verbose is used by default.
* There is a fixed limit to the length of the log line this function is able * There is a fixed limit to the length of the log line this function is able
* to emit, this limti is not specified but is guaranteed to be more than * to emit, this limit is not specified but is guaranteed to be more than
* a few lines of text. * a few lines of text.
*/ */
void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) { void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) {
...@@ -4499,7 +4502,15 @@ int moduleUnload(sds name) { ...@@ -4499,7 +4502,15 @@ int moduleUnload(sds name) {
* MODULE LOAD <path> [args...] */ * MODULE LOAD <path> [args...] */
void moduleCommand(client *c) { void moduleCommand(client *c) {
char *subcmd = c->argv[1]->ptr; char *subcmd = c->argv[1]->ptr;
if (c->argc == 2 && !strcasecmp(subcmd,"help")) {
const char *help[] = {
"LIST -- Return a list of loaded modules.",
"LOAD <path> [arg ...] -- Load a module library from <path>.",
"UNLOAD <name> -- Unload a module.",
NULL
};
addReplyHelp(c, help);
} else
if (!strcasecmp(subcmd,"load") && c->argc >= 3) { if (!strcasecmp(subcmd,"load") && c->argc >= 3) {
robj **argv = NULL; robj **argv = NULL;
int argc = 0; int argc = 0;
...@@ -4548,7 +4559,8 @@ void moduleCommand(client *c) { ...@@ -4548,7 +4559,8 @@ void moduleCommand(client *c) {
} }
dictReleaseIterator(di); dictReleaseIterator(di);
} else { } else {
addReply(c,shared.syntaxerr); addReplySubcommandSyntaxError(c);
return;
} }
} }
......
...@@ -75,6 +75,8 @@ void linkClient(client *c) { ...@@ -75,6 +75,8 @@ void linkClient(client *c) {
* this way removing the client in unlinkClient() will not require * this way removing the client in unlinkClient() will not require
* a linear scan, but just a constant time operation. */ * a linear scan, but just a constant time operation. */
c->client_list_node = listLast(server.clients); c->client_list_node = listLast(server.clients);
uint64_t id = htonu64(c->id);
raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
} }
client *createClient(int fd) { client *createClient(int fd) {
...@@ -247,7 +249,7 @@ void _addReplyStringToList(client *c, const char *s, size_t len) { ...@@ -247,7 +249,7 @@ void _addReplyStringToList(client *c, const char *s, size_t len) {
/* Append to this object when possible. If tail == NULL it was /* Append to this object when possible. If tail == NULL it was
* set via addDeferredMultiBulkLength(). */ * set via addDeferredMultiBulkLength(). */
if (tail && sdslen(tail)+len <= PROTO_REPLY_CHUNK_BYTES) { if (tail && (sdsavail(tail) >= len || sdslen(tail)+len <= PROTO_REPLY_CHUNK_BYTES)) {
tail = sdscatlen(tail,s,len); tail = sdscatlen(tail,s,len);
listNodeValue(ln) = tail; listNodeValue(ln) = tail;
c->reply_bytes += len; c->reply_bytes += len;
...@@ -560,6 +562,18 @@ void addReplyHelp(client *c, const char **help) { ...@@ -560,6 +562,18 @@ void addReplyHelp(client *c, const char **help) {
setDeferredMultiBulkLength(c,blenp,blen); setDeferredMultiBulkLength(c,blenp,blen);
} }
/* Add a suggestive error reply.
* This function is typically invoked by from commands that support
* subcommands in response to an unknown subcommand or argument error. */
void addReplySubcommandSyntaxError(client *c) {
sds cmd = sdsnew((char*) c->argv[0]->ptr);
sdstoupper(cmd);
addReplyErrorFormat(c,
"Unknown subcommand or wrong number of arguments for '%s'. Try %s HELP.",
c->argv[1]->ptr,cmd);
sdsfree(cmd);
}
/* Copy 'src' client output buffers into 'dst' client output buffers. /* Copy 'src' client output buffers into 'dst' client output buffers.
* The function takes care of freeing the old output buffers of the * The function takes care of freeing the old output buffers of the
* destination client. */ * destination client. */
...@@ -720,6 +734,8 @@ void unlinkClient(client *c) { ...@@ -720,6 +734,8 @@ void unlinkClient(client *c) {
if (c->fd != -1) { if (c->fd != -1) {
/* Remove from the list of active clients. */ /* Remove from the list of active clients. */
if (c->client_list_node) { if (c->client_list_node) {
uint64_t id = htonu64(c->id);
raxRemove(server.clients_index,(unsigned char*)&id,sizeof(id),NULL);
listDelNode(server.clients,c->client_list_node); listDelNode(server.clients,c->client_list_node);
c->client_list_node = NULL; c->client_list_node = NULL;
} }
...@@ -864,6 +880,15 @@ void freeClientsInAsyncFreeQueue(void) { ...@@ -864,6 +880,15 @@ void freeClientsInAsyncFreeQueue(void) {
} }
} }
/* Return a client by ID, or NULL if the client ID is not in the set
* of registered clients. Note that "fake clients", created with -1 as FD,
* are not registered clients. */
client *lookupClientByID(uint64_t id) {
id = htonu64(id);
client *c = raxFind(server.clients_index,(unsigned char*)&id,sizeof(id));
return (c == raxNotFound) ? NULL : c;
}
/* Write data in output buffers to client. Return C_OK if the client /* Write data in output buffers to client. Return C_OK if the client
* is still valid after the call, C_ERR if it was freed. */ * is still valid after the call, C_ERR if it was freed. */
int writeToClient(int fd, client *c, int handler_installed) { int writeToClient(int fd, client *c, int handler_installed) {
...@@ -1493,6 +1518,7 @@ sds catClientInfoString(sds s, client *client) { ...@@ -1493,6 +1518,7 @@ sds catClientInfoString(sds s, client *client) {
*p++ = 'S'; *p++ = 'S';
} }
if (client->flags & CLIENT_MASTER) *p++ = 'M'; if (client->flags & CLIENT_MASTER) *p++ = 'M';
if (client->flags & CLIENT_PUBSUB) *p++ = 'P';
if (client->flags & CLIENT_MULTI) *p++ = 'x'; if (client->flags & CLIENT_MULTI) *p++ = 'x';
if (client->flags & CLIENT_BLOCKED) *p++ = 'b'; if (client->flags & CLIENT_BLOCKED) *p++ = 'b';
if (client->flags & CLIENT_DIRTY_CAS) *p++ = 'd'; if (client->flags & CLIENT_DIRTY_CAS) *p++ = 'd';
...@@ -1531,7 +1557,7 @@ sds catClientInfoString(sds s, client *client) { ...@@ -1531,7 +1557,7 @@ sds catClientInfoString(sds s, client *client) {
client->lastcmd ? client->lastcmd->name : "NULL"); client->lastcmd ? client->lastcmd->name : "NULL");
} }
sds getAllClientsInfoString(void) { sds getAllClientsInfoString(int type) {
listNode *ln; listNode *ln;
listIter li; listIter li;
client *client; client *client;
...@@ -1540,6 +1566,7 @@ sds getAllClientsInfoString(void) { ...@@ -1540,6 +1566,7 @@ sds getAllClientsInfoString(void) {
listRewind(server.clients,&li); listRewind(server.clients,&li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
client = listNodeValue(ln); client = listNodeValue(ln);
if (type != -1 && getClientType(client) != type) continue;
o = catClientInfoString(o,client); o = catClientInfoString(o,client);
o = sdscatlen(o,"\n",1); o = sdscatlen(o,"\n",1);
} }
...@@ -1553,22 +1580,40 @@ void clientCommand(client *c) { ...@@ -1553,22 +1580,40 @@ void clientCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"id -- Return the ID of the current connection.",
"getname -- Return the name of the current connection.", "getname -- Return the name of the current connection.",
"kill <ip:port> -- Kill connection made from <ip:port>.", "kill <ip:port> -- Kill connection made from <ip:port>.",
"kill <option> <value> [option value ...] -- Kill connections. Options are:", "kill <option> <value> [option value ...] -- Kill connections. Options are:",
" addr <ip:port> -- Kill connection made from <ip:port>.", " addr <ip:port> -- Kill connection made from <ip:port>",
" type (normal|master|slave|pubsub) -- Kill connections by type.", " type (normal|master|slave|pubsub) -- Kill connections by type.",
" skipme (yes|no) -- Skip killing current connection (default: yes).", " skipme (yes|no) -- Skip killing current connection (default: yes).",
"list -- Return information about client connections.", "list [options ...] -- Return information about client connections. Options:",
" type (normal|master|slave|pubsub) -- Return clients of specified type.",
"pause <timeout> -- Suspend all Redis clients for <timout> milliseconds.", "pause <timeout> -- Suspend all Redis clients for <timout> milliseconds.",
"reply (on|off|skip) -- Control the replies sent to the current connection.", "reply (on|off|skip) -- Control the replies sent to the current connection.",
"setname <name> -- Assign the name <name> to the current connection.", "setname <name> -- Assign the name <name> to the current connection.",
"unblock -- Unblock the specified blocked client.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
} else if (!strcasecmp(c->argv[1]->ptr,"list") && c->argc == 2) { } else if (!strcasecmp(c->argv[1]->ptr,"id") && c->argc == 2) {
/* CLIENT ID */
addReplyLongLong(c,c->id);
} else if (!strcasecmp(c->argv[1]->ptr,"list")) {
/* CLIENT LIST */ /* CLIENT LIST */
sds o = getAllClientsInfoString(); int type = -1;
if (c->argc == 4 && !strcasecmp(c->argv[2]->ptr,"type")) {
type = getClientTypeByName(c->argv[3]->ptr);
if (type == -1) {
addReplyErrorFormat(c,"Unknown client type '%s'",
(char*) c->argv[3]->ptr);
return;
}
} else if (c->argc != 2) {
addReply(c,shared.syntaxerr);
return;
}
sds o = getAllClientsInfoString(type);
addReplyBulkCBuffer(c,o,sdslen(o)); addReplyBulkCBuffer(c,o,sdslen(o));
sdsfree(o); sdsfree(o);
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
...@@ -1671,6 +1716,38 @@ NULL ...@@ -1671,6 +1716,38 @@ NULL
/* If this client has to be closed, flag it as CLOSE_AFTER_REPLY /* If this client has to be closed, flag it as CLOSE_AFTER_REPLY
* only after we queued the reply to its output buffers. */ * only after we queued the reply to its output buffers. */
if (close_this_client) c->flags |= CLIENT_CLOSE_AFTER_REPLY; if (close_this_client) c->flags |= CLIENT_CLOSE_AFTER_REPLY;
} else if (!strcasecmp(c->argv[1]->ptr,"unblock") && (c->argc == 3 ||
c->argc == 4))
{
/* CLIENT UNBLOCK <id> [timeout|error] */
long long id;
int unblock_error = 0;
if (c->argc == 4) {
if (!strcasecmp(c->argv[3]->ptr,"timeout")) {
unblock_error = 0;
} else if (!strcasecmp(c->argv[3]->ptr,"error")) {
unblock_error = 1;
} else {
addReplyError(c,
"CLIENT UNBLOCK reason should be TIMEOUT or ERROR");
return;
}
}
if (getLongLongFromObjectOrReply(c,c->argv[2],&id,NULL)
!= C_OK) return;
struct client *target = lookupClientByID(id);
if (target && target->flags & CLIENT_BLOCKED) {
if (unblock_error)
addReplyError(target,
"-UNBLOCKED client unblocked via CLIENT UNBLOCK");
else
replyToBlockedClientTimedOut(target);
unblockClient(target);
addReply(c,shared.cone);
} else {
addReply(c,shared.czero);
}
} else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) { } else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) {
int j, len = sdslen(c->argv[2]->ptr); int j, len = sdslen(c->argv[2]->ptr);
char *p = c->argv[2]->ptr; char *p = c->argv[2]->ptr;
......
...@@ -123,9 +123,25 @@ robj *createStringObject(const char *ptr, size_t len) { ...@@ -123,9 +123,25 @@ robj *createStringObject(const char *ptr, size_t len) {
return createRawStringObject(ptr,len); return createRawStringObject(ptr,len);
} }
robj *createStringObjectFromLongLong(long long value) { /* Create a string object from a long long value. When possible returns a
* shared integer object, or at least an integer encoded one.
*
* If valueobj is non zero, the function avoids returning a a shared
* integer, because the object is going to be used as value in the Redis key
* space (for instance when the INCR command is used), so we want LFU/LRU
* values specific for each key. */
robj *createStringObjectFromLongLongWithOptions(long long value, int valueobj) {
robj *o; robj *o;
if (value >= 0 && value < OBJ_SHARED_INTEGERS) {
if (server.maxmemory == 0 ||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS))
{
/* If the maxmemory policy permits, we can still return shared integers
* even if valueobj is true. */
valueobj = 0;
}
if (value >= 0 && value < OBJ_SHARED_INTEGERS && valueobj == 0) {
incrRefCount(shared.integers[value]); incrRefCount(shared.integers[value]);
o = shared.integers[value]; o = shared.integers[value];
} else { } else {
...@@ -140,6 +156,20 @@ robj *createStringObjectFromLongLong(long long value) { ...@@ -140,6 +156,20 @@ robj *createStringObjectFromLongLong(long long value) {
return o; return o;
} }
/* Wrapper for createStringObjectFromLongLongWithOptions() always demanding
* to create a shared object if possible. */
robj *createStringObjectFromLongLong(long long value) {
return createStringObjectFromLongLongWithOptions(value,0);
}
/* Wrapper for createStringObjectFromLongLongWithOptions() avoiding a shared
* object when LFU/LRU info are needed, that is, when the object is used
* as a value in the key space, and Redis is configured to evict based on
* LFU/LRU. */
robj *createStringObjectFromLongLongForValue(long long value) {
return createStringObjectFromLongLongWithOptions(value,1);
}
/* Create a string object from a long double. If humanfriendly is non-zero /* Create a string object from a long double. If humanfriendly is non-zero
* it does not use exponential format and trims trailing zeroes at the end, * it does not use exponential format and trims trailing zeroes at the end,
* however this results in loss of precision. Otherwise exp format is used * however this results in loss of precision. Otherwise exp format is used
...@@ -715,7 +745,7 @@ char *strEncoding(int encoding) { ...@@ -715,7 +745,7 @@ char *strEncoding(int encoding) {
* size of a radix tree that is used to store Stream IDs. * size of a radix tree that is used to store Stream IDs.
* *
* Note: to guess the size of the radix tree is not trivial, so we * Note: to guess the size of the radix tree is not trivial, so we
* approximate it considering 128 bytes of data overhead for each * approximate it considering 16 bytes of data overhead for each
* key (the ID), and then adding the number of bare nodes, plus some * key (the ID), and then adding the number of bare nodes, plus some
* overhead due by the data and child pointers. This secret recipe * overhead due by the data and child pointers. This secret recipe
* was obtained by checking the average radix tree created by real * was obtained by checking the average radix tree created by real
...@@ -874,6 +904,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) { ...@@ -874,6 +904,7 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
* structures and the PEL memory usage. */ * structures and the PEL memory usage. */
raxIterator cri; raxIterator cri;
raxStart(&cri,cg->consumers); raxStart(&cri,cg->consumers);
raxSeek(&cri,"^",NULL,0);
while(raxNext(&cri)) { while(raxNext(&cri)) {
streamConsumer *consumer = cri.data; streamConsumer *consumer = cri.data;
asize += sizeof(*consumer); asize += sizeof(*consumer);
...@@ -968,7 +999,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) { ...@@ -968,7 +999,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
listRewind(server.clients,&li); listRewind(server.clients,&li);
while((ln = listNext(&li))) { while((ln = listNext(&li))) {
client *c = listNodeValue(ln); client *c = listNodeValue(ln);
if (c->flags & CLIENT_SLAVE) if (c->flags & CLIENT_SLAVE && !(c->flags & CLIENT_MONITOR))
continue; continue;
mem += getClientOutputBufferMemoryUsage(c); mem += getClientOutputBufferMemoryUsage(c);
mem += sdsAllocSize(c->querybuf); mem += sdsAllocSize(c->querybuf);
...@@ -1136,6 +1167,32 @@ sds getMemoryDoctorReport(void) { ...@@ -1136,6 +1167,32 @@ sds getMemoryDoctorReport(void) {
return s; return s;
} }
/* Set the object LRU/LFU depending on server.maxmemory_policy.
* The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU.
* The lru_idle and lru_clock args are only relevant if policy
* is MAXMEMORY_FLAG_LRU.
* Either or both of them may be <0, in that case, nothing is set. */
void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
long long lru_clock) {
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
if (lfu_freq >= 0) {
serverAssert(lfu_freq <= 255);
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
}
} else if (lru_idle >= 0) {
/* Serialized LRU idle time is in seconds. Scale
* according to the LRU clock resolution this Redis
* instance was compiled with (normally 1000 ms, so the
* below statement will expand to lru_idle*1000/1000. */
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
val->lru = lru_clock - lru_idle;
/* If the lru field overflows (since LRU it is a wrapping
* clock), the best we can do is to provide the maximum
* representable idle time. */
if (val->lru < 0) val->lru = lru_clock+1;
}
}
/* ======================= The OBJECT and MEMORY commands =================== */ /* ======================= The OBJECT and MEMORY commands =================== */
/* This is a helper function for the OBJECT command. We need to lookup keys /* This is a helper function for the OBJECT command. We need to lookup keys
...@@ -1161,10 +1218,10 @@ void objectCommand(client *c) { ...@@ -1161,10 +1218,10 @@ void objectCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"encoding <key> -- Return the kind of internal representation used in order to store the value associated with a key.", "ENCODING <key> -- Return the kind of internal representation used in order to store the value associated with a key.",
"freq <key> -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.", "FREQ <key> -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.",
"idletime <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.", "IDLETIME <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.",
"refcount <key> -- Return the number of references of the value associated with the specified key.", "REFCOUNT <key> -- Return the number of references of the value associated with the specified key.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
...@@ -1197,7 +1254,7 @@ NULL ...@@ -1197,7 +1254,7 @@ NULL
* when the key is read or overwritten. */ * when the key is read or overwritten. */
addReplyLongLong(c,LFUDecrAndReturn(o)); addReplyLongLong(c,LFUDecrAndReturn(o));
} else { } else {
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try OBJECT help", (char *)c->argv[1]->ptr); addReplySubcommandSyntaxError(c);
} }
} }
......
...@@ -327,9 +327,9 @@ void publishCommand(client *c) { ...@@ -327,9 +327,9 @@ void publishCommand(client *c) {
void pubsubCommand(client *c) { void pubsubCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"channels [<pattern>] -- Return the currently active channels matching a pattern (default: all).", "CHANNELS [<pattern>] -- Return the currently active channels matching a pattern (default: all).",
"numpat -- Return number of subscriptions to patterns.", "NUMPAT -- Return number of subscriptions to patterns.",
"numsub [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).", "NUMSUB [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
...@@ -372,7 +372,6 @@ NULL ...@@ -372,7 +372,6 @@ NULL
/* PUBSUB NUMPAT */ /* PUBSUB NUMPAT */
addReplyLongLong(c,listLength(server.pubsub_patterns)); addReplyLongLong(c,listLength(server.pubsub_patterns));
} else { } else {
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try PUBSUB HELP", addReplySubcommandSyntaxError(c);
(char*)c->argv[1]->ptr);
} }
} }
...@@ -359,7 +359,18 @@ raxNode *raxCompressNode(raxNode *n, unsigned char *s, size_t len, raxNode **chi ...@@ -359,7 +359,18 @@ raxNode *raxCompressNode(raxNode *n, unsigned char *s, size_t len, raxNode **chi
* parent's node is returned as '*plink' if not NULL. Finally, if the * parent's node is returned as '*plink' if not NULL. Finally, if the
* search stopped in a compressed node, '*splitpos' returns the index * search stopped in a compressed node, '*splitpos' returns the index
* inside the compressed node where the search ended. This is useful to * inside the compressed node where the search ended. This is useful to
* know where to split the node for insertion. */ * know where to split the node for insertion.
*
* Note that when we stop in the middle of a compressed node with
* a perfect match, this function will return a length equal to the
* 'len' argument (all the key matched), and will return a *splitpos which is
* always positive (that will represent the index of the character immediately
* *after* the last match in the current compressed node).
*
* When instead we stop at a compressed node and *splitpos is zero, it
* means that the current node represents the key (that is, none of the
* compressed node characters are needed to represent the key, just all
* its parents nodes). */
static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode **stopnode, raxNode ***plink, int *splitpos, raxStack *ts) { static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode **stopnode, raxNode ***plink, int *splitpos, raxStack *ts) {
raxNode *h = rax->head; raxNode *h = rax->head;
raxNode **parentlink = &rax->head; raxNode **parentlink = &rax->head;
...@@ -405,10 +416,12 @@ static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode ...@@ -405,10 +416,12 @@ static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode
/* Insert the element 's' of size 'len', setting as auxiliary data /* Insert the element 's' of size 'len', setting as auxiliary data
* the pointer 'data'. If the element is already present, the associated * the pointer 'data'. If the element is already present, the associated
* data is updated, and 0 is returned, otherwise the element is inserted * data is updated (only if 'overwrite' is set to 1), and 0 is returned,
* and 1 is returned. On out of memory the function returns 0 as well but * otherwise the element is inserted and 1 is returned. On out of memory the
* sets errno to ENOMEM, otherwise errno will be set to 0. */ * function returns 0 as well but sets errno to ENOMEM, otherwise errno will
int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { * be set to 0.
*/
int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old, int overwrite) {
size_t i; size_t i;
int j = 0; /* Split position. If raxLowWalk() stops in a compressed int j = 0; /* Split position. If raxLowWalk() stops in a compressed
node, the index 'j' represents the char we stopped within the node, the index 'j' represents the char we stopped within the
...@@ -426,7 +439,8 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { ...@@ -426,7 +439,8 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
* data pointer. */ * data pointer. */
if (i == len && (!h->iscompr || j == 0 /* not in the middle if j is 0 */)) { if (i == len && (!h->iscompr || j == 0 /* not in the middle if j is 0 */)) {
debugf("### Insert: node representing key exists\n"); debugf("### Insert: node representing key exists\n");
if (!h->iskey || h->isnull) { /* Make space for the value pointer if needed. */
if (!h->iskey || (h->isnull && overwrite)) {
h = raxReallocForData(h,data); h = raxReallocForData(h,data);
if (h) memcpy(parentlink,&h,sizeof(h)); if (h) memcpy(parentlink,&h,sizeof(h));
} }
...@@ -434,12 +448,17 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) { ...@@ -434,12 +448,17 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
errno = ENOMEM; errno = ENOMEM;
return 0; return 0;
} }
/* Update the existing key if there is already one. */
if (h->iskey) { if (h->iskey) {
if (old) *old = raxGetData(h); if (old) *old = raxGetData(h);
raxSetData(h,data); if (overwrite) raxSetData(h,data);
errno = 0; errno = 0;
return 0; /* Element already exists. */ return 0; /* Element already exists. */
} }
/* Otherwise set the node as a key. Note that raxSetData()
* will set h->iskey. */
raxSetData(h,data); raxSetData(h,data);
rax->numele++; rax->numele++;
return 1; /* Element inserted. */ return 1; /* Element inserted. */
...@@ -793,6 +812,19 @@ oom: ...@@ -793,6 +812,19 @@ oom:
return 0; return 0;
} }
/* Overwriting insert. Just a wrapper for raxGenericInsert() that will
* update the element if there is already one for the same key. */
int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
return raxGenericInsert(rax,s,len,data,old,1);
}
/* Non overwriting insert function: this if an element with the same key
* exists, the value is not updated and the function returns 0.
* This is a just a wrapper for raxGenericInsert(). */
int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
return raxGenericInsert(rax,s,len,data,old,0);
}
/* Find a key in the rax, returns raxNotFound special void pointer value /* Find a key in the rax, returns raxNotFound special void pointer value
* if the item was not found, otherwise the value associated with the * if the item was not found, otherwise the value associated with the
* item is returned. */ * item is returned. */
...@@ -1135,6 +1167,7 @@ void raxStart(raxIterator *it, rax *rt) { ...@@ -1135,6 +1167,7 @@ void raxStart(raxIterator *it, rax *rt) {
it->key = it->key_static_string; it->key = it->key_static_string;
it->key_max = RAX_ITER_STATIC_LEN; it->key_max = RAX_ITER_STATIC_LEN;
it->data = NULL; it->data = NULL;
it->node_cb = NULL;
raxStackInit(&it->stack); raxStackInit(&it->stack);
} }
...@@ -1208,6 +1241,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) { ...@@ -1208,6 +1241,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
if (!raxIteratorAddChars(it,it->node->data, if (!raxIteratorAddChars(it,it->node->data,
it->node->iscompr ? it->node->size : 1)) return 0; it->node->iscompr ? it->node->size : 1)) return 0;
memcpy(&it->node,cp,sizeof(it->node)); memcpy(&it->node,cp,sizeof(it->node));
/* Call the node callback if any, and replace the node pointer
* if the callback returns true. */
if (it->node_cb && it->node_cb(&it->node))
memcpy(cp,&it->node,sizeof(it->node));
/* For "next" step, stop every time we find a key along the /* For "next" step, stop every time we find a key along the
* way, since the key is lexicograhically smaller compared to * way, since the key is lexicograhically smaller compared to
* what follows in the sub-children. */ * what follows in the sub-children. */
...@@ -1260,6 +1297,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) { ...@@ -1260,6 +1297,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
raxIteratorAddChars(it,it->node->data+i,1); raxIteratorAddChars(it,it->node->data+i,1);
if (!raxStackPush(&it->stack,it->node)) return 0; if (!raxStackPush(&it->stack,it->node)) return 0;
memcpy(&it->node,cp,sizeof(it->node)); memcpy(&it->node,cp,sizeof(it->node));
/* Call the node callback if any, and replace the node
* pointer if the callback returns true. */
if (it->node_cb && it->node_cb(&it->node))
memcpy(cp,&it->node,sizeof(it->node));
if (it->node->iskey) { if (it->node->iskey) {
it->data = raxGetData(it->node); it->data = raxGetData(it->node);
return 1; return 1;
...@@ -1293,7 +1334,7 @@ int raxSeekGreatest(raxIterator *it) { ...@@ -1293,7 +1334,7 @@ int raxSeekGreatest(raxIterator *it) {
/* Like raxIteratorNextStep() but implements an iteration step moving /* Like raxIteratorNextStep() but implements an iteration step moving
* to the lexicographically previous element. The 'noup' option has a similar * to the lexicographically previous element. The 'noup' option has a similar
* effect to the one of raxIteratorPrevSte(). */ * effect to the one of raxIteratorNextStep(). */
int raxIteratorPrevStep(raxIterator *it, int noup) { int raxIteratorPrevStep(raxIterator *it, int noup) {
if (it->flags & RAX_ITER_EOF) { if (it->flags & RAX_ITER_EOF) {
return 1; return 1;
...@@ -1523,11 +1564,26 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) { ...@@ -1523,11 +1564,26 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
/* If there was no mismatch we are into a node representing the /* If there was no mismatch we are into a node representing the
* key, (but which is not a key or the seek operator does not * key, (but which is not a key or the seek operator does not
* include 'eq'), or we stopped in the middle of a compressed node * include 'eq'), or we stopped in the middle of a compressed node
* after processing all the key. Cotinue iterating as this was * after processing all the key. Continue iterating as this was
* a legitimate key we stopped at. */ * a legitimate key we stopped at. */
it->flags &= ~RAX_ITER_JUST_SEEKED; it->flags &= ~RAX_ITER_JUST_SEEKED;
if (it->node->iscompr && it->node->iskey && splitpos && lt) {
/* If we stopped in the middle of a compressed node with
* perfect match, and the condition is to seek a key "<" than
* the specified one, then if this node is a key it already
* represents our match. For instance we may have nodes:
*
* "f" -> "oobar" = 1 -> "" = 2
*
* Representing keys "f" = 1, "foobar" = 2. A seek for
* the key < "foo" will stop in the middle of the "oobar"
* node, but will be our match, representing the key "f".
*
* So in that case, we don't seek backward. */
} else {
if (gt && !raxIteratorNextStep(it,0)) return 0; if (gt && !raxIteratorNextStep(it,0)) return 0;
if (lt && !raxIteratorPrevStep(it,0)) return 0; if (lt && !raxIteratorPrevStep(it,0)) return 0;
}
it->flags |= RAX_ITER_JUST_SEEKED; /* Ignore next call. */ it->flags |= RAX_ITER_JUST_SEEKED; /* Ignore next call. */
} }
} else { } else {
......
...@@ -119,6 +119,21 @@ typedef struct raxStack { ...@@ -119,6 +119,21 @@ typedef struct raxStack {
int oom; /* True if pushing into this stack failed for OOM at some point. */ int oom; /* True if pushing into this stack failed for OOM at some point. */
} raxStack; } raxStack;
/* Optional callback used for iterators and be notified on each rax node,
* including nodes not representing keys. If the callback returns true
* the callback changed the node pointer in the iterator structure, and the
* iterator implementation will have to replace the pointer in the radix tree
* internals. This allows the callback to reallocate the node to perform
* very special operations, normally not needed by normal applications.
*
* This callback is used to perform very low level analysis of the radix tree
* structure, scanning each possible node (but the root node), or in order to
* reallocate the nodes to reduce the allocation fragmentation (this is the
* Redis application for this callback).
*
* This is currently only supported in forward iterations (raxNext) */
typedef int (*raxNodeCallback)(raxNode **noderef);
/* Radix tree iterator state is encapsulated into this data structure. */ /* Radix tree iterator state is encapsulated into this data structure. */
#define RAX_ITER_STATIC_LEN 128 #define RAX_ITER_STATIC_LEN 128
#define RAX_ITER_JUST_SEEKED (1<<0) /* Iterator was just seeked. Return current #define RAX_ITER_JUST_SEEKED (1<<0) /* Iterator was just seeked. Return current
...@@ -137,6 +152,7 @@ typedef struct raxIterator { ...@@ -137,6 +152,7 @@ typedef struct raxIterator {
unsigned char key_static_string[RAX_ITER_STATIC_LEN]; unsigned char key_static_string[RAX_ITER_STATIC_LEN];
raxNode *node; /* Current node. Only for unsafe iteration. */ raxNode *node; /* Current node. Only for unsafe iteration. */
raxStack stack; /* Stack used for unsafe iteration. */ raxStack stack; /* Stack used for unsafe iteration. */
raxNodeCallback node_cb; /* Optional node callback. Normally set to NULL. */
} raxIterator; } raxIterator;
/* A special pointer returned for not found items. */ /* A special pointer returned for not found items. */
...@@ -145,6 +161,7 @@ extern void *raxNotFound; ...@@ -145,6 +161,7 @@ extern void *raxNotFound;
/* Exported API. */ /* Exported API. */
rax *raxNew(void); rax *raxNew(void);
int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old); int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old); int raxRemove(rax *rax, unsigned char *s, size_t len, void **old);
void *raxFind(rax *rax, unsigned char *s, size_t len); void *raxFind(rax *rax, unsigned char *s, size_t len);
void raxFree(rax *rax); void raxFree(rax *rax);
...@@ -160,4 +177,8 @@ int raxEOF(raxIterator *it); ...@@ -160,4 +177,8 @@ int raxEOF(raxIterator *it);
void raxShow(rax *rax); void raxShow(rax *rax);
uint64_t raxSize(rax *rax); uint64_t raxSize(rax *rax);
/* Internal API. May be used by the node callback in order to access rax nodes
* in a low level way, so this function is exported as well. */
void raxSetData(raxNode *n, void *data);
#endif #endif
...@@ -100,6 +100,9 @@ int rdbLoadType(rio *rdb) { ...@@ -100,6 +100,9 @@ int rdbLoadType(rio *rdb) {
return type; return type;
} }
/* This is only used to load old databases stored with the RDB_OPCODE_EXPIRETIME
* opcode. New versions of Redis store using the RDB_OPCODE_EXPIRETIME_MS
* opcode. */
time_t rdbLoadTime(rio *rdb) { time_t rdbLoadTime(rio *rdb) {
int32_t t32; int32_t t32;
rdbLoadRaw(rdb,&t32,4); rdbLoadRaw(rdb,&t32,4);
...@@ -108,12 +111,26 @@ time_t rdbLoadTime(rio *rdb) { ...@@ -108,12 +111,26 @@ time_t rdbLoadTime(rio *rdb) {
int rdbSaveMillisecondTime(rio *rdb, long long t) { int rdbSaveMillisecondTime(rio *rdb, long long t) {
int64_t t64 = (int64_t) t; int64_t t64 = (int64_t) t;
memrev64ifbe(&t64); /* Store in little endian. */
return rdbWriteRaw(rdb,&t64,8); return rdbWriteRaw(rdb,&t64,8);
} }
long long rdbLoadMillisecondTime(rio *rdb) { /* This function loads a time from the RDB file. It gets the version of the
* RDB because, unfortunately, before Redis 5 (RDB version 9), the function
* failed to convert data to/from little endian, so RDB files with keys having
* expires could not be shared between big endian and little endian systems
* (because the expire time will be totally wrong). The fix for this is just
* to call memrev64ifbe(), however if we fix this for all the RDB versions,
* this call will introduce an incompatibility for big endian systems:
* after upgrading to Redis version 5 they will no longer be able to load their
* own old RDB files. Because of that, we instead fix the function only for new
* RDB versions, and load older RDB versions as we used to do in the past,
* allowing big endian systems to load their own old RDB files. */
long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {
int64_t t64; int64_t t64;
rdbLoadRaw(rdb,&t64,8); rdbLoadRaw(rdb,&t64,8);
if (rdbver >= 9) /* Check the top comment of this function. */
memrev64ifbe(&t64); /* Convert in big endian if the system is BE. */
return (long long)t64; return (long long)t64;
} }
...@@ -271,7 +288,7 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) { ...@@ -271,7 +288,7 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
memcpy(p,buf,len); memcpy(p,buf,len);
return p; return p;
} else if (encode) { } else if (encode) {
return createStringObjectFromLongLong(val); return createStringObjectFromLongLongForValue(val);
} else { } else {
return createObject(OBJ_STRING,sdsfromlonglong(val)); return createObject(OBJ_STRING,sdsfromlonglong(val));
} }
...@@ -988,8 +1005,7 @@ size_t rdbSavedObjectLen(robj *o) { ...@@ -988,8 +1005,7 @@ size_t rdbSavedObjectLen(robj *o) {
* On error -1 is returned. * On error -1 is returned.
* On success if the key was actually saved 1 is returned, otherwise 0 * On success if the key was actually saved 1 is returned, otherwise 0
* is returned (the key was already expired). */ * is returned (the key was already expired). */
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
{
int savelru = server.maxmemory_policy & MAXMEMORY_FLAG_LRU; int savelru = server.maxmemory_policy & MAXMEMORY_FLAG_LRU;
int savelfu = server.maxmemory_policy & MAXMEMORY_FLAG_LFU; int savelfu = server.maxmemory_policy & MAXMEMORY_FLAG_LFU;
...@@ -1001,7 +1017,7 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) ...@@ -1001,7 +1017,7 @@ int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime)
/* Save the LRU info. */ /* Save the LRU info. */
if (savelru) { if (savelru) {
int idletime = estimateObjectIdleTime(val); uint64_t idletime = estimateObjectIdleTime(val);
idletime /= 1000; /* Using seconds is enough and requires less space.*/ idletime /= 1000; /* Using seconds is enough and requires less space.*/
if (rdbSaveType(rdb,RDB_OPCODE_IDLE) == -1) return -1; if (rdbSaveType(rdb,RDB_OPCODE_IDLE) == -1) return -1;
if (rdbSaveLen(rdb,idletime) == -1) return -1; if (rdbSaveLen(rdb,idletime) == -1) return -1;
...@@ -1111,13 +1127,9 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) { ...@@ -1111,13 +1127,9 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
* is currently the largest type we are able to represent in RDB sizes. * is currently the largest type we are able to represent in RDB sizes.
* However this does not limit the actual size of the DB to load since * However this does not limit the actual size of the DB to load since
* these sizes are just hints to resize the hash tables. */ * these sizes are just hints to resize the hash tables. */
uint32_t db_size, expires_size; uint64_t db_size, expires_size;
db_size = (dictSize(db->dict) <= UINT32_MAX) ? db_size = dictSize(db->dict);
dictSize(db->dict) : expires_size = dictSize(db->expires);
UINT32_MAX;
expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
dictSize(db->expires) :
UINT32_MAX;
if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr; if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;
if (rdbSaveLen(rdb,db_size) == -1) goto werr; if (rdbSaveLen(rdb,db_size) == -1) goto werr;
if (rdbSaveLen(rdb,expires_size) == -1) goto werr; if (rdbSaveLen(rdb,expires_size) == -1) goto werr;
...@@ -1225,6 +1237,10 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) { ...@@ -1225,6 +1237,10 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
} }
rioInitWithFile(&rdb,fp); rioInitWithFile(&rdb,fp);
if (server.rdb_save_incremental_fsync)
rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) { if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) {
errno = error; errno = error;
goto werr; goto werr;
...@@ -1441,6 +1457,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { ...@@ -1441,6 +1457,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
o = createZsetObject(); o = createZsetObject();
zs = o->ptr; zs = o->ptr;
if (zsetlen > DICT_HT_INITIAL_SIZE)
dictExpand(zs->dict,zsetlen);
/* Load every single element of the sorted set. */ /* Load every single element of the sorted set. */
while(zsetlen--) { while(zsetlen--) {
sds sdsele; sds sdsele;
...@@ -1509,6 +1528,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { ...@@ -1509,6 +1528,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
sdsfree(value); sdsfree(value);
} }
if (o->encoding == OBJ_ENCODING_HT && len > DICT_HT_INITIAL_SIZE)
dictExpand(o->ptr,len);
/* Load remaining fields and values into the hash table */ /* Load remaining fields and values into the hash table */
while (o->encoding == OBJ_ENCODING_HT && len > 0) { while (o->encoding == OBJ_ENCODING_HT && len > 0) {
len--; len--;
...@@ -1683,7 +1705,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { ...@@ -1683,7 +1705,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
unsigned char rawid[sizeof(streamID)]; unsigned char rawid[sizeof(streamID)];
rdbLoadRaw(rdb,rawid,sizeof(rawid)); rdbLoadRaw(rdb,rawid,sizeof(rawid));
streamNACK *nack = streamCreateNACK(NULL); streamNACK *nack = streamCreateNACK(NULL);
nack->delivery_time = rdbLoadMillisecondTime(rdb); nack->delivery_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
nack->delivery_count = rdbLoadLen(rdb,NULL); nack->delivery_count = rdbLoadLen(rdb,NULL);
if (!raxInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL)) if (!raxInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL))
rdbExitReportCorruptRDB("Duplicated gobal PEL entry " rdbExitReportCorruptRDB("Duplicated gobal PEL entry "
...@@ -1702,7 +1724,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) { ...@@ -1702,7 +1724,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
streamConsumer *consumer = streamLookupConsumer(cgroup,cname, streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
1); 1);
sdsfree(cname); sdsfree(cname);
consumer->seen_time = rdbLoadMillisecondTime(rdb); consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
/* Load the PEL about entries owned by this specific /* Load the PEL about entries owned by this specific
* consumer. */ * consumer. */
...@@ -1845,10 +1867,8 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { ...@@ -1845,10 +1867,8 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
} }
/* Key-specific attributes, set by opcodes before the key type. */ /* Key-specific attributes, set by opcodes before the key type. */
long long expiretime = -1, now = mstime(); long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now = mstime();
long long lru_clock = LRU_CLOCK(); long long lru_clock = LRU_CLOCK();
uint64_t lru_idle = -1;
int lfu_freq = -1;
while(1) { while(1) {
robj *key, *val; robj *key, *val;
...@@ -1867,7 +1887,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { ...@@ -1867,7 +1887,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
} else if (type == RDB_OPCODE_EXPIRETIME_MS) { } else if (type == RDB_OPCODE_EXPIRETIME_MS) {
/* EXPIRETIME_MS: milliseconds precision expire times introduced /* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */ * with RDB v3. Like EXPIRETIME but no with more precision. */
expiretime = rdbLoadMillisecondTime(rdb); expiretime = rdbLoadMillisecondTime(rdb,rdbver);
continue; /* Read next opcode. */ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_FREQ) { } else if (type == RDB_OPCODE_FREQ) {
/* FREQ: LFU frequency. */ /* FREQ: LFU frequency. */
...@@ -1877,7 +1897,9 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { ...@@ -1877,7 +1897,9 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
continue; /* Read next opcode. */ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_IDLE) { } else if (type == RDB_OPCODE_IDLE) {
/* IDLE: LRU idle time. */ /* IDLE: LRU idle time. */
if ((lru_idle = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr; uint64_t qword;
if ((qword = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr;
lru_idle = qword;
continue; /* Read next opcode. */ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_EOF) { } else if (type == RDB_OPCODE_EOF) {
/* EOF: End of file, exit the main loop. */ /* EOF: End of file, exit the main loop. */
...@@ -1996,20 +2018,9 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { ...@@ -1996,20 +2018,9 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
/* Set the expire time if needed */ /* Set the expire time if needed */
if (expiretime != -1) setExpire(NULL,db,key,expiretime); if (expiretime != -1) setExpire(NULL,db,key,expiretime);
if (lfu_freq != -1) {
val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq; /* Set usage information (for eviction). */
} else { objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock);
/* LRU idle time loaded from RDB is in seconds. Scale
* according to the LRU clock resolution this Redis
* instance was compiled with (normaly 1000 ms, so the
* below statement will expand to lru_idle*1000/1000. */
lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
val->lru = lru_clock - lru_idle;
/* If the lru field overflows (since LRU it is a wrapping
* clock), the best we can do is to provide the maxium
* representable idle time. */
if (val->lru < 0) val->lru = lru_clock+1;
}
/* Decrement the key refcount since dbAdd() will take its /* Decrement the key refcount since dbAdd() will take its
* own reference. */ * own reference. */
......
...@@ -129,6 +129,8 @@ int rdbLoadType(rio *rdb); ...@@ -129,6 +129,8 @@ int rdbLoadType(rio *rdb);
int rdbSaveTime(rio *rdb, time_t t); int rdbSaveTime(rio *rdb, time_t t);
time_t rdbLoadTime(rio *rdb); time_t rdbLoadTime(rio *rdb);
int rdbSaveLen(rio *rdb, uint64_t len); int rdbSaveLen(rio *rdb, uint64_t len);
int rdbSaveMillisecondTime(rio *rdb, long long t);
long long rdbLoadMillisecondTime(rio *rdb, int rdbver);
uint64_t rdbLoadLen(rio *rdb, int *isencoded); uint64_t rdbLoadLen(rio *rdb, int *isencoded);
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr); int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
int rdbSaveObjectType(rio *rdb, robj *o); int rdbSaveObjectType(rio *rdb, robj *o);
......
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
void createSharedObjects(void); void createSharedObjects(void);
void rdbLoadProgressCallback(rio *r, const void *buf, size_t len); void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
long long rdbLoadMillisecondTime(rio *rdb);
int rdbCheckMode = 0; int rdbCheckMode = 0;
struct { struct {
...@@ -224,7 +223,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) { ...@@ -224,7 +223,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
/* EXPIRETIME_MS: milliseconds precision expire times introduced /* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */ * with RDB v3. Like EXPIRETIME but no with more precision. */
rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE; rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr; if ((expiretime = rdbLoadMillisecondTime(&rdb, rdbver)) == -1) goto eoferr;
continue; /* Read next opcode. */ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_FREQ) { } else if (type == RDB_OPCODE_FREQ) {
/* FREQ: LFU frequency. */ /* FREQ: LFU frequency. */
......
...@@ -220,6 +220,7 @@ static struct config { ...@@ -220,6 +220,7 @@ static struct config {
int last_cmd_type; int last_cmd_type;
int verbose; int verbose;
clusterManagerCommand cluster_manager_command; clusterManagerCommand cluster_manager_command;
int no_auth_warning;
} config; } config;
/* User preferences. */ /* User preferences. */
...@@ -258,20 +259,25 @@ static long long mstime(void) { ...@@ -258,20 +259,25 @@ static long long mstime(void) {
} }
static void cliRefreshPrompt(void) { static void cliRefreshPrompt(void) {
int len;
if (config.eval_ldb) return; if (config.eval_ldb) return;
if (config.hostsocket != NULL)
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s", sds prompt = sdsempty();
config.hostsocket); if (config.hostsocket != NULL) {
else prompt = sdscatfmt(prompt,"redis %s",config.hostsocket);
len = anetFormatAddr(config.prompt, sizeof(config.prompt), } else {
config.hostip, config.hostport); char addr[256];
anetFormatAddr(addr, sizeof(addr), config.hostip, config.hostport);
prompt = sdscatlen(prompt,addr,strlen(addr));
}
/* Add [dbnum] if needed */ /* Add [dbnum] if needed */
if (config.dbnum != 0) if (config.dbnum != 0)
len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]", prompt = sdscatfmt(prompt,"[%i]",config.dbnum);
config.dbnum);
snprintf(config.prompt+len,sizeof(config.prompt)-len,"> "); /* Copy the prompt in the static buffer. */
prompt = sdscatlen(prompt,"> ",2);
snprintf(config.prompt,sizeof(config.prompt),"%s",prompt);
sdsfree(prompt);
} }
/* Return the name of the dotfile for the specified 'dotfilename'. /* Return the name of the dotfile for the specified 'dotfilename'.
...@@ -1075,13 +1081,15 @@ static int cliSendCommand(int argc, char **argv, long repeat) { ...@@ -1075,13 +1081,15 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
if (!strcasecmp(command,"info") || if (!strcasecmp(command,"info") ||
(argc >= 2 && !strcasecmp(command,"debug") && (argc >= 2 && !strcasecmp(command,"debug") &&
!strcasecmp(argv[1],"htstats")) || !strcasecmp(argv[1],"htstats")) ||
(argc >= 2 && !strcasecmp(command,"debug") &&
!strcasecmp(argv[1],"htstats-key")) ||
(argc >= 2 && !strcasecmp(command,"memory") && (argc >= 2 && !strcasecmp(command,"memory") &&
(!strcasecmp(argv[1],"malloc-stats") || (!strcasecmp(argv[1],"malloc-stats") ||
!strcasecmp(argv[1],"doctor"))) || !strcasecmp(argv[1],"doctor"))) ||
(argc == 2 && !strcasecmp(command,"cluster") && (argc == 2 && !strcasecmp(command,"cluster") &&
(!strcasecmp(argv[1],"nodes") || (!strcasecmp(argv[1],"nodes") ||
!strcasecmp(argv[1],"info"))) || !strcasecmp(argv[1],"info"))) ||
(argc == 2 && !strcasecmp(command,"client") && (argc >= 2 && !strcasecmp(command,"client") &&
!strcasecmp(argv[1],"list")) || !strcasecmp(argv[1],"list")) ||
(argc == 3 && !strcasecmp(command,"latency") && (argc == 3 && !strcasecmp(command,"latency") &&
!strcasecmp(argv[1],"graph")) || !strcasecmp(argv[1],"graph")) ||
...@@ -1228,8 +1236,9 @@ static int parseOptions(int argc, char **argv) { ...@@ -1228,8 +1236,9 @@ static int parseOptions(int argc, char **argv) {
config.interval = seconds*1000000; config.interval = seconds*1000000;
} else if (!strcmp(argv[i],"-n") && !lastarg) { } else if (!strcmp(argv[i],"-n") && !lastarg) {
config.dbnum = atoi(argv[++i]); config.dbnum = atoi(argv[++i]);
} else if (!strcmp(argv[i], "--no-auth-warning")) {
config.no_auth_warning = 1;
} else if (!strcmp(argv[i],"-a") && !lastarg) { } else if (!strcmp(argv[i],"-a") && !lastarg) {
fputs("Warning: Using a password with '-a' option on the command line interface may not be safe.\n", stderr);
config.auth = argv[++i]; config.auth = argv[++i];
} else if (!strcmp(argv[i],"-u") && !lastarg) { } else if (!strcmp(argv[i],"-u") && !lastarg) {
parseRedisUri(argv[++i]); parseRedisUri(argv[++i]);
...@@ -1380,6 +1389,12 @@ static int parseOptions(int argc, char **argv) { ...@@ -1380,6 +1389,12 @@ static int parseOptions(int argc, char **argv) {
fprintf(stderr,"Try %s --help for more information.\n", argv[0]); fprintf(stderr,"Try %s --help for more information.\n", argv[0]);
exit(1); exit(1);
} }
if (!config.no_auth_warning && config.auth != NULL) {
fputs("Warning: Using a password with '-a' or '-u' option on the command"
" line interface may not be safe.\n", stderr);
}
return i; return i;
} }
...@@ -1456,9 +1471,14 @@ static void usage(void) { ...@@ -1456,9 +1471,14 @@ static void usage(void) {
" --cluster <command> [args...] [opts...]\n" " --cluster <command> [args...] [opts...]\n"
" Cluster Manager command and arguments (see below).\n" " Cluster Manager command and arguments (see below).\n"
" --verbose Verbose mode.\n" " --verbose Verbose mode.\n"
" --no-auth-warning Don't show warning message when using password on command\n"
" line interface.\n"
" --help Output this help and exit.\n" " --help Output this help and exit.\n"
" --version Output version and exit.\n" " --version Output version and exit.\n"
"\n" "\n",
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
/* Using another fprintf call to avoid -Woverlength-strings compile warning */
fprintf(stderr,
"Cluster Manager Commands:\n" "Cluster Manager Commands:\n"
" Use --cluster help to list all available cluster manager commands.\n" " Use --cluster help to list all available cluster manager commands.\n"
"\n" "\n"
...@@ -1475,8 +1495,7 @@ static void usage(void) { ...@@ -1475,8 +1495,7 @@ static void usage(void) {
"When no command is given, redis-cli starts in interactive mode.\n" "When no command is given, redis-cli starts in interactive mode.\n"
"Type \"help\" in interactive mode for information on available commands\n" "Type \"help\" in interactive mode for information on available commands\n"
"and settings.\n" "and settings.\n"
"\n", "\n");
version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
sdsfree(version); sdsfree(version);
exit(1); exit(1);
} }
...@@ -3088,7 +3107,7 @@ static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts, ...@@ -3088,7 +3107,7 @@ static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts,
currentNode->flags |= CLUSTER_MANAGER_FLAG_FAIL; currentNode->flags |= CLUSTER_MANAGER_FLAG_FAIL;
else if (strcmp(flag, "slave") == 0) { else if (strcmp(flag, "slave") == 0) {
currentNode->flags |= CLUSTER_MANAGER_FLAG_SLAVE; currentNode->flags |= CLUSTER_MANAGER_FLAG_SLAVE;
if (master_id == 0) { if (master_id != NULL) {
if (currentNode->replicate) sdsfree(currentNode->replicate); if (currentNode->replicate) sdsfree(currentNode->replicate);
currentNode->replicate = sdsnew(master_id); currentNode->replicate = sdsnew(master_id);
} }
...@@ -4887,7 +4906,7 @@ static int clusterManagerCommandRebalance(int argc, char **argv) { ...@@ -4887,7 +4906,7 @@ static int clusterManagerCommandRebalance(int argc, char **argv) {
listRewind(involved, &li); listRewind(involved, &li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
clusterManagerNode *n = ln->value; clusterManagerNode *n = ln->value;
if (n->balance < 0 && total_balance > 0) { if (n->balance <= 0 && total_balance > 0) {
n->balance--; n->balance--;
total_balance--; total_balance--;
} }
...@@ -5091,7 +5110,7 @@ static int clusterManagerCommandImport(int argc, char **argv) { ...@@ -5091,7 +5110,7 @@ static int clusterManagerCommandImport(int argc, char **argv) {
// Build a slot -> node map // Build a slot -> node map
clusterManagerNode *slots_map[CLUSTER_MANAGER_SLOTS]; clusterManagerNode *slots_map[CLUSTER_MANAGER_SLOTS];
memset(slots_map, 0, sizeof(slots_map) / sizeof(clusterManagerNode *)); memset(slots_map, 0, sizeof(slots_map));
listIter li; listIter li;
listNode *ln; listNode *ln;
for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) { for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
...@@ -5575,7 +5594,7 @@ static void getRDB(void) { ...@@ -5575,7 +5594,7 @@ static void getRDB(void) {
nwritten = write(fd, buf, nread); nwritten = write(fd, buf, nread);
if (nwritten != nread) { if (nwritten != nread) {
fprintf(stderr,"Error writing data to file: %s\n", fprintf(stderr,"Error writing data to file: %s\n",
strerror(errno)); (nwritten == -1) ? strerror(errno) : "short write");
exit(1); exit(1);
} }
payload -= nread; payload -= nread;
...@@ -6544,6 +6563,7 @@ int main(int argc, char **argv) { ...@@ -6544,6 +6563,7 @@ int main(int argc, char **argv) {
config.enable_ldb_on_eval = 0; config.enable_ldb_on_eval = 0;
config.last_cmd_type = -1; config.last_cmd_type = -1;
config.verbose = 0; config.verbose = 0;
config.no_auth_warning = 0;
config.cluster_manager_command.name = NULL; config.cluster_manager_command.name = NULL;
config.cluster_manager_command.argc = 0; config.cluster_manager_command.argc = 0;
config.cluster_manager_command.argv = NULL; config.cluster_manager_command.argv = NULL;
......
...@@ -1105,7 +1105,7 @@ void restartAOF() { ...@@ -1105,7 +1105,7 @@ void restartAOF() {
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */ #define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
char buf[4096]; char buf[4096];
ssize_t nread, readlen; ssize_t nread, readlen, nwritten;
off_t left; off_t left;
UNUSED(el); UNUSED(el);
UNUSED(privdata); UNUSED(privdata);
...@@ -1206,8 +1206,9 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -1206,8 +1206,9 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
} }
server.repl_transfer_lastio = server.unixtime; server.repl_transfer_lastio = server.unixtime;
if (write(server.repl_transfer_fd,buf,nread) != nread) { if ((nwritten = write(server.repl_transfer_fd,buf,nread)) != nread) {
serverLog(LL_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s", strerror(errno)); serverLog(LL_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s",
(nwritten == -1) ? strerror(errno) : "short write");
goto error; goto error;
} }
server.repl_transfer_read += nread; server.repl_transfer_read += nread;
...@@ -1314,24 +1315,31 @@ error: ...@@ -1314,24 +1315,31 @@ error:
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE) #define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
char *sendSynchronousCommand(int flags, int fd, ...) { char *sendSynchronousCommand(int flags, int fd, ...) {
/* Create the command to send to the master, we use simple inline /* Create the command to send to the master, we use redis binary
* protocol for simplicity as currently we only send simple strings. */ * protocol to make sure correct arguments are sent. This function
* is not safe for all binary data. */
if (flags & SYNC_CMD_WRITE) { if (flags & SYNC_CMD_WRITE) {
char *arg; char *arg;
va_list ap; va_list ap;
sds cmd = sdsempty(); sds cmd = sdsempty();
sds cmdargs = sdsempty();
size_t argslen = 0;
va_start(ap,fd); va_start(ap,fd);
while(1) { while(1) {
arg = va_arg(ap, char*); arg = va_arg(ap, char*);
if (arg == NULL) break; if (arg == NULL) break;
if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1); cmdargs = sdscatprintf(cmdargs,"$%zu\r\n%s\r\n",strlen(arg),arg);
cmd = sdscat(cmd,arg); argslen++;
} }
cmd = sdscatlen(cmd,"\r\n",2);
va_end(ap); va_end(ap);
cmd = sdscatprintf(cmd,"*%zu\r\n",argslen);
cmd = sdscatsds(cmd,cmdargs);
sdsfree(cmdargs);
/* Transfer command to the server. */ /* Transfer command to the server. */
if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000) if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
== -1) == -1)
......
...@@ -116,7 +116,7 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) { ...@@ -116,7 +116,7 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
r->io.file.buffered >= r->io.file.autosync) r->io.file.buffered >= r->io.file.autosync)
{ {
fflush(r->io.file.fp); fflush(r->io.file.fp);
aof_fsync(fileno(r->io.file.fp)); redis_fsync(fileno(r->io.file.fp));
r->io.file.buffered = 0; r->io.file.buffered = 0;
} }
return retval; return retval;
......
...@@ -1457,11 +1457,11 @@ void evalShaCommand(client *c) { ...@@ -1457,11 +1457,11 @@ void evalShaCommand(client *c) {
void scriptCommand(client *c) { void scriptCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"debug (yes|sync|no) -- Set the debug mode for subsequent scripts executed.", "DEBUG (yes|sync|no) -- Set the debug mode for subsequent scripts executed.",
"exists <sha1> [<sha1> ...] -- Return information about the existence of the scripts in the script cache.", "EXISTS <sha1> [<sha1> ...] -- Return information about the existence of the scripts in the script cache.",
"flush -- Flush the Lua scripts cache. Very dangerous on slaves.", "FLUSH -- Flush the Lua scripts cache. Very dangerous on slaves.",
"kill -- Kill the currently executing Lua script.", "KILL -- Kill the currently executing Lua script.",
"load <script> -- Load a script into the scripts cache, without executing it.", "LOAD <script> -- Load a script into the scripts cache, without executing it.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
...@@ -1514,7 +1514,7 @@ NULL ...@@ -1514,7 +1514,7 @@ NULL
return; return;
} }
} else { } else {
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try SCRIPT HELP", (char*)c->argv[1]->ptr); addReplySubcommandSyntaxError(c);
} }
} }
......
This diff is collapsed.
...@@ -198,8 +198,8 @@ struct redisCommand redisCommandTable[] = { ...@@ -198,8 +198,8 @@ struct redisCommand redisCommandTable[] = {
{"zrank",zrankCommand,3,"rF",0,NULL,1,1,1,0,0}, {"zrank",zrankCommand,3,"rF",0,NULL,1,1,1,0,0},
{"zrevrank",zrevrankCommand,3,"rF",0,NULL,1,1,1,0,0}, {"zrevrank",zrevrankCommand,3,"rF",0,NULL,1,1,1,0,0},
{"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0}, {"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
{"zpopmin",zpopminCommand,-2,"wF",0,NULL,1,-1,1,0,0}, {"zpopmin",zpopminCommand,-2,"wF",0,NULL,1,1,1,0,0},
{"zpopmax",zpopmaxCommand,-2,"wF",0,NULL,1,-1,1,0,0}, {"zpopmax",zpopmaxCommand,-2,"wF",0,NULL,1,1,1,0,0},
{"bzpopmin",bzpopminCommand,-2,"wsF",0,NULL,1,-2,1,0,0}, {"bzpopmin",bzpopminCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
{"bzpopmax",bzpopmaxCommand,-2,"wsF",0,NULL,1,-2,1,0,0}, {"bzpopmax",bzpopmaxCommand,-2,"wsF",0,NULL,1,-2,1,0,0},
{"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0}, {"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
...@@ -845,13 +845,14 @@ int clientsCronResizeQueryBuffer(client *c) { ...@@ -845,13 +845,14 @@ int clientsCronResizeQueryBuffer(client *c) {
/* There are two conditions to resize the query buffer: /* There are two conditions to resize the query buffer:
* 1) Query buffer is > BIG_ARG and too big for latest peak. * 1) Query buffer is > BIG_ARG and too big for latest peak.
* 2) Client is inactive and the buffer is bigger than 1k. */ * 2) Query buffer is > BIG_ARG and client is idle. */
if (((querybuf_size > PROTO_MBULK_BIG_ARG) && if (querybuf_size > PROTO_MBULK_BIG_ARG &&
(querybuf_size/(c->querybuf_peak+1)) > 2) || ((querybuf_size/(c->querybuf_peak+1)) > 2 ||
(querybuf_size > 1024 && idletime > 2)) idletime > 2))
{ {
/* Only resize the query buffer if it is actually wasting space. */ /* Only resize the query buffer if it is actually wasting
if (sdsavail(c->querybuf) > 1024) { * at least a few kbytes. */
if (sdsavail(c->querybuf) > 1024*4) {
c->querybuf = sdsRemoveFreeSpace(c->querybuf); c->querybuf = sdsRemoveFreeSpace(c->querybuf);
} }
} }
...@@ -1467,6 +1468,7 @@ void initServerConfig(void) { ...@@ -1467,6 +1468,7 @@ void initServerConfig(void) {
server.aof_selected_db = -1; /* Make sure the first time will not match */ server.aof_selected_db = -1; /* Make sure the first time will not match */
server.aof_flush_postponed_start = 0; server.aof_flush_postponed_start = 0;
server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC; server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC;
server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED; server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED;
server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE; server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE;
server.pidfile = NULL; server.pidfile = NULL;
...@@ -1496,6 +1498,8 @@ void initServerConfig(void) { ...@@ -1496,6 +1498,8 @@ void initServerConfig(void) {
server.zset_max_ziplist_entries = OBJ_ZSET_MAX_ZIPLIST_ENTRIES; server.zset_max_ziplist_entries = OBJ_ZSET_MAX_ZIPLIST_ENTRIES;
server.zset_max_ziplist_value = OBJ_ZSET_MAX_ZIPLIST_VALUE; server.zset_max_ziplist_value = OBJ_ZSET_MAX_ZIPLIST_VALUE;
server.hll_sparse_max_bytes = CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES; server.hll_sparse_max_bytes = CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES;
server.stream_node_max_bytes = OBJ_STREAM_NODE_MAX_BYTES;
server.stream_node_max_entries = OBJ_STREAM_NODE_MAX_ENTRIES;
server.shutdown_asap = 0; server.shutdown_asap = 0;
server.cluster_enabled = 0; server.cluster_enabled = 0;
server.cluster_node_timeout = CLUSTER_DEFAULT_NODE_TIMEOUT; server.cluster_node_timeout = CLUSTER_DEFAULT_NODE_TIMEOUT;
...@@ -1897,6 +1901,7 @@ void initServer(void) { ...@@ -1897,6 +1901,7 @@ void initServer(void) {
server.pid = getpid(); server.pid = getpid();
server.current_client = NULL; server.current_client = NULL;
server.clients = listCreate(); server.clients = listCreate();
server.clients_index = raxNew();
server.clients_to_close = listCreate(); server.clients_to_close = listCreate();
server.slaves = listCreate(); server.slaves = listCreate();
server.monitors = listCreate(); server.monitors = listCreate();
...@@ -2646,7 +2651,7 @@ int prepareForShutdown(int flags) { ...@@ -2646,7 +2651,7 @@ int prepareForShutdown(int flags) {
/* Append only file: flush buffers and fsync() the AOF at exit */ /* Append only file: flush buffers and fsync() the AOF at exit */
serverLog(LL_NOTICE,"Calling fsync() on the AOF file."); serverLog(LL_NOTICE,"Calling fsync() on the AOF file.");
flushAppendOnlyFile(1); flushAppendOnlyFile(1);
aof_fsync(server.aof_fd); redis_fsync(server.aof_fd);
} }
/* Create a new RDB file before exiting. */ /* Create a new RDB file before exiting. */
...@@ -2835,9 +2840,9 @@ void commandCommand(client *c) { ...@@ -2835,9 +2840,9 @@ void commandCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) { if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = { const char *help[] = {
"(no subcommand) -- Return details about all Redis commands.", "(no subcommand) -- Return details about all Redis commands.",
"count -- Return the total number of commands in this Redis server.", "COUNT -- Return the total number of commands in this Redis server.",
"getkeys <full-command> -- Return the keys from a full Redis command.", "GETKEYS <full-command> -- Return the keys from a full Redis command.",
"info [command-name ...] -- Return details about multiple Redis commands.", "INFO [command-name ...] -- Return details about multiple Redis commands.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
...@@ -2861,7 +2866,10 @@ NULL ...@@ -2861,7 +2866,10 @@ NULL
int *keys, numkeys, j; int *keys, numkeys, j;
if (!cmd) { if (!cmd) {
addReplyErrorFormat(c,"Invalid command specified"); addReplyError(c,"Invalid command specified");
return;
} else if (cmd->getkeys_proc == NULL && cmd->firstkey == 0) {
addReplyError(c,"The command has no key arguments");
return; return;
} else if ((cmd->arity > 0 && cmd->arity != c->argc-2) || } else if ((cmd->arity > 0 && cmd->arity != c->argc-2) ||
((c->argc-2) < -cmd->arity)) ((c->argc-2) < -cmd->arity))
...@@ -2871,11 +2879,15 @@ NULL ...@@ -2871,11 +2879,15 @@ NULL
} }
keys = getKeysFromCommand(cmd,c->argv+2,c->argc-2,&numkeys); keys = getKeysFromCommand(cmd,c->argv+2,c->argc-2,&numkeys);
if (!keys) {
addReplyError(c,"Invalid arguments specified for command");
} else {
addReplyMultiBulkLen(c,numkeys); addReplyMultiBulkLen(c,numkeys);
for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]); for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]);
getKeysFreeResult(keys); getKeysFreeResult(keys);
}
} else { } else {
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try COMMAND HELP", (char*)c->argv[1]->ptr); addReplySubcommandSyntaxError(c);
} }
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment