From c7bc8d7e77fcbff37dc0b08c51eb954f142f5670 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Mon, 7 Jan 2013 15:05:07 +0800 Subject: [PATCH 01/93] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=B8=89=E4=B8=AA?= =?UTF-8?q?=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rl_hget rl_hset rl_hdel --- src/ds.c | 1806 +++++++++--------- src/redis.c | 5293 ++++++++++++++++++++++++++------------------------- src/redis.h | 2513 ++++++++++++------------ 3 files changed, 4822 insertions(+), 4790 deletions(-) diff --git a/src/ds.c b/src/ds.c index 1b381b5..d76674e 100644 --- a/src/ds.c +++ b/src/ds.c @@ -1,890 +1,916 @@ -#include "redis.h" - -/* -static char *urlencode(char const *s, int len, int *new_length) -{ - #define safe_emalloc(nmemb, size, offset) zmalloc((nmemb) * (size) + (offset)) - static unsigned char hexchars[] = "0123456789ABCDEF"; - register unsigned char c; - unsigned char *to, *start; - unsigned char const *from, *end; - - from = (unsigned char *)s; - end = (unsigned char *)s + len; - start = to = (unsigned char *) safe_emalloc(3, len, 1); - - while (from < end) { - c = *from++; - - if (c == ' ') { - *to++ = '+'; -#ifndef CHARSET_EBCDIC - } else if ((c < '0' && c != '-' && c != '.') || - (c < 'A' && c > '9') || - (c > 'Z' && c < 'a' && c != '_') || - (c > 'z')) { - to[0] = '%'; - to[1] = hexchars[c >> 4]; - to[2] = hexchars[c & 15]; - to += 3; -#else //CHARSET_EBCDIC - } else if (!isalnum(c) && strchr("_-.", c) == NULL) { - // Allow only alphanumeric chars and '_', '-', '.'; escape the rest - to[0] = '%'; - to[1] = hexchars[os_toascii[c] >> 4]; - to[2] = hexchars[os_toascii[c] & 15]; - to += 3; -#endif //CHARSET_EBCDIC - } else { - *to++ = c; - } - } - *to = 0; - if (new_length) { - *new_length = to - start; - } - return (char *) start; -} -*/ - -void ds_init() -{ - char *err = NULL; - - server.ds_cache = leveldb_cache_create_lru(server.ds_lru_cache); - server.ds_options = leveldb_options_create(); - - server.policy = leveldb_filterpolicy_create_bloom(10); - - - //leveldb_options_set_comparator(server.ds_options, cmp); - leveldb_options_set_filter_policy(server.ds_options, server.policy); - leveldb_options_set_create_if_missing(server.ds_options, server.ds_create_if_missing); - leveldb_options_set_error_if_exists(server.ds_options, server.ds_error_if_exists); - leveldb_options_set_cache(server.ds_options, server.ds_cache); - leveldb_options_set_info_log(server.ds_options, NULL); - leveldb_options_set_write_buffer_size(server.ds_options, server.ds_write_buffer_size); - leveldb_options_set_paranoid_checks(server.ds_options, server.ds_paranoid_checks); - leveldb_options_set_max_open_files(server.ds_options, server.ds_max_open_files); - leveldb_options_set_block_size(server.ds_options, server.ds_block_cache_size); - leveldb_options_set_block_restart_interval(server.ds_options, server.ds_block_restart_interval); - leveldb_options_set_compression(server.ds_options, leveldb_snappy_compression); - - server.ds_db = leveldb_open(server.ds_options, server.ds_path, &err); - if (err != NULL) - { - fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__,err); - leveldb_free(err); - exit(1); - } -} - -void ds_mget(redisClient *c) -{ - int i, len, pos; - size_t val_len; - char *err, *value; - - leveldb_readoptions_t *roptions; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - - addReplyMultiBulkLen(c,c->argc-1); - for(i=1; iargc; i++) - { - err = NULL; - value = NULL; - val_len = pos = 0; - len = strlen((char *)c->argv[i]->ptr); - value = leveldb_get(server.ds_db, roptions, (char *)c->argv[i]->ptr, len, &val_len, &err); - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - leveldb_free(value); - leveldb_readoptions_destroy(roptions); - return ; - } - else if(val_len > 0) - { - addReplyBulkCBuffer(c, value, val_len); - leveldb_free(value); - value = NULL; - } - else - { - addReply(c,shared.nullbulk); - - } - } - leveldb_readoptions_destroy(roptions); -} - -void ds_get(redisClient *c) -{ - - bool is_int; - int64_t recore; - char *err = NULL; - size_t val_len, i; - char *key = NULL; - char *value = NULL; - - leveldb_readoptions_t *roptions; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - - key = (char *)c->argv[1]->ptr; - value = leveldb_get(server.ds_db, roptions, key, strlen(key), &val_len, &err); - leveldb_readoptions_destroy(roptions); - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - leveldb_free(value); - - return ; - } - else if(value == NULL) - { - addReply(c,shared.nullbulk); - return ; - } - - is_int = true; - for(i=0; value[i]!=0; i++) - { - is_int = isgraph(value[i]) ? false : true; - } - - if(is_int) - { - recore = *(int64_t *)value; - addReplyLongLong(c, recore); - } - else - { - addReplyBulkCBuffer(c, value, val_len); - } - leveldb_free(value); -} -void rl_get(redisClient *c) -{ - //从redis里取数据 - robj *o; - - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) != NULL) { - - if (o->type == REDIS_STRING) { - addReplyBulk(c,o); - return; - } - } - - ds_get(c); -} - - - -void ds_mset(redisClient *c) -{ - int i; - char *key, *value; - char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_writebatch_t *wb; - - if((c->argc%2) == 0) - { - addReply(c,shared.nullbulk); - return ; - } - - - woptions = leveldb_writeoptions_create(); - wb = leveldb_writebatch_create(); - for(i=1; iargc; i++) - { - key = (char *)c->argv[i]->ptr; - value = (char *)c->argv[++i]->ptr; - leveldb_writebatch_put(wb, key, strlen(key), value, strlen(value)); - } - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_writeoptions_destroy(woptions); - leveldb_writebatch_destroy(wb); - - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - return ; - } - addReply(c,shared.ok); - - return ; -} - - -void ds_hincrby(redisClient *c) -{ - int64_t val, recore; - sds keyword; - char *value; - - size_t val_len; - char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_readoptions_t *roptions; - - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - - err = NULL; - val_len = 0; - keyword = sdsempty(); - keyword = sdscpy(keyword, c->argv[1]->ptr); - keyword = sdscatlen(keyword, "*", 1); - keyword = sdscat(keyword, c->argv[2]->ptr); - - value = leveldb_get(server.ds_db, roptions, keyword, sdslen(keyword), &val_len, &err); - leveldb_readoptions_destroy(roptions); - - if(err != NULL) - { - sdsfree(keyword); - leveldb_free(err); - if(val_len > 0) leveldb_free(value); - addReplyError(c, err); - return ; - } - else if(val_len < 1) - { - val = 0; - } - else - { - val = *(int64_t *)value; - } - - err = NULL; - recore = strtoll(c->argv[3]->ptr, NULL, 10); - recore = val + recore; - woptions = leveldb_writeoptions_create(); - - leveldb_put(server.ds_db, woptions, keyword, sdslen(keyword), (char *)&recore, sizeof(int64_t), &err); - leveldb_writeoptions_destroy(woptions); - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - } - else - { - addReplyLongLong(c, recore); - } - leveldb_free(value); - sdsfree(keyword); - return ; -} - -void ds_hmget(redisClient *c) -{ - int i; - sds keyword; - size_t val_len; - char *key, *err = NULL, *value = NULL; - - leveldb_readoptions_t *roptions; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - - addReplyMultiBulkLen(c, c->argc-2); - - key = (char *)c->argv[1]->ptr; - keyword = sdsempty(); - - for(i=2; iargc; i++) - { - err = NULL; - value = NULL; - val_len = 0; - - sdsclear(keyword); - keyword = sdscat(keyword, key); - keyword = sdscatlen(keyword, "*", 1); - keyword = sdscat(keyword, c->argv[i]->ptr); - - value = leveldb_get(server.ds_db, roptions, keyword, sdslen(keyword), &val_len, &err); - if(err != NULL) - { - sdsfree(keyword); - leveldb_free(err); - leveldb_free(value); - addReplyError(c, err); - leveldb_readoptions_destroy(roptions); - return ; - } - else if(val_len > 0) - { - addReplyBulkCBuffer(c, value, val_len); - leveldb_free(value); - value = NULL; - } - else - { - addReply(c,shared.nullbulk); - - } - } - - sdsfree(keyword); - leveldb_readoptions_destroy(roptions); -} - -void ds_hmset(redisClient *c) -{ - int i; - sds keyword; - char *key, *field, *value; - char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_writebatch_t *wb; - - if((c->argc%2) != 0) - { - addReply(c,shared.nullbulk); - return ; - } - - keyword = sdsempty(); - woptions = leveldb_writeoptions_create(); - wb = leveldb_writebatch_create(); - key = (char *)c->argv[1]->ptr; - - keyword = sdscat(keyword, key); - keyword = sdscatlen(keyword, "*", 1); - leveldb_writebatch_put(wb, keyword, sdslen(keyword), "1", 1); - for(i=2; iargc; i++) - { - field = (char *)c->argv[i]->ptr; - value = (char *)c->argv[++i]->ptr; - - sdsclear(keyword); - keyword = sdscat(keyword, key); - keyword = sdscatlen(keyword, "*", 1); - keyword = sdscat(keyword, field); - leveldb_writebatch_put(wb, keyword, sdslen(keyword), value, strlen(value)); - } - sdsfree(keyword); - - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_writeoptions_destroy(woptions); - leveldb_writebatch_destroy(wb); - - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - return ; - } - addReply(c,shared.ok); - - return ; -} - -void ds_hset(redisClient *c) -{ - sds str; - char *key, *field, *value, *err; - leveldb_writeoptions_t *woptions; - leveldb_writebatch_t *wb; - - key = (char *)c->argv[1]->ptr; - field = (char *)c->argv[2]->ptr; - value = (char *)c->argv[3]->ptr; - - woptions = leveldb_writeoptions_create(); - wb = leveldb_writebatch_create(); - - str = sdsempty(); - str = sdscpy(str, key); - str = sdscatlen(str, "*", 1); - - leveldb_writebatch_put(wb, str, sdslen(str), "1", 1); - - sdsclear(str); - str = sdscpy(str, key); - str = sdscatlen(str, "*", 1); - str = sdscat(str, field); - leveldb_writebatch_put(wb, str, sdslen(str), value, strlen(value)); - - leveldb_write(server.ds_db, woptions, wb, &err); - - leveldb_writeoptions_destroy(woptions); - leveldb_writebatch_destroy(wb); - sdsfree(str); - - addReply(c,shared.ok); - return ; -} - -void ds_hgetall(redisClient *c) -{ - sds str, header; - char *keyword = NULL; - - const char *key, *value; - size_t key_len, value_len, len, i; - - leveldb_iterator_t *iter; - leveldb_readoptions_t *roptions; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - - i = 0; - str = sdsempty(); - iter = leveldb_create_iterator(server.ds_db, roptions); - - str = sdscpy(str, c->argv[1]->ptr); - str = sdscatlen(str, "*", 1); - len = sdslen(str); - keyword = zmalloc(len+1); - memcpy(keyword, str, len); - - leveldb_iter_seek(iter, keyword, len); - - if(!leveldb_iter_valid(iter)) - { - sdsfree(str); - zfree(keyword); - addReply(c,shared.nullbulk); - leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - return ; - } - - sdsclear(str); - leveldb_iter_next(iter); - while(1) - { - if(!leveldb_iter_valid(iter)) - break; - - key_len = value_len = 0; - key = leveldb_iter_key(iter, &key_len); - value = leveldb_iter_value(iter, &value_len); - - if(strncmp(keyword, key, len) != 0) - break; - - str = sdscatprintf(str, "$%lu\r\n", key_len-len); - str = sdscatlen(str, key+len, key_len-len); - str = sdscatprintf(str, "\r\n$%lu\r\n", value_len); - str = sdscatlen(str, value, value_len); - str = sdscatlen(str, "\r\n", 2); - - i++; - leveldb_iter_next(iter); - } - - if(i == 0) - { - addReply(c,shared.nullbulk); - } - else - { - header = sdsempty(); - header = sdscatprintf(header, "*%lu\r\n", i*2); - header = sdscatlen(header, str, sdslen(str)); - addReplySds(c, header); - sdsfree(header); - } - - sdsfree(str); - zfree(keyword); - leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - return ; -} - - -void ds_hdel(redisClient *c) -{ - const char *key; - size_t key_len, i; - - sds keyword; - char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_writebatch_t *wb; - - leveldb_iterator_t *iter; - leveldb_readoptions_t *roptions; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - - iter = leveldb_create_iterator(server.ds_db, roptions); - - keyword = sdsempty(); - woptions = leveldb_writeoptions_create(); - - if(c->argc < 3) - { - keyword = sdscpy(keyword, c->argv[1]->ptr); - keyword = sdscatlen(keyword, "*", 1); - - leveldb_iter_seek(iter, keyword, sdslen(keyword)); - if(!leveldb_iter_valid(iter)) - { - sdsfree(keyword); - addReply(c,shared.nullbulk); - leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - leveldb_writeoptions_destroy(woptions); - return ; - } - - wb = leveldb_writebatch_create(); - while(1) - { - key_len = 0; - key = leveldb_iter_key(iter, &key_len); - - if(strncmp(keyword, key, sdslen(keyword)) != 0) - break; - - //printf("key = %s\r\n", key); - leveldb_writebatch_delete(wb, key, key_len); - leveldb_iter_next(iter); - if(!leveldb_iter_valid(iter)) - break; - } - - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_writeoptions_destroy(woptions); - leveldb_writebatch_destroy(wb); - - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - - sdsfree(keyword); - leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - leveldb_writeoptions_destroy(woptions); - return ; - } - addReply(c,shared.ok); - - sdsfree(keyword); - leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - leveldb_writeoptions_destroy(woptions); - - return ; - } - - wb = leveldb_writebatch_create(); - for(i=2; iargc; i++) - { - sdsclear(keyword); - keyword = sdscpy(keyword, c->argv[1]->ptr); - keyword = sdscatlen(keyword, "*", 1); - keyword = sdscat(keyword, c->argv[i]->ptr); - leveldb_writebatch_delete(wb, keyword, sdslen(keyword)); - } - - sdsfree(keyword); - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_readoptions_destroy(roptions); - leveldb_writeoptions_destroy(woptions); - leveldb_writebatch_destroy(wb); - - - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - return ; - } - addReply(c,shared.ok); - - return ; -} - -void ds_hget(redisClient *c) -{ - sds str; - size_t val_len = 0; - char *key = NULL, *field = NULL, *value = NULL, *err = NULL; - - leveldb_readoptions_t *roptions; - - key = (char *)c->argv[1]->ptr; - field = (char *)c->argv[2]->ptr; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - - str = sdsnew(key); - str = sdscatlen(str, "*", 1); - str = sdscat(str, field); - value = leveldb_get(server.ds_db, roptions, str, sdslen(str), &val_len, &err); - leveldb_readoptions_destroy(roptions); - - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - if(val_len > 0) leveldb_free(value); - - return ; - } - else if(value == NULL) - { - addReply(c,shared.nullbulk); - return ; - } - sdsfree(str); - - addReplyBulkCBuffer(c, value, val_len); - leveldb_free(value); -} - - -void ds_incrby(redisClient *c) -{ - char *value; - int64_t val, recore; - - size_t val_len; - char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_readoptions_t *roptions; - - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - - err = NULL; - val_len = 0; - - value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), &val_len, &err); - leveldb_readoptions_destroy(roptions); - - if(err != NULL) - { - leveldb_free(err); - if(val_len > 0) leveldb_free(value); - addReplyError(c, err); - return ; - } - else if(val_len < 1) - { - val = 0; - } - else - { - val = *(int64_t *)value; - } - - err = NULL; - recore = strtoll(c->argv[2]->ptr, NULL, 10); - recore = val + recore; - woptions = leveldb_writeoptions_create(); - - leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), (char *)&recore, sizeof(int64_t), &err); - leveldb_writeoptions_destroy(woptions); - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - } - else - { - addReplyLongLong(c, recore); - } - leveldb_free(value); - return ; -} - - -void ds_append(redisClient *c) -{ - sds recore; - char *value; - - size_t val_len; - char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_readoptions_t *roptions; - - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - - err = NULL; - val_len = 0; - - value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), &val_len, &err); - leveldb_readoptions_destroy(roptions); - - if(err != NULL) - { - leveldb_free(err); - if(val_len > 0) leveldb_free(value); - addReplyError(c, err); - return ; - } - - - err = NULL; - recore = sdsempty(); - if(val_len > 0) - { - recore = sdscpy(recore, value); - } - recore = sdscat(recore, c->argv[1]->ptr); - woptions = leveldb_writeoptions_create(); - - leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), recore, sdslen(recore), &err); - leveldb_writeoptions_destroy(woptions); - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - } - else - { - addReply(c,shared.ok); - } - - sdsfree(recore); - leveldb_free(value); - return ; -} - -void ds_set(redisClient *c) -{ - char *key, *value; - char *err = NULL; - leveldb_writeoptions_t *woptions; - - woptions = leveldb_writeoptions_create(); - - key = (char *)c->argv[1]->ptr; - value = (char *)c->argv[2]->ptr; - leveldb_put(server.ds_db, woptions, key, strlen(key), value, strlen(value), &err); - leveldb_writeoptions_destroy(woptions); - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - return ; - } - addReply(c,shared.ok); - return ; -} - -void rl_set(redisClient *c) -{ - char *key, *value; - char *err = NULL; - leveldb_writeoptions_t *woptions; - - woptions = leveldb_writeoptions_create(); - - key = (char *)c->argv[1]->ptr; - value = (char *)c->argv[2]->ptr; - leveldb_put(server.ds_db, woptions, key, strlen(key), value, strlen(value), &err); - leveldb_writeoptions_destroy(woptions); - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - return ; - } - //addReply(c,shared.ok); - - //存到redis - setCommand(c); -} - -void ds_delete(redisClient *c) -{ - int i; - char *key; - char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_writebatch_t *wb; - - woptions = leveldb_writeoptions_create(); - - if(c->argc < 3) - { - key = (char *)c->argv[1]->ptr; - leveldb_delete(server.ds_db, woptions, key, strlen(key), &err); - leveldb_writeoptions_destroy(woptions); - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - return ; - } - addReply(c,shared.ok); - return ; - } - - wb = leveldb_writebatch_create(); - for(i=1; iargc; i++) - { - leveldb_writebatch_delete(wb, (char *)c->argv[i]->ptr, strlen((char *)c->argv[i]->ptr)); - } - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_writeoptions_destroy(woptions); - leveldb_writebatch_destroy(wb); - - if(err != NULL) - { - addReplyError(c, err); - leveldb_free(err); - return ; - } - addReply(c,shared.ok); - - return ; -} - - - -void rl_delete(redisClient *c) -{ - ds_delete(c); - delCommand(c); -} - -void ds_close() -{ - leveldb_options_set_filter_policy(server.ds_options, NULL); - leveldb_filterpolicy_destroy(server.policy); - leveldb_close(server.ds_db); - leveldb_options_destroy(server.ds_options); - leveldb_cache_destroy(server.ds_cache); -} - +#include "redis.h" + +/* +static char *urlencode(char const *s, int len, int *new_length) +{ + #define safe_emalloc(nmemb, size, offset) zmalloc((nmemb) * (size) + (offset)) + static unsigned char hexchars[] = "0123456789ABCDEF"; + register unsigned char c; + unsigned char *to, *start; + unsigned char const *from, *end; + + from = (unsigned char *)s; + end = (unsigned char *)s + len; + start = to = (unsigned char *) safe_emalloc(3, len, 1); + + while (from < end) { + c = *from++; + + if (c == ' ') { + *to++ = '+'; +#ifndef CHARSET_EBCDIC + } else if ((c < '0' && c != '-' && c != '.') || + (c < 'A' && c > '9') || + (c > 'Z' && c < 'a' && c != '_') || + (c > 'z')) { + to[0] = '%'; + to[1] = hexchars[c >> 4]; + to[2] = hexchars[c & 15]; + to += 3; +#else //CHARSET_EBCDIC + } else if (!isalnum(c) && strchr("_-.", c) == NULL) { + // Allow only alphanumeric chars and '_', '-', '.'; escape the rest + to[0] = '%'; + to[1] = hexchars[os_toascii[c] >> 4]; + to[2] = hexchars[os_toascii[c] & 15]; + to += 3; +#endif //CHARSET_EBCDIC + } else { + *to++ = c; + } + } + *to = 0; + if (new_length) { + *new_length = to - start; + } + return (char *) start; +} +*/ + +void ds_init() +{ + char *err = NULL; + + server.ds_cache = leveldb_cache_create_lru(server.ds_lru_cache); + server.ds_options = leveldb_options_create(); + + server.policy = leveldb_filterpolicy_create_bloom(10); + + + //leveldb_options_set_comparator(server.ds_options, cmp); + leveldb_options_set_filter_policy(server.ds_options, server.policy); + leveldb_options_set_create_if_missing(server.ds_options, server.ds_create_if_missing); + leveldb_options_set_error_if_exists(server.ds_options, server.ds_error_if_exists); + leveldb_options_set_cache(server.ds_options, server.ds_cache); + leveldb_options_set_info_log(server.ds_options, NULL); + leveldb_options_set_write_buffer_size(server.ds_options, server.ds_write_buffer_size); + leveldb_options_set_paranoid_checks(server.ds_options, server.ds_paranoid_checks); + leveldb_options_set_max_open_files(server.ds_options, server.ds_max_open_files); + leveldb_options_set_block_size(server.ds_options, server.ds_block_cache_size); + leveldb_options_set_block_restart_interval(server.ds_options, server.ds_block_restart_interval); + leveldb_options_set_compression(server.ds_options, leveldb_snappy_compression); + + server.ds_db = leveldb_open(server.ds_options, server.ds_path, &err); + if (err != NULL) + { + fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__,err); + leveldb_free(err); + exit(1); + } +} + +void ds_mget(redisClient *c) +{ + int i, len, pos; + size_t val_len; + char *err, *value; + + leveldb_readoptions_t *roptions; + + roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(roptions, 0); + leveldb_readoptions_set_fill_cache(roptions, 1); + + addReplyMultiBulkLen(c,c->argc-1); + for(i=1; iargc; i++) + { + err = NULL; + value = NULL; + val_len = pos = 0; + len = strlen((char *)c->argv[i]->ptr); + value = leveldb_get(server.ds_db, roptions, (char *)c->argv[i]->ptr, len, &val_len, &err); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + leveldb_free(value); + leveldb_readoptions_destroy(roptions); + return ; + } + else if(val_len > 0) + { + addReplyBulkCBuffer(c, value, val_len); + leveldb_free(value); + value = NULL; + } + else + { + addReply(c,shared.nullbulk); + + } + } + leveldb_readoptions_destroy(roptions); +} + +void ds_get(redisClient *c) +{ + + bool is_int; + int64_t recore; + char *err = NULL; + size_t val_len, i; + char *key = NULL; + char *value = NULL; + + leveldb_readoptions_t *roptions; + + roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(roptions, 0); + leveldb_readoptions_set_fill_cache(roptions, 1); + + key = (char *)c->argv[1]->ptr; + value = leveldb_get(server.ds_db, roptions, key, strlen(key), &val_len, &err); + leveldb_readoptions_destroy(roptions); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + leveldb_free(value); + + return ; + } + else if(value == NULL) + { + addReply(c,shared.nullbulk); + return ; + } + + is_int = true; + for(i=0; value[i]!=0; i++) + { + is_int = isgraph(value[i]) ? false : true; + } + + if(is_int) + { + recore = *(int64_t *)value; + addReplyLongLong(c, recore); + } + else + { + addReplyBulkCBuffer(c, value, val_len); + } + leveldb_free(value); +} +void rl_get(redisClient *c) +{ + //从redis里取数据 + robj *o; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) != NULL) { + + if (o->type == REDIS_STRING) { + addReplyBulk(c,o); + return; + } + } + + ds_get(c); +} + + + +void ds_mset(redisClient *c) +{ + int i; + char *key, *value; + char *err = NULL; + leveldb_writeoptions_t *woptions; + leveldb_writebatch_t *wb; + + if((c->argc%2) == 0) + { + addReply(c,shared.nullbulk); + return ; + } + + + woptions = leveldb_writeoptions_create(); + wb = leveldb_writebatch_create(); + for(i=1; iargc; i++) + { + key = (char *)c->argv[i]->ptr; + value = (char *)c->argv[++i]->ptr; + leveldb_writebatch_put(wb, key, strlen(key), value, strlen(value)); + } + leveldb_write(server.ds_db, woptions, wb, &err); + leveldb_writeoptions_destroy(woptions); + leveldb_writebatch_destroy(wb); + + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + return ; + } + addReply(c,shared.ok); + + return ; +} + + +void ds_hincrby(redisClient *c) +{ + int64_t val, recore; + sds keyword; + char *value; + + size_t val_len; + char *err = NULL; + leveldb_writeoptions_t *woptions; + leveldb_readoptions_t *roptions; + + + roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(roptions, 0); + leveldb_readoptions_set_fill_cache(roptions, 1); + + err = NULL; + val_len = 0; + keyword = sdsempty(); + keyword = sdscpy(keyword, c->argv[1]->ptr); + keyword = sdscatlen(keyword, "*", 1); + keyword = sdscat(keyword, c->argv[2]->ptr); + + value = leveldb_get(server.ds_db, roptions, keyword, sdslen(keyword), &val_len, &err); + leveldb_readoptions_destroy(roptions); + + if(err != NULL) + { + sdsfree(keyword); + leveldb_free(err); + if(val_len > 0) leveldb_free(value); + addReplyError(c, err); + return ; + } + else if(val_len < 1) + { + val = 0; + } + else + { + val = *(int64_t *)value; + } + + err = NULL; + recore = strtoll(c->argv[3]->ptr, NULL, 10); + recore = val + recore; + woptions = leveldb_writeoptions_create(); + + leveldb_put(server.ds_db, woptions, keyword, sdslen(keyword), (char *)&recore, sizeof(int64_t), &err); + leveldb_writeoptions_destroy(woptions); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + } + else + { + addReplyLongLong(c, recore); + } + leveldb_free(value); + sdsfree(keyword); + return ; +} + +void ds_hmget(redisClient *c) +{ + int i; + sds keyword; + size_t val_len; + char *key, *err = NULL, *value = NULL; + + leveldb_readoptions_t *roptions; + + roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(roptions, 0); + leveldb_readoptions_set_fill_cache(roptions, 1); + + addReplyMultiBulkLen(c, c->argc-2); + + key = (char *)c->argv[1]->ptr; + keyword = sdsempty(); + + for(i=2; iargc; i++) + { + err = NULL; + value = NULL; + val_len = 0; + + sdsclear(keyword); + keyword = sdscat(keyword, key); + keyword = sdscatlen(keyword, "*", 1); + keyword = sdscat(keyword, c->argv[i]->ptr); + + value = leveldb_get(server.ds_db, roptions, keyword, sdslen(keyword), &val_len, &err); + if(err != NULL) + { + sdsfree(keyword); + leveldb_free(err); + leveldb_free(value); + addReplyError(c, err); + leveldb_readoptions_destroy(roptions); + return ; + } + else if(val_len > 0) + { + addReplyBulkCBuffer(c, value, val_len); + leveldb_free(value); + value = NULL; + } + else + { + addReply(c,shared.nullbulk); + + } + } + + sdsfree(keyword); + leveldb_readoptions_destroy(roptions); +} + +void ds_hmset(redisClient *c) +{ + int i; + sds keyword; + char *key, *field, *value; + char *err = NULL; + leveldb_writeoptions_t *woptions; + leveldb_writebatch_t *wb; + + if((c->argc%2) != 0) + { + addReply(c,shared.nullbulk); + return ; + } + + keyword = sdsempty(); + woptions = leveldb_writeoptions_create(); + wb = leveldb_writebatch_create(); + key = (char *)c->argv[1]->ptr; + + keyword = sdscat(keyword, key); + keyword = sdscatlen(keyword, "*", 1); + leveldb_writebatch_put(wb, keyword, sdslen(keyword), "1", 1); + for(i=2; iargc; i++) + { + field = (char *)c->argv[i]->ptr; + value = (char *)c->argv[++i]->ptr; + + sdsclear(keyword); + keyword = sdscat(keyword, key); + keyword = sdscatlen(keyword, "*", 1); + keyword = sdscat(keyword, field); + leveldb_writebatch_put(wb, keyword, sdslen(keyword), value, strlen(value)); + } + sdsfree(keyword); + + leveldb_write(server.ds_db, woptions, wb, &err); + leveldb_writeoptions_destroy(woptions); + leveldb_writebatch_destroy(wb); + + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + return ; + } + addReply(c,shared.ok); + + return ; +} + +void ds_hset(redisClient *c) +{ + sds str; + char *key, *field, *value, *err; + leveldb_writeoptions_t *woptions; + leveldb_writebatch_t *wb; + + key = (char *)c->argv[1]->ptr; + field = (char *)c->argv[2]->ptr; + value = (char *)c->argv[3]->ptr; + + woptions = leveldb_writeoptions_create(); + wb = leveldb_writebatch_create(); + + str = sdsempty(); + str = sdscpy(str, key); + str = sdscatlen(str, "*", 1); + + leveldb_writebatch_put(wb, str, sdslen(str), "1", 1); + + sdsclear(str); + str = sdscpy(str, key); + str = sdscatlen(str, "*", 1); + str = sdscat(str, field); + leveldb_writebatch_put(wb, str, sdslen(str), value, strlen(value)); + + leveldb_write(server.ds_db, woptions, wb, &err); + + leveldb_writeoptions_destroy(woptions); + leveldb_writebatch_destroy(wb); + sdsfree(str); + + addReply(c,shared.ok); + return ; +} + +void rl_hset(redisClient *c) +{ + ds_hset(*c); + hsetCommand(*c); +} + +void rl_hdel(redisClient *c) +{ + ds_hdel(c); + hdelCommand(c); +} + +void ds_hgetall(redisClient *c) +{ + sds str, header; + char *keyword = NULL; + + const char *key, *value; + size_t key_len, value_len, len, i; + + leveldb_iterator_t *iter; + leveldb_readoptions_t *roptions; + + roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(roptions, 0); + leveldb_readoptions_set_fill_cache(roptions, 1); + + i = 0; + str = sdsempty(); + iter = leveldb_create_iterator(server.ds_db, roptions); + + str = sdscpy(str, c->argv[1]->ptr); + str = sdscatlen(str, "*", 1); + len = sdslen(str); + keyword = zmalloc(len+1); + memcpy(keyword, str, len); + + leveldb_iter_seek(iter, keyword, len); + + if(!leveldb_iter_valid(iter)) + { + sdsfree(str); + zfree(keyword); + addReply(c,shared.nullbulk); + leveldb_iter_destroy(iter); + leveldb_readoptions_destroy(roptions); + return ; + } + + sdsclear(str); + leveldb_iter_next(iter); + while(1) + { + if(!leveldb_iter_valid(iter)) + break; + + key_len = value_len = 0; + key = leveldb_iter_key(iter, &key_len); + value = leveldb_iter_value(iter, &value_len); + + if(strncmp(keyword, key, len) != 0) + break; + + str = sdscatprintf(str, "$%lu\r\n", key_len-len); + str = sdscatlen(str, key+len, key_len-len); + str = sdscatprintf(str, "\r\n$%lu\r\n", value_len); + str = sdscatlen(str, value, value_len); + str = sdscatlen(str, "\r\n", 2); + + i++; + leveldb_iter_next(iter); + } + + if(i == 0) + { + addReply(c,shared.nullbulk); + } + else + { + header = sdsempty(); + header = sdscatprintf(header, "*%lu\r\n", i*2); + header = sdscatlen(header, str, sdslen(str)); + addReplySds(c, header); + sdsfree(header); + } + + sdsfree(str); + zfree(keyword); + leveldb_iter_destroy(iter); + leveldb_readoptions_destroy(roptions); + return ; +} + + +void ds_hdel(redisClient *c) +{ + const char *key; + size_t key_len, i; + + sds keyword; + char *err = NULL; + leveldb_writeoptions_t *woptions; + leveldb_writebatch_t *wb; + + leveldb_iterator_t *iter; + leveldb_readoptions_t *roptions; + + roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(roptions, 0); + leveldb_readoptions_set_fill_cache(roptions, 1); + + iter = leveldb_create_iterator(server.ds_db, roptions); + + keyword = sdsempty(); + woptions = leveldb_writeoptions_create(); + + if(c->argc < 3) + { + keyword = sdscpy(keyword, c->argv[1]->ptr); + keyword = sdscatlen(keyword, "*", 1); + + leveldb_iter_seek(iter, keyword, sdslen(keyword)); + if(!leveldb_iter_valid(iter)) + { + sdsfree(keyword); + addReply(c,shared.nullbulk); + leveldb_iter_destroy(iter); + leveldb_readoptions_destroy(roptions); + leveldb_writeoptions_destroy(woptions); + return ; + } + + wb = leveldb_writebatch_create(); + while(1) + { + key_len = 0; + key = leveldb_iter_key(iter, &key_len); + + if(strncmp(keyword, key, sdslen(keyword)) != 0) + break; + + //printf("key = %s\r\n", key); + leveldb_writebatch_delete(wb, key, key_len); + leveldb_iter_next(iter); + if(!leveldb_iter_valid(iter)) + break; + } + + leveldb_write(server.ds_db, woptions, wb, &err); + leveldb_writeoptions_destroy(woptions); + leveldb_writebatch_destroy(wb); + + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + + sdsfree(keyword); + leveldb_iter_destroy(iter); + leveldb_readoptions_destroy(roptions); + leveldb_writeoptions_destroy(woptions); + return ; + } + addReply(c,shared.ok); + + sdsfree(keyword); + leveldb_iter_destroy(iter); + leveldb_readoptions_destroy(roptions); + leveldb_writeoptions_destroy(woptions); + + return ; + } + + wb = leveldb_writebatch_create(); + for(i=2; iargc; i++) + { + sdsclear(keyword); + keyword = sdscpy(keyword, c->argv[1]->ptr); + keyword = sdscatlen(keyword, "*", 1); + keyword = sdscat(keyword, c->argv[i]->ptr); + leveldb_writebatch_delete(wb, keyword, sdslen(keyword)); + } + + sdsfree(keyword); + leveldb_write(server.ds_db, woptions, wb, &err); + leveldb_readoptions_destroy(roptions); + leveldb_writeoptions_destroy(woptions); + leveldb_writebatch_destroy(wb); + + + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + return ; + } + addReply(c,shared.ok); + + return ; +} + +void ds_hget(redisClient *c) +{ + sds str; + size_t val_len = 0; + char *key = NULL, *field = NULL, *value = NULL, *err = NULL; + + leveldb_readoptions_t *roptions; + + key = (char *)c->argv[1]->ptr; + field = (char *)c->argv[2]->ptr; + + roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(roptions, 0); + leveldb_readoptions_set_fill_cache(roptions, 1); + + str = sdsnew(key); + str = sdscatlen(str, "*", 1); + str = sdscat(str, field); + value = leveldb_get(server.ds_db, roptions, str, sdslen(str), &val_len, &err); + leveldb_readoptions_destroy(roptions); + + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + if(val_len > 0) leveldb_free(value); + + return ; + } + else if(value == NULL) + { + addReply(c,shared.nullbulk); + return ; + } + sdsfree(str); + + addReplyBulkCBuffer(c, value, val_len); + leveldb_free(value); +} + +void rl_hget(redisClient *c) +{ + robj *o; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,REDIS_HASH)) + { + ds_hget(c); + return; + } + + addHashFieldToReply(c, o, c->argv[2]); +} + + +void ds_incrby(redisClient *c) +{ + char *value; + int64_t val, recore; + + size_t val_len; + char *err = NULL; + leveldb_writeoptions_t *woptions; + leveldb_readoptions_t *roptions; + + + roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(roptions, 0); + leveldb_readoptions_set_fill_cache(roptions, 1); + + err = NULL; + val_len = 0; + + value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), &val_len, &err); + leveldb_readoptions_destroy(roptions); + + if(err != NULL) + { + leveldb_free(err); + if(val_len > 0) leveldb_free(value); + addReplyError(c, err); + return ; + } + else if(val_len < 1) + { + val = 0; + } + else + { + val = *(int64_t *)value; + } + + err = NULL; + recore = strtoll(c->argv[2]->ptr, NULL, 10); + recore = val + recore; + woptions = leveldb_writeoptions_create(); + + leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), (char *)&recore, sizeof(int64_t), &err); + leveldb_writeoptions_destroy(woptions); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + } + else + { + addReplyLongLong(c, recore); + } + leveldb_free(value); + return ; +} + + +void ds_append(redisClient *c) +{ + sds recore; + char *value; + + size_t val_len; + char *err = NULL; + leveldb_writeoptions_t *woptions; + leveldb_readoptions_t *roptions; + + + roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(roptions, 0); + leveldb_readoptions_set_fill_cache(roptions, 1); + + err = NULL; + val_len = 0; + + value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), &val_len, &err); + leveldb_readoptions_destroy(roptions); + + if(err != NULL) + { + leveldb_free(err); + if(val_len > 0) leveldb_free(value); + addReplyError(c, err); + return ; + } + + + err = NULL; + recore = sdsempty(); + if(val_len > 0) + { + recore = sdscpy(recore, value); + } + recore = sdscat(recore, c->argv[1]->ptr); + woptions = leveldb_writeoptions_create(); + + leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), recore, sdslen(recore), &err); + leveldb_writeoptions_destroy(woptions); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + } + else + { + addReply(c,shared.ok); + } + + sdsfree(recore); + leveldb_free(value); + return ; +} + +void ds_set(redisClient *c) +{ + char *key, *value; + char *err = NULL; + leveldb_writeoptions_t *woptions; + + woptions = leveldb_writeoptions_create(); + + key = (char *)c->argv[1]->ptr; + value = (char *)c->argv[2]->ptr; + leveldb_put(server.ds_db, woptions, key, strlen(key), value, strlen(value), &err); + leveldb_writeoptions_destroy(woptions); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + return ; + } + addReply(c,shared.ok); + return ; +} + +void rl_set(redisClient *c) +{ + char *key, *value; + char *err = NULL; + leveldb_writeoptions_t *woptions; + + woptions = leveldb_writeoptions_create(); + + key = (char *)c->argv[1]->ptr; + value = (char *)c->argv[2]->ptr; + leveldb_put(server.ds_db, woptions, key, strlen(key), value, strlen(value), &err); + leveldb_writeoptions_destroy(woptions); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + return ; + } + //addReply(c,shared.ok); + + //存到redis + setCommand(c); +} + +void ds_delete(redisClient *c) +{ + int i; + char *key; + char *err = NULL; + leveldb_writeoptions_t *woptions; + leveldb_writebatch_t *wb; + + woptions = leveldb_writeoptions_create(); + + if(c->argc < 3) + { + key = (char *)c->argv[1]->ptr; + leveldb_delete(server.ds_db, woptions, key, strlen(key), &err); + leveldb_writeoptions_destroy(woptions); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + return ; + } + addReply(c,shared.ok); + return ; + } + + wb = leveldb_writebatch_create(); + for(i=1; iargc; i++) + { + leveldb_writebatch_delete(wb, (char *)c->argv[i]->ptr, strlen((char *)c->argv[i]->ptr)); + } + leveldb_write(server.ds_db, woptions, wb, &err); + leveldb_writeoptions_destroy(woptions); + leveldb_writebatch_destroy(wb); + + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + return ; + } + addReply(c,shared.ok); + + return ; +} + + + +void rl_delete(redisClient *c) +{ + ds_delete(c); + delCommand(c); +} + +void ds_close() +{ + leveldb_options_set_filter_policy(server.ds_options, NULL); + leveldb_filterpolicy_destroy(server.policy); + leveldb_close(server.ds_db); + leveldb_options_destroy(server.ds_options); + leveldb_cache_destroy(server.ds_cache); +} + diff --git a/src/redis.c b/src/redis.c index f25d281..2203479 100644 --- a/src/redis.c +++ b/src/redis.c @@ -1,2645 +1,2648 @@ -/* - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "redis.h" -#include "slowlog.h" -#include "bio.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Our shared "common" objects */ - -struct sharedObjectsStruct shared; - -/* Global vars that are actually used as constants. The following double - * values are used for double on-disk serialization, and are initialized - * at runtime to avoid strange compiler optimizations. */ - -double R_Zero, R_PosInf, R_NegInf, R_Nan; - -/*================================= Globals ================================= */ - -/* Global vars */ -struct redisServer server; /* server global state */ -struct redisCommand *commandTable; - -/* Our command table. - * - * Every entry is composed of the following fields: - * - * name: a string representing the command name. - * function: pointer to the C function implementing the command. - * arity: number of arguments, it is possible to use -N to say >= N - * sflags: command flags as string. See below for a table of flags. - * flags: flags as bitmask. Computed by Redis using the 'sflags' field. - * get_keys_proc: an optional function to get key arguments from a command. - * This is only used when the following three fields are not - * enough to specify what arguments are keys. - * first_key_index: first argument that is a key - * last_key_index: last argument that is a key - * key_step: step to get all the keys from first to last argument. For instance - * in MSET the step is two since arguments are key,val,key,val,... - * microseconds: microseconds of total execution time for this command. - * calls: total number of calls of this command. - * - * The flags, microseconds and calls fields are computed by Redis and should - * always be set to zero. - * - * Command flags are expressed using strings where every character represents - * a flag. Later the populateCommandTable() function will take care of - * populating the real 'flags' field using this characters. - * - * This is the meaning of the flags: - * - * w: write command (may modify the key space). - * r: read command (will never modify the key space). - * m: may increase memory usage once called. Don't allow if out of memory. - * a: admin command, like SAVE or SHUTDOWN. - * p: Pub/Sub related command. - * f: force replication of this command, regarless of server.dirty. - * s: command not allowed in scripts. - * R: random command. Command is not deterministic, that is, the same command - * with the same arguments, with the same key space, may have different - * results. For instance SPOP and RANDOMKEY are two random commands. - * S: Sort command output array if called from script, so that the output - * is deterministic. - * l: Allow command while loading the database. - * t: Allow command while a slave has stale data but is not allowed to - * server this data. Normally no command is accepted in this condition - * but just a few. - * M: Do not automatically propagate the command on MONITOR. - */ -struct redisCommand redisCommandTable[] = { - {"ds_get",ds_get,2,"r",0,NULL,1,1,1,0,0}, - {"rl_get",rl_get,2,"r",0,NULL,1,1,1,0,0}, - {"ds_mget",ds_mget,-2,"r",0,NULL,1,-1,1,0,0}, - {"ds_mset",ds_mset,-3,"wm",0,NULL,1,-1,2,0,0}, - {"ds_del",ds_delete,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0}, - {"rl_del",rl_delete,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0}, - {"ds_set",ds_set,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, - {"rl_set",rl_set,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, - {"ds_hset",ds_hset,4,"wm",0,NULL,1,1,1,0,0}, - {"ds_hdel",ds_hdel,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0}, - {"ds_hget",ds_hget,3,"r",0,NULL,1,1,1,0,0}, - {"ds_hmget",ds_hmget,-3,"r",0,NULL,1,1,1,0,0}, - {"ds_hmset",ds_hmset,-4,"wm",0,NULL,1,1,1,0,0}, - {"ds_hincrby",ds_hincrby,4,"wm",0,NULL,1,1,1,0,0}, - {"ds_hgetall",ds_hgetall,2,"r",0,NULL,1,1,1,0,0}, - {"ds_append",ds_append,3,"wm",0,NULL,1,1,1,0,0}, - {"ds_incrby",ds_incrby,3,"wm",0,NULL,1,1,1,0,0}, - - {"get",getCommand,2,"r",0,NULL,1,1,1,0,0}, - {"set",setCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, - {"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, - {"setex",setexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0}, - {"psetex",psetexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0}, - {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0}, - {"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0}, - {"del",delCommand,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0}, - {"exists",existsCommand,2,"r",0,NULL,1,1,1,0,0}, - {"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0}, - {"getbit",getbitCommand,3,"r",0,NULL,1,1,1,0,0}, - {"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0}, - {"getrange",getrangeCommand,4,"r",0,NULL,1,1,1,0,0}, - {"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0}, - {"incr",incrCommand,2,"wm",0,NULL,1,1,1,0,0}, - {"decr",decrCommand,2,"wm",0,NULL,1,1,1,0,0}, - {"mget",mgetCommand,-2,"r",0,NULL,1,-1,1,0,0}, - {"rpush",rpushCommand,-3,"wm",0,NULL,1,1,1,0,0}, - {"lpush",lpushCommand,-3,"wm",0,NULL,1,1,1,0,0}, - {"rpushx",rpushxCommand,3,"wm",0,NULL,1,1,1,0,0}, - {"lpushx",lpushxCommand,3,"wm",0,NULL,1,1,1,0,0}, - {"linsert",linsertCommand,5,"wm",0,NULL,1,1,1,0,0}, - {"rpop",rpopCommand,2,"w",0,NULL,1,1,1,0,0}, - {"lpop",lpopCommand,2,"w",0,NULL,1,1,1,0,0}, - {"brpop",brpopCommand,-3,"ws",0,NULL,1,1,1,0,0}, - {"brpoplpush",brpoplpushCommand,4,"wms",0,NULL,1,2,1,0,0}, - {"blpop",blpopCommand,-3,"ws",0,NULL,1,-2,1,0,0}, - {"llen",llenCommand,2,"r",0,NULL,1,1,1,0,0}, - {"lindex",lindexCommand,3,"r",0,NULL,1,1,1,0,0}, - {"lset",lsetCommand,4,"wm",0,NULL,1,1,1,0,0}, - {"lrange",lrangeCommand,4,"r",0,NULL,1,1,1,0,0}, - {"ltrim",ltrimCommand,4,"w",0,NULL,1,1,1,0,0}, - {"lrem",lremCommand,4,"w",0,NULL,1,1,1,0,0}, - {"rpoplpush",rpoplpushCommand,3,"wm",0,NULL,1,2,1,0,0}, - {"sadd",saddCommand,-3,"wm",0,NULL,1,1,1,0,0}, - {"srem",sremCommand,-3,"w",0,NULL,1,1,1,0,0}, - {"smove",smoveCommand,4,"w",0,NULL,1,2,1,0,0}, - {"sismember",sismemberCommand,3,"r",0,NULL,1,1,1,0,0}, - {"scard",scardCommand,2,"r",0,NULL,1,1,1,0,0}, - {"spop",spopCommand,2,"wRs",0,NULL,1,1,1,0,0}, - {"srandmember",srandmemberCommand,-2,"rR",0,NULL,1,1,1,0,0}, - {"sinter",sinterCommand,-2,"rS",0,NULL,1,-1,1,0,0}, - {"sinterstore",sinterstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0}, - {"sunion",sunionCommand,-2,"rS",0,NULL,1,-1,1,0,0}, - {"sunionstore",sunionstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0}, - {"sdiff",sdiffCommand,-2,"rS",0,NULL,1,-1,1,0,0}, - {"sdiffstore",sdiffstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0}, - {"smembers",sinterCommand,2,"rS",0,NULL,1,1,1,0,0}, - {"zadd",zaddCommand,-4,"wm",0,NULL,1,1,1,0,0}, - {"zincrby",zincrbyCommand,4,"wm",0,NULL,1,1,1,0,0}, - {"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}, - {"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}, - {"zcount",zcountCommand,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}, - {"zrank",zrankCommand,3,"r",0,NULL,1,1,1,0,0}, - {"zrevrank",zrevrankCommand,3,"r",0,NULL,1,1,1,0,0}, - {"hset",hsetCommand,4,"wm",0,NULL,1,1,1,0,0}, - {"hsetnx",hsetnxCommand,4,"wm",0,NULL,1,1,1,0,0}, - {"hget",hgetCommand,3,"r",0,NULL,1,1,1,0,0}, - {"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0}, - {"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0}, - {"hincrby",hincrbyCommand,4,"wm",0,NULL,1,1,1,0,0}, - {"hincrbyfloat",hincrbyfloatCommand,4,"wm",0,NULL,1,1,1,0,0}, - {"hdel",hdelCommand,-3,"w",0,NULL,1,1,1,0,0}, - {"hlen",hlenCommand,2,"r",0,NULL,1,1,1,0,0}, - {"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0}, - {"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0}, - {"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0}, - {"hexists",hexistsCommand,3,"r",0,NULL,1,1,1,0,0}, - {"incrby",incrbyCommand,3,"wm",0,NULL,1,1,1,0,0}, - {"decrby",decrbyCommand,3,"wm",0,NULL,1,1,1,0,0}, - {"incrbyfloat",incrbyfloatCommand,3,"wm",0,NULL,1,1,1,0,0}, - {"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0}, - {"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0}, - {"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0}, - {"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0}, - {"select",selectCommand,2,"r",0,NULL,0,0,0,0,0}, - {"move",moveCommand,3,"w",0,NULL,1,1,1,0,0}, - {"rename",renameCommand,3,"w",0,renameGetKeys,1,2,1,0,0}, - {"renamenx",renamenxCommand,3,"w",0,renameGetKeys,1,2,1,0,0}, - {"expire",expireCommand,3,"w",0,NULL,1,1,1,0,0}, - {"expireat",expireatCommand,3,"w",0,NULL,1,1,1,0,0}, - {"pexpire",pexpireCommand,3,"w",0,NULL,1,1,1,0,0}, - {"pexpireat",pexpireatCommand,3,"w",0,NULL,1,1,1,0,0}, - {"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0}, - {"dbsize",dbsizeCommand,1,"r",0,NULL,0,0,0,0,0}, - {"auth",authCommand,2,"rs",0,NULL,0,0,0,0,0}, - {"ping",pingCommand,1,"r",0,NULL,0,0,0,0,0}, - {"echo",echoCommand,2,"r",0,NULL,0,0,0,0,0}, - {"save",saveCommand,1,"ars",0,NULL,0,0,0,0,0}, - {"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0}, - {"bgrewriteaof",bgrewriteaofCommand,1,"ar",0,NULL,0,0,0,0,0}, - {"shutdown",shutdownCommand,-1,"ar",0,NULL,0,0,0,0,0}, - {"lastsave",lastsaveCommand,1,"r",0,NULL,0,0,0,0,0}, - {"type",typeCommand,2,"r",0,NULL,1,1,1,0,0}, - {"multi",multiCommand,1,"rs",0,NULL,0,0,0,0,0}, - {"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0}, - {"discard",discardCommand,1,"rs",0,NULL,0,0,0,0,0}, - {"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0}, - {"replconf",replconfCommand,-1,"ars",0,NULL,0,0,0,0,0}, - {"flushdb",flushdbCommand,1,"w",0,NULL,0,0,0,0,0}, - {"flushall",flushallCommand,1,"w",0,NULL,0,0,0,0,0}, - {"sort",sortCommand,-2,"wm",0,NULL,1,1,1,0,0}, - {"info",infoCommand,-1,"rlt",0,NULL,0,0,0,0,0}, - {"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0}, - {"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0}, - {"pttl",pttlCommand,2,"r",0,NULL,1,1,1,0,0}, - {"persist",persistCommand,2,"w",0,NULL,1,1,1,0,0}, - {"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0}, - {"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0}, - {"config",configCommand,-2,"ar",0,NULL,0,0,0,0,0}, - {"subscribe",subscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0}, - {"unsubscribe",unsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0}, - {"psubscribe",psubscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0}, - {"punsubscribe",punsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0}, - {"publish",publishCommand,3,"pflt",0,NULL,0,0,0,0,0}, - {"watch",watchCommand,-2,"rs",0,noPreloadGetKeys,1,-1,1,0,0}, - {"unwatch",unwatchCommand,1,"rs",0,NULL,0,0,0,0,0}, - {"restore",restoreCommand,4,"awm",0,NULL,1,1,1,0,0}, - {"migrate",migrateCommand,6,"aw",0,NULL,0,0,0,0,0}, - {"dump",dumpCommand,2,"ar",0,NULL,1,1,1,0,0}, - {"object",objectCommand,-2,"r",0,NULL,2,2,2,0,0}, - {"client",clientCommand,-2,"ar",0,NULL,0,0,0,0,0}, - {"eval",evalCommand,-3,"s",0,zunionInterGetKeys,0,0,0,0,0}, - {"evalsha",evalShaCommand,-3,"s",0,zunionInterGetKeys,0,0,0,0,0}, - {"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0}, - {"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0}, - {"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0}, - {"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0}, - {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0} -}; - -/*============================ Utility functions ============================ */ - -/* Low level logging. To use only for very big messages, otherwise - * redisLog() is to prefer. */ -void redisLogRaw(int level, const char *msg) { - const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING }; - const char *c = ".-*#"; - FILE *fp; - char buf[64]; - int rawmode = (level & REDIS_LOG_RAW); - - level &= 0xff; /* clear flags */ - if (level < server.verbosity) return; - - fp = (server.logfile == NULL) ? stdout : fopen(server.logfile,"a"); - if (!fp) return; - - if (rawmode) { - fprintf(fp,"%s",msg); - } else { - int off; - struct timeval tv; - - 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); - fprintf(fp,"[%d] %s %c %s\n",(int)getpid(),buf,c[level],msg); - } - fflush(fp); - - if (server.logfile) fclose(fp); - - if (server.syslog_enabled) syslog(syslogLevelMap[level], "%s", msg); -} - -/* Like redisLogRaw() but with printf-alike support. This is the funciton that - * is used across the code. The raw version is only used in order to dump - * the INFO output on crash. */ -void redisLog(int level, const char *fmt, ...) { - va_list ap; - char msg[REDIS_MAX_LOGMSG_LEN]; - - if ((level&0xff) < server.verbosity) return; - - va_start(ap, fmt); - vsnprintf(msg, sizeof(msg), fmt, ap); - va_end(ap); - - redisLogRaw(level,msg); -} - -/* Log a fixed message without printf-alike capabilities, in a way that is - * safe to call from a signal handler. - * - * We actually use this only for signals that are not fatal from the point - * of view of Redis. Signals that are going to kill the server anyway and - * where we need printf-alike features are served by redisLog(). */ -void redisLogFromHandler(int level, const char *msg) { - int fd; - char buf[64]; - - if ((level&0xff) < server.verbosity || - (server.logfile == NULL && server.daemonize)) return; - fd = server.logfile ? - open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644) : - STDOUT_FILENO; - 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] (",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; - if (write(fd,msg,strlen(msg)) == -1) goto err; - if (write(fd,"\n",1) == -1) goto err; -err: - if (server.logfile) close(fd); -} - -/* Return the UNIX time in microseconds */ -long long ustime(void) { - struct timeval tv; - long long ust; - - gettimeofday(&tv, NULL); - ust = ((long long)tv.tv_sec)*1000000; - ust += tv.tv_usec; - return ust; -} - -/* Return the UNIX time in milliseconds */ -long long mstime(void) { - return ustime()/1000; -} - -/* After an RDB dump or AOF rewrite we exit from children using _exit() instead of - * exit(), because the latter may interact with the same file objects used by - * the parent process. However if we are testing the coverage normal exit() is - * used in order to obtain the right coverage information. */ -void exitFromChild(int retcode) { -#ifdef COVERAGE_TEST - exit(retcode); -#else - _exit(retcode); -#endif -} - -/*====================== Hash table type implementation ==================== */ - -/* This is an hash table type that uses the SDS dynamic strings libary as - * keys and radis objects as values (objects can hold SDS strings, - * lists, sets). */ - -void dictVanillaFree(void *privdata, void *val) -{ - DICT_NOTUSED(privdata); - zfree(val); -} - -void dictListDestructor(void *privdata, void *val) -{ - DICT_NOTUSED(privdata); - listRelease((list*)val); -} - -int dictSdsKeyCompare(void *privdata, const void *key1, - const void *key2) -{ - int l1,l2; - DICT_NOTUSED(privdata); - - l1 = sdslen((sds)key1); - l2 = sdslen((sds)key2); - if (l1 != l2) return 0; - return memcmp(key1, key2, l1) == 0; -} - -/* A case insensitive version used for the command lookup table and other - * places where case insensitive non binary-safe comparison is needed. */ -int dictSdsKeyCaseCompare(void *privdata, const void *key1, - const void *key2) -{ - DICT_NOTUSED(privdata); - - return strcasecmp(key1, key2) == 0; -} - -void dictRedisObjectDestructor(void *privdata, void *val) -{ - DICT_NOTUSED(privdata); - - if (val == NULL) return; /* Values of swapped out keys as set to NULL */ - decrRefCount(val); -} - -void dictSdsDestructor(void *privdata, void *val) -{ - DICT_NOTUSED(privdata); - - sdsfree(val); -} - -int dictObjKeyCompare(void *privdata, const void *key1, - const void *key2) -{ - const robj *o1 = key1, *o2 = key2; - return dictSdsKeyCompare(privdata,o1->ptr,o2->ptr); -} - -unsigned int dictObjHash(const void *key) { - const robj *o = key; - return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr)); -} - -unsigned int dictSdsHash(const void *key) { - return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); -} - -unsigned int dictSdsCaseHash(const void *key) { - return dictGenCaseHashFunction((unsigned char*)key, sdslen((char*)key)); -} - -int dictEncObjKeyCompare(void *privdata, const void *key1, - const void *key2) -{ - robj *o1 = (robj*) key1, *o2 = (robj*) key2; - int cmp; - - if (o1->encoding == REDIS_ENCODING_INT && - o2->encoding == REDIS_ENCODING_INT) - return o1->ptr == o2->ptr; - - o1 = getDecodedObject(o1); - o2 = getDecodedObject(o2); - cmp = dictSdsKeyCompare(privdata,o1->ptr,o2->ptr); - decrRefCount(o1); - decrRefCount(o2); - return cmp; -} - -unsigned int dictEncObjHash(const void *key) { - robj *o = (robj*) key; - - if (o->encoding == REDIS_ENCODING_RAW) { - return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr)); - } else { - if (o->encoding == REDIS_ENCODING_INT) { - char buf[32]; - int len; - - len = ll2string(buf,32,(long)o->ptr); - return dictGenHashFunction((unsigned char*)buf, len); - } else { - unsigned int hash; - - o = getDecodedObject(o); - hash = dictGenHashFunction(o->ptr, sdslen((sds)o->ptr)); - decrRefCount(o); - return hash; - } - } -} - -/* Sets type hash table */ -dictType setDictType = { - dictEncObjHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictEncObjKeyCompare, /* key compare */ - dictRedisObjectDestructor, /* key destructor */ - NULL /* val destructor */ -}; - -/* Sorted sets hash (note: a skiplist is used in addition to the hash table) */ -dictType zsetDictType = { - dictEncObjHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictEncObjKeyCompare, /* key compare */ - dictRedisObjectDestructor, /* key destructor */ - NULL /* val destructor */ -}; - -/* Db->dict, keys are sds strings, vals are Redis objects. */ -dictType dbDictType = { - dictSdsHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictSdsKeyCompare, /* key compare */ - dictSdsDestructor, /* key destructor */ - dictRedisObjectDestructor /* val destructor */ -}; - -/* server.lua_scripts sha (as sds string) -> scripts (as robj) cache. */ -dictType shaScriptObjectDictType = { - dictSdsCaseHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictSdsKeyCaseCompare, /* key compare */ - dictSdsDestructor, /* key destructor */ - dictRedisObjectDestructor /* val destructor */ -}; - -/* Db->expires */ -dictType keyptrDictType = { - dictSdsHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictSdsKeyCompare, /* key compare */ - NULL, /* key destructor */ - NULL /* val destructor */ -}; - -/* Command table. sds string -> command struct pointer. */ -dictType commandTableDictType = { - dictSdsCaseHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictSdsKeyCaseCompare, /* key compare */ - dictSdsDestructor, /* key destructor */ - NULL /* val destructor */ -}; - -/* Hash type hash table (note that small hashes are represented with zimpaps) */ -dictType hashDictType = { - dictEncObjHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictEncObjKeyCompare, /* key compare */ - dictRedisObjectDestructor, /* key destructor */ - dictRedisObjectDestructor /* val destructor */ -}; - -/* Keylist hash table type has unencoded redis objects as keys and - * lists as values. It's used for blocking operations (BLPOP) and to - * map swapped keys to a list of clients waiting for this keys to be loaded. */ -dictType keylistDictType = { - dictObjHash, /* hash function */ - NULL, /* key dup */ - NULL, /* val dup */ - dictObjKeyCompare, /* key compare */ - dictRedisObjectDestructor, /* key destructor */ - dictListDestructor /* val destructor */ -}; - -int htNeedsResize(dict *dict) { - long long size, used; - - size = dictSlots(dict); - used = dictSize(dict); - return (size && used && size > DICT_HT_INITIAL_SIZE && - (used*100/size < REDIS_HT_MINFILL)); -} - -/* If the percentage of used slots in the HT reaches REDIS_HT_MINFILL - * we resize the hash table to save memory */ -void tryResizeHashTables(void) { - int j; - - for (j = 0; j < server.dbnum; j++) { - if (htNeedsResize(server.db[j].dict)) - dictResize(server.db[j].dict); - if (htNeedsResize(server.db[j].expires)) - dictResize(server.db[j].expires); - } -} - -/* Our hash table implementation performs rehashing incrementally while - * we write/read from the hash table. Still if the server is idle, the hash - * table will use two tables for a long time. So we try to use 1 millisecond - * of CPU time at every serverCron() loop in order to rehash some key. */ -void incrementallyRehash(void) { - int j; - - for (j = 0; j < server.dbnum; j++) { - /* Keys dictionary */ - if (dictIsRehashing(server.db[j].dict)) { - dictRehashMilliseconds(server.db[j].dict,1); - break; /* already used our millisecond for this loop... */ - } - /* Expires */ - if (dictIsRehashing(server.db[j].expires)) { - dictRehashMilliseconds(server.db[j].expires,1); - break; /* already used our millisecond for this loop... */ - } - } -} - -/* This function is called once a background process of some kind terminates, - * as we want to avoid resizing the hash tables when there is a child in order - * to play well with copy-on-write (otherwise when a resize happens lots of - * memory pages are copied). The goal of this function is to update the ability - * for dict.c to resize the hash tables accordingly to the fact we have o not - * running childs. */ -void updateDictResizePolicy(void) { - if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) - dictEnableResize(); - else - dictDisableResize(); -} - -/* ======================= Cron: called every 100 ms ======================== */ - -/* Try to expire a few timed out keys. The algorithm used is adaptive and - * will use few CPU cycles if there are few expiring keys, otherwise - * it will get more aggressive to avoid that too much memory is used by - * keys that can be removed from the keyspace. */ -void activeExpireCycle(void) { - int j, iteration = 0; - long long start = ustime(), timelimit; - - /* We can use at max REDIS_EXPIRELOOKUPS_TIME_PERC percentage of CPU time - * per iteration. Since this function gets called with a frequency of - * REDIS_HZ times per second, the following is the max amount of - * microseconds we can spend in this function. */ - timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/REDIS_HZ/100; - if (timelimit <= 0) timelimit = 1; - - for (j = 0; j < server.dbnum; j++) { - int expired; - redisDb *db = server.db+j; - - /* Continue to expire if at the end of the cycle more than 25% - * of the keys were expired. */ - do { - unsigned long num = dictSize(db->expires); - unsigned long slots = dictSlots(db->expires); - long long now = mstime(); - - /* When there are less than 1% filled slots getting random - * keys is expensive, so stop here waiting for better times... - * The dictionary will be resized asap. */ - if (num && slots > DICT_HT_INITIAL_SIZE && - (num*100/slots < 1)) break; - - /* The main collection cycle. Sample random keys among keys - * with an expire set, checking for expired ones. */ - expired = 0; - if (num > REDIS_EXPIRELOOKUPS_PER_CRON) - num = REDIS_EXPIRELOOKUPS_PER_CRON; - while (num--) { - dictEntry *de; - long long t; - - if ((de = dictGetRandomKey(db->expires)) == NULL) break; - t = dictGetSignedIntegerVal(de); - if (now > t) { - sds key = dictGetKey(de); - robj *keyobj = createStringObject(key,sdslen(key)); - - propagateExpire(db,keyobj); - dbDelete(db,keyobj); - decrRefCount(keyobj); - expired++; - server.stat_expiredkeys++; - } - } - /* We can't block forever here even if there are many keys to - * expire. So after a given amount of milliseconds return to the - * caller waiting for the other active expire cycle. */ - iteration++; - if ((iteration & 0xf) == 0 && /* check once every 16 cycles. */ - (ustime()-start) > timelimit) return; - } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4); - } -} - -void updateLRUClock(void) { - server.lruclock = (server.unixtime/REDIS_LRU_CLOCK_RESOLUTION) & - REDIS_LRU_CLOCK_MAX; -} - - -/* Add a sample to the operations per second array of samples. */ -void trackOperationsPerSecond(void) { - long long t = mstime() - server.ops_sec_last_sample_time; - long long ops = server.stat_numcommands - server.ops_sec_last_sample_ops; - long long ops_sec; - - ops_sec = t > 0 ? (ops*1000/t) : 0; - - server.ops_sec_samples[server.ops_sec_idx] = ops_sec; - server.ops_sec_idx = (server.ops_sec_idx+1) % REDIS_OPS_SEC_SAMPLES; - server.ops_sec_last_sample_time = mstime(); - server.ops_sec_last_sample_ops = server.stat_numcommands; -} - -/* Return the mean of all the samples. */ -long long getOperationsPerSecond(void) { - int j; - long long sum = 0; - - for (j = 0; j < REDIS_OPS_SEC_SAMPLES; j++) - sum += server.ops_sec_samples[j]; - return sum / REDIS_OPS_SEC_SAMPLES; -} - -/* Check for timeouts. Returns non-zero if the client was terminated */ -int clientsCronHandleTimeout(redisClient *c) { - time_t now = server.unixtime; - - if (server.maxidletime && - !(c->flags & REDIS_SLAVE) && /* no timeout for slaves */ - !(c->flags & REDIS_MASTER) && /* no timeout for masters */ - !(c->flags & REDIS_BLOCKED) && /* no timeout for BLPOP */ - dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */ - listLength(c->pubsub_patterns) == 0 && - (now - c->lastinteraction > server.maxidletime)) - { - redisLog(REDIS_VERBOSE,"Closing idle client"); - freeClient(c); - return 1; - } else if (c->flags & REDIS_BLOCKED) { - if (c->bpop.timeout != 0 && c->bpop.timeout < now) { - addReply(c,shared.nullmultibulk); - unblockClientWaitingData(c); - } - } - return 0; -} - -/* The client query buffer is an sds.c string that can end with a lot of - * free space not used, this function reclaims space if needed. - * - * The funciton always returns 0 as it never terminates the client. */ -int clientsCronResizeQueryBuffer(redisClient *c) { - size_t querybuf_size = sdsAllocSize(c->querybuf); - time_t idletime = server.unixtime - c->lastinteraction; - - /* There are two conditions to resize the query buffer: - * 1) Query buffer is > BIG_ARG and too big for latest peak. - * 2) Client is inactive and the buffer is bigger than 1k. */ - if (((querybuf_size > REDIS_MBULK_BIG_ARG) && - (querybuf_size/(c->querybuf_peak+1)) > 2) || - (querybuf_size > 1024 && idletime > 2)) - { - /* Only resize the query buffer if it is actually wasting space. */ - if (sdsavail(c->querybuf) > 1024) { - c->querybuf = sdsRemoveFreeSpace(c->querybuf); - } - } - /* Reset the peak again to capture the peak memory usage in the next - * cycle. */ - c->querybuf_peak = 0; - return 0; -} - -void clientsCron(void) { - /* Make sure to process at least 1/(REDIS_HZ*10) of clients per call. - * Since this function is called REDIS_HZ times per second we are sure that - * in the worst case we process all the clients in 10 seconds. - * In normal conditions (a reasonable number of clients) we process - * all the clients in a shorter time. */ - int numclients = listLength(server.clients); - int iterations = numclients/(REDIS_HZ*10); - - if (iterations < 50) - iterations = (numclients < 50) ? numclients : 50; - while(listLength(server.clients) && iterations--) { - redisClient *c; - listNode *head; - - /* Rotate the list, take the current head, process. - * This way if the client must be removed from the list it's the - * first element and we don't incur into O(N) computation. */ - listRotate(server.clients); - head = listFirst(server.clients); - c = listNodeValue(head); - /* The following functions do different service checks on the client. - * The protocol is that they return non-zero if the client was - * terminated. */ - if (clientsCronHandleTimeout(c)) continue; - if (clientsCronResizeQueryBuffer(c)) continue; - } -} - -/* This is our timer interrupt, called REDIS_HZ times per second. - * Here is where we do a number of things that need to be done asynchronously. - * For instance: - * - * - Active expired keys collection (it is also performed in a lazy way on - * lookup). - * - Software watchdong. - * - Update some statistic. - * - Incremental rehashing of the DBs hash tables. - * - Triggering BGSAVE / AOF rewrite, and handling of terminated children. - * - Clients timeout of differnet kinds. - * - Replication reconnection. - * - Many more... - * - * Everything directly called here will be called REDIS_HZ times per second, - * so in order to throttle execution of things we want to do less frequently - * a macro is used: run_with_period(milliseconds) { .... } - */ - -int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { - int j; - REDIS_NOTUSED(eventLoop); - REDIS_NOTUSED(id); - REDIS_NOTUSED(clientData); - - /* Software watchdog: deliver the SIGALRM that will reach the signal - * handler if we don't return here fast enough. */ - if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period); - - /* We take a cached value of the unix time in the global state because - * with virtual memory and aging there is to store the current time - * in objects at every object access, and accuracy is not needed. - * To access a global var is faster than calling time(NULL) */ - server.unixtime = time(NULL); - - run_with_period(100) trackOperationsPerSecond(); - - /* We have just 22 bits per object for LRU information. - * So we use an (eventually wrapping) LRU clock with 10 seconds resolution. - * 2^22 bits with 10 seconds resoluton is more or less 1.5 years. - * - * Note that even if this will wrap after 1.5 years it's not a problem, - * everything will still work but just some object will appear younger - * to Redis. But for this to happen a given object should never be touched - * for 1.5 years. - * - * Note that you can change the resolution altering the - * REDIS_LRU_CLOCK_RESOLUTION define. - */ - updateLRUClock(); - - /* Record the max memory used since the server was started. */ - if (zmalloc_used_memory() > server.stat_peak_memory) - server.stat_peak_memory = zmalloc_used_memory(); - - /* We received a SIGTERM, shutting down here in a safe way, as it is - * not ok doing so inside the signal handler. */ - if (server.shutdown_asap) { - if (prepareForShutdown(0) == REDIS_OK) exit(0); - redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information"); - } - - /* Show some info about non-empty databases */ - run_with_period(5000) { - for (j = 0; j < server.dbnum; j++) { - long long size, used, vkeys; - - size = dictSlots(server.db[j].dict); - used = dictSize(server.db[j].dict); - vkeys = dictSize(server.db[j].expires); - if (used || vkeys) { - redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size); - /* dictPrintStats(server.dict); */ - } - } - } - - /* We don't want to resize the hash tables while a bacground saving - * is in progress: the saving child is created using fork() that is - * implemented with a copy-on-write semantic in most modern systems, so - * if we resize the HT while there is the saving child at work actually - * a lot of memory movements in the parent will cause a lot of pages - * copied. */ - if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) { - tryResizeHashTables(); - if (server.activerehashing) incrementallyRehash(); - } - - /* Show information about connected clients */ - if (!server.sentinel_mode) { - run_with_period(5000) { - redisLog(REDIS_VERBOSE, - "%d clients connected (%d slaves), %zu bytes in use", - listLength(server.clients)-listLength(server.slaves), - listLength(server.slaves), - zmalloc_used_memory()); - } - } - - /* We need to do a few operations on clients asynchronously. */ - clientsCron(); - - /* Start a scheduled AOF rewrite if this was requested by the user while - * a BGSAVE was in progress. */ - if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 && - server.aof_rewrite_scheduled) - { - rewriteAppendOnlyFileBackground(); - } - - /* Check if a background saving or AOF rewrite in progress terminated. */ - if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) { - int statloc; - pid_t pid; - - if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) { - int exitcode = WEXITSTATUS(statloc); - int bysignal = 0; - - if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc); - - if (pid == server.rdb_child_pid) { - backgroundSaveDoneHandler(exitcode,bysignal); - } else if (pid == server.aof_child_pid) { - backgroundRewriteDoneHandler(exitcode,bysignal); - } else { - redisLog(REDIS_WARNING, - "Warning, detected child with unmatched pid: %ld", - (long)pid); - } - updateDictResizePolicy(); - } - } else { - /* If there is not a background saving/rewrite in progress check if - * we have to save/rewrite now */ - for (j = 0; j < server.saveparamslen; j++) { - struct saveparam *sp = server.saveparams+j; - - if (server.dirty >= sp->changes && - server.unixtime-server.lastsave > sp->seconds) { - redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...", - sp->changes, sp->seconds); - rdbSaveBackground(server.rdb_filename); - break; - } - } - - /* Trigger an AOF rewrite if needed */ - if (server.rdb_child_pid == -1 && - server.aof_child_pid == -1 && - server.aof_rewrite_perc && - server.aof_current_size > server.aof_rewrite_min_size) - { - long long base = server.aof_rewrite_base_size ? - server.aof_rewrite_base_size : 1; - long long growth = (server.aof_current_size*100/base) - 100; - if (growth >= server.aof_rewrite_perc) { - redisLog(REDIS_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth); - rewriteAppendOnlyFileBackground(); - } - } - } - - - /* If we postponed an AOF buffer flush, let's try to do it every time the - * cron function is called. */ - if (server.aof_flush_postponed_start) flushAppendOnlyFile(0); - - /* Expire a few keys per cycle, only if this is a master. - * On slaves we wait for DEL operations synthesized by the master - * in order to guarantee a strict consistency. */ - if (server.masterhost == NULL) activeExpireCycle(); - - /* Close clients that need to be closed asynchronous */ - freeClientsInAsyncFreeQueue(); - - /* Replication cron function -- used to reconnect to master and - * to detect transfer failures. */ - run_with_period(1000) replicationCron(); - - /* Run the sentinel timer if we are in sentinel mode. */ - run_with_period(100) { - if (server.sentinel_mode) sentinelTimer(); - } - - server.cronloops++; - return 1000/REDIS_HZ; -} - -/* This function gets called every time Redis is entering the - * main loop of the event driven library, that is, before to sleep - * for ready file descriptors. */ -void beforeSleep(struct aeEventLoop *eventLoop) { - REDIS_NOTUSED(eventLoop); - listNode *ln; - redisClient *c; - - /* Try to process pending commands for clients that were just unblocked. */ - while (listLength(server.unblocked_clients)) { - ln = listFirst(server.unblocked_clients); - redisAssert(ln != NULL); - c = ln->value; - listDelNode(server.unblocked_clients,ln); - c->flags &= ~REDIS_UNBLOCKED; - - /* Process remaining data in the input buffer. */ - if (c->querybuf && sdslen(c->querybuf) > 0) { - server.current_client = c; - processInputBuffer(c); - server.current_client = NULL; - } - } - - /* Write the AOF buffer on disk */ - flushAppendOnlyFile(0); -} - -/* =========================== Server initialization ======================== */ - -void createSharedObjects(void) { - int j; - - shared.crlf = createObject(REDIS_STRING,sdsnew("\r\n")); - shared.ok = createObject(REDIS_STRING,sdsnew("+OK\r\n")); - shared.err = createObject(REDIS_STRING,sdsnew("-ERR\r\n")); - shared.emptybulk = createObject(REDIS_STRING,sdsnew("$0\r\n\r\n")); - shared.czero = createObject(REDIS_STRING,sdsnew(":0\r\n")); - shared.cone = createObject(REDIS_STRING,sdsnew(":1\r\n")); - shared.cnegone = createObject(REDIS_STRING,sdsnew(":-1\r\n")); - shared.nullbulk = createObject(REDIS_STRING,sdsnew("$-1\r\n")); - shared.nullmultibulk = createObject(REDIS_STRING,sdsnew("*-1\r\n")); - shared.emptymultibulk = createObject(REDIS_STRING,sdsnew("*0\r\n")); - shared.pong = createObject(REDIS_STRING,sdsnew("+PONG\r\n")); - shared.queued = createObject(REDIS_STRING,sdsnew("+QUEUED\r\n")); - shared.wrongtypeerr = createObject(REDIS_STRING,sdsnew( - "-ERR Operation against a key holding the wrong kind of value\r\n")); - shared.nokeyerr = createObject(REDIS_STRING,sdsnew( - "-ERR no such key\r\n")); - shared.syntaxerr = createObject(REDIS_STRING,sdsnew( - "-ERR syntax error\r\n")); - shared.sameobjecterr = createObject(REDIS_STRING,sdsnew( - "-ERR source and destination objects are the same\r\n")); - shared.outofrangeerr = createObject(REDIS_STRING,sdsnew( - "-ERR index out of range\r\n")); - shared.noscripterr = createObject(REDIS_STRING,sdsnew( - "-NOSCRIPT No matching script. Please use EVAL.\r\n")); - shared.loadingerr = createObject(REDIS_STRING,sdsnew( - "-LOADING Redis is loading the dataset in memory\r\n")); - shared.slowscripterr = createObject(REDIS_STRING,sdsnew( - "-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n")); - shared.masterdownerr = createObject(REDIS_STRING,sdsnew( - "-MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'.\r\n")); - shared.bgsaveerr = createObject(REDIS_STRING,sdsnew( - "-MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.\r\n")); - shared.roslaveerr = createObject(REDIS_STRING,sdsnew( - "-READONLY You can't write against a read only slave.\r\n")); - shared.oomerr = createObject(REDIS_STRING,sdsnew( - "-OOM command not allowed when used memory > 'maxmemory'.\r\n")); - shared.execaborterr = createObject(REDIS_STRING,sdsnew( - "-EXECABORT Transaction discarded because of previous errors.\r\n")); - shared.space = createObject(REDIS_STRING,sdsnew(" ")); - shared.colon = createObject(REDIS_STRING,sdsnew(":")); - shared.plus = createObject(REDIS_STRING,sdsnew("+")); - - for (j = 0; j < REDIS_SHARED_SELECT_CMDS; j++) { - shared.select[j] = createObject(REDIS_STRING, - sdscatprintf(sdsempty(),"select %d\r\n", j)); - } - shared.messagebulk = createStringObject("$7\r\nmessage\r\n",13); - shared.pmessagebulk = createStringObject("$8\r\npmessage\r\n",14); - shared.subscribebulk = createStringObject("$9\r\nsubscribe\r\n",15); - shared.unsubscribebulk = createStringObject("$11\r\nunsubscribe\r\n",18); - shared.psubscribebulk = createStringObject("$10\r\npsubscribe\r\n",17); - shared.punsubscribebulk = createStringObject("$12\r\npunsubscribe\r\n",19); - shared.del = createStringObject("DEL",3); - shared.rpop = createStringObject("RPOP",4); - shared.lpop = createStringObject("LPOP",4); - shared.lpush = createStringObject("LPUSH",5); - for (j = 0; j < REDIS_SHARED_INTEGERS; j++) { - shared.integers[j] = createObject(REDIS_STRING,(void*)(long)j); - shared.integers[j]->encoding = REDIS_ENCODING_INT; - } - for (j = 0; j < REDIS_SHARED_BULKHDR_LEN; j++) { - shared.mbulkhdr[j] = createObject(REDIS_STRING, - sdscatprintf(sdsempty(),"*%d\r\n",j)); - shared.bulkhdr[j] = createObject(REDIS_STRING, - sdscatprintf(sdsempty(),"$%d\r\n",j)); - } -} - -void initServerConfig() { - getRandomHexChars(server.runid,REDIS_RUN_ID_SIZE); - server.runid[REDIS_RUN_ID_SIZE] = '\0'; - server.arch_bits = (sizeof(long) == 8) ? 64 : 32; - server.port = REDIS_SERVERPORT; - server.bindaddr = NULL; - server.unixsocket = NULL; - server.unixsocketperm = 0; - server.ipfd = -1; - server.sofd = -1; - server.dbnum = REDIS_DEFAULT_DBNUM; - server.verbosity = REDIS_NOTICE; - server.maxidletime = REDIS_MAXIDLETIME; - server.client_max_querybuf_len = REDIS_MAX_QUERYBUF_LEN; - server.saveparams = NULL; - server.loading = 0; - server.logfile = NULL; /* NULL = log on standard output */ - server.syslog_enabled = 0; - server.syslog_ident = zstrdup("redis"); - server.syslog_facility = LOG_LOCAL0; - server.daemonize = 0; - server.aof_state = REDIS_AOF_OFF; - server.aof_fsync = AOF_FSYNC_EVERYSEC; - server.aof_no_fsync_on_rewrite = 0; - server.aof_rewrite_perc = REDIS_AOF_REWRITE_PERC; - server.aof_rewrite_min_size = REDIS_AOF_REWRITE_MIN_SIZE; - server.aof_rewrite_base_size = 0; - server.aof_rewrite_scheduled = 0; - server.aof_last_fsync = time(NULL); - server.aof_rewrite_time_last = -1; - server.aof_rewrite_time_start = -1; - server.aof_lastbgrewrite_status = REDIS_OK; - server.aof_delayed_fsync = 0; - server.aof_fd = -1; - server.aof_selected_db = -1; /* Make sure the first time will not match */ - server.aof_flush_postponed_start = 0; - server.pidfile = zstrdup("/var/run/redis.pid"); - server.rdb_filename = zstrdup("dump.rdb"); - server.aof_filename = zstrdup("appendonly.aof"); - server.requirepass = NULL; - server.rdb_compression = 1; - server.rdb_checksum = 1; - server.activerehashing = 1; - server.maxclients = REDIS_MAX_CLIENTS; - server.bpop_blocked_clients = 0; - server.maxmemory = 0; - server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU; - server.maxmemory_samples = 3; - server.hash_max_ziplist_entries = REDIS_HASH_MAX_ZIPLIST_ENTRIES; - server.hash_max_ziplist_value = REDIS_HASH_MAX_ZIPLIST_VALUE; - server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES; - server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE; - 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.shutdown_asap = 0; - server.repl_ping_slave_period = REDIS_REPL_PING_SLAVE_PERIOD; - server.repl_timeout = REDIS_REPL_TIMEOUT; - server.lua_caller = NULL; - server.lua_time_limit = REDIS_LUA_TIME_LIMIT; - server.lua_client = NULL; - server.lua_timedout = 0; - - updateLRUClock(); - resetServerSaveParams(); - - appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */ - appendServerSaveParams(300,100); /* save after 5 minutes and 100 changes */ - appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */ - /* Replication related */ - server.masterauth = NULL; - server.masterhost = NULL; - server.masterport = 6379; - server.master = NULL; - server.repl_state = REDIS_REPL_NONE; - server.repl_syncio_timeout = REDIS_REPL_SYNCIO_TIMEOUT; - server.repl_serve_stale_data = 1; - server.repl_slave_ro = 1; - server.repl_down_since = time(NULL); - server.slave_priority = REDIS_DEFAULT_SLAVE_PRIORITY; - - /* Client output buffer limits */ - server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_NORMAL].hard_limit_bytes = 0; - server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_NORMAL].soft_limit_bytes = 0; - server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_NORMAL].soft_limit_seconds = 0; - server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_SLAVE].hard_limit_bytes = 1024*1024*256; - server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_SLAVE].soft_limit_bytes = 1024*1024*64; - server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_SLAVE].soft_limit_seconds = 60; - server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_PUBSUB].hard_limit_bytes = 1024*1024*32; - server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_PUBSUB].soft_limit_bytes = 1024*1024*8; - server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_PUBSUB].soft_limit_seconds = 60; - - /* Double constants initialization */ - R_Zero = 0.0; - R_PosInf = 1.0/R_Zero; - R_NegInf = -1.0/R_Zero; - R_Nan = R_Zero/R_Zero; - - /* Command table -- we intiialize it here as it is part of the - * initial configuration, since command names may be changed via - * redis.conf using the rename-command directive. */ - server.commands = dictCreate(&commandTableDictType,NULL); - populateCommandTable(); - server.delCommand = lookupCommandByCString("del"); - server.multiCommand = lookupCommandByCString("multi"); - server.lpushCommand = lookupCommandByCString("lpush"); - server.lpopCommand = lookupCommandByCString("lpop"); - server.rpopCommand = lookupCommandByCString("rpop"); - - /* Slow log */ - server.slowlog_log_slower_than = REDIS_SLOWLOG_LOG_SLOWER_THAN; - server.slowlog_max_len = REDIS_SLOWLOG_MAX_LEN; - - /* Debugging */ - server.assert_failed = ""; - server.assert_file = ""; - server.assert_line = 0; - server.bug_report_start = 0; - server.watchdog_period = 0; -} - -/* This function will try to raise the max number of open files accordingly to - * the configured max number of clients. It will also account for 32 additional - * file descriptors as we need a few more for persistence, listening - * sockets, log files and so forth. - * - * If it will not be possible to set the limit accordingly to the configured - * max number of clients, the function will do the reverse setting - * server.maxclients to the value that we can actually handle. */ -void adjustOpenFilesLimit(void) { - rlim_t maxfiles = server.maxclients+32; - struct rlimit limit; - - if (getrlimit(RLIMIT_NOFILE,&limit) == -1) { - redisLog(REDIS_WARNING,"Unable to obtain the current NOFILE limit (%s), assuming 1024 and setting the max clients configuration accordingly.", - strerror(errno)); - server.maxclients = 1024-32; - } else { - rlim_t oldlimit = limit.rlim_cur; - - /* Set the max number of files if the current limit is not enough - * for our needs. */ - if (oldlimit < maxfiles) { - rlim_t f; - - f = maxfiles; - while(f > oldlimit) { - limit.rlim_cur = f; - limit.rlim_max = f; - if (setrlimit(RLIMIT_NOFILE,&limit) != -1) break; - f -= 128; - } - if (f < oldlimit) f = oldlimit; - if (f != maxfiles) { - server.maxclients = f-32; - redisLog(REDIS_WARNING,"Unable to set the max number of files limit to %d (%s), setting the max clients configuration to %d.", - (int) maxfiles, strerror(errno), (int) server.maxclients); - } else { - redisLog(REDIS_NOTICE,"Max number of open files set to %d", - (int) maxfiles); - } - } - } -} - -void initServer() { - int j; - - signal(SIGHUP, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - setupSignalHandlers(); - - if (server.syslog_enabled) { - openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT, - server.syslog_facility); - } - - server.current_client = NULL; - server.clients = listCreate(); - server.clients_to_close = listCreate(); - server.slaves = listCreate(); - server.monitors = listCreate(); - server.unblocked_clients = listCreate(); - server.ready_keys = listCreate(); - - createSharedObjects(); - adjustOpenFilesLimit(); - server.el = aeCreateEventLoop(server.maxclients+1024); - server.db = zmalloc(sizeof(redisDb)*server.dbnum); - - if (server.port != 0) { - server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr); - if (server.ipfd == ANET_ERR) { - redisLog(REDIS_WARNING, "Opening port %d: %s", - server.port, server.neterr); - exit(1); - } - } - if (server.unixsocket != NULL) { - unlink(server.unixsocket); /* don't care if this fails */ - server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm); - if (server.sofd == ANET_ERR) { - redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr); - exit(1); - } - } - if (server.ipfd < 0 && server.sofd < 0) { - redisLog(REDIS_WARNING, "Configured to not listen anywhere, exiting."); - exit(1); - } - for (j = 0; j < server.dbnum; j++) { - server.db[j].dict = dictCreate(&dbDictType,NULL); - server.db[j].expires = dictCreate(&keyptrDictType,NULL); - server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL); - server.db[j].ready_keys = dictCreate(&setDictType,NULL); - server.db[j].watched_keys = dictCreate(&keylistDictType,NULL); - server.db[j].id = j; - } - server.pubsub_channels = dictCreate(&keylistDictType,NULL); - server.pubsub_patterns = listCreate(); - listSetFreeMethod(server.pubsub_patterns,freePubsubPattern); - listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern); - server.cronloops = 0; - server.rdb_child_pid = -1; - server.aof_child_pid = -1; - aofRewriteBufferReset(); - server.aof_buf = sdsempty(); - server.lastsave = time(NULL); - server.rdb_save_time_last = -1; - server.rdb_save_time_start = -1; - server.dirty = 0; - server.stat_numcommands = 0; - server.stat_numconnections = 0; - server.stat_expiredkeys = 0; - server.stat_evictedkeys = 0; - server.stat_starttime = time(NULL); - server.stat_keyspace_misses = 0; - server.stat_keyspace_hits = 0; - server.stat_peak_memory = 0; - server.stat_fork_time = 0; - server.stat_rejected_conn = 0; - memset(server.ops_sec_samples,0,sizeof(server.ops_sec_samples)); - server.ops_sec_idx = 0; - server.ops_sec_last_sample_time = mstime(); - server.ops_sec_last_sample_ops = 0; - server.unixtime = time(NULL); - server.lastbgsave_status = REDIS_OK; - server.stop_writes_on_bgsave_err = 1; - aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL); - if (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE, - acceptTcpHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.ipfd file event."); - if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE, - acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event."); - - if (server.aof_state == REDIS_AOF_ON) { - server.aof_fd = open(server.aof_filename, - O_WRONLY|O_APPEND|O_CREAT,0644); - if (server.aof_fd == -1) { - redisLog(REDIS_WARNING, "Can't open the append-only file: %s", - strerror(errno)); - exit(1); - } - } - - /* 32 bit instances are limited to 4GB of address space, so if there is - * no explicit limit in the user provided configuration we set a limit - * at 3 GB using maxmemory with 'noeviction' policy'. This avoids - * useless crashes of the Redis instance for out of memory. */ - if (server.arch_bits == 32 && server.maxmemory == 0) { - redisLog(REDIS_WARNING,"Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now."); - server.maxmemory = 3072LL*(1024*1024); /* 3 GB */ - server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION; - } - - scriptingInit(); - slowlogInit(); - bioInit(); - ds_init(); -} - -/* Populates the Redis Command Table starting from the hard coded list - * we have on top of redis.c file. */ -void populateCommandTable(void) { - int j; - int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand); - - for (j = 0; j < numcommands; j++) { - struct redisCommand *c = redisCommandTable+j; - char *f = c->sflags; - int retval; - - while(*f != '\0') { - switch(*f) { - case 'w': c->flags |= REDIS_CMD_WRITE; break; - case 'r': c->flags |= REDIS_CMD_READONLY; break; - case 'm': c->flags |= REDIS_CMD_DENYOOM; break; - case 'a': c->flags |= REDIS_CMD_ADMIN; break; - case 'p': c->flags |= REDIS_CMD_PUBSUB; break; - case 'f': c->flags |= REDIS_CMD_FORCE_REPLICATION; break; - case 's': c->flags |= REDIS_CMD_NOSCRIPT; break; - case 'R': c->flags |= REDIS_CMD_RANDOM; break; - case 'S': c->flags |= REDIS_CMD_SORT_FOR_SCRIPT; break; - case 'l': c->flags |= REDIS_CMD_LOADING; break; - case 't': c->flags |= REDIS_CMD_STALE; break; - case 'M': c->flags |= REDIS_CMD_SKIP_MONITOR; break; - default: redisPanic("Unsupported command flag"); break; - } - f++; - } - - retval = dictAdd(server.commands, sdsnew(c->name), c); - assert(retval == DICT_OK); - } -} - -void resetCommandTableStats(void) { - int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand); - int j; - - for (j = 0; j < numcommands; j++) { - struct redisCommand *c = redisCommandTable+j; - - c->microseconds = 0; - c->calls = 0; - } -} - -/* ========================== Redis OP Array API ============================ */ - -void redisOpArrayInit(redisOpArray *oa) { - oa->ops = NULL; - oa->numops = 0; -} - -int redisOpArrayAppend(redisOpArray *oa, struct redisCommand *cmd, int dbid, - robj **argv, int argc, int target) -{ - redisOp *op; - - oa->ops = zrealloc(oa->ops,sizeof(redisOp)*(oa->numops+1)); - op = oa->ops+oa->numops; - op->cmd = cmd; - op->dbid = dbid; - op->argv = argv; - op->argc = argc; - op->target = target; - oa->numops++; - return oa->numops; -} - -void redisOpArrayFree(redisOpArray *oa) { - while(oa->numops) { - int j; - redisOp *op; - - oa->numops--; - op = oa->ops+oa->numops; - for (j = 0; j < op->argc; j++) - decrRefCount(op->argv[j]); - zfree(op->argv); - } - zfree(oa->ops); -} - -/* ====================== Commands lookup and execution ===================== */ - -struct redisCommand *lookupCommand(sds name) { - return dictFetchValue(server.commands, name); -} - -struct redisCommand *lookupCommandByCString(char *s) { - struct redisCommand *cmd; - sds name = sdsnew(s); - - cmd = dictFetchValue(server.commands, name); - sdsfree(name); - return cmd; -} - -/* Propagate the specified command (in the context of the specified database id) - * to AOF and Slaves. - * - * flags are an xor between: - * + REDIS_PROPAGATE_NONE (no propagation of command at all) - * + REDIS_PROPAGATE_AOF (propagate into the AOF file if is enabled) - * + REDIS_PROPAGATE_REPL (propagate into the replication link) - */ -void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, - int flags) -{ - if (server.aof_state != REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF) - feedAppendOnlyFile(cmd,dbid,argv,argc); - if (flags & REDIS_PROPAGATE_REPL && listLength(server.slaves)) - replicationFeedSlaves(server.slaves,dbid,argv,argc); -} - -/* Used inside commands to schedule the propagation of additional commands - * after the current command is propagated to AOF / Replication. */ -void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, - int target) -{ - redisOpArrayAppend(&server.also_propagate,cmd,dbid,argv,argc,target); -} - -/* Call() is the core of Redis execution of a command */ -void call(redisClient *c, int flags) { - long long dirty, start = ustime(), duration; - - /* Sent the command to clients in MONITOR mode, only if the commands are - * not geneated from reading an AOF. */ - if (listLength(server.monitors) && - !server.loading && - !(c->cmd->flags & REDIS_CMD_SKIP_MONITOR)) - { - replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc); - } - - /* Call the command. */ - redisOpArrayInit(&server.also_propagate); - dirty = server.dirty; - c->cmd->proc(c); - dirty = server.dirty-dirty; - duration = ustime()-start; - - /* When EVAL is called loading the AOF we don't want commands called - * from Lua to go into the slowlog or to populate statistics. */ - if (server.loading && c->flags & REDIS_LUA_CLIENT) - flags &= ~(REDIS_CALL_SLOWLOG | REDIS_CALL_STATS); - - /* Log the command into the Slow log if needed, and populate the - * per-command statistics that we show in INFO commandstats. */ - if (flags & REDIS_CALL_SLOWLOG) - slowlogPushEntryIfNeeded(c->argv,c->argc,duration); - if (flags & REDIS_CALL_STATS) { - c->cmd->microseconds += duration; - c->cmd->calls++; - } - - /* Propagate the command into the AOF and replication link */ - if (flags & REDIS_CALL_PROPAGATE) { - int flags = REDIS_PROPAGATE_NONE; - - if (c->cmd->flags & REDIS_CMD_FORCE_REPLICATION) - flags |= REDIS_PROPAGATE_REPL; - if (dirty) - flags |= (REDIS_PROPAGATE_REPL | REDIS_PROPAGATE_AOF); - if (flags != REDIS_PROPAGATE_NONE) - propagate(c->cmd,c->db->id,c->argv,c->argc,flags); - } - /* Commands such as LPUSH or BRPOPLPUSH may propagate an additional - * PUSH command. */ - if (server.also_propagate.numops) { - int j; - redisOp *rop; - - for (j = 0; j < server.also_propagate.numops; j++) { - rop = &server.also_propagate.ops[j]; - propagate(rop->cmd, rop->dbid, rop->argv, rop->argc, rop->target); - } - redisOpArrayFree(&server.also_propagate); - } - server.stat_numcommands++; -} - -/* If this function gets called we already read a whole - * command, arguments are in the client argv/argc fields. - * processCommand() execute the command or prepare the - * server for a bulk read from the client. - * - * If 1 is returned the client is still alive and valid and - * and other operations can be performed by the caller. Otherwise - * if 0 is returned the client was destroied (i.e. after QUIT). */ -int processCommand(redisClient *c) { - /* The QUIT command is handled separately. Normal command procs will - * go through checking for replication and QUIT will cause trouble - * when FORCE_REPLICATION is enabled and would be implemented in - * a regular command proc. */ - if (!strcasecmp(c->argv[0]->ptr,"quit")) { - addReply(c,shared.ok); - c->flags |= REDIS_CLOSE_AFTER_REPLY; - return REDIS_ERR; - } - - /* Now lookup the command and check ASAP about trivial error conditions - * such as wrong arity, bad command name and so forth. */ - c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr); - if (!c->cmd) { - flagTransaction(c); - addReplyErrorFormat(c,"unknown command '%s'", - (char*)c->argv[0]->ptr); - return REDIS_OK; - } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) || - (c->argc < -c->cmd->arity)) { - flagTransaction(c); - addReplyErrorFormat(c,"wrong number of arguments for '%s' command", - c->cmd->name); - return REDIS_OK; - } - - /* Check if the user is authenticated */ - if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand) - { - flagTransaction(c); - addReplyError(c,"operation not permitted"); - return REDIS_OK; - } - - /* Handle the maxmemory directive. - * - * First we try to free some memory if possible (if there are volatile - * keys in the dataset). If there are not the only thing we can do - * is returning an error. */ - if (server.maxmemory) { - int retval = freeMemoryIfNeeded(); - if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) { - flagTransaction(c); - addReply(c, shared.oomerr); - return REDIS_OK; - } - } - - /* Don't accept write commands if there are problems persisting on disk. */ - if (server.stop_writes_on_bgsave_err && - server.saveparamslen > 0 - && server.lastbgsave_status == REDIS_ERR && - c->cmd->flags & REDIS_CMD_WRITE) - { - flagTransaction(c); - addReply(c, shared.bgsaveerr); - return REDIS_OK; - } - - /* Don't accept write commands if this is a read only slave. But - * accept write commands if this is our master. */ - if (server.masterhost && server.repl_slave_ro && - !(c->flags & REDIS_MASTER) && - c->cmd->flags & REDIS_CMD_WRITE) - { - addReply(c, shared.roslaveerr); - return REDIS_OK; - } - - /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */ - if ((dictSize(c->pubsub_channels) > 0 || listLength(c->pubsub_patterns) > 0) - && - c->cmd->proc != subscribeCommand && - c->cmd->proc != unsubscribeCommand && - c->cmd->proc != psubscribeCommand && - c->cmd->proc != punsubscribeCommand) { - addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context"); - return REDIS_OK; - } - - /* Only allow INFO and SLAVEOF when slave-serve-stale-data is no and - * we are a slave with a broken link with master. */ - if (server.masterhost && server.repl_state != REDIS_REPL_CONNECTED && - server.repl_serve_stale_data == 0 && - !(c->cmd->flags & REDIS_CMD_STALE)) - { - flagTransaction(c); - addReply(c, shared.masterdownerr); - return REDIS_OK; - } - - /* Loading DB? Return an error if the command has not the - * REDIS_CMD_LOADING flag. */ - if (server.loading && !(c->cmd->flags & REDIS_CMD_LOADING)) { - addReply(c, shared.loadingerr); - return REDIS_OK; - } - - /* Lua script too slow? Only allow commands with REDIS_CMD_STALE flag. */ - if (server.lua_timedout && - c->cmd->proc != authCommand && - !(c->cmd->proc == shutdownCommand && - c->argc == 2 && - tolower(((char*)c->argv[1]->ptr)[0]) == 'n') && - !(c->cmd->proc == scriptCommand && - c->argc == 2 && - tolower(((char*)c->argv[1]->ptr)[0]) == 'k')) - { - flagTransaction(c); - addReply(c, shared.slowscripterr); - return REDIS_OK; - } - - /* Exec the command */ - if (c->flags & REDIS_MULTI && - c->cmd->proc != execCommand && c->cmd->proc != discardCommand && - c->cmd->proc != multiCommand && c->cmd->proc != watchCommand) - { - queueMultiCommand(c); - addReply(c,shared.queued); - } else { - call(c,REDIS_CALL_FULL); - if (listLength(server.ready_keys)) - handleClientsBlockedOnLists(); - } - return REDIS_OK; -} - -/*================================== Shutdown =============================== */ - -int prepareForShutdown(int flags) { - int save = flags & REDIS_SHUTDOWN_SAVE; - int nosave = flags & REDIS_SHUTDOWN_NOSAVE; - - redisLog(REDIS_WARNING,"User requested shutdown..."); - /* Kill the saving child if there is a background saving in progress. - We want to avoid race conditions, for instance our saving child may - overwrite the synchronous saving did by SHUTDOWN. */ - if (server.rdb_child_pid != -1) { - redisLog(REDIS_WARNING,"There is a child saving an .rdb. Killing it!"); - kill(server.rdb_child_pid,SIGKILL); - rdbRemoveTempFile(server.rdb_child_pid); - } - if (server.aof_state != REDIS_AOF_OFF) { - /* Kill the AOF saving child as the AOF we already have may be longer - * but contains the full dataset anyway. */ - if (server.aof_child_pid != -1) { - redisLog(REDIS_WARNING, - "There is a child rewriting the AOF. Killing it!"); - kill(server.aof_child_pid,SIGKILL); - } - /* Append only file: fsync() the AOF and exit */ - redisLog(REDIS_NOTICE,"Calling fsync() on the AOF file."); - aof_fsync(server.aof_fd); - } - if ((server.saveparamslen > 0 && !nosave) || save) { - redisLog(REDIS_NOTICE,"Saving the final RDB snapshot before exiting."); - /* Snapshotting. Perform a SYNC SAVE and exit */ - if (rdbSave(server.rdb_filename) != REDIS_OK) { - /* Ooops.. error saving! The best we can do is to continue - * operating. Note that if there was a background saving process, - * in the next cron() Redis will be notified that the background - * saving aborted, handling special stuff like slaves pending for - * synchronization... */ - redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit."); - return REDIS_ERR; - } - } - if (server.daemonize) { - redisLog(REDIS_NOTICE,"Removing the pid file."); - unlink(server.pidfile); - } - /* Close the listening sockets. Apparently this allows faster restarts. */ - if (server.ipfd != -1) close(server.ipfd); - if (server.sofd != -1) close(server.sofd); - if (server.unixsocket) { - redisLog(REDIS_NOTICE,"Removing the unix socket file."); - unlink(server.unixsocket); /* don't care if this fails */ - } - - redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye..."); - return REDIS_OK; -} - -/*================================== Commands =============================== */ - -/* Return zero if strings are the same, non-zero if they are not. - * The comparison is performed in a way that prevents an attacker to obtain - * information about the nature of the strings just monitoring the execution - * time of the function. - * - * Note that limiting the comparison length to strings up to 512 bytes we - * can avoid leaking any information about the password length and any - * possible branch misprediction related leak. - */ -int time_independent_strcmp(char *a, char *b) { - char bufa[REDIS_AUTHPASS_MAX_LEN], bufb[REDIS_AUTHPASS_MAX_LEN]; - /* The above two strlen perform len(a) + len(b) operations where either - * a or b are fixed (our password) length, and the difference is only - * relative to the length of the user provided string, so no information - * leak is possible in the following two lines of code. */ - int alen = strlen(a); - int blen = strlen(b); - int j; - int diff = 0; - - /* We can't compare strings longer than our static buffers. - * Note that this will never pass the first test in practical circumstances - * so there is no info leak. */ - if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1; - - memset(bufa,0,sizeof(bufa)); /* Constant time. */ - memset(bufb,0,sizeof(bufb)); /* Constant time. */ - /* Again the time of the following two copies is proportional to - * len(a) + len(b) so no info is leaked. */ - memcpy(bufa,a,alen); - memcpy(bufb,b,blen); - - /* Always compare all the chars in the two buffers without - * conditional expressions. */ - for (j = 0; j < sizeof(bufa); j++) { - diff |= (bufa[j] ^ bufb[j]); - } - /* Length must be equal as well. */ - diff |= alen ^ blen; - return diff; /* If zero strings are the same. */ -} - -void authCommand(redisClient *c) { - if (!server.requirepass) { - addReplyError(c,"Client sent AUTH, but no password is set"); - } else if (!time_independent_strcmp(c->argv[1]->ptr, server.requirepass)) { - c->authenticated = 1; - addReply(c,shared.ok); - } else { - c->authenticated = 0; - addReplyError(c,"invalid password"); - } -} - -void pingCommand(redisClient *c) { - addReply(c,shared.pong); -} - -void echoCommand(redisClient *c) { - addReplyBulk(c,c->argv[1]); -} - -void timeCommand(redisClient *c) { - struct timeval tv; - - /* gettimeofday() can only fail if &tv is a bad addresss so we - * don't check for errors. */ - gettimeofday(&tv,NULL); - addReplyMultiBulkLen(c,2); - addReplyBulkLongLong(c,tv.tv_sec); - addReplyBulkLongLong(c,tv.tv_usec); -} - -/* Convert an amount of bytes into a human readable string in the form - * of 100B, 2G, 100M, 4K, and so forth. */ -void bytesToHuman(char *s, unsigned long long n) { - double d; - - if (n < 1024) { - /* Bytes */ - sprintf(s,"%lluB",n); - return; - } else if (n < (1024*1024)) { - d = (double)n/(1024); - sprintf(s,"%.2fK",d); - } else if (n < (1024LL*1024*1024)) { - d = (double)n/(1024*1024); - sprintf(s,"%.2fM",d); - } else if (n < (1024LL*1024*1024*1024)) { - d = (double)n/(1024LL*1024*1024); - sprintf(s,"%.2fG",d); - } -} - -/* Create the string returned by the INFO command. This is decoupled - * by the INFO command itself as we need to report the same information - * on memory corruption problems. */ -sds genRedisInfoString(char *section) { - sds info = sdsempty(); - time_t uptime = server.unixtime-server.stat_starttime; - int j, numcommands; - struct rusage self_ru, c_ru; - unsigned long lol, bib; - int allsections = 0, defsections = 0; - int sections = 0; - - if (section) { - allsections = strcasecmp(section,"all") == 0; - defsections = strcasecmp(section,"default") == 0; - } - - getrusage(RUSAGE_SELF, &self_ru); - getrusage(RUSAGE_CHILDREN, &c_ru); - getClientsMaxBuffers(&lol,&bib); - - /* Server */ - if (allsections || defsections || !strcasecmp(section,"server")) { - struct utsname name; - char *mode; - - if (server.sentinel_mode) mode = "sentinel"; - else mode = "standalone"; - - if (sections++) info = sdscat(info,"\r\n"); - uname(&name); - info = sdscatprintf(info, - "# Server\r\n" - "redis_version:%s\r\n" - "redis_git_sha1:%s\r\n" - "redis_git_dirty:%d\r\n" - "redis_mode:%s\r\n" - "os:%s %s %s\r\n" - "arch_bits:%d\r\n" - "multiplexing_api:%s\r\n" - "gcc_version:%d.%d.%d\r\n" - "process_id:%ld\r\n" - "run_id:%s\r\n" - "tcp_port:%d\r\n" - "uptime_in_seconds:%ld\r\n" - "uptime_in_days:%ld\r\n" - "lru_clock:%ld\r\n", - REDIS_VERSION, - redisGitSHA1(), - strtol(redisGitDirty(),NULL,10) > 0, - mode, - name.sysname, name.release, name.machine, - server.arch_bits, - aeGetApiName(), -#ifdef __GNUC__ - __GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__, -#else - 0,0,0, -#endif - (long) getpid(), - server.runid, - server.port, - uptime, - uptime/(3600*24), - (unsigned long) server.lruclock); - } - - /* Clients */ - if (allsections || defsections || !strcasecmp(section,"clients")) { - if (sections++) info = sdscat(info,"\r\n"); - info = sdscatprintf(info, - "# Clients\r\n" - "connected_clients:%lu\r\n" - "client_longest_output_list:%lu\r\n" - "client_biggest_input_buf:%lu\r\n" - "blocked_clients:%d\r\n", - listLength(server.clients)-listLength(server.slaves), - lol, bib, - server.bpop_blocked_clients); - } - - /* Memory */ - if (allsections || defsections || !strcasecmp(section,"memory")) { - char hmem[64]; - char peak_hmem[64]; - - bytesToHuman(hmem,zmalloc_used_memory()); - bytesToHuman(peak_hmem,server.stat_peak_memory); - if (sections++) info = sdscat(info,"\r\n"); - info = sdscatprintf(info, - "# Memory\r\n" - "used_memory:%zu\r\n" - "used_memory_human:%s\r\n" - "used_memory_rss:%zu\r\n" - "used_memory_peak:%zu\r\n" - "used_memory_peak_human:%s\r\n" - "used_memory_lua:%lld\r\n" - "mem_fragmentation_ratio:%.2f\r\n" - "mem_allocator:%s\r\n", - zmalloc_used_memory(), - hmem, - zmalloc_get_rss(), - server.stat_peak_memory, - peak_hmem, - ((long long)lua_gc(server.lua,LUA_GCCOUNT,0))*1024LL, - zmalloc_get_fragmentation_ratio(), - ZMALLOC_LIB - ); - } - - /* Persistence */ - if (allsections || defsections || !strcasecmp(section,"persistence")) { - if (sections++) info = sdscat(info,"\r\n"); - info = sdscatprintf(info, - "# Persistence\r\n" - "loading:%d\r\n" - "rdb_changes_since_last_save:%lld\r\n" - "rdb_bgsave_in_progress:%d\r\n" - "rdb_last_save_time:%ld\r\n" - "rdb_last_bgsave_status:%s\r\n" - "rdb_last_bgsave_time_sec:%ld\r\n" - "rdb_current_bgsave_time_sec:%ld\r\n" - "aof_enabled:%d\r\n" - "aof_rewrite_in_progress:%d\r\n" - "aof_rewrite_scheduled:%d\r\n" - "aof_last_rewrite_time_sec:%ld\r\n" - "aof_current_rewrite_time_sec:%ld\r\n" - "aof_last_bgrewrite_status:%s\r\n", - server.loading, - server.dirty, - server.rdb_child_pid != -1, - server.lastsave, - (server.lastbgsave_status == REDIS_OK) ? "ok" : "err", - server.rdb_save_time_last, - (server.rdb_child_pid == -1) ? - -1 : time(NULL)-server.rdb_save_time_start, - server.aof_state != REDIS_AOF_OFF, - server.aof_child_pid != -1, - server.aof_rewrite_scheduled, - server.aof_rewrite_time_last, - (server.aof_child_pid == -1) ? - -1 : time(NULL)-server.aof_rewrite_time_start, - (server.aof_lastbgrewrite_status == REDIS_OK) ? "ok" : "err"); - - if (server.aof_state != REDIS_AOF_OFF) { - info = sdscatprintf(info, - "aof_current_size:%lld\r\n" - "aof_base_size:%lld\r\n" - "aof_pending_rewrite:%d\r\n" - "aof_buffer_length:%zu\r\n" - "aof_rewrite_buffer_length:%lu\r\n" - "aof_pending_bio_fsync:%llu\r\n" - "aof_delayed_fsync:%lu\r\n", - (long long) server.aof_current_size, - (long long) server.aof_rewrite_base_size, - server.aof_rewrite_scheduled, - sdslen(server.aof_buf), - aofRewriteBufferSize(), - bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC), - server.aof_delayed_fsync); - } - - if (server.loading) { - double perc; - time_t eta, elapsed; - off_t remaining_bytes = server.loading_total_bytes- - server.loading_loaded_bytes; - - perc = ((double)server.loading_loaded_bytes / - server.loading_total_bytes) * 100; - - elapsed = server.unixtime-server.loading_start_time; - if (elapsed == 0) { - eta = 1; /* A fake 1 second figure if we don't have - enough info */ - } else { - eta = (elapsed*remaining_bytes)/server.loading_loaded_bytes; - } - - info = sdscatprintf(info, - "loading_start_time:%ld\r\n" - "loading_total_bytes:%llu\r\n" - "loading_loaded_bytes:%llu\r\n" - "loading_loaded_perc:%.2f\r\n" - "loading_eta_seconds:%ld\r\n" - ,(unsigned long) server.loading_start_time, - (unsigned long long) server.loading_total_bytes, - (unsigned long long) server.loading_loaded_bytes, - perc, - eta - ); - } - } - - /* Stats */ - if (allsections || defsections || !strcasecmp(section,"stats")) { - if (sections++) info = sdscat(info,"\r\n"); - info = sdscatprintf(info, - "# Stats\r\n" - "total_connections_received:%lld\r\n" - "total_commands_processed:%lld\r\n" - "instantaneous_ops_per_sec:%lld\r\n" - "rejected_connections:%lld\r\n" - "expired_keys:%lld\r\n" - "evicted_keys:%lld\r\n" - "keyspace_hits:%lld\r\n" - "keyspace_misses:%lld\r\n" - "pubsub_channels:%ld\r\n" - "pubsub_patterns:%lu\r\n" - "latest_fork_usec:%lld\r\n", - server.stat_numconnections, - server.stat_numcommands, - getOperationsPerSecond(), - server.stat_rejected_conn, - server.stat_expiredkeys, - server.stat_evictedkeys, - server.stat_keyspace_hits, - server.stat_keyspace_misses, - dictSize(server.pubsub_channels), - listLength(server.pubsub_patterns), - server.stat_fork_time); - } - - /* Replication */ - if (allsections || defsections || !strcasecmp(section,"replication")) { - if (sections++) info = sdscat(info,"\r\n"); - info = sdscatprintf(info, - "# Replication\r\n" - "role:%s\r\n", - server.masterhost == NULL ? "master" : "slave"); - if (server.masterhost) { - info = sdscatprintf(info, - "master_host:%s\r\n" - "master_port:%d\r\n" - "master_link_status:%s\r\n" - "master_last_io_seconds_ago:%d\r\n" - "master_sync_in_progress:%d\r\n" - ,server.masterhost, - server.masterport, - (server.repl_state == REDIS_REPL_CONNECTED) ? - "up" : "down", - server.master ? - ((int)(server.unixtime-server.master->lastinteraction)) : -1, - server.repl_state == REDIS_REPL_TRANSFER - ); - - if (server.repl_state == REDIS_REPL_TRANSFER) { - info = sdscatprintf(info, - "master_sync_left_bytes:%lld\r\n" - "master_sync_last_io_seconds_ago:%d\r\n" - , (long long) - (server.repl_transfer_size - server.repl_transfer_read), - (int)(server.unixtime-server.repl_transfer_lastio) - ); - } - - if (server.repl_state != REDIS_REPL_CONNECTED) { - info = sdscatprintf(info, - "master_link_down_since_seconds:%ld\r\n", - (long)server.unixtime-server.repl_down_since); - } - info = sdscatprintf(info, - "slave_priority:%d\r\n" - "slave_read_only:%d\r\n", - server.slave_priority, - server.repl_slave_ro); - } - info = sdscatprintf(info, - "connected_slaves:%lu\r\n", - listLength(server.slaves)); - if (listLength(server.slaves)) { - int slaveid = 0; - listNode *ln; - listIter li; - - listRewind(server.slaves,&li); - while((ln = listNext(&li))) { - redisClient *slave = listNodeValue(ln); - char *state = NULL; - char ip[32]; - int port; - - if (anetPeerToString(slave->fd,ip,&port) == -1) continue; - switch(slave->replstate) { - case REDIS_REPL_WAIT_BGSAVE_START: - case REDIS_REPL_WAIT_BGSAVE_END: - state = "wait_bgsave"; - break; - case REDIS_REPL_SEND_BULK: - state = "send_bulk"; - break; - case REDIS_REPL_ONLINE: - state = "online"; - break; - } - if (state == NULL) continue; - info = sdscatprintf(info,"slave%d:%s,%d,%s\r\n", - slaveid,ip,slave->slave_listening_port,state); - slaveid++; - } - } - } - - /* CPU */ - if (allsections || defsections || !strcasecmp(section,"cpu")) { - if (sections++) info = sdscat(info,"\r\n"); - info = sdscatprintf(info, - "# CPU\r\n" - "used_cpu_sys:%.2f\r\n" - "used_cpu_user:%.2f\r\n" - "used_cpu_sys_children:%.2f\r\n" - "used_cpu_user_children:%.2f\r\n", - (float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000, - (float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000, - (float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000, - (float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000); - } - - /* cmdtime */ - if (allsections || !strcasecmp(section,"commandstats")) { - if (sections++) info = sdscat(info,"\r\n"); - info = sdscatprintf(info, "# Commandstats\r\n"); - numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand); - for (j = 0; j < numcommands; j++) { - struct redisCommand *c = redisCommandTable+j; - - if (!c->calls) continue; - info = sdscatprintf(info, - "cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f\r\n", - c->name, c->calls, c->microseconds, - (c->calls == 0) ? 0 : ((float)c->microseconds/c->calls)); - } - } - - /* Key space */ - if (allsections || defsections || !strcasecmp(section,"keyspace")) { - if (sections++) info = sdscat(info,"\r\n"); - info = sdscatprintf(info, "# Keyspace\r\n"); - for (j = 0; j < server.dbnum; j++) { - long long keys, vkeys; - - keys = dictSize(server.db[j].dict); - vkeys = dictSize(server.db[j].expires); - if (keys || vkeys) { - info = sdscatprintf(info, "db%d:keys=%lld,expires=%lld\r\n", - j, keys, vkeys); - } - } - } - return info; -} - -void infoCommand(redisClient *c) { - char *section = c->argc == 2 ? c->argv[1]->ptr : "default"; - - if (c->argc > 2) { - addReply(c,shared.syntaxerr); - return; - } - sds info = genRedisInfoString(section); - addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n", - (unsigned long)sdslen(info))); - addReplySds(c,info); - addReply(c,shared.crlf); -} - -void monitorCommand(redisClient *c) { - /* ignore MONITOR if already slave or in monitor mode */ - if (c->flags & REDIS_SLAVE) return; - - c->flags |= (REDIS_SLAVE|REDIS_MONITOR); - c->slaveseldb = 0; - listAddNodeTail(server.monitors,c); - addReply(c,shared.ok); -} - -/* ============================ Maxmemory directive ======================== */ - -/* This function gets called when 'maxmemory' is set on the config file to limit - * the max memory used by the server, before processing a command. - * - * The goal of the function is to free enough memory to keep Redis under the - * configured memory limit. - * - * The function starts calculating how many bytes should be freed to keep - * Redis under the limit, and enters a loop selecting the best keys to - * evict accordingly to the configured policy. - * - * If all the bytes needed to return back under the limit were freed the - * function returns REDIS_OK, otherwise REDIS_ERR is returned, and the caller - * should block the execution of commands that will result in more memory - * used by the server. - */ -int freeMemoryIfNeeded(void) { - size_t mem_used, mem_tofree, mem_freed; - int slaves = listLength(server.slaves); - - /* Remove the size of slaves output buffers and AOF buffer from the - * count of used memory. */ - mem_used = zmalloc_used_memory(); - if (slaves) { - listIter li; - listNode *ln; - - listRewind(server.slaves,&li); - while((ln = listNext(&li))) { - redisClient *slave = listNodeValue(ln); - unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave); - if (obuf_bytes > mem_used) - mem_used = 0; - else - mem_used -= obuf_bytes; - } - } - if (server.aof_state != REDIS_AOF_OFF) { - mem_used -= sdslen(server.aof_buf); - mem_used -= aofRewriteBufferSize(); - } - - /* Check if we are over the memory limit. */ - if (mem_used <= server.maxmemory) return REDIS_OK; - - if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION) - return REDIS_ERR; /* We need to free memory, but policy forbids. */ - - /* Compute how much memory we need to free. */ - mem_tofree = mem_used - server.maxmemory; - mem_freed = 0; - while (mem_freed < mem_tofree) { - int j, k, keys_freed = 0; - - for (j = 0; j < server.dbnum; j++) { - long bestval = 0; /* just to prevent warning */ - sds bestkey = NULL; - struct dictEntry *de; - redisDb *db = server.db+j; - dict *dict; - - if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || - server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM) - { - dict = server.db[j].dict; - } else { - dict = server.db[j].expires; - } - if (dictSize(dict) == 0) continue; - - /* volatile-random and allkeys-random policy */ - if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM || - server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM) - { - de = dictGetRandomKey(dict); - bestkey = dictGetKey(de); - } - - /* volatile-lru and allkeys-lru policy */ - else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || - server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) - { - for (k = 0; k < server.maxmemory_samples; k++) { - sds thiskey; - long thisval; - robj *o; - - de = dictGetRandomKey(dict); - thiskey = dictGetKey(de); - /* When policy is volatile-lru we need an additional lookup - * to locate the real key, as dict is set to db->expires. */ - if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) - de = dictFind(db->dict, thiskey); - o = dictGetVal(de); - thisval = estimateObjectIdleTime(o); - - /* Higher idle time is better candidate for deletion */ - if (bestkey == NULL || thisval > bestval) { - bestkey = thiskey; - bestval = thisval; - } - } - } - - /* volatile-ttl */ - else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) { - for (k = 0; k < server.maxmemory_samples; k++) { - sds thiskey; - long thisval; - - de = dictGetRandomKey(dict); - thiskey = dictGetKey(de); - thisval = (long) dictGetVal(de); - - /* Expire sooner (minor expire unix timestamp) is better - * candidate for deletion */ - if (bestkey == NULL || thisval < bestval) { - bestkey = thiskey; - bestval = thisval; - } - } - } - - /* Finally remove the selected key. */ - if (bestkey) { - long long delta; - - robj *keyobj = createStringObject(bestkey,sdslen(bestkey)); - propagateExpire(db,keyobj); - /* We compute the amount of memory freed by dbDelete() alone. - * It is possible that actually the memory needed to propagate - * the DEL in AOF and replication link is greater than the one - * we are freeing removing the key, but we can't account for - * that otherwise we would never exit the loop. - * - * AOF and Output buffer memory will be freed eventually so - * we only care about memory used by the key space. */ - delta = (long long) zmalloc_used_memory(); - dbDelete(db,keyobj); - delta -= (long long) zmalloc_used_memory(); - mem_freed += delta; - server.stat_evictedkeys++; - decrRefCount(keyobj); - keys_freed++; - - /* When the memory to free starts to be big enough, we may - * start spending so much time here that is impossible to - * deliver data to the slaves fast enough, so we force the - * transmission here inside the loop. */ - if (slaves) flushSlavesOutputBuffers(); - } - } - if (!keys_freed) return REDIS_ERR; /* nothing to free... */ - } - return REDIS_OK; -} - -/* =================================== Main! ================================ */ - -#ifdef __linux__ -int linuxOvercommitMemoryValue(void) { - FILE *fp = fopen("/proc/sys/vm/overcommit_memory","r"); - char buf[64]; - - if (!fp) return -1; - if (fgets(buf,64,fp) == NULL) { - fclose(fp); - return -1; - } - fclose(fp); - - return atoi(buf); -} - -void linuxOvercommitMemoryWarning(void) { - if (linuxOvercommitMemoryValue() == 0) { - redisLog(REDIS_WARNING,"WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect."); - } -} -#endif /* __linux__ */ - -void createPidFile(void) { - /* Try to write the pid file in a best-effort way. */ - FILE *fp = fopen(server.pidfile,"w"); - if (fp) { - fprintf(fp,"%d\n",(int)getpid()); - fclose(fp); - } -} - -void daemonize(void) { - int fd; - - if (fork() != 0) exit(0); /* parent exits */ - setsid(); /* create a new session */ - - /* Every output goes to /dev/null. If Redis is daemonized but - * the 'logfile' is set to 'stdout' in the configuration file - * it will not log at all. */ - if ((fd = open("/dev/null", O_RDWR, 0)) != -1) { - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - if (fd > STDERR_FILENO) close(fd); - } -} - -void version() { - printf("Redis server v=%s sha=%s:%d malloc=%s bits=%d\n", - REDIS_VERSION, - redisGitSHA1(), - atoi(redisGitDirty()) > 0, - ZMALLOC_LIB, - sizeof(long) == 4 ? 32 : 64); - exit(0); -} - -void usage() { - fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf] [options]\n"); - fprintf(stderr," ./redis-server - (read config from stdin)\n"); - fprintf(stderr," ./redis-server -v or --version\n"); - fprintf(stderr," ./redis-server -h or --help\n"); - fprintf(stderr," ./redis-server --test-memory \n\n"); - fprintf(stderr,"Examples:\n"); - fprintf(stderr," ./redis-server (run the server with default conf)\n"); - fprintf(stderr," ./redis-server /etc/redis/6379.conf\n"); - fprintf(stderr," ./redis-server --port 7777\n"); - fprintf(stderr," ./redis-server --port 7777 --slaveof 127.0.0.1 8888\n"); - fprintf(stderr," ./redis-server /etc/myredis.conf --loglevel verbose\n\n"); - fprintf(stderr,"Sentinel mode:\n"); - fprintf(stderr," ./redis-server /etc/sentinel.conf --sentinel\n"); - exit(1); -} - -void redisAsciiArt(void) { -#include "asciilogo.h" - char *buf = zmalloc(1024*16); - char *mode = "stand alone"; - - if (server.sentinel_mode) mode = "sentinel"; - - snprintf(buf,1024*16,ascii_logo, - REDIS_VERSION, - redisGitSHA1(), - strtol(redisGitDirty(),NULL,10) > 0, - (sizeof(long) == 8) ? "64" : "32", - mode, server.port, - (long) getpid() - ); - redisLogRaw(REDIS_NOTICE|REDIS_LOG_RAW,buf); - zfree(buf); -} - -static void sigtermHandler(int sig) { - REDIS_NOTUSED(sig); - - redisLogFromHandler(REDIS_WARNING,"Received SIGTERM, scheduling shutdown..."); - server.shutdown_asap = 1; -} - -void setupSignalHandlers(void) { - struct sigaction act; - - /* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used. - * Otherwise, sa_handler is used. */ - sigemptyset(&act.sa_mask); - act.sa_flags = 0; - act.sa_handler = sigtermHandler; - sigaction(SIGTERM, &act, NULL); - -#ifdef HAVE_BACKTRACE - sigemptyset(&act.sa_mask); - act.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; - act.sa_sigaction = sigsegvHandler; - sigaction(SIGSEGV, &act, NULL); - sigaction(SIGBUS, &act, NULL); - sigaction(SIGFPE, &act, NULL); - sigaction(SIGILL, &act, NULL); -#endif - return; -} - -void memtest(size_t megabytes, int passes); - -/* Returns 1 if there is --sentinel among the arguments or if - * argv[0] is exactly "redis-sentinel". */ -int checkForSentinelMode(int argc, char **argv) { - int j; - - if (strstr(argv[0],"redis-sentinel") != NULL) return 1; - for (j = 1; j < argc; j++) - if (!strcmp(argv[j],"--sentinel")) return 1; - return 0; -} - -/* Function called at startup to load RDB or AOF file in memory. */ -void loadDataFromDisk(void) { - long long start = ustime(); - if (server.aof_state == REDIS_AOF_ON) { - if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK) - redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000); - } else { - if (rdbLoad(server.rdb_filename) == REDIS_OK) { - redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds", - (float)(ustime()-start)/1000000); - } else if (errno != ENOENT) { - redisLog(REDIS_WARNING,"Fatal error loading the DB. Exiting."); - exit(1); - } - } -} - -void redisOutOfMemoryHandler(size_t allocation_size) { - redisLog(REDIS_WARNING,"Out Of Memory allocating %zu bytes!", - allocation_size); - redisPanic("OOM"); -} - -int main(int argc, char **argv) { - struct timeval tv; - - /* We need to initialize our libraries, and the server configuration. */ - zmalloc_enable_thread_safeness(); - zmalloc_set_oom_handler(redisOutOfMemoryHandler); - srand(time(NULL)^getpid()); - gettimeofday(&tv,NULL); - dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid()); - server.sentinel_mode = checkForSentinelMode(argc,argv); - initServerConfig(); - - /* We need to init sentinel right now as parsing the configuration file - * in sentinel mode will have the effect of populating the sentinel - * data structures with master nodes to monitor. */ - if (server.sentinel_mode) { - initSentinelConfig(); - initSentinel(); - } - - if (argc >= 2) { - int j = 1; /* First option to parse in argv[] */ - sds options = sdsempty(); - char *configfile = NULL; - - /* Handle special options --help and --version */ - if (strcmp(argv[1], "-v") == 0 || - strcmp(argv[1], "--version") == 0) version(); - if (strcmp(argv[1], "--help") == 0 || - strcmp(argv[1], "-h") == 0) usage(); - if (strcmp(argv[1], "--test-memory") == 0) { - if (argc == 3) { - memtest(atoi(argv[2]),50); - exit(0); - } else { - fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n"); - fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n"); - exit(1); - } - } - - /* First argument is the config file name? */ - if (argv[j][0] != '-' || argv[j][1] != '-') - configfile = argv[j++]; - /* All the other options are parsed and conceptually appended to the - * configuration file. For instance --port 6380 will generate the - * string "port 6380\n" to be parsed after the actual file name - * is parsed, if any. */ - while(j != argc) { - if (argv[j][0] == '-' && argv[j][1] == '-') { - /* Option name */ - if (sdslen(options)) options = sdscat(options,"\n"); - options = sdscat(options,argv[j]+2); - options = sdscat(options," "); - } else { - /* Option argument */ - options = sdscatrepr(options,argv[j],strlen(argv[j])); - options = sdscat(options," "); - } - j++; - } - resetServerSaveParams(); - loadServerConfig(configfile,options); - sdsfree(options); - } else { - redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis"); - } - if (server.daemonize) daemonize(); - initServer(); - if (server.daemonize) createPidFile(); - redisAsciiArt(); - - if (!server.sentinel_mode) { - /* Things only needed when not running in Sentinel mode. */ - redisLog(REDIS_WARNING,"Server started, Redis version " REDIS_VERSION); - #ifdef __linux__ - linuxOvercommitMemoryWarning(); - #endif - loadDataFromDisk(); - if (server.ipfd > 0) - redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port); - if (server.sofd > 0) - redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket); - } - - /* Warning the user about suspicious maxmemory setting. */ - if (server.maxmemory > 0 && server.maxmemory < 1024*1024) { - redisLog(REDIS_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory); - } - - aeSetBeforeSleepProc(server.el,beforeSleep); - aeMain(server.el); - aeDeleteEventLoop(server.el); - return 0; -} - -/* The End */ +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "redis.h" +#include "slowlog.h" +#include "bio.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Our shared "common" objects */ + +struct sharedObjectsStruct shared; + +/* Global vars that are actually used as constants. The following double + * values are used for double on-disk serialization, and are initialized + * at runtime to avoid strange compiler optimizations. */ + +double R_Zero, R_PosInf, R_NegInf, R_Nan; + +/*================================= Globals ================================= */ + +/* Global vars */ +struct redisServer server; /* server global state */ +struct redisCommand *commandTable; + +/* Our command table. + * + * Every entry is composed of the following fields: + * + * name: a string representing the command name. + * function: pointer to the C function implementing the command. + * arity: number of arguments, it is possible to use -N to say >= N + * sflags: command flags as string. See below for a table of flags. + * flags: flags as bitmask. Computed by Redis using the 'sflags' field. + * get_keys_proc: an optional function to get key arguments from a command. + * This is only used when the following three fields are not + * enough to specify what arguments are keys. + * first_key_index: first argument that is a key + * last_key_index: last argument that is a key + * key_step: step to get all the keys from first to last argument. For instance + * in MSET the step is two since arguments are key,val,key,val,... + * microseconds: microseconds of total execution time for this command. + * calls: total number of calls of this command. + * + * The flags, microseconds and calls fields are computed by Redis and should + * always be set to zero. + * + * Command flags are expressed using strings where every character represents + * a flag. Later the populateCommandTable() function will take care of + * populating the real 'flags' field using this characters. + * + * This is the meaning of the flags: + * + * w: write command (may modify the key space). + * r: read command (will never modify the key space). + * m: may increase memory usage once called. Don't allow if out of memory. + * a: admin command, like SAVE or SHUTDOWN. + * p: Pub/Sub related command. + * f: force replication of this command, regarless of server.dirty. + * s: command not allowed in scripts. + * R: random command. Command is not deterministic, that is, the same command + * with the same arguments, with the same key space, may have different + * results. For instance SPOP and RANDOMKEY are two random commands. + * S: Sort command output array if called from script, so that the output + * is deterministic. + * l: Allow command while loading the database. + * t: Allow command while a slave has stale data but is not allowed to + * server this data. Normally no command is accepted in this condition + * but just a few. + * M: Do not automatically propagate the command on MONITOR. + */ +struct redisCommand redisCommandTable[] = { + {"ds_get",ds_get,2,"r",0,NULL,1,1,1,0,0}, + {"rl_get",rl_get,2,"r",0,NULL,1,1,1,0,0}, + {"ds_mget",ds_mget,-2,"r",0,NULL,1,-1,1,0,0}, + {"ds_mset",ds_mset,-3,"wm",0,NULL,1,-1,2,0,0}, + {"ds_del",ds_delete,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0}, + {"rl_del",rl_delete,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0}, + {"ds_set",ds_set,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, + {"rl_set",rl_set,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, + {"ds_hset",ds_hset,4,"wm",0,NULL,1,1,1,0,0}, + {"rl_hset",rl_hset,4,"wm",0,NULL,1,1,1,0,0}, + {"ds_hdel",ds_hdel,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0}, + {"rl_hdel",rl_hdel,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0}, + {"ds_hget",ds_hget,3,"r",0,NULL,1,1,1,0,0}, + {"rl_hget",rl_hget,3,"r",0,NULL,1,1,1,0,0}, + {"ds_hmget",ds_hmget,-3,"r",0,NULL,1,1,1,0,0}, + {"ds_hmset",ds_hmset,-4,"wm",0,NULL,1,1,1,0,0}, + {"ds_hincrby",ds_hincrby,4,"wm",0,NULL,1,1,1,0,0}, + {"ds_hgetall",ds_hgetall,2,"r",0,NULL,1,1,1,0,0}, + {"ds_append",ds_append,3,"wm",0,NULL,1,1,1,0,0}, + {"ds_incrby",ds_incrby,3,"wm",0,NULL,1,1,1,0,0}, + + {"get",getCommand,2,"r",0,NULL,1,1,1,0,0}, + {"set",setCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, + {"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, + {"setex",setexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0}, + {"psetex",psetexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0}, + {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0}, + {"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0}, + {"del",delCommand,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0}, + {"exists",existsCommand,2,"r",0,NULL,1,1,1,0,0}, + {"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0}, + {"getbit",getbitCommand,3,"r",0,NULL,1,1,1,0,0}, + {"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0}, + {"getrange",getrangeCommand,4,"r",0,NULL,1,1,1,0,0}, + {"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0}, + {"incr",incrCommand,2,"wm",0,NULL,1,1,1,0,0}, + {"decr",decrCommand,2,"wm",0,NULL,1,1,1,0,0}, + {"mget",mgetCommand,-2,"r",0,NULL,1,-1,1,0,0}, + {"rpush",rpushCommand,-3,"wm",0,NULL,1,1,1,0,0}, + {"lpush",lpushCommand,-3,"wm",0,NULL,1,1,1,0,0}, + {"rpushx",rpushxCommand,3,"wm",0,NULL,1,1,1,0,0}, + {"lpushx",lpushxCommand,3,"wm",0,NULL,1,1,1,0,0}, + {"linsert",linsertCommand,5,"wm",0,NULL,1,1,1,0,0}, + {"rpop",rpopCommand,2,"w",0,NULL,1,1,1,0,0}, + {"lpop",lpopCommand,2,"w",0,NULL,1,1,1,0,0}, + {"brpop",brpopCommand,-3,"ws",0,NULL,1,1,1,0,0}, + {"brpoplpush",brpoplpushCommand,4,"wms",0,NULL,1,2,1,0,0}, + {"blpop",blpopCommand,-3,"ws",0,NULL,1,-2,1,0,0}, + {"llen",llenCommand,2,"r",0,NULL,1,1,1,0,0}, + {"lindex",lindexCommand,3,"r",0,NULL,1,1,1,0,0}, + {"lset",lsetCommand,4,"wm",0,NULL,1,1,1,0,0}, + {"lrange",lrangeCommand,4,"r",0,NULL,1,1,1,0,0}, + {"ltrim",ltrimCommand,4,"w",0,NULL,1,1,1,0,0}, + {"lrem",lremCommand,4,"w",0,NULL,1,1,1,0,0}, + {"rpoplpush",rpoplpushCommand,3,"wm",0,NULL,1,2,1,0,0}, + {"sadd",saddCommand,-3,"wm",0,NULL,1,1,1,0,0}, + {"srem",sremCommand,-3,"w",0,NULL,1,1,1,0,0}, + {"smove",smoveCommand,4,"w",0,NULL,1,2,1,0,0}, + {"sismember",sismemberCommand,3,"r",0,NULL,1,1,1,0,0}, + {"scard",scardCommand,2,"r",0,NULL,1,1,1,0,0}, + {"spop",spopCommand,2,"wRs",0,NULL,1,1,1,0,0}, + {"srandmember",srandmemberCommand,-2,"rR",0,NULL,1,1,1,0,0}, + {"sinter",sinterCommand,-2,"rS",0,NULL,1,-1,1,0,0}, + {"sinterstore",sinterstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0}, + {"sunion",sunionCommand,-2,"rS",0,NULL,1,-1,1,0,0}, + {"sunionstore",sunionstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0}, + {"sdiff",sdiffCommand,-2,"rS",0,NULL,1,-1,1,0,0}, + {"sdiffstore",sdiffstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0}, + {"smembers",sinterCommand,2,"rS",0,NULL,1,1,1,0,0}, + {"zadd",zaddCommand,-4,"wm",0,NULL,1,1,1,0,0}, + {"zincrby",zincrbyCommand,4,"wm",0,NULL,1,1,1,0,0}, + {"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}, + {"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}, + {"zcount",zcountCommand,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}, + {"zrank",zrankCommand,3,"r",0,NULL,1,1,1,0,0}, + {"zrevrank",zrevrankCommand,3,"r",0,NULL,1,1,1,0,0}, + {"hset",hsetCommand,4,"wm",0,NULL,1,1,1,0,0}, + {"hsetnx",hsetnxCommand,4,"wm",0,NULL,1,1,1,0,0}, + {"hget",hgetCommand,3,"r",0,NULL,1,1,1,0,0}, + {"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0}, + {"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0}, + {"hincrby",hincrbyCommand,4,"wm",0,NULL,1,1,1,0,0}, + {"hincrbyfloat",hincrbyfloatCommand,4,"wm",0,NULL,1,1,1,0,0}, + {"hdel",hdelCommand,-3,"w",0,NULL,1,1,1,0,0}, + {"hlen",hlenCommand,2,"r",0,NULL,1,1,1,0,0}, + {"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0}, + {"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0}, + {"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0}, + {"hexists",hexistsCommand,3,"r",0,NULL,1,1,1,0,0}, + {"incrby",incrbyCommand,3,"wm",0,NULL,1,1,1,0,0}, + {"decrby",decrbyCommand,3,"wm",0,NULL,1,1,1,0,0}, + {"incrbyfloat",incrbyfloatCommand,3,"wm",0,NULL,1,1,1,0,0}, + {"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0}, + {"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0}, + {"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0}, + {"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0}, + {"select",selectCommand,2,"r",0,NULL,0,0,0,0,0}, + {"move",moveCommand,3,"w",0,NULL,1,1,1,0,0}, + {"rename",renameCommand,3,"w",0,renameGetKeys,1,2,1,0,0}, + {"renamenx",renamenxCommand,3,"w",0,renameGetKeys,1,2,1,0,0}, + {"expire",expireCommand,3,"w",0,NULL,1,1,1,0,0}, + {"expireat",expireatCommand,3,"w",0,NULL,1,1,1,0,0}, + {"pexpire",pexpireCommand,3,"w",0,NULL,1,1,1,0,0}, + {"pexpireat",pexpireatCommand,3,"w",0,NULL,1,1,1,0,0}, + {"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0}, + {"dbsize",dbsizeCommand,1,"r",0,NULL,0,0,0,0,0}, + {"auth",authCommand,2,"rs",0,NULL,0,0,0,0,0}, + {"ping",pingCommand,1,"r",0,NULL,0,0,0,0,0}, + {"echo",echoCommand,2,"r",0,NULL,0,0,0,0,0}, + {"save",saveCommand,1,"ars",0,NULL,0,0,0,0,0}, + {"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0}, + {"bgrewriteaof",bgrewriteaofCommand,1,"ar",0,NULL,0,0,0,0,0}, + {"shutdown",shutdownCommand,-1,"ar",0,NULL,0,0,0,0,0}, + {"lastsave",lastsaveCommand,1,"r",0,NULL,0,0,0,0,0}, + {"type",typeCommand,2,"r",0,NULL,1,1,1,0,0}, + {"multi",multiCommand,1,"rs",0,NULL,0,0,0,0,0}, + {"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0}, + {"discard",discardCommand,1,"rs",0,NULL,0,0,0,0,0}, + {"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0}, + {"replconf",replconfCommand,-1,"ars",0,NULL,0,0,0,0,0}, + {"flushdb",flushdbCommand,1,"w",0,NULL,0,0,0,0,0}, + {"flushall",flushallCommand,1,"w",0,NULL,0,0,0,0,0}, + {"sort",sortCommand,-2,"wm",0,NULL,1,1,1,0,0}, + {"info",infoCommand,-1,"rlt",0,NULL,0,0,0,0,0}, + {"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0}, + {"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0}, + {"pttl",pttlCommand,2,"r",0,NULL,1,1,1,0,0}, + {"persist",persistCommand,2,"w",0,NULL,1,1,1,0,0}, + {"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0}, + {"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0}, + {"config",configCommand,-2,"ar",0,NULL,0,0,0,0,0}, + {"subscribe",subscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0}, + {"unsubscribe",unsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0}, + {"psubscribe",psubscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0}, + {"punsubscribe",punsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0}, + {"publish",publishCommand,3,"pflt",0,NULL,0,0,0,0,0}, + {"watch",watchCommand,-2,"rs",0,noPreloadGetKeys,1,-1,1,0,0}, + {"unwatch",unwatchCommand,1,"rs",0,NULL,0,0,0,0,0}, + {"restore",restoreCommand,4,"awm",0,NULL,1,1,1,0,0}, + {"migrate",migrateCommand,6,"aw",0,NULL,0,0,0,0,0}, + {"dump",dumpCommand,2,"ar",0,NULL,1,1,1,0,0}, + {"object",objectCommand,-2,"r",0,NULL,2,2,2,0,0}, + {"client",clientCommand,-2,"ar",0,NULL,0,0,0,0,0}, + {"eval",evalCommand,-3,"s",0,zunionInterGetKeys,0,0,0,0,0}, + {"evalsha",evalShaCommand,-3,"s",0,zunionInterGetKeys,0,0,0,0,0}, + {"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0}, + {"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0}, + {"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0}, + {"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0}, + {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0} +}; + +/*============================ Utility functions ============================ */ + +/* Low level logging. To use only for very big messages, otherwise + * redisLog() is to prefer. */ +void redisLogRaw(int level, const char *msg) { + const int syslogLevelMap[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING }; + const char *c = ".-*#"; + FILE *fp; + char buf[64]; + int rawmode = (level & REDIS_LOG_RAW); + + level &= 0xff; /* clear flags */ + if (level < server.verbosity) return; + + fp = (server.logfile == NULL) ? stdout : fopen(server.logfile,"a"); + if (!fp) return; + + if (rawmode) { + fprintf(fp,"%s",msg); + } else { + int off; + struct timeval tv; + + 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); + fprintf(fp,"[%d] %s %c %s\n",(int)getpid(),buf,c[level],msg); + } + fflush(fp); + + if (server.logfile) fclose(fp); + + if (server.syslog_enabled) syslog(syslogLevelMap[level], "%s", msg); +} + +/* Like redisLogRaw() but with printf-alike support. This is the funciton that + * is used across the code. The raw version is only used in order to dump + * the INFO output on crash. */ +void redisLog(int level, const char *fmt, ...) { + va_list ap; + char msg[REDIS_MAX_LOGMSG_LEN]; + + if ((level&0xff) < server.verbosity) return; + + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + redisLogRaw(level,msg); +} + +/* Log a fixed message without printf-alike capabilities, in a way that is + * safe to call from a signal handler. + * + * We actually use this only for signals that are not fatal from the point + * of view of Redis. Signals that are going to kill the server anyway and + * where we need printf-alike features are served by redisLog(). */ +void redisLogFromHandler(int level, const char *msg) { + int fd; + char buf[64]; + + if ((level&0xff) < server.verbosity || + (server.logfile == NULL && server.daemonize)) return; + fd = server.logfile ? + open(server.logfile, O_APPEND|O_CREAT|O_WRONLY, 0644) : + STDOUT_FILENO; + 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] (",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; + if (write(fd,msg,strlen(msg)) == -1) goto err; + if (write(fd,"\n",1) == -1) goto err; +err: + if (server.logfile) close(fd); +} + +/* Return the UNIX time in microseconds */ +long long ustime(void) { + struct timeval tv; + long long ust; + + gettimeofday(&tv, NULL); + ust = ((long long)tv.tv_sec)*1000000; + ust += tv.tv_usec; + return ust; +} + +/* Return the UNIX time in milliseconds */ +long long mstime(void) { + return ustime()/1000; +} + +/* After an RDB dump or AOF rewrite we exit from children using _exit() instead of + * exit(), because the latter may interact with the same file objects used by + * the parent process. However if we are testing the coverage normal exit() is + * used in order to obtain the right coverage information. */ +void exitFromChild(int retcode) { +#ifdef COVERAGE_TEST + exit(retcode); +#else + _exit(retcode); +#endif +} + +/*====================== Hash table type implementation ==================== */ + +/* This is an hash table type that uses the SDS dynamic strings libary as + * keys and radis objects as values (objects can hold SDS strings, + * lists, sets). */ + +void dictVanillaFree(void *privdata, void *val) +{ + DICT_NOTUSED(privdata); + zfree(val); +} + +void dictListDestructor(void *privdata, void *val) +{ + DICT_NOTUSED(privdata); + listRelease((list*)val); +} + +int dictSdsKeyCompare(void *privdata, const void *key1, + const void *key2) +{ + int l1,l2; + DICT_NOTUSED(privdata); + + l1 = sdslen((sds)key1); + l2 = sdslen((sds)key2); + if (l1 != l2) return 0; + return memcmp(key1, key2, l1) == 0; +} + +/* A case insensitive version used for the command lookup table and other + * places where case insensitive non binary-safe comparison is needed. */ +int dictSdsKeyCaseCompare(void *privdata, const void *key1, + const void *key2) +{ + DICT_NOTUSED(privdata); + + return strcasecmp(key1, key2) == 0; +} + +void dictRedisObjectDestructor(void *privdata, void *val) +{ + DICT_NOTUSED(privdata); + + if (val == NULL) return; /* Values of swapped out keys as set to NULL */ + decrRefCount(val); +} + +void dictSdsDestructor(void *privdata, void *val) +{ + DICT_NOTUSED(privdata); + + sdsfree(val); +} + +int dictObjKeyCompare(void *privdata, const void *key1, + const void *key2) +{ + const robj *o1 = key1, *o2 = key2; + return dictSdsKeyCompare(privdata,o1->ptr,o2->ptr); +} + +unsigned int dictObjHash(const void *key) { + const robj *o = key; + return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr)); +} + +unsigned int dictSdsHash(const void *key) { + return dictGenHashFunction((unsigned char*)key, sdslen((char*)key)); +} + +unsigned int dictSdsCaseHash(const void *key) { + return dictGenCaseHashFunction((unsigned char*)key, sdslen((char*)key)); +} + +int dictEncObjKeyCompare(void *privdata, const void *key1, + const void *key2) +{ + robj *o1 = (robj*) key1, *o2 = (robj*) key2; + int cmp; + + if (o1->encoding == REDIS_ENCODING_INT && + o2->encoding == REDIS_ENCODING_INT) + return o1->ptr == o2->ptr; + + o1 = getDecodedObject(o1); + o2 = getDecodedObject(o2); + cmp = dictSdsKeyCompare(privdata,o1->ptr,o2->ptr); + decrRefCount(o1); + decrRefCount(o2); + return cmp; +} + +unsigned int dictEncObjHash(const void *key) { + robj *o = (robj*) key; + + if (o->encoding == REDIS_ENCODING_RAW) { + return dictGenHashFunction(o->ptr, sdslen((sds)o->ptr)); + } else { + if (o->encoding == REDIS_ENCODING_INT) { + char buf[32]; + int len; + + len = ll2string(buf,32,(long)o->ptr); + return dictGenHashFunction((unsigned char*)buf, len); + } else { + unsigned int hash; + + o = getDecodedObject(o); + hash = dictGenHashFunction(o->ptr, sdslen((sds)o->ptr)); + decrRefCount(o); + return hash; + } + } +} + +/* Sets type hash table */ +dictType setDictType = { + dictEncObjHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictEncObjKeyCompare, /* key compare */ + dictRedisObjectDestructor, /* key destructor */ + NULL /* val destructor */ +}; + +/* Sorted sets hash (note: a skiplist is used in addition to the hash table) */ +dictType zsetDictType = { + dictEncObjHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictEncObjKeyCompare, /* key compare */ + dictRedisObjectDestructor, /* key destructor */ + NULL /* val destructor */ +}; + +/* Db->dict, keys are sds strings, vals are Redis objects. */ +dictType dbDictType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + dictRedisObjectDestructor /* val destructor */ +}; + +/* server.lua_scripts sha (as sds string) -> scripts (as robj) cache. */ +dictType shaScriptObjectDictType = { + dictSdsCaseHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCaseCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + dictRedisObjectDestructor /* val destructor */ +}; + +/* Db->expires */ +dictType keyptrDictType = { + dictSdsHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCompare, /* key compare */ + NULL, /* key destructor */ + NULL /* val destructor */ +}; + +/* Command table. sds string -> command struct pointer. */ +dictType commandTableDictType = { + dictSdsCaseHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictSdsKeyCaseCompare, /* key compare */ + dictSdsDestructor, /* key destructor */ + NULL /* val destructor */ +}; + +/* Hash type hash table (note that small hashes are represented with zimpaps) */ +dictType hashDictType = { + dictEncObjHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictEncObjKeyCompare, /* key compare */ + dictRedisObjectDestructor, /* key destructor */ + dictRedisObjectDestructor /* val destructor */ +}; + +/* Keylist hash table type has unencoded redis objects as keys and + * lists as values. It's used for blocking operations (BLPOP) and to + * map swapped keys to a list of clients waiting for this keys to be loaded. */ +dictType keylistDictType = { + dictObjHash, /* hash function */ + NULL, /* key dup */ + NULL, /* val dup */ + dictObjKeyCompare, /* key compare */ + dictRedisObjectDestructor, /* key destructor */ + dictListDestructor /* val destructor */ +}; + +int htNeedsResize(dict *dict) { + long long size, used; + + size = dictSlots(dict); + used = dictSize(dict); + return (size && used && size > DICT_HT_INITIAL_SIZE && + (used*100/size < REDIS_HT_MINFILL)); +} + +/* If the percentage of used slots in the HT reaches REDIS_HT_MINFILL + * we resize the hash table to save memory */ +void tryResizeHashTables(void) { + int j; + + for (j = 0; j < server.dbnum; j++) { + if (htNeedsResize(server.db[j].dict)) + dictResize(server.db[j].dict); + if (htNeedsResize(server.db[j].expires)) + dictResize(server.db[j].expires); + } +} + +/* Our hash table implementation performs rehashing incrementally while + * we write/read from the hash table. Still if the server is idle, the hash + * table will use two tables for a long time. So we try to use 1 millisecond + * of CPU time at every serverCron() loop in order to rehash some key. */ +void incrementallyRehash(void) { + int j; + + for (j = 0; j < server.dbnum; j++) { + /* Keys dictionary */ + if (dictIsRehashing(server.db[j].dict)) { + dictRehashMilliseconds(server.db[j].dict,1); + break; /* already used our millisecond for this loop... */ + } + /* Expires */ + if (dictIsRehashing(server.db[j].expires)) { + dictRehashMilliseconds(server.db[j].expires,1); + break; /* already used our millisecond for this loop... */ + } + } +} + +/* This function is called once a background process of some kind terminates, + * as we want to avoid resizing the hash tables when there is a child in order + * to play well with copy-on-write (otherwise when a resize happens lots of + * memory pages are copied). The goal of this function is to update the ability + * for dict.c to resize the hash tables accordingly to the fact we have o not + * running childs. */ +void updateDictResizePolicy(void) { + if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) + dictEnableResize(); + else + dictDisableResize(); +} + +/* ======================= Cron: called every 100 ms ======================== */ + +/* Try to expire a few timed out keys. The algorithm used is adaptive and + * will use few CPU cycles if there are few expiring keys, otherwise + * it will get more aggressive to avoid that too much memory is used by + * keys that can be removed from the keyspace. */ +void activeExpireCycle(void) { + int j, iteration = 0; + long long start = ustime(), timelimit; + + /* We can use at max REDIS_EXPIRELOOKUPS_TIME_PERC percentage of CPU time + * per iteration. Since this function gets called with a frequency of + * REDIS_HZ times per second, the following is the max amount of + * microseconds we can spend in this function. */ + timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/REDIS_HZ/100; + if (timelimit <= 0) timelimit = 1; + + for (j = 0; j < server.dbnum; j++) { + int expired; + redisDb *db = server.db+j; + + /* Continue to expire if at the end of the cycle more than 25% + * of the keys were expired. */ + do { + unsigned long num = dictSize(db->expires); + unsigned long slots = dictSlots(db->expires); + long long now = mstime(); + + /* When there are less than 1% filled slots getting random + * keys is expensive, so stop here waiting for better times... + * The dictionary will be resized asap. */ + if (num && slots > DICT_HT_INITIAL_SIZE && + (num*100/slots < 1)) break; + + /* The main collection cycle. Sample random keys among keys + * with an expire set, checking for expired ones. */ + expired = 0; + if (num > REDIS_EXPIRELOOKUPS_PER_CRON) + num = REDIS_EXPIRELOOKUPS_PER_CRON; + while (num--) { + dictEntry *de; + long long t; + + if ((de = dictGetRandomKey(db->expires)) == NULL) break; + t = dictGetSignedIntegerVal(de); + if (now > t) { + sds key = dictGetKey(de); + robj *keyobj = createStringObject(key,sdslen(key)); + + propagateExpire(db,keyobj); + dbDelete(db,keyobj); + decrRefCount(keyobj); + expired++; + server.stat_expiredkeys++; + } + } + /* We can't block forever here even if there are many keys to + * expire. So after a given amount of milliseconds return to the + * caller waiting for the other active expire cycle. */ + iteration++; + if ((iteration & 0xf) == 0 && /* check once every 16 cycles. */ + (ustime()-start) > timelimit) return; + } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4); + } +} + +void updateLRUClock(void) { + server.lruclock = (server.unixtime/REDIS_LRU_CLOCK_RESOLUTION) & + REDIS_LRU_CLOCK_MAX; +} + + +/* Add a sample to the operations per second array of samples. */ +void trackOperationsPerSecond(void) { + long long t = mstime() - server.ops_sec_last_sample_time; + long long ops = server.stat_numcommands - server.ops_sec_last_sample_ops; + long long ops_sec; + + ops_sec = t > 0 ? (ops*1000/t) : 0; + + server.ops_sec_samples[server.ops_sec_idx] = ops_sec; + server.ops_sec_idx = (server.ops_sec_idx+1) % REDIS_OPS_SEC_SAMPLES; + server.ops_sec_last_sample_time = mstime(); + server.ops_sec_last_sample_ops = server.stat_numcommands; +} + +/* Return the mean of all the samples. */ +long long getOperationsPerSecond(void) { + int j; + long long sum = 0; + + for (j = 0; j < REDIS_OPS_SEC_SAMPLES; j++) + sum += server.ops_sec_samples[j]; + return sum / REDIS_OPS_SEC_SAMPLES; +} + +/* Check for timeouts. Returns non-zero if the client was terminated */ +int clientsCronHandleTimeout(redisClient *c) { + time_t now = server.unixtime; + + if (server.maxidletime && + !(c->flags & REDIS_SLAVE) && /* no timeout for slaves */ + !(c->flags & REDIS_MASTER) && /* no timeout for masters */ + !(c->flags & REDIS_BLOCKED) && /* no timeout for BLPOP */ + dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */ + listLength(c->pubsub_patterns) == 0 && + (now - c->lastinteraction > server.maxidletime)) + { + redisLog(REDIS_VERBOSE,"Closing idle client"); + freeClient(c); + return 1; + } else if (c->flags & REDIS_BLOCKED) { + if (c->bpop.timeout != 0 && c->bpop.timeout < now) { + addReply(c,shared.nullmultibulk); + unblockClientWaitingData(c); + } + } + return 0; +} + +/* The client query buffer is an sds.c string that can end with a lot of + * free space not used, this function reclaims space if needed. + * + * The funciton always returns 0 as it never terminates the client. */ +int clientsCronResizeQueryBuffer(redisClient *c) { + size_t querybuf_size = sdsAllocSize(c->querybuf); + time_t idletime = server.unixtime - c->lastinteraction; + + /* There are two conditions to resize the query buffer: + * 1) Query buffer is > BIG_ARG and too big for latest peak. + * 2) Client is inactive and the buffer is bigger than 1k. */ + if (((querybuf_size > REDIS_MBULK_BIG_ARG) && + (querybuf_size/(c->querybuf_peak+1)) > 2) || + (querybuf_size > 1024 && idletime > 2)) + { + /* Only resize the query buffer if it is actually wasting space. */ + if (sdsavail(c->querybuf) > 1024) { + c->querybuf = sdsRemoveFreeSpace(c->querybuf); + } + } + /* Reset the peak again to capture the peak memory usage in the next + * cycle. */ + c->querybuf_peak = 0; + return 0; +} + +void clientsCron(void) { + /* Make sure to process at least 1/(REDIS_HZ*10) of clients per call. + * Since this function is called REDIS_HZ times per second we are sure that + * in the worst case we process all the clients in 10 seconds. + * In normal conditions (a reasonable number of clients) we process + * all the clients in a shorter time. */ + int numclients = listLength(server.clients); + int iterations = numclients/(REDIS_HZ*10); + + if (iterations < 50) + iterations = (numclients < 50) ? numclients : 50; + while(listLength(server.clients) && iterations--) { + redisClient *c; + listNode *head; + + /* Rotate the list, take the current head, process. + * This way if the client must be removed from the list it's the + * first element and we don't incur into O(N) computation. */ + listRotate(server.clients); + head = listFirst(server.clients); + c = listNodeValue(head); + /* The following functions do different service checks on the client. + * The protocol is that they return non-zero if the client was + * terminated. */ + if (clientsCronHandleTimeout(c)) continue; + if (clientsCronResizeQueryBuffer(c)) continue; + } +} + +/* This is our timer interrupt, called REDIS_HZ times per second. + * Here is where we do a number of things that need to be done asynchronously. + * For instance: + * + * - Active expired keys collection (it is also performed in a lazy way on + * lookup). + * - Software watchdong. + * - Update some statistic. + * - Incremental rehashing of the DBs hash tables. + * - Triggering BGSAVE / AOF rewrite, and handling of terminated children. + * - Clients timeout of differnet kinds. + * - Replication reconnection. + * - Many more... + * + * Everything directly called here will be called REDIS_HZ times per second, + * so in order to throttle execution of things we want to do less frequently + * a macro is used: run_with_period(milliseconds) { .... } + */ + +int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { + int j; + REDIS_NOTUSED(eventLoop); + REDIS_NOTUSED(id); + REDIS_NOTUSED(clientData); + + /* Software watchdog: deliver the SIGALRM that will reach the signal + * handler if we don't return here fast enough. */ + if (server.watchdog_period) watchdogScheduleSignal(server.watchdog_period); + + /* We take a cached value of the unix time in the global state because + * with virtual memory and aging there is to store the current time + * in objects at every object access, and accuracy is not needed. + * To access a global var is faster than calling time(NULL) */ + server.unixtime = time(NULL); + + run_with_period(100) trackOperationsPerSecond(); + + /* We have just 22 bits per object for LRU information. + * So we use an (eventually wrapping) LRU clock with 10 seconds resolution. + * 2^22 bits with 10 seconds resoluton is more or less 1.5 years. + * + * Note that even if this will wrap after 1.5 years it's not a problem, + * everything will still work but just some object will appear younger + * to Redis. But for this to happen a given object should never be touched + * for 1.5 years. + * + * Note that you can change the resolution altering the + * REDIS_LRU_CLOCK_RESOLUTION define. + */ + updateLRUClock(); + + /* Record the max memory used since the server was started. */ + if (zmalloc_used_memory() > server.stat_peak_memory) + server.stat_peak_memory = zmalloc_used_memory(); + + /* We received a SIGTERM, shutting down here in a safe way, as it is + * not ok doing so inside the signal handler. */ + if (server.shutdown_asap) { + if (prepareForShutdown(0) == REDIS_OK) exit(0); + redisLog(REDIS_WARNING,"SIGTERM received but errors trying to shut down the server, check the logs for more information"); + } + + /* Show some info about non-empty databases */ + run_with_period(5000) { + for (j = 0; j < server.dbnum; j++) { + long long size, used, vkeys; + + size = dictSlots(server.db[j].dict); + used = dictSize(server.db[j].dict); + vkeys = dictSize(server.db[j].expires); + if (used || vkeys) { + redisLog(REDIS_VERBOSE,"DB %d: %lld keys (%lld volatile) in %lld slots HT.",j,used,vkeys,size); + /* dictPrintStats(server.dict); */ + } + } + } + + /* We don't want to resize the hash tables while a bacground saving + * is in progress: the saving child is created using fork() that is + * implemented with a copy-on-write semantic in most modern systems, so + * if we resize the HT while there is the saving child at work actually + * a lot of memory movements in the parent will cause a lot of pages + * copied. */ + if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) { + tryResizeHashTables(); + if (server.activerehashing) incrementallyRehash(); + } + + /* Show information about connected clients */ + if (!server.sentinel_mode) { + run_with_period(5000) { + redisLog(REDIS_VERBOSE, + "%d clients connected (%d slaves), %zu bytes in use", + listLength(server.clients)-listLength(server.slaves), + listLength(server.slaves), + zmalloc_used_memory()); + } + } + + /* We need to do a few operations on clients asynchronously. */ + clientsCron(); + + /* Start a scheduled AOF rewrite if this was requested by the user while + * a BGSAVE was in progress. */ + if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 && + server.aof_rewrite_scheduled) + { + rewriteAppendOnlyFileBackground(); + } + + /* Check if a background saving or AOF rewrite in progress terminated. */ + if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) { + int statloc; + pid_t pid; + + if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) { + int exitcode = WEXITSTATUS(statloc); + int bysignal = 0; + + if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc); + + if (pid == server.rdb_child_pid) { + backgroundSaveDoneHandler(exitcode,bysignal); + } else if (pid == server.aof_child_pid) { + backgroundRewriteDoneHandler(exitcode,bysignal); + } else { + redisLog(REDIS_WARNING, + "Warning, detected child with unmatched pid: %ld", + (long)pid); + } + updateDictResizePolicy(); + } + } else { + /* If there is not a background saving/rewrite in progress check if + * we have to save/rewrite now */ + for (j = 0; j < server.saveparamslen; j++) { + struct saveparam *sp = server.saveparams+j; + + if (server.dirty >= sp->changes && + server.unixtime-server.lastsave > sp->seconds) { + redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...", + sp->changes, sp->seconds); + rdbSaveBackground(server.rdb_filename); + break; + } + } + + /* Trigger an AOF rewrite if needed */ + if (server.rdb_child_pid == -1 && + server.aof_child_pid == -1 && + server.aof_rewrite_perc && + server.aof_current_size > server.aof_rewrite_min_size) + { + long long base = server.aof_rewrite_base_size ? + server.aof_rewrite_base_size : 1; + long long growth = (server.aof_current_size*100/base) - 100; + if (growth >= server.aof_rewrite_perc) { + redisLog(REDIS_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth); + rewriteAppendOnlyFileBackground(); + } + } + } + + + /* If we postponed an AOF buffer flush, let's try to do it every time the + * cron function is called. */ + if (server.aof_flush_postponed_start) flushAppendOnlyFile(0); + + /* Expire a few keys per cycle, only if this is a master. + * On slaves we wait for DEL operations synthesized by the master + * in order to guarantee a strict consistency. */ + if (server.masterhost == NULL) activeExpireCycle(); + + /* Close clients that need to be closed asynchronous */ + freeClientsInAsyncFreeQueue(); + + /* Replication cron function -- used to reconnect to master and + * to detect transfer failures. */ + run_with_period(1000) replicationCron(); + + /* Run the sentinel timer if we are in sentinel mode. */ + run_with_period(100) { + if (server.sentinel_mode) sentinelTimer(); + } + + server.cronloops++; + return 1000/REDIS_HZ; +} + +/* This function gets called every time Redis is entering the + * main loop of the event driven library, that is, before to sleep + * for ready file descriptors. */ +void beforeSleep(struct aeEventLoop *eventLoop) { + REDIS_NOTUSED(eventLoop); + listNode *ln; + redisClient *c; + + /* Try to process pending commands for clients that were just unblocked. */ + while (listLength(server.unblocked_clients)) { + ln = listFirst(server.unblocked_clients); + redisAssert(ln != NULL); + c = ln->value; + listDelNode(server.unblocked_clients,ln); + c->flags &= ~REDIS_UNBLOCKED; + + /* Process remaining data in the input buffer. */ + if (c->querybuf && sdslen(c->querybuf) > 0) { + server.current_client = c; + processInputBuffer(c); + server.current_client = NULL; + } + } + + /* Write the AOF buffer on disk */ + flushAppendOnlyFile(0); +} + +/* =========================== Server initialization ======================== */ + +void createSharedObjects(void) { + int j; + + shared.crlf = createObject(REDIS_STRING,sdsnew("\r\n")); + shared.ok = createObject(REDIS_STRING,sdsnew("+OK\r\n")); + shared.err = createObject(REDIS_STRING,sdsnew("-ERR\r\n")); + shared.emptybulk = createObject(REDIS_STRING,sdsnew("$0\r\n\r\n")); + shared.czero = createObject(REDIS_STRING,sdsnew(":0\r\n")); + shared.cone = createObject(REDIS_STRING,sdsnew(":1\r\n")); + shared.cnegone = createObject(REDIS_STRING,sdsnew(":-1\r\n")); + shared.nullbulk = createObject(REDIS_STRING,sdsnew("$-1\r\n")); + shared.nullmultibulk = createObject(REDIS_STRING,sdsnew("*-1\r\n")); + shared.emptymultibulk = createObject(REDIS_STRING,sdsnew("*0\r\n")); + shared.pong = createObject(REDIS_STRING,sdsnew("+PONG\r\n")); + shared.queued = createObject(REDIS_STRING,sdsnew("+QUEUED\r\n")); + shared.wrongtypeerr = createObject(REDIS_STRING,sdsnew( + "-ERR Operation against a key holding the wrong kind of value\r\n")); + shared.nokeyerr = createObject(REDIS_STRING,sdsnew( + "-ERR no such key\r\n")); + shared.syntaxerr = createObject(REDIS_STRING,sdsnew( + "-ERR syntax error\r\n")); + shared.sameobjecterr = createObject(REDIS_STRING,sdsnew( + "-ERR source and destination objects are the same\r\n")); + shared.outofrangeerr = createObject(REDIS_STRING,sdsnew( + "-ERR index out of range\r\n")); + shared.noscripterr = createObject(REDIS_STRING,sdsnew( + "-NOSCRIPT No matching script. Please use EVAL.\r\n")); + shared.loadingerr = createObject(REDIS_STRING,sdsnew( + "-LOADING Redis is loading the dataset in memory\r\n")); + shared.slowscripterr = createObject(REDIS_STRING,sdsnew( + "-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n")); + shared.masterdownerr = createObject(REDIS_STRING,sdsnew( + "-MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'.\r\n")); + shared.bgsaveerr = createObject(REDIS_STRING,sdsnew( + "-MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.\r\n")); + shared.roslaveerr = createObject(REDIS_STRING,sdsnew( + "-READONLY You can't write against a read only slave.\r\n")); + shared.oomerr = createObject(REDIS_STRING,sdsnew( + "-OOM command not allowed when used memory > 'maxmemory'.\r\n")); + shared.execaborterr = createObject(REDIS_STRING,sdsnew( + "-EXECABORT Transaction discarded because of previous errors.\r\n")); + shared.space = createObject(REDIS_STRING,sdsnew(" ")); + shared.colon = createObject(REDIS_STRING,sdsnew(":")); + shared.plus = createObject(REDIS_STRING,sdsnew("+")); + + for (j = 0; j < REDIS_SHARED_SELECT_CMDS; j++) { + shared.select[j] = createObject(REDIS_STRING, + sdscatprintf(sdsempty(),"select %d\r\n", j)); + } + shared.messagebulk = createStringObject("$7\r\nmessage\r\n",13); + shared.pmessagebulk = createStringObject("$8\r\npmessage\r\n",14); + shared.subscribebulk = createStringObject("$9\r\nsubscribe\r\n",15); + shared.unsubscribebulk = createStringObject("$11\r\nunsubscribe\r\n",18); + shared.psubscribebulk = createStringObject("$10\r\npsubscribe\r\n",17); + shared.punsubscribebulk = createStringObject("$12\r\npunsubscribe\r\n",19); + shared.del = createStringObject("DEL",3); + shared.rpop = createStringObject("RPOP",4); + shared.lpop = createStringObject("LPOP",4); + shared.lpush = createStringObject("LPUSH",5); + for (j = 0; j < REDIS_SHARED_INTEGERS; j++) { + shared.integers[j] = createObject(REDIS_STRING,(void*)(long)j); + shared.integers[j]->encoding = REDIS_ENCODING_INT; + } + for (j = 0; j < REDIS_SHARED_BULKHDR_LEN; j++) { + shared.mbulkhdr[j] = createObject(REDIS_STRING, + sdscatprintf(sdsempty(),"*%d\r\n",j)); + shared.bulkhdr[j] = createObject(REDIS_STRING, + sdscatprintf(sdsempty(),"$%d\r\n",j)); + } +} + +void initServerConfig() { + getRandomHexChars(server.runid,REDIS_RUN_ID_SIZE); + server.runid[REDIS_RUN_ID_SIZE] = '\0'; + server.arch_bits = (sizeof(long) == 8) ? 64 : 32; + server.port = REDIS_SERVERPORT; + server.bindaddr = NULL; + server.unixsocket = NULL; + server.unixsocketperm = 0; + server.ipfd = -1; + server.sofd = -1; + server.dbnum = REDIS_DEFAULT_DBNUM; + server.verbosity = REDIS_NOTICE; + server.maxidletime = REDIS_MAXIDLETIME; + server.client_max_querybuf_len = REDIS_MAX_QUERYBUF_LEN; + server.saveparams = NULL; + server.loading = 0; + server.logfile = NULL; /* NULL = log on standard output */ + server.syslog_enabled = 0; + server.syslog_ident = zstrdup("redis"); + server.syslog_facility = LOG_LOCAL0; + server.daemonize = 0; + server.aof_state = REDIS_AOF_OFF; + server.aof_fsync = AOF_FSYNC_EVERYSEC; + server.aof_no_fsync_on_rewrite = 0; + server.aof_rewrite_perc = REDIS_AOF_REWRITE_PERC; + server.aof_rewrite_min_size = REDIS_AOF_REWRITE_MIN_SIZE; + server.aof_rewrite_base_size = 0; + server.aof_rewrite_scheduled = 0; + server.aof_last_fsync = time(NULL); + server.aof_rewrite_time_last = -1; + server.aof_rewrite_time_start = -1; + server.aof_lastbgrewrite_status = REDIS_OK; + server.aof_delayed_fsync = 0; + server.aof_fd = -1; + server.aof_selected_db = -1; /* Make sure the first time will not match */ + server.aof_flush_postponed_start = 0; + server.pidfile = zstrdup("/var/run/redis.pid"); + server.rdb_filename = zstrdup("dump.rdb"); + server.aof_filename = zstrdup("appendonly.aof"); + server.requirepass = NULL; + server.rdb_compression = 1; + server.rdb_checksum = 1; + server.activerehashing = 1; + server.maxclients = REDIS_MAX_CLIENTS; + server.bpop_blocked_clients = 0; + server.maxmemory = 0; + server.maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU; + server.maxmemory_samples = 3; + server.hash_max_ziplist_entries = REDIS_HASH_MAX_ZIPLIST_ENTRIES; + server.hash_max_ziplist_value = REDIS_HASH_MAX_ZIPLIST_VALUE; + server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES; + server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE; + 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.shutdown_asap = 0; + server.repl_ping_slave_period = REDIS_REPL_PING_SLAVE_PERIOD; + server.repl_timeout = REDIS_REPL_TIMEOUT; + server.lua_caller = NULL; + server.lua_time_limit = REDIS_LUA_TIME_LIMIT; + server.lua_client = NULL; + server.lua_timedout = 0; + + updateLRUClock(); + resetServerSaveParams(); + + appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */ + appendServerSaveParams(300,100); /* save after 5 minutes and 100 changes */ + appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */ + /* Replication related */ + server.masterauth = NULL; + server.masterhost = NULL; + server.masterport = 6379; + server.master = NULL; + server.repl_state = REDIS_REPL_NONE; + server.repl_syncio_timeout = REDIS_REPL_SYNCIO_TIMEOUT; + server.repl_serve_stale_data = 1; + server.repl_slave_ro = 1; + server.repl_down_since = time(NULL); + server.slave_priority = REDIS_DEFAULT_SLAVE_PRIORITY; + + /* Client output buffer limits */ + server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_NORMAL].hard_limit_bytes = 0; + server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_NORMAL].soft_limit_bytes = 0; + server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_NORMAL].soft_limit_seconds = 0; + server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_SLAVE].hard_limit_bytes = 1024*1024*256; + server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_SLAVE].soft_limit_bytes = 1024*1024*64; + server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_SLAVE].soft_limit_seconds = 60; + server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_PUBSUB].hard_limit_bytes = 1024*1024*32; + server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_PUBSUB].soft_limit_bytes = 1024*1024*8; + server.client_obuf_limits[REDIS_CLIENT_LIMIT_CLASS_PUBSUB].soft_limit_seconds = 60; + + /* Double constants initialization */ + R_Zero = 0.0; + R_PosInf = 1.0/R_Zero; + R_NegInf = -1.0/R_Zero; + R_Nan = R_Zero/R_Zero; + + /* Command table -- we intiialize it here as it is part of the + * initial configuration, since command names may be changed via + * redis.conf using the rename-command directive. */ + server.commands = dictCreate(&commandTableDictType,NULL); + populateCommandTable(); + server.delCommand = lookupCommandByCString("del"); + server.multiCommand = lookupCommandByCString("multi"); + server.lpushCommand = lookupCommandByCString("lpush"); + server.lpopCommand = lookupCommandByCString("lpop"); + server.rpopCommand = lookupCommandByCString("rpop"); + + /* Slow log */ + server.slowlog_log_slower_than = REDIS_SLOWLOG_LOG_SLOWER_THAN; + server.slowlog_max_len = REDIS_SLOWLOG_MAX_LEN; + + /* Debugging */ + server.assert_failed = ""; + server.assert_file = ""; + server.assert_line = 0; + server.bug_report_start = 0; + server.watchdog_period = 0; +} + +/* This function will try to raise the max number of open files accordingly to + * the configured max number of clients. It will also account for 32 additional + * file descriptors as we need a few more for persistence, listening + * sockets, log files and so forth. + * + * If it will not be possible to set the limit accordingly to the configured + * max number of clients, the function will do the reverse setting + * server.maxclients to the value that we can actually handle. */ +void adjustOpenFilesLimit(void) { + rlim_t maxfiles = server.maxclients+32; + struct rlimit limit; + + if (getrlimit(RLIMIT_NOFILE,&limit) == -1) { + redisLog(REDIS_WARNING,"Unable to obtain the current NOFILE limit (%s), assuming 1024 and setting the max clients configuration accordingly.", + strerror(errno)); + server.maxclients = 1024-32; + } else { + rlim_t oldlimit = limit.rlim_cur; + + /* Set the max number of files if the current limit is not enough + * for our needs. */ + if (oldlimit < maxfiles) { + rlim_t f; + + f = maxfiles; + while(f > oldlimit) { + limit.rlim_cur = f; + limit.rlim_max = f; + if (setrlimit(RLIMIT_NOFILE,&limit) != -1) break; + f -= 128; + } + if (f < oldlimit) f = oldlimit; + if (f != maxfiles) { + server.maxclients = f-32; + redisLog(REDIS_WARNING,"Unable to set the max number of files limit to %d (%s), setting the max clients configuration to %d.", + (int) maxfiles, strerror(errno), (int) server.maxclients); + } else { + redisLog(REDIS_NOTICE,"Max number of open files set to %d", + (int) maxfiles); + } + } + } +} + +void initServer() { + int j; + + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + setupSignalHandlers(); + + if (server.syslog_enabled) { + openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT, + server.syslog_facility); + } + + server.current_client = NULL; + server.clients = listCreate(); + server.clients_to_close = listCreate(); + server.slaves = listCreate(); + server.monitors = listCreate(); + server.unblocked_clients = listCreate(); + server.ready_keys = listCreate(); + + createSharedObjects(); + adjustOpenFilesLimit(); + server.el = aeCreateEventLoop(server.maxclients+1024); + server.db = zmalloc(sizeof(redisDb)*server.dbnum); + + if (server.port != 0) { + server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr); + if (server.ipfd == ANET_ERR) { + redisLog(REDIS_WARNING, "Opening port %d: %s", + server.port, server.neterr); + exit(1); + } + } + if (server.unixsocket != NULL) { + unlink(server.unixsocket); /* don't care if this fails */ + server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketperm); + if (server.sofd == ANET_ERR) { + redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr); + exit(1); + } + } + if (server.ipfd < 0 && server.sofd < 0) { + redisLog(REDIS_WARNING, "Configured to not listen anywhere, exiting."); + exit(1); + } + for (j = 0; j < server.dbnum; j++) { + server.db[j].dict = dictCreate(&dbDictType,NULL); + server.db[j].expires = dictCreate(&keyptrDictType,NULL); + server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL); + server.db[j].ready_keys = dictCreate(&setDictType,NULL); + server.db[j].watched_keys = dictCreate(&keylistDictType,NULL); + server.db[j].id = j; + } + server.pubsub_channels = dictCreate(&keylistDictType,NULL); + server.pubsub_patterns = listCreate(); + listSetFreeMethod(server.pubsub_patterns,freePubsubPattern); + listSetMatchMethod(server.pubsub_patterns,listMatchPubsubPattern); + server.cronloops = 0; + server.rdb_child_pid = -1; + server.aof_child_pid = -1; + aofRewriteBufferReset(); + server.aof_buf = sdsempty(); + server.lastsave = time(NULL); + server.rdb_save_time_last = -1; + server.rdb_save_time_start = -1; + server.dirty = 0; + server.stat_numcommands = 0; + server.stat_numconnections = 0; + server.stat_expiredkeys = 0; + server.stat_evictedkeys = 0; + server.stat_starttime = time(NULL); + server.stat_keyspace_misses = 0; + server.stat_keyspace_hits = 0; + server.stat_peak_memory = 0; + server.stat_fork_time = 0; + server.stat_rejected_conn = 0; + memset(server.ops_sec_samples,0,sizeof(server.ops_sec_samples)); + server.ops_sec_idx = 0; + server.ops_sec_last_sample_time = mstime(); + server.ops_sec_last_sample_ops = 0; + server.unixtime = time(NULL); + server.lastbgsave_status = REDIS_OK; + server.stop_writes_on_bgsave_err = 1; + aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL); + if (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE, + acceptTcpHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.ipfd file event."); + if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE, + acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event."); + + if (server.aof_state == REDIS_AOF_ON) { + server.aof_fd = open(server.aof_filename, + O_WRONLY|O_APPEND|O_CREAT,0644); + if (server.aof_fd == -1) { + redisLog(REDIS_WARNING, "Can't open the append-only file: %s", + strerror(errno)); + exit(1); + } + } + + /* 32 bit instances are limited to 4GB of address space, so if there is + * no explicit limit in the user provided configuration we set a limit + * at 3 GB using maxmemory with 'noeviction' policy'. This avoids + * useless crashes of the Redis instance for out of memory. */ + if (server.arch_bits == 32 && server.maxmemory == 0) { + redisLog(REDIS_WARNING,"Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now."); + server.maxmemory = 3072LL*(1024*1024); /* 3 GB */ + server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION; + } + + scriptingInit(); + slowlogInit(); + bioInit(); + ds_init(); +} + +/* Populates the Redis Command Table starting from the hard coded list + * we have on top of redis.c file. */ +void populateCommandTable(void) { + int j; + int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand); + + for (j = 0; j < numcommands; j++) { + struct redisCommand *c = redisCommandTable+j; + char *f = c->sflags; + int retval; + + while(*f != '\0') { + switch(*f) { + case 'w': c->flags |= REDIS_CMD_WRITE; break; + case 'r': c->flags |= REDIS_CMD_READONLY; break; + case 'm': c->flags |= REDIS_CMD_DENYOOM; break; + case 'a': c->flags |= REDIS_CMD_ADMIN; break; + case 'p': c->flags |= REDIS_CMD_PUBSUB; break; + case 'f': c->flags |= REDIS_CMD_FORCE_REPLICATION; break; + case 's': c->flags |= REDIS_CMD_NOSCRIPT; break; + case 'R': c->flags |= REDIS_CMD_RANDOM; break; + case 'S': c->flags |= REDIS_CMD_SORT_FOR_SCRIPT; break; + case 'l': c->flags |= REDIS_CMD_LOADING; break; + case 't': c->flags |= REDIS_CMD_STALE; break; + case 'M': c->flags |= REDIS_CMD_SKIP_MONITOR; break; + default: redisPanic("Unsupported command flag"); break; + } + f++; + } + + retval = dictAdd(server.commands, sdsnew(c->name), c); + assert(retval == DICT_OK); + } +} + +void resetCommandTableStats(void) { + int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand); + int j; + + for (j = 0; j < numcommands; j++) { + struct redisCommand *c = redisCommandTable+j; + + c->microseconds = 0; + c->calls = 0; + } +} + +/* ========================== Redis OP Array API ============================ */ + +void redisOpArrayInit(redisOpArray *oa) { + oa->ops = NULL; + oa->numops = 0; +} + +int redisOpArrayAppend(redisOpArray *oa, struct redisCommand *cmd, int dbid, + robj **argv, int argc, int target) +{ + redisOp *op; + + oa->ops = zrealloc(oa->ops,sizeof(redisOp)*(oa->numops+1)); + op = oa->ops+oa->numops; + op->cmd = cmd; + op->dbid = dbid; + op->argv = argv; + op->argc = argc; + op->target = target; + oa->numops++; + return oa->numops; +} + +void redisOpArrayFree(redisOpArray *oa) { + while(oa->numops) { + int j; + redisOp *op; + + oa->numops--; + op = oa->ops+oa->numops; + for (j = 0; j < op->argc; j++) + decrRefCount(op->argv[j]); + zfree(op->argv); + } + zfree(oa->ops); +} + +/* ====================== Commands lookup and execution ===================== */ + +struct redisCommand *lookupCommand(sds name) { + return dictFetchValue(server.commands, name); +} + +struct redisCommand *lookupCommandByCString(char *s) { + struct redisCommand *cmd; + sds name = sdsnew(s); + + cmd = dictFetchValue(server.commands, name); + sdsfree(name); + return cmd; +} + +/* Propagate the specified command (in the context of the specified database id) + * to AOF and Slaves. + * + * flags are an xor between: + * + REDIS_PROPAGATE_NONE (no propagation of command at all) + * + REDIS_PROPAGATE_AOF (propagate into the AOF file if is enabled) + * + REDIS_PROPAGATE_REPL (propagate into the replication link) + */ +void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, + int flags) +{ + if (server.aof_state != REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF) + feedAppendOnlyFile(cmd,dbid,argv,argc); + if (flags & REDIS_PROPAGATE_REPL && listLength(server.slaves)) + replicationFeedSlaves(server.slaves,dbid,argv,argc); +} + +/* Used inside commands to schedule the propagation of additional commands + * after the current command is propagated to AOF / Replication. */ +void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, + int target) +{ + redisOpArrayAppend(&server.also_propagate,cmd,dbid,argv,argc,target); +} + +/* Call() is the core of Redis execution of a command */ +void call(redisClient *c, int flags) { + long long dirty, start = ustime(), duration; + + /* Sent the command to clients in MONITOR mode, only if the commands are + * not geneated from reading an AOF. */ + if (listLength(server.monitors) && + !server.loading && + !(c->cmd->flags & REDIS_CMD_SKIP_MONITOR)) + { + replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc); + } + + /* Call the command. */ + redisOpArrayInit(&server.also_propagate); + dirty = server.dirty; + c->cmd->proc(c); + dirty = server.dirty-dirty; + duration = ustime()-start; + + /* When EVAL is called loading the AOF we don't want commands called + * from Lua to go into the slowlog or to populate statistics. */ + if (server.loading && c->flags & REDIS_LUA_CLIENT) + flags &= ~(REDIS_CALL_SLOWLOG | REDIS_CALL_STATS); + + /* Log the command into the Slow log if needed, and populate the + * per-command statistics that we show in INFO commandstats. */ + if (flags & REDIS_CALL_SLOWLOG) + slowlogPushEntryIfNeeded(c->argv,c->argc,duration); + if (flags & REDIS_CALL_STATS) { + c->cmd->microseconds += duration; + c->cmd->calls++; + } + + /* Propagate the command into the AOF and replication link */ + if (flags & REDIS_CALL_PROPAGATE) { + int flags = REDIS_PROPAGATE_NONE; + + if (c->cmd->flags & REDIS_CMD_FORCE_REPLICATION) + flags |= REDIS_PROPAGATE_REPL; + if (dirty) + flags |= (REDIS_PROPAGATE_REPL | REDIS_PROPAGATE_AOF); + if (flags != REDIS_PROPAGATE_NONE) + propagate(c->cmd,c->db->id,c->argv,c->argc,flags); + } + /* Commands such as LPUSH or BRPOPLPUSH may propagate an additional + * PUSH command. */ + if (server.also_propagate.numops) { + int j; + redisOp *rop; + + for (j = 0; j < server.also_propagate.numops; j++) { + rop = &server.also_propagate.ops[j]; + propagate(rop->cmd, rop->dbid, rop->argv, rop->argc, rop->target); + } + redisOpArrayFree(&server.also_propagate); + } + server.stat_numcommands++; +} + +/* If this function gets called we already read a whole + * command, arguments are in the client argv/argc fields. + * processCommand() execute the command or prepare the + * server for a bulk read from the client. + * + * If 1 is returned the client is still alive and valid and + * and other operations can be performed by the caller. Otherwise + * if 0 is returned the client was destroied (i.e. after QUIT). */ +int processCommand(redisClient *c) { + /* The QUIT command is handled separately. Normal command procs will + * go through checking for replication and QUIT will cause trouble + * when FORCE_REPLICATION is enabled and would be implemented in + * a regular command proc. */ + if (!strcasecmp(c->argv[0]->ptr,"quit")) { + addReply(c,shared.ok); + c->flags |= REDIS_CLOSE_AFTER_REPLY; + return REDIS_ERR; + } + + /* Now lookup the command and check ASAP about trivial error conditions + * such as wrong arity, bad command name and so forth. */ + c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr); + if (!c->cmd) { + flagTransaction(c); + addReplyErrorFormat(c,"unknown command '%s'", + (char*)c->argv[0]->ptr); + return REDIS_OK; + } else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) || + (c->argc < -c->cmd->arity)) { + flagTransaction(c); + addReplyErrorFormat(c,"wrong number of arguments for '%s' command", + c->cmd->name); + return REDIS_OK; + } + + /* Check if the user is authenticated */ + if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand) + { + flagTransaction(c); + addReplyError(c,"operation not permitted"); + return REDIS_OK; + } + + /* Handle the maxmemory directive. + * + * First we try to free some memory if possible (if there are volatile + * keys in the dataset). If there are not the only thing we can do + * is returning an error. */ + if (server.maxmemory) { + int retval = freeMemoryIfNeeded(); + if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) { + flagTransaction(c); + addReply(c, shared.oomerr); + return REDIS_OK; + } + } + + /* Don't accept write commands if there are problems persisting on disk. */ + if (server.stop_writes_on_bgsave_err && + server.saveparamslen > 0 + && server.lastbgsave_status == REDIS_ERR && + c->cmd->flags & REDIS_CMD_WRITE) + { + flagTransaction(c); + addReply(c, shared.bgsaveerr); + return REDIS_OK; + } + + /* Don't accept write commands if this is a read only slave. But + * accept write commands if this is our master. */ + if (server.masterhost && server.repl_slave_ro && + !(c->flags & REDIS_MASTER) && + c->cmd->flags & REDIS_CMD_WRITE) + { + addReply(c, shared.roslaveerr); + return REDIS_OK; + } + + /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */ + if ((dictSize(c->pubsub_channels) > 0 || listLength(c->pubsub_patterns) > 0) + && + c->cmd->proc != subscribeCommand && + c->cmd->proc != unsubscribeCommand && + c->cmd->proc != psubscribeCommand && + c->cmd->proc != punsubscribeCommand) { + addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context"); + return REDIS_OK; + } + + /* Only allow INFO and SLAVEOF when slave-serve-stale-data is no and + * we are a slave with a broken link with master. */ + if (server.masterhost && server.repl_state != REDIS_REPL_CONNECTED && + server.repl_serve_stale_data == 0 && + !(c->cmd->flags & REDIS_CMD_STALE)) + { + flagTransaction(c); + addReply(c, shared.masterdownerr); + return REDIS_OK; + } + + /* Loading DB? Return an error if the command has not the + * REDIS_CMD_LOADING flag. */ + if (server.loading && !(c->cmd->flags & REDIS_CMD_LOADING)) { + addReply(c, shared.loadingerr); + return REDIS_OK; + } + + /* Lua script too slow? Only allow commands with REDIS_CMD_STALE flag. */ + if (server.lua_timedout && + c->cmd->proc != authCommand && + !(c->cmd->proc == shutdownCommand && + c->argc == 2 && + tolower(((char*)c->argv[1]->ptr)[0]) == 'n') && + !(c->cmd->proc == scriptCommand && + c->argc == 2 && + tolower(((char*)c->argv[1]->ptr)[0]) == 'k')) + { + flagTransaction(c); + addReply(c, shared.slowscripterr); + return REDIS_OK; + } + + /* Exec the command */ + if (c->flags & REDIS_MULTI && + c->cmd->proc != execCommand && c->cmd->proc != discardCommand && + c->cmd->proc != multiCommand && c->cmd->proc != watchCommand) + { + queueMultiCommand(c); + addReply(c,shared.queued); + } else { + call(c,REDIS_CALL_FULL); + if (listLength(server.ready_keys)) + handleClientsBlockedOnLists(); + } + return REDIS_OK; +} + +/*================================== Shutdown =============================== */ + +int prepareForShutdown(int flags) { + int save = flags & REDIS_SHUTDOWN_SAVE; + int nosave = flags & REDIS_SHUTDOWN_NOSAVE; + + redisLog(REDIS_WARNING,"User requested shutdown..."); + /* Kill the saving child if there is a background saving in progress. + We want to avoid race conditions, for instance our saving child may + overwrite the synchronous saving did by SHUTDOWN. */ + if (server.rdb_child_pid != -1) { + redisLog(REDIS_WARNING,"There is a child saving an .rdb. Killing it!"); + kill(server.rdb_child_pid,SIGKILL); + rdbRemoveTempFile(server.rdb_child_pid); + } + if (server.aof_state != REDIS_AOF_OFF) { + /* Kill the AOF saving child as the AOF we already have may be longer + * but contains the full dataset anyway. */ + if (server.aof_child_pid != -1) { + redisLog(REDIS_WARNING, + "There is a child rewriting the AOF. Killing it!"); + kill(server.aof_child_pid,SIGKILL); + } + /* Append only file: fsync() the AOF and exit */ + redisLog(REDIS_NOTICE,"Calling fsync() on the AOF file."); + aof_fsync(server.aof_fd); + } + if ((server.saveparamslen > 0 && !nosave) || save) { + redisLog(REDIS_NOTICE,"Saving the final RDB snapshot before exiting."); + /* Snapshotting. Perform a SYNC SAVE and exit */ + if (rdbSave(server.rdb_filename) != REDIS_OK) { + /* Ooops.. error saving! The best we can do is to continue + * operating. Note that if there was a background saving process, + * in the next cron() Redis will be notified that the background + * saving aborted, handling special stuff like slaves pending for + * synchronization... */ + redisLog(REDIS_WARNING,"Error trying to save the DB, can't exit."); + return REDIS_ERR; + } + } + if (server.daemonize) { + redisLog(REDIS_NOTICE,"Removing the pid file."); + unlink(server.pidfile); + } + /* Close the listening sockets. Apparently this allows faster restarts. */ + if (server.ipfd != -1) close(server.ipfd); + if (server.sofd != -1) close(server.sofd); + if (server.unixsocket) { + redisLog(REDIS_NOTICE,"Removing the unix socket file."); + unlink(server.unixsocket); /* don't care if this fails */ + } + + redisLog(REDIS_WARNING,"Redis is now ready to exit, bye bye..."); + return REDIS_OK; +} + +/*================================== Commands =============================== */ + +/* Return zero if strings are the same, non-zero if they are not. + * The comparison is performed in a way that prevents an attacker to obtain + * information about the nature of the strings just monitoring the execution + * time of the function. + * + * Note that limiting the comparison length to strings up to 512 bytes we + * can avoid leaking any information about the password length and any + * possible branch misprediction related leak. + */ +int time_independent_strcmp(char *a, char *b) { + char bufa[REDIS_AUTHPASS_MAX_LEN], bufb[REDIS_AUTHPASS_MAX_LEN]; + /* The above two strlen perform len(a) + len(b) operations where either + * a or b are fixed (our password) length, and the difference is only + * relative to the length of the user provided string, so no information + * leak is possible in the following two lines of code. */ + int alen = strlen(a); + int blen = strlen(b); + int j; + int diff = 0; + + /* We can't compare strings longer than our static buffers. + * Note that this will never pass the first test in practical circumstances + * so there is no info leak. */ + if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1; + + memset(bufa,0,sizeof(bufa)); /* Constant time. */ + memset(bufb,0,sizeof(bufb)); /* Constant time. */ + /* Again the time of the following two copies is proportional to + * len(a) + len(b) so no info is leaked. */ + memcpy(bufa,a,alen); + memcpy(bufb,b,blen); + + /* Always compare all the chars in the two buffers without + * conditional expressions. */ + for (j = 0; j < sizeof(bufa); j++) { + diff |= (bufa[j] ^ bufb[j]); + } + /* Length must be equal as well. */ + diff |= alen ^ blen; + return diff; /* If zero strings are the same. */ +} + +void authCommand(redisClient *c) { + if (!server.requirepass) { + addReplyError(c,"Client sent AUTH, but no password is set"); + } else if (!time_independent_strcmp(c->argv[1]->ptr, server.requirepass)) { + c->authenticated = 1; + addReply(c,shared.ok); + } else { + c->authenticated = 0; + addReplyError(c,"invalid password"); + } +} + +void pingCommand(redisClient *c) { + addReply(c,shared.pong); +} + +void echoCommand(redisClient *c) { + addReplyBulk(c,c->argv[1]); +} + +void timeCommand(redisClient *c) { + struct timeval tv; + + /* gettimeofday() can only fail if &tv is a bad addresss so we + * don't check for errors. */ + gettimeofday(&tv,NULL); + addReplyMultiBulkLen(c,2); + addReplyBulkLongLong(c,tv.tv_sec); + addReplyBulkLongLong(c,tv.tv_usec); +} + +/* Convert an amount of bytes into a human readable string in the form + * of 100B, 2G, 100M, 4K, and so forth. */ +void bytesToHuman(char *s, unsigned long long n) { + double d; + + if (n < 1024) { + /* Bytes */ + sprintf(s,"%lluB",n); + return; + } else if (n < (1024*1024)) { + d = (double)n/(1024); + sprintf(s,"%.2fK",d); + } else if (n < (1024LL*1024*1024)) { + d = (double)n/(1024*1024); + sprintf(s,"%.2fM",d); + } else if (n < (1024LL*1024*1024*1024)) { + d = (double)n/(1024LL*1024*1024); + sprintf(s,"%.2fG",d); + } +} + +/* Create the string returned by the INFO command. This is decoupled + * by the INFO command itself as we need to report the same information + * on memory corruption problems. */ +sds genRedisInfoString(char *section) { + sds info = sdsempty(); + time_t uptime = server.unixtime-server.stat_starttime; + int j, numcommands; + struct rusage self_ru, c_ru; + unsigned long lol, bib; + int allsections = 0, defsections = 0; + int sections = 0; + + if (section) { + allsections = strcasecmp(section,"all") == 0; + defsections = strcasecmp(section,"default") == 0; + } + + getrusage(RUSAGE_SELF, &self_ru); + getrusage(RUSAGE_CHILDREN, &c_ru); + getClientsMaxBuffers(&lol,&bib); + + /* Server */ + if (allsections || defsections || !strcasecmp(section,"server")) { + struct utsname name; + char *mode; + + if (server.sentinel_mode) mode = "sentinel"; + else mode = "standalone"; + + if (sections++) info = sdscat(info,"\r\n"); + uname(&name); + info = sdscatprintf(info, + "# Server\r\n" + "redis_version:%s\r\n" + "redis_git_sha1:%s\r\n" + "redis_git_dirty:%d\r\n" + "redis_mode:%s\r\n" + "os:%s %s %s\r\n" + "arch_bits:%d\r\n" + "multiplexing_api:%s\r\n" + "gcc_version:%d.%d.%d\r\n" + "process_id:%ld\r\n" + "run_id:%s\r\n" + "tcp_port:%d\r\n" + "uptime_in_seconds:%ld\r\n" + "uptime_in_days:%ld\r\n" + "lru_clock:%ld\r\n", + REDIS_VERSION, + redisGitSHA1(), + strtol(redisGitDirty(),NULL,10) > 0, + mode, + name.sysname, name.release, name.machine, + server.arch_bits, + aeGetApiName(), +#ifdef __GNUC__ + __GNUC__,__GNUC_MINOR__,__GNUC_PATCHLEVEL__, +#else + 0,0,0, +#endif + (long) getpid(), + server.runid, + server.port, + uptime, + uptime/(3600*24), + (unsigned long) server.lruclock); + } + + /* Clients */ + if (allsections || defsections || !strcasecmp(section,"clients")) { + if (sections++) info = sdscat(info,"\r\n"); + info = sdscatprintf(info, + "# Clients\r\n" + "connected_clients:%lu\r\n" + "client_longest_output_list:%lu\r\n" + "client_biggest_input_buf:%lu\r\n" + "blocked_clients:%d\r\n", + listLength(server.clients)-listLength(server.slaves), + lol, bib, + server.bpop_blocked_clients); + } + + /* Memory */ + if (allsections || defsections || !strcasecmp(section,"memory")) { + char hmem[64]; + char peak_hmem[64]; + + bytesToHuman(hmem,zmalloc_used_memory()); + bytesToHuman(peak_hmem,server.stat_peak_memory); + if (sections++) info = sdscat(info,"\r\n"); + info = sdscatprintf(info, + "# Memory\r\n" + "used_memory:%zu\r\n" + "used_memory_human:%s\r\n" + "used_memory_rss:%zu\r\n" + "used_memory_peak:%zu\r\n" + "used_memory_peak_human:%s\r\n" + "used_memory_lua:%lld\r\n" + "mem_fragmentation_ratio:%.2f\r\n" + "mem_allocator:%s\r\n", + zmalloc_used_memory(), + hmem, + zmalloc_get_rss(), + server.stat_peak_memory, + peak_hmem, + ((long long)lua_gc(server.lua,LUA_GCCOUNT,0))*1024LL, + zmalloc_get_fragmentation_ratio(), + ZMALLOC_LIB + ); + } + + /* Persistence */ + if (allsections || defsections || !strcasecmp(section,"persistence")) { + if (sections++) info = sdscat(info,"\r\n"); + info = sdscatprintf(info, + "# Persistence\r\n" + "loading:%d\r\n" + "rdb_changes_since_last_save:%lld\r\n" + "rdb_bgsave_in_progress:%d\r\n" + "rdb_last_save_time:%ld\r\n" + "rdb_last_bgsave_status:%s\r\n" + "rdb_last_bgsave_time_sec:%ld\r\n" + "rdb_current_bgsave_time_sec:%ld\r\n" + "aof_enabled:%d\r\n" + "aof_rewrite_in_progress:%d\r\n" + "aof_rewrite_scheduled:%d\r\n" + "aof_last_rewrite_time_sec:%ld\r\n" + "aof_current_rewrite_time_sec:%ld\r\n" + "aof_last_bgrewrite_status:%s\r\n", + server.loading, + server.dirty, + server.rdb_child_pid != -1, + server.lastsave, + (server.lastbgsave_status == REDIS_OK) ? "ok" : "err", + server.rdb_save_time_last, + (server.rdb_child_pid == -1) ? + -1 : time(NULL)-server.rdb_save_time_start, + server.aof_state != REDIS_AOF_OFF, + server.aof_child_pid != -1, + server.aof_rewrite_scheduled, + server.aof_rewrite_time_last, + (server.aof_child_pid == -1) ? + -1 : time(NULL)-server.aof_rewrite_time_start, + (server.aof_lastbgrewrite_status == REDIS_OK) ? "ok" : "err"); + + if (server.aof_state != REDIS_AOF_OFF) { + info = sdscatprintf(info, + "aof_current_size:%lld\r\n" + "aof_base_size:%lld\r\n" + "aof_pending_rewrite:%d\r\n" + "aof_buffer_length:%zu\r\n" + "aof_rewrite_buffer_length:%lu\r\n" + "aof_pending_bio_fsync:%llu\r\n" + "aof_delayed_fsync:%lu\r\n", + (long long) server.aof_current_size, + (long long) server.aof_rewrite_base_size, + server.aof_rewrite_scheduled, + sdslen(server.aof_buf), + aofRewriteBufferSize(), + bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC), + server.aof_delayed_fsync); + } + + if (server.loading) { + double perc; + time_t eta, elapsed; + off_t remaining_bytes = server.loading_total_bytes- + server.loading_loaded_bytes; + + perc = ((double)server.loading_loaded_bytes / + server.loading_total_bytes) * 100; + + elapsed = server.unixtime-server.loading_start_time; + if (elapsed == 0) { + eta = 1; /* A fake 1 second figure if we don't have + enough info */ + } else { + eta = (elapsed*remaining_bytes)/server.loading_loaded_bytes; + } + + info = sdscatprintf(info, + "loading_start_time:%ld\r\n" + "loading_total_bytes:%llu\r\n" + "loading_loaded_bytes:%llu\r\n" + "loading_loaded_perc:%.2f\r\n" + "loading_eta_seconds:%ld\r\n" + ,(unsigned long) server.loading_start_time, + (unsigned long long) server.loading_total_bytes, + (unsigned long long) server.loading_loaded_bytes, + perc, + eta + ); + } + } + + /* Stats */ + if (allsections || defsections || !strcasecmp(section,"stats")) { + if (sections++) info = sdscat(info,"\r\n"); + info = sdscatprintf(info, + "# Stats\r\n" + "total_connections_received:%lld\r\n" + "total_commands_processed:%lld\r\n" + "instantaneous_ops_per_sec:%lld\r\n" + "rejected_connections:%lld\r\n" + "expired_keys:%lld\r\n" + "evicted_keys:%lld\r\n" + "keyspace_hits:%lld\r\n" + "keyspace_misses:%lld\r\n" + "pubsub_channels:%ld\r\n" + "pubsub_patterns:%lu\r\n" + "latest_fork_usec:%lld\r\n", + server.stat_numconnections, + server.stat_numcommands, + getOperationsPerSecond(), + server.stat_rejected_conn, + server.stat_expiredkeys, + server.stat_evictedkeys, + server.stat_keyspace_hits, + server.stat_keyspace_misses, + dictSize(server.pubsub_channels), + listLength(server.pubsub_patterns), + server.stat_fork_time); + } + + /* Replication */ + if (allsections || defsections || !strcasecmp(section,"replication")) { + if (sections++) info = sdscat(info,"\r\n"); + info = sdscatprintf(info, + "# Replication\r\n" + "role:%s\r\n", + server.masterhost == NULL ? "master" : "slave"); + if (server.masterhost) { + info = sdscatprintf(info, + "master_host:%s\r\n" + "master_port:%d\r\n" + "master_link_status:%s\r\n" + "master_last_io_seconds_ago:%d\r\n" + "master_sync_in_progress:%d\r\n" + ,server.masterhost, + server.masterport, + (server.repl_state == REDIS_REPL_CONNECTED) ? + "up" : "down", + server.master ? + ((int)(server.unixtime-server.master->lastinteraction)) : -1, + server.repl_state == REDIS_REPL_TRANSFER + ); + + if (server.repl_state == REDIS_REPL_TRANSFER) { + info = sdscatprintf(info, + "master_sync_left_bytes:%lld\r\n" + "master_sync_last_io_seconds_ago:%d\r\n" + , (long long) + (server.repl_transfer_size - server.repl_transfer_read), + (int)(server.unixtime-server.repl_transfer_lastio) + ); + } + + if (server.repl_state != REDIS_REPL_CONNECTED) { + info = sdscatprintf(info, + "master_link_down_since_seconds:%ld\r\n", + (long)server.unixtime-server.repl_down_since); + } + info = sdscatprintf(info, + "slave_priority:%d\r\n" + "slave_read_only:%d\r\n", + server.slave_priority, + server.repl_slave_ro); + } + info = sdscatprintf(info, + "connected_slaves:%lu\r\n", + listLength(server.slaves)); + if (listLength(server.slaves)) { + int slaveid = 0; + listNode *ln; + listIter li; + + listRewind(server.slaves,&li); + while((ln = listNext(&li))) { + redisClient *slave = listNodeValue(ln); + char *state = NULL; + char ip[32]; + int port; + + if (anetPeerToString(slave->fd,ip,&port) == -1) continue; + switch(slave->replstate) { + case REDIS_REPL_WAIT_BGSAVE_START: + case REDIS_REPL_WAIT_BGSAVE_END: + state = "wait_bgsave"; + break; + case REDIS_REPL_SEND_BULK: + state = "send_bulk"; + break; + case REDIS_REPL_ONLINE: + state = "online"; + break; + } + if (state == NULL) continue; + info = sdscatprintf(info,"slave%d:%s,%d,%s\r\n", + slaveid,ip,slave->slave_listening_port,state); + slaveid++; + } + } + } + + /* CPU */ + if (allsections || defsections || !strcasecmp(section,"cpu")) { + if (sections++) info = sdscat(info,"\r\n"); + info = sdscatprintf(info, + "# CPU\r\n" + "used_cpu_sys:%.2f\r\n" + "used_cpu_user:%.2f\r\n" + "used_cpu_sys_children:%.2f\r\n" + "used_cpu_user_children:%.2f\r\n", + (float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000, + (float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000, + (float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000, + (float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000); + } + + /* cmdtime */ + if (allsections || !strcasecmp(section,"commandstats")) { + if (sections++) info = sdscat(info,"\r\n"); + info = sdscatprintf(info, "# Commandstats\r\n"); + numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand); + for (j = 0; j < numcommands; j++) { + struct redisCommand *c = redisCommandTable+j; + + if (!c->calls) continue; + info = sdscatprintf(info, + "cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f\r\n", + c->name, c->calls, c->microseconds, + (c->calls == 0) ? 0 : ((float)c->microseconds/c->calls)); + } + } + + /* Key space */ + if (allsections || defsections || !strcasecmp(section,"keyspace")) { + if (sections++) info = sdscat(info,"\r\n"); + info = sdscatprintf(info, "# Keyspace\r\n"); + for (j = 0; j < server.dbnum; j++) { + long long keys, vkeys; + + keys = dictSize(server.db[j].dict); + vkeys = dictSize(server.db[j].expires); + if (keys || vkeys) { + info = sdscatprintf(info, "db%d:keys=%lld,expires=%lld\r\n", + j, keys, vkeys); + } + } + } + return info; +} + +void infoCommand(redisClient *c) { + char *section = c->argc == 2 ? c->argv[1]->ptr : "default"; + + if (c->argc > 2) { + addReply(c,shared.syntaxerr); + return; + } + sds info = genRedisInfoString(section); + addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n", + (unsigned long)sdslen(info))); + addReplySds(c,info); + addReply(c,shared.crlf); +} + +void monitorCommand(redisClient *c) { + /* ignore MONITOR if already slave or in monitor mode */ + if (c->flags & REDIS_SLAVE) return; + + c->flags |= (REDIS_SLAVE|REDIS_MONITOR); + c->slaveseldb = 0; + listAddNodeTail(server.monitors,c); + addReply(c,shared.ok); +} + +/* ============================ Maxmemory directive ======================== */ + +/* This function gets called when 'maxmemory' is set on the config file to limit + * the max memory used by the server, before processing a command. + * + * The goal of the function is to free enough memory to keep Redis under the + * configured memory limit. + * + * The function starts calculating how many bytes should be freed to keep + * Redis under the limit, and enters a loop selecting the best keys to + * evict accordingly to the configured policy. + * + * If all the bytes needed to return back under the limit were freed the + * function returns REDIS_OK, otherwise REDIS_ERR is returned, and the caller + * should block the execution of commands that will result in more memory + * used by the server. + */ +int freeMemoryIfNeeded(void) { + size_t mem_used, mem_tofree, mem_freed; + int slaves = listLength(server.slaves); + + /* Remove the size of slaves output buffers and AOF buffer from the + * count of used memory. */ + mem_used = zmalloc_used_memory(); + if (slaves) { + listIter li; + listNode *ln; + + listRewind(server.slaves,&li); + while((ln = listNext(&li))) { + redisClient *slave = listNodeValue(ln); + unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave); + if (obuf_bytes > mem_used) + mem_used = 0; + else + mem_used -= obuf_bytes; + } + } + if (server.aof_state != REDIS_AOF_OFF) { + mem_used -= sdslen(server.aof_buf); + mem_used -= aofRewriteBufferSize(); + } + + /* Check if we are over the memory limit. */ + if (mem_used <= server.maxmemory) return REDIS_OK; + + if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION) + return REDIS_ERR; /* We need to free memory, but policy forbids. */ + + /* Compute how much memory we need to free. */ + mem_tofree = mem_used - server.maxmemory; + mem_freed = 0; + while (mem_freed < mem_tofree) { + int j, k, keys_freed = 0; + + for (j = 0; j < server.dbnum; j++) { + long bestval = 0; /* just to prevent warning */ + sds bestkey = NULL; + struct dictEntry *de; + redisDb *db = server.db+j; + dict *dict; + + if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || + server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM) + { + dict = server.db[j].dict; + } else { + dict = server.db[j].expires; + } + if (dictSize(dict) == 0) continue; + + /* volatile-random and allkeys-random policy */ + if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM || + server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM) + { + de = dictGetRandomKey(dict); + bestkey = dictGetKey(de); + } + + /* volatile-lru and allkeys-lru policy */ + else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU || + server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) + { + for (k = 0; k < server.maxmemory_samples; k++) { + sds thiskey; + long thisval; + robj *o; + + de = dictGetRandomKey(dict); + thiskey = dictGetKey(de); + /* When policy is volatile-lru we need an additional lookup + * to locate the real key, as dict is set to db->expires. */ + if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) + de = dictFind(db->dict, thiskey); + o = dictGetVal(de); + thisval = estimateObjectIdleTime(o); + + /* Higher idle time is better candidate for deletion */ + if (bestkey == NULL || thisval > bestval) { + bestkey = thiskey; + bestval = thisval; + } + } + } + + /* volatile-ttl */ + else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) { + for (k = 0; k < server.maxmemory_samples; k++) { + sds thiskey; + long thisval; + + de = dictGetRandomKey(dict); + thiskey = dictGetKey(de); + thisval = (long) dictGetVal(de); + + /* Expire sooner (minor expire unix timestamp) is better + * candidate for deletion */ + if (bestkey == NULL || thisval < bestval) { + bestkey = thiskey; + bestval = thisval; + } + } + } + + /* Finally remove the selected key. */ + if (bestkey) { + long long delta; + + robj *keyobj = createStringObject(bestkey,sdslen(bestkey)); + propagateExpire(db,keyobj); + /* We compute the amount of memory freed by dbDelete() alone. + * It is possible that actually the memory needed to propagate + * the DEL in AOF and replication link is greater than the one + * we are freeing removing the key, but we can't account for + * that otherwise we would never exit the loop. + * + * AOF and Output buffer memory will be freed eventually so + * we only care about memory used by the key space. */ + delta = (long long) zmalloc_used_memory(); + dbDelete(db,keyobj); + delta -= (long long) zmalloc_used_memory(); + mem_freed += delta; + server.stat_evictedkeys++; + decrRefCount(keyobj); + keys_freed++; + + /* When the memory to free starts to be big enough, we may + * start spending so much time here that is impossible to + * deliver data to the slaves fast enough, so we force the + * transmission here inside the loop. */ + if (slaves) flushSlavesOutputBuffers(); + } + } + if (!keys_freed) return REDIS_ERR; /* nothing to free... */ + } + return REDIS_OK; +} + +/* =================================== Main! ================================ */ + +#ifdef __linux__ +int linuxOvercommitMemoryValue(void) { + FILE *fp = fopen("/proc/sys/vm/overcommit_memory","r"); + char buf[64]; + + if (!fp) return -1; + if (fgets(buf,64,fp) == NULL) { + fclose(fp); + return -1; + } + fclose(fp); + + return atoi(buf); +} + +void linuxOvercommitMemoryWarning(void) { + if (linuxOvercommitMemoryValue() == 0) { + redisLog(REDIS_WARNING,"WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect."); + } +} +#endif /* __linux__ */ + +void createPidFile(void) { + /* Try to write the pid file in a best-effort way. */ + FILE *fp = fopen(server.pidfile,"w"); + if (fp) { + fprintf(fp,"%d\n",(int)getpid()); + fclose(fp); + } +} + +void daemonize(void) { + int fd; + + if (fork() != 0) exit(0); /* parent exits */ + setsid(); /* create a new session */ + + /* Every output goes to /dev/null. If Redis is daemonized but + * the 'logfile' is set to 'stdout' in the configuration file + * it will not log at all. */ + if ((fd = open("/dev/null", O_RDWR, 0)) != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) close(fd); + } +} + +void version() { + printf("Redis server v=%s sha=%s:%d malloc=%s bits=%d\n", + REDIS_VERSION, + redisGitSHA1(), + atoi(redisGitDirty()) > 0, + ZMALLOC_LIB, + sizeof(long) == 4 ? 32 : 64); + exit(0); +} + +void usage() { + fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf] [options]\n"); + fprintf(stderr," ./redis-server - (read config from stdin)\n"); + fprintf(stderr," ./redis-server -v or --version\n"); + fprintf(stderr," ./redis-server -h or --help\n"); + fprintf(stderr," ./redis-server --test-memory \n\n"); + fprintf(stderr,"Examples:\n"); + fprintf(stderr," ./redis-server (run the server with default conf)\n"); + fprintf(stderr," ./redis-server /etc/redis/6379.conf\n"); + fprintf(stderr," ./redis-server --port 7777\n"); + fprintf(stderr," ./redis-server --port 7777 --slaveof 127.0.0.1 8888\n"); + fprintf(stderr," ./redis-server /etc/myredis.conf --loglevel verbose\n\n"); + fprintf(stderr,"Sentinel mode:\n"); + fprintf(stderr," ./redis-server /etc/sentinel.conf --sentinel\n"); + exit(1); +} + +void redisAsciiArt(void) { +#include "asciilogo.h" + char *buf = zmalloc(1024*16); + char *mode = "stand alone"; + + if (server.sentinel_mode) mode = "sentinel"; + + snprintf(buf,1024*16,ascii_logo, + REDIS_VERSION, + redisGitSHA1(), + strtol(redisGitDirty(),NULL,10) > 0, + (sizeof(long) == 8) ? "64" : "32", + mode, server.port, + (long) getpid() + ); + redisLogRaw(REDIS_NOTICE|REDIS_LOG_RAW,buf); + zfree(buf); +} + +static void sigtermHandler(int sig) { + REDIS_NOTUSED(sig); + + redisLogFromHandler(REDIS_WARNING,"Received SIGTERM, scheduling shutdown..."); + server.shutdown_asap = 1; +} + +void setupSignalHandlers(void) { + struct sigaction act; + + /* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used. + * Otherwise, sa_handler is used. */ + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = sigtermHandler; + sigaction(SIGTERM, &act, NULL); + +#ifdef HAVE_BACKTRACE + sigemptyset(&act.sa_mask); + act.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; + act.sa_sigaction = sigsegvHandler; + sigaction(SIGSEGV, &act, NULL); + sigaction(SIGBUS, &act, NULL); + sigaction(SIGFPE, &act, NULL); + sigaction(SIGILL, &act, NULL); +#endif + return; +} + +void memtest(size_t megabytes, int passes); + +/* Returns 1 if there is --sentinel among the arguments or if + * argv[0] is exactly "redis-sentinel". */ +int checkForSentinelMode(int argc, char **argv) { + int j; + + if (strstr(argv[0],"redis-sentinel") != NULL) return 1; + for (j = 1; j < argc; j++) + if (!strcmp(argv[j],"--sentinel")) return 1; + return 0; +} + +/* Function called at startup to load RDB or AOF file in memory. */ +void loadDataFromDisk(void) { + long long start = ustime(); + if (server.aof_state == REDIS_AOF_ON) { + if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK) + redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000); + } else { + if (rdbLoad(server.rdb_filename) == REDIS_OK) { + redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds", + (float)(ustime()-start)/1000000); + } else if (errno != ENOENT) { + redisLog(REDIS_WARNING,"Fatal error loading the DB. Exiting."); + exit(1); + } + } +} + +void redisOutOfMemoryHandler(size_t allocation_size) { + redisLog(REDIS_WARNING,"Out Of Memory allocating %zu bytes!", + allocation_size); + redisPanic("OOM"); +} + +int main(int argc, char **argv) { + struct timeval tv; + + /* We need to initialize our libraries, and the server configuration. */ + zmalloc_enable_thread_safeness(); + zmalloc_set_oom_handler(redisOutOfMemoryHandler); + srand(time(NULL)^getpid()); + gettimeofday(&tv,NULL); + dictSetHashFunctionSeed(tv.tv_sec^tv.tv_usec^getpid()); + server.sentinel_mode = checkForSentinelMode(argc,argv); + initServerConfig(); + + /* We need to init sentinel right now as parsing the configuration file + * in sentinel mode will have the effect of populating the sentinel + * data structures with master nodes to monitor. */ + if (server.sentinel_mode) { + initSentinelConfig(); + initSentinel(); + } + + if (argc >= 2) { + int j = 1; /* First option to parse in argv[] */ + sds options = sdsempty(); + char *configfile = NULL; + + /* Handle special options --help and --version */ + if (strcmp(argv[1], "-v") == 0 || + strcmp(argv[1], "--version") == 0) version(); + if (strcmp(argv[1], "--help") == 0 || + strcmp(argv[1], "-h") == 0) usage(); + if (strcmp(argv[1], "--test-memory") == 0) { + if (argc == 3) { + memtest(atoi(argv[2]),50); + exit(0); + } else { + fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n"); + fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n"); + exit(1); + } + } + + /* First argument is the config file name? */ + if (argv[j][0] != '-' || argv[j][1] != '-') + configfile = argv[j++]; + /* All the other options are parsed and conceptually appended to the + * configuration file. For instance --port 6380 will generate the + * string "port 6380\n" to be parsed after the actual file name + * is parsed, if any. */ + while(j != argc) { + if (argv[j][0] == '-' && argv[j][1] == '-') { + /* Option name */ + if (sdslen(options)) options = sdscat(options,"\n"); + options = sdscat(options,argv[j]+2); + options = sdscat(options," "); + } else { + /* Option argument */ + options = sdscatrepr(options,argv[j],strlen(argv[j])); + options = sdscat(options," "); + } + j++; + } + resetServerSaveParams(); + loadServerConfig(configfile,options); + sdsfree(options); + } else { + redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis"); + } + if (server.daemonize) daemonize(); + initServer(); + if (server.daemonize) createPidFile(); + redisAsciiArt(); + + if (!server.sentinel_mode) { + /* Things only needed when not running in Sentinel mode. */ + redisLog(REDIS_WARNING,"Server started, Redis version " REDIS_VERSION); + #ifdef __linux__ + linuxOvercommitMemoryWarning(); + #endif + loadDataFromDisk(); + if (server.ipfd > 0) + redisLog(REDIS_NOTICE,"The server is now ready to accept connections on port %d", server.port); + if (server.sofd > 0) + redisLog(REDIS_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket); + } + + /* Warning the user about suspicious maxmemory setting. */ + if (server.maxmemory > 0 && server.maxmemory < 1024*1024) { + redisLog(REDIS_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory); + } + + aeSetBeforeSleepProc(server.el,beforeSleep); + aeMain(server.el); + aeDeleteEventLoop(server.el); + return 0; +} + +/* The End */ diff --git a/src/redis.h b/src/redis.h index c71e372..4c91dcc 100644 --- a/src/redis.h +++ b/src/redis.h @@ -1,1255 +1,1258 @@ -/* - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __REDIS_H -#define __REDIS_H - -#include "fmacros.h" -#include "config.h" - -#if defined(__sun) -#include "solarisfixes.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ae.h" /* Event driven programming library */ -#include "sds.h" /* Dynamic safe strings */ -#include "dict.h" /* Hash tables */ -#include "adlist.h" /* Linked lists */ -#include "zmalloc.h" /* total memory usage aware version of malloc/free */ -#include "anet.h" /* Networking the easy way */ -#include "ziplist.h" /* Compact list data structure */ -#include "intset.h" /* Compact integer set structure */ -#include "version.h" /* Version macro */ -#include "util.h" /* Misc functions useful in many places */ - -/* Error codes */ -#define REDIS_OK 0 -#define REDIS_ERR -1 - -/* Static server configuration */ -#define REDIS_HZ 100 /* Time interrupt calls/sec. */ -#define REDIS_SERVERPORT 6379 /* TCP port */ -#define REDIS_MAXIDLETIME 0 /* default client timeout: infinite */ -#define REDIS_DEFAULT_DBNUM 16 -#define REDIS_CONFIGLINE_MAX 1024 -#define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* lookup 10 expires per loop */ -#define REDIS_EXPIRELOOKUPS_TIME_PERC 25 /* CPU max % for keys collection */ -#define REDIS_MAX_WRITE_PER_EVENT (1024*64) -#define REDIS_SHARED_SELECT_CMDS 10 -#define REDIS_SHARED_INTEGERS 10000 -#define REDIS_SHARED_BULKHDR_LEN 32 -#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */ -#define REDIS_AOF_REWRITE_PERC 100 -#define REDIS_AOF_REWRITE_MIN_SIZE (1024*1024) -#define REDIS_AOF_REWRITE_ITEMS_PER_CMD 64 -#define REDIS_SLOWLOG_LOG_SLOWER_THAN 10000 -#define REDIS_SLOWLOG_MAX_LEN 128 -#define REDIS_MAX_CLIENTS 10000 -#define REDIS_AUTHPASS_MAX_LEN 512 -#define REDIS_DEFAULT_SLAVE_PRIORITY 100 -#define REDIS_REPL_TIMEOUT 60 -#define REDIS_REPL_PING_SLAVE_PERIOD 10 -#define REDIS_RUN_ID_SIZE 40 -#define REDIS_OPS_SEC_SAMPLES 16 - -/* Protocol and I/O related defines */ -#define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */ -#define REDIS_IOBUF_LEN (1024*16) /* Generic I/O buffer size */ -#define REDIS_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */ -#define REDIS_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */ -#define REDIS_MBULK_BIG_ARG (1024*32) - -/* Hash table parameters */ -#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */ - -/* Command flags. Please check the command table defined in the redis.c file - * for more information about the meaning of every flag. */ -#define REDIS_CMD_WRITE 1 /* "w" flag */ -#define REDIS_CMD_READONLY 2 /* "r" flag */ -#define REDIS_CMD_DENYOOM 4 /* "m" flag */ -#define REDIS_CMD_FORCE_REPLICATION 8 /* "f" flag */ -#define REDIS_CMD_ADMIN 16 /* "a" flag */ -#define REDIS_CMD_PUBSUB 32 /* "p" flag */ -#define REDIS_CMD_NOSCRIPT 64 /* "s" flag */ -#define REDIS_CMD_RANDOM 128 /* "R" flag */ -#define REDIS_CMD_SORT_FOR_SCRIPT 256 /* "S" flag */ -#define REDIS_CMD_LOADING 512 /* "l" flag */ -#define REDIS_CMD_STALE 1024 /* "t" flag */ -#define REDIS_CMD_SKIP_MONITOR 2048 /* "M" flag */ - -/* Object types */ -#define REDIS_STRING 0 -#define REDIS_LIST 1 -#define REDIS_SET 2 -#define REDIS_ZSET 3 -#define REDIS_HASH 4 - -/* 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. */ -#define REDIS_ENCODING_RAW 0 /* Raw representation */ -#define REDIS_ENCODING_INT 1 /* Encoded as integer */ -#define REDIS_ENCODING_HT 2 /* Encoded as hash table */ -#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ -#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */ -#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ -#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */ -#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ - -/* 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 - * the first byte to interpreter the length: - * - * 00|000000 => if the two MSB are 00 the len is the 6 bits of this byte - * 01|000000 00000000 => 01, the len is 14 byes, 6 bits + 8 bits of next byte - * 10|000000 [32 bit integer] => if it's 01, a full 32 bit len will follow - * 11|000000 this means: specially encoded object will follow. The six bits - * number specify the kind of object that follows. - * See the REDIS_RDB_ENC_* defines. - * - * Lenghts up to 63 are stored using a single byte, most DB keys, and may - * values, will fit inside. */ -#define REDIS_RDB_6BITLEN 0 -#define REDIS_RDB_14BITLEN 1 -#define REDIS_RDB_32BITLEN 2 -#define REDIS_RDB_ENCVAL 3 -#define REDIS_RDB_LENERR UINT_MAX - -/* When a length of a string object stored on disk has the first two bits - * set, the remaining two bits specify a special encoding for the object - * accordingly to the following defines: */ -#define REDIS_RDB_ENC_INT8 0 /* 8 bit signed integer */ -#define REDIS_RDB_ENC_INT16 1 /* 16 bit signed integer */ -#define REDIS_RDB_ENC_INT32 2 /* 32 bit signed integer */ -#define REDIS_RDB_ENC_LZF 3 /* string compressed with FASTLZ */ - -/* AOF states */ -#define REDIS_AOF_OFF 0 /* AOF is off */ -#define REDIS_AOF_ON 1 /* AOF is on */ -#define REDIS_AOF_WAIT_REWRITE 2 /* AOF waits rewrite to start appending */ - -/* Client flags */ -#define REDIS_SLAVE (1<<0) /* This client is a slave server */ -#define REDIS_MASTER (1<<1) /* This client is a master server */ -#define REDIS_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */ -#define REDIS_MULTI (1<<3) /* This client is in a MULTI context */ -#define REDIS_BLOCKED (1<<4) /* The client is waiting in a blocking operation */ -#define REDIS_DIRTY_CAS (1<<5) /* Watched keys modified. EXEC will fail. */ -#define REDIS_CLOSE_AFTER_REPLY (1<<6) /* Close after writing entire reply. */ -#define REDIS_UNBLOCKED (1<<7) /* This client was unblocked and is stored in - server.unblocked_clients */ -#define REDIS_LUA_CLIENT (1<<8) /* This is a non connected client used by Lua */ -#define REDIS_ASKING (1<<9) /* Client issued the ASKING command */ -#define REDIS_CLOSE_ASAP (1<<10)/* Close this client ASAP */ -#define REDIS_UNIX_SOCKET (1<<11) /* Client connected via Unix domain socket */ -#define REDIS_DIRTY_EXEC (1<<12) /* EXEC will fail for errors while queueing */ - -/* Client request types */ -#define REDIS_REQ_INLINE 1 -#define REDIS_REQ_MULTIBULK 2 - -/* Client classes for client limits, currently used only for - * the max-client-output-buffer limit implementation. */ -#define REDIS_CLIENT_LIMIT_CLASS_NORMAL 0 -#define REDIS_CLIENT_LIMIT_CLASS_SLAVE 1 -#define REDIS_CLIENT_LIMIT_CLASS_PUBSUB 2 -#define REDIS_CLIENT_LIMIT_NUM_CLASSES 3 - -/* Slave replication state - slave side */ -#define REDIS_REPL_NONE 0 /* No active replication */ -#define REDIS_REPL_CONNECT 1 /* Must connect to master */ -#define REDIS_REPL_CONNECTING 2 /* Connecting to master */ -#define REDIS_REPL_RECEIVE_PONG 3 /* Wait for PING reply */ -#define REDIS_REPL_TRANSFER 4 /* Receiving .rdb from master */ -#define REDIS_REPL_CONNECTED 5 /* Connected to master */ - -/* Synchronous read timeout - slave side */ -#define REDIS_REPL_SYNCIO_TIMEOUT 5 - -/* Slave replication state - from the point of view of master - * Note that in SEND_BULK and ONLINE state the slave receives new updates - * in its output queue. In the WAIT_BGSAVE state instead the server is waiting - * to start the next background saving in order to send updates to it. */ -#define REDIS_REPL_WAIT_BGSAVE_START 3 /* master waits bgsave to start feeding it */ -#define REDIS_REPL_WAIT_BGSAVE_END 4 /* master waits bgsave to start bulk DB transmission */ -#define REDIS_REPL_SEND_BULK 5 /* master is sending the bulk DB */ -#define REDIS_REPL_ONLINE 6 /* bulk DB already transmitted, receive updates */ - -/* List related stuff */ -#define REDIS_HEAD 0 -#define REDIS_TAIL 1 - -/* Sort operations */ -#define REDIS_SORT_GET 0 -#define REDIS_SORT_ASC 1 -#define REDIS_SORT_DESC 2 -#define REDIS_SORTKEY_MAX 1024 - -/* Log levels */ -#define REDIS_DEBUG 0 -#define REDIS_VERBOSE 1 -#define REDIS_NOTICE 2 -#define REDIS_WARNING 3 -#define REDIS_LOG_RAW (1<<10) /* Modifier to log without timestamp */ - -/* Anti-warning macro... */ -#define REDIS_NOTUSED(V) ((void) V) - -#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */ -#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */ - -/* Append only defines */ -#define AOF_FSYNC_NO 0 -#define AOF_FSYNC_ALWAYS 1 -#define AOF_FSYNC_EVERYSEC 2 - -/* Zip structure related defaults */ -#define REDIS_HASH_MAX_ZIPLIST_ENTRIES 512 -#define REDIS_HASH_MAX_ZIPLIST_VALUE 64 -#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512 -#define REDIS_LIST_MAX_ZIPLIST_VALUE 64 -#define REDIS_SET_MAX_INTSET_ENTRIES 512 -#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128 -#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64 - -/* Sets operations codes */ -#define REDIS_OP_UNION 0 -#define REDIS_OP_DIFF 1 -#define REDIS_OP_INTER 2 - -/* Redis maxmemory strategies */ -#define REDIS_MAXMEMORY_VOLATILE_LRU 0 -#define REDIS_MAXMEMORY_VOLATILE_TTL 1 -#define REDIS_MAXMEMORY_VOLATILE_RANDOM 2 -#define REDIS_MAXMEMORY_ALLKEYS_LRU 3 -#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4 -#define REDIS_MAXMEMORY_NO_EVICTION 5 - -/* Scripting */ -#define REDIS_LUA_TIME_LIMIT 5000 /* milliseconds */ - -/* Units */ -#define UNIT_SECONDS 0 -#define UNIT_MILLISECONDS 1 - -/* SHUTDOWN flags */ -#define REDIS_SHUTDOWN_SAVE 1 /* Force SAVE on SHUTDOWN even if no save - points are configured. */ -#define REDIS_SHUTDOWN_NOSAVE 2 /* Don't SAVE on SHUTDOWN. */ - -/* Command call flags, see call() function */ -#define REDIS_CALL_NONE 0 -#define REDIS_CALL_SLOWLOG 1 -#define REDIS_CALL_STATS 2 -#define REDIS_CALL_PROPAGATE 4 -#define REDIS_CALL_FULL (REDIS_CALL_SLOWLOG | REDIS_CALL_STATS | REDIS_CALL_PROPAGATE) - -/* Command propagation flags, see propagate() function */ -#define REDIS_PROPAGATE_NONE 0 -#define REDIS_PROPAGATE_AOF 1 -#define REDIS_PROPAGATE_REPL 2 - -/* Using the following macro you can run code inside serverCron() with the - * specified period, specified in milliseconds. - * The actual resolution depends on REDIS_HZ. */ -#define run_with_period(_ms_) if (!(server.cronloops%((_ms_)/(1000/REDIS_HZ)))) - -/* We can print the stacktrace, so our assert is defined this way: */ -#define redisAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_redisAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1))) -#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1))) -#define redisPanic(_e) _redisPanic(#_e,__FILE__,__LINE__),_exit(1) - -/*----------------------------------------------------------------------------- - * Data types - *----------------------------------------------------------------------------*/ - -/* A redis object, that is a type able to hold a string / list / set */ - -/* The actual Redis Object */ -#define REDIS_LRU_CLOCK_MAX ((1<<21)-1) /* Max value of obj->lru */ -#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */ -typedef struct redisObject { - unsigned type:4; - unsigned notused:2; /* Not used */ - unsigned encoding:4; - unsigned lru:22; /* lru time (relative to server.lruclock) */ - int refcount; - void *ptr; -} robj; - -/* Macro used to initalize a Redis object allocated on the stack. - * Note that this macro is taken near the structure definition to make sure - * we'll update it when the structure is changed, to avoid bugs like - * bug #85 introduced exactly in this way. */ -#define initStaticStringObject(_var,_ptr) do { \ - _var.refcount = 1; \ - _var.type = REDIS_STRING; \ - _var.encoding = REDIS_ENCODING_RAW; \ - _var.ptr = _ptr; \ -} while(0); - -typedef struct redisDb { - dict *dict; /* The keyspace for this DB */ - dict *expires; /* Timeout of keys with a timeout set */ - dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */ - dict *ready_keys; /* Blocked keys that received a PUSH */ - dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ - int id; -} redisDb; - -/* Client MULTI/EXEC state */ -typedef struct multiCmd { - robj **argv; - int argc; - struct redisCommand *cmd; -} multiCmd; - -typedef struct multiState { - multiCmd *commands; /* Array of MULTI commands */ - int count; /* Total number of MULTI commands */ -} multiState; - -typedef struct blockingState { - dict *keys; /* The keys we are waiting to terminate a blocking - * operation such as BLPOP. Otherwise NULL. */ - time_t timeout; /* Blocking operation timeout. If UNIX current time - * is >= timeout then the operation timed out. */ - robj *target; /* The key that should receive the element, - * for BRPOPLPUSH. */ -} blockingState; - -/* The following structure represents a node in the server.ready_keys list, - * where we accumulate all the keys that had clients blocked with a blocking - * operation such as B[LR]POP, but received new data in the context of the - * last executed command. - * - * After the execution of every command or script, we run this list to check - * if as a result we should serve data to clients blocked, unblocking them. - * Note that server.ready_keys will not have duplicates as there dictionary - * also called ready_keys in every structure representing a Redis database, - * where we make sure to remember if a given key was already added in the - * server.ready_keys list. */ -typedef struct readyList { - redisDb *db; - robj *key; -} readyList; - -/* With multiplexing we need to take per-clinet state. - * Clients are taken in a liked list. */ -typedef struct redisClient { - int fd; - redisDb *db; - int dictid; - sds querybuf; - size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */ - int argc; - robj **argv; - struct redisCommand *cmd, *lastcmd; - int reqtype; - int multibulklen; /* number of multi bulk arguments left to read */ - long bulklen; /* length of bulk argument in multi bulk request */ - list *reply; - unsigned long reply_bytes; /* Tot bytes of objects in reply list */ - int sentlen; - time_t ctime; /* Client creation time */ - time_t lastinteraction; /* time of the last interaction, used for timeout */ - time_t obuf_soft_limit_reached_time; - int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */ - int slaveseldb; /* slave selected db, if this client is a slave */ - int authenticated; /* when requirepass is non-NULL */ - int replstate; /* replication state if this is a slave */ - int repldbfd; /* replication DB file descriptor */ - long repldboff; /* replication DB file offset */ - off_t repldbsize; /* replication DB file size */ - int slave_listening_port; /* As configured with: SLAVECONF listening-port */ - multiState mstate; /* MULTI/EXEC state */ - blockingState bpop; /* blocking state */ - list *io_keys; /* Keys this client is waiting to be loaded from the - * swap file in order to continue. */ - 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) */ - - /* Response buffer */ - int bufpos; - char buf[REDIS_REPLY_CHUNK_BYTES]; -} redisClient; - -struct saveparam { - time_t seconds; - int changes; -}; - -struct sharedObjectsStruct { - robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space, - *colon, *nullbulk, *nullmultibulk, *queued, - *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, - *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, - *masterdownerr, *roslaveerr, *execaborterr, - *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk, - *unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop, - *lpush, - *select[REDIS_SHARED_SELECT_CMDS], - *integers[REDIS_SHARED_INTEGERS], - *mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*\r\n" */ - *bulkhdr[REDIS_SHARED_BULKHDR_LEN]; /* "$\r\n" */ -}; - -/* ZSETs use a specialized version of Skiplists */ -typedef struct zskiplistNode { - robj *obj; - double score; - struct zskiplistNode *backward; - struct zskiplistLevel { - struct zskiplistNode *forward; - unsigned int span; - } level[]; -} zskiplistNode; - -typedef struct zskiplist { - struct zskiplistNode *header, *tail; - unsigned long length; - int level; -} zskiplist; - -typedef struct zset { - dict *dict; - zskiplist *zsl; -} zset; - -typedef struct clientBufferLimitsConfig { - unsigned long long hard_limit_bytes; - unsigned long long soft_limit_bytes; - time_t soft_limit_seconds; -} clientBufferLimitsConfig; - -/* The redisOp structure defines a Redis Operation, that is an instance of - * a command with an argument vector, database ID, propagation target - * (REDIS_PROPAGATE_*), and command pointer. - * - * Currently only used to additionally propagate more commands to AOF/Replication - * after the propagation of the executed command. */ -typedef struct redisOp { - robj **argv; - int argc, dbid, target; - struct redisCommand *cmd; -} redisOp; - -/* Defines an array of Redis operations. There is an API to add to this - * structure in a easy way. - * - * redisOpArrayInit(); - * redisOpArrayAppend(); - * redisOpArrayFree(); - */ -typedef struct redisOpArray { - redisOp *ops; - int numops; -} redisOpArray; - -/*----------------------------------------------------------------------------- - * Global server state - *----------------------------------------------------------------------------*/ - -struct redisServer { - leveldb_t *ds_db; - leveldb_comparator_t *ds_cmp; - leveldb_cache_t *ds_cache; - leveldb_options_t *ds_options; - leveldb_filterpolicy_t *policy; - - uint16_t ds_lru_cache; - uint16_t ds_create_if_missing; - uint16_t ds_error_if_exists; - uint16_t ds_paranoid_checks; - uint32_t ds_block_cache_size; - uint32_t ds_write_buffer_size; - uint32_t ds_block_size; - uint16_t ds_max_open_files; - uint16_t ds_block_restart_interval; - char *ds_path; - - /* General */ - redisDb *db; - dict *commands; /* Command table hash table */ - aeEventLoop *el; - unsigned lruclock:22; /* Clock incrementing every minute, for LRU */ - unsigned lruclock_padding:10; - int shutdown_asap; /* SHUTDOWN needed ASAP */ - int activerehashing; /* Incremental rehash in serverCron() */ - char *requirepass; /* Pass for AUTH command, or NULL */ - char *pidfile; /* PID file path */ - int arch_bits; /* 32 or 64 depending on sizeof(long) */ - int cronloops; /* Number of times the cron function run */ - char runid[REDIS_RUN_ID_SIZE+1]; /* ID always different at every exec. */ - int sentinel_mode; /* True if this instance is a Sentinel. */ - /* Networking */ - int port; /* TCP listening port */ - char *bindaddr; /* Bind address or NULL */ - char *unixsocket; /* UNIX socket path */ - mode_t unixsocketperm; /* UNIX socket permission */ - int ipfd; /* TCP socket file descriptor */ - int sofd; /* Unix socket file descriptor */ - list *clients; /* List of active clients */ - list *clients_to_close; /* Clients to close asynchronously */ - list *slaves, *monitors; /* List of slaves and MONITORs */ - redisClient *current_client; /* Current client, only used on crash report */ - char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */ - /* RDB / AOF loading information */ - int loading; /* We are loading data from disk if true */ - off_t loading_total_bytes; - off_t loading_loaded_bytes; - time_t loading_start_time; - /* Fast pointers to often looked up command */ - struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand, - *rpopCommand; - /* Fields used only for stats */ - time_t stat_starttime; /* Server start time */ - long long stat_numcommands; /* Number of processed commands */ - long long stat_numconnections; /* Number of connections received */ - long long stat_expiredkeys; /* Number of expired keys */ - long long stat_evictedkeys; /* Number of evicted keys (maxmemory) */ - long long stat_keyspace_hits; /* Number of successful lookups of keys */ - long long stat_keyspace_misses; /* Number of failed lookups of keys */ - size_t stat_peak_memory; /* Max used memory record */ - long long stat_fork_time; /* Time needed to perform latets fork() */ - long long stat_rejected_conn; /* Clients rejected because of maxclients */ - list *slowlog; /* SLOWLOG list of commands */ - long long slowlog_entry_id; /* SLOWLOG current entry ID */ - long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */ - unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */ - /* The following two are used to track instantaneous "load" in terms - * of operations per second. */ - long long ops_sec_last_sample_time; /* Timestamp of last sample (in ms) */ - long long ops_sec_last_sample_ops; /* numcommands in last sample */ - long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES]; - int ops_sec_idx; - /* Configuration */ - int verbosity; /* Loglevel in redis.conf */ - int maxidletime; /* Client timeout in seconds */ - size_t client_max_querybuf_len; /* Limit for client query buffer length */ - int dbnum; /* Total number of configured DBs */ - int daemonize; /* True if running as a daemon */ - clientBufferLimitsConfig client_obuf_limits[REDIS_CLIENT_LIMIT_NUM_CLASSES]; - /* AOF persistence */ - int aof_state; /* REDIS_AOF_(ON|OFF|WAIT_REWRITE) */ - int aof_fsync; /* Kind of fsync() policy */ - char *aof_filename; /* Name of the AOF file */ - int aof_no_fsync_on_rewrite; /* Don't fsync if a rewrite is in prog. */ - int aof_rewrite_perc; /* Rewrite AOF if % growth is > M and... */ - off_t aof_rewrite_min_size; /* the AOF file is at least N bytes. */ - off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */ - off_t aof_current_size; /* AOF current size. */ - int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */ - pid_t aof_child_pid; /* PID if rewriting process */ - list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */ - sds aof_buf; /* AOF buffer, written before entering the event loop */ - int aof_fd; /* File descriptor of currently selected AOF file */ - int aof_selected_db; /* Currently selected DB in AOF */ - time_t aof_flush_postponed_start; /* UNIX time of postponed AOF flush */ - time_t aof_last_fsync; /* UNIX time of last fsync() */ - time_t aof_rewrite_time_last; /* Time used by last AOF rewrite run. */ - time_t aof_rewrite_time_start; /* Current AOF rewrite start time. */ - int aof_lastbgrewrite_status; /* REDIS_OK or REDIS_ERR */ - unsigned long aof_delayed_fsync; /* delayed AOF fsync() counter */ - /* RDB persistence */ - long long dirty; /* Changes to DB from the last save */ - long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */ - pid_t rdb_child_pid; /* PID of RDB saving child */ - struct saveparam *saveparams; /* Save points array for RDB */ - int saveparamslen; /* Number of saving points */ - char *rdb_filename; /* Name of RDB file */ - int rdb_compression; /* Use compression in RDB? */ - int rdb_checksum; /* Use RDB checksum? */ - time_t lastsave; /* Unix time of last save succeeede */ - time_t rdb_save_time_last; /* Time used by last RDB save run. */ - time_t rdb_save_time_start; /* Current RDB save start time. */ - int lastbgsave_status; /* REDIS_OK or REDIS_ERR */ - int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */ - /* Propagation of commands in AOF / replication */ - redisOpArray also_propagate; /* Additional command to propagate. */ - /* Logging */ - char *logfile; /* Path of log file */ - int syslog_enabled; /* Is syslog enabled? */ - char *syslog_ident; /* Syslog ident */ - int syslog_facility; /* Syslog facility */ - /* Slave specific fields */ - char *masterauth; /* AUTH with this password with master */ - char *masterhost; /* Hostname of master */ - int masterport; /* Port of master */ - int repl_ping_slave_period; /* Master pings the slave every N seconds */ - int repl_timeout; /* Timeout after N seconds of master idle */ - redisClient *master; /* Client that is master for this slave */ - int repl_syncio_timeout; /* Timeout for synchronous I/O calls */ - int repl_state; /* Replication status if the instance is a slave */ - off_t repl_transfer_size; /* Size of RDB to read from master during sync. */ - off_t repl_transfer_read; /* Amount of RDB read from master during sync. */ - off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */ - int repl_transfer_s; /* Slave -> Master SYNC socket */ - int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */ - char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */ - time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */ - int repl_serve_stale_data; /* Serve stale data when link is down? */ - int repl_slave_ro; /* Slave is read only? */ - time_t repl_down_since; /* Unix time at which link with master went down */ - int slave_priority; /* Reported in INFO and used by Sentinel. */ - /* Limits */ - unsigned int maxclients; /* Max number of simultaneous clients */ - unsigned long long maxmemory; /* Max number of memory bytes to use */ - int maxmemory_policy; /* Policy for key evition */ - int maxmemory_samples; /* Pricision of random sampling */ - /* Blocked clients */ - unsigned int bpop_blocked_clients; /* Number of clients blocked by lists */ - list *unblocked_clients; /* list of clients to unblock before next loop */ - list *ready_keys; /* List of readyList structures for BLPOP & co */ - /* Sort parameters - qsort_r() is only available under BSD so we - * have to take this state global, in order to pass it to sortCompare() */ - int sort_desc; - int sort_alpha; - int sort_bypattern; - /* Zip structure config, see redis.conf for more information */ - size_t hash_max_ziplist_entries; - size_t hash_max_ziplist_value; - size_t list_max_ziplist_entries; - size_t list_max_ziplist_value; - size_t set_max_intset_entries; - size_t zset_max_ziplist_entries; - size_t zset_max_ziplist_value; - time_t unixtime; /* Unix time sampled every second. */ - /* Pubsub */ - dict *pubsub_channels; /* Map channels to list of subscribed clients */ - list *pubsub_patterns; /* A list of pubsub_patterns */ - /* 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 */ - redisClient *lua_caller; /* The client running EVAL right now, or NULL */ - dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ - long long lua_time_limit; /* Script timeout in seconds */ - long long lua_time_start; /* Start time of script */ - int lua_write_dirty; /* True if a write command was called during the - execution of the current script. */ - int lua_random_dirty; /* True if a random command was called during the - execution of the current script. */ - int lua_timedout; /* True if we reached the time limit for script - execution. */ - int lua_kill; /* Kill the script if true. */ - /* Assert & bug reportign */ - char *assert_failed; - char *assert_file; - int assert_line; - int bug_report_start; /* True if bug report header was already logged. */ - int watchdog_period; /* Software watchdog period in ms. 0 = off */ -}; - -typedef struct pubsubPattern { - redisClient *client; - robj *pattern; -} pubsubPattern; - -typedef void redisCommandProc(redisClient *c); -typedef int *redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags); -struct redisCommand { - char *name; - redisCommandProc *proc; - int arity; - char *sflags; /* Flags as string represenation, one char per flag. */ - int flags; /* The actual flags, obtained from the 'sflags' field. */ - /* Use a function to determine keys arguments in a command line. */ - redisGetKeysProc *getkeys_proc; - /* What keys should be loaded in background when calling this command? */ - int firstkey; /* The first argument that's a key (0 = no keys) */ - int lastkey; /* THe last argument that's a key */ - int keystep; /* The step between first and last key */ - long long microseconds, calls; -}; - -struct redisFunctionSym { - char *name; - unsigned long pointer; -}; - -typedef struct _redisSortObject { - robj *obj; - union { - double score; - robj *cmpobj; - } u; -} redisSortObject; - -typedef struct _redisSortOperation { - int type; - robj *pattern; -} redisSortOperation; - -/* Structure to hold list iteration abstraction. */ -typedef struct { - robj *subject; - unsigned char encoding; - unsigned char direction; /* Iteration direction */ - unsigned char *zi; - listNode *ln; -} listTypeIterator; - -/* Structure for an entry while iterating over a list. */ -typedef struct { - listTypeIterator *li; - unsigned char *zi; /* Entry in ziplist */ - listNode *ln; /* Entry in linked list */ -} listTypeEntry; - -/* Structure to hold set iteration abstraction. */ -typedef struct { - robj *subject; - int encoding; - int ii; /* intset iterator */ - dictIterator *di; -} setTypeIterator; - -/* Structure to hold hash iteration abstration. Note that iteration over - * hashes involves both fields and values. Because it is possible that - * not both are required, store pointers in the iterator to avoid - * unnecessary memory allocation for fields/values. */ -typedef struct { - robj *subject; - int encoding; - - unsigned char *fptr, *vptr; - - dictIterator *di; - dictEntry *de; -} hashTypeIterator; - -#define REDIS_HASH_KEY 1 -#define REDIS_HASH_VALUE 2 - -/*----------------------------------------------------------------------------- - * Extern declarations - *----------------------------------------------------------------------------*/ - -extern struct redisServer server; -extern struct sharedObjectsStruct shared; -extern dictType setDictType; -extern dictType zsetDictType; -extern dictType dbDictType; -extern dictType shaScriptObjectDictType; -extern double R_Zero, R_PosInf, R_NegInf, R_Nan; -extern dictType hashDictType; - -/*----------------------------------------------------------------------------- - * Functions prototypes - *----------------------------------------------------------------------------*/ - -/* Utils */ -long long ustime(void); -long long mstime(void); -void getRandomHexChars(char *p, unsigned int len); -uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); -void exitFromChild(int retcode); - -/* networking.c -- Networking and Client related operations */ -redisClient *createClient(int fd); -void closeTimedoutClients(void); -void freeClient(redisClient *c); -void resetClient(redisClient *c); -void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask); -void addReply(redisClient *c, robj *obj); -void *addDeferredMultiBulkLength(redisClient *c); -void setDeferredMultiBulkLength(redisClient *c, void *node, long length); -void addReplySds(redisClient *c, sds s); -void processInputBuffer(redisClient *c); -void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask); -void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask); -void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask); -void addReplyBulk(redisClient *c, robj *obj); -void addReplyBulkCString(redisClient *c, char *s); -void addReplyBulkCBuffer(redisClient *c, void *p, size_t len); -void addReplyBulkLongLong(redisClient *c, long long ll); -void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); -void addReply(redisClient *c, robj *obj); -void addReplySds(redisClient *c, sds s); -void addReplyError(redisClient *c, char *err); -void addReplyStatus(redisClient *c, char *status); -void addReplyDouble(redisClient *c, double d); -void addReplyLongLong(redisClient *c, long long ll); -void addReplyMultiBulkLen(redisClient *c, long length); -void copyClientOutputBuffer(redisClient *dst, redisClient *src); -void *dupClientReplyValue(void *o); -void getClientsMaxBuffers(unsigned long *longest_output_list, - unsigned long *biggest_input_buffer); -sds getClientInfoString(redisClient *client); -sds getAllClientsInfoString(void); -void rewriteClientCommandVector(redisClient *c, int argc, ...); -void rewriteClientCommandArgument(redisClient *c, int i, robj *newval); -unsigned long getClientOutputBufferMemoryUsage(redisClient *c); -void freeClientsInAsyncFreeQueue(void); -void asyncCloseClientOnOutputBufferLimitReached(redisClient *c); -int getClientLimitClassByName(char *name); -char *getClientLimitClassName(int class); -void flushSlavesOutputBuffers(void); -void disconnectSlaves(void); - -#ifdef __GNUC__ -void addReplyErrorFormat(redisClient *c, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -void addReplyStatusFormat(redisClient *c, const char *fmt, ...) - __attribute__((format(printf, 2, 3))); -#else -void addReplyErrorFormat(redisClient *c, const char *fmt, ...); -void addReplyStatusFormat(redisClient *c, const char *fmt, ...); -#endif - -/* List data type */ -void listTypeTryConversion(robj *subject, robj *value); -void listTypePush(robj *subject, robj *value, int where); -robj *listTypePop(robj *subject, int where); -unsigned long listTypeLength(robj *subject); -listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction); -void listTypeReleaseIterator(listTypeIterator *li); -int listTypeNext(listTypeIterator *li, listTypeEntry *entry); -robj *listTypeGet(listTypeEntry *entry); -void listTypeInsert(listTypeEntry *entry, robj *value, int where); -int listTypeEqual(listTypeEntry *entry, robj *o); -void listTypeDelete(listTypeEntry *entry); -void listTypeConvert(robj *subject, int enc); -void unblockClientWaitingData(redisClient *c); -void handleClientsBlockedOnLists(void); -void popGenericCommand(redisClient *c, int where); - -/* MULTI/EXEC/WATCH... */ -void unwatchAllKeys(redisClient *c); -void initClientMultiState(redisClient *c); -void freeClientMultiState(redisClient *c); -void queueMultiCommand(redisClient *c); -void touchWatchedKey(redisDb *db, robj *key); -void touchWatchedKeysOnFlush(int dbid); -void discardTransaction(redisClient *c); -void flagTransaction(redisClient *c); - -/* Redis object implementation */ -void decrRefCount(void *o); -void incrRefCount(robj *o); -robj *resetRefCount(robj *obj); -void freeStringObject(robj *o); -void freeListObject(robj *o); -void freeSetObject(robj *o); -void freeZsetObject(robj *o); -void freeHashObject(robj *o); -robj *createObject(int type, void *ptr); -robj *createStringObject(char *ptr, size_t len); -robj *dupStringObject(robj *o); -int isObjectRepresentableAsLongLong(robj *o, long long *llongval); -robj *tryObjectEncoding(robj *o); -robj *getDecodedObject(robj *o); -size_t stringObjectLen(robj *o); -robj *createStringObjectFromLongLong(long long value); -robj *createStringObjectFromLongDouble(long double value); -robj *createListObject(void); -robj *createZiplistObject(void); -robj *createSetObject(void); -robj *createIntsetObject(void); -robj *createHashObject(void); -robj *createZsetObject(void); -robj *createZsetZiplistObject(void); -int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg); -int checkType(redisClient *c, robj *o, int type); -int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg); -int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg); -int getLongLongFromObject(robj *o, long long *target); -int getLongDoubleFromObject(robj *o, long double *target); -int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg); -char *strEncoding(int encoding); -int compareStringObjects(robj *a, robj *b); -int equalStringObjects(robj *a, robj *b); -unsigned long estimateObjectIdleTime(robj *o); - -/* Synchronous I/O with timeout */ -ssize_t syncWrite(int fd, char *ptr, ssize_t size, long long timeout); -ssize_t syncRead(int fd, char *ptr, ssize_t size, long long timeout); -ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout); - -/* Replication */ -void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc); -void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **argv, int argc); -void updateSlavesWaitingBgsave(int bgsaveerr); -void replicationCron(void); - -/* Generic persistence functions */ -void startLoading(FILE *fp); -void loadingProgress(off_t pos); -void stopLoading(void); - -/* RDB persistence */ -#include "rdb.h" - -/* AOF persistence */ -void flushAppendOnlyFile(int force); -void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc); -void aofRemoveTempFile(pid_t childpid); -int rewriteAppendOnlyFileBackground(void); -int loadAppendOnlyFile(char *filename); -void stopAppendOnly(void); -int startAppendOnly(void); -void backgroundRewriteDoneHandler(int exitcode, int bysignal); -void aofRewriteBufferReset(void); -unsigned long aofRewriteBufferSize(void); - -/* Sorted sets data type */ - -/* Struct to hold a inclusive/exclusive range spec. */ -typedef struct { - double min, max; - int minex, maxex; /* are min or max exclusive? */ -} zrangespec; - -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); -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); -unsigned int zsetLength(robj *zobj); -void zsetConvert(robj *zobj, int encoding); - -/* Core functions */ -int freeMemoryIfNeeded(void); -int processCommand(redisClient *c); -void setupSignalHandlers(void); -struct redisCommand *lookupCommand(sds name); -struct redisCommand *lookupCommandByCString(char *s); -void call(redisClient *c, int flags); -void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags); -void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target); -int prepareForShutdown(); -void redisLog(int level, const char *fmt, ...); -void redisLogRaw(int level, const char *msg); -void redisLogFromHandler(int level, const char *msg); -void usage(); -void updateDictResizePolicy(void); -int htNeedsResize(dict *dict); -void oom(const char *msg); -void populateCommandTable(void); -void resetCommandTableStats(void); - -/* Set data type */ -robj *setTypeCreate(robj *value); -int setTypeAdd(robj *subject, robj *value); -int setTypeRemove(robj *subject, robj *value); -int setTypeIsMember(robj *subject, robj *value); -setTypeIterator *setTypeInitIterator(robj *subject); -void setTypeReleaseIterator(setTypeIterator *si); -int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele); -robj *setTypeNextObject(setTypeIterator *si); -int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele); -unsigned long setTypeSize(robj *subject); -void setTypeConvert(robj *subject, int enc); - -/* Hash data type */ -void hashTypeConvert(robj *o, int enc); -void hashTypeTryConversion(robj *subject, robj **argv, int start, int end); -void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2); -robj *hashTypeGetObject(robj *o, robj *key); -int hashTypeExists(robj *o, robj *key); -int hashTypeSet(robj *o, robj *key, robj *value); -int hashTypeDelete(robj *o, robj *key); -unsigned long hashTypeLength(robj *o); -hashTypeIterator *hashTypeInitIterator(robj *subject); -void hashTypeReleaseIterator(hashTypeIterator *hi); -int hashTypeNext(hashTypeIterator *hi); -void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, - unsigned char **vstr, - unsigned int *vlen, - long long *vll); -void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst); -robj *hashTypeCurrentObject(hashTypeIterator *hi, int what); -robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key); - -/* Pub / Sub */ -int pubsubUnsubscribeAllChannels(redisClient *c, int notify); -int pubsubUnsubscribeAllPatterns(redisClient *c, int notify); -void freePubsubPattern(void *p); -int listMatchPubsubPattern(void *a, void *b); -int pubsubPublishMessage(robj *channel, robj *message); - -/* Configuration */ -void loadServerConfig(char *filename, char *options); -void appendServerSaveParams(time_t seconds, int changes); -void resetServerSaveParams(); - -/* db.c -- Keyspace access API */ -int removeExpire(redisDb *db, robj *key); -void propagateExpire(redisDb *db, robj *key); -int expireIfNeeded(redisDb *db, robj *key); -long long getExpire(redisDb *db, robj *key); -void setExpire(redisDb *db, robj *key, long long when); -robj *lookupKey(redisDb *db, robj *key); -robj *lookupKeyRead(redisDb *db, robj *key); -robj *lookupKeyWrite(redisDb *db, robj *key); -robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply); -robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply); -void dbAdd(redisDb *db, robj *key, robj *val); -void dbOverwrite(redisDb *db, robj *key, robj *val); -void setKey(redisDb *db, robj *key, robj *val); -int dbExists(redisDb *db, robj *key); -robj *dbRandomKey(redisDb *db); -int dbDelete(redisDb *db, robj *key); -long long emptyDb(); -int selectDb(redisClient *c, int id); -void signalModifiedKey(redisDb *db, robj *key); -void signalFlushedDb(int dbid); -unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count); - -/* API to get key arguments from commands */ -#define REDIS_GETKEYS_ALL 0 -#define REDIS_GETKEYS_PRELOAD 1 -int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags); -void getKeysFreeResult(int *result); -int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags); -int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags); -int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags); - -/* Sentinel */ -void initSentinelConfig(void); -void initSentinel(void); -void sentinelTimer(void); -char *sentinelHandleConfiguration(char **argv, int argc); - -/* Scripting */ -void scriptingInit(void); - -/* Git SHA1 */ -char *redisGitSHA1(void); -char *redisGitDirty(void); - -/* Commands prototypes */ -void authCommand(redisClient *c); -void pingCommand(redisClient *c); -void echoCommand(redisClient *c); -void setCommand(redisClient *c); -void setnxCommand(redisClient *c); -void setexCommand(redisClient *c); -void psetexCommand(redisClient *c); -void getCommand(redisClient *c); -void delCommand(redisClient *c); -void existsCommand(redisClient *c); -void setbitCommand(redisClient *c); -void getbitCommand(redisClient *c); -void setrangeCommand(redisClient *c); -void getrangeCommand(redisClient *c); -void incrCommand(redisClient *c); -void decrCommand(redisClient *c); -void incrbyCommand(redisClient *c); -void decrbyCommand(redisClient *c); -void incrbyfloatCommand(redisClient *c); -void selectCommand(redisClient *c); -void randomkeyCommand(redisClient *c); -void keysCommand(redisClient *c); -void dbsizeCommand(redisClient *c); -void lastsaveCommand(redisClient *c); -void saveCommand(redisClient *c); -void bgsaveCommand(redisClient *c); -void bgrewriteaofCommand(redisClient *c); -void shutdownCommand(redisClient *c); -void moveCommand(redisClient *c); -void renameCommand(redisClient *c); -void renamenxCommand(redisClient *c); -void lpushCommand(redisClient *c); -void rpushCommand(redisClient *c); -void lpushxCommand(redisClient *c); -void rpushxCommand(redisClient *c); -void linsertCommand(redisClient *c); -void lpopCommand(redisClient *c); -void rpopCommand(redisClient *c); -void llenCommand(redisClient *c); -void lindexCommand(redisClient *c); -void lrangeCommand(redisClient *c); -void ltrimCommand(redisClient *c); -void typeCommand(redisClient *c); -void lsetCommand(redisClient *c); -void saddCommand(redisClient *c); -void sremCommand(redisClient *c); -void smoveCommand(redisClient *c); -void sismemberCommand(redisClient *c); -void scardCommand(redisClient *c); -void spopCommand(redisClient *c); -void srandmemberCommand(redisClient *c); -void sinterCommand(redisClient *c); -void sinterstoreCommand(redisClient *c); -void sunionCommand(redisClient *c); -void sunionstoreCommand(redisClient *c); -void sdiffCommand(redisClient *c); -void sdiffstoreCommand(redisClient *c); -void syncCommand(redisClient *c); -void flushdbCommand(redisClient *c); -void flushallCommand(redisClient *c); -void sortCommand(redisClient *c); -void lremCommand(redisClient *c); -void rpoplpushCommand(redisClient *c); -void infoCommand(redisClient *c); -void mgetCommand(redisClient *c); -void monitorCommand(redisClient *c); -void expireCommand(redisClient *c); -void expireatCommand(redisClient *c); -void pexpireCommand(redisClient *c); -void pexpireatCommand(redisClient *c); -void getsetCommand(redisClient *c); -void ttlCommand(redisClient *c); -void pttlCommand(redisClient *c); -void persistCommand(redisClient *c); -void slaveofCommand(redisClient *c); -void debugCommand(redisClient *c); -void msetCommand(redisClient *c); -void msetnxCommand(redisClient *c); -void zaddCommand(redisClient *c); -void zincrbyCommand(redisClient *c); -void zrangeCommand(redisClient *c); -void zrangebyscoreCommand(redisClient *c); -void zrevrangebyscoreCommand(redisClient *c); -void zcountCommand(redisClient *c); -void zrevrangeCommand(redisClient *c); -void zcardCommand(redisClient *c); -void zremCommand(redisClient *c); -void zscoreCommand(redisClient *c); -void zremrangebyscoreCommand(redisClient *c); -void multiCommand(redisClient *c); -void execCommand(redisClient *c); -void discardCommand(redisClient *c); -void blpopCommand(redisClient *c); -void brpopCommand(redisClient *c); -void brpoplpushCommand(redisClient *c); -void appendCommand(redisClient *c); -void strlenCommand(redisClient *c); -void zrankCommand(redisClient *c); -void zrevrankCommand(redisClient *c); -void hsetCommand(redisClient *c); -void hsetnxCommand(redisClient *c); -void hgetCommand(redisClient *c); -void hmsetCommand(redisClient *c); -void hmgetCommand(redisClient *c); -void hdelCommand(redisClient *c); -void hlenCommand(redisClient *c); -void zremrangebyrankCommand(redisClient *c); -void zunionstoreCommand(redisClient *c); -void zinterstoreCommand(redisClient *c); -void hkeysCommand(redisClient *c); -void hvalsCommand(redisClient *c); -void hgetallCommand(redisClient *c); -void hexistsCommand(redisClient *c); -void configCommand(redisClient *c); -void hincrbyCommand(redisClient *c); -void hincrbyfloatCommand(redisClient *c); -void subscribeCommand(redisClient *c); -void unsubscribeCommand(redisClient *c); -void psubscribeCommand(redisClient *c); -void punsubscribeCommand(redisClient *c); -void publishCommand(redisClient *c); -void watchCommand(redisClient *c); -void unwatchCommand(redisClient *c); -void restoreCommand(redisClient *c); -void migrateCommand(redisClient *c); -void dumpCommand(redisClient *c); -void objectCommand(redisClient *c); -void clientCommand(redisClient *c); -void evalCommand(redisClient *c); -void evalShaCommand(redisClient *c); -void scriptCommand(redisClient *c); -void timeCommand(redisClient *c); -void bitopCommand(redisClient *c); -void bitcountCommand(redisClient *c); -void replconfCommand(redisClient *c); - -#if defined(__GNUC__) -void *calloc(size_t count, size_t size) __attribute__ ((deprecated)); -void free(void *ptr) __attribute__ ((deprecated)); -void *malloc(size_t size) __attribute__ ((deprecated)); -void *realloc(void *ptr, size_t size) __attribute__ ((deprecated)); -#endif - -/* Debugging stuff */ -void _redisAssertWithInfo(redisClient *c, robj *o, char *estr, char *file, int line); -void _redisAssert(char *estr, char *file, int line); -void _redisPanic(char *msg, char *file, int line); -void bugReportStart(void); -void redisLogObjectDebugInfo(robj *o); -void sigsegvHandler(int sig, siginfo_t *info, void *secret); -sds genRedisInfoString(char *section); -void enableWatchdog(int period); -void disableWatchdog(void); -void watchdogScheduleSignal(int period); -void redisLogHexDump(int level, char *descr, void *value, size_t len); - -void ds_init(); -void ds_close(); -void ds_get(redisClient *c); -void ds_set(redisClient *c); -void ds_mset(redisClient *c); -void ds_mget(redisClient *c); - -void ds_append(redisClient *c); -void ds_incrby(redisClient *c); - -void ds_hdel(redisClient *c); -void ds_hget(redisClient *c); -void ds_hset(redisClient *c); -void ds_hmget(redisClient *c); -void ds_hmset(redisClient *c); -void ds_hincrby(redisClient *c); -void ds_hgetall(redisClient *c); - -void ds_delete(redisClient *c); -void rl_delete(redisClient *c); -void rl_get(redisClient *c); -void rl_set(redisClient *c); - - -#define redisDebug(fmt, ...) \ - printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__) -#define redisDebugMark() \ - printf("-- MARK %s:%d --\n", __FILE__, __LINE__) - -#endif +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __REDIS_H +#define __REDIS_H + +#include "fmacros.h" +#include "config.h" + +#if defined(__sun) +#include "solarisfixes.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ae.h" /* Event driven programming library */ +#include "sds.h" /* Dynamic safe strings */ +#include "dict.h" /* Hash tables */ +#include "adlist.h" /* Linked lists */ +#include "zmalloc.h" /* total memory usage aware version of malloc/free */ +#include "anet.h" /* Networking the easy way */ +#include "ziplist.h" /* Compact list data structure */ +#include "intset.h" /* Compact integer set structure */ +#include "version.h" /* Version macro */ +#include "util.h" /* Misc functions useful in many places */ + +/* Error codes */ +#define REDIS_OK 0 +#define REDIS_ERR -1 + +/* Static server configuration */ +#define REDIS_HZ 100 /* Time interrupt calls/sec. */ +#define REDIS_SERVERPORT 6379 /* TCP port */ +#define REDIS_MAXIDLETIME 0 /* default client timeout: infinite */ +#define REDIS_DEFAULT_DBNUM 16 +#define REDIS_CONFIGLINE_MAX 1024 +#define REDIS_EXPIRELOOKUPS_PER_CRON 10 /* lookup 10 expires per loop */ +#define REDIS_EXPIRELOOKUPS_TIME_PERC 25 /* CPU max % for keys collection */ +#define REDIS_MAX_WRITE_PER_EVENT (1024*64) +#define REDIS_SHARED_SELECT_CMDS 10 +#define REDIS_SHARED_INTEGERS 10000 +#define REDIS_SHARED_BULKHDR_LEN 32 +#define REDIS_MAX_LOGMSG_LEN 1024 /* Default maximum length of syslog messages */ +#define REDIS_AOF_REWRITE_PERC 100 +#define REDIS_AOF_REWRITE_MIN_SIZE (1024*1024) +#define REDIS_AOF_REWRITE_ITEMS_PER_CMD 64 +#define REDIS_SLOWLOG_LOG_SLOWER_THAN 10000 +#define REDIS_SLOWLOG_MAX_LEN 128 +#define REDIS_MAX_CLIENTS 10000 +#define REDIS_AUTHPASS_MAX_LEN 512 +#define REDIS_DEFAULT_SLAVE_PRIORITY 100 +#define REDIS_REPL_TIMEOUT 60 +#define REDIS_REPL_PING_SLAVE_PERIOD 10 +#define REDIS_RUN_ID_SIZE 40 +#define REDIS_OPS_SEC_SAMPLES 16 + +/* Protocol and I/O related defines */ +#define REDIS_MAX_QUERYBUF_LEN (1024*1024*1024) /* 1GB max query buffer. */ +#define REDIS_IOBUF_LEN (1024*16) /* Generic I/O buffer size */ +#define REDIS_REPLY_CHUNK_BYTES (16*1024) /* 16k output buffer */ +#define REDIS_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */ +#define REDIS_MBULK_BIG_ARG (1024*32) + +/* Hash table parameters */ +#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */ + +/* Command flags. Please check the command table defined in the redis.c file + * for more information about the meaning of every flag. */ +#define REDIS_CMD_WRITE 1 /* "w" flag */ +#define REDIS_CMD_READONLY 2 /* "r" flag */ +#define REDIS_CMD_DENYOOM 4 /* "m" flag */ +#define REDIS_CMD_FORCE_REPLICATION 8 /* "f" flag */ +#define REDIS_CMD_ADMIN 16 /* "a" flag */ +#define REDIS_CMD_PUBSUB 32 /* "p" flag */ +#define REDIS_CMD_NOSCRIPT 64 /* "s" flag */ +#define REDIS_CMD_RANDOM 128 /* "R" flag */ +#define REDIS_CMD_SORT_FOR_SCRIPT 256 /* "S" flag */ +#define REDIS_CMD_LOADING 512 /* "l" flag */ +#define REDIS_CMD_STALE 1024 /* "t" flag */ +#define REDIS_CMD_SKIP_MONITOR 2048 /* "M" flag */ + +/* Object types */ +#define REDIS_STRING 0 +#define REDIS_LIST 1 +#define REDIS_SET 2 +#define REDIS_ZSET 3 +#define REDIS_HASH 4 + +/* 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. */ +#define REDIS_ENCODING_RAW 0 /* Raw representation */ +#define REDIS_ENCODING_INT 1 /* Encoded as integer */ +#define REDIS_ENCODING_HT 2 /* Encoded as hash table */ +#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ +#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */ +#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ +#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */ +#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ + +/* 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 + * the first byte to interpreter the length: + * + * 00|000000 => if the two MSB are 00 the len is the 6 bits of this byte + * 01|000000 00000000 => 01, the len is 14 byes, 6 bits + 8 bits of next byte + * 10|000000 [32 bit integer] => if it's 01, a full 32 bit len will follow + * 11|000000 this means: specially encoded object will follow. The six bits + * number specify the kind of object that follows. + * See the REDIS_RDB_ENC_* defines. + * + * Lenghts up to 63 are stored using a single byte, most DB keys, and may + * values, will fit inside. */ +#define REDIS_RDB_6BITLEN 0 +#define REDIS_RDB_14BITLEN 1 +#define REDIS_RDB_32BITLEN 2 +#define REDIS_RDB_ENCVAL 3 +#define REDIS_RDB_LENERR UINT_MAX + +/* When a length of a string object stored on disk has the first two bits + * set, the remaining two bits specify a special encoding for the object + * accordingly to the following defines: */ +#define REDIS_RDB_ENC_INT8 0 /* 8 bit signed integer */ +#define REDIS_RDB_ENC_INT16 1 /* 16 bit signed integer */ +#define REDIS_RDB_ENC_INT32 2 /* 32 bit signed integer */ +#define REDIS_RDB_ENC_LZF 3 /* string compressed with FASTLZ */ + +/* AOF states */ +#define REDIS_AOF_OFF 0 /* AOF is off */ +#define REDIS_AOF_ON 1 /* AOF is on */ +#define REDIS_AOF_WAIT_REWRITE 2 /* AOF waits rewrite to start appending */ + +/* Client flags */ +#define REDIS_SLAVE (1<<0) /* This client is a slave server */ +#define REDIS_MASTER (1<<1) /* This client is a master server */ +#define REDIS_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */ +#define REDIS_MULTI (1<<3) /* This client is in a MULTI context */ +#define REDIS_BLOCKED (1<<4) /* The client is waiting in a blocking operation */ +#define REDIS_DIRTY_CAS (1<<5) /* Watched keys modified. EXEC will fail. */ +#define REDIS_CLOSE_AFTER_REPLY (1<<6) /* Close after writing entire reply. */ +#define REDIS_UNBLOCKED (1<<7) /* This client was unblocked and is stored in + server.unblocked_clients */ +#define REDIS_LUA_CLIENT (1<<8) /* This is a non connected client used by Lua */ +#define REDIS_ASKING (1<<9) /* Client issued the ASKING command */ +#define REDIS_CLOSE_ASAP (1<<10)/* Close this client ASAP */ +#define REDIS_UNIX_SOCKET (1<<11) /* Client connected via Unix domain socket */ +#define REDIS_DIRTY_EXEC (1<<12) /* EXEC will fail for errors while queueing */ + +/* Client request types */ +#define REDIS_REQ_INLINE 1 +#define REDIS_REQ_MULTIBULK 2 + +/* Client classes for client limits, currently used only for + * the max-client-output-buffer limit implementation. */ +#define REDIS_CLIENT_LIMIT_CLASS_NORMAL 0 +#define REDIS_CLIENT_LIMIT_CLASS_SLAVE 1 +#define REDIS_CLIENT_LIMIT_CLASS_PUBSUB 2 +#define REDIS_CLIENT_LIMIT_NUM_CLASSES 3 + +/* Slave replication state - slave side */ +#define REDIS_REPL_NONE 0 /* No active replication */ +#define REDIS_REPL_CONNECT 1 /* Must connect to master */ +#define REDIS_REPL_CONNECTING 2 /* Connecting to master */ +#define REDIS_REPL_RECEIVE_PONG 3 /* Wait for PING reply */ +#define REDIS_REPL_TRANSFER 4 /* Receiving .rdb from master */ +#define REDIS_REPL_CONNECTED 5 /* Connected to master */ + +/* Synchronous read timeout - slave side */ +#define REDIS_REPL_SYNCIO_TIMEOUT 5 + +/* Slave replication state - from the point of view of master + * Note that in SEND_BULK and ONLINE state the slave receives new updates + * in its output queue. In the WAIT_BGSAVE state instead the server is waiting + * to start the next background saving in order to send updates to it. */ +#define REDIS_REPL_WAIT_BGSAVE_START 3 /* master waits bgsave to start feeding it */ +#define REDIS_REPL_WAIT_BGSAVE_END 4 /* master waits bgsave to start bulk DB transmission */ +#define REDIS_REPL_SEND_BULK 5 /* master is sending the bulk DB */ +#define REDIS_REPL_ONLINE 6 /* bulk DB already transmitted, receive updates */ + +/* List related stuff */ +#define REDIS_HEAD 0 +#define REDIS_TAIL 1 + +/* Sort operations */ +#define REDIS_SORT_GET 0 +#define REDIS_SORT_ASC 1 +#define REDIS_SORT_DESC 2 +#define REDIS_SORTKEY_MAX 1024 + +/* Log levels */ +#define REDIS_DEBUG 0 +#define REDIS_VERBOSE 1 +#define REDIS_NOTICE 2 +#define REDIS_WARNING 3 +#define REDIS_LOG_RAW (1<<10) /* Modifier to log without timestamp */ + +/* Anti-warning macro... */ +#define REDIS_NOTUSED(V) ((void) V) + +#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */ +#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */ + +/* Append only defines */ +#define AOF_FSYNC_NO 0 +#define AOF_FSYNC_ALWAYS 1 +#define AOF_FSYNC_EVERYSEC 2 + +/* Zip structure related defaults */ +#define REDIS_HASH_MAX_ZIPLIST_ENTRIES 512 +#define REDIS_HASH_MAX_ZIPLIST_VALUE 64 +#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 512 +#define REDIS_LIST_MAX_ZIPLIST_VALUE 64 +#define REDIS_SET_MAX_INTSET_ENTRIES 512 +#define REDIS_ZSET_MAX_ZIPLIST_ENTRIES 128 +#define REDIS_ZSET_MAX_ZIPLIST_VALUE 64 + +/* Sets operations codes */ +#define REDIS_OP_UNION 0 +#define REDIS_OP_DIFF 1 +#define REDIS_OP_INTER 2 + +/* Redis maxmemory strategies */ +#define REDIS_MAXMEMORY_VOLATILE_LRU 0 +#define REDIS_MAXMEMORY_VOLATILE_TTL 1 +#define REDIS_MAXMEMORY_VOLATILE_RANDOM 2 +#define REDIS_MAXMEMORY_ALLKEYS_LRU 3 +#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4 +#define REDIS_MAXMEMORY_NO_EVICTION 5 + +/* Scripting */ +#define REDIS_LUA_TIME_LIMIT 5000 /* milliseconds */ + +/* Units */ +#define UNIT_SECONDS 0 +#define UNIT_MILLISECONDS 1 + +/* SHUTDOWN flags */ +#define REDIS_SHUTDOWN_SAVE 1 /* Force SAVE on SHUTDOWN even if no save + points are configured. */ +#define REDIS_SHUTDOWN_NOSAVE 2 /* Don't SAVE on SHUTDOWN. */ + +/* Command call flags, see call() function */ +#define REDIS_CALL_NONE 0 +#define REDIS_CALL_SLOWLOG 1 +#define REDIS_CALL_STATS 2 +#define REDIS_CALL_PROPAGATE 4 +#define REDIS_CALL_FULL (REDIS_CALL_SLOWLOG | REDIS_CALL_STATS | REDIS_CALL_PROPAGATE) + +/* Command propagation flags, see propagate() function */ +#define REDIS_PROPAGATE_NONE 0 +#define REDIS_PROPAGATE_AOF 1 +#define REDIS_PROPAGATE_REPL 2 + +/* Using the following macro you can run code inside serverCron() with the + * specified period, specified in milliseconds. + * The actual resolution depends on REDIS_HZ. */ +#define run_with_period(_ms_) if (!(server.cronloops%((_ms_)/(1000/REDIS_HZ)))) + +/* We can print the stacktrace, so our assert is defined this way: */ +#define redisAssertWithInfo(_c,_o,_e) ((_e)?(void)0 : (_redisAssertWithInfo(_c,_o,#_e,__FILE__,__LINE__),_exit(1))) +#define redisAssert(_e) ((_e)?(void)0 : (_redisAssert(#_e,__FILE__,__LINE__),_exit(1))) +#define redisPanic(_e) _redisPanic(#_e,__FILE__,__LINE__),_exit(1) + +/*----------------------------------------------------------------------------- + * Data types + *----------------------------------------------------------------------------*/ + +/* A redis object, that is a type able to hold a string / list / set */ + +/* The actual Redis Object */ +#define REDIS_LRU_CLOCK_MAX ((1<<21)-1) /* Max value of obj->lru */ +#define REDIS_LRU_CLOCK_RESOLUTION 10 /* LRU clock resolution in seconds */ +typedef struct redisObject { + unsigned type:4; + unsigned notused:2; /* Not used */ + unsigned encoding:4; + unsigned lru:22; /* lru time (relative to server.lruclock) */ + int refcount; + void *ptr; +} robj; + +/* Macro used to initalize a Redis object allocated on the stack. + * Note that this macro is taken near the structure definition to make sure + * we'll update it when the structure is changed, to avoid bugs like + * bug #85 introduced exactly in this way. */ +#define initStaticStringObject(_var,_ptr) do { \ + _var.refcount = 1; \ + _var.type = REDIS_STRING; \ + _var.encoding = REDIS_ENCODING_RAW; \ + _var.ptr = _ptr; \ +} while(0); + +typedef struct redisDb { + dict *dict; /* The keyspace for this DB */ + dict *expires; /* Timeout of keys with a timeout set */ + dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) */ + dict *ready_keys; /* Blocked keys that received a PUSH */ + dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */ + int id; +} redisDb; + +/* Client MULTI/EXEC state */ +typedef struct multiCmd { + robj **argv; + int argc; + struct redisCommand *cmd; +} multiCmd; + +typedef struct multiState { + multiCmd *commands; /* Array of MULTI commands */ + int count; /* Total number of MULTI commands */ +} multiState; + +typedef struct blockingState { + dict *keys; /* The keys we are waiting to terminate a blocking + * operation such as BLPOP. Otherwise NULL. */ + time_t timeout; /* Blocking operation timeout. If UNIX current time + * is >= timeout then the operation timed out. */ + robj *target; /* The key that should receive the element, + * for BRPOPLPUSH. */ +} blockingState; + +/* The following structure represents a node in the server.ready_keys list, + * where we accumulate all the keys that had clients blocked with a blocking + * operation such as B[LR]POP, but received new data in the context of the + * last executed command. + * + * After the execution of every command or script, we run this list to check + * if as a result we should serve data to clients blocked, unblocking them. + * Note that server.ready_keys will not have duplicates as there dictionary + * also called ready_keys in every structure representing a Redis database, + * where we make sure to remember if a given key was already added in the + * server.ready_keys list. */ +typedef struct readyList { + redisDb *db; + robj *key; +} readyList; + +/* With multiplexing we need to take per-clinet state. + * Clients are taken in a liked list. */ +typedef struct redisClient { + int fd; + redisDb *db; + int dictid; + sds querybuf; + size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */ + int argc; + robj **argv; + struct redisCommand *cmd, *lastcmd; + int reqtype; + int multibulklen; /* number of multi bulk arguments left to read */ + long bulklen; /* length of bulk argument in multi bulk request */ + list *reply; + unsigned long reply_bytes; /* Tot bytes of objects in reply list */ + int sentlen; + time_t ctime; /* Client creation time */ + time_t lastinteraction; /* time of the last interaction, used for timeout */ + time_t obuf_soft_limit_reached_time; + int flags; /* REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI ... */ + int slaveseldb; /* slave selected db, if this client is a slave */ + int authenticated; /* when requirepass is non-NULL */ + int replstate; /* replication state if this is a slave */ + int repldbfd; /* replication DB file descriptor */ + long repldboff; /* replication DB file offset */ + off_t repldbsize; /* replication DB file size */ + int slave_listening_port; /* As configured with: SLAVECONF listening-port */ + multiState mstate; /* MULTI/EXEC state */ + blockingState bpop; /* blocking state */ + list *io_keys; /* Keys this client is waiting to be loaded from the + * swap file in order to continue. */ + 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) */ + + /* Response buffer */ + int bufpos; + char buf[REDIS_REPLY_CHUNK_BYTES]; +} redisClient; + +struct saveparam { + time_t seconds; + int changes; +}; + +struct sharedObjectsStruct { + robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space, + *colon, *nullbulk, *nullmultibulk, *queued, + *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr, + *outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr, + *masterdownerr, *roslaveerr, *execaborterr, + *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk, + *unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *rpop, *lpop, + *lpush, + *select[REDIS_SHARED_SELECT_CMDS], + *integers[REDIS_SHARED_INTEGERS], + *mbulkhdr[REDIS_SHARED_BULKHDR_LEN], /* "*\r\n" */ + *bulkhdr[REDIS_SHARED_BULKHDR_LEN]; /* "$\r\n" */ +}; + +/* ZSETs use a specialized version of Skiplists */ +typedef struct zskiplistNode { + robj *obj; + double score; + struct zskiplistNode *backward; + struct zskiplistLevel { + struct zskiplistNode *forward; + unsigned int span; + } level[]; +} zskiplistNode; + +typedef struct zskiplist { + struct zskiplistNode *header, *tail; + unsigned long length; + int level; +} zskiplist; + +typedef struct zset { + dict *dict; + zskiplist *zsl; +} zset; + +typedef struct clientBufferLimitsConfig { + unsigned long long hard_limit_bytes; + unsigned long long soft_limit_bytes; + time_t soft_limit_seconds; +} clientBufferLimitsConfig; + +/* The redisOp structure defines a Redis Operation, that is an instance of + * a command with an argument vector, database ID, propagation target + * (REDIS_PROPAGATE_*), and command pointer. + * + * Currently only used to additionally propagate more commands to AOF/Replication + * after the propagation of the executed command. */ +typedef struct redisOp { + robj **argv; + int argc, dbid, target; + struct redisCommand *cmd; +} redisOp; + +/* Defines an array of Redis operations. There is an API to add to this + * structure in a easy way. + * + * redisOpArrayInit(); + * redisOpArrayAppend(); + * redisOpArrayFree(); + */ +typedef struct redisOpArray { + redisOp *ops; + int numops; +} redisOpArray; + +/*----------------------------------------------------------------------------- + * Global server state + *----------------------------------------------------------------------------*/ + +struct redisServer { + leveldb_t *ds_db; + leveldb_comparator_t *ds_cmp; + leveldb_cache_t *ds_cache; + leveldb_options_t *ds_options; + leveldb_filterpolicy_t *policy; + + uint16_t ds_lru_cache; + uint16_t ds_create_if_missing; + uint16_t ds_error_if_exists; + uint16_t ds_paranoid_checks; + uint32_t ds_block_cache_size; + uint32_t ds_write_buffer_size; + uint32_t ds_block_size; + uint16_t ds_max_open_files; + uint16_t ds_block_restart_interval; + char *ds_path; + + /* General */ + redisDb *db; + dict *commands; /* Command table hash table */ + aeEventLoop *el; + unsigned lruclock:22; /* Clock incrementing every minute, for LRU */ + unsigned lruclock_padding:10; + int shutdown_asap; /* SHUTDOWN needed ASAP */ + int activerehashing; /* Incremental rehash in serverCron() */ + char *requirepass; /* Pass for AUTH command, or NULL */ + char *pidfile; /* PID file path */ + int arch_bits; /* 32 or 64 depending on sizeof(long) */ + int cronloops; /* Number of times the cron function run */ + char runid[REDIS_RUN_ID_SIZE+1]; /* ID always different at every exec. */ + int sentinel_mode; /* True if this instance is a Sentinel. */ + /* Networking */ + int port; /* TCP listening port */ + char *bindaddr; /* Bind address or NULL */ + char *unixsocket; /* UNIX socket path */ + mode_t unixsocketperm; /* UNIX socket permission */ + int ipfd; /* TCP socket file descriptor */ + int sofd; /* Unix socket file descriptor */ + list *clients; /* List of active clients */ + list *clients_to_close; /* Clients to close asynchronously */ + list *slaves, *monitors; /* List of slaves and MONITORs */ + redisClient *current_client; /* Current client, only used on crash report */ + char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */ + /* RDB / AOF loading information */ + int loading; /* We are loading data from disk if true */ + off_t loading_total_bytes; + off_t loading_loaded_bytes; + time_t loading_start_time; + /* Fast pointers to often looked up command */ + struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand, + *rpopCommand; + /* Fields used only for stats */ + time_t stat_starttime; /* Server start time */ + long long stat_numcommands; /* Number of processed commands */ + long long stat_numconnections; /* Number of connections received */ + long long stat_expiredkeys; /* Number of expired keys */ + long long stat_evictedkeys; /* Number of evicted keys (maxmemory) */ + long long stat_keyspace_hits; /* Number of successful lookups of keys */ + long long stat_keyspace_misses; /* Number of failed lookups of keys */ + size_t stat_peak_memory; /* Max used memory record */ + long long stat_fork_time; /* Time needed to perform latets fork() */ + long long stat_rejected_conn; /* Clients rejected because of maxclients */ + list *slowlog; /* SLOWLOG list of commands */ + long long slowlog_entry_id; /* SLOWLOG current entry ID */ + long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */ + unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */ + /* The following two are used to track instantaneous "load" in terms + * of operations per second. */ + long long ops_sec_last_sample_time; /* Timestamp of last sample (in ms) */ + long long ops_sec_last_sample_ops; /* numcommands in last sample */ + long long ops_sec_samples[REDIS_OPS_SEC_SAMPLES]; + int ops_sec_idx; + /* Configuration */ + int verbosity; /* Loglevel in redis.conf */ + int maxidletime; /* Client timeout in seconds */ + size_t client_max_querybuf_len; /* Limit for client query buffer length */ + int dbnum; /* Total number of configured DBs */ + int daemonize; /* True if running as a daemon */ + clientBufferLimitsConfig client_obuf_limits[REDIS_CLIENT_LIMIT_NUM_CLASSES]; + /* AOF persistence */ + int aof_state; /* REDIS_AOF_(ON|OFF|WAIT_REWRITE) */ + int aof_fsync; /* Kind of fsync() policy */ + char *aof_filename; /* Name of the AOF file */ + int aof_no_fsync_on_rewrite; /* Don't fsync if a rewrite is in prog. */ + int aof_rewrite_perc; /* Rewrite AOF if % growth is > M and... */ + off_t aof_rewrite_min_size; /* the AOF file is at least N bytes. */ + off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */ + off_t aof_current_size; /* AOF current size. */ + int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */ + pid_t aof_child_pid; /* PID if rewriting process */ + list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */ + sds aof_buf; /* AOF buffer, written before entering the event loop */ + int aof_fd; /* File descriptor of currently selected AOF file */ + int aof_selected_db; /* Currently selected DB in AOF */ + time_t aof_flush_postponed_start; /* UNIX time of postponed AOF flush */ + time_t aof_last_fsync; /* UNIX time of last fsync() */ + time_t aof_rewrite_time_last; /* Time used by last AOF rewrite run. */ + time_t aof_rewrite_time_start; /* Current AOF rewrite start time. */ + int aof_lastbgrewrite_status; /* REDIS_OK or REDIS_ERR */ + unsigned long aof_delayed_fsync; /* delayed AOF fsync() counter */ + /* RDB persistence */ + long long dirty; /* Changes to DB from the last save */ + long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */ + pid_t rdb_child_pid; /* PID of RDB saving child */ + struct saveparam *saveparams; /* Save points array for RDB */ + int saveparamslen; /* Number of saving points */ + char *rdb_filename; /* Name of RDB file */ + int rdb_compression; /* Use compression in RDB? */ + int rdb_checksum; /* Use RDB checksum? */ + time_t lastsave; /* Unix time of last save succeeede */ + time_t rdb_save_time_last; /* Time used by last RDB save run. */ + time_t rdb_save_time_start; /* Current RDB save start time. */ + int lastbgsave_status; /* REDIS_OK or REDIS_ERR */ + int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */ + /* Propagation of commands in AOF / replication */ + redisOpArray also_propagate; /* Additional command to propagate. */ + /* Logging */ + char *logfile; /* Path of log file */ + int syslog_enabled; /* Is syslog enabled? */ + char *syslog_ident; /* Syslog ident */ + int syslog_facility; /* Syslog facility */ + /* Slave specific fields */ + char *masterauth; /* AUTH with this password with master */ + char *masterhost; /* Hostname of master */ + int masterport; /* Port of master */ + int repl_ping_slave_period; /* Master pings the slave every N seconds */ + int repl_timeout; /* Timeout after N seconds of master idle */ + redisClient *master; /* Client that is master for this slave */ + int repl_syncio_timeout; /* Timeout for synchronous I/O calls */ + int repl_state; /* Replication status if the instance is a slave */ + off_t repl_transfer_size; /* Size of RDB to read from master during sync. */ + off_t repl_transfer_read; /* Amount of RDB read from master during sync. */ + off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */ + int repl_transfer_s; /* Slave -> Master SYNC socket */ + int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */ + char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */ + time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */ + int repl_serve_stale_data; /* Serve stale data when link is down? */ + int repl_slave_ro; /* Slave is read only? */ + time_t repl_down_since; /* Unix time at which link with master went down */ + int slave_priority; /* Reported in INFO and used by Sentinel. */ + /* Limits */ + unsigned int maxclients; /* Max number of simultaneous clients */ + unsigned long long maxmemory; /* Max number of memory bytes to use */ + int maxmemory_policy; /* Policy for key evition */ + int maxmemory_samples; /* Pricision of random sampling */ + /* Blocked clients */ + unsigned int bpop_blocked_clients; /* Number of clients blocked by lists */ + list *unblocked_clients; /* list of clients to unblock before next loop */ + list *ready_keys; /* List of readyList structures for BLPOP & co */ + /* Sort parameters - qsort_r() is only available under BSD so we + * have to take this state global, in order to pass it to sortCompare() */ + int sort_desc; + int sort_alpha; + int sort_bypattern; + /* Zip structure config, see redis.conf for more information */ + size_t hash_max_ziplist_entries; + size_t hash_max_ziplist_value; + size_t list_max_ziplist_entries; + size_t list_max_ziplist_value; + size_t set_max_intset_entries; + size_t zset_max_ziplist_entries; + size_t zset_max_ziplist_value; + time_t unixtime; /* Unix time sampled every second. */ + /* Pubsub */ + dict *pubsub_channels; /* Map channels to list of subscribed clients */ + list *pubsub_patterns; /* A list of pubsub_patterns */ + /* 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 */ + redisClient *lua_caller; /* The client running EVAL right now, or NULL */ + dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */ + long long lua_time_limit; /* Script timeout in seconds */ + long long lua_time_start; /* Start time of script */ + int lua_write_dirty; /* True if a write command was called during the + execution of the current script. */ + int lua_random_dirty; /* True if a random command was called during the + execution of the current script. */ + int lua_timedout; /* True if we reached the time limit for script + execution. */ + int lua_kill; /* Kill the script if true. */ + /* Assert & bug reportign */ + char *assert_failed; + char *assert_file; + int assert_line; + int bug_report_start; /* True if bug report header was already logged. */ + int watchdog_period; /* Software watchdog period in ms. 0 = off */ +}; + +typedef struct pubsubPattern { + redisClient *client; + robj *pattern; +} pubsubPattern; + +typedef void redisCommandProc(redisClient *c); +typedef int *redisGetKeysProc(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags); +struct redisCommand { + char *name; + redisCommandProc *proc; + int arity; + char *sflags; /* Flags as string represenation, one char per flag. */ + int flags; /* The actual flags, obtained from the 'sflags' field. */ + /* Use a function to determine keys arguments in a command line. */ + redisGetKeysProc *getkeys_proc; + /* What keys should be loaded in background when calling this command? */ + int firstkey; /* The first argument that's a key (0 = no keys) */ + int lastkey; /* THe last argument that's a key */ + int keystep; /* The step between first and last key */ + long long microseconds, calls; +}; + +struct redisFunctionSym { + char *name; + unsigned long pointer; +}; + +typedef struct _redisSortObject { + robj *obj; + union { + double score; + robj *cmpobj; + } u; +} redisSortObject; + +typedef struct _redisSortOperation { + int type; + robj *pattern; +} redisSortOperation; + +/* Structure to hold list iteration abstraction. */ +typedef struct { + robj *subject; + unsigned char encoding; + unsigned char direction; /* Iteration direction */ + unsigned char *zi; + listNode *ln; +} listTypeIterator; + +/* Structure for an entry while iterating over a list. */ +typedef struct { + listTypeIterator *li; + unsigned char *zi; /* Entry in ziplist */ + listNode *ln; /* Entry in linked list */ +} listTypeEntry; + +/* Structure to hold set iteration abstraction. */ +typedef struct { + robj *subject; + int encoding; + int ii; /* intset iterator */ + dictIterator *di; +} setTypeIterator; + +/* Structure to hold hash iteration abstration. Note that iteration over + * hashes involves both fields and values. Because it is possible that + * not both are required, store pointers in the iterator to avoid + * unnecessary memory allocation for fields/values. */ +typedef struct { + robj *subject; + int encoding; + + unsigned char *fptr, *vptr; + + dictIterator *di; + dictEntry *de; +} hashTypeIterator; + +#define REDIS_HASH_KEY 1 +#define REDIS_HASH_VALUE 2 + +/*----------------------------------------------------------------------------- + * Extern declarations + *----------------------------------------------------------------------------*/ + +extern struct redisServer server; +extern struct sharedObjectsStruct shared; +extern dictType setDictType; +extern dictType zsetDictType; +extern dictType dbDictType; +extern dictType shaScriptObjectDictType; +extern double R_Zero, R_PosInf, R_NegInf, R_Nan; +extern dictType hashDictType; + +/*----------------------------------------------------------------------------- + * Functions prototypes + *----------------------------------------------------------------------------*/ + +/* Utils */ +long long ustime(void); +long long mstime(void); +void getRandomHexChars(char *p, unsigned int len); +uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l); +void exitFromChild(int retcode); + +/* networking.c -- Networking and Client related operations */ +redisClient *createClient(int fd); +void closeTimedoutClients(void); +void freeClient(redisClient *c); +void resetClient(redisClient *c); +void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask); +void addReply(redisClient *c, robj *obj); +void *addDeferredMultiBulkLength(redisClient *c); +void setDeferredMultiBulkLength(redisClient *c, void *node, long length); +void addReplySds(redisClient *c, sds s); +void processInputBuffer(redisClient *c); +void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask); +void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask); +void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask); +void addReplyBulk(redisClient *c, robj *obj); +void addReplyBulkCString(redisClient *c, char *s); +void addReplyBulkCBuffer(redisClient *c, void *p, size_t len); +void addReplyBulkLongLong(redisClient *c, long long ll); +void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); +void addReply(redisClient *c, robj *obj); +void addReplySds(redisClient *c, sds s); +void addReplyError(redisClient *c, char *err); +void addReplyStatus(redisClient *c, char *status); +void addReplyDouble(redisClient *c, double d); +void addReplyLongLong(redisClient *c, long long ll); +void addReplyMultiBulkLen(redisClient *c, long length); +void copyClientOutputBuffer(redisClient *dst, redisClient *src); +void *dupClientReplyValue(void *o); +void getClientsMaxBuffers(unsigned long *longest_output_list, + unsigned long *biggest_input_buffer); +sds getClientInfoString(redisClient *client); +sds getAllClientsInfoString(void); +void rewriteClientCommandVector(redisClient *c, int argc, ...); +void rewriteClientCommandArgument(redisClient *c, int i, robj *newval); +unsigned long getClientOutputBufferMemoryUsage(redisClient *c); +void freeClientsInAsyncFreeQueue(void); +void asyncCloseClientOnOutputBufferLimitReached(redisClient *c); +int getClientLimitClassByName(char *name); +char *getClientLimitClassName(int class); +void flushSlavesOutputBuffers(void); +void disconnectSlaves(void); + +#ifdef __GNUC__ +void addReplyErrorFormat(redisClient *c, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +void addReplyStatusFormat(redisClient *c, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); +#else +void addReplyErrorFormat(redisClient *c, const char *fmt, ...); +void addReplyStatusFormat(redisClient *c, const char *fmt, ...); +#endif + +/* List data type */ +void listTypeTryConversion(robj *subject, robj *value); +void listTypePush(robj *subject, robj *value, int where); +robj *listTypePop(robj *subject, int where); +unsigned long listTypeLength(robj *subject); +listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction); +void listTypeReleaseIterator(listTypeIterator *li); +int listTypeNext(listTypeIterator *li, listTypeEntry *entry); +robj *listTypeGet(listTypeEntry *entry); +void listTypeInsert(listTypeEntry *entry, robj *value, int where); +int listTypeEqual(listTypeEntry *entry, robj *o); +void listTypeDelete(listTypeEntry *entry); +void listTypeConvert(robj *subject, int enc); +void unblockClientWaitingData(redisClient *c); +void handleClientsBlockedOnLists(void); +void popGenericCommand(redisClient *c, int where); + +/* MULTI/EXEC/WATCH... */ +void unwatchAllKeys(redisClient *c); +void initClientMultiState(redisClient *c); +void freeClientMultiState(redisClient *c); +void queueMultiCommand(redisClient *c); +void touchWatchedKey(redisDb *db, robj *key); +void touchWatchedKeysOnFlush(int dbid); +void discardTransaction(redisClient *c); +void flagTransaction(redisClient *c); + +/* Redis object implementation */ +void decrRefCount(void *o); +void incrRefCount(robj *o); +robj *resetRefCount(robj *obj); +void freeStringObject(robj *o); +void freeListObject(robj *o); +void freeSetObject(robj *o); +void freeZsetObject(robj *o); +void freeHashObject(robj *o); +robj *createObject(int type, void *ptr); +robj *createStringObject(char *ptr, size_t len); +robj *dupStringObject(robj *o); +int isObjectRepresentableAsLongLong(robj *o, long long *llongval); +robj *tryObjectEncoding(robj *o); +robj *getDecodedObject(robj *o); +size_t stringObjectLen(robj *o); +robj *createStringObjectFromLongLong(long long value); +robj *createStringObjectFromLongDouble(long double value); +robj *createListObject(void); +robj *createZiplistObject(void); +robj *createSetObject(void); +robj *createIntsetObject(void); +robj *createHashObject(void); +robj *createZsetObject(void); +robj *createZsetZiplistObject(void); +int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg); +int checkType(redisClient *c, robj *o, int type); +int getLongLongFromObjectOrReply(redisClient *c, robj *o, long long *target, const char *msg); +int getDoubleFromObjectOrReply(redisClient *c, robj *o, double *target, const char *msg); +int getLongLongFromObject(robj *o, long long *target); +int getLongDoubleFromObject(robj *o, long double *target); +int getLongDoubleFromObjectOrReply(redisClient *c, robj *o, long double *target, const char *msg); +char *strEncoding(int encoding); +int compareStringObjects(robj *a, robj *b); +int equalStringObjects(robj *a, robj *b); +unsigned long estimateObjectIdleTime(robj *o); + +/* Synchronous I/O with timeout */ +ssize_t syncWrite(int fd, char *ptr, ssize_t size, long long timeout); +ssize_t syncRead(int fd, char *ptr, ssize_t size, long long timeout); +ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout); + +/* Replication */ +void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc); +void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **argv, int argc); +void updateSlavesWaitingBgsave(int bgsaveerr); +void replicationCron(void); + +/* Generic persistence functions */ +void startLoading(FILE *fp); +void loadingProgress(off_t pos); +void stopLoading(void); + +/* RDB persistence */ +#include "rdb.h" + +/* AOF persistence */ +void flushAppendOnlyFile(int force); +void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc); +void aofRemoveTempFile(pid_t childpid); +int rewriteAppendOnlyFileBackground(void); +int loadAppendOnlyFile(char *filename); +void stopAppendOnly(void); +int startAppendOnly(void); +void backgroundRewriteDoneHandler(int exitcode, int bysignal); +void aofRewriteBufferReset(void); +unsigned long aofRewriteBufferSize(void); + +/* Sorted sets data type */ + +/* Struct to hold a inclusive/exclusive range spec. */ +typedef struct { + double min, max; + int minex, maxex; /* are min or max exclusive? */ +} zrangespec; + +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); +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); +unsigned int zsetLength(robj *zobj); +void zsetConvert(robj *zobj, int encoding); + +/* Core functions */ +int freeMemoryIfNeeded(void); +int processCommand(redisClient *c); +void setupSignalHandlers(void); +struct redisCommand *lookupCommand(sds name); +struct redisCommand *lookupCommandByCString(char *s); +void call(redisClient *c, int flags); +void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags); +void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target); +int prepareForShutdown(); +void redisLog(int level, const char *fmt, ...); +void redisLogRaw(int level, const char *msg); +void redisLogFromHandler(int level, const char *msg); +void usage(); +void updateDictResizePolicy(void); +int htNeedsResize(dict *dict); +void oom(const char *msg); +void populateCommandTable(void); +void resetCommandTableStats(void); + +/* Set data type */ +robj *setTypeCreate(robj *value); +int setTypeAdd(robj *subject, robj *value); +int setTypeRemove(robj *subject, robj *value); +int setTypeIsMember(robj *subject, robj *value); +setTypeIterator *setTypeInitIterator(robj *subject); +void setTypeReleaseIterator(setTypeIterator *si); +int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele); +robj *setTypeNextObject(setTypeIterator *si); +int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele); +unsigned long setTypeSize(robj *subject); +void setTypeConvert(robj *subject, int enc); + +/* Hash data type */ +void hashTypeConvert(robj *o, int enc); +void hashTypeTryConversion(robj *subject, robj **argv, int start, int end); +void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2); +robj *hashTypeGetObject(robj *o, robj *key); +int hashTypeExists(robj *o, robj *key); +int hashTypeSet(robj *o, robj *key, robj *value); +int hashTypeDelete(robj *o, robj *key); +unsigned long hashTypeLength(robj *o); +hashTypeIterator *hashTypeInitIterator(robj *subject); +void hashTypeReleaseIterator(hashTypeIterator *hi); +int hashTypeNext(hashTypeIterator *hi); +void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, + unsigned char **vstr, + unsigned int *vlen, + long long *vll); +void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst); +robj *hashTypeCurrentObject(hashTypeIterator *hi, int what); +robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key); + +/* Pub / Sub */ +int pubsubUnsubscribeAllChannels(redisClient *c, int notify); +int pubsubUnsubscribeAllPatterns(redisClient *c, int notify); +void freePubsubPattern(void *p); +int listMatchPubsubPattern(void *a, void *b); +int pubsubPublishMessage(robj *channel, robj *message); + +/* Configuration */ +void loadServerConfig(char *filename, char *options); +void appendServerSaveParams(time_t seconds, int changes); +void resetServerSaveParams(); + +/* db.c -- Keyspace access API */ +int removeExpire(redisDb *db, robj *key); +void propagateExpire(redisDb *db, robj *key); +int expireIfNeeded(redisDb *db, robj *key); +long long getExpire(redisDb *db, robj *key); +void setExpire(redisDb *db, robj *key, long long when); +robj *lookupKey(redisDb *db, robj *key); +robj *lookupKeyRead(redisDb *db, robj *key); +robj *lookupKeyWrite(redisDb *db, robj *key); +robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply); +robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply); +void dbAdd(redisDb *db, robj *key, robj *val); +void dbOverwrite(redisDb *db, robj *key, robj *val); +void setKey(redisDb *db, robj *key, robj *val); +int dbExists(redisDb *db, robj *key); +robj *dbRandomKey(redisDb *db); +int dbDelete(redisDb *db, robj *key); +long long emptyDb(); +int selectDb(redisClient *c, int id); +void signalModifiedKey(redisDb *db, robj *key); +void signalFlushedDb(int dbid); +unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count); + +/* API to get key arguments from commands */ +#define REDIS_GETKEYS_ALL 0 +#define REDIS_GETKEYS_PRELOAD 1 +int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys, int flags); +void getKeysFreeResult(int *result); +int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags); +int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags); +int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags); + +/* Sentinel */ +void initSentinelConfig(void); +void initSentinel(void); +void sentinelTimer(void); +char *sentinelHandleConfiguration(char **argv, int argc); + +/* Scripting */ +void scriptingInit(void); + +/* Git SHA1 */ +char *redisGitSHA1(void); +char *redisGitDirty(void); + +/* Commands prototypes */ +void authCommand(redisClient *c); +void pingCommand(redisClient *c); +void echoCommand(redisClient *c); +void setCommand(redisClient *c); +void setnxCommand(redisClient *c); +void setexCommand(redisClient *c); +void psetexCommand(redisClient *c); +void getCommand(redisClient *c); +void delCommand(redisClient *c); +void existsCommand(redisClient *c); +void setbitCommand(redisClient *c); +void getbitCommand(redisClient *c); +void setrangeCommand(redisClient *c); +void getrangeCommand(redisClient *c); +void incrCommand(redisClient *c); +void decrCommand(redisClient *c); +void incrbyCommand(redisClient *c); +void decrbyCommand(redisClient *c); +void incrbyfloatCommand(redisClient *c); +void selectCommand(redisClient *c); +void randomkeyCommand(redisClient *c); +void keysCommand(redisClient *c); +void dbsizeCommand(redisClient *c); +void lastsaveCommand(redisClient *c); +void saveCommand(redisClient *c); +void bgsaveCommand(redisClient *c); +void bgrewriteaofCommand(redisClient *c); +void shutdownCommand(redisClient *c); +void moveCommand(redisClient *c); +void renameCommand(redisClient *c); +void renamenxCommand(redisClient *c); +void lpushCommand(redisClient *c); +void rpushCommand(redisClient *c); +void lpushxCommand(redisClient *c); +void rpushxCommand(redisClient *c); +void linsertCommand(redisClient *c); +void lpopCommand(redisClient *c); +void rpopCommand(redisClient *c); +void llenCommand(redisClient *c); +void lindexCommand(redisClient *c); +void lrangeCommand(redisClient *c); +void ltrimCommand(redisClient *c); +void typeCommand(redisClient *c); +void lsetCommand(redisClient *c); +void saddCommand(redisClient *c); +void sremCommand(redisClient *c); +void smoveCommand(redisClient *c); +void sismemberCommand(redisClient *c); +void scardCommand(redisClient *c); +void spopCommand(redisClient *c); +void srandmemberCommand(redisClient *c); +void sinterCommand(redisClient *c); +void sinterstoreCommand(redisClient *c); +void sunionCommand(redisClient *c); +void sunionstoreCommand(redisClient *c); +void sdiffCommand(redisClient *c); +void sdiffstoreCommand(redisClient *c); +void syncCommand(redisClient *c); +void flushdbCommand(redisClient *c); +void flushallCommand(redisClient *c); +void sortCommand(redisClient *c); +void lremCommand(redisClient *c); +void rpoplpushCommand(redisClient *c); +void infoCommand(redisClient *c); +void mgetCommand(redisClient *c); +void monitorCommand(redisClient *c); +void expireCommand(redisClient *c); +void expireatCommand(redisClient *c); +void pexpireCommand(redisClient *c); +void pexpireatCommand(redisClient *c); +void getsetCommand(redisClient *c); +void ttlCommand(redisClient *c); +void pttlCommand(redisClient *c); +void persistCommand(redisClient *c); +void slaveofCommand(redisClient *c); +void debugCommand(redisClient *c); +void msetCommand(redisClient *c); +void msetnxCommand(redisClient *c); +void zaddCommand(redisClient *c); +void zincrbyCommand(redisClient *c); +void zrangeCommand(redisClient *c); +void zrangebyscoreCommand(redisClient *c); +void zrevrangebyscoreCommand(redisClient *c); +void zcountCommand(redisClient *c); +void zrevrangeCommand(redisClient *c); +void zcardCommand(redisClient *c); +void zremCommand(redisClient *c); +void zscoreCommand(redisClient *c); +void zremrangebyscoreCommand(redisClient *c); +void multiCommand(redisClient *c); +void execCommand(redisClient *c); +void discardCommand(redisClient *c); +void blpopCommand(redisClient *c); +void brpopCommand(redisClient *c); +void brpoplpushCommand(redisClient *c); +void appendCommand(redisClient *c); +void strlenCommand(redisClient *c); +void zrankCommand(redisClient *c); +void zrevrankCommand(redisClient *c); +void hsetCommand(redisClient *c); +void hsetnxCommand(redisClient *c); +void hgetCommand(redisClient *c); +void hmsetCommand(redisClient *c); +void hmgetCommand(redisClient *c); +void hdelCommand(redisClient *c); +void hlenCommand(redisClient *c); +void zremrangebyrankCommand(redisClient *c); +void zunionstoreCommand(redisClient *c); +void zinterstoreCommand(redisClient *c); +void hkeysCommand(redisClient *c); +void hvalsCommand(redisClient *c); +void hgetallCommand(redisClient *c); +void hexistsCommand(redisClient *c); +void configCommand(redisClient *c); +void hincrbyCommand(redisClient *c); +void hincrbyfloatCommand(redisClient *c); +void subscribeCommand(redisClient *c); +void unsubscribeCommand(redisClient *c); +void psubscribeCommand(redisClient *c); +void punsubscribeCommand(redisClient *c); +void publishCommand(redisClient *c); +void watchCommand(redisClient *c); +void unwatchCommand(redisClient *c); +void restoreCommand(redisClient *c); +void migrateCommand(redisClient *c); +void dumpCommand(redisClient *c); +void objectCommand(redisClient *c); +void clientCommand(redisClient *c); +void evalCommand(redisClient *c); +void evalShaCommand(redisClient *c); +void scriptCommand(redisClient *c); +void timeCommand(redisClient *c); +void bitopCommand(redisClient *c); +void bitcountCommand(redisClient *c); +void replconfCommand(redisClient *c); + +#if defined(__GNUC__) +void *calloc(size_t count, size_t size) __attribute__ ((deprecated)); +void free(void *ptr) __attribute__ ((deprecated)); +void *malloc(size_t size) __attribute__ ((deprecated)); +void *realloc(void *ptr, size_t size) __attribute__ ((deprecated)); +#endif + +/* Debugging stuff */ +void _redisAssertWithInfo(redisClient *c, robj *o, char *estr, char *file, int line); +void _redisAssert(char *estr, char *file, int line); +void _redisPanic(char *msg, char *file, int line); +void bugReportStart(void); +void redisLogObjectDebugInfo(robj *o); +void sigsegvHandler(int sig, siginfo_t *info, void *secret); +sds genRedisInfoString(char *section); +void enableWatchdog(int period); +void disableWatchdog(void); +void watchdogScheduleSignal(int period); +void redisLogHexDump(int level, char *descr, void *value, size_t len); + +void ds_init(); +void ds_close(); +void ds_get(redisClient *c); +void ds_set(redisClient *c); +void ds_mset(redisClient *c); +void ds_mget(redisClient *c); + +void ds_append(redisClient *c); +void ds_incrby(redisClient *c); + +void ds_hdel(redisClient *c); +void ds_hget(redisClient *c); +void ds_hset(redisClient *c); +void ds_hmget(redisClient *c); +void ds_hmset(redisClient *c); +void ds_hincrby(redisClient *c); +void ds_hgetall(redisClient *c); + +void ds_delete(redisClient *c); +void rl_delete(redisClient *c); +void rl_get(redisClient *c); +void rl_set(redisClient *c); +void rl_hget(redisClient *c); +void rl_hset(redisClient *c); +void rl_hdel(redisClient *c); + + +#define redisDebug(fmt, ...) \ + printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__) +#define redisDebugMark() \ + printf("-- MARK %s:%d --\n", __FILE__, __LINE__) + +#endif From e9098512c5feff0a564858dc86e4e662ad4db8ef Mon Sep 17 00:00:00 2001 From: shenzhe Date: Mon, 7 Jan 2013 15:35:34 +0800 Subject: [PATCH 02/93] =?UTF-8?q?ds=5Fhset=20=E8=BE=93=E5=87=BA=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E6=94=B9=E6=88=90=E5=92=8Chset=E4=B8=80=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ds.c b/src/ds.c index d76674e..de410c3 100644 --- a/src/ds.c +++ b/src/ds.c @@ -432,7 +432,7 @@ void ds_hset(redisClient *c) leveldb_writebatch_destroy(wb); sdsfree(str); - addReply(c,shared.ok); + addReply(c, shared.czero); return ; } From f136b7fc18f632ee20d6490399744ab89fb8d83e Mon Sep 17 00:00:00 2001 From: shenzhe Date: Mon, 7 Jan 2013 17:34:25 +0800 Subject: [PATCH 03/93] merge from qiye --- php-hiredis/redis.php | 18 ++++++++-- src/ds.c | 78 +++++++++++++++++++------------------------ 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/php-hiredis/redis.php b/php-hiredis/redis.php index 797b0a7..4234abe 100644 --- a/php-hiredis/redis.php +++ b/php-hiredis/redis.php @@ -47,7 +47,18 @@ public function hmset($cmd, $recores) return false; } - + private function array_to_hash($data) + { + $len = count($data); + $recore = array(); + + for($i=0; $i<$len; $i++) + { + $recore[$data[$i++]] = $data[$i]; + } + + return $recore; + } public function __call($method, $params) { if(is_null($params)) @@ -58,7 +69,10 @@ public function __call($method, $params) $data = phpiredis_command_bs($this->conn, $params); if($data == "NULL") return NULL; - return $data; + if(strcasecmp($method, "ds_mget") == 0 || strcasecmp($method, "ds_hmget") == 0) + return $this->array_to_hash($data); + + return $data; } diff --git a/src/ds.c b/src/ds.c index de410c3..6592ad2 100644 --- a/src/ds.c +++ b/src/ds.c @@ -81,7 +81,7 @@ void ds_init() void ds_mget(redisClient *c) { - int i, len, pos; + int i; size_t val_len; char *err, *value; @@ -96,9 +96,8 @@ void ds_mget(redisClient *c) { err = NULL; value = NULL; - val_len = pos = 0; - len = strlen((char *)c->argv[i]->ptr); - value = leveldb_get(server.ds_db, roptions, (char *)c->argv[i]->ptr, len, &val_len, &err); + val_len = 0; + value = leveldb_get(server.ds_db, roptions, c->argv[i]->ptr, sdslen((sds)c->argv[i]->ptr), &val_len, &err); if(err != NULL) { addReplyError(c, err); @@ -124,11 +123,8 @@ void ds_mget(redisClient *c) void ds_get(redisClient *c) { - - bool is_int; - int64_t recore; - char *err = NULL; - size_t val_len, i; + char *err; + size_t val_len; char *key = NULL; char *value = NULL; @@ -137,9 +133,10 @@ void ds_get(redisClient *c) roptions = leveldb_readoptions_create(); leveldb_readoptions_set_verify_checksums(roptions, 0); leveldb_readoptions_set_fill_cache(roptions, 1); - + + err = NULL; key = (char *)c->argv[1]->ptr; - value = leveldb_get(server.ds_db, roptions, key, strlen(key), &val_len, &err); + value = leveldb_get(server.ds_db, roptions, key, sdslen((sds)key), &val_len, &err); leveldb_readoptions_destroy(roptions); if(err != NULL) { @@ -155,21 +152,8 @@ void ds_get(redisClient *c) return ; } - is_int = true; - for(i=0; value[i]!=0; i++) - { - is_int = isgraph(value[i]) ? false : true; - } + addReplyBulkCBuffer(c, value, val_len); - if(is_int) - { - recore = *(int64_t *)value; - addReplyLongLong(c, recore); - } - else - { - addReplyBulkCBuffer(c, value, val_len); - } leveldb_free(value); } void rl_get(redisClient *c) @@ -211,7 +195,7 @@ void ds_mset(redisClient *c) { key = (char *)c->argv[i]->ptr; value = (char *)c->argv[++i]->ptr; - leveldb_writebatch_put(wb, key, strlen(key), value, strlen(value)); + leveldb_writebatch_put(wb, key, sdslen((sds)key), value, sdslen((sds)value)); } leveldb_write(server.ds_db, woptions, wb, &err); leveldb_writeoptions_destroy(woptions); @@ -231,12 +215,13 @@ void ds_mset(redisClient *c) void ds_hincrby(redisClient *c) { - int64_t val, recore; - sds keyword; char *value; + sds keyword, data; size_t val_len; char *err = NULL; + + int64_t val, recore; leveldb_writeoptions_t *woptions; leveldb_readoptions_t *roptions; @@ -269,15 +254,16 @@ void ds_hincrby(redisClient *c) } else { - val = *(int64_t *)value; + val = strtoll(value, NULL, 10); } err = NULL; recore = strtoll(c->argv[3]->ptr, NULL, 10); recore = val + recore; - woptions = leveldb_writeoptions_create(); + data = sdsfromlonglong(recore); - leveldb_put(server.ds_db, woptions, keyword, sdslen(keyword), (char *)&recore, sizeof(int64_t), &err); + woptions = leveldb_writeoptions_create(); + leveldb_put(server.ds_db, woptions, keyword, sdslen(keyword), data, sdslen(data), &err); leveldb_writeoptions_destroy(woptions); if(err != NULL) { @@ -288,8 +274,10 @@ void ds_hincrby(redisClient *c) { addReplyLongLong(c, recore); } - leveldb_free(value); + + sdsfree(data); sdsfree(keyword); + leveldb_free(value); return ; } @@ -381,7 +369,7 @@ void ds_hmset(redisClient *c) keyword = sdscat(keyword, key); keyword = sdscatlen(keyword, "*", 1); keyword = sdscat(keyword, field); - leveldb_writebatch_put(wb, keyword, sdslen(keyword), value, strlen(value)); + leveldb_writebatch_put(wb, keyword, sdslen(keyword), value, sdslen((sds)value)); } sdsfree(keyword); @@ -424,7 +412,7 @@ void ds_hset(redisClient *c) str = sdscpy(str, key); str = sdscatlen(str, "*", 1); str = sdscat(str, field); - leveldb_writebatch_put(wb, str, sdslen(str), value, strlen(value)); + leveldb_writebatch_put(wb, str, sdslen(str), value, sdslen((sds)value)); leveldb_write(server.ds_db, woptions, wb, &err); @@ -694,6 +682,7 @@ void rl_hget(redisClient *c) void ds_incrby(redisClient *c) { + sds data; char *value; int64_t val, recore; @@ -710,7 +699,7 @@ void ds_incrby(redisClient *c) err = NULL; val_len = 0; - value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), &val_len, &err); + value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), &val_len, &err); leveldb_readoptions_destroy(roptions); if(err != NULL) @@ -726,15 +715,16 @@ void ds_incrby(redisClient *c) } else { - val = *(int64_t *)value; + val = strtoll(value, NULL, 10); } err = NULL; recore = strtoll(c->argv[2]->ptr, NULL, 10); recore = val + recore; + data = sdsfromlonglong(recore); woptions = leveldb_writeoptions_create(); - leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), (char *)&recore, sizeof(int64_t), &err); + leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), data, sdslen(data), &err); leveldb_writeoptions_destroy(woptions); if(err != NULL) { @@ -745,6 +735,8 @@ void ds_incrby(redisClient *c) { addReplyLongLong(c, recore); } + + sdsfree(data); leveldb_free(value); return ; } @@ -768,7 +760,7 @@ void ds_append(redisClient *c) err = NULL; val_len = 0; - value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), &val_len, &err); + value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), &val_len, &err); leveldb_readoptions_destroy(roptions); if(err != NULL) @@ -789,7 +781,7 @@ void ds_append(redisClient *c) recore = sdscat(recore, c->argv[1]->ptr); woptions = leveldb_writeoptions_create(); - leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, strlen(c->argv[1]->ptr), recore, sdslen(recore), &err); + leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), recore, sdslen(recore), &err); leveldb_writeoptions_destroy(woptions); if(err != NULL) { @@ -816,7 +808,7 @@ void ds_set(redisClient *c) key = (char *)c->argv[1]->ptr; value = (char *)c->argv[2]->ptr; - leveldb_put(server.ds_db, woptions, key, strlen(key), value, strlen(value), &err); + leveldb_put(server.ds_db, woptions, key, sdslen((sds)key), value, sdslen((sds)value), &err); leveldb_writeoptions_destroy(woptions); if(err != NULL) { @@ -838,7 +830,7 @@ void rl_set(redisClient *c) key = (char *)c->argv[1]->ptr; value = (char *)c->argv[2]->ptr; - leveldb_put(server.ds_db, woptions, key, strlen(key), value, strlen(value), &err); + leveldb_put(server.ds_db, woptions, key, sdslen((sds)key), value, sdslen((sds)value), &err); leveldb_writeoptions_destroy(woptions); if(err != NULL) { @@ -865,7 +857,7 @@ void ds_delete(redisClient *c) if(c->argc < 3) { key = (char *)c->argv[1]->ptr; - leveldb_delete(server.ds_db, woptions, key, strlen(key), &err); + leveldb_delete(server.ds_db, woptions, key, sdslen((sds)key), &err); leveldb_writeoptions_destroy(woptions); if(err != NULL) { @@ -880,7 +872,7 @@ void ds_delete(redisClient *c) wb = leveldb_writebatch_create(); for(i=1; iargc; i++) { - leveldb_writebatch_delete(wb, (char *)c->argv[i]->ptr, strlen((char *)c->argv[i]->ptr)); + leveldb_writebatch_delete(wb, (char *)c->argv[i]->ptr, sdslen((sds)c->argv[i]->ptr)); } leveldb_write(server.ds_db, woptions, wb, &err); leveldb_writeoptions_destroy(woptions); From d80d188d58ec23ec47cc3005bd2f64655a7be077 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Mon, 7 Jan 2013 17:39:24 +0800 Subject: [PATCH 04/93] =?UTF-8?q?=E4=BF=AE=E5=A4=8Drl=5Fhset=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ds.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ds.c b/src/ds.c index 6592ad2..5718b51 100644 --- a/src/ds.c +++ b/src/ds.c @@ -426,8 +426,8 @@ void ds_hset(redisClient *c) void rl_hset(redisClient *c) { - ds_hset(*c); - hsetCommand(*c); + ds_hset(c); + hsetCommand(c); } void rl_hdel(redisClient *c) From fa1c8c9684cb2b4db562e87dbdeb825180417dd0 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Mon, 7 Jan 2013 17:44:30 +0800 Subject: [PATCH 05/93] =?UTF-8?q?=E4=BF=AE=E5=A4=8Drl=5Fhget=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/redis.h | 1 + src/t_hash.c | 1508 +++++++++++++++++++++++++------------------------- 2 files changed, 755 insertions(+), 754 deletions(-) diff --git a/src/redis.h b/src/redis.h index 4c91dcc..104bfc1 100644 --- a/src/redis.h +++ b/src/redis.h @@ -815,6 +815,7 @@ void addReplyStatus(redisClient *c, char *status); void addReplyDouble(redisClient *c, double d); void addReplyLongLong(redisClient *c, long long ll); void addReplyMultiBulkLen(redisClient *c, long length); +void addHashFieldToReply(redisClient *c, robj *o, robj *field); void copyClientOutputBuffer(redisClient *dst, redisClient *src); void *dupClientReplyValue(void *o); void getClientsMaxBuffers(unsigned long *longest_output_list, diff --git a/src/t_hash.c b/src/t_hash.c index 414fb1b..40927a8 100644 --- a/src/t_hash.c +++ b/src/t_hash.c @@ -1,754 +1,754 @@ -/* - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "redis.h" -#include - -/*----------------------------------------------------------------------------- - * Hash type API - *----------------------------------------------------------------------------*/ - -/* Check the length of a number of objects to see if we need to convert a - * ziplist to a real hash. Note that we only check string encoded objects - * as their string length can be queried in constant time. */ -void hashTypeTryConversion(robj *o, robj **argv, int start, int end) { - int i; - - if (o->encoding != REDIS_ENCODING_ZIPLIST) return; - - for (i = start; i <= end; i++) { - if (argv[i]->encoding == REDIS_ENCODING_RAW && - sdslen(argv[i]->ptr) > server.hash_max_ziplist_value) - { - hashTypeConvert(o, REDIS_ENCODING_HT); - break; - } - } -} - -/* Encode given objects in-place when the hash uses a dict. */ -void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) { - if (subject->encoding == REDIS_ENCODING_HT) { - if (o1) *o1 = tryObjectEncoding(*o1); - if (o2) *o2 = tryObjectEncoding(*o2); - } -} - -/* Get the value from a ziplist encoded hash, identified by field. - * Returns -1 when the field cannot be found. */ -int hashTypeGetFromZiplist(robj *o, robj *field, - unsigned char **vstr, - unsigned int *vlen, - long long *vll) -{ - unsigned char *zl, *fptr = NULL, *vptr = NULL; - int ret; - - redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); - - field = getDecodedObject(field); - - zl = o->ptr; - fptr = ziplistIndex(zl, ZIPLIST_HEAD); - if (fptr != NULL) { - fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); - if (fptr != NULL) { - /* Grab pointer to the value (fptr points to the field) */ - vptr = ziplistNext(zl, fptr); - redisAssert(vptr != NULL); - } - } - - decrRefCount(field); - - if (vptr != NULL) { - ret = ziplistGet(vptr, vstr, vlen, vll); - redisAssert(ret); - return 0; - } - - return -1; -} - -/* Get the value from a hash table encoded hash, identified by field. - * Returns -1 when the field cannot be found. */ -int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) { - dictEntry *de; - - redisAssert(o->encoding == REDIS_ENCODING_HT); - - de = dictFind(o->ptr, field); - if (de == NULL) return -1; - *value = dictGetVal(de); - return 0; -} - -/* Higher level function of hashTypeGet*() that always returns a Redis - * object (either new or with refcount incremented), so that the caller - * can retain a reference or call decrRefCount after the usage. - * - * The lower level function can prevent copy on write so it is - * the preferred way of doing read operations. */ -robj *hashTypeGetObject(robj *o, robj *field) { - robj *value = NULL; - - if (o->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *vstr = NULL; - unsigned int vlen = UINT_MAX; - long long vll = LLONG_MAX; - - if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) { - if (vstr) { - value = createStringObject((char*)vstr, vlen); - } else { - value = createStringObjectFromLongLong(vll); - } - } - - } else if (o->encoding == REDIS_ENCODING_HT) { - robj *aux; - - if (hashTypeGetFromHashTable(o, field, &aux) == 0) { - incrRefCount(aux); - value = aux; - } - } else { - redisPanic("Unknown hash encoding"); - } - return value; -} - -/* Test if the specified field exists in the given hash. Returns 1 if the field - * exists, and 0 when it doesn't. */ -int hashTypeExists(robj *o, robj *field) { - if (o->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *vstr = NULL; - unsigned int vlen = UINT_MAX; - long long vll = LLONG_MAX; - - if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1; - } else if (o->encoding == REDIS_ENCODING_HT) { - robj *aux; - - if (hashTypeGetFromHashTable(o, field, &aux) == 0) return 1; - } else { - redisPanic("Unknown hash encoding"); - } - return 0; -} - -/* Add an element, discard the old if the key already exists. - * Return 0 on insert and 1 on update. - * This function will take care of incrementing the reference count of the - * retained fields and value objects. */ -int hashTypeSet(robj *o, robj *field, robj *value) { - int update = 0; - - if (o->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *zl, *fptr, *vptr; - - field = getDecodedObject(field); - value = getDecodedObject(value); - - zl = o->ptr; - fptr = ziplistIndex(zl, ZIPLIST_HEAD); - if (fptr != NULL) { - fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); - if (fptr != NULL) { - /* Grab pointer to the value (fptr points to the field) */ - vptr = ziplistNext(zl, fptr); - redisAssert(vptr != NULL); - update = 1; - - /* Delete value */ - zl = ziplistDelete(zl, &vptr); - - /* Insert new value */ - zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr)); - } - } - - if (!update) { - /* Push new field/value pair onto the tail of the ziplist */ - zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL); - zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL); - } - o->ptr = zl; - decrRefCount(field); - decrRefCount(value); - - /* Check if the ziplist needs to be converted to a hash table */ - if (hashTypeLength(o) > server.hash_max_ziplist_entries) - hashTypeConvert(o, REDIS_ENCODING_HT); - } else if (o->encoding == REDIS_ENCODING_HT) { - if (dictReplace(o->ptr, field, value)) { /* Insert */ - incrRefCount(field); - } else { /* Update */ - update = 1; - } - incrRefCount(value); - } else { - redisPanic("Unknown hash encoding"); - } - return update; -} - -/* Delete an element from a hash. - * Return 1 on deleted and 0 on not found. */ -int hashTypeDelete(robj *o, robj *field) { - int deleted = 0; - - if (o->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *zl, *fptr; - - field = getDecodedObject(field); - - zl = o->ptr; - fptr = ziplistIndex(zl, ZIPLIST_HEAD); - if (fptr != NULL) { - fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); - if (fptr != NULL) { - zl = ziplistDelete(zl,&fptr); - zl = ziplistDelete(zl,&fptr); - o->ptr = zl; - deleted = 1; - } - } - - decrRefCount(field); - - } else if (o->encoding == REDIS_ENCODING_HT) { - if (dictDelete((dict*)o->ptr, field) == REDIS_OK) { - deleted = 1; - - /* Always check if the dictionary needs a resize after a delete. */ - if (htNeedsResize(o->ptr)) dictResize(o->ptr); - } - - } else { - redisPanic("Unknown hash encoding"); - } - - return deleted; -} - -/* Return the number of elements in a hash. */ -unsigned long hashTypeLength(robj *o) { - unsigned long length = ULONG_MAX; - - if (o->encoding == REDIS_ENCODING_ZIPLIST) { - length = ziplistLen(o->ptr) / 2; - } else if (o->encoding == REDIS_ENCODING_HT) { - length = dictSize((dict*)o->ptr); - } else { - redisPanic("Unknown hash encoding"); - } - - return length; -} - -hashTypeIterator *hashTypeInitIterator(robj *subject) { - hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator)); - hi->subject = subject; - hi->encoding = subject->encoding; - - if (hi->encoding == REDIS_ENCODING_ZIPLIST) { - hi->fptr = NULL; - hi->vptr = NULL; - } else if (hi->encoding == REDIS_ENCODING_HT) { - hi->di = dictGetIterator(subject->ptr); - } else { - redisPanic("Unknown hash encoding"); - } - - return hi; -} - -void hashTypeReleaseIterator(hashTypeIterator *hi) { - if (hi->encoding == REDIS_ENCODING_HT) { - dictReleaseIterator(hi->di); - } - - zfree(hi); -} - -/* Move to the next entry in the hash. Return REDIS_OK when the next entry - * could be found and REDIS_ERR when the iterator reaches the end. */ -int hashTypeNext(hashTypeIterator *hi) { - if (hi->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *zl; - unsigned char *fptr, *vptr; - - zl = hi->subject->ptr; - fptr = hi->fptr; - vptr = hi->vptr; - - if (fptr == NULL) { - /* Initialize cursor */ - redisAssert(vptr == NULL); - fptr = ziplistIndex(zl, 0); - } else { - /* Advance cursor */ - redisAssert(vptr != NULL); - fptr = ziplistNext(zl, vptr); - } - if (fptr == NULL) return REDIS_ERR; - - /* Grab pointer to the value (fptr points to the field) */ - vptr = ziplistNext(zl, fptr); - redisAssert(vptr != NULL); - - /* fptr, vptr now point to the first or next pair */ - hi->fptr = fptr; - hi->vptr = vptr; - } else if (hi->encoding == REDIS_ENCODING_HT) { - if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR; - } else { - redisPanic("Unknown hash encoding"); - } - return REDIS_OK; -} - -/* Get the field or value at iterator cursor, for an iterator on a hash value - * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */ -void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, - unsigned char **vstr, - unsigned int *vlen, - long long *vll) -{ - int ret; - - redisAssert(hi->encoding == REDIS_ENCODING_ZIPLIST); - - if (what & REDIS_HASH_KEY) { - ret = ziplistGet(hi->fptr, vstr, vlen, vll); - redisAssert(ret); - } else { - ret = ziplistGet(hi->vptr, vstr, vlen, vll); - redisAssert(ret); - } -} - -/* Get the field or value at iterator cursor, for an iterator on a hash value - * encoded as a ziplist. Prototype is similar to `hashTypeGetFromHashTable`. */ -void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst) { - redisAssert(hi->encoding == REDIS_ENCODING_HT); - - if (what & REDIS_HASH_KEY) { - *dst = dictGetKey(hi->de); - } else { - *dst = dictGetVal(hi->de); - } -} - -/* A non copy-on-write friendly but higher level version of hashTypeCurrent*() - * that returns an object with incremented refcount (or a new object). It is up - * to the caller to decrRefCount() the object if no reference is retained. */ -robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) { - robj *dst; - - if (hi->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *vstr = NULL; - unsigned int vlen = UINT_MAX; - long long vll = LLONG_MAX; - - hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); - if (vstr) { - dst = createStringObject((char*)vstr, vlen); - } else { - dst = createStringObjectFromLongLong(vll); - } - - } else if (hi->encoding == REDIS_ENCODING_HT) { - hashTypeCurrentFromHashTable(hi, what, &dst); - incrRefCount(dst); - - } else { - redisPanic("Unknown hash encoding"); - } - - return dst; -} - -robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) { - robj *o = lookupKeyWrite(c->db,key); - if (o == NULL) { - o = createHashObject(); - dbAdd(c->db,key,o); - } else { - if (o->type != REDIS_HASH) { - addReply(c,shared.wrongtypeerr); - return NULL; - } - } - return o; -} - -void hashTypeConvertZiplist(robj *o, int enc) { - redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); - - if (enc == REDIS_ENCODING_ZIPLIST) { - /* Nothing to do... */ - - } else if (enc == REDIS_ENCODING_HT) { - hashTypeIterator *hi; - dict *dict; - int ret; - - hi = hashTypeInitIterator(o); - dict = dictCreate(&hashDictType, NULL); - - while (hashTypeNext(hi) != REDIS_ERR) { - robj *field, *value; - - field = hashTypeCurrentObject(hi, REDIS_HASH_KEY); - field = tryObjectEncoding(field); - value = hashTypeCurrentObject(hi, REDIS_HASH_VALUE); - value = tryObjectEncoding(value); - ret = dictAdd(dict, field, value); - if (ret != DICT_OK) { - redisLogHexDump(REDIS_WARNING,"ziplist with dup elements dump", - o->ptr,ziplistBlobLen(o->ptr)); - redisAssert(ret == DICT_OK); - } - } - - hashTypeReleaseIterator(hi); - zfree(o->ptr); - - o->encoding = REDIS_ENCODING_HT; - o->ptr = dict; - - } else { - redisPanic("Unknown hash encoding"); - } -} - -void hashTypeConvert(robj *o, int enc) { - if (o->encoding == REDIS_ENCODING_ZIPLIST) { - hashTypeConvertZiplist(o, enc); - } else if (o->encoding == REDIS_ENCODING_HT) { - redisPanic("Not implemented"); - } else { - redisPanic("Unknown hash encoding"); - } -} - -/*----------------------------------------------------------------------------- - * Hash type commands - *----------------------------------------------------------------------------*/ - -void hsetCommand(redisClient *c) { - int update; - robj *o; - - if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; - hashTypeTryConversion(o,c->argv,2,3); - hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); - update = hashTypeSet(o,c->argv[2],c->argv[3]); - addReply(c, update ? shared.czero : shared.cone); - signalModifiedKey(c->db,c->argv[1]); - server.dirty++; -} - -void hsetnxCommand(redisClient *c) { - robj *o; - if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; - hashTypeTryConversion(o,c->argv,2,3); - - if (hashTypeExists(o, c->argv[2])) { - addReply(c, shared.czero); - } else { - hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); - hashTypeSet(o,c->argv[2],c->argv[3]); - addReply(c, shared.cone); - signalModifiedKey(c->db,c->argv[1]); - server.dirty++; - } -} - -void hmsetCommand(redisClient *c) { - int i; - robj *o; - - if ((c->argc % 2) == 1) { - addReplyError(c,"wrong number of arguments for HMSET"); - return; - } - - if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; - hashTypeTryConversion(o,c->argv,2,c->argc-1); - for (i = 2; i < c->argc; i += 2) { - hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); - hashTypeSet(o,c->argv[i],c->argv[i+1]); - } - addReply(c, shared.ok); - signalModifiedKey(c->db,c->argv[1]); - server.dirty++; -} - -void hincrbyCommand(redisClient *c) { - long long value, incr, oldvalue; - robj *o, *current, *new; - - if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; - if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; - if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { - if (getLongLongFromObjectOrReply(c,current,&value, - "hash value is not an integer") != REDIS_OK) { - decrRefCount(current); - return; - } - decrRefCount(current); - } else { - value = 0; - } - - oldvalue = value; - if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || - (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { - addReplyError(c,"increment or decrement would overflow"); - return; - } - value += incr; - new = createStringObjectFromLongLong(value); - hashTypeTryObjectEncoding(o,&c->argv[2],NULL); - hashTypeSet(o,c->argv[2],new); - decrRefCount(new); - addReplyLongLong(c,value); - signalModifiedKey(c->db,c->argv[1]); - server.dirty++; -} - -void hincrbyfloatCommand(redisClient *c) { - double long value, incr; - robj *o, *current, *new, *aux; - - if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; - if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; - if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { - if (getLongDoubleFromObjectOrReply(c,current,&value, - "hash value is not a valid float") != REDIS_OK) { - decrRefCount(current); - return; - } - decrRefCount(current); - } else { - value = 0; - } - - value += incr; - new = createStringObjectFromLongDouble(value); - hashTypeTryObjectEncoding(o,&c->argv[2],NULL); - hashTypeSet(o,c->argv[2],new); - addReplyBulk(c,new); - signalModifiedKey(c->db,c->argv[1]); - server.dirty++; - - /* Always replicate HINCRBYFLOAT as an HSET command with the final value - * in order to make sure that differences in float pricision or formatting - * will not create differences in replicas or after an AOF restart. */ - aux = createStringObject("HSET",4); - rewriteClientCommandArgument(c,0,aux); - decrRefCount(aux); - rewriteClientCommandArgument(c,3,new); - decrRefCount(new); -} - -static void addHashFieldToReply(redisClient *c, robj *o, robj *field) { - int ret; - - if (o == NULL) { - addReply(c, shared.nullbulk); - return; - } - - if (o->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *vstr = NULL; - unsigned int vlen = UINT_MAX; - long long vll = LLONG_MAX; - - ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll); - if (ret < 0) { - addReply(c, shared.nullbulk); - } else { - if (vstr) { - addReplyBulkCBuffer(c, vstr, vlen); - } else { - addReplyBulkLongLong(c, vll); - } - } - - } else if (o->encoding == REDIS_ENCODING_HT) { - robj *value; - - ret = hashTypeGetFromHashTable(o, field, &value); - if (ret < 0) { - addReply(c, shared.nullbulk); - } else { - addReplyBulk(c, value); - } - - } else { - redisPanic("Unknown hash encoding"); - } -} - -void hgetCommand(redisClient *c) { - robj *o; - - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || - checkType(c,o,REDIS_HASH)) return; - - addHashFieldToReply(c, o, c->argv[2]); -} - -void hmgetCommand(redisClient *c) { - robj *o; - int i; - - /* Don't abort when the key cannot be found. Non-existing keys are empty - * hashes, where HMGET should respond with a series of null bulks. */ - o = lookupKeyRead(c->db, c->argv[1]); - if (o != NULL && o->type != REDIS_HASH) { - addReply(c, shared.wrongtypeerr); - return; - } - - addReplyMultiBulkLen(c, c->argc-2); - for (i = 2; i < c->argc; i++) { - addHashFieldToReply(c, o, c->argv[i]); - } -} - -void hdelCommand(redisClient *c) { - robj *o; - int j, deleted = 0; - - if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || - checkType(c,o,REDIS_HASH)) return; - - for (j = 2; j < c->argc; j++) { - if (hashTypeDelete(o,c->argv[j])) { - deleted++; - if (hashTypeLength(o) == 0) { - dbDelete(c->db,c->argv[1]); - break; - } - } - } - if (deleted) { - signalModifiedKey(c->db,c->argv[1]); - server.dirty += deleted; - } - addReplyLongLong(c,deleted); -} - -void hlenCommand(redisClient *c) { - robj *o; - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || - checkType(c,o,REDIS_HASH)) return; - - addReplyLongLong(c,hashTypeLength(o)); -} - -static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) { - if (hi->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *vstr = NULL; - unsigned int vlen = UINT_MAX; - long long vll = LLONG_MAX; - - hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); - if (vstr) { - addReplyBulkCBuffer(c, vstr, vlen); - } else { - addReplyBulkLongLong(c, vll); - } - - } else if (hi->encoding == REDIS_ENCODING_HT) { - robj *value; - - hashTypeCurrentFromHashTable(hi, what, &value); - addReplyBulk(c, value); - - } else { - redisPanic("Unknown hash encoding"); - } -} - -void genericHgetallCommand(redisClient *c, int flags) { - robj *o; - hashTypeIterator *hi; - int multiplier = 0; - int length, count = 0; - - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL - || checkType(c,o,REDIS_HASH)) return; - - if (flags & REDIS_HASH_KEY) multiplier++; - if (flags & REDIS_HASH_VALUE) multiplier++; - - length = hashTypeLength(o) * multiplier; - addReplyMultiBulkLen(c, length); - - hi = hashTypeInitIterator(o); - while (hashTypeNext(hi) != REDIS_ERR) { - if (flags & REDIS_HASH_KEY) { - addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY); - count++; - } - if (flags & REDIS_HASH_VALUE) { - addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE); - count++; - } - } - - hashTypeReleaseIterator(hi); - redisAssert(count == length); -} - -void hkeysCommand(redisClient *c) { - genericHgetallCommand(c,REDIS_HASH_KEY); -} - -void hvalsCommand(redisClient *c) { - genericHgetallCommand(c,REDIS_HASH_VALUE); -} - -void hgetallCommand(redisClient *c) { - genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE); -} - -void hexistsCommand(redisClient *c) { - robj *o; - if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || - checkType(c,o,REDIS_HASH)) return; - - addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero); -} +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "redis.h" +#include + +/*----------------------------------------------------------------------------- + * Hash type API + *----------------------------------------------------------------------------*/ + +/* Check the length of a number of objects to see if we need to convert a + * ziplist to a real hash. Note that we only check string encoded objects + * as their string length can be queried in constant time. */ +void hashTypeTryConversion(robj *o, robj **argv, int start, int end) { + int i; + + if (o->encoding != REDIS_ENCODING_ZIPLIST) return; + + for (i = start; i <= end; i++) { + if (argv[i]->encoding == REDIS_ENCODING_RAW && + sdslen(argv[i]->ptr) > server.hash_max_ziplist_value) + { + hashTypeConvert(o, REDIS_ENCODING_HT); + break; + } + } +} + +/* Encode given objects in-place when the hash uses a dict. */ +void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) { + if (subject->encoding == REDIS_ENCODING_HT) { + if (o1) *o1 = tryObjectEncoding(*o1); + if (o2) *o2 = tryObjectEncoding(*o2); + } +} + +/* Get the value from a ziplist encoded hash, identified by field. + * Returns -1 when the field cannot be found. */ +int hashTypeGetFromZiplist(robj *o, robj *field, + unsigned char **vstr, + unsigned int *vlen, + long long *vll) +{ + unsigned char *zl, *fptr = NULL, *vptr = NULL; + int ret; + + redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); + + field = getDecodedObject(field); + + zl = o->ptr; + fptr = ziplistIndex(zl, ZIPLIST_HEAD); + if (fptr != NULL) { + fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); + if (fptr != NULL) { + /* Grab pointer to the value (fptr points to the field) */ + vptr = ziplistNext(zl, fptr); + redisAssert(vptr != NULL); + } + } + + decrRefCount(field); + + if (vptr != NULL) { + ret = ziplistGet(vptr, vstr, vlen, vll); + redisAssert(ret); + return 0; + } + + return -1; +} + +/* Get the value from a hash table encoded hash, identified by field. + * Returns -1 when the field cannot be found. */ +int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) { + dictEntry *de; + + redisAssert(o->encoding == REDIS_ENCODING_HT); + + de = dictFind(o->ptr, field); + if (de == NULL) return -1; + *value = dictGetVal(de); + return 0; +} + +/* Higher level function of hashTypeGet*() that always returns a Redis + * object (either new or with refcount incremented), so that the caller + * can retain a reference or call decrRefCount after the usage. + * + * The lower level function can prevent copy on write so it is + * the preferred way of doing read operations. */ +robj *hashTypeGetObject(robj *o, robj *field) { + robj *value = NULL; + + if (o->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) { + if (vstr) { + value = createStringObject((char*)vstr, vlen); + } else { + value = createStringObjectFromLongLong(vll); + } + } + + } else if (o->encoding == REDIS_ENCODING_HT) { + robj *aux; + + if (hashTypeGetFromHashTable(o, field, &aux) == 0) { + incrRefCount(aux); + value = aux; + } + } else { + redisPanic("Unknown hash encoding"); + } + return value; +} + +/* Test if the specified field exists in the given hash. Returns 1 if the field + * exists, and 0 when it doesn't. */ +int hashTypeExists(robj *o, robj *field) { + if (o->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1; + } else if (o->encoding == REDIS_ENCODING_HT) { + robj *aux; + + if (hashTypeGetFromHashTable(o, field, &aux) == 0) return 1; + } else { + redisPanic("Unknown hash encoding"); + } + return 0; +} + +/* Add an element, discard the old if the key already exists. + * Return 0 on insert and 1 on update. + * This function will take care of incrementing the reference count of the + * retained fields and value objects. */ +int hashTypeSet(robj *o, robj *field, robj *value) { + int update = 0; + + if (o->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *zl, *fptr, *vptr; + + field = getDecodedObject(field); + value = getDecodedObject(value); + + zl = o->ptr; + fptr = ziplistIndex(zl, ZIPLIST_HEAD); + if (fptr != NULL) { + fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); + if (fptr != NULL) { + /* Grab pointer to the value (fptr points to the field) */ + vptr = ziplistNext(zl, fptr); + redisAssert(vptr != NULL); + update = 1; + + /* Delete value */ + zl = ziplistDelete(zl, &vptr); + + /* Insert new value */ + zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr)); + } + } + + if (!update) { + /* Push new field/value pair onto the tail of the ziplist */ + zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL); + zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL); + } + o->ptr = zl; + decrRefCount(field); + decrRefCount(value); + + /* Check if the ziplist needs to be converted to a hash table */ + if (hashTypeLength(o) > server.hash_max_ziplist_entries) + hashTypeConvert(o, REDIS_ENCODING_HT); + } else if (o->encoding == REDIS_ENCODING_HT) { + if (dictReplace(o->ptr, field, value)) { /* Insert */ + incrRefCount(field); + } else { /* Update */ + update = 1; + } + incrRefCount(value); + } else { + redisPanic("Unknown hash encoding"); + } + return update; +} + +/* Delete an element from a hash. + * Return 1 on deleted and 0 on not found. */ +int hashTypeDelete(robj *o, robj *field) { + int deleted = 0; + + if (o->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *zl, *fptr; + + field = getDecodedObject(field); + + zl = o->ptr; + fptr = ziplistIndex(zl, ZIPLIST_HEAD); + if (fptr != NULL) { + fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1); + if (fptr != NULL) { + zl = ziplistDelete(zl,&fptr); + zl = ziplistDelete(zl,&fptr); + o->ptr = zl; + deleted = 1; + } + } + + decrRefCount(field); + + } else if (o->encoding == REDIS_ENCODING_HT) { + if (dictDelete((dict*)o->ptr, field) == REDIS_OK) { + deleted = 1; + + /* Always check if the dictionary needs a resize after a delete. */ + if (htNeedsResize(o->ptr)) dictResize(o->ptr); + } + + } else { + redisPanic("Unknown hash encoding"); + } + + return deleted; +} + +/* Return the number of elements in a hash. */ +unsigned long hashTypeLength(robj *o) { + unsigned long length = ULONG_MAX; + + if (o->encoding == REDIS_ENCODING_ZIPLIST) { + length = ziplistLen(o->ptr) / 2; + } else if (o->encoding == REDIS_ENCODING_HT) { + length = dictSize((dict*)o->ptr); + } else { + redisPanic("Unknown hash encoding"); + } + + return length; +} + +hashTypeIterator *hashTypeInitIterator(robj *subject) { + hashTypeIterator *hi = zmalloc(sizeof(hashTypeIterator)); + hi->subject = subject; + hi->encoding = subject->encoding; + + if (hi->encoding == REDIS_ENCODING_ZIPLIST) { + hi->fptr = NULL; + hi->vptr = NULL; + } else if (hi->encoding == REDIS_ENCODING_HT) { + hi->di = dictGetIterator(subject->ptr); + } else { + redisPanic("Unknown hash encoding"); + } + + return hi; +} + +void hashTypeReleaseIterator(hashTypeIterator *hi) { + if (hi->encoding == REDIS_ENCODING_HT) { + dictReleaseIterator(hi->di); + } + + zfree(hi); +} + +/* Move to the next entry in the hash. Return REDIS_OK when the next entry + * could be found and REDIS_ERR when the iterator reaches the end. */ +int hashTypeNext(hashTypeIterator *hi) { + if (hi->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *zl; + unsigned char *fptr, *vptr; + + zl = hi->subject->ptr; + fptr = hi->fptr; + vptr = hi->vptr; + + if (fptr == NULL) { + /* Initialize cursor */ + redisAssert(vptr == NULL); + fptr = ziplistIndex(zl, 0); + } else { + /* Advance cursor */ + redisAssert(vptr != NULL); + fptr = ziplistNext(zl, vptr); + } + if (fptr == NULL) return REDIS_ERR; + + /* Grab pointer to the value (fptr points to the field) */ + vptr = ziplistNext(zl, fptr); + redisAssert(vptr != NULL); + + /* fptr, vptr now point to the first or next pair */ + hi->fptr = fptr; + hi->vptr = vptr; + } else if (hi->encoding == REDIS_ENCODING_HT) { + if ((hi->de = dictNext(hi->di)) == NULL) return REDIS_ERR; + } else { + redisPanic("Unknown hash encoding"); + } + return REDIS_OK; +} + +/* Get the field or value at iterator cursor, for an iterator on a hash value + * encoded as a ziplist. Prototype is similar to `hashTypeGetFromZiplist`. */ +void hashTypeCurrentFromZiplist(hashTypeIterator *hi, int what, + unsigned char **vstr, + unsigned int *vlen, + long long *vll) +{ + int ret; + + redisAssert(hi->encoding == REDIS_ENCODING_ZIPLIST); + + if (what & REDIS_HASH_KEY) { + ret = ziplistGet(hi->fptr, vstr, vlen, vll); + redisAssert(ret); + } else { + ret = ziplistGet(hi->vptr, vstr, vlen, vll); + redisAssert(ret); + } +} + +/* Get the field or value at iterator cursor, for an iterator on a hash value + * encoded as a ziplist. Prototype is similar to `hashTypeGetFromHashTable`. */ +void hashTypeCurrentFromHashTable(hashTypeIterator *hi, int what, robj **dst) { + redisAssert(hi->encoding == REDIS_ENCODING_HT); + + if (what & REDIS_HASH_KEY) { + *dst = dictGetKey(hi->de); + } else { + *dst = dictGetVal(hi->de); + } +} + +/* A non copy-on-write friendly but higher level version of hashTypeCurrent*() + * that returns an object with incremented refcount (or a new object). It is up + * to the caller to decrRefCount() the object if no reference is retained. */ +robj *hashTypeCurrentObject(hashTypeIterator *hi, int what) { + robj *dst; + + if (hi->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); + if (vstr) { + dst = createStringObject((char*)vstr, vlen); + } else { + dst = createStringObjectFromLongLong(vll); + } + + } else if (hi->encoding == REDIS_ENCODING_HT) { + hashTypeCurrentFromHashTable(hi, what, &dst); + incrRefCount(dst); + + } else { + redisPanic("Unknown hash encoding"); + } + + return dst; +} + +robj *hashTypeLookupWriteOrCreate(redisClient *c, robj *key) { + robj *o = lookupKeyWrite(c->db,key); + if (o == NULL) { + o = createHashObject(); + dbAdd(c->db,key,o); + } else { + if (o->type != REDIS_HASH) { + addReply(c,shared.wrongtypeerr); + return NULL; + } + } + return o; +} + +void hashTypeConvertZiplist(robj *o, int enc) { + redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST); + + if (enc == REDIS_ENCODING_ZIPLIST) { + /* Nothing to do... */ + + } else if (enc == REDIS_ENCODING_HT) { + hashTypeIterator *hi; + dict *dict; + int ret; + + hi = hashTypeInitIterator(o); + dict = dictCreate(&hashDictType, NULL); + + while (hashTypeNext(hi) != REDIS_ERR) { + robj *field, *value; + + field = hashTypeCurrentObject(hi, REDIS_HASH_KEY); + field = tryObjectEncoding(field); + value = hashTypeCurrentObject(hi, REDIS_HASH_VALUE); + value = tryObjectEncoding(value); + ret = dictAdd(dict, field, value); + if (ret != DICT_OK) { + redisLogHexDump(REDIS_WARNING,"ziplist with dup elements dump", + o->ptr,ziplistBlobLen(o->ptr)); + redisAssert(ret == DICT_OK); + } + } + + hashTypeReleaseIterator(hi); + zfree(o->ptr); + + o->encoding = REDIS_ENCODING_HT; + o->ptr = dict; + + } else { + redisPanic("Unknown hash encoding"); + } +} + +void hashTypeConvert(robj *o, int enc) { + if (o->encoding == REDIS_ENCODING_ZIPLIST) { + hashTypeConvertZiplist(o, enc); + } else if (o->encoding == REDIS_ENCODING_HT) { + redisPanic("Not implemented"); + } else { + redisPanic("Unknown hash encoding"); + } +} + +/*----------------------------------------------------------------------------- + * Hash type commands + *----------------------------------------------------------------------------*/ + +void hsetCommand(redisClient *c) { + int update; + robj *o; + + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + hashTypeTryConversion(o,c->argv,2,3); + hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); + update = hashTypeSet(o,c->argv[2],c->argv[3]); + addReply(c, update ? shared.czero : shared.cone); + signalModifiedKey(c->db,c->argv[1]); + server.dirty++; +} + +void hsetnxCommand(redisClient *c) { + robj *o; + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + hashTypeTryConversion(o,c->argv,2,3); + + if (hashTypeExists(o, c->argv[2])) { + addReply(c, shared.czero); + } else { + hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); + hashTypeSet(o,c->argv[2],c->argv[3]); + addReply(c, shared.cone); + signalModifiedKey(c->db,c->argv[1]); + server.dirty++; + } +} + +void hmsetCommand(redisClient *c) { + int i; + robj *o; + + if ((c->argc % 2) == 1) { + addReplyError(c,"wrong number of arguments for HMSET"); + return; + } + + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + hashTypeTryConversion(o,c->argv,2,c->argc-1); + for (i = 2; i < c->argc; i += 2) { + hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); + hashTypeSet(o,c->argv[i],c->argv[i+1]); + } + addReply(c, shared.ok); + signalModifiedKey(c->db,c->argv[1]); + server.dirty++; +} + +void hincrbyCommand(redisClient *c) { + long long value, incr, oldvalue; + robj *o, *current, *new; + + if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { + if (getLongLongFromObjectOrReply(c,current,&value, + "hash value is not an integer") != REDIS_OK) { + decrRefCount(current); + return; + } + decrRefCount(current); + } else { + value = 0; + } + + oldvalue = value; + if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) || + (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) { + addReplyError(c,"increment or decrement would overflow"); + return; + } + value += incr; + new = createStringObjectFromLongLong(value); + hashTypeTryObjectEncoding(o,&c->argv[2],NULL); + hashTypeSet(o,c->argv[2],new); + decrRefCount(new); + addReplyLongLong(c,value); + signalModifiedKey(c->db,c->argv[1]); + server.dirty++; +} + +void hincrbyfloatCommand(redisClient *c) { + double long value, incr; + robj *o, *current, *new, *aux; + + if (getLongDoubleFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return; + if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) { + if (getLongDoubleFromObjectOrReply(c,current,&value, + "hash value is not a valid float") != REDIS_OK) { + decrRefCount(current); + return; + } + decrRefCount(current); + } else { + value = 0; + } + + value += incr; + new = createStringObjectFromLongDouble(value); + hashTypeTryObjectEncoding(o,&c->argv[2],NULL); + hashTypeSet(o,c->argv[2],new); + addReplyBulk(c,new); + signalModifiedKey(c->db,c->argv[1]); + server.dirty++; + + /* Always replicate HINCRBYFLOAT as an HSET command with the final value + * in order to make sure that differences in float pricision or formatting + * will not create differences in replicas or after an AOF restart. */ + aux = createStringObject("HSET",4); + rewriteClientCommandArgument(c,0,aux); + decrRefCount(aux); + rewriteClientCommandArgument(c,3,new); + decrRefCount(new); +} + +void addHashFieldToReply(redisClient *c, robj *o, robj *field) { + int ret; + + if (o == NULL) { + addReply(c, shared.nullbulk); + return; + } + + if (o->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll); + if (ret < 0) { + addReply(c, shared.nullbulk); + } else { + if (vstr) { + addReplyBulkCBuffer(c, vstr, vlen); + } else { + addReplyBulkLongLong(c, vll); + } + } + + } else if (o->encoding == REDIS_ENCODING_HT) { + robj *value; + + ret = hashTypeGetFromHashTable(o, field, &value); + if (ret < 0) { + addReply(c, shared.nullbulk); + } else { + addReplyBulk(c, value); + } + + } else { + redisPanic("Unknown hash encoding"); + } +} + +void hgetCommand(redisClient *c) { + robj *o; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || + checkType(c,o,REDIS_HASH)) return; + + addHashFieldToReply(c, o, c->argv[2]); +} + +void hmgetCommand(redisClient *c) { + robj *o; + int i; + + /* Don't abort when the key cannot be found. Non-existing keys are empty + * hashes, where HMGET should respond with a series of null bulks. */ + o = lookupKeyRead(c->db, c->argv[1]); + if (o != NULL && o->type != REDIS_HASH) { + addReply(c, shared.wrongtypeerr); + return; + } + + addReplyMultiBulkLen(c, c->argc-2); + for (i = 2; i < c->argc; i++) { + addHashFieldToReply(c, o, c->argv[i]); + } +} + +void hdelCommand(redisClient *c) { + robj *o; + int j, deleted = 0; + + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_HASH)) return; + + for (j = 2; j < c->argc; j++) { + if (hashTypeDelete(o,c->argv[j])) { + deleted++; + if (hashTypeLength(o) == 0) { + dbDelete(c->db,c->argv[1]); + break; + } + } + } + if (deleted) { + signalModifiedKey(c->db,c->argv[1]); + server.dirty += deleted; + } + addReplyLongLong(c,deleted); +} + +void hlenCommand(redisClient *c) { + robj *o; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_HASH)) return; + + addReplyLongLong(c,hashTypeLength(o)); +} + +static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) { + if (hi->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); + if (vstr) { + addReplyBulkCBuffer(c, vstr, vlen); + } else { + addReplyBulkLongLong(c, vll); + } + + } else if (hi->encoding == REDIS_ENCODING_HT) { + robj *value; + + hashTypeCurrentFromHashTable(hi, what, &value); + addReplyBulk(c, value); + + } else { + redisPanic("Unknown hash encoding"); + } +} + +void genericHgetallCommand(redisClient *c, int flags) { + robj *o; + hashTypeIterator *hi; + int multiplier = 0; + int length, count = 0; + + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL + || checkType(c,o,REDIS_HASH)) return; + + if (flags & REDIS_HASH_KEY) multiplier++; + if (flags & REDIS_HASH_VALUE) multiplier++; + + length = hashTypeLength(o) * multiplier; + addReplyMultiBulkLen(c, length); + + hi = hashTypeInitIterator(o); + while (hashTypeNext(hi) != REDIS_ERR) { + if (flags & REDIS_HASH_KEY) { + addHashIteratorCursorToReply(c, hi, REDIS_HASH_KEY); + count++; + } + if (flags & REDIS_HASH_VALUE) { + addHashIteratorCursorToReply(c, hi, REDIS_HASH_VALUE); + count++; + } + } + + hashTypeReleaseIterator(hi); + redisAssert(count == length); +} + +void hkeysCommand(redisClient *c) { + genericHgetallCommand(c,REDIS_HASH_KEY); +} + +void hvalsCommand(redisClient *c) { + genericHgetallCommand(c,REDIS_HASH_VALUE); +} + +void hgetallCommand(redisClient *c) { + genericHgetallCommand(c,REDIS_HASH_KEY|REDIS_HASH_VALUE); +} + +void hexistsCommand(redisClient *c) { + robj *o; + if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_HASH)) return; + + addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero); +} From 9f98496de8acdc404960811b9e91733ed65df713 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Mon, 7 Jan 2013 19:54:04 +0800 Subject: [PATCH 06/93] rl_hget rl_hdel debug --- src/ds.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/ds.c b/src/ds.c index 5718b51..1674e8a 100644 --- a/src/ds.c +++ b/src/ds.c @@ -426,14 +426,62 @@ void ds_hset(redisClient *c) void rl_hset(redisClient *c) { - ds_hset(c); + sds str; + char *key, *field, *value, *err; + leveldb_writeoptions_t *woptions; + leveldb_writebatch_t *wb; + + key = (char *)c->argv[1]->ptr; + field = (char *)c->argv[2]->ptr; + value = (char *)c->argv[3]->ptr; + + woptions = leveldb_writeoptions_create(); + wb = leveldb_writebatch_create(); + + str = sdsempty(); + str = sdscpy(str, key); + str = sdscatlen(str, "*", 1); + + leveldb_writebatch_put(wb, str, sdslen(str), "1", 1); + + sdsclear(str); + str = sdscpy(str, key); + str = sdscatlen(str, "*", 1); + str = sdscat(str, field); + leveldb_writebatch_put(wb, str, sdslen(str), value, sdslen((sds)value)); + + leveldb_write(server.ds_db, woptions, wb, &err); + + leveldb_writeoptions_destroy(woptions); + leveldb_writebatch_destroy(wb); + sdsfree(str); + hsetCommand(c); } void rl_hdel(redisClient *c) { + robj *o; + int j, deleted = 0; + + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || + checkType(c,o,REDIS_HASH)) return; + + for (j = 2; j < c->argc; j++) { + if (hashTypeDelete(o,c->argv[j])) { + deleted++; + if (hashTypeLength(o) == 0) { + dbDelete(c->db,c->argv[1]); + break; + } + } + } + if (deleted) { + signalModifiedKey(c->db,c->argv[1]); + server.dirty += deleted; + } + ds_hdel(c); - hdelCommand(c); } void ds_hgetall(redisClient *c) From 1ce4aabee2fa51c72e7429e8fe6f16c3fcbedf83 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Mon, 7 Jan 2013 20:16:25 +0800 Subject: [PATCH 07/93] debug ds_hset --- src/ds.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/ds.c b/src/ds.c index 1674e8a..5c64848 100644 --- a/src/ds.c +++ b/src/ds.c @@ -420,7 +420,7 @@ void ds_hset(redisClient *c) leveldb_writebatch_destroy(wb); sdsfree(str); - addReply(c, shared.czero); + addReply(c, shared.cone); return ; } @@ -480,7 +480,7 @@ void rl_hdel(redisClient *c) signalModifiedKey(c->db,c->argv[1]); server.dirty += deleted; } - + ds_hdel(c); } @@ -565,6 +565,30 @@ void ds_hgetall(redisClient *c) return ; } +static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) { + if (hi->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); + if (vstr) { + addReplyBulkCBuffer(c, vstr, vlen); + } else { + addReplyBulkLongLong(c, vll); + } + + } else if (hi->encoding == REDIS_ENCODING_HT) { + robj *value; + + hashTypeCurrentFromHashTable(hi, what, &value); + addReplyBulk(c, value); + + } else { + redisPanic("Unknown hash encoding"); + } +} + void ds_hdel(redisClient *c) { From 5367d9698603ad29dbac4cec2751b9e25936fa5d Mon Sep 17 00:00:00 2001 From: shenzhe Date: Mon, 7 Jan 2013 20:19:13 +0800 Subject: [PATCH 08/93] debug ds_hset --- src/ds.c | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/ds.c b/src/ds.c index 5c64848..bfe7470 100644 --- a/src/ds.c +++ b/src/ds.c @@ -565,31 +565,6 @@ void ds_hgetall(redisClient *c) return ; } -static void addHashIteratorCursorToReply(redisClient *c, hashTypeIterator *hi, int what) { - if (hi->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *vstr = NULL; - unsigned int vlen = UINT_MAX; - long long vll = LLONG_MAX; - - hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); - if (vstr) { - addReplyBulkCBuffer(c, vstr, vlen); - } else { - addReplyBulkLongLong(c, vll); - } - - } else if (hi->encoding == REDIS_ENCODING_HT) { - robj *value; - - hashTypeCurrentFromHashTable(hi, what, &value); - addReplyBulk(c, value); - - } else { - redisPanic("Unknown hash encoding"); - } -} - - void ds_hdel(redisClient *c) { const char *key; From a7f3f240dda2d3e608f55c1d8ab74790195ebce3 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Tue, 8 Jan 2013 14:20:34 +0800 Subject: [PATCH 09/93] merge new code --- Makefile | 66 ++++++++++++++++++++++++------------------------- README.markdown | 56 +++++++---------------------------------- src/ds.c | 14 +++++++---- 3 files changed, 51 insertions(+), 85 deletions(-) diff --git a/Makefile b/Makefile index 9078aca..3854b28 100644 --- a/Makefile +++ b/Makefile @@ -1,33 +1,33 @@ -# Top level makefile, the real shit is at src/Makefile - -default: all - -.DEFAULT: - cd deps/lua && $(MAKE) $@ - cd deps/hiredis && $(MAKE) $@ - cd src && $(MAKE) $@ - -init: - cd deps/gperftools-2.0 && ./configure --enable-minimal --enable-frame-pointers && make - cd deps/snappy-1.0.5 && ./configure && make - cd deps/leveldb-1.8.0 && make - cd deps/linenoise && make - cp ./deps/leveldb-1.8.0/libleveldb.so.1 /usr/local/lib - cp ./deps/snappy-1.0.5/.libs/libsnappy.so.1 /usr/local/lib - ldconfig - -install: - mkdir -p $(PREFIX)/db - mkdir -p $(PREFIX)/bin - mkdir -p $(PREFIX)/log - mkdir -p $(PREFIX)/conf - cp src/redis-server $(PREFIX)/bin - cp src/redis-cli $(PREFIX)/bin - cp src/redis-check-dump $(PREFIX)/bin - cp src/redis-sentinel $(PREFIX)/bin - cp src/redis-benchmark $(PREFIX)/bin - cp src/redis-check-aof $(PREFIX)/bin - cp redis.conf $(PREFIX)/conf - - -.PHONY: install +# Top level makefile, the real shit is at src/Makefile + +default: all + +.DEFAULT: + cd deps/lua && $(MAKE) $@ + cd deps/hiredis && $(MAKE) $@ + cd deps/linenoise && $(MAKE) + cd src && $(MAKE) $@ + +init: + cd deps/gperftools-2.0 && ./configure --enable-minimal --enable-frame-pointers && make + cd deps/snappy-1.0.5 && ./configure && make + cd deps/leveldb-1.8.0 && make + cp ./deps/leveldb-1.8.0/libleveldb.so.1 /usr/local/lib + cp ./deps/snappy-1.0.5/.libs/libsnappy.so.1 /usr/local/lib + ldconfig + +install: + mkdir -p $(PREFIX)/db + mkdir -p $(PREFIX)/bin + mkdir -p $(PREFIX)/log + mkdir -p $(PREFIX)/conf + cp src/redis-server $(PREFIX)/bin + cp src/redis-cli $(PREFIX)/bin + cp src/redis-check-dump $(PREFIX)/bin + cp src/redis-sentinel $(PREFIX)/bin + cp src/redis-benchmark $(PREFIX)/bin + cp src/redis-check-aof $(PREFIX)/bin + cp redis.conf $(PREFIX)/conf + + +.PHONY: install diff --git a/README.markdown b/README.markdown index cbce796..7dbbba9 100644 --- a/README.markdown +++ b/README.markdown @@ -1,25 +1,11 @@ -新增加字符串函数 +redis-storage manual ========= -
-ds_append
-ds_incrby
-
- -新增加类似redis的hashs功能,用法一样 -========= -
-ds_hdel
-ds_hget
-ds_hset
-ds_hmget
-ds_hmset
-ds_hincrby
-ds_hgetall
-
+[https://github.com/qiye/redis-storage/wiki/redis-storage-manual](https://github.com/qiye/redis-storage/wiki/redis-storage-manual) -redis-storage +overview ========= - - 基于最新的redis-2.6.7开发的 + - 基于最新的redis-2.6.7, leveldb开发的,实现海量、高效数据持久存储 + - 实现redis的string和hashs功能函数,完全兼容redis客户端 - 用luajit替换LUA,增强lua执行性能 - author: 七夜, shenzhe - QQ: 531020471 @@ -28,20 +14,19 @@ redis-storage - mail: lijinxing@gmail.com, shenzhe163@gmail.com - -安装 redis-storage +Install =========
-https://github.com/qiye/redis-storage 获取源码
+https://github.com/qiye/redis-storage/archive/master.zip get source code
     
 make init
 make MALLOC=tcmalloc_minimal
 
-这一步需要root权限
+need root
 make install PREFIX=/usr/local/redis
 
-修改redis配置文件 +redis.conf =========
 ds:create_if_missing 1                //if the specified database didn't exist will create a new one
@@ -55,29 +40,6 @@ ds:block_restart_interval 16
 ds:path /usr/local/redis/db/leveldb  //leveldb save path
 
-redis new cmd 用法跟redis的一样 -========= -
-ds_append
-ds_incrby
-ds_hdel
-ds_hget
-ds_hset
-ds_hmget
-ds_hmset
-ds_hincrby
-ds_hgetall    
-ds_set name qiye
-ds_get name
-ds_del name 
-ds_mset key value age 20
-ds_mget key age
-ds_del key age
-rl_set name shenzhe  //先把数据存到leveldb,再存到redis
-rl_get name          //先尝试从redis取数据,如没取到,再尝试从redis取数据
-rl_del name          //先从leveldb删除数据,再从redis删除数据
-
- cd php-hiredis/ ========= diff --git a/src/ds.c b/src/ds.c index bfe7470..3d32b56 100644 --- a/src/ds.c +++ b/src/ds.c @@ -440,7 +440,7 @@ void rl_hset(redisClient *c) str = sdsempty(); str = sdscpy(str, key); - str = sdscatlen(str, "*", 1); + str = sdscatlen(str, "*", 1);s leveldb_writebatch_put(wb, str, sdslen(str), "1", 1); @@ -503,6 +503,7 @@ void ds_hgetall(redisClient *c) str = sdsempty(); iter = leveldb_create_iterator(server.ds_db, roptions); + str = sdscpy(str, c->argv[1]->ptr); str = sdscatlen(str, "*", 1); len = sdslen(str); @@ -522,9 +523,10 @@ void ds_hgetall(redisClient *c) } sdsclear(str); - leveldb_iter_next(iter); + while(1) { + leveldb_iter_next(iter); if(!leveldb_iter_valid(iter)) break; @@ -542,9 +544,11 @@ void ds_hgetall(redisClient *c) str = sdscatlen(str, "\r\n", 2); i++; - leveldb_iter_next(iter); } + leveldb_iter_destroy(iter); + leveldb_readoptions_destroy(roptions); + if(i == 0) { addReply(c,shared.nullbulk); @@ -560,11 +564,11 @@ void ds_hgetall(redisClient *c) sdsfree(str); zfree(keyword); - leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); + return ; } + void ds_hdel(redisClient *c) { const char *key; From fce6b052ed4c2755d009bee0ea3b062a20bd8b21 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Mon, 14 Jan 2013 15:52:33 +0800 Subject: [PATCH 10/93] debug --- src/ds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ds.c b/src/ds.c index 3d32b56..d792ef6 100644 --- a/src/ds.c +++ b/src/ds.c @@ -440,7 +440,7 @@ void rl_hset(redisClient *c) str = sdsempty(); str = sdscpy(str, key); - str = sdscatlen(str, "*", 1);s + str = sdscatlen(str, "*", 1); leveldb_writebatch_put(wb, str, sdslen(str), "1", 1); From 07b241452470925840064604216258edef9a03e6 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Tue, 15 Jan 2013 16:29:42 +0800 Subject: [PATCH 11/93] merge new code --- Makefile | 2 +- README.markdown | 7 + src/ds.c | 419 ++++++++++++++++++++++++++---------------------- src/redis.c | 9 +- src/redis.h | 4 + 5 files changed, 247 insertions(+), 194 deletions(-) diff --git a/Makefile b/Makefile index 3854b28..7ce6180 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ default: all .DEFAULT: cd deps/lua && $(MAKE) $@ cd deps/hiredis && $(MAKE) $@ - cd deps/linenoise && $(MAKE) + cd deps/linenoise && $(MAKE) cd src && $(MAKE) $@ init: diff --git a/README.markdown b/README.markdown index 7dbbba9..3cb0426 100644 --- a/README.markdown +++ b/README.markdown @@ -1,3 +1,10 @@ +新增两条命令 +========= +
+- ds_exists
+- ds_hexists
+具体使用方法见 redis-storage manual
+
redis-storage manual ========= [https://github.com/qiye/redis-storage/wiki/redis-storage-manual](https://github.com/qiye/redis-storage/wiki/redis-storage-manual) diff --git a/src/ds.c b/src/ds.c index d792ef6..d1db3eb 100644 --- a/src/ds.c +++ b/src/ds.c @@ -77,19 +77,167 @@ void ds_init() leveldb_free(err); exit(1); } + + server.woptions = leveldb_writeoptions_create(); + server.roptions = leveldb_readoptions_create(); + leveldb_readoptions_set_verify_checksums(server.roptions, 0); + leveldb_readoptions_set_fill_cache(server.roptions, 1); + + leveldb_writeoptions_set_sync(server.woptions, 0); } +void ds_exists(redisClient *c) +{ + int i; + char *err; + leveldb_iterator_t *iter; + + iter = leveldb_create_iterator(server.ds_db, server.roptions); + addReplyMultiBulkLen(c, c->argc-1); + for(i=1; iargc; i++) + { + leveldb_iter_seek(iter, c->argv[i]->ptr, sdslen((sds)c->argv[i]->ptr)); + if(leveldb_iter_valid(iter)) + addReplyLongLong(c, 1); + else + addReplyLongLong(c, 0); + } + + err = NULL; + leveldb_iter_get_error(iter, &err); + leveldb_iter_destroy(iter); + + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + + return ; + } + + return ; +} + +void ds_hexists(redisClient *c) +{ + int i; + sds key; + char *err; + leveldb_iterator_t *iter; + + key = sdsempty(); + iter = leveldb_create_iterator(server.ds_db, server.roptions); + addReplyMultiBulkLen(c, c->argc-2); + for(i=2; iargc; i++) + { + sdsclear(key); + key = sdscpy(key, c->argv[1]->ptr); + key = sdscatlen(key, "*", 1); + key = sdscat(key, c->argv[i]->ptr); + + leveldb_iter_seek(iter, key, sdslen(key)); + if(leveldb_iter_valid(iter)) + addReplyLongLong(c, 1); + else + addReplyLongLong(c, 0); + } + + err = NULL; + leveldb_iter_get_error(iter, &err); + leveldb_iter_destroy(iter); + sdsfree(key); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + + return ; + } + + return ; +} +/* +void ds_seek_asc(redisClient *c) +{ + sds str, header; + char *keyword = NULL; + + ulong limit; + const char *key; + size_t key_len, len, i; + + limit = 0; + limit = strtoul(c->argv[1]->ptr, NULL, 10); + + i = 0; + len = 0; + str = sdsempty(); + for(leveldb_iter_seek_to_first(server.iter); leveldb_iter_valid(server.iter); leveldb_iter_next(server.iter)) + { + key_len = 0; + key = leveldb_iter_key(server.iter, &key_len); + + if(key[(key_len-1)] == '*') + { + if(keyword != NULL) + { + zfree(keyword); + keyword = NULL; + } + len = key_len; + keyword = zmalloc(key_len+1); + strncpy(keyword, key, key_len); + str = sdscatprintf(str, "$%zu\r\n", (key_len-1)); + str = sdscatlen(str, key, key_len-1); + str = sdscatlen(str, "\r\n", 2); + i++; + } + else if((keyword != NULL) && (strncmp(keyword, key, len) == 0)) + { + continue; + } + else + { + str = sdscatprintf(str, "$%zu\r\n", key_len); + str = sdscatlen(str, key, key_len); + str = sdscatlen(str, "\r\n", 2); + i++; + } + + if(limit != 0 && i == limit) + break; + + } + + if(keyword != NULL) + { + zfree(keyword); + keyword = NULL; + } + + if(i == 0) + { + addReply(c,shared.nullbulk); + } + else + { + header = sdsempty(); + header = sdscatprintf(header, "*%zu\r\n", i); + header = sdscatlen(header, str, sdslen(str)); + + addReplySds(c, header); + sdsfree(str); + sdsfree(header); + } + return ; +} +*/ + void ds_mget(redisClient *c) { int i; size_t val_len; char *err, *value; - - leveldb_readoptions_t *roptions; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); addReplyMultiBulkLen(c,c->argc-1); for(i=1; iargc; i++) @@ -97,13 +245,12 @@ void ds_mget(redisClient *c) err = NULL; value = NULL; val_len = 0; - value = leveldb_get(server.ds_db, roptions, c->argv[i]->ptr, sdslen((sds)c->argv[i]->ptr), &val_len, &err); + value = leveldb_get(server.ds_db, server.roptions, c->argv[i]->ptr, sdslen((sds)c->argv[i]->ptr), &val_len, &err); if(err != NULL) { addReplyError(c, err); leveldb_free(err); leveldb_free(value); - leveldb_readoptions_destroy(roptions); return ; } else if(val_len > 0) @@ -118,7 +265,6 @@ void ds_mget(redisClient *c) } } - leveldb_readoptions_destroy(roptions); } void ds_get(redisClient *c) @@ -128,16 +274,10 @@ void ds_get(redisClient *c) char *key = NULL; char *value = NULL; - leveldb_readoptions_t *roptions; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); err = NULL; key = (char *)c->argv[1]->ptr; - value = leveldb_get(server.ds_db, roptions, key, sdslen((sds)key), &val_len, &err); - leveldb_readoptions_destroy(roptions); + value = leveldb_get(server.ds_db, server.roptions, key, sdslen((sds)key), &val_len, &err); if(err != NULL) { addReplyError(c, err); @@ -179,7 +319,7 @@ void ds_mset(redisClient *c) int i; char *key, *value; char *err = NULL; - leveldb_writeoptions_t *woptions; + leveldb_writebatch_t *wb; if((c->argc%2) == 0) @@ -189,7 +329,6 @@ void ds_mset(redisClient *c) } - woptions = leveldb_writeoptions_create(); wb = leveldb_writebatch_create(); for(i=1; iargc; i++) { @@ -197,8 +336,7 @@ void ds_mset(redisClient *c) value = (char *)c->argv[++i]->ptr; leveldb_writebatch_put(wb, key, sdslen((sds)key), value, sdslen((sds)value)); } - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_writeoptions_destroy(woptions); + leveldb_write(server.ds_db, server.woptions, wb, &err); leveldb_writebatch_destroy(wb); if(err != NULL) @@ -222,13 +360,6 @@ void ds_hincrby(redisClient *c) char *err = NULL; int64_t val, recore; - leveldb_writeoptions_t *woptions; - leveldb_readoptions_t *roptions; - - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); err = NULL; val_len = 0; @@ -237,9 +368,7 @@ void ds_hincrby(redisClient *c) keyword = sdscatlen(keyword, "*", 1); keyword = sdscat(keyword, c->argv[2]->ptr); - value = leveldb_get(server.ds_db, roptions, keyword, sdslen(keyword), &val_len, &err); - leveldb_readoptions_destroy(roptions); - + value = leveldb_get(server.ds_db, server.roptions, keyword, sdslen(keyword), &val_len, &err); if(err != NULL) { sdsfree(keyword); @@ -262,9 +391,7 @@ void ds_hincrby(redisClient *c) recore = val + recore; data = sdsfromlonglong(recore); - woptions = leveldb_writeoptions_create(); - leveldb_put(server.ds_db, woptions, keyword, sdslen(keyword), data, sdslen(data), &err); - leveldb_writeoptions_destroy(woptions); + leveldb_put(server.ds_db, server.woptions, keyword, sdslen(keyword), data, sdslen(data), &err); if(err != NULL) { addReplyError(c, err); @@ -288,11 +415,6 @@ void ds_hmget(redisClient *c) size_t val_len; char *key, *err = NULL, *value = NULL; - leveldb_readoptions_t *roptions; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); addReplyMultiBulkLen(c, c->argc-2); @@ -310,14 +432,13 @@ void ds_hmget(redisClient *c) keyword = sdscatlen(keyword, "*", 1); keyword = sdscat(keyword, c->argv[i]->ptr); - value = leveldb_get(server.ds_db, roptions, keyword, sdslen(keyword), &val_len, &err); + value = leveldb_get(server.ds_db, server.roptions, keyword, sdslen(keyword), &val_len, &err); if(err != NULL) { sdsfree(keyword); leveldb_free(err); leveldb_free(value); addReplyError(c, err); - leveldb_readoptions_destroy(roptions); return ; } else if(val_len > 0) @@ -334,7 +455,6 @@ void ds_hmget(redisClient *c) } sdsfree(keyword); - leveldb_readoptions_destroy(roptions); } void ds_hmset(redisClient *c) @@ -343,7 +463,6 @@ void ds_hmset(redisClient *c) sds keyword; char *key, *field, *value; char *err = NULL; - leveldb_writeoptions_t *woptions; leveldb_writebatch_t *wb; if((c->argc%2) != 0) @@ -353,7 +472,6 @@ void ds_hmset(redisClient *c) } keyword = sdsempty(); - woptions = leveldb_writeoptions_create(); wb = leveldb_writebatch_create(); key = (char *)c->argv[1]->ptr; @@ -373,8 +491,7 @@ void ds_hmset(redisClient *c) } sdsfree(keyword); - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_writeoptions_destroy(woptions); + leveldb_write(server.ds_db, server.woptions, wb, &err); leveldb_writebatch_destroy(wb); if(err != NULL) @@ -392,14 +509,12 @@ void ds_hset(redisClient *c) { sds str; char *key, *field, *value, *err; - leveldb_writeoptions_t *woptions; leveldb_writebatch_t *wb; key = (char *)c->argv[1]->ptr; field = (char *)c->argv[2]->ptr; value = (char *)c->argv[3]->ptr; - woptions = leveldb_writeoptions_create(); wb = leveldb_writebatch_create(); str = sdsempty(); @@ -414,13 +529,11 @@ void ds_hset(redisClient *c) str = sdscat(str, field); leveldb_writebatch_put(wb, str, sdslen(str), value, sdslen((sds)value)); - leveldb_write(server.ds_db, woptions, wb, &err); - - leveldb_writeoptions_destroy(woptions); + leveldb_write(server.ds_db, server.woptions, wb, &err); leveldb_writebatch_destroy(wb); sdsfree(str); - addReply(c, shared.cone); + addReply(c,shared.cone); return ; } @@ -428,14 +541,12 @@ void rl_hset(redisClient *c) { sds str; char *key, *field, *value, *err; - leveldb_writeoptions_t *woptions; leveldb_writebatch_t *wb; key = (char *)c->argv[1]->ptr; field = (char *)c->argv[2]->ptr; value = (char *)c->argv[3]->ptr; - woptions = leveldb_writeoptions_create(); wb = leveldb_writebatch_create(); str = sdsempty(); @@ -450,11 +561,10 @@ void rl_hset(redisClient *c) str = sdscat(str, field); leveldb_writebatch_put(wb, str, sdslen(str), value, sdslen((sds)value)); - leveldb_write(server.ds_db, woptions, wb, &err); - - leveldb_writeoptions_destroy(woptions); + leveldb_write(server.ds_db, server.woptions, wb, &err); leveldb_writebatch_destroy(wb); sdsfree(str); + hsetCommand(c); } @@ -487,84 +597,69 @@ void rl_hdel(redisClient *c) void ds_hgetall(redisClient *c) { sds str, header; - char *keyword = NULL; - - const char *key, *value; - size_t key_len, value_len, len, i; + char *keyword = NULL, *err; leveldb_iterator_t *iter; - leveldb_readoptions_t *roptions; - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); + const char *key, *value; + size_t key_len, value_len, len, i; i = 0; str = sdsempty(); - iter = leveldb_create_iterator(server.ds_db, roptions); - str = sdscpy(str, c->argv[1]->ptr); str = sdscatlen(str, "*", 1); len = sdslen(str); keyword = zmalloc(len+1); memcpy(keyword, str, len); - - leveldb_iter_seek(iter, keyword, len); - - if(!leveldb_iter_valid(iter)) - { - sdsfree(str); - zfree(keyword); - addReply(c,shared.nullbulk); - leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - return ; - } sdsclear(str); - - while(1) + iter = leveldb_create_iterator(server.ds_db, server.roptions); + for(leveldb_iter_seek(iter, keyword, len); leveldb_iter_valid(iter); leveldb_iter_next(iter)) { - leveldb_iter_next(iter); - if(!leveldb_iter_valid(iter)) - break; key_len = value_len = 0; key = leveldb_iter_key(iter, &key_len); value = leveldb_iter_value(iter, &value_len); - if(strncmp(keyword, key, len) != 0) + if(key_len == len) + continue; + else if(strncmp(keyword, key, len) != 0) break; - str = sdscatprintf(str, "$%lu\r\n", key_len-len); + str = sdscatprintf(str, "$%zu\r\n", (key_len-len)); str = sdscatlen(str, key+len, key_len-len); - str = sdscatprintf(str, "\r\n$%lu\r\n", value_len); + str = sdscatprintf(str, "\r\n$%zu\r\n", value_len); str = sdscatlen(str, value, value_len); str = sdscatlen(str, "\r\n", 2); i++; } - + err = NULL; + zfree(keyword); + leveldb_iter_get_error(iter, &err); leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - if(i == 0) + if(err) + { + addReplyError(c, err); + leveldb_free(err); + } + else if(i == 0) { addReply(c,shared.nullbulk); } else { header = sdsempty(); - header = sdscatprintf(header, "*%lu\r\n", i*2); + header = sdscatprintf(header, "*%zu\r\n", (i*2)); header = sdscatlen(header, str, sdslen(str)); addReplySds(c, header); sdsfree(header); } sdsfree(str); - zfree(keyword); - + return ; } @@ -576,39 +671,22 @@ void ds_hdel(redisClient *c) sds keyword; char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_writebatch_t *wb; - - leveldb_iterator_t *iter; - leveldb_readoptions_t *roptions; - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - iter = leveldb_create_iterator(server.ds_db, roptions); + leveldb_writebatch_t *wb; + leveldb_iterator_t *iter; keyword = sdsempty(); - woptions = leveldb_writeoptions_create(); + //delete hashtable key if(c->argc < 3) { keyword = sdscpy(keyword, c->argv[1]->ptr); keyword = sdscatlen(keyword, "*", 1); - leveldb_iter_seek(iter, keyword, sdslen(keyword)); - if(!leveldb_iter_valid(iter)) - { - sdsfree(keyword); - addReply(c,shared.nullbulk); - leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - leveldb_writeoptions_destroy(woptions); - return ; - } - wb = leveldb_writebatch_create(); - while(1) + iter = leveldb_create_iterator(server.ds_db, server.roptions); + wb = leveldb_writebatch_create(); + for(leveldb_iter_seek(iter, keyword, sdslen(keyword)); leveldb_iter_valid(iter); leveldb_iter_next(iter)) { key_len = 0; key = leveldb_iter_key(iter, &key_len); @@ -616,36 +694,32 @@ void ds_hdel(redisClient *c) if(strncmp(keyword, key, sdslen(keyword)) != 0) break; - //printf("key = %s\r\n", key); leveldb_writebatch_delete(wb, key, key_len); - leveldb_iter_next(iter); - if(!leveldb_iter_valid(iter)) - break; } - - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_writeoptions_destroy(woptions); - leveldb_writebatch_destroy(wb); - if(err != NULL) + leveldb_write(server.ds_db, server.woptions, wb, &err); + leveldb_writebatch_clear(wb); + leveldb_writebatch_destroy(wb); + sdsfree(keyword); + + if(err != NULL) { addReplyError(c, err); - leveldb_free(err); - - sdsfree(keyword); - leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - leveldb_writeoptions_destroy(woptions); + leveldb_free(err); return ; } - addReply(c,shared.ok); - sdsfree(keyword); + err = NULL; + leveldb_iter_get_error(iter, &err); leveldb_iter_destroy(iter); - leveldb_readoptions_destroy(roptions); - leveldb_writeoptions_destroy(woptions); + + if(err) + { + addReplyError(c, err); + leveldb_free(err); + } - return ; + addReply(c,shared.ok); } wb = leveldb_writebatch_create(); @@ -659,9 +733,8 @@ void ds_hdel(redisClient *c) } sdsfree(keyword); - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_readoptions_destroy(roptions); - leveldb_writeoptions_destroy(woptions); + leveldb_write(server.ds_db, server.woptions, wb, &err); + leveldb_writebatch_clear(wb); leveldb_writebatch_destroy(wb); @@ -682,21 +755,14 @@ void ds_hget(redisClient *c) size_t val_len = 0; char *key = NULL, *field = NULL, *value = NULL, *err = NULL; - leveldb_readoptions_t *roptions; key = (char *)c->argv[1]->ptr; field = (char *)c->argv[2]->ptr; - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); - str = sdsnew(key); str = sdscatlen(str, "*", 1); str = sdscat(str, field); - value = leveldb_get(server.ds_db, roptions, str, sdslen(str), &val_len, &err); - leveldb_readoptions_destroy(roptions); - + value = leveldb_get(server.ds_db, server.roptions, str, sdslen(str), &val_len, &err); if(err != NULL) { addReplyError(c, err); @@ -739,20 +805,11 @@ void ds_incrby(redisClient *c) size_t val_len; char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_readoptions_t *roptions; - - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); err = NULL; val_len = 0; - value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), &val_len, &err); - leveldb_readoptions_destroy(roptions); - + value = leveldb_get(server.ds_db, server.roptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), &val_len, &err); if(err != NULL) { leveldb_free(err); @@ -773,10 +830,8 @@ void ds_incrby(redisClient *c) recore = strtoll(c->argv[2]->ptr, NULL, 10); recore = val + recore; data = sdsfromlonglong(recore); - woptions = leveldb_writeoptions_create(); - leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), data, sdslen(data), &err); - leveldb_writeoptions_destroy(woptions); + leveldb_put(server.ds_db, server.woptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), data, sdslen(data), &err); if(err != NULL) { addReplyError(c, err); @@ -800,20 +855,11 @@ void ds_append(redisClient *c) size_t val_len; char *err = NULL; - leveldb_writeoptions_t *woptions; - leveldb_readoptions_t *roptions; - - - roptions = leveldb_readoptions_create(); - leveldb_readoptions_set_verify_checksums(roptions, 0); - leveldb_readoptions_set_fill_cache(roptions, 1); err = NULL; val_len = 0; - value = leveldb_get(server.ds_db, roptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), &val_len, &err); - leveldb_readoptions_destroy(roptions); - + value = leveldb_get(server.ds_db, server.roptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), &val_len, &err); if(err != NULL) { leveldb_free(err); @@ -827,13 +873,11 @@ void ds_append(redisClient *c) recore = sdsempty(); if(val_len > 0) { - recore = sdscpy(recore, value); + recore = sdscpylen(recore, value, val_len); } - recore = sdscat(recore, c->argv[1]->ptr); - woptions = leveldb_writeoptions_create(); + recore = sdscat(recore, c->argv[2]->ptr); - leveldb_put(server.ds_db, woptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), recore, sdslen(recore), &err); - leveldb_writeoptions_destroy(woptions); + leveldb_put(server.ds_db, server.woptions, c->argv[1]->ptr, sdslen((sds)c->argv[1]->ptr), recore, sdslen(recore), &err); if(err != NULL) { addReplyError(c, err); @@ -841,7 +885,7 @@ void ds_append(redisClient *c) } else { - addReply(c,shared.ok); + addReplyLongLong(c, sdslen(recore)); } sdsfree(recore); @@ -853,14 +897,10 @@ void ds_set(redisClient *c) { char *key, *value; char *err = NULL; - leveldb_writeoptions_t *woptions; - - woptions = leveldb_writeoptions_create(); key = (char *)c->argv[1]->ptr; value = (char *)c->argv[2]->ptr; - leveldb_put(server.ds_db, woptions, key, sdslen((sds)key), value, sdslen((sds)value), &err); - leveldb_writeoptions_destroy(woptions); + leveldb_put(server.ds_db, server.woptions, key, sdslen((sds)key), value, sdslen((sds)value), &err); if(err != NULL) { addReplyError(c, err); @@ -875,14 +915,10 @@ void rl_set(redisClient *c) { char *key, *value; char *err = NULL; - leveldb_writeoptions_t *woptions; - - woptions = leveldb_writeoptions_create(); key = (char *)c->argv[1]->ptr; value = (char *)c->argv[2]->ptr; - leveldb_put(server.ds_db, woptions, key, sdslen((sds)key), value, sdslen((sds)value), &err); - leveldb_writeoptions_destroy(woptions); + leveldb_put(server.ds_db, server.woptions, key, sdslen((sds)key), value, sdslen((sds)value), &err); if(err != NULL) { addReplyError(c, err); @@ -900,16 +936,12 @@ void ds_delete(redisClient *c) int i; char *key; char *err = NULL; - leveldb_writeoptions_t *woptions; leveldb_writebatch_t *wb; - - woptions = leveldb_writeoptions_create(); if(c->argc < 3) { key = (char *)c->argv[1]->ptr; - leveldb_delete(server.ds_db, woptions, key, sdslen((sds)key), &err); - leveldb_writeoptions_destroy(woptions); + leveldb_delete(server.ds_db, server.woptions, key, sdslen((sds)key), &err); if(err != NULL) { addReplyError(c, err); @@ -925,9 +957,10 @@ void ds_delete(redisClient *c) { leveldb_writebatch_delete(wb, (char *)c->argv[i]->ptr, sdslen((sds)c->argv[i]->ptr)); } - leveldb_write(server.ds_db, woptions, wb, &err); - leveldb_writeoptions_destroy(woptions); + leveldb_write(server.ds_db, server.woptions, wb, &err); + leveldb_writebatch_clear(wb); leveldb_writebatch_destroy(wb); + wb = NULL; if(err != NULL) { @@ -950,6 +983,8 @@ void rl_delete(redisClient *c) void ds_close() { + leveldb_readoptions_destroy(server.roptions); + leveldb_writeoptions_destroy(server.woptions); leveldb_options_set_filter_policy(server.ds_options, NULL); leveldb_filterpolicy_destroy(server.policy); leveldb_close(server.ds_db); diff --git a/src/redis.c b/src/redis.c index 2203479..71a38b0 100644 --- a/src/redis.c +++ b/src/redis.c @@ -133,7 +133,14 @@ struct redisCommand redisCommandTable[] = { {"ds_hgetall",ds_hgetall,2,"r",0,NULL,1,1,1,0,0}, {"ds_append",ds_append,3,"wm",0,NULL,1,1,1,0,0}, {"ds_incrby",ds_incrby,3,"wm",0,NULL,1,1,1,0,0}, - + {"ds_exists",ds_exists,-2,"r",0,NULL,1,-1,1,0,0}, + {"ds_hexists",ds_hexists,-2,"r",0,NULL,1,-1,1,0,0}, + + /* + {"ds_keys_asc",ds_keys_asc,2,"r",0,NULL,1,1,1,0,0}, + {"ds_keys_desc",ds_keys_desc,2,"r",0,NULL,1,1,1,0,0}, + */ + {"get",getCommand,2,"r",0,NULL,1,1,1,0,0}, {"set",setCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, {"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0}, diff --git a/src/redis.h b/src/redis.h index 104bfc1..2159081 100644 --- a/src/redis.h +++ b/src/redis.h @@ -500,6 +500,10 @@ struct redisServer { leveldb_cache_t *ds_cache; leveldb_options_t *ds_options; leveldb_filterpolicy_t *policy; + leveldb_writeoptions_t *woptions; + leveldb_readoptions_t *roptions; + //leveldb_iterator_t *iter; + uint16_t ds_lru_cache; uint16_t ds_create_if_missing; From c5f2849dc9a8926bbfffff0076b840ab997959f3 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Tue, 15 Jan 2013 16:46:36 +0800 Subject: [PATCH 12/93] edit redis.h --- src/redis.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/redis.h b/src/redis.h index 2159081..983c09d 100644 --- a/src/redis.h +++ b/src/redis.h @@ -1238,6 +1238,14 @@ void ds_mget(redisClient *c); void ds_append(redisClient *c); void ds_incrby(redisClient *c); +void ds_exists(redisClient *c); + +/* +void ds_keys_asc(redisClient *c); +void ds_keys_desc(redisClient *c); +*/ +void ds_hexists(redisClient *c); + void ds_hdel(redisClient *c); void ds_hget(redisClient *c); void ds_hset(redisClient *c); @@ -1245,8 +1253,8 @@ void ds_hmget(redisClient *c); void ds_hmset(redisClient *c); void ds_hincrby(redisClient *c); void ds_hgetall(redisClient *c); - void ds_delete(redisClient *c); + void rl_delete(redisClient *c); void rl_get(redisClient *c); void rl_set(redisClient *c); From f55af86b7e300889017e8fba209b3612fb69bb43 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Tue, 15 Jan 2013 17:09:02 +0800 Subject: [PATCH 13/93] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20rl=5Fdel=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E5=80=BC=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/ds.c | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/ds.c b/src/ds.c index d1db3eb..2462d22 100644 --- a/src/ds.c +++ b/src/ds.c @@ -977,7 +977,41 @@ void ds_delete(redisClient *c) void rl_delete(redisClient *c) { - ds_delete(c); + int i; + char *key; + char *err = NULL; + leveldb_writebatch_t *wb; + + if(c->argc < 3) + { + key = (char *)c->argv[1]->ptr; + leveldb_delete(server.ds_db, server.woptions, key, sdslen((sds)key), &err); + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + return ; + } + return ; + } + + wb = leveldb_writebatch_create(); + for(i=1; iargc; i++) + { + leveldb_writebatch_delete(wb, (char *)c->argv[i]->ptr, sdslen((sds)c->argv[i]->ptr)); + } + leveldb_write(server.ds_db, server.woptions, wb, &err); + leveldb_writebatch_clear(wb); + leveldb_writebatch_destroy(wb); + wb = NULL; + + if(err != NULL) + { + addReplyError(c, err); + leveldb_free(err); + return ; + } + delCommand(c); } From c97551c4da9a621d4207d7514409f3f98a89f221 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Wed, 16 Jan 2013 15:04:32 +0800 Subject: [PATCH 14/93] edit readme --- README.markdown | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 3cb0426..c11a4e4 100644 --- a/README.markdown +++ b/README.markdown @@ -89,4 +89,12 @@ $redis->get("name"); $redis->dsGet("name"); $redis->rlDel("name"); - \ No newline at end of file + + +Nginx 用户可使用`redis2-nginx-module`或者ngx_lua的`lua-resty-redis`库 +========= + +https://github.com/agentzh/redis2-nginx-module + +https://github.com/agentzh/lua-resty-redis + From 0e6ca73fd196a05994983ffe6b0af069efcf1ef0 Mon Sep 17 00:00:00 2001 From: shenzhe Date: Sat, 19 Jan 2013 15:07:28 +0800 Subject: [PATCH 15/93] =?UTF-8?q?redis=E5=8D=87=E7=BA=A7=E5=88=B02.6.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 00-RELEASENOTES | 633 ++++---- CONTRIBUTING | 67 +- src/Makefile.dep | 203 +-- src/ae.c | 866 +++++------ src/aof.c | 2345 ++++++++++++++-------------- src/asciilogo.h | 94 +- src/config.h | 345 +++-- src/endianconv.h | 127 +- src/fmacros.h | 96 +- src/networking.c | 2783 +++++++++++++++++----------------- src/redis-benchmark.c | 1362 ++++++++--------- src/redis-cli.c | 2595 ++++++++++++++++--------------- src/redis.h | 1 + src/replication.c | 1632 ++++++++++---------- src/scripting.c | 2039 ++++++++++++------------- tests/unit/introspection.tcl | 77 +- tests/unit/scripting.tcl | 758 ++++----- 17 files changed, 8127 insertions(+), 7896 deletions(-) diff --git a/00-RELEASENOTES b/00-RELEASENOTES index e6dd01d..d022523 100644 --- a/00-RELEASENOTES +++ b/00-RELEASENOTES @@ -1,304 +1,329 @@ -Redis 2.6 release notes -======================= - -** IMPORTANT ** Check the 'Migrating from 2.4 to 2.6' section at the end of - this file for information about what changed between 2.4 and - 2.6 and how this may affect your application. - --------------------------------------------------------------------------------- -Upgrade urgency levels: - -LOW: No need to upgrade unless there are new features you want to use. -MODERATE: Program an upgrade of the server, but it's not urgent. -HIGH: There is a critical bug that may affect a subset of users. Upgrade! -CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP. --------------------------------------------------------------------------------- - ---[ Redis 2.6.7 ] - -UPGRADE URGENCY: MODERATE (unless you BLPOP using the same key multiple times). - -* [BUGFIX] Don't crash if BLPOP & co are called with the same key repeated - multiple times (Issue #801). - ---[ Redis 2.6.6 ] - -UPGRADE URGENCY: CRITICAL if you experienced one more more crashes. - MODERATE if Redis is running fine for you. - -* [BUGFIX] Jemalloc updated to 3.2.0. - ---[ Redis 2.6.5 ] - -UPGRADE URGENCY: MODERATE - -Warning: this release of Redis introduces a different behavior in MULTI/EXEC - handling of errors. This was done because the new behavior is safer - compared to the old one, and should not break any code targeting - Redis 2.6 in a critical way. - - For more information check http://redis.io/topics/transactions - -* [IMPROVED] RDB/AOF childern now log amount of additional memory used - because of copy on write. -* [BUGFIX] MIGRATE non critical fixes (see commits for details). -* [BUGFIX] MULTI/EXEC: now EXEC aborts on errors before EXEC. -* [BUGFIX] Fix integer overflow in zunionInterGenericCommand resulting - into Z[INTER|UNION][STORE] commands to crash under extremely - unlikely conditions (almost impossible in real world). -* [BUGFIX] EVALSHA is now case insensitive (and will not crash). - ---[ Redis 2.6.4 ] - -UPGRADE URGENCY: LOW - -* [IMPROVED] BSD license and copyright notice added to every .c and .h file. - ---[ Redis 2.6.2 ] - -UPGRADE URGENCY: LOW - -* [BUGFIX] The compilation fix for RHLE5 in 2.6.1 was broken. Fixed. -* [IMPROVED] Linenoise updated, now supports Ctrl+w. - ----[ Redis 2.6.1 ] - -UPGRADE URGENCY: LOW - -* [BUGFIX] Compilation on Linux < 2.6.17 or glibc < 2.6 fixed (RHLE5 & co). - ----[ Redis 2.6.0 ] - -UPGRADE URGENCY: HIGH - -* [BUGFIX] Allow AUTH when server is in -BUSY state because of a slow script. -* [BUGFIX] MULTI/EXEC flow now makes sense when observed in MONITOR -* [BUGFIX] SCRIPT KILL now uses different error prefixes for different errors. -* [BUGFIX] Default memory limit for 32bit archs lowered from 3.5 to 3 GB. -* [BUGFIX] redis-check-dump is now compatible with RDB files generated by 2.6. -* [IMPROVED] New field in INFO: slave_read_only. - ----[ Redis 2.5.14 (2.6 Release Candidate 8) ] - -* [BUGFIX] Fixed compilation on FreeBSD. -* [IMPROVED] SRANDMEMBER that returns multiple random elements. -* [IMPROVED] Sentinel backported to 2.6. It will be taken in sync with 2.8. -* [IMPROVED] Helper function for scripting to return errors and status replies. -* [IMPROVED] SORT by nosort [ASC|DESC] uses sorted set elements ordering. -* [BUGFIX] Better resistence to system clock skew. -* [IMPROVED] Warn the user when the configured maxmemory seems odd. -* [BUGFIX] Hashing function is now murmurhash2 for security purposes. -* [IMPROVED] Install script no longer uses a template but redis.conf itself. - ----[ Redis 2.5.13 (2.6 Release Candidate 7) ] - -UPGRADE URGENCY: HIGH - -* [BUGFIX] Theoretical bug in ziplist fixed. -* [BUGFIX] Better out of memory handling (Log produced in log file). -* [BUGFIX] Incrementally flush RDB file on slave side while performing the - first synchronization with the master. This makes Redis less - blocking in environments where disk I/O is slow. -* [BUGFIX] Don't crash with Lua's redis.call() without arguments. -* [BUGFIX] Don't crash after a big number of Lua calls on 32 bit systems - because of a failed assertion. -* [BUGFIX] Fix SORT behaviour when called from scripting. -* [BUGFIX] Adjust slave PING period accordingly to REDIS_HZ define. -* [BUGFIX] BITCOUNT: fix crash on overflowing arguments. -* [BUGFIX] Return an error when SELECT argument is not an integer. -* [BUGFIX] Blocking operations on lists were completely reimplemented for - correctness. Now blocking list ops and pushes originated from - Lua scripts will play well together and will be replicated - and transmitted to the AOF correctly. -* [IMPROVED] Send async PING before starting replication to avoid blocking if - master allows us to connect but it is actually not able to reply. -* [IMPROVED] Support slave-priority for Redis Sentinel. -* [IMPROVED] Hiredis library updated. - ----[ Redis 2.5.12 (2.6 Release Candidate 6) ] - -UPGRADE URGENCY: MODERATE. - -* [BUGFIX] Fixed a timing attack on AUTH (Issue #560). -* [BUGFIX] Don't assume that "char" is signed. -* [BUGFIX] Check that we have connection before enabling pipe mode. -* [BUGFIX] Use the optimized version of the function to convert a double to - its string representation. Compilation was disabled because of - a typo in the #if statement. -* [IMPROVED} REPLCONF internal command introduced, now INFO shows slaves with - correct port numbers. This makes 2.5.12 Redis Sentinel compatible. -* [IMPROVED] Truncate short write from the AOF for a cleaner restart. On short - writes (for instance out of space) Redis will now try to remove - the half-written data so that the next restart will work without - the need for the "redis-check-aof" utility. -* [IMPROVED] New in INFO: aof_last_bgrewrite_status -* [IMPROVED] Allow Pub/Sub in contexts where other commands are blocked. -* [BUGFIX] mark fd as writable when EPOLLERR or EPOLLHUP is returned by - epoll_wait. - ----[ Redis 2.5.11 (2.6 Release Candidate 5) ] - -UPGRADE URGENCY: HIGH. - -* [BUGFIX] Fixed Hash corruption when loading an RDB file generated by - previous versions of Redis that encoded hashes using - a different ziplist encoding format for small integers. - All the fileds that are integers in the range 0-255 may not - be recognized, or duplicated un updates, causing a crash - when the ziplist is converted to a real hash. (Issue #547). -* [BUGFIX] Fixed the count of memory used by output buffers in the - setDeferredMultiBulkLength() function. - ----[ Redis 2.5.10 (2.6 Release Candidate 4) ] - -UPGRADE URGENCY: HIGH. - -* [BUGFIX] Allow PREFIX to be overwritten on "make install". -* [BUGFIX] Run the test with just one client if the computer is slow. -* [BUGFIX] Event port support in our event driven libray. -* [BUGFIX] Jemalloc updated to 3.0.0. This fixes a possibly AOF rewrite issue. - See https://github.com/antirez/redis/issues/504 for info. -* [BUGFIX] Fixed issue #516: ZINTERSTORE / ZUNIONSTORE with mixed sets/zsets. -* [BUGFIX] Set fd to writable when poll(2) detects POLLERR or POLLHUP event. -* [BUGFIX] Fixed RESTORE hash failure (Issue #532). -* [IMPROVED] Allow an AOF rewrite buffer > 2GB (Related to issue #504). -* [IMPROVED] Server cron function frequency is now configurable (REDIS_HZ). -* [IMPROVED] Better, less blocking expired keys collection algorithm. -* [FEATURE] New commands: BITOP and BITCOUNT. -* [FEATURE] redis-cli --pipe for mass import. - -What's new in Redis 2.5.9 (aka 2.6 Release Candidate 3) -======================================================= - -UPGRADE URGENCY: critical, upgrade ASAP. - -* [BUGFIX] Fix for issue #500 (https://github.com/antirez/redis/pull/500). - Redis 2.6-RC1 and RC2 may corrupt ziplist-encoded sorted sets - produced by Redis 2.4.x. -* [BUGFIX] Fixed several bugs in init.d script. -* [BUGFIX] syncio.c functions modified for speed and correctness. On osx - (and possibly other BSD-based systems) the slave would block on - replication to send the SYNC command when the master was not - available. This is fixed now, but was not affecting Linux installs. -* Now when slave-serve-stale-data is set to yes and the master is down, instead - of reporting a generic error Redis replies with -MASTERDOWN. - -What's new in Redis 2.5.8 (aka 2.6 Release Candidate 2) -======================================================= - -UPGRADE URGENCY: high for all the users of the KEYS command, otherwise low. - -* [BUGFIX] Fix for KEYS command: if the DB contains keys with expires the KEYS - command may return the wrong output, having duplicated or missing - keys. See issue #487 and #488 on github for details. - -What's new in Redis 2.5.7 (aka 2.6 Release Candidate 1) -======================================================= - -UPGRADE URGENCY: upgrade not recommended because this is an RC release. - -* This is the first release candidate for Redis 2.6. We are not aware of - bugs, but part of this code is young and was never tested in production - environments, so handle with care. - -An overview of new features and changes in Redis 2.6.x -====================================================== - -* Server side Lua scripting, see http://redis.io/commands/eval -* Virtual Memory removed (was deprecated in 2.4) -* Hardcoded limits about max number of clients removed. -* AOF low level semantics is generally more sane, and especially when used - in slaves. -* Milliseconds resolution expires, also added new commands with milliseconds - precision (PEXPIRE, PTTL, ...). -* Better memory usage for "small" lists, ziplists and hashes when fields or - values contain small integers. -* Read only slaves. -* New bit opeations: BITCOUNT and BITOP commands. -* Clients max output buffer soft and hard limits. You can specifiy different - limits for different classes of clients (normal,pubsub,slave). -* More incremental (less blocking) expired keys collection algorithm, in - practical terms this means that Redis is more responsive when a very - big number of keys expire about at the same time. -* AOF is now able to rewrite aggregate data types using variadic commands, - often producing an AOF that is faster to save, load, and is smaller in size. -* Every redis.conf directive is now accepted as a command line option for the - redis-server binary, with the same name and number of arguments. -* Hash table seed randomization for protection against collisions attacks. -* Performances improved when writing large objects to Redis. -* Integrated memory test, see redis-server --test-memory. -* INCRBYFLOAT and HINCRBYFLOAT commands. -* New DUMP, RESTORE, MIGRATE commands (back ported from Redis Cluster to 2.6). -* CRC64 checksump in RDB files. -* Better MONITOR output and behavior (now commands are logged before execution). -* "Software Watchdog" feature to debug latency issues. -* Significant parts of the core refactored or rewritten. New internal APIs - and core changes allowed to develop Redis Cluster on top of the new code, - however for 2.6 all the cluster code was removed, and will be released with - Redis 3.0 when it is more complete and stable. -* Redis ASCII art logo added at startup. -* Crash report on memory violation or failed asserts improved significantly - to make debugging of hard to catch bugs simpler. -* redis-benchmark improvements: ability to run selected tests, - CSV output, faster, better help. -* redis-cli improvements: --eval for comfortable development of Lua scripts. -* SHUTDOWN now supports two optional arguments: "SAVE" and "NOSAVE". -* INFO output split into sections, the command is now able to just show - pecific sections. -* New statistics about how many time a command was called, and how much - execution time it used (INFO commandstats). -* More predictable SORT behavior in edge cases. -* Better support for big endian and *BSD systems. -* Build system improved. - -Migrating from 2.4 to 2.6 -========================= - -Redis 2.4 is mostly a strict subset of 2.6. However there are a few things -that you should be aware of: - -* You can't use .rdb and AOF files generated with 2.6 into a 2.4 instance. -* 2.6 slaves can be attached to 2.4 masters, but not the contrary, and only - for the time needed to perform the version upgrade. - -There are also a few API differences, that are unlikely to cause problems, -but it is better to keep them in mind: - -* SORT now will refuse to sort in numerical mode elements that can't be parsed - as numbers. -* EXPIREs now all have millisecond resolution (but this is very unlikely to - break code that was not conceived exploting the previous resolution error - in some way.) -* INFO output is a bit different now, and contains empty lines and comments - starting with '#'. All the major clients should be already fixed to work - with the new INFO format. -* Slaves are only read-only by default (but you can change this easily - setting the "slave-read-only" configuration option to "no" editing your - redis.conf or using CONFIG SET. - -The following INFO fields were renamed for consistency: - - changes_since_last_save -> rdb_changes_since_last_save - bgsave_in_progress -> rdb_bgsave_in_progress - last_save_time -> rdb_last_save_time - last_bgsave_status -> rdb_last_bgsave_status - bgrewriteaof_in_progress -> aof_rewrite_in_progress - bgrewriteaof_scheduled -> aof_rewrite_scheduled - -The following redis.conf and CONFIG GET / SET parameters changed: - - * hash-max-zipmap-entries, now replaced by hash-max-ziplist-entries - * hash-max-zipmap-value, now replaced by hash-max-ziplist-value - * glueoutputbuf option was now completely removed (was deprecated) - --------------------------------------------------------------------------------- - -Credits: Where not specified the implementation and design are done by -Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all -this possible. Also many thanks to all the other contributors and the amazing -community we have. - -See commit messages for more credits. - -Cheers, -Salvatore +Redis 2.6 release notes +======================= + +** IMPORTANT ** Check the 'Migrating from 2.4 to 2.6' section at the end of + this file for information about what changed between 2.4 and + 2.6 and how this may affect your application. + +-------------------------------------------------------------------------------- +Upgrade urgency levels: + +LOW: No need to upgrade unless there are new features you want to use. +MODERATE: Program an upgrade of the server, but it's not urgent. +HIGH: There is a critical bug that may affect a subset of users. Upgrade! +CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP. +-------------------------------------------------------------------------------- + +--[ Redis 2.6.9 ] + +UPGRADE URGENCY: MODERATE if you use replication. + +* [BUGFIX] Changing master at runtime (SLAVEOF command) in presence of + network problems, or in very rapid succession, could result + in non-critical problems (GitHub Issue #828). +* [IMPROVED] CLINGET GETNAME and SETNAME to set and query connection names + reported by CLIENT LIST. Very useful for debugging of + problems. +* [IMPROVED] redis-cli is now able to transfer an RDB file from a remote + server to a local file using the --rdb command + line option. + +--[ Redis 2.6.8 ] + +UPGRADE URGENCY: MODERATE if you use Lua scripting. Otherwise LOW. + +* [BUGFIX] Multiple fixes for EVAL (issue #872). +* [BUGFIX] Fix overflow in mstime() in redis-cli and benchmark. +* [BUGFIX] Fix Linux / PPC64 behavior by correcting endianess detection. +* [BUGFIX] Fix NetBSD build by defining _XOPEN_SOURCE appropriately. +* [BUGFIX] Added missing license and copyright in a few places. +* [BUGFIX] Better error reporting when fd event creation fails. + +--[ Redis 2.6.7 ] + +UPGRADE URGENCY: MODERATE (unless you BLPOP using the same key multiple times). + +* [BUGFIX] Don't crash if BLPOP & co are called with the same key repeated + multiple times (Issue #801). + +--[ Redis 2.6.6 ] + +UPGRADE URGENCY: CRITICAL if you experienced one more more crashes. + MODERATE if Redis is running fine for you. + +* [BUGFIX] Jemalloc updated to 3.2.0. + +--[ Redis 2.6.5 ] + +UPGRADE URGENCY: MODERATE + +Warning: this release of Redis introduces a different behavior in MULTI/EXEC + handling of errors. This was done because the new behavior is safer + compared to the old one, and should not break any code targeting + Redis 2.6 in a critical way. + + For more information check http://redis.io/topics/transactions + +* [IMPROVED] RDB/AOF childern now log amount of additional memory used + because of copy on write. +* [BUGFIX] MIGRATE non critical fixes (see commits for details). +* [BUGFIX] MULTI/EXEC: now EXEC aborts on errors before EXEC. +* [BUGFIX] Fix integer overflow in zunionInterGenericCommand resulting + into Z[INTER|UNION][STORE] commands to crash under extremely + unlikely conditions (almost impossible in real world). +* [BUGFIX] EVALSHA is now case insensitive (and will not crash). + +--[ Redis 2.6.4 ] + +UPGRADE URGENCY: LOW + +* [IMPROVED] BSD license and copyright notice added to every .c and .h file. + +--[ Redis 2.6.2 ] + +UPGRADE URGENCY: LOW + +* [BUGFIX] The compilation fix for RHLE5 in 2.6.1 was broken. Fixed. +* [IMPROVED] Linenoise updated, now supports Ctrl+w. + +---[ Redis 2.6.1 ] + +UPGRADE URGENCY: LOW + +* [BUGFIX] Compilation on Linux < 2.6.17 or glibc < 2.6 fixed (RHLE5 & co). + +---[ Redis 2.6.0 ] + +UPGRADE URGENCY: HIGH + +* [BUGFIX] Allow AUTH when server is in -BUSY state because of a slow script. +* [BUGFIX] MULTI/EXEC flow now makes sense when observed in MONITOR +* [BUGFIX] SCRIPT KILL now uses different error prefixes for different errors. +* [BUGFIX] Default memory limit for 32bit archs lowered from 3.5 to 3 GB. +* [BUGFIX] redis-check-dump is now compatible with RDB files generated by 2.6. +* [IMPROVED] New field in INFO: slave_read_only. + +---[ Redis 2.5.14 (2.6 Release Candidate 8) ] + +* [BUGFIX] Fixed compilation on FreeBSD. +* [IMPROVED] SRANDMEMBER that returns multiple random elements. +* [IMPROVED] Sentinel backported to 2.6. It will be taken in sync with 2.8. +* [IMPROVED] Helper function for scripting to return errors and status replies. +* [IMPROVED] SORT by nosort [ASC|DESC] uses sorted set elements ordering. +* [BUGFIX] Better resistence to system clock skew. +* [IMPROVED] Warn the user when the configured maxmemory seems odd. +* [BUGFIX] Hashing function is now murmurhash2 for security purposes. +* [IMPROVED] Install script no longer uses a template but redis.conf itself. + +---[ Redis 2.5.13 (2.6 Release Candidate 7) ] + +UPGRADE URGENCY: HIGH + +* [BUGFIX] Theoretical bug in ziplist fixed. +* [BUGFIX] Better out of memory handling (Log produced in log file). +* [BUGFIX] Incrementally flush RDB file on slave side while performing the + first synchronization with the master. This makes Redis less + blocking in environments where disk I/O is slow. +* [BUGFIX] Don't crash with Lua's redis.call() without arguments. +* [BUGFIX] Don't crash after a big number of Lua calls on 32 bit systems + because of a failed assertion. +* [BUGFIX] Fix SORT behaviour when called from scripting. +* [BUGFIX] Adjust slave PING period accordingly to REDIS_HZ define. +* [BUGFIX] BITCOUNT: fix crash on overflowing arguments. +* [BUGFIX] Return an error when SELECT argument is not an integer. +* [BUGFIX] Blocking operations on lists were completely reimplemented for + correctness. Now blocking list ops and pushes originated from + Lua scripts will play well together and will be replicated + and transmitted to the AOF correctly. +* [IMPROVED] Send async PING before starting replication to avoid blocking if + master allows us to connect but it is actually not able to reply. +* [IMPROVED] Support slave-priority for Redis Sentinel. +* [IMPROVED] Hiredis library updated. + +---[ Redis 2.5.12 (2.6 Release Candidate 6) ] + +UPGRADE URGENCY: MODERATE. + +* [BUGFIX] Fixed a timing attack on AUTH (Issue #560). +* [BUGFIX] Don't assume that "char" is signed. +* [BUGFIX] Check that we have connection before enabling pipe mode. +* [BUGFIX] Use the optimized version of the function to convert a double to + its string representation. Compilation was disabled because of + a typo in the #if statement. +* [IMPROVED} REPLCONF internal command introduced, now INFO shows slaves with + correct port numbers. This makes 2.5.12 Redis Sentinel compatible. +* [IMPROVED] Truncate short write from the AOF for a cleaner restart. On short + writes (for instance out of space) Redis will now try to remove + the half-written data so that the next restart will work without + the need for the "redis-check-aof" utility. +* [IMPROVED] New in INFO: aof_last_bgrewrite_status +* [IMPROVED] Allow Pub/Sub in contexts where other commands are blocked. +* [BUGFIX] mark fd as writable when EPOLLERR or EPOLLHUP is returned by + epoll_wait. + +---[ Redis 2.5.11 (2.6 Release Candidate 5) ] + +UPGRADE URGENCY: HIGH. + +* [BUGFIX] Fixed Hash corruption when loading an RDB file generated by + previous versions of Redis that encoded hashes using + a different ziplist encoding format for small integers. + All the fileds that are integers in the range 0-255 may not + be recognized, or duplicated un updates, causing a crash + when the ziplist is converted to a real hash. (Issue #547). +* [BUGFIX] Fixed the count of memory used by output buffers in the + setDeferredMultiBulkLength() function. + +---[ Redis 2.5.10 (2.6 Release Candidate 4) ] + +UPGRADE URGENCY: HIGH. + +* [BUGFIX] Allow PREFIX to be overwritten on "make install". +* [BUGFIX] Run the test with just one client if the computer is slow. +* [BUGFIX] Event port support in our event driven libray. +* [BUGFIX] Jemalloc updated to 3.0.0. This fixes a possibly AOF rewrite issue. + See https://github.com/antirez/redis/issues/504 for info. +* [BUGFIX] Fixed issue #516: ZINTERSTORE / ZUNIONSTORE with mixed sets/zsets. +* [BUGFIX] Set fd to writable when poll(2) detects POLLERR or POLLHUP event. +* [BUGFIX] Fixed RESTORE hash failure (Issue #532). +* [IMPROVED] Allow an AOF rewrite buffer > 2GB (Related to issue #504). +* [IMPROVED] Server cron function frequency is now configurable (REDIS_HZ). +* [IMPROVED] Better, less blocking expired keys collection algorithm. +* [FEATURE] New commands: BITOP and BITCOUNT. +* [FEATURE] redis-cli --pipe for mass import. + +What's new in Redis 2.5.9 (aka 2.6 Release Candidate 3) +======================================================= + +UPGRADE URGENCY: critical, upgrade ASAP. + +* [BUGFIX] Fix for issue #500 (https://github.com/antirez/redis/pull/500). + Redis 2.6-RC1 and RC2 may corrupt ziplist-encoded sorted sets + produced by Redis 2.4.x. +* [BUGFIX] Fixed several bugs in init.d script. +* [BUGFIX] syncio.c functions modified for speed and correctness. On osx + (and possibly other BSD-based systems) the slave would block on + replication to send the SYNC command when the master was not + available. This is fixed now, but was not affecting Linux installs. +* Now when slave-serve-stale-data is set to yes and the master is down, instead + of reporting a generic error Redis replies with -MASTERDOWN. + +What's new in Redis 2.5.8 (aka 2.6 Release Candidate 2) +======================================================= + +UPGRADE URGENCY: high for all the users of the KEYS command, otherwise low. + +* [BUGFIX] Fix for KEYS command: if the DB contains keys with expires the KEYS + command may return the wrong output, having duplicated or missing + keys. See issue #487 and #488 on github for details. + +What's new in Redis 2.5.7 (aka 2.6 Release Candidate 1) +======================================================= + +UPGRADE URGENCY: upgrade not recommended because this is an RC release. + +* This is the first release candidate for Redis 2.6. We are not aware of + bugs, but part of this code is young and was never tested in production + environments, so handle with care. + +An overview of new features and changes in Redis 2.6.x +====================================================== + +* Server side Lua scripting, see http://redis.io/commands/eval +* Virtual Memory removed (was deprecated in 2.4) +* Hardcoded limits about max number of clients removed. +* AOF low level semantics is generally more sane, and especially when used + in slaves. +* Milliseconds resolution expires, also added new commands with milliseconds + precision (PEXPIRE, PTTL, ...). +* Better memory usage for "small" lists, ziplists and hashes when fields or + values contain small integers. +* Read only slaves. +* New bit opeations: BITCOUNT and BITOP commands. +* Clients max output buffer soft and hard limits. You can specifiy different + limits for different classes of clients (normal,pubsub,slave). +* More incremental (less blocking) expired keys collection algorithm, in + practical terms this means that Redis is more responsive when a very + big number of keys expire about at the same time. +* AOF is now able to rewrite aggregate data types using variadic commands, + often producing an AOF that is faster to save, load, and is smaller in size. +* Every redis.conf directive is now accepted as a command line option for the + redis-server binary, with the same name and number of arguments. +* Hash table seed randomization for protection against collisions attacks. +* Performances improved when writing large objects to Redis. +* Integrated memory test, see redis-server --test-memory. +* INCRBYFLOAT and HINCRBYFLOAT commands. +* New DUMP, RESTORE, MIGRATE commands (back ported from Redis Cluster to 2.6). +* CRC64 checksump in RDB files. +* Better MONITOR output and behavior (now commands are logged before execution). +* "Software Watchdog" feature to debug latency issues. +* Significant parts of the core refactored or rewritten. New internal APIs + and core changes allowed to develop Redis Cluster on top of the new code, + however for 2.6 all the cluster code was removed, and will be released with + Redis 3.0 when it is more complete and stable. +* Redis ASCII art logo added at startup. +* Crash report on memory violation or failed asserts improved significantly + to make debugging of hard to catch bugs simpler. +* redis-benchmark improvements: ability to run selected tests, + CSV output, faster, better help. +* redis-cli improvements: --eval for comfortable development of Lua scripts. +* SHUTDOWN now supports two optional arguments: "SAVE" and "NOSAVE". +* INFO output split into sections, the command is now able to just show + pecific sections. +* New statistics about how many time a command was called, and how much + execution time it used (INFO commandstats). +* More predictable SORT behavior in edge cases. +* Better support for big endian and *BSD systems. +* Build system improved. + +Migrating from 2.4 to 2.6 +========================= + +Redis 2.4 is mostly a strict subset of 2.6. However there are a few things +that you should be aware of: + +* You can't use .rdb and AOF files generated with 2.6 into a 2.4 instance. +* 2.6 slaves can be attached to 2.4 masters, but not the contrary, and only + for the time needed to perform the version upgrade. + +There are also a few API differences, that are unlikely to cause problems, +but it is better to keep them in mind: + +* SORT now will refuse to sort in numerical mode elements that can't be parsed + as numbers. +* EXPIREs now all have millisecond resolution (but this is very unlikely to + break code that was not conceived exploting the previous resolution error + in some way.) +* INFO output is a bit different now, and contains empty lines and comments + starting with '#'. All the major clients should be already fixed to work + with the new INFO format. +* Slaves are only read-only by default (but you can change this easily + setting the "slave-read-only" configuration option to "no" editing your + redis.conf or using CONFIG SET. + +The following INFO fields were renamed for consistency: + + changes_since_last_save -> rdb_changes_since_last_save + bgsave_in_progress -> rdb_bgsave_in_progress + last_save_time -> rdb_last_save_time + last_bgsave_status -> rdb_last_bgsave_status + bgrewriteaof_in_progress -> aof_rewrite_in_progress + bgrewriteaof_scheduled -> aof_rewrite_scheduled + +The following redis.conf and CONFIG GET / SET parameters changed: + + * hash-max-zipmap-entries, now replaced by hash-max-ziplist-entries + * hash-max-zipmap-value, now replaced by hash-max-ziplist-value + * glueoutputbuf option was now completely removed (was deprecated) + +-------------------------------------------------------------------------------- + +Credits: Where not specified the implementation and design are done by +Salvatore Sanfilippo and Pieter Noordhuis. Thanks to VMware for making all +this possible. Also many thanks to all the other contributors and the amazing +community we have. + +See commit messages for more credits. + +Cheers, +Salvatore diff --git a/CONTRIBUTING b/CONTRIBUTING index 1707874..26fe047 100644 --- a/CONTRIBUTING +++ b/CONTRIBUTING @@ -1,33 +1,34 @@ -Note: by contributing code to the Redis project in any form, including sending -a pull request via Github, a code fragment or patch via private email or -public discussion groups, you agree to release your code under the terms -of the BSD license that you can find in the COPYING file included in the Redis -source distribution. - -# IMPORTANT: HOW TO USE REDIS GITHUB ISSUES - -* Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature - requests. Everything else belongs to the Redis Google Group. - - PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected - bugs in the Github issues system. We'll be very happy to help you and provide - all the support in the Redis Google Group. - - Redis Google Group address: - - https://groups.google.com/forum/?fromgroups#!forum/redis-db - -# How to provide a patch for a new feature - -1. Drop a message to the Redis Google Group with a proposal of semantics/API. - -2. If in steps 1 you get an acknowledge from the project leaders, use the - following procedure to submit a patch: - - a. Fork Redis on github ( http://help.github.com/fork-a-repo/ ) - b. Create a topic branch (git checkout -b my_branch) - c. Push to your branch (git push origin my_branch) - d. Initiate a pull request on github ( http://help.github.com/send-pull-requests/ ) - e. Done :) - -Thanks! +Note: by contributing code to the Redis project in any form, including sending +a pull request via Github, a code fragment or patch via private email or +public discussion groups, you agree to release your code under the terms +of the BSD license that you can find in the COPYING file included in the Redis +source distribution. You will include BSD license in the COPYING file within +each source file that you contribute. + +# IMPORTANT: HOW TO USE REDIS GITHUB ISSUES + +* Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature + requests. Everything else belongs to the Redis Google Group. + + PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected + bugs in the Github issues system. We'll be very happy to help you and provide + all the support in the Redis Google Group. + + Redis Google Group address: + + https://groups.google.com/forum/?fromgroups#!forum/redis-db + +# How to provide a patch for a new feature + +1. Drop a message to the Redis Google Group with a proposal of semantics/API. + +2. If in steps 1 you get an acknowledge from the project leaders, use the + following procedure to submit a patch: + + a. Fork Redis on github ( http://help.github.com/fork-a-repo/ ) + b. Create a topic branch (git checkout -b my_branch) + c. Push to your branch (git push origin my_branch) + d. Initiate a pull request on github ( http://help.github.com/send-pull-requests/ ) + e. Done :) + +Thanks! diff --git a/src/Makefile.dep b/src/Makefile.dep index 5331189..4d24d02 100644 --- a/src/Makefile.dep +++ b/src/Makefile.dep @@ -1,101 +1,102 @@ -adlist.o: adlist.c adlist.h zmalloc.h -ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c -ae_epoll.o: ae_epoll.c -ae_kqueue.o: ae_kqueue.c -ae_select.o: ae_select.c -anet.o: anet.c fmacros.h anet.h -aof.o: aof.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h bio.h -bio.o: bio.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h bio.h -config.o: config.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -crc64.o: crc64.c -db.o: db.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h -dict.o: dict.c fmacros.h dict.h zmalloc.h -endianconv.o: endianconv.c -intset.o: intset.c intset.h zmalloc.h endianconv.h -lzf_c.o: lzf_c.c lzfP.h -lzf_d.o: lzf_d.c lzfP.h -memtest.o: memtest.c -migrate.o: migrate.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h endianconv.h -multi.o: multi.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -networking.o: networking.c redis.h fmacros.h config.h \ - ../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \ - adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \ - rio.h -object.o: object.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -pqsort.o: pqsort.c -pubsub.o: pubsub.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -rand.o: rand.c -rdb.o: rdb.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h lzf.h zipmap.h \ - endianconv.h -redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \ - ../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h -redis-check-aof.o: redis-check-aof.c fmacros.h config.h -redis-check-dump.o: redis-check-dump.c lzf.h -redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \ - sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h -redis.o: redis.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.h bio.h \ - asciilogo.h -release.o: release.c release.h -replication.o: replication.c redis.h fmacros.h config.h \ - ../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \ - adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \ - rio.h -rio.o: rio.c fmacros.h rio.h sds.h util.h -scripting.o: scripting.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h rand.h \ - ../deps/lua/src/lauxlib.h ../deps/lua/src/lua.h \ - ../deps/lua/src/lualib.h -sds.o: sds.c sds.h zmalloc.h -sha1.o: sha1.c sha1.h config.h -slowlog.o: slowlog.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.h -sort.o: sort.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h pqsort.h -syncio.o: syncio.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -t_hash.o: t_hash.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -t_list.o: t_list.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -t_set.o: t_set.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -t_string.o: t_string.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -t_zset.o: t_zset.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ - ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ - ziplist.h intset.h version.h util.h rdb.h rio.h -util.o: util.c fmacros.h util.h -ziplist.o: ziplist.c zmalloc.h util.h ziplist.h endianconv.h -zipmap.o: zipmap.c zmalloc.h endianconv.h -zmalloc.o: zmalloc.c config.h zmalloc.h +adlist.o: adlist.c adlist.h zmalloc.h +ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c +ae_epoll.o: ae_epoll.c +ae_evport.o: ae_evport.c +ae_kqueue.o: ae_kqueue.c +ae_select.o: ae_select.c +anet.o: anet.c fmacros.h anet.h +aof.o: aof.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h bio.h +bio.o: bio.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h bio.h +config.o: config.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +crc64.o: crc64.c +db.o: db.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h +dict.o: dict.c fmacros.h dict.h zmalloc.h +endianconv.o: endianconv.c +intset.o: intset.c intset.h zmalloc.h endianconv.h config.h +lzf_c.o: lzf_c.c lzfP.h +lzf_d.o: lzf_d.c lzfP.h +memtest.o: memtest.c +migrate.o: migrate.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h endianconv.h +multi.o: multi.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +networking.o: networking.c redis.h fmacros.h config.h \ + ../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \ + adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \ + rio.h +object.o: object.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +pqsort.o: pqsort.c +pubsub.o: pubsub.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +rand.o: rand.c +rdb.o: rdb.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h lzf.h zipmap.h \ + endianconv.h +redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \ + ../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h +redis-check-aof.o: redis-check-aof.c fmacros.h config.h +redis-check-dump.o: redis-check-dump.c lzf.h +redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \ + sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h anet.h ae.h +redis.o: redis.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.h bio.h \ + asciilogo.h +release.o: release.c release.h +replication.o: replication.c redis.h fmacros.h config.h \ + ../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \ + adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \ + rio.h +rio.o: rio.c fmacros.h rio.h sds.h util.h +scripting.o: scripting.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h rand.h \ + ../deps/lua/src/lauxlib.h ../deps/lua/src/lua.h \ + ../deps/lua/src/lualib.h +sds.o: sds.c sds.h zmalloc.h +sha1.o: sha1.c sha1.h config.h +slowlog.o: slowlog.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.h +sort.o: sort.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h pqsort.h +syncio.o: syncio.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +t_hash.o: t_hash.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +t_list.o: t_list.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +t_set.o: t_set.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +t_string.o: t_string.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +t_zset.o: t_zset.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \ + ../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \ + ziplist.h intset.h version.h util.h rdb.h rio.h +util.o: util.c fmacros.h util.h +ziplist.o: ziplist.c zmalloc.h util.h ziplist.h endianconv.h config.h +zipmap.o: zipmap.c zmalloc.h endianconv.h config.h +zmalloc.o: zmalloc.c config.h zmalloc.h diff --git a/src/ae.c b/src/ae.c index d2faed3..9f20e35 100644 --- a/src/ae.c +++ b/src/ae.c @@ -1,431 +1,435 @@ -/* A simple event-driven programming library. Originally I wrote this code - * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated - * it in form of a library for easy reuse. - * - * Copyright (c) 2006-2010, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ae.h" -#include "zmalloc.h" -#include "config.h" - -/* Include the best multiplexing layer supported by this system. - * The following should be ordered by performances, descending. */ -#ifdef HAVE_EVPORT -#include "ae_evport.c" -#else - #ifdef HAVE_EPOLL - #include "ae_epoll.c" - #else - #ifdef HAVE_KQUEUE - #include "ae_kqueue.c" - #else - #include "ae_select.c" - #endif - #endif -#endif - -aeEventLoop *aeCreateEventLoop(int setsize) { - aeEventLoop *eventLoop; - int i; - - if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err; - eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize); - eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize); - if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err; - eventLoop->setsize = setsize; - eventLoop->lastTime = time(NULL); - eventLoop->timeEventHead = NULL; - eventLoop->timeEventNextId = 0; - eventLoop->stop = 0; - eventLoop->maxfd = -1; - eventLoop->beforesleep = NULL; - if (aeApiCreate(eventLoop) == -1) goto err; - /* Events with mask == AE_NONE are not set. So let's initialize the - * vector with it. */ - for (i = 0; i < setsize; i++) - eventLoop->events[i].mask = AE_NONE; - return eventLoop; - -err: - if (eventLoop) { - zfree(eventLoop->events); - zfree(eventLoop->fired); - zfree(eventLoop); - } - return NULL; -} - -void aeDeleteEventLoop(aeEventLoop *eventLoop) { - aeApiFree(eventLoop); - zfree(eventLoop->events); - zfree(eventLoop->fired); - zfree(eventLoop); -} - -void aeStop(aeEventLoop *eventLoop) { - eventLoop->stop = 1; -} - -int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, - aeFileProc *proc, void *clientData) -{ - if (fd >= eventLoop->setsize) return AE_ERR; - aeFileEvent *fe = &eventLoop->events[fd]; - - if (aeApiAddEvent(eventLoop, fd, mask) == -1) - return AE_ERR; - fe->mask |= mask; - if (mask & AE_READABLE) fe->rfileProc = proc; - if (mask & AE_WRITABLE) fe->wfileProc = proc; - fe->clientData = clientData; - if (fd > eventLoop->maxfd) - eventLoop->maxfd = fd; - return AE_OK; -} - -void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask) -{ - if (fd >= eventLoop->setsize) return; - aeFileEvent *fe = &eventLoop->events[fd]; - - if (fe->mask == AE_NONE) return; - fe->mask = fe->mask & (~mask); - if (fd == eventLoop->maxfd && fe->mask == AE_NONE) { - /* Update the max fd */ - int j; - - for (j = eventLoop->maxfd-1; j >= 0; j--) - if (eventLoop->events[j].mask != AE_NONE) break; - eventLoop->maxfd = j; - } - aeApiDelEvent(eventLoop, fd, mask); -} - -int aeGetFileEvents(aeEventLoop *eventLoop, int fd) { - if (fd >= eventLoop->setsize) return 0; - aeFileEvent *fe = &eventLoop->events[fd]; - - return fe->mask; -} - -static void aeGetTime(long *seconds, long *milliseconds) -{ - struct timeval tv; - - gettimeofday(&tv, NULL); - *seconds = tv.tv_sec; - *milliseconds = tv.tv_usec/1000; -} - -static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) { - long cur_sec, cur_ms, when_sec, when_ms; - - aeGetTime(&cur_sec, &cur_ms); - when_sec = cur_sec + milliseconds/1000; - when_ms = cur_ms + milliseconds%1000; - if (when_ms >= 1000) { - when_sec ++; - when_ms -= 1000; - } - *sec = when_sec; - *ms = when_ms; -} - -long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, - aeTimeProc *proc, void *clientData, - aeEventFinalizerProc *finalizerProc) -{ - long long id = eventLoop->timeEventNextId++; - aeTimeEvent *te; - - te = zmalloc(sizeof(*te)); - if (te == NULL) return AE_ERR; - te->id = id; - aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms); - te->timeProc = proc; - te->finalizerProc = finalizerProc; - te->clientData = clientData; - te->next = eventLoop->timeEventHead; - eventLoop->timeEventHead = te; - return id; -} - -int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id) -{ - aeTimeEvent *te, *prev = NULL; - - te = eventLoop->timeEventHead; - while(te) { - if (te->id == id) { - if (prev == NULL) - eventLoop->timeEventHead = te->next; - else - prev->next = te->next; - if (te->finalizerProc) - te->finalizerProc(eventLoop, te->clientData); - zfree(te); - return AE_OK; - } - prev = te; - te = te->next; - } - return AE_ERR; /* NO event with the specified ID found */ -} - -/* Search the first timer to fire. - * This operation is useful to know how many time the select can be - * put in sleep without to delay any event. - * If there are no timers NULL is returned. - * - * Note that's O(N) since time events are unsorted. - * Possible optimizations (not needed by Redis so far, but...): - * 1) Insert the event in order, so that the nearest is just the head. - * Much better but still insertion or deletion of timers is O(N). - * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)). - */ -static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop) -{ - aeTimeEvent *te = eventLoop->timeEventHead; - aeTimeEvent *nearest = NULL; - - while(te) { - if (!nearest || te->when_sec < nearest->when_sec || - (te->when_sec == nearest->when_sec && - te->when_ms < nearest->when_ms)) - nearest = te; - te = te->next; - } - return nearest; -} - -/* Process time events */ -static int processTimeEvents(aeEventLoop *eventLoop) { - int processed = 0; - aeTimeEvent *te; - long long maxId; - time_t now = time(NULL); - - /* If the system clock is moved to the future, and then set back to the - * right value, time events may be delayed in a random way. Often this - * means that scheduled operations will not be performed soon enough. - * - * Here we try to detect system clock skews, and force all the time - * events to be processed ASAP when this happens: the idea is that - * processing events earlier is less dangerous than delaying them - * indefinitely, and practice suggests it is. */ - if (now < eventLoop->lastTime) { - te = eventLoop->timeEventHead; - while(te) { - te->when_sec = 0; - te = te->next; - } - } - eventLoop->lastTime = now; - - te = eventLoop->timeEventHead; - maxId = eventLoop->timeEventNextId-1; - while(te) { - long now_sec, now_ms; - long long id; - - if (te->id > maxId) { - te = te->next; - continue; - } - aeGetTime(&now_sec, &now_ms); - if (now_sec > te->when_sec || - (now_sec == te->when_sec && now_ms >= te->when_ms)) - { - int retval; - - id = te->id; - retval = te->timeProc(eventLoop, id, te->clientData); - processed++; - /* After an event is processed our time event list may - * no longer be the same, so we restart from head. - * Still we make sure to don't process events registered - * by event handlers itself in order to don't loop forever. - * To do so we saved the max ID we want to handle. - * - * FUTURE OPTIMIZATIONS: - * Note that this is NOT great algorithmically. Redis uses - * a single time event so it's not a problem but the right - * way to do this is to add the new elements on head, and - * to flag deleted elements in a special way for later - * deletion (putting references to the nodes to delete into - * another linked list). */ - if (retval != AE_NOMORE) { - aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); - } else { - aeDeleteTimeEvent(eventLoop, id); - } - te = eventLoop->timeEventHead; - } else { - te = te->next; - } - } - return processed; -} - -/* Process every pending time event, then every pending file event - * (that may be registered by time event callbacks just processed). - * Without special flags the function sleeps until some file event - * fires, or when the next time event occurrs (if any). - * - * If flags is 0, the function does nothing and returns. - * if flags has AE_ALL_EVENTS set, all the kind of events are processed. - * if flags has AE_FILE_EVENTS set, file events are processed. - * if flags has AE_TIME_EVENTS set, time events are processed. - * if flags has AE_DONT_WAIT set the function returns ASAP until all - * the events that's possible to process without to wait are processed. - * - * The function returns the number of events processed. */ -int aeProcessEvents(aeEventLoop *eventLoop, int flags) -{ - int processed = 0, numevents; - - /* Nothing to do? return ASAP */ - if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; - - /* Note that we want call select() even if there are no - * file events to process as long as we want to process time - * events, in order to sleep until the next time event is ready - * to fire. */ - if (eventLoop->maxfd != -1 || - ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { - int j; - aeTimeEvent *shortest = NULL; - struct timeval tv, *tvp; - - if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) - shortest = aeSearchNearestTimer(eventLoop); - if (shortest) { - long now_sec, now_ms; - - /* Calculate the time missing for the nearest - * timer to fire. */ - aeGetTime(&now_sec, &now_ms); - tvp = &tv; - tvp->tv_sec = shortest->when_sec - now_sec; - if (shortest->when_ms < now_ms) { - tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000; - tvp->tv_sec --; - } else { - tvp->tv_usec = (shortest->when_ms - now_ms)*1000; - } - if (tvp->tv_sec < 0) tvp->tv_sec = 0; - if (tvp->tv_usec < 0) tvp->tv_usec = 0; - } else { - /* If we have to check for events but need to return - * ASAP because of AE_DONT_WAIT we need to se the timeout - * to zero */ - if (flags & AE_DONT_WAIT) { - tv.tv_sec = tv.tv_usec = 0; - tvp = &tv; - } else { - /* Otherwise we can block */ - tvp = NULL; /* wait forever */ - } - } - - numevents = aeApiPoll(eventLoop, tvp); - for (j = 0; j < numevents; j++) { - aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd]; - int mask = eventLoop->fired[j].mask; - int fd = eventLoop->fired[j].fd; - int rfired = 0; - - /* note the fe->mask & mask & ... code: maybe an already processed - * event removed an element that fired and we still didn't - * processed, so we check if the event is still valid. */ - if (fe->mask & mask & AE_READABLE) { - rfired = 1; - fe->rfileProc(eventLoop,fd,fe->clientData,mask); - } - if (fe->mask & mask & AE_WRITABLE) { - if (!rfired || fe->wfileProc != fe->rfileProc) - fe->wfileProc(eventLoop,fd,fe->clientData,mask); - } - processed++; - } - } - /* Check time events */ - if (flags & AE_TIME_EVENTS) - processed += processTimeEvents(eventLoop); - - return processed; /* return the number of processed file/time events */ -} - -/* Wait for millseconds until the given file descriptor becomes - * writable/readable/exception */ -int aeWait(int fd, int mask, long long milliseconds) { - struct pollfd pfd; - int retmask = 0, retval; - - memset(&pfd, 0, sizeof(pfd)); - pfd.fd = fd; - if (mask & AE_READABLE) pfd.events |= POLLIN; - if (mask & AE_WRITABLE) pfd.events |= POLLOUT; - - if ((retval = poll(&pfd, 1, milliseconds))== 1) { - if (pfd.revents & POLLIN) retmask |= AE_READABLE; - if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE; - if (pfd.revents & POLLERR) retmask |= AE_WRITABLE; - if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE; - return retmask; - } else { - return retval; - } -} - -void aeMain(aeEventLoop *eventLoop) { - eventLoop->stop = 0; - while (!eventLoop->stop) { - if (eventLoop->beforesleep != NULL) - eventLoop->beforesleep(eventLoop); - aeProcessEvents(eventLoop, AE_ALL_EVENTS); - } -} - -char *aeGetApiName(void) { - return aeApiName(); -} - -void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) { - eventLoop->beforesleep = beforesleep; -} +/* A simple event-driven programming library. Originally I wrote this code + * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated + * it in form of a library for easy reuse. + * + * Copyright (c) 2006-2010, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ae.h" +#include "zmalloc.h" +#include "config.h" + +/* Include the best multiplexing layer supported by this system. + * The following should be ordered by performances, descending. */ +#ifdef HAVE_EVPORT +#include "ae_evport.c" +#else + #ifdef HAVE_EPOLL + #include "ae_epoll.c" + #else + #ifdef HAVE_KQUEUE + #include "ae_kqueue.c" + #else + #include "ae_select.c" + #endif + #endif +#endif + +aeEventLoop *aeCreateEventLoop(int setsize) { + aeEventLoop *eventLoop; + int i; + + if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err; + eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize); + eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize); + if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err; + eventLoop->setsize = setsize; + eventLoop->lastTime = time(NULL); + eventLoop->timeEventHead = NULL; + eventLoop->timeEventNextId = 0; + eventLoop->stop = 0; + eventLoop->maxfd = -1; + eventLoop->beforesleep = NULL; + if (aeApiCreate(eventLoop) == -1) goto err; + /* Events with mask == AE_NONE are not set. So let's initialize the + * vector with it. */ + for (i = 0; i < setsize; i++) + eventLoop->events[i].mask = AE_NONE; + return eventLoop; + +err: + if (eventLoop) { + zfree(eventLoop->events); + zfree(eventLoop->fired); + zfree(eventLoop); + } + return NULL; +} + +void aeDeleteEventLoop(aeEventLoop *eventLoop) { + aeApiFree(eventLoop); + zfree(eventLoop->events); + zfree(eventLoop->fired); + zfree(eventLoop); +} + +void aeStop(aeEventLoop *eventLoop) { + eventLoop->stop = 1; +} + +int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, + aeFileProc *proc, void *clientData) +{ + if (fd >= eventLoop->setsize) { + errno = ERANGE; + return AE_ERR; + } + aeFileEvent *fe = &eventLoop->events[fd]; + + if (aeApiAddEvent(eventLoop, fd, mask) == -1) + return AE_ERR; + fe->mask |= mask; + if (mask & AE_READABLE) fe->rfileProc = proc; + if (mask & AE_WRITABLE) fe->wfileProc = proc; + fe->clientData = clientData; + if (fd > eventLoop->maxfd) + eventLoop->maxfd = fd; + return AE_OK; +} + +void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask) +{ + if (fd >= eventLoop->setsize) return; + aeFileEvent *fe = &eventLoop->events[fd]; + + if (fe->mask == AE_NONE) return; + fe->mask = fe->mask & (~mask); + if (fd == eventLoop->maxfd && fe->mask == AE_NONE) { + /* Update the max fd */ + int j; + + for (j = eventLoop->maxfd-1; j >= 0; j--) + if (eventLoop->events[j].mask != AE_NONE) break; + eventLoop->maxfd = j; + } + aeApiDelEvent(eventLoop, fd, mask); +} + +int aeGetFileEvents(aeEventLoop *eventLoop, int fd) { + if (fd >= eventLoop->setsize) return 0; + aeFileEvent *fe = &eventLoop->events[fd]; + + return fe->mask; +} + +static void aeGetTime(long *seconds, long *milliseconds) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + *seconds = tv.tv_sec; + *milliseconds = tv.tv_usec/1000; +} + +static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) { + long cur_sec, cur_ms, when_sec, when_ms; + + aeGetTime(&cur_sec, &cur_ms); + when_sec = cur_sec + milliseconds/1000; + when_ms = cur_ms + milliseconds%1000; + if (when_ms >= 1000) { + when_sec ++; + when_ms -= 1000; + } + *sec = when_sec; + *ms = when_ms; +} + +long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, + aeTimeProc *proc, void *clientData, + aeEventFinalizerProc *finalizerProc) +{ + long long id = eventLoop->timeEventNextId++; + aeTimeEvent *te; + + te = zmalloc(sizeof(*te)); + if (te == NULL) return AE_ERR; + te->id = id; + aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms); + te->timeProc = proc; + te->finalizerProc = finalizerProc; + te->clientData = clientData; + te->next = eventLoop->timeEventHead; + eventLoop->timeEventHead = te; + return id; +} + +int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id) +{ + aeTimeEvent *te, *prev = NULL; + + te = eventLoop->timeEventHead; + while(te) { + if (te->id == id) { + if (prev == NULL) + eventLoop->timeEventHead = te->next; + else + prev->next = te->next; + if (te->finalizerProc) + te->finalizerProc(eventLoop, te->clientData); + zfree(te); + return AE_OK; + } + prev = te; + te = te->next; + } + return AE_ERR; /* NO event with the specified ID found */ +} + +/* Search the first timer to fire. + * This operation is useful to know how many time the select can be + * put in sleep without to delay any event. + * If there are no timers NULL is returned. + * + * Note that's O(N) since time events are unsorted. + * Possible optimizations (not needed by Redis so far, but...): + * 1) Insert the event in order, so that the nearest is just the head. + * Much better but still insertion or deletion of timers is O(N). + * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)). + */ +static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop) +{ + aeTimeEvent *te = eventLoop->timeEventHead; + aeTimeEvent *nearest = NULL; + + while(te) { + if (!nearest || te->when_sec < nearest->when_sec || + (te->when_sec == nearest->when_sec && + te->when_ms < nearest->when_ms)) + nearest = te; + te = te->next; + } + return nearest; +} + +/* Process time events */ +static int processTimeEvents(aeEventLoop *eventLoop) { + int processed = 0; + aeTimeEvent *te; + long long maxId; + time_t now = time(NULL); + + /* If the system clock is moved to the future, and then set back to the + * right value, time events may be delayed in a random way. Often this + * means that scheduled operations will not be performed soon enough. + * + * Here we try to detect system clock skews, and force all the time + * events to be processed ASAP when this happens: the idea is that + * processing events earlier is less dangerous than delaying them + * indefinitely, and practice suggests it is. */ + if (now < eventLoop->lastTime) { + te = eventLoop->timeEventHead; + while(te) { + te->when_sec = 0; + te = te->next; + } + } + eventLoop->lastTime = now; + + te = eventLoop->timeEventHead; + maxId = eventLoop->timeEventNextId-1; + while(te) { + long now_sec, now_ms; + long long id; + + if (te->id > maxId) { + te = te->next; + continue; + } + aeGetTime(&now_sec, &now_ms); + if (now_sec > te->when_sec || + (now_sec == te->when_sec && now_ms >= te->when_ms)) + { + int retval; + + id = te->id; + retval = te->timeProc(eventLoop, id, te->clientData); + processed++; + /* After an event is processed our time event list may + * no longer be the same, so we restart from head. + * Still we make sure to don't process events registered + * by event handlers itself in order to don't loop forever. + * To do so we saved the max ID we want to handle. + * + * FUTURE OPTIMIZATIONS: + * Note that this is NOT great algorithmically. Redis uses + * a single time event so it's not a problem but the right + * way to do this is to add the new elements on head, and + * to flag deleted elements in a special way for later + * deletion (putting references to the nodes to delete into + * another linked list). */ + if (retval != AE_NOMORE) { + aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); + } else { + aeDeleteTimeEvent(eventLoop, id); + } + te = eventLoop->timeEventHead; + } else { + te = te->next; + } + } + return processed; +} + +/* Process every pending time event, then every pending file event + * (that may be registered by time event callbacks just processed). + * Without special flags the function sleeps until some file event + * fires, or when the next time event occurrs (if any). + * + * If flags is 0, the function does nothing and returns. + * if flags has AE_ALL_EVENTS set, all the kind of events are processed. + * if flags has AE_FILE_EVENTS set, file events are processed. + * if flags has AE_TIME_EVENTS set, time events are processed. + * if flags has AE_DONT_WAIT set the function returns ASAP until all + * the events that's possible to process without to wait are processed. + * + * The function returns the number of events processed. */ +int aeProcessEvents(aeEventLoop *eventLoop, int flags) +{ + int processed = 0, numevents; + + /* Nothing to do? return ASAP */ + if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; + + /* Note that we want call select() even if there are no + * file events to process as long as we want to process time + * events, in order to sleep until the next time event is ready + * to fire. */ + if (eventLoop->maxfd != -1 || + ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { + int j; + aeTimeEvent *shortest = NULL; + struct timeval tv, *tvp; + + if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) + shortest = aeSearchNearestTimer(eventLoop); + if (shortest) { + long now_sec, now_ms; + + /* Calculate the time missing for the nearest + * timer to fire. */ + aeGetTime(&now_sec, &now_ms); + tvp = &tv; + tvp->tv_sec = shortest->when_sec - now_sec; + if (shortest->when_ms < now_ms) { + tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000; + tvp->tv_sec --; + } else { + tvp->tv_usec = (shortest->when_ms - now_ms)*1000; + } + if (tvp->tv_sec < 0) tvp->tv_sec = 0; + if (tvp->tv_usec < 0) tvp->tv_usec = 0; + } else { + /* If we have to check for events but need to return + * ASAP because of AE_DONT_WAIT we need to se the timeout + * to zero */ + if (flags & AE_DONT_WAIT) { + tv.tv_sec = tv.tv_usec = 0; + tvp = &tv; + } else { + /* Otherwise we can block */ + tvp = NULL; /* wait forever */ + } + } + + numevents = aeApiPoll(eventLoop, tvp); + for (j = 0; j < numevents; j++) { + aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd]; + int mask = eventLoop->fired[j].mask; + int fd = eventLoop->fired[j].fd; + int rfired = 0; + + /* note the fe->mask & mask & ... code: maybe an already processed + * event removed an element that fired and we still didn't + * processed, so we check if the event is still valid. */ + if (fe->mask & mask & AE_READABLE) { + rfired = 1; + fe->rfileProc(eventLoop,fd,fe->clientData,mask); + } + if (fe->mask & mask & AE_WRITABLE) { + if (!rfired || fe->wfileProc != fe->rfileProc) + fe->wfileProc(eventLoop,fd,fe->clientData,mask); + } + processed++; + } + } + /* Check time events */ + if (flags & AE_TIME_EVENTS) + processed += processTimeEvents(eventLoop); + + return processed; /* return the number of processed file/time events */ +} + +/* Wait for millseconds until the given file descriptor becomes + * writable/readable/exception */ +int aeWait(int fd, int mask, long long milliseconds) { + struct pollfd pfd; + int retmask = 0, retval; + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = fd; + if (mask & AE_READABLE) pfd.events |= POLLIN; + if (mask & AE_WRITABLE) pfd.events |= POLLOUT; + + if ((retval = poll(&pfd, 1, milliseconds))== 1) { + if (pfd.revents & POLLIN) retmask |= AE_READABLE; + if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE; + if (pfd.revents & POLLERR) retmask |= AE_WRITABLE; + if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE; + return retmask; + } else { + return retval; + } +} + +void aeMain(aeEventLoop *eventLoop) { + eventLoop->stop = 0; + while (!eventLoop->stop) { + if (eventLoop->beforesleep != NULL) + eventLoop->beforesleep(eventLoop); + aeProcessEvents(eventLoop, AE_ALL_EVENTS); + } +} + +char *aeGetApiName(void) { + return aeApiName(); +} + +void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) { + eventLoop->beforesleep = beforesleep; +} diff --git a/src/aof.c b/src/aof.c index 6289e4d..3ad1382 100644 --- a/src/aof.c +++ b/src/aof.c @@ -1,1172 +1,1173 @@ -/* - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "redis.h" -#include "bio.h" -#include "rio.h" - -#include -#include -#include -#include -#include -#include -#include - -void aofUpdateCurrentSize(void); - -/* ---------------------------------------------------------------------------- - * AOF rewrite buffer implementation. - * - * The following code implement a simple buffer used in order to accumulate - * changes while the background process is rewriting the AOF file. - * - * We only need to append, but can't just use realloc with a large block - * because 'huge' reallocs are not always handled as one could expect - * (via remapping of pages at OS level) but may involve copying data. - * - * For this reason we use a list of blocks, every block is - * AOF_RW_BUF_BLOCK_SIZE bytes. - * ------------------------------------------------------------------------- */ - -#define AOF_RW_BUF_BLOCK_SIZE (1024*1024*10) /* 10 MB per block */ - -typedef struct aofrwblock { - unsigned long used, free; - char buf[AOF_RW_BUF_BLOCK_SIZE]; -} aofrwblock; - -/* This function free the old AOF rewrite buffer if needed, and initialize - * a fresh new one. It tests for server.aof_rewrite_buf_blocks equal to NULL - * so can be used for the first initialization as well. */ -void aofRewriteBufferReset(void) { - if (server.aof_rewrite_buf_blocks) - listRelease(server.aof_rewrite_buf_blocks); - - server.aof_rewrite_buf_blocks = listCreate(); - listSetFreeMethod(server.aof_rewrite_buf_blocks,zfree); -} - -/* Return the current size of the AOF rerwite buffer. */ -unsigned long aofRewriteBufferSize(void) { - listNode *ln = listLast(server.aof_rewrite_buf_blocks); - aofrwblock *block = ln ? ln->value : NULL; - - if (block == NULL) return 0; - unsigned long size = - (listLength(server.aof_rewrite_buf_blocks)-1) * AOF_RW_BUF_BLOCK_SIZE; - size += block->used; - return size; -} - -/* Append data to the AOF rewrite buffer, allocating new blocks if needed. */ -void aofRewriteBufferAppend(unsigned char *s, unsigned long len) { - listNode *ln = listLast(server.aof_rewrite_buf_blocks); - aofrwblock *block = ln ? ln->value : NULL; - - while(len) { - /* If we already got at least an allocated block, try appending - * at least some piece into it. */ - if (block) { - unsigned long thislen = (block->free < len) ? block->free : len; - if (thislen) { /* The current block is not already full. */ - memcpy(block->buf+block->used, s, thislen); - block->used += thislen; - block->free -= thislen; - s += thislen; - len -= thislen; - } - } - - if (len) { /* First block to allocate, or need another block. */ - int numblocks; - - block = zmalloc(sizeof(*block)); - block->free = AOF_RW_BUF_BLOCK_SIZE; - block->used = 0; - listAddNodeTail(server.aof_rewrite_buf_blocks,block); - - /* Log every time we cross more 10 or 100 blocks, respectively - * as a notice or warning. */ - numblocks = listLength(server.aof_rewrite_buf_blocks); - if (((numblocks+1) % 10) == 0) { - int level = ((numblocks+1) % 100) == 0 ? REDIS_WARNING : - REDIS_NOTICE; - redisLog(level,"Background AOF buffer size: %lu MB", - aofRewriteBufferSize()/(1024*1024)); - } - } - } -} - -/* Write the buffer (possibly composed of multiple blocks) into the specified - * fd. If no short write or any other error happens -1 is returned, - * otherwise the number of bytes written is returned. */ -ssize_t aofRewriteBufferWrite(int fd) { - listNode *ln; - listIter li; - ssize_t count = 0; - - listRewind(server.aof_rewrite_buf_blocks,&li); - while((ln = listNext(&li))) { - aofrwblock *block = listNodeValue(ln); - ssize_t nwritten; - - if (block->used) { - nwritten = write(fd,block->buf,block->used); - if (nwritten != block->used) { - if (nwritten == 0) errno = EIO; - return -1; - } - count += nwritten; - } - } - return count; -} - -/* ---------------------------------------------------------------------------- - * AOF file implementation - * ------------------------------------------------------------------------- */ - -/* Starts a background task that performs fsync() against the specified - * file descriptor (the one of the AOF file) in another thread. */ -void aof_background_fsync(int fd) { - bioCreateBackgroundJob(REDIS_BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL); -} - -/* Called when the user switches from "appendonly yes" to "appendonly no" - * at runtime using the CONFIG command. */ -void stopAppendOnly(void) { - redisAssert(server.aof_state != REDIS_AOF_OFF); - flushAppendOnlyFile(1); - aof_fsync(server.aof_fd); - close(server.aof_fd); - - server.aof_fd = -1; - server.aof_selected_db = -1; - server.aof_state = REDIS_AOF_OFF; - /* rewrite operation in progress? kill it, wait child exit */ - if (server.aof_child_pid != -1) { - int statloc; - - redisLog(REDIS_NOTICE,"Killing running AOF rewrite child: %ld", - (long) server.aof_child_pid); - if (kill(server.aof_child_pid,SIGKILL) != -1) - wait3(&statloc,0,NULL); - /* reset the buffer accumulating changes while the child saves */ - aofRewriteBufferReset(); - aofRemoveTempFile(server.aof_child_pid); - server.aof_child_pid = -1; - server.aof_rewrite_time_start = -1; - } -} - -/* Called when the user switches from "appendonly no" to "appendonly yes" - * at runtime using the CONFIG command. */ -int startAppendOnly(void) { - server.aof_last_fsync = server.unixtime; - server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644); - redisAssert(server.aof_state == REDIS_AOF_OFF); - if (server.aof_fd == -1) { - redisLog(REDIS_WARNING,"Redis needs to enable the AOF but can't open the append only file: %s",strerror(errno)); - return REDIS_ERR; - } - if (rewriteAppendOnlyFileBackground() == REDIS_ERR) { - close(server.aof_fd); - redisLog(REDIS_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error."); - return REDIS_ERR; - } - /* We correctly switched on AOF, now wait for the rerwite to be complete - * in order to append data on disk. */ - server.aof_state = REDIS_AOF_WAIT_REWRITE; - return REDIS_OK; -} - -/* Write the append only file buffer on disk. - * - * Since we are required to write the AOF before replying to the client, - * and the only way the client socket can get a write is entering when the - * the event loop, we accumulate all the AOF writes in a memory - * buffer and write it on disk using this function just before entering - * the event loop again. - * - * About the 'force' argument: - * - * When the fsync policy is set to 'everysec' we may delay the flush if there - * is still an fsync() going on in the background thread, since for instance - * on Linux write(2) will be blocked by the background fsync anyway. - * When this happens we remember that there is some aof buffer to be - * flushed ASAP, and will try to do that in the serverCron() function. - * - * However if force is set to 1 we'll write regardless of the background - * fsync. */ -void flushAppendOnlyFile(int force) { - ssize_t nwritten; - int sync_in_progress = 0; - - if (sdslen(server.aof_buf) == 0) return; - - if (server.aof_fsync == AOF_FSYNC_EVERYSEC) - sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0; - - if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) { - /* With this append fsync policy we do background fsyncing. - * If the fsync is still in progress we can try to delay - * the write for a couple of seconds. */ - if (sync_in_progress) { - if (server.aof_flush_postponed_start == 0) { - /* No previous write postponinig, remember that we are - * postponing the flush and return. */ - server.aof_flush_postponed_start = server.unixtime; - return; - } else if (server.unixtime - server.aof_flush_postponed_start < 2) { - /* We were already waiting for fsync to finish, but for less - * than two seconds this is still ok. Postpone again. */ - return; - } - /* Otherwise fall trough, and go write since we can't wait - * over two seconds. */ - server.aof_delayed_fsync++; - redisLog(REDIS_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis."); - } - } - /* If you are following this code path, then we are going to write so - * set reset the postponed flush sentinel to zero. */ - server.aof_flush_postponed_start = 0; - - /* We want to perform a single write. This should be guaranteed atomic - * at least if the filesystem we are writing is a real physical one. - * While this will save us against the server being killed I don't think - * there is much to do about the whole server stopping for power problems - * or alike */ - nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf)); - if (nwritten != (signed)sdslen(server.aof_buf)) { - /* Ooops, we are in troubles. The best thing to do for now is - * aborting instead of giving the illusion that everything is - * working as expected. */ - if (nwritten == -1) { - redisLog(REDIS_WARNING,"Exiting on error writing to the append-only file: %s",strerror(errno)); - } else { - redisLog(REDIS_WARNING,"Exiting on short write while writing to " - "the append-only file: %s (nwritten=%ld, " - "expected=%ld)", - strerror(errno), - (long)nwritten, - (long)sdslen(server.aof_buf)); - - if (ftruncate(server.aof_fd, server.aof_current_size) == -1) { - redisLog(REDIS_WARNING, "Could not remove short write " - "from the append-only file. Redis may refuse " - "to load the AOF the next time it starts. " - "ftruncate: %s", strerror(errno)); - } - } - exit(1); - } - server.aof_current_size += nwritten; - - /* Re-use AOF buffer when it is small enough. The maximum comes from the - * arena size of 4k minus some overhead (but is otherwise arbitrary). */ - if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) { - sdsclear(server.aof_buf); - } else { - sdsfree(server.aof_buf); - server.aof_buf = sdsempty(); - } - - /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are - * children doing I/O in the background. */ - if (server.aof_no_fsync_on_rewrite && - (server.aof_child_pid != -1 || server.rdb_child_pid != -1)) - return; - - /* Perform the fsync if needed. */ - if (server.aof_fsync == AOF_FSYNC_ALWAYS) { - /* aof_fsync is defined as fdatasync() for Linux in order to avoid - * flushing metadata. */ - aof_fsync(server.aof_fd); /* Let's try to get this data on the disk */ - server.aof_last_fsync = server.unixtime; - } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC && - server.unixtime > server.aof_last_fsync)) { - if (!sync_in_progress) aof_background_fsync(server.aof_fd); - server.aof_last_fsync = server.unixtime; - } -} - -sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) { - char buf[32]; - int len, j; - robj *o; - - buf[0] = '*'; - len = 1+ll2string(buf+1,sizeof(buf)-1,argc); - buf[len++] = '\r'; - buf[len++] = '\n'; - dst = sdscatlen(dst,buf,len); - - for (j = 0; j < argc; j++) { - o = getDecodedObject(argv[j]); - buf[0] = '$'; - len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(o->ptr)); - buf[len++] = '\r'; - buf[len++] = '\n'; - dst = sdscatlen(dst,buf,len); - dst = sdscatlen(dst,o->ptr,sdslen(o->ptr)); - dst = sdscatlen(dst,"\r\n",2); - decrRefCount(o); - } - return dst; -} - -/* Create the sds representation of an PEXPIREAT command, using - * 'seconds' as time to live and 'cmd' to understand what command - * we are translating into a PEXPIREAT. - * - * This command is used in order to translate EXPIRE and PEXPIRE commands - * into PEXPIREAT command so that we retain precision in the append only - * file, and the time is always absolute and not relative. */ -sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, robj *seconds) { - long long when; - robj *argv[3]; - - /* Make sure we can use strtol */ - seconds = getDecodedObject(seconds); - when = strtoll(seconds->ptr,NULL,10); - /* Convert argument into milliseconds for EXPIRE, SETEX, EXPIREAT */ - if (cmd->proc == expireCommand || cmd->proc == setexCommand || - cmd->proc == expireatCommand) - { - when *= 1000; - } - /* Convert into absolute time for EXPIRE, PEXPIRE, SETEX, PSETEX */ - if (cmd->proc == expireCommand || cmd->proc == pexpireCommand || - cmd->proc == setexCommand || cmd->proc == psetexCommand) - { - when += mstime(); - } - decrRefCount(seconds); - - argv[0] = createStringObject("PEXPIREAT",9); - argv[1] = key; - argv[2] = createStringObjectFromLongLong(when); - buf = catAppendOnlyGenericCommand(buf, 3, argv); - decrRefCount(argv[0]); - decrRefCount(argv[2]); - return buf; -} - -void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) { - sds buf = sdsempty(); - robj *tmpargv[3]; - - /* The DB this command was targetting is not the same as the last command - * we appendend. To issue a SELECT command is needed. */ - if (dictid != server.aof_selected_db) { - char seldb[64]; - - snprintf(seldb,sizeof(seldb),"%d",dictid); - buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n", - (unsigned long)strlen(seldb),seldb); - server.aof_selected_db = dictid; - } - - if (cmd->proc == expireCommand || cmd->proc == pexpireCommand || - cmd->proc == expireatCommand) { - /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */ - buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]); - } else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) { - /* Translate SETEX/PSETEX to SET and PEXPIREAT */ - tmpargv[0] = createStringObject("SET",3); - tmpargv[1] = argv[1]; - tmpargv[2] = argv[3]; - buf = catAppendOnlyGenericCommand(buf,3,tmpargv); - decrRefCount(tmpargv[0]); - buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]); - } else { - /* All the other commands don't need translation or need the - * same translation already operated in the command vector - * for the replication itself. */ - buf = catAppendOnlyGenericCommand(buf,argc,argv); - } - - /* Append to the AOF buffer. This will be flushed on disk just before - * of re-entering the event loop, so before the client will get a - * positive reply about the operation performed. */ - if (server.aof_state == REDIS_AOF_ON) - server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf)); - - /* If a background append only file rewriting is in progress we want to - * accumulate the differences between the child DB and the current one - * in a buffer, so that when the child process will do its work we - * can append the differences to the new append only file. */ - if (server.aof_child_pid != -1) - aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf)); - - sdsfree(buf); -} - -/* ---------------------------------------------------------------------------- - * AOF loading - * ------------------------------------------------------------------------- */ - -/* In Redis commands are always executed in the context of a client, so in - * order to load the append only file we need to create a fake client. */ -struct redisClient *createFakeClient(void) { - struct redisClient *c = zmalloc(sizeof(*c)); - - selectDb(c,0); - c->fd = -1; - c->querybuf = sdsempty(); - c->querybuf_peak = 0; - c->argc = 0; - c->argv = NULL; - c->bufpos = 0; - c->flags = 0; - /* We set the fake client as a slave waiting for the synchronization - * so that Redis will not try to send replies to this client. */ - c->replstate = REDIS_REPL_WAIT_BGSAVE_START; - c->reply = listCreate(); - c->reply_bytes = 0; - c->obuf_soft_limit_reached_time = 0; - c->watched_keys = listCreate(); - listSetFreeMethod(c->reply,decrRefCount); - listSetDupMethod(c->reply,dupClientReplyValue); - initClientMultiState(c); - return c; -} - -void freeFakeClient(struct redisClient *c) { - sdsfree(c->querybuf); - listRelease(c->reply); - listRelease(c->watched_keys); - freeClientMultiState(c); - zfree(c); -} - -/* Replay the append log file. On error REDIS_OK is returned. On non fatal - * error (the append only file is zero-length) REDIS_ERR is returned. On - * fatal error an error message is logged and the program exists. */ -int loadAppendOnlyFile(char *filename) { - struct redisClient *fakeClient; - FILE *fp = fopen(filename,"r"); - struct redis_stat sb; - int old_aof_state = server.aof_state; - long loops = 0; - - if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) { - server.aof_current_size = 0; - fclose(fp); - return REDIS_ERR; - } - - if (fp == NULL) { - redisLog(REDIS_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno)); - exit(1); - } - - /* Temporarily disable AOF, to prevent EXEC from feeding a MULTI - * to the same file we're about to read. */ - server.aof_state = REDIS_AOF_OFF; - - fakeClient = createFakeClient(); - startLoading(fp); - - while(1) { - int argc, j; - unsigned long len; - robj **argv; - char buf[128]; - sds argsds; - struct redisCommand *cmd; - - /* Serve the clients from time to time */ - if (!(loops++ % 1000)) { - loadingProgress(ftello(fp)); - aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT); - } - - if (fgets(buf,sizeof(buf),fp) == NULL) { - if (feof(fp)) - break; - else - goto readerr; - } - if (buf[0] != '*') goto fmterr; - argc = atoi(buf+1); - if (argc < 1) goto fmterr; - - argv = zmalloc(sizeof(robj*)*argc); - for (j = 0; j < argc; j++) { - if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr; - if (buf[0] != '$') goto fmterr; - len = strtol(buf+1,NULL,10); - argsds = sdsnewlen(NULL,len); - if (len && fread(argsds,len,1,fp) == 0) goto fmterr; - argv[j] = createObject(REDIS_STRING,argsds); - if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */ - } - - /* Command lookup */ - cmd = lookupCommand(argv[0]->ptr); - if (!cmd) { - redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr); - exit(1); - } - /* Run the command in the context of a fake client */ - fakeClient->argc = argc; - fakeClient->argv = argv; - cmd->proc(fakeClient); - - /* The fake client should not have a reply */ - redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0); - /* The fake client should never get blocked */ - redisAssert((fakeClient->flags & REDIS_BLOCKED) == 0); - - /* 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 < fakeClient->argc; j++) - decrRefCount(fakeClient->argv[j]); - zfree(fakeClient->argv); - } - - /* This point can only be reached when EOF is reached without errors. - * If the client is in the middle of a MULTI/EXEC, log error and quit. */ - if (fakeClient->flags & REDIS_MULTI) goto readerr; - - fclose(fp); - freeFakeClient(fakeClient); - server.aof_state = old_aof_state; - stopLoading(); - aofUpdateCurrentSize(); - server.aof_rewrite_base_size = server.aof_current_size; - return REDIS_OK; - -readerr: - if (feof(fp)) { - redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file"); - } else { - redisLog(REDIS_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno)); - } - exit(1); -fmterr: - redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix "); - exit(1); -} - -/* ---------------------------------------------------------------------------- - * AOF rewrite - * ------------------------------------------------------------------------- */ - -/* Delegate writing an object to writing a bulk string or bulk long long. - * This is not placed in rio.c since that adds the redis.h dependency. */ -int rioWriteBulkObject(rio *r, robj *obj) { - /* Avoid using getDecodedObject to help copy-on-write (we are often - * in a child process when this function is called). */ - if (obj->encoding == REDIS_ENCODING_INT) { - return rioWriteBulkLongLong(r,(long)obj->ptr); - } else if (obj->encoding == REDIS_ENCODING_RAW) { - return rioWriteBulkString(r,obj->ptr,sdslen(obj->ptr)); - } else { - redisPanic("Unknown string encoding"); - } -} - -/* Emit the commands needed to rebuild a list object. - * The function returns 0 on error, 1 on success. */ -int rewriteListObject(rio *r, robj *key, robj *o) { - long long count = 0, items = listTypeLength(o); - - if (o->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *zl = o->ptr; - unsigned char *p = ziplistIndex(zl,0); - unsigned char *vstr; - unsigned int vlen; - long long vlong; - - while(ziplistGet(p,&vstr,&vlen,&vlong)) { - if (count == 0) { - int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? - REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; - - if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; - if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0; - if (rioWriteBulkObject(r,key) == 0) return 0; - } - if (vstr) { - if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0; - } else { - if (rioWriteBulkLongLong(r,vlong) == 0) return 0; - } - p = ziplistNext(zl,p); - if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; - items--; - } - } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { - list *list = o->ptr; - listNode *ln; - listIter li; - - listRewind(list,&li); - while((ln = listNext(&li))) { - robj *eleobj = listNodeValue(ln); - - if (count == 0) { - int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? - REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; - - if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; - if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0; - if (rioWriteBulkObject(r,key) == 0) return 0; - } - if (rioWriteBulkObject(r,eleobj) == 0) return 0; - if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; - items--; - } - } else { - redisPanic("Unknown list encoding"); - } - return 1; -} - -/* Emit the commands needed to rebuild a set object. - * The function returns 0 on error, 1 on success. */ -int rewriteSetObject(rio *r, robj *key, robj *o) { - long long count = 0, items = setTypeSize(o); - - if (o->encoding == REDIS_ENCODING_INTSET) { - int ii = 0; - int64_t llval; - - while(intsetGet(o->ptr,ii++,&llval)) { - if (count == 0) { - int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? - REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; - - if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; - if (rioWriteBulkString(r,"SADD",4) == 0) return 0; - if (rioWriteBulkObject(r,key) == 0) return 0; - } - if (rioWriteBulkLongLong(r,llval) == 0) return 0; - if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; - items--; - } - } else if (o->encoding == REDIS_ENCODING_HT) { - dictIterator *di = dictGetIterator(o->ptr); - dictEntry *de; - - while((de = dictNext(di)) != NULL) { - robj *eleobj = dictGetKey(de); - if (count == 0) { - int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? - REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; - - if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; - if (rioWriteBulkString(r,"SADD",4) == 0) return 0; - if (rioWriteBulkObject(r,key) == 0) return 0; - } - if (rioWriteBulkObject(r,eleobj) == 0) return 0; - if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; - items--; - } - dictReleaseIterator(di); - } else { - redisPanic("Unknown set encoding"); - } - return 1; -} - -/* Emit the commands needed to rebuild a sorted set object. - * The function returns 0 on error, 1 on success. */ -int rewriteSortedSetObject(rio *r, robj *key, robj *o) { - long long count = 0, items = zsetLength(o); - - if (o->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *zl = o->ptr; - unsigned char *eptr, *sptr; - unsigned char *vstr; - unsigned int vlen; - long long vll; - double score; - - eptr = ziplistIndex(zl,0); - redisAssert(eptr != NULL); - sptr = ziplistNext(zl,eptr); - redisAssert(sptr != NULL); - - while (eptr != NULL) { - redisAssert(ziplistGet(eptr,&vstr,&vlen,&vll)); - score = zzlGetScore(sptr); - - if (count == 0) { - int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? - REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; - - if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; - if (rioWriteBulkString(r,"ZADD",4) == 0) return 0; - if (rioWriteBulkObject(r,key) == 0) return 0; - } - if (rioWriteBulkDouble(r,score) == 0) return 0; - if (vstr != NULL) { - if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0; - } else { - if (rioWriteBulkLongLong(r,vll) == 0) return 0; - } - zzlNext(zl,&eptr,&sptr); - if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; - items--; - } - } else if (o->encoding == REDIS_ENCODING_SKIPLIST) { - zset *zs = o->ptr; - dictIterator *di = dictGetIterator(zs->dict); - dictEntry *de; - - while((de = dictNext(di)) != NULL) { - robj *eleobj = dictGetKey(de); - double *score = dictGetVal(de); - - if (count == 0) { - int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? - REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; - - if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; - if (rioWriteBulkString(r,"ZADD",4) == 0) return 0; - if (rioWriteBulkObject(r,key) == 0) return 0; - } - if (rioWriteBulkDouble(r,*score) == 0) return 0; - if (rioWriteBulkObject(r,eleobj) == 0) return 0; - if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; - items--; - } - dictReleaseIterator(di); - } else { - redisPanic("Unknown sorted zset encoding"); - } - return 1; -} - -/* Write either the key or the value of the currently selected item of an hash. - * The 'hi' argument passes a valid Redis hash iterator. - * The 'what' filed specifies if to write a key or a value and can be - * either REDIS_HASH_KEY or REDIS_HASH_VALUE. - * - * The function returns 0 on error, non-zero on success. */ -static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) { - if (hi->encoding == REDIS_ENCODING_ZIPLIST) { - unsigned char *vstr = NULL; - unsigned int vlen = UINT_MAX; - long long vll = LLONG_MAX; - - hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); - if (vstr) { - return rioWriteBulkString(r, (char*)vstr, vlen); - } else { - return rioWriteBulkLongLong(r, vll); - } - - } else if (hi->encoding == REDIS_ENCODING_HT) { - robj *value; - - hashTypeCurrentFromHashTable(hi, what, &value); - return rioWriteBulkObject(r, value); - } - - redisPanic("Unknown hash encoding"); - return 0; -} - -/* Emit the commands needed to rebuild a hash object. - * The function returns 0 on error, 1 on success. */ -int rewriteHashObject(rio *r, robj *key, robj *o) { - hashTypeIterator *hi; - long long count = 0, items = hashTypeLength(o); - - hi = hashTypeInitIterator(o); - while (hashTypeNext(hi) != REDIS_ERR) { - if (count == 0) { - int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? - REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; - - if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; - if (rioWriteBulkString(r,"HMSET",5) == 0) return 0; - if (rioWriteBulkObject(r,key) == 0) return 0; - } - - if (rioWriteHashIteratorCursor(r, hi, REDIS_HASH_KEY) == 0) return 0; - if (rioWriteHashIteratorCursor(r, hi, REDIS_HASH_VALUE) == 0) return 0; - if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; - items--; - } - - hashTypeReleaseIterator(hi); - - return 1; -} - -/* Write a sequence of commands able to fully rebuild the dataset into - * "filename". Used both by REWRITEAOF and BGREWRITEAOF. - * - * In order to minimize the number of commands needed in the rewritten - * log Redis uses variadic commands when possible, such as RPUSH, SADD - * and ZADD. However at max REDIS_AOF_REWRITE_ITEMS_PER_CMD items per time - * are inserted using a single command. */ -int rewriteAppendOnlyFile(char *filename) { - dictIterator *di = NULL; - dictEntry *de; - rio aof; - FILE *fp; - char tmpfile[256]; - int j; - long long now = mstime(); - - /* Note that we have to use a different temp name here compared to the - * one used by rewriteAppendOnlyFileBackground() function. */ - snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid()); - fp = fopen(tmpfile,"w"); - if (!fp) { - redisLog(REDIS_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno)); - return REDIS_ERR; - } - - rioInitWithFile(&aof,fp); - for (j = 0; j < server.dbnum; j++) { - char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n"; - redisDb *db = server.db+j; - dict *d = db->dict; - if (dictSize(d) == 0) continue; - di = dictGetSafeIterator(d); - if (!di) { - fclose(fp); - return REDIS_ERR; - } - - /* SELECT the new DB */ - if (rioWrite(&aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr; - if (rioWriteBulkLongLong(&aof,j) == 0) goto werr; - - /* Iterate this DB writing every entry */ - while((de = dictNext(di)) != NULL) { - sds keystr; - robj key, *o; - long long expiretime; - - keystr = dictGetKey(de); - o = dictGetVal(de); - initStaticStringObject(key,keystr); - - expiretime = getExpire(db,&key); - - /* Save the key and associated value */ - if (o->type == REDIS_STRING) { - /* Emit a SET command */ - char cmd[]="*3\r\n$3\r\nSET\r\n"; - if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr; - /* Key and value */ - if (rioWriteBulkObject(&aof,&key) == 0) goto werr; - if (rioWriteBulkObject(&aof,o) == 0) goto werr; - } else if (o->type == REDIS_LIST) { - if (rewriteListObject(&aof,&key,o) == 0) goto werr; - } else if (o->type == REDIS_SET) { - if (rewriteSetObject(&aof,&key,o) == 0) goto werr; - } else if (o->type == REDIS_ZSET) { - if (rewriteSortedSetObject(&aof,&key,o) == 0) goto werr; - } else if (o->type == REDIS_HASH) { - if (rewriteHashObject(&aof,&key,o) == 0) goto werr; - } else { - redisPanic("Unknown object type"); - } - /* Save the expire time */ - if (expiretime != -1) { - char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n"; - /* If this key is already expired skip it */ - if (expiretime < now) continue; - if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr; - if (rioWriteBulkObject(&aof,&key) == 0) goto werr; - if (rioWriteBulkLongLong(&aof,expiretime) == 0) goto werr; - } - } - dictReleaseIterator(di); - } - - /* Make sure data will not remain on the OS's output buffers */ - fflush(fp); - aof_fsync(fileno(fp)); - fclose(fp); - - /* Use RENAME to make sure the DB file is changed atomically only - * if the generate DB file is ok. */ - if (rename(tmpfile,filename) == -1) { - redisLog(REDIS_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno)); - unlink(tmpfile); - return REDIS_ERR; - } - redisLog(REDIS_NOTICE,"SYNC append only file rewrite performed"); - return REDIS_OK; - -werr: - fclose(fp); - unlink(tmpfile); - redisLog(REDIS_WARNING,"Write error writing append only file on disk: %s", strerror(errno)); - if (di) dictReleaseIterator(di); - return REDIS_ERR; -} - -/* This is how rewriting of the append only file in background works: - * - * 1) The user calls BGREWRITEAOF - * 2) Redis calls this function, that forks(): - * 2a) the child rewrite the append only file in a temp file. - * 2b) the parent accumulates differences in server.aof_rewrite_buf. - * 3) When the child finished '2a' exists. - * 4) The parent will trap the exit code, if it's OK, will append the - * data accumulated into server.aof_rewrite_buf into the temp file, and - * finally will rename(2) the temp file in the actual file name. - * The the new file is reopened as the new append only file. Profit! - */ -int rewriteAppendOnlyFileBackground(void) { - pid_t childpid; - long long start; - - if (server.aof_child_pid != -1) return REDIS_ERR; - start = ustime(); - if ((childpid = fork()) == 0) { - char tmpfile[256]; - - /* Child */ - if (server.ipfd > 0) close(server.ipfd); - if (server.sofd > 0) close(server.sofd); - snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); - if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) { - size_t private_dirty = zmalloc_get_private_dirty(); - - if (private_dirty) { - redisLog(REDIS_NOTICE, - "AOF rewrite: %lu MB of memory used by copy-on-write", - private_dirty/(1024*1024)); - } - exitFromChild(0); - } else { - exitFromChild(1); - } - } else { - /* Parent */ - server.stat_fork_time = ustime()-start; - if (childpid == -1) { - redisLog(REDIS_WARNING, - "Can't rewrite append only file in background: fork: %s", - strerror(errno)); - return REDIS_ERR; - } - redisLog(REDIS_NOTICE, - "Background append only file rewriting started by pid %d",childpid); - server.aof_rewrite_scheduled = 0; - server.aof_rewrite_time_start = time(NULL); - server.aof_child_pid = childpid; - updateDictResizePolicy(); - /* We set appendseldb to -1 in order to force the next call to the - * feedAppendOnlyFile() to issue a SELECT command, so the differences - * accumulated by the parent into server.aof_rewrite_buf will start - * with a SELECT statement and it will be safe to merge. */ - server.aof_selected_db = -1; - return REDIS_OK; - } - return REDIS_OK; /* unreached */ -} - -void bgrewriteaofCommand(redisClient *c) { - if (server.aof_child_pid != -1) { - addReplyError(c,"Background append only file rewriting already in progress"); - } else if (server.rdb_child_pid != -1) { - server.aof_rewrite_scheduled = 1; - addReplyStatus(c,"Background append only file rewriting scheduled"); - } else if (rewriteAppendOnlyFileBackground() == REDIS_OK) { - addReplyStatus(c,"Background append only file rewriting started"); - } else { - addReply(c,shared.err); - } -} - -void aofRemoveTempFile(pid_t childpid) { - char tmpfile[256]; - - snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) childpid); - unlink(tmpfile); -} - -/* Update the server.aof_current_size filed explicitly using stat(2) - * to check the size of the file. This is useful after a rewrite or after - * a restart, normally the size is updated just adding the write length - * to the current length, that is much faster. */ -void aofUpdateCurrentSize(void) { - struct redis_stat sb; - - if (redis_fstat(server.aof_fd,&sb) == -1) { - redisLog(REDIS_WARNING,"Unable to obtain the AOF file length. stat: %s", - strerror(errno)); - } else { - server.aof_current_size = sb.st_size; - } -} - -/* A background append only file rewriting (BGREWRITEAOF) terminated its work. - * Handle this. */ -void backgroundRewriteDoneHandler(int exitcode, int bysignal) { - if (!bysignal && exitcode == 0) { - int newfd, oldfd; - char tmpfile[256]; - long long now = ustime(); - - redisLog(REDIS_NOTICE, - "Background AOF rewrite terminated with success"); - - /* Flush the differences accumulated by the parent to the - * rewritten AOF. */ - snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", - (int)server.aof_child_pid); - newfd = open(tmpfile,O_WRONLY|O_APPEND); - if (newfd == -1) { - redisLog(REDIS_WARNING, - "Unable to open the temporary AOF produced by the child: %s", strerror(errno)); - goto cleanup; - } - - if (aofRewriteBufferWrite(newfd) == -1) { - redisLog(REDIS_WARNING, - "Error trying to flush the parent diff to the rewritten AOF: %s", strerror(errno)); - close(newfd); - goto cleanup; - } - - redisLog(REDIS_NOTICE, - "Parent diff successfully flushed to the rewritten AOF (%lu bytes)", aofRewriteBufferSize()); - - /* The only remaining thing to do is to rename the temporary file to - * the configured file and switch the file descriptor used to do AOF - * writes. We don't want close(2) or rename(2) calls to block the - * server on old file deletion. - * - * There are two possible scenarios: - * - * 1) AOF is DISABLED and this was a one time rewrite. The temporary - * file will be renamed to the configured file. When this file already - * exists, it will be unlinked, which may block the server. - * - * 2) AOF is ENABLED and the rewritten AOF will immediately start - * receiving writes. After the temporary file is renamed to the - * configured file, the original AOF file descriptor will be closed. - * Since this will be the last reference to that file, closing it - * causes the underlying file to be unlinked, which may block the - * server. - * - * To mitigate the blocking effect of the unlink operation (either - * caused by rename(2) in scenario 1, or by close(2) in scenario 2), we - * use a background thread to take care of this. First, we - * make scenario 1 identical to scenario 2 by opening the target file - * when it exists. The unlink operation after the rename(2) will then - * be executed upon calling close(2) for its descriptor. Everything to - * guarantee atomicity for this switch has already happened by then, so - * we don't care what the outcome or duration of that close operation - * is, as long as the file descriptor is released again. */ - if (server.aof_fd == -1) { - /* AOF disabled */ - - /* Don't care if this fails: oldfd will be -1 and we handle that. - * One notable case of -1 return is if the old file does - * not exist. */ - oldfd = open(server.aof_filename,O_RDONLY|O_NONBLOCK); - } else { - /* AOF enabled */ - oldfd = -1; /* We'll set this to the current AOF filedes later. */ - } - - /* Rename the temporary file. This will not unlink the target file if - * it exists, because we reference it with "oldfd". */ - if (rename(tmpfile,server.aof_filename) == -1) { - redisLog(REDIS_WARNING, - "Error trying to rename the temporary AOF file: %s", strerror(errno)); - close(newfd); - if (oldfd != -1) close(oldfd); - goto cleanup; - } - - if (server.aof_fd == -1) { - /* AOF disabled, we don't need to set the AOF file descriptor - * to this new file, so we can close it. */ - close(newfd); - } else { - /* AOF enabled, replace the old fd with the new one. */ - oldfd = server.aof_fd; - server.aof_fd = newfd; - if (server.aof_fsync == AOF_FSYNC_ALWAYS) - aof_fsync(newfd); - else if (server.aof_fsync == AOF_FSYNC_EVERYSEC) - aof_background_fsync(newfd); - server.aof_selected_db = -1; /* Make sure SELECT is re-issued */ - aofUpdateCurrentSize(); - server.aof_rewrite_base_size = server.aof_current_size; - - /* Clear regular AOF buffer since its contents was just written to - * the new AOF from the background rewrite buffer. */ - sdsfree(server.aof_buf); - server.aof_buf = sdsempty(); - } - - server.aof_lastbgrewrite_status = REDIS_OK; - - redisLog(REDIS_NOTICE, "Background AOF rewrite finished successfully"); - /* Change state from WAIT_REWRITE to ON if needed */ - if (server.aof_state == REDIS_AOF_WAIT_REWRITE) - server.aof_state = REDIS_AOF_ON; - - /* Asynchronously close the overwritten AOF. */ - if (oldfd != -1) bioCreateBackgroundJob(REDIS_BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL); - - redisLog(REDIS_VERBOSE, - "Background AOF rewrite signal handler took %lldus", ustime()-now); - } else if (!bysignal && exitcode != 0) { - server.aof_lastbgrewrite_status = REDIS_ERR; - - redisLog(REDIS_WARNING, - "Background AOF rewrite terminated with error"); - } else { - server.aof_lastbgrewrite_status = REDIS_ERR; - - redisLog(REDIS_WARNING, - "Background AOF rewrite terminated by signal %d", bysignal); - } - -cleanup: - aofRewriteBufferReset(); - aofRemoveTempFile(server.aof_child_pid); - server.aof_child_pid = -1; - server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start; - server.aof_rewrite_time_start = -1; - /* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */ - if (server.aof_state == REDIS_AOF_WAIT_REWRITE) - server.aof_rewrite_scheduled = 1; -} +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "redis.h" +#include "bio.h" +#include "rio.h" + +#include +#include +#include +#include +#include +#include +#include + +void aofUpdateCurrentSize(void); + +/* ---------------------------------------------------------------------------- + * AOF rewrite buffer implementation. + * + * The following code implement a simple buffer used in order to accumulate + * changes while the background process is rewriting the AOF file. + * + * We only need to append, but can't just use realloc with a large block + * because 'huge' reallocs are not always handled as one could expect + * (via remapping of pages at OS level) but may involve copying data. + * + * For this reason we use a list of blocks, every block is + * AOF_RW_BUF_BLOCK_SIZE bytes. + * ------------------------------------------------------------------------- */ + +#define AOF_RW_BUF_BLOCK_SIZE (1024*1024*10) /* 10 MB per block */ + +typedef struct aofrwblock { + unsigned long used, free; + char buf[AOF_RW_BUF_BLOCK_SIZE]; +} aofrwblock; + +/* This function free the old AOF rewrite buffer if needed, and initialize + * a fresh new one. It tests for server.aof_rewrite_buf_blocks equal to NULL + * so can be used for the first initialization as well. */ +void aofRewriteBufferReset(void) { + if (server.aof_rewrite_buf_blocks) + listRelease(server.aof_rewrite_buf_blocks); + + server.aof_rewrite_buf_blocks = listCreate(); + listSetFreeMethod(server.aof_rewrite_buf_blocks,zfree); +} + +/* Return the current size of the AOF rerwite buffer. */ +unsigned long aofRewriteBufferSize(void) { + listNode *ln = listLast(server.aof_rewrite_buf_blocks); + aofrwblock *block = ln ? ln->value : NULL; + + if (block == NULL) return 0; + unsigned long size = + (listLength(server.aof_rewrite_buf_blocks)-1) * AOF_RW_BUF_BLOCK_SIZE; + size += block->used; + return size; +} + +/* Append data to the AOF rewrite buffer, allocating new blocks if needed. */ +void aofRewriteBufferAppend(unsigned char *s, unsigned long len) { + listNode *ln = listLast(server.aof_rewrite_buf_blocks); + aofrwblock *block = ln ? ln->value : NULL; + + while(len) { + /* If we already got at least an allocated block, try appending + * at least some piece into it. */ + if (block) { + unsigned long thislen = (block->free < len) ? block->free : len; + if (thislen) { /* The current block is not already full. */ + memcpy(block->buf+block->used, s, thislen); + block->used += thislen; + block->free -= thislen; + s += thislen; + len -= thislen; + } + } + + if (len) { /* First block to allocate, or need another block. */ + int numblocks; + + block = zmalloc(sizeof(*block)); + block->free = AOF_RW_BUF_BLOCK_SIZE; + block->used = 0; + listAddNodeTail(server.aof_rewrite_buf_blocks,block); + + /* Log every time we cross more 10 or 100 blocks, respectively + * as a notice or warning. */ + numblocks = listLength(server.aof_rewrite_buf_blocks); + if (((numblocks+1) % 10) == 0) { + int level = ((numblocks+1) % 100) == 0 ? REDIS_WARNING : + REDIS_NOTICE; + redisLog(level,"Background AOF buffer size: %lu MB", + aofRewriteBufferSize()/(1024*1024)); + } + } + } +} + +/* Write the buffer (possibly composed of multiple blocks) into the specified + * fd. If no short write or any other error happens -1 is returned, + * otherwise the number of bytes written is returned. */ +ssize_t aofRewriteBufferWrite(int fd) { + listNode *ln; + listIter li; + ssize_t count = 0; + + listRewind(server.aof_rewrite_buf_blocks,&li); + while((ln = listNext(&li))) { + aofrwblock *block = listNodeValue(ln); + ssize_t nwritten; + + if (block->used) { + nwritten = write(fd,block->buf,block->used); + if (nwritten != block->used) { + if (nwritten == 0) errno = EIO; + return -1; + } + count += nwritten; + } + } + return count; +} + +/* ---------------------------------------------------------------------------- + * AOF file implementation + * ------------------------------------------------------------------------- */ + +/* Starts a background task that performs fsync() against the specified + * file descriptor (the one of the AOF file) in another thread. */ +void aof_background_fsync(int fd) { + bioCreateBackgroundJob(REDIS_BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL); +} + +/* Called when the user switches from "appendonly yes" to "appendonly no" + * at runtime using the CONFIG command. */ +void stopAppendOnly(void) { + redisAssert(server.aof_state != REDIS_AOF_OFF); + flushAppendOnlyFile(1); + aof_fsync(server.aof_fd); + close(server.aof_fd); + + server.aof_fd = -1; + server.aof_selected_db = -1; + server.aof_state = REDIS_AOF_OFF; + /* rewrite operation in progress? kill it, wait child exit */ + if (server.aof_child_pid != -1) { + int statloc; + + redisLog(REDIS_NOTICE,"Killing running AOF rewrite child: %ld", + (long) server.aof_child_pid); + if (kill(server.aof_child_pid,SIGKILL) != -1) + wait3(&statloc,0,NULL); + /* reset the buffer accumulating changes while the child saves */ + aofRewriteBufferReset(); + aofRemoveTempFile(server.aof_child_pid); + server.aof_child_pid = -1; + server.aof_rewrite_time_start = -1; + } +} + +/* Called when the user switches from "appendonly no" to "appendonly yes" + * at runtime using the CONFIG command. */ +int startAppendOnly(void) { + server.aof_last_fsync = server.unixtime; + server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644); + redisAssert(server.aof_state == REDIS_AOF_OFF); + if (server.aof_fd == -1) { + redisLog(REDIS_WARNING,"Redis needs to enable the AOF but can't open the append only file: %s",strerror(errno)); + return REDIS_ERR; + } + if (rewriteAppendOnlyFileBackground() == REDIS_ERR) { + close(server.aof_fd); + redisLog(REDIS_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error."); + return REDIS_ERR; + } + /* We correctly switched on AOF, now wait for the rerwite to be complete + * in order to append data on disk. */ + server.aof_state = REDIS_AOF_WAIT_REWRITE; + return REDIS_OK; +} + +/* Write the append only file buffer on disk. + * + * Since we are required to write the AOF before replying to the client, + * and the only way the client socket can get a write is entering when the + * the event loop, we accumulate all the AOF writes in a memory + * buffer and write it on disk using this function just before entering + * the event loop again. + * + * About the 'force' argument: + * + * When the fsync policy is set to 'everysec' we may delay the flush if there + * is still an fsync() going on in the background thread, since for instance + * on Linux write(2) will be blocked by the background fsync anyway. + * When this happens we remember that there is some aof buffer to be + * flushed ASAP, and will try to do that in the serverCron() function. + * + * However if force is set to 1 we'll write regardless of the background + * fsync. */ +void flushAppendOnlyFile(int force) { + ssize_t nwritten; + int sync_in_progress = 0; + + if (sdslen(server.aof_buf) == 0) return; + + if (server.aof_fsync == AOF_FSYNC_EVERYSEC) + sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0; + + if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) { + /* With this append fsync policy we do background fsyncing. + * If the fsync is still in progress we can try to delay + * the write for a couple of seconds. */ + if (sync_in_progress) { + if (server.aof_flush_postponed_start == 0) { + /* No previous write postponinig, remember that we are + * postponing the flush and return. */ + server.aof_flush_postponed_start = server.unixtime; + return; + } else if (server.unixtime - server.aof_flush_postponed_start < 2) { + /* We were already waiting for fsync to finish, but for less + * than two seconds this is still ok. Postpone again. */ + return; + } + /* Otherwise fall trough, and go write since we can't wait + * over two seconds. */ + server.aof_delayed_fsync++; + redisLog(REDIS_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis."); + } + } + /* If you are following this code path, then we are going to write so + * set reset the postponed flush sentinel to zero. */ + server.aof_flush_postponed_start = 0; + + /* We want to perform a single write. This should be guaranteed atomic + * at least if the filesystem we are writing is a real physical one. + * While this will save us against the server being killed I don't think + * there is much to do about the whole server stopping for power problems + * or alike */ + nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf)); + if (nwritten != (signed)sdslen(server.aof_buf)) { + /* Ooops, we are in troubles. The best thing to do for now is + * aborting instead of giving the illusion that everything is + * working as expected. */ + if (nwritten == -1) { + redisLog(REDIS_WARNING,"Exiting on error writing to the append-only file: %s",strerror(errno)); + } else { + redisLog(REDIS_WARNING,"Exiting on short write while writing to " + "the append-only file: %s (nwritten=%ld, " + "expected=%ld)", + strerror(errno), + (long)nwritten, + (long)sdslen(server.aof_buf)); + + if (ftruncate(server.aof_fd, server.aof_current_size) == -1) { + redisLog(REDIS_WARNING, "Could not remove short write " + "from the append-only file. Redis may refuse " + "to load the AOF the next time it starts. " + "ftruncate: %s", strerror(errno)); + } + } + exit(1); + } + server.aof_current_size += nwritten; + + /* Re-use AOF buffer when it is small enough. The maximum comes from the + * arena size of 4k minus some overhead (but is otherwise arbitrary). */ + if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) { + sdsclear(server.aof_buf); + } else { + sdsfree(server.aof_buf); + server.aof_buf = sdsempty(); + } + + /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are + * children doing I/O in the background. */ + if (server.aof_no_fsync_on_rewrite && + (server.aof_child_pid != -1 || server.rdb_child_pid != -1)) + return; + + /* Perform the fsync if needed. */ + if (server.aof_fsync == AOF_FSYNC_ALWAYS) { + /* aof_fsync is defined as fdatasync() for Linux in order to avoid + * flushing metadata. */ + aof_fsync(server.aof_fd); /* Let's try to get this data on the disk */ + server.aof_last_fsync = server.unixtime; + } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC && + server.unixtime > server.aof_last_fsync)) { + if (!sync_in_progress) aof_background_fsync(server.aof_fd); + server.aof_last_fsync = server.unixtime; + } +} + +sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) { + char buf[32]; + int len, j; + robj *o; + + buf[0] = '*'; + len = 1+ll2string(buf+1,sizeof(buf)-1,argc); + buf[len++] = '\r'; + buf[len++] = '\n'; + dst = sdscatlen(dst,buf,len); + + for (j = 0; j < argc; j++) { + o = getDecodedObject(argv[j]); + buf[0] = '$'; + len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(o->ptr)); + buf[len++] = '\r'; + buf[len++] = '\n'; + dst = sdscatlen(dst,buf,len); + dst = sdscatlen(dst,o->ptr,sdslen(o->ptr)); + dst = sdscatlen(dst,"\r\n",2); + decrRefCount(o); + } + return dst; +} + +/* Create the sds representation of an PEXPIREAT command, using + * 'seconds' as time to live and 'cmd' to understand what command + * we are translating into a PEXPIREAT. + * + * This command is used in order to translate EXPIRE and PEXPIRE commands + * into PEXPIREAT command so that we retain precision in the append only + * file, and the time is always absolute and not relative. */ +sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, robj *seconds) { + long long when; + robj *argv[3]; + + /* Make sure we can use strtol */ + seconds = getDecodedObject(seconds); + when = strtoll(seconds->ptr,NULL,10); + /* Convert argument into milliseconds for EXPIRE, SETEX, EXPIREAT */ + if (cmd->proc == expireCommand || cmd->proc == setexCommand || + cmd->proc == expireatCommand) + { + when *= 1000; + } + /* Convert into absolute time for EXPIRE, PEXPIRE, SETEX, PSETEX */ + if (cmd->proc == expireCommand || cmd->proc == pexpireCommand || + cmd->proc == setexCommand || cmd->proc == psetexCommand) + { + when += mstime(); + } + decrRefCount(seconds); + + argv[0] = createStringObject("PEXPIREAT",9); + argv[1] = key; + argv[2] = createStringObjectFromLongLong(when); + buf = catAppendOnlyGenericCommand(buf, 3, argv); + decrRefCount(argv[0]); + decrRefCount(argv[2]); + return buf; +} + +void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) { + sds buf = sdsempty(); + robj *tmpargv[3]; + + /* The DB this command was targetting is not the same as the last command + * we appendend. To issue a SELECT command is needed. */ + if (dictid != server.aof_selected_db) { + char seldb[64]; + + snprintf(seldb,sizeof(seldb),"%d",dictid); + buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n", + (unsigned long)strlen(seldb),seldb); + server.aof_selected_db = dictid; + } + + if (cmd->proc == expireCommand || cmd->proc == pexpireCommand || + cmd->proc == expireatCommand) { + /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */ + buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]); + } else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) { + /* Translate SETEX/PSETEX to SET and PEXPIREAT */ + tmpargv[0] = createStringObject("SET",3); + tmpargv[1] = argv[1]; + tmpargv[2] = argv[3]; + buf = catAppendOnlyGenericCommand(buf,3,tmpargv); + decrRefCount(tmpargv[0]); + buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]); + } else { + /* All the other commands don't need translation or need the + * same translation already operated in the command vector + * for the replication itself. */ + buf = catAppendOnlyGenericCommand(buf,argc,argv); + } + + /* Append to the AOF buffer. This will be flushed on disk just before + * of re-entering the event loop, so before the client will get a + * positive reply about the operation performed. */ + if (server.aof_state == REDIS_AOF_ON) + server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf)); + + /* If a background append only file rewriting is in progress we want to + * accumulate the differences between the child DB and the current one + * in a buffer, so that when the child process will do its work we + * can append the differences to the new append only file. */ + if (server.aof_child_pid != -1) + aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf)); + + sdsfree(buf); +} + +/* ---------------------------------------------------------------------------- + * AOF loading + * ------------------------------------------------------------------------- */ + +/* In Redis commands are always executed in the context of a client, so in + * order to load the append only file we need to create a fake client. */ +struct redisClient *createFakeClient(void) { + struct redisClient *c = zmalloc(sizeof(*c)); + + selectDb(c,0); + c->fd = -1; + c->name = NULL; + c->querybuf = sdsempty(); + c->querybuf_peak = 0; + c->argc = 0; + c->argv = NULL; + c->bufpos = 0; + c->flags = 0; + /* We set the fake client as a slave waiting for the synchronization + * so that Redis will not try to send replies to this client. */ + c->replstate = REDIS_REPL_WAIT_BGSAVE_START; + c->reply = listCreate(); + c->reply_bytes = 0; + c->obuf_soft_limit_reached_time = 0; + c->watched_keys = listCreate(); + listSetFreeMethod(c->reply,decrRefCount); + listSetDupMethod(c->reply,dupClientReplyValue); + initClientMultiState(c); + return c; +} + +void freeFakeClient(struct redisClient *c) { + sdsfree(c->querybuf); + listRelease(c->reply); + listRelease(c->watched_keys); + freeClientMultiState(c); + zfree(c); +} + +/* Replay the append log file. On error REDIS_OK is returned. On non fatal + * error (the append only file is zero-length) REDIS_ERR is returned. On + * fatal error an error message is logged and the program exists. */ +int loadAppendOnlyFile(char *filename) { + struct redisClient *fakeClient; + FILE *fp = fopen(filename,"r"); + struct redis_stat sb; + int old_aof_state = server.aof_state; + long loops = 0; + + if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) { + server.aof_current_size = 0; + fclose(fp); + return REDIS_ERR; + } + + if (fp == NULL) { + redisLog(REDIS_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno)); + exit(1); + } + + /* Temporarily disable AOF, to prevent EXEC from feeding a MULTI + * to the same file we're about to read. */ + server.aof_state = REDIS_AOF_OFF; + + fakeClient = createFakeClient(); + startLoading(fp); + + while(1) { + int argc, j; + unsigned long len; + robj **argv; + char buf[128]; + sds argsds; + struct redisCommand *cmd; + + /* Serve the clients from time to time */ + if (!(loops++ % 1000)) { + loadingProgress(ftello(fp)); + aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT); + } + + if (fgets(buf,sizeof(buf),fp) == NULL) { + if (feof(fp)) + break; + else + goto readerr; + } + if (buf[0] != '*') goto fmterr; + argc = atoi(buf+1); + if (argc < 1) goto fmterr; + + argv = zmalloc(sizeof(robj*)*argc); + for (j = 0; j < argc; j++) { + if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr; + if (buf[0] != '$') goto fmterr; + len = strtol(buf+1,NULL,10); + argsds = sdsnewlen(NULL,len); + if (len && fread(argsds,len,1,fp) == 0) goto fmterr; + argv[j] = createObject(REDIS_STRING,argsds); + if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */ + } + + /* Command lookup */ + cmd = lookupCommand(argv[0]->ptr); + if (!cmd) { + redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr); + exit(1); + } + /* Run the command in the context of a fake client */ + fakeClient->argc = argc; + fakeClient->argv = argv; + cmd->proc(fakeClient); + + /* The fake client should not have a reply */ + redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0); + /* The fake client should never get blocked */ + redisAssert((fakeClient->flags & REDIS_BLOCKED) == 0); + + /* 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 < fakeClient->argc; j++) + decrRefCount(fakeClient->argv[j]); + zfree(fakeClient->argv); + } + + /* This point can only be reached when EOF is reached without errors. + * If the client is in the middle of a MULTI/EXEC, log error and quit. */ + if (fakeClient->flags & REDIS_MULTI) goto readerr; + + fclose(fp); + freeFakeClient(fakeClient); + server.aof_state = old_aof_state; + stopLoading(); + aofUpdateCurrentSize(); + server.aof_rewrite_base_size = server.aof_current_size; + return REDIS_OK; + +readerr: + if (feof(fp)) { + redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file"); + } else { + redisLog(REDIS_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno)); + } + exit(1); +fmterr: + redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix "); + exit(1); +} + +/* ---------------------------------------------------------------------------- + * AOF rewrite + * ------------------------------------------------------------------------- */ + +/* Delegate writing an object to writing a bulk string or bulk long long. + * This is not placed in rio.c since that adds the redis.h dependency. */ +int rioWriteBulkObject(rio *r, robj *obj) { + /* Avoid using getDecodedObject to help copy-on-write (we are often + * in a child process when this function is called). */ + if (obj->encoding == REDIS_ENCODING_INT) { + return rioWriteBulkLongLong(r,(long)obj->ptr); + } else if (obj->encoding == REDIS_ENCODING_RAW) { + return rioWriteBulkString(r,obj->ptr,sdslen(obj->ptr)); + } else { + redisPanic("Unknown string encoding"); + } +} + +/* Emit the commands needed to rebuild a list object. + * The function returns 0 on error, 1 on success. */ +int rewriteListObject(rio *r, robj *key, robj *o) { + long long count = 0, items = listTypeLength(o); + + if (o->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *zl = o->ptr; + unsigned char *p = ziplistIndex(zl,0); + unsigned char *vstr; + unsigned int vlen; + long long vlong; + + while(ziplistGet(p,&vstr,&vlen,&vlong)) { + if (count == 0) { + int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? + REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; + + if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; + if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0; + if (rioWriteBulkObject(r,key) == 0) return 0; + } + if (vstr) { + if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0; + } else { + if (rioWriteBulkLongLong(r,vlong) == 0) return 0; + } + p = ziplistNext(zl,p); + if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; + items--; + } + } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { + list *list = o->ptr; + listNode *ln; + listIter li; + + listRewind(list,&li); + while((ln = listNext(&li))) { + robj *eleobj = listNodeValue(ln); + + if (count == 0) { + int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? + REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; + + if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; + if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0; + if (rioWriteBulkObject(r,key) == 0) return 0; + } + if (rioWriteBulkObject(r,eleobj) == 0) return 0; + if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; + items--; + } + } else { + redisPanic("Unknown list encoding"); + } + return 1; +} + +/* Emit the commands needed to rebuild a set object. + * The function returns 0 on error, 1 on success. */ +int rewriteSetObject(rio *r, robj *key, robj *o) { + long long count = 0, items = setTypeSize(o); + + if (o->encoding == REDIS_ENCODING_INTSET) { + int ii = 0; + int64_t llval; + + while(intsetGet(o->ptr,ii++,&llval)) { + if (count == 0) { + int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? + REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; + + if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; + if (rioWriteBulkString(r,"SADD",4) == 0) return 0; + if (rioWriteBulkObject(r,key) == 0) return 0; + } + if (rioWriteBulkLongLong(r,llval) == 0) return 0; + if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; + items--; + } + } else if (o->encoding == REDIS_ENCODING_HT) { + dictIterator *di = dictGetIterator(o->ptr); + dictEntry *de; + + while((de = dictNext(di)) != NULL) { + robj *eleobj = dictGetKey(de); + if (count == 0) { + int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? + REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; + + if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; + if (rioWriteBulkString(r,"SADD",4) == 0) return 0; + if (rioWriteBulkObject(r,key) == 0) return 0; + } + if (rioWriteBulkObject(r,eleobj) == 0) return 0; + if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; + items--; + } + dictReleaseIterator(di); + } else { + redisPanic("Unknown set encoding"); + } + return 1; +} + +/* Emit the commands needed to rebuild a sorted set object. + * The function returns 0 on error, 1 on success. */ +int rewriteSortedSetObject(rio *r, robj *key, robj *o) { + long long count = 0, items = zsetLength(o); + + if (o->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *zl = o->ptr; + unsigned char *eptr, *sptr; + unsigned char *vstr; + unsigned int vlen; + long long vll; + double score; + + eptr = ziplistIndex(zl,0); + redisAssert(eptr != NULL); + sptr = ziplistNext(zl,eptr); + redisAssert(sptr != NULL); + + while (eptr != NULL) { + redisAssert(ziplistGet(eptr,&vstr,&vlen,&vll)); + score = zzlGetScore(sptr); + + if (count == 0) { + int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? + REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; + + if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; + if (rioWriteBulkString(r,"ZADD",4) == 0) return 0; + if (rioWriteBulkObject(r,key) == 0) return 0; + } + if (rioWriteBulkDouble(r,score) == 0) return 0; + if (vstr != NULL) { + if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0; + } else { + if (rioWriteBulkLongLong(r,vll) == 0) return 0; + } + zzlNext(zl,&eptr,&sptr); + if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; + items--; + } + } else if (o->encoding == REDIS_ENCODING_SKIPLIST) { + zset *zs = o->ptr; + dictIterator *di = dictGetIterator(zs->dict); + dictEntry *de; + + while((de = dictNext(di)) != NULL) { + robj *eleobj = dictGetKey(de); + double *score = dictGetVal(de); + + if (count == 0) { + int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? + REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; + + if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; + if (rioWriteBulkString(r,"ZADD",4) == 0) return 0; + if (rioWriteBulkObject(r,key) == 0) return 0; + } + if (rioWriteBulkDouble(r,*score) == 0) return 0; + if (rioWriteBulkObject(r,eleobj) == 0) return 0; + if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; + items--; + } + dictReleaseIterator(di); + } else { + redisPanic("Unknown sorted zset encoding"); + } + return 1; +} + +/* Write either the key or the value of the currently selected item of an hash. + * The 'hi' argument passes a valid Redis hash iterator. + * The 'what' filed specifies if to write a key or a value and can be + * either REDIS_HASH_KEY or REDIS_HASH_VALUE. + * + * The function returns 0 on error, non-zero on success. */ +static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) { + if (hi->encoding == REDIS_ENCODING_ZIPLIST) { + unsigned char *vstr = NULL; + unsigned int vlen = UINT_MAX; + long long vll = LLONG_MAX; + + hashTypeCurrentFromZiplist(hi, what, &vstr, &vlen, &vll); + if (vstr) { + return rioWriteBulkString(r, (char*)vstr, vlen); + } else { + return rioWriteBulkLongLong(r, vll); + } + + } else if (hi->encoding == REDIS_ENCODING_HT) { + robj *value; + + hashTypeCurrentFromHashTable(hi, what, &value); + return rioWriteBulkObject(r, value); + } + + redisPanic("Unknown hash encoding"); + return 0; +} + +/* Emit the commands needed to rebuild a hash object. + * The function returns 0 on error, 1 on success. */ +int rewriteHashObject(rio *r, robj *key, robj *o) { + hashTypeIterator *hi; + long long count = 0, items = hashTypeLength(o); + + hi = hashTypeInitIterator(o); + while (hashTypeNext(hi) != REDIS_ERR) { + if (count == 0) { + int cmd_items = (items > REDIS_AOF_REWRITE_ITEMS_PER_CMD) ? + REDIS_AOF_REWRITE_ITEMS_PER_CMD : items; + + if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; + if (rioWriteBulkString(r,"HMSET",5) == 0) return 0; + if (rioWriteBulkObject(r,key) == 0) return 0; + } + + if (rioWriteHashIteratorCursor(r, hi, REDIS_HASH_KEY) == 0) return 0; + if (rioWriteHashIteratorCursor(r, hi, REDIS_HASH_VALUE) == 0) return 0; + if (++count == REDIS_AOF_REWRITE_ITEMS_PER_CMD) count = 0; + items--; + } + + hashTypeReleaseIterator(hi); + + return 1; +} + +/* Write a sequence of commands able to fully rebuild the dataset into + * "filename". Used both by REWRITEAOF and BGREWRITEAOF. + * + * In order to minimize the number of commands needed in the rewritten + * log Redis uses variadic commands when possible, such as RPUSH, SADD + * and ZADD. However at max REDIS_AOF_REWRITE_ITEMS_PER_CMD items per time + * are inserted using a single command. */ +int rewriteAppendOnlyFile(char *filename) { + dictIterator *di = NULL; + dictEntry *de; + rio aof; + FILE *fp; + char tmpfile[256]; + int j; + long long now = mstime(); + + /* Note that we have to use a different temp name here compared to the + * one used by rewriteAppendOnlyFileBackground() function. */ + snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid()); + fp = fopen(tmpfile,"w"); + if (!fp) { + redisLog(REDIS_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno)); + return REDIS_ERR; + } + + rioInitWithFile(&aof,fp); + for (j = 0; j < server.dbnum; j++) { + char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n"; + redisDb *db = server.db+j; + dict *d = db->dict; + if (dictSize(d) == 0) continue; + di = dictGetSafeIterator(d); + if (!di) { + fclose(fp); + return REDIS_ERR; + } + + /* SELECT the new DB */ + if (rioWrite(&aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr; + if (rioWriteBulkLongLong(&aof,j) == 0) goto werr; + + /* Iterate this DB writing every entry */ + while((de = dictNext(di)) != NULL) { + sds keystr; + robj key, *o; + long long expiretime; + + keystr = dictGetKey(de); + o = dictGetVal(de); + initStaticStringObject(key,keystr); + + expiretime = getExpire(db,&key); + + /* Save the key and associated value */ + if (o->type == REDIS_STRING) { + /* Emit a SET command */ + char cmd[]="*3\r\n$3\r\nSET\r\n"; + if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr; + /* Key and value */ + if (rioWriteBulkObject(&aof,&key) == 0) goto werr; + if (rioWriteBulkObject(&aof,o) == 0) goto werr; + } else if (o->type == REDIS_LIST) { + if (rewriteListObject(&aof,&key,o) == 0) goto werr; + } else if (o->type == REDIS_SET) { + if (rewriteSetObject(&aof,&key,o) == 0) goto werr; + } else if (o->type == REDIS_ZSET) { + if (rewriteSortedSetObject(&aof,&key,o) == 0) goto werr; + } else if (o->type == REDIS_HASH) { + if (rewriteHashObject(&aof,&key,o) == 0) goto werr; + } else { + redisPanic("Unknown object type"); + } + /* Save the expire time */ + if (expiretime != -1) { + char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n"; + /* If this key is already expired skip it */ + if (expiretime < now) continue; + if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr; + if (rioWriteBulkObject(&aof,&key) == 0) goto werr; + if (rioWriteBulkLongLong(&aof,expiretime) == 0) goto werr; + } + } + dictReleaseIterator(di); + } + + /* Make sure data will not remain on the OS's output buffers */ + fflush(fp); + aof_fsync(fileno(fp)); + fclose(fp); + + /* Use RENAME to make sure the DB file is changed atomically only + * if the generate DB file is ok. */ + if (rename(tmpfile,filename) == -1) { + redisLog(REDIS_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno)); + unlink(tmpfile); + return REDIS_ERR; + } + redisLog(REDIS_NOTICE,"SYNC append only file rewrite performed"); + return REDIS_OK; + +werr: + fclose(fp); + unlink(tmpfile); + redisLog(REDIS_WARNING,"Write error writing append only file on disk: %s", strerror(errno)); + if (di) dictReleaseIterator(di); + return REDIS_ERR; +} + +/* This is how rewriting of the append only file in background works: + * + * 1) The user calls BGREWRITEAOF + * 2) Redis calls this function, that forks(): + * 2a) the child rewrite the append only file in a temp file. + * 2b) the parent accumulates differences in server.aof_rewrite_buf. + * 3) When the child finished '2a' exists. + * 4) The parent will trap the exit code, if it's OK, will append the + * data accumulated into server.aof_rewrite_buf into the temp file, and + * finally will rename(2) the temp file in the actual file name. + * The the new file is reopened as the new append only file. Profit! + */ +int rewriteAppendOnlyFileBackground(void) { + pid_t childpid; + long long start; + + if (server.aof_child_pid != -1) return REDIS_ERR; + start = ustime(); + if ((childpid = fork()) == 0) { + char tmpfile[256]; + + /* Child */ + if (server.ipfd > 0) close(server.ipfd); + if (server.sofd > 0) close(server.sofd); + snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); + if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) { + size_t private_dirty = zmalloc_get_private_dirty(); + + if (private_dirty) { + redisLog(REDIS_NOTICE, + "AOF rewrite: %lu MB of memory used by copy-on-write", + private_dirty/(1024*1024)); + } + exitFromChild(0); + } else { + exitFromChild(1); + } + } else { + /* Parent */ + server.stat_fork_time = ustime()-start; + if (childpid == -1) { + redisLog(REDIS_WARNING, + "Can't rewrite append only file in background: fork: %s", + strerror(errno)); + return REDIS_ERR; + } + redisLog(REDIS_NOTICE, + "Background append only file rewriting started by pid %d",childpid); + server.aof_rewrite_scheduled = 0; + server.aof_rewrite_time_start = time(NULL); + server.aof_child_pid = childpid; + updateDictResizePolicy(); + /* We set appendseldb to -1 in order to force the next call to the + * feedAppendOnlyFile() to issue a SELECT command, so the differences + * accumulated by the parent into server.aof_rewrite_buf will start + * with a SELECT statement and it will be safe to merge. */ + server.aof_selected_db = -1; + return REDIS_OK; + } + return REDIS_OK; /* unreached */ +} + +void bgrewriteaofCommand(redisClient *c) { + if (server.aof_child_pid != -1) { + addReplyError(c,"Background append only file rewriting already in progress"); + } else if (server.rdb_child_pid != -1) { + server.aof_rewrite_scheduled = 1; + addReplyStatus(c,"Background append only file rewriting scheduled"); + } else if (rewriteAppendOnlyFileBackground() == REDIS_OK) { + addReplyStatus(c,"Background append only file rewriting started"); + } else { + addReply(c,shared.err); + } +} + +void aofRemoveTempFile(pid_t childpid) { + char tmpfile[256]; + + snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) childpid); + unlink(tmpfile); +} + +/* Update the server.aof_current_size filed explicitly using stat(2) + * to check the size of the file. This is useful after a rewrite or after + * a restart, normally the size is updated just adding the write length + * to the current length, that is much faster. */ +void aofUpdateCurrentSize(void) { + struct redis_stat sb; + + if (redis_fstat(server.aof_fd,&sb) == -1) { + redisLog(REDIS_WARNING,"Unable to obtain the AOF file length. stat: %s", + strerror(errno)); + } else { + server.aof_current_size = sb.st_size; + } +} + +/* A background append only file rewriting (BGREWRITEAOF) terminated its work. + * Handle this. */ +void backgroundRewriteDoneHandler(int exitcode, int bysignal) { + if (!bysignal && exitcode == 0) { + int newfd, oldfd; + char tmpfile[256]; + long long now = ustime(); + + redisLog(REDIS_NOTICE, + "Background AOF rewrite terminated with success"); + + /* Flush the differences accumulated by the parent to the + * rewritten AOF. */ + snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", + (int)server.aof_child_pid); + newfd = open(tmpfile,O_WRONLY|O_APPEND); + if (newfd == -1) { + redisLog(REDIS_WARNING, + "Unable to open the temporary AOF produced by the child: %s", strerror(errno)); + goto cleanup; + } + + if (aofRewriteBufferWrite(newfd) == -1) { + redisLog(REDIS_WARNING, + "Error trying to flush the parent diff to the rewritten AOF: %s", strerror(errno)); + close(newfd); + goto cleanup; + } + + redisLog(REDIS_NOTICE, + "Parent diff successfully flushed to the rewritten AOF (%lu bytes)", aofRewriteBufferSize()); + + /* The only remaining thing to do is to rename the temporary file to + * the configured file and switch the file descriptor used to do AOF + * writes. We don't want close(2) or rename(2) calls to block the + * server on old file deletion. + * + * There are two possible scenarios: + * + * 1) AOF is DISABLED and this was a one time rewrite. The temporary + * file will be renamed to the configured file. When this file already + * exists, it will be unlinked, which may block the server. + * + * 2) AOF is ENABLED and the rewritten AOF will immediately start + * receiving writes. After the temporary file is renamed to the + * configured file, the original AOF file descriptor will be closed. + * Since this will be the last reference to that file, closing it + * causes the underlying file to be unlinked, which may block the + * server. + * + * To mitigate the blocking effect of the unlink operation (either + * caused by rename(2) in scenario 1, or by close(2) in scenario 2), we + * use a background thread to take care of this. First, we + * make scenario 1 identical to scenario 2 by opening the target file + * when it exists. The unlink operation after the rename(2) will then + * be executed upon calling close(2) for its descriptor. Everything to + * guarantee atomicity for this switch has already happened by then, so + * we don't care what the outcome or duration of that close operation + * is, as long as the file descriptor is released again. */ + if (server.aof_fd == -1) { + /* AOF disabled */ + + /* Don't care if this fails: oldfd will be -1 and we handle that. + * One notable case of -1 return is if the old file does + * not exist. */ + oldfd = open(server.aof_filename,O_RDONLY|O_NONBLOCK); + } else { + /* AOF enabled */ + oldfd = -1; /* We'll set this to the current AOF filedes later. */ + } + + /* Rename the temporary file. This will not unlink the target file if + * it exists, because we reference it with "oldfd". */ + if (rename(tmpfile,server.aof_filename) == -1) { + redisLog(REDIS_WARNING, + "Error trying to rename the temporary AOF file: %s", strerror(errno)); + close(newfd); + if (oldfd != -1) close(oldfd); + goto cleanup; + } + + if (server.aof_fd == -1) { + /* AOF disabled, we don't need to set the AOF file descriptor + * to this new file, so we can close it. */ + close(newfd); + } else { + /* AOF enabled, replace the old fd with the new one. */ + oldfd = server.aof_fd; + server.aof_fd = newfd; + if (server.aof_fsync == AOF_FSYNC_ALWAYS) + aof_fsync(newfd); + else if (server.aof_fsync == AOF_FSYNC_EVERYSEC) + aof_background_fsync(newfd); + server.aof_selected_db = -1; /* Make sure SELECT is re-issued */ + aofUpdateCurrentSize(); + server.aof_rewrite_base_size = server.aof_current_size; + + /* Clear regular AOF buffer since its contents was just written to + * the new AOF from the background rewrite buffer. */ + sdsfree(server.aof_buf); + server.aof_buf = sdsempty(); + } + + server.aof_lastbgrewrite_status = REDIS_OK; + + redisLog(REDIS_NOTICE, "Background AOF rewrite finished successfully"); + /* Change state from WAIT_REWRITE to ON if needed */ + if (server.aof_state == REDIS_AOF_WAIT_REWRITE) + server.aof_state = REDIS_AOF_ON; + + /* Asynchronously close the overwritten AOF. */ + if (oldfd != -1) bioCreateBackgroundJob(REDIS_BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL); + + redisLog(REDIS_VERBOSE, + "Background AOF rewrite signal handler took %lldus", ustime()-now); + } else if (!bysignal && exitcode != 0) { + server.aof_lastbgrewrite_status = REDIS_ERR; + + redisLog(REDIS_WARNING, + "Background AOF rewrite terminated with error"); + } else { + server.aof_lastbgrewrite_status = REDIS_ERR; + + redisLog(REDIS_WARNING, + "Background AOF rewrite terminated by signal %d", bysignal); + } + +cleanup: + aofRewriteBufferReset(); + aofRemoveTempFile(server.aof_child_pid); + server.aof_child_pid = -1; + server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start; + server.aof_rewrite_time_start = -1; + /* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */ + if (server.aof_state == REDIS_AOF_WAIT_REWRITE) + server.aof_rewrite_scheduled = 1; +} diff --git a/src/asciilogo.h b/src/asciilogo.h index 83c538b..a38b8cc 100644 --- a/src/asciilogo.h +++ b/src/asciilogo.h @@ -1,47 +1,47 @@ -/* - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -char *ascii_logo = -" _._ \n" -" _.-``__ ''-._ \n" -" _.-`` `. `_. ''-._ Redis %s (%s/%d) %s bit\n" -" .-`` .-```. ```\\/ _.,_ ''-._ \n" -" ( ' , .-` | `, ) Running in %s mode\n" -" |`-._`-...-` __...-.``-._|'` _.-'| Port: %d\n" -" | `-._ `._ / _.-' | PID: %ld\n" -" `-._ `-._ `-./ _.-' _.-' \n" -" |`-._`-._ `-.__.-' _.-'_.-'| \n" -" | `-._`-._ _.-'_.-' | http://redis.io \n" -" `-._ `-._`-.__.-'_.-' _.-' \n" -" |`-._`-._ `-.__.-' _.-'_.-'| \n" -" | `-._`-._ _.-'_.-' | \n" -" `-._ `-._`-.__.-'_.-' _.-' \n" -" `-._ `-.__.-' _.-' \n" -" `-._ _.-' \n" -" `-.__.-' \n\n"; +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +char *ascii_logo = +" _._ \n" +" _.-``__ ''-._ \n" +" _.-`` `. `_. ''-._ Redis-storage %s (%s/%d) %s bit\n" +" .-`` .-```. ```\\/ _.,_ ''-._ \n" +" ( ' , .-` | `, ) Running in %s mode\n" +" |`-._`-...-` __...-.``-._|'` _.-'| Port: %d\n" +" | `-._ `._ / _.-' | PID: %ld\n" +" `-._ `-._ `-./ _.-' _.-' \n" +" |`-._`-._ `-.__.-' _.-'_.-'| \n" +" | `-._`-._ _.-'_.-' | https://github.com/qiye/redis-storage \n" +" `-._ `-._`-.__.-'_.-' _.-' \n" +" |`-._`-._ `-.__.-' _.-'_.-'| \n" +" | `-._`-._ _.-'_.-' | \n" +" `-._ `-._`-.__.-'_.-' _.-' \n" +" `-._ `-.__.-' _.-' \n" +" `-._ _.-' \n" +" `-.__.-' \n\n"; diff --git a/src/config.h b/src/config.h index db33407..1a6d12c 100644 --- a/src/config.h +++ b/src/config.h @@ -1,166 +1,179 @@ -/* - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __CONFIG_H -#define __CONFIG_H - -#ifdef __APPLE__ -#include -#endif - -/* Define redis_fstat to fstat or fstat64() */ -#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6) -#define redis_fstat fstat64 -#define redis_stat stat64 -#else -#define redis_fstat fstat -#define redis_stat stat -#endif - -/* Test for proc filesystem */ -#ifdef __linux__ -#define HAVE_PROCFS 1 -#endif - -/* Test for task_info() */ -#if defined(__APPLE__) -#define HAVE_TASKINFO 1 -#endif - -/* Test for backtrace() */ -#if defined(__APPLE__) || defined(__linux__) || defined(__sun) -#define HAVE_BACKTRACE 1 -#endif - -/* Test for polling API */ -#ifdef __linux__ -#define HAVE_EPOLL 1 -#endif - -#if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__) -#define HAVE_KQUEUE 1 -#endif - -#ifdef __sun -#include -#ifdef _DTRACE_VERSION -#define HAVE_EVPORT 1 -#endif -#endif - -/* Define aof_fsync to fdatasync() in Linux and fsync() for all the rest */ -#ifdef __linux__ -#define aof_fsync fdatasync -#else -#define aof_fsync fsync -#endif - -/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use - * the plain fsync() call. */ -#ifdef __linux__ -#include -#include -#if defined(__GLIBC__) && defined(__GLIBC_PREREQ) -#if (LINUX_VERSION_CODE >= 0x020611 && __GLIBC_PREREQ(2, 6)) -#define HAVE_SYNC_FILE_RANGE 1 -#endif -#else -#if (LINUX_VERSION_CODE >= 0x020611) -#define HAVE_SYNC_FILE_RANGE 1 -#endif -#endif -#endif - -#ifdef HAVE_SYNC_FILE_RANGE -#define rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE) -#else -#define rdb_fsync_range(fd,off,size) fsync(fd) -#endif - -/* Byte ordering detection */ -#include /* This will likely define BYTE_ORDER */ - -#ifndef BYTE_ORDER -#if (BSD >= 199103) -# include -#else -#if defined(linux) || defined(__linux__) -# include -#else -#define LITTLE_ENDIAN 1234 /* least-significant byte first (vax, pc) */ -#define BIG_ENDIAN 4321 /* most-significant byte first (IBM, net) */ -#define PDP_ENDIAN 3412 /* LSB first in word, MSW first in long (pdp)*/ - -#if defined(__i386__) || defined(__x86_64__) || defined(__amd64__) || \ - defined(vax) || defined(ns32000) || defined(sun386) || \ - defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \ - defined(__alpha__) || defined(__alpha) -#define BYTE_ORDER LITTLE_ENDIAN -#endif - -#if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \ - defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \ - defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) ||\ - defined(apollo) || defined(__convex__) || defined(_CRAY) || \ - defined(__hppa) || defined(__hp9000) || \ - defined(__hp9000s300) || defined(__hp9000s700) || \ - defined (BIT_ZERO_ON_LEFT) || defined(m68k) || defined(__sparc) -#define BYTE_ORDER BIG_ENDIAN -#endif -#endif /* linux */ -#endif /* BSD */ -#endif /* BYTE_ORDER */ - -#if defined(__BYTE_ORDER) && !defined(BYTE_ORDER) -#if (__BYTE_ORDER == __LITTLE_ENDIAN) -#define BYTE_ORDER LITTLE_ENDIAN -#else -#define BYTE_ORDER BIG_ENDIAN -#endif -#endif - -#if !defined(BYTE_ORDER) || \ - (BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN) - /* you must determine what the correct bit order is for - * your compiler - the next line is an intentional error - * which will force your compiles to bomb until you fix - * the above macros. - */ -#error "Undefined or invalid BYTE_ORDER" -#endif - -#if (__i386 || __amd64) && __GNUC__ -#define GNUC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) -#if GNUC_VERSION >= 40100 -#define HAVE_ATOMIC -#endif -#endif - - -#endif +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __CONFIG_H +#define __CONFIG_H + +#ifdef __APPLE__ +#include +#endif + +/* Define redis_fstat to fstat or fstat64() */ +#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6) +#define redis_fstat fstat64 +#define redis_stat stat64 +#else +#define redis_fstat fstat +#define redis_stat stat +#endif + +/* Test for proc filesystem */ +#ifdef __linux__ +#define HAVE_PROCFS 1 +#endif + +/* Test for task_info() */ +#if defined(__APPLE__) +#define HAVE_TASKINFO 1 +#endif + +/* Test for backtrace() */ +#if defined(__APPLE__) || defined(__linux__) || defined(__sun) +#define HAVE_BACKTRACE 1 +#endif + +/* Test for polling API */ +#ifdef __linux__ +#define HAVE_EPOLL 1 +#endif + +#if (defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined (__NetBSD__) +#define HAVE_KQUEUE 1 +#endif + +#ifdef __sun +#include +#ifdef _DTRACE_VERSION +#define HAVE_EVPORT 1 +#endif +#endif + +/* Define aof_fsync to fdatasync() in Linux and fsync() for all the rest */ +#ifdef __linux__ +#define aof_fsync fdatasync +#else +#define aof_fsync fsync +#endif + +/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use + * the plain fsync() call. */ +#ifdef __linux__ +#include +#include +#if defined(__GLIBC__) && defined(__GLIBC_PREREQ) +#if (LINUX_VERSION_CODE >= 0x020611 && __GLIBC_PREREQ(2, 6)) +#define HAVE_SYNC_FILE_RANGE 1 +#endif +#else +#if (LINUX_VERSION_CODE >= 0x020611) +#define HAVE_SYNC_FILE_RANGE 1 +#endif +#endif +#endif + +#ifdef HAVE_SYNC_FILE_RANGE +#define rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE) +#else +#define rdb_fsync_range(fd,off,size) fsync(fd) +#endif + +/* Byte ordering detection */ +#include /* This will likely define BYTE_ORDER */ + +#ifndef BYTE_ORDER +#if (BSD >= 199103) +# include +#else +#if defined(linux) || defined(__linux__) +# include +#else +#define LITTLE_ENDIAN 1234 /* least-significant byte first (vax, pc) */ +#define BIG_ENDIAN 4321 /* most-significant byte first (IBM, net) */ +#define PDP_ENDIAN 3412 /* LSB first in word, MSW first in long (pdp)*/ + +#if defined(__i386__) || defined(__x86_64__) || defined(__amd64__) || \ + defined(vax) || defined(ns32000) || defined(sun386) || \ + defined(MIPSEL) || defined(_MIPSEL) || defined(BIT_ZERO_ON_RIGHT) || \ + defined(__alpha__) || defined(__alpha) +#define BYTE_ORDER LITTLE_ENDIAN +#endif + +#if defined(sel) || defined(pyr) || defined(mc68000) || defined(sparc) || \ + defined(is68k) || defined(tahoe) || defined(ibm032) || defined(ibm370) || \ + defined(MIPSEB) || defined(_MIPSEB) || defined(_IBMR2) || defined(DGUX) ||\ + defined(apollo) || defined(__convex__) || defined(_CRAY) || \ + defined(__hppa) || defined(__hp9000) || \ + defined(__hp9000s300) || defined(__hp9000s700) || \ + defined (BIT_ZERO_ON_LEFT) || defined(m68k) || defined(__sparc) +#define BYTE_ORDER BIG_ENDIAN +#endif +#endif /* linux */ +#endif /* BSD */ +#endif /* BYTE_ORDER */ + +/* Sometimes after including an OS-specific header that defines the + * endianess we end with __BYTE_ORDER but not with BYTE_ORDER that is what + * the Redis code uses. In this case let's define everything without the + * underscores. */ +#ifndef BYTE_ORDER +#ifdef __BYTE_ORDER +#if defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN) +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#endif +#ifndef BIG_ENDIAN +#define BIG_ENDIAN __BIG_ENDIAN +#endif +#if (__BYTE_ORDER == __LITTLE_ENDIAN) +#define BYTE_ORDER LITTLE_ENDIAN +#else +#define BYTE_ORDER BIG_ENDIAN +#endif +#endif +#endif +#endif + +#if !defined(BYTE_ORDER) || \ + (BYTE_ORDER != BIG_ENDIAN && BYTE_ORDER != LITTLE_ENDIAN) + /* you must determine what the correct bit order is for + * your compiler - the next line is an intentional error + * which will force your compiles to bomb until you fix + * the above macros. + */ +#error "Undefined or invalid BYTE_ORDER" +#endif + +#if (__i386 || __amd64) && __GNUC__ +#define GNUC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#if GNUC_VERSION >= 40100 +#define HAVE_ATOMIC +#endif +#endif + +#endif diff --git a/src/endianconv.h b/src/endianconv.h index f76e0e6..450fb51 100644 --- a/src/endianconv.h +++ b/src/endianconv.h @@ -1,63 +1,64 @@ -/* See endianconv.c top comments for more information - * - * ---------------------------------------------------------------------------- - * - * Copyright (c) 2011-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __ENDIANCONV_H -#define __ENDIANCONV_H - -#include - -void memrev16(void *p); -void memrev32(void *p); -void memrev64(void *p); -uint16_t intrev16(uint16_t v); -uint32_t intrev32(uint32_t v); -uint64_t intrev64(uint64_t v); - -/* variants of the function doing the actual convertion only if the target - * host is big endian */ -#if (BYTE_ORDER == LITTLE_ENDIAN) -#define memrev16ifbe(p) -#define memrev32ifbe(p) -#define memrev64ifbe(p) -#define intrev16ifbe(v) (v) -#define intrev32ifbe(v) (v) -#define intrev64ifbe(v) (v) -#else -#define memrev16ifbe(p) memrev16(p) -#define memrev32ifbe(p) memrev32(p) -#define memrev64ifbe(p) memrev64(p) -#define intrev16ifbe(v) intrev16(v) -#define intrev32ifbe(v) intrev32(v) -#define intrev64ifbe(v) intrev64(v) -#endif - -#endif +/* See endianconv.c top comments for more information + * + * ---------------------------------------------------------------------------- + * + * Copyright (c) 2011-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __ENDIANCONV_H +#define __ENDIANCONV_H + +#include "config.h" +#include + +void memrev16(void *p); +void memrev32(void *p); +void memrev64(void *p); +uint16_t intrev16(uint16_t v); +uint32_t intrev32(uint32_t v); +uint64_t intrev64(uint64_t v); + +/* variants of the function doing the actual convertion only if the target + * host is big endian */ +#if (BYTE_ORDER == LITTLE_ENDIAN) +#define memrev16ifbe(p) +#define memrev32ifbe(p) +#define memrev64ifbe(p) +#define intrev16ifbe(v) (v) +#define intrev32ifbe(v) (v) +#define intrev64ifbe(v) (v) +#else +#define memrev16ifbe(p) memrev16(p) +#define memrev32ifbe(p) memrev32(p) +#define memrev64ifbe(p) memrev64(p) +#define intrev16ifbe(v) intrev16(v) +#define intrev32ifbe(v) intrev32(v) +#define intrev64ifbe(v) intrev64(v) +#endif + +#endif diff --git a/src/fmacros.h b/src/fmacros.h index 6f3c4b4..3f2c8c7 100644 --- a/src/fmacros.h +++ b/src/fmacros.h @@ -1,48 +1,48 @@ -/* - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef _REDIS_FMACRO_H -#define _REDIS_FMACRO_H - -#define _BSD_SOURCE - -#if defined(__linux__) -#define _GNU_SOURCE -#endif - -#if defined(__linux__) || defined(__OpenBSD__) -#define _XOPEN_SOURCE 700 -#else -#define _XOPEN_SOURCE -#endif - -#define _LARGEFILE_SOURCE -#define _FILE_OFFSET_BITS 64 - -#endif +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _REDIS_FMACRO_H +#define _REDIS_FMACRO_H + +#define _BSD_SOURCE + +#if defined(__linux__) +#define _GNU_SOURCE +#endif + +#if defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__) +#define _XOPEN_SOURCE 700 +#else +#define _XOPEN_SOURCE +#endif + +#define _LARGEFILE_SOURCE +#define _FILE_OFFSET_BITS 64 + +#endif diff --git a/src/networking.c b/src/networking.c index 4365bc8..b3786a0 100644 --- a/src/networking.c +++ b/src/networking.c @@ -1,1372 +1,1411 @@ -/* - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "redis.h" -#include - -static void setProtocolError(redisClient *c, int pos); - -/* To evaluate the output buffer size of a client we need to get size of - * allocated objects, however we can't used zmalloc_size() directly on sds - * strings because of the trick they use to work (the header is before the - * returned pointer), so we use this helper function. */ -size_t zmalloc_size_sds(sds s) { - return zmalloc_size(s-sizeof(struct sdshdr)); -} - -void *dupClientReplyValue(void *o) { - incrRefCount((robj*)o); - return o; -} - -int listMatchObjects(void *a, void *b) { - return equalStringObjects(a,b); -} - -redisClient *createClient(int fd) { - redisClient *c = zmalloc(sizeof(redisClient)); - - /* passing -1 as fd it is possible to create a non connected client. - * This is useful since all the Redis commands needs to be executed - * in the context of a client. When commands are executed in other - * contexts (for instance a Lua script) we need a non connected client. */ - if (fd != -1) { - anetNonBlock(NULL,fd); - anetTcpNoDelay(NULL,fd); - if (aeCreateFileEvent(server.el,fd,AE_READABLE, - readQueryFromClient, c) == AE_ERR) - { - close(fd); - zfree(c); - return NULL; - } - } - - selectDb(c,0); - c->fd = fd; - c->bufpos = 0; - c->querybuf = sdsempty(); - c->querybuf_peak = 0; - c->reqtype = 0; - c->argc = 0; - c->argv = NULL; - c->cmd = c->lastcmd = NULL; - c->multibulklen = 0; - c->bulklen = -1; - c->sentlen = 0; - c->flags = 0; - c->ctime = c->lastinteraction = server.unixtime; - c->authenticated = 0; - c->replstate = REDIS_REPL_NONE; - c->slave_listening_port = 0; - c->reply = listCreate(); - c->reply_bytes = 0; - c->obuf_soft_limit_reached_time = 0; - listSetFreeMethod(c->reply,decrRefCount); - listSetDupMethod(c->reply,dupClientReplyValue); - c->bpop.keys = dictCreate(&setDictType,NULL); - c->bpop.timeout = 0; - c->bpop.target = NULL; - c->io_keys = listCreate(); - c->watched_keys = listCreate(); - listSetFreeMethod(c->io_keys,decrRefCount); - c->pubsub_channels = dictCreate(&setDictType,NULL); - c->pubsub_patterns = listCreate(); - listSetFreeMethod(c->pubsub_patterns,decrRefCount); - listSetMatchMethod(c->pubsub_patterns,listMatchObjects); - if (fd != -1) listAddNodeTail(server.clients,c); - initClientMultiState(c); - return c; -} - -/* This function is called every time we are going to transmit new data - * to the client. The behavior is the following: - * - * If the client should receive new data (normal clients will) the function - * returns REDIS_OK, and make sure to install the write handler in our event - * loop so that when the socket is writable new data gets written. - * - * If the client should not receive new data, because it is a fake client - * or a slave, or because the setup of the write handler failed, the function - * returns REDIS_ERR. - * - * Typically gets called every time a reply is built, before adding more - * data to the clients output buffers. If the function returns REDIS_ERR no - * data should be appended to the output buffers. */ -int prepareClientToWrite(redisClient *c) { - if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK; - if (c->fd <= 0) return REDIS_ERR; /* Fake client */ - if (c->bufpos == 0 && listLength(c->reply) == 0 && - (c->replstate == REDIS_REPL_NONE || - c->replstate == REDIS_REPL_ONLINE) && - aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, - sendReplyToClient, c) == AE_ERR) return REDIS_ERR; - return REDIS_OK; -} - -/* Create a duplicate of the last object in the reply list when - * it is not exclusively owned by the reply list. */ -robj *dupLastObjectIfNeeded(list *reply) { - robj *new, *cur; - listNode *ln; - redisAssert(listLength(reply) > 0); - ln = listLast(reply); - cur = listNodeValue(ln); - if (cur->refcount > 1) { - new = dupStringObject(cur); - decrRefCount(cur); - listNodeValue(ln) = new; - } - return listNodeValue(ln); -} - -/* ----------------------------------------------------------------------------- - * Low level functions to add more data to output buffers. - * -------------------------------------------------------------------------- */ - -int _addReplyToBuffer(redisClient *c, char *s, size_t len) { - size_t available = sizeof(c->buf)-c->bufpos; - - if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK; - - /* If there already are entries in the reply list, we cannot - * add anything more to the static buffer. */ - if (listLength(c->reply) > 0) return REDIS_ERR; - - /* Check that the buffer has enough space available for this string. */ - if (len > available) return REDIS_ERR; - - memcpy(c->buf+c->bufpos,s,len); - c->bufpos+=len; - return REDIS_OK; -} - -void _addReplyObjectToList(redisClient *c, robj *o) { - robj *tail; - - if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; - - if (listLength(c->reply) == 0) { - incrRefCount(o); - listAddNodeTail(c->reply,o); - c->reply_bytes += zmalloc_size_sds(o->ptr); - } else { - tail = listNodeValue(listLast(c->reply)); - - /* Append to this object when possible. */ - if (tail->ptr != NULL && - sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES) - { - c->reply_bytes -= zmalloc_size_sds(tail->ptr); - tail = dupLastObjectIfNeeded(c->reply); - tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr)); - c->reply_bytes += zmalloc_size_sds(tail->ptr); - } else { - incrRefCount(o); - listAddNodeTail(c->reply,o); - c->reply_bytes += zmalloc_size_sds(o->ptr); - } - } - asyncCloseClientOnOutputBufferLimitReached(c); -} - -/* This method takes responsibility over the sds. When it is no longer - * needed it will be free'd, otherwise it ends up in a robj. */ -void _addReplySdsToList(redisClient *c, sds s) { - robj *tail; - - if (c->flags & REDIS_CLOSE_AFTER_REPLY) { - sdsfree(s); - return; - } - - if (listLength(c->reply) == 0) { - listAddNodeTail(c->reply,createObject(REDIS_STRING,s)); - c->reply_bytes += zmalloc_size_sds(s); - } else { - tail = listNodeValue(listLast(c->reply)); - - /* Append to this object when possible. */ - if (tail->ptr != NULL && - sdslen(tail->ptr)+sdslen(s) <= REDIS_REPLY_CHUNK_BYTES) - { - c->reply_bytes -= zmalloc_size_sds(tail->ptr); - tail = dupLastObjectIfNeeded(c->reply); - tail->ptr = sdscatlen(tail->ptr,s,sdslen(s)); - c->reply_bytes += zmalloc_size_sds(tail->ptr); - sdsfree(s); - } else { - listAddNodeTail(c->reply,createObject(REDIS_STRING,s)); - c->reply_bytes += zmalloc_size_sds(s); - } - } - asyncCloseClientOnOutputBufferLimitReached(c); -} - -void _addReplyStringToList(redisClient *c, char *s, size_t len) { - robj *tail; - - if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; - - if (listLength(c->reply) == 0) { - robj *o = createStringObject(s,len); - - listAddNodeTail(c->reply,o); - c->reply_bytes += zmalloc_size_sds(o->ptr); - } else { - tail = listNodeValue(listLast(c->reply)); - - /* Append to this object when possible. */ - if (tail->ptr != NULL && - sdslen(tail->ptr)+len <= REDIS_REPLY_CHUNK_BYTES) - { - c->reply_bytes -= zmalloc_size_sds(tail->ptr); - tail = dupLastObjectIfNeeded(c->reply); - tail->ptr = sdscatlen(tail->ptr,s,len); - c->reply_bytes += zmalloc_size_sds(tail->ptr); - } else { - robj *o = createStringObject(s,len); - - listAddNodeTail(c->reply,o); - c->reply_bytes += zmalloc_size_sds(o->ptr); - } - } - asyncCloseClientOnOutputBufferLimitReached(c); -} - -/* ----------------------------------------------------------------------------- - * Higher level functions to queue data on the client output buffer. - * The following functions are the ones that commands implementations will call. - * -------------------------------------------------------------------------- */ - -void addReply(redisClient *c, robj *obj) { - if (prepareClientToWrite(c) != REDIS_OK) return; - - /* This is an important place where we can avoid copy-on-write - * when there is a saving child running, avoiding touching the - * refcount field of the object if it's not needed. - * - * If the encoding is RAW and there is room in the static buffer - * we'll be able to send the object to the client without - * messing with its page. */ - if (obj->encoding == REDIS_ENCODING_RAW) { - if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK) - _addReplyObjectToList(c,obj); - } else if (obj->encoding == REDIS_ENCODING_INT) { - /* Optimization: if there is room in the static buffer for 32 bytes - * (more than the max chars a 64 bit integer can take as string) we - * avoid decoding the object and go for the lower level approach. */ - if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) { - char buf[32]; - int len; - - len = ll2string(buf,sizeof(buf),(long)obj->ptr); - if (_addReplyToBuffer(c,buf,len) == REDIS_OK) - return; - /* else... continue with the normal code path, but should never - * happen actually since we verified there is room. */ - } - 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()"); - } -} - -void addReplySds(redisClient *c, sds s) { - if (prepareClientToWrite(c) != REDIS_OK) { - /* The caller expects the sds to be free'd. */ - sdsfree(s); - return; - } - if (_addReplyToBuffer(c,s,sdslen(s)) == REDIS_OK) { - sdsfree(s); - } else { - /* This method free's the sds when it is no longer needed. */ - _addReplySdsToList(c,s); - } -} - -void addReplyString(redisClient *c, char *s, size_t len) { - if (prepareClientToWrite(c) != REDIS_OK) return; - if (_addReplyToBuffer(c,s,len) != REDIS_OK) - _addReplyStringToList(c,s,len); -} - -void addReplyErrorLength(redisClient *c, char *s, size_t len) { - addReplyString(c,"-ERR ",5); - addReplyString(c,s,len); - addReplyString(c,"\r\n",2); -} - -void addReplyError(redisClient *c, char *err) { - addReplyErrorLength(c,err,strlen(err)); -} - -void addReplyErrorFormat(redisClient *c, const char *fmt, ...) { - size_t l, j; - va_list ap; - va_start(ap,fmt); - sds s = sdscatvprintf(sdsempty(),fmt,ap); - va_end(ap); - /* Make sure there are no newlines in the string, otherwise invalid protocol - * is emitted. */ - l = sdslen(s); - for (j = 0; j < l; j++) { - if (s[j] == '\r' || s[j] == '\n') s[j] = ' '; - } - addReplyErrorLength(c,s,sdslen(s)); - sdsfree(s); -} - -void addReplyStatusLength(redisClient *c, char *s, size_t len) { - addReplyString(c,"+",1); - addReplyString(c,s,len); - addReplyString(c,"\r\n",2); -} - -void addReplyStatus(redisClient *c, char *status) { - addReplyStatusLength(c,status,strlen(status)); -} - -void addReplyStatusFormat(redisClient *c, const char *fmt, ...) { - va_list ap; - va_start(ap,fmt); - sds s = sdscatvprintf(sdsempty(),fmt,ap); - va_end(ap); - addReplyStatusLength(c,s,sdslen(s)); - sdsfree(s); -} - -/* Adds an empty object to the reply list that will contain the multi bulk - * length, which is not known when this function is called. */ -void *addDeferredMultiBulkLength(redisClient *c) { - /* Note that we install the write event here even if the object is not - * ready to be sent, since we are sure that before returning to the - * event loop setDeferredMultiBulkLength() will be called. */ - if (prepareClientToWrite(c) != REDIS_OK) return NULL; - listAddNodeTail(c->reply,createObject(REDIS_STRING,NULL)); - return listLast(c->reply); -} - -/* Populate the length object and try glueing it to the next chunk. */ -void setDeferredMultiBulkLength(redisClient *c, void *node, long length) { - listNode *ln = (listNode*)node; - robj *len, *next; - - /* Abort when *node is NULL (see addDeferredMultiBulkLength). */ - if (node == NULL) return; - - len = listNodeValue(ln); - len->ptr = sdscatprintf(sdsempty(),"*%ld\r\n",length); - c->reply_bytes += zmalloc_size_sds(len->ptr); - if (ln->next != NULL) { - next = listNodeValue(ln->next); - - /* Only glue when the next node is non-NULL (an sds in this case) */ - if (next->ptr != NULL) { - c->reply_bytes -= zmalloc_size_sds(len->ptr); - c->reply_bytes -= zmalloc_size_sds(next->ptr); - len->ptr = sdscatlen(len->ptr,next->ptr,sdslen(next->ptr)); - c->reply_bytes += zmalloc_size_sds(len->ptr); - listDelNode(c->reply,ln->next); - } - } - asyncCloseClientOnOutputBufferLimitReached(c); -} - -/* Add a duble as a bulk reply */ -void addReplyDouble(redisClient *c, double d) { - char dbuf[128], sbuf[128]; - int dlen, slen; - dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d); - slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf); - addReplyString(c,sbuf,slen); -} - -/* Add a long long as integer reply or bulk len / multi bulk count. - * Basically this is used to output . */ -void addReplyLongLongWithPrefix(redisClient *c, long long ll, char prefix) { - char buf[128]; - int len; - - /* Things like $3\r\n or *2\r\n are emitted very often by the protocol - * so we have a few shared objects to use if the integer is small - * like it is most of the times. */ - if (prefix == '*' && ll < REDIS_SHARED_BULKHDR_LEN) { - addReply(c,shared.mbulkhdr[ll]); - return; - } else if (prefix == '$' && ll < REDIS_SHARED_BULKHDR_LEN) { - addReply(c,shared.bulkhdr[ll]); - return; - } - - buf[0] = prefix; - len = ll2string(buf+1,sizeof(buf)-1,ll); - buf[len+1] = '\r'; - buf[len+2] = '\n'; - addReplyString(c,buf,len+3); -} - -void addReplyLongLong(redisClient *c, long long ll) { - if (ll == 0) - addReply(c,shared.czero); - else if (ll == 1) - addReply(c,shared.cone); - else - addReplyLongLongWithPrefix(c,ll,':'); -} - -void addReplyMultiBulkLen(redisClient *c, long length) { - addReplyLongLongWithPrefix(c,length,'*'); -} - -/* Create the length prefix of a bulk reply, example: $2234 */ -void addReplyBulkLen(redisClient *c, robj *obj) { - size_t len; - - if (obj->encoding == REDIS_ENCODING_RAW) { - len = sdslen(obj->ptr); - } else { - long n = (long)obj->ptr; - - /* Compute how many bytes will take this integer as a radix 10 string */ - len = 1; - if (n < 0) { - len++; - n = -n; - } - while((n = n/10) != 0) { - len++; - } - } - addReplyLongLongWithPrefix(c,len,'$'); -} - -/* Add a Redis Object as a bulk reply */ -void addReplyBulk(redisClient *c, robj *obj) { - addReplyBulkLen(c,obj); - addReply(c,obj); - addReply(c,shared.crlf); -} - -/* Add a C buffer as bulk reply */ -void addReplyBulkCBuffer(redisClient *c, void *p, size_t len) { - addReplyLongLongWithPrefix(c,len,'$'); - addReplyString(c,p,len); - addReply(c,shared.crlf); -} - -/* Add a C nul term string as bulk reply */ -void addReplyBulkCString(redisClient *c, char *s) { - if (s == NULL) { - addReply(c,shared.nullbulk); - } else { - addReplyBulkCBuffer(c,s,strlen(s)); - } -} - -/* Add a long long as a bulk reply */ -void addReplyBulkLongLong(redisClient *c, long long ll) { - char buf[64]; - int len; - - len = ll2string(buf,64,ll); - addReplyBulkCBuffer(c,buf,len); -} - -/* Copy 'src' client output buffers into 'dst' client output buffers. - * The function takes care of freeing the old output buffers of the - * destination client. */ -void copyClientOutputBuffer(redisClient *dst, redisClient *src) { - listRelease(dst->reply); - dst->reply = listDup(src->reply); - memcpy(dst->buf,src->buf,src->bufpos); - dst->bufpos = src->bufpos; - dst->reply_bytes = src->reply_bytes; -} - -static void acceptCommonHandler(int fd, int flags) { - redisClient *c; - if ((c = createClient(fd)) == NULL) { - redisLog(REDIS_WARNING,"Error allocating resources for the client"); - close(fd); /* May be already closed, just ignore errors */ - return; - } - /* If maxclient directive is set and this is one client more... close the - * connection. Note that we create the client instead to check before - * for this condition, since now the socket is already set in nonblocking - * mode and we can send an error for free using the Kernel I/O */ - if (listLength(server.clients) > server.maxclients) { - char *err = "-ERR max number of clients reached\r\n"; - - /* That's a best effort error message, don't check write errors */ - if (write(c->fd,err,strlen(err)) == -1) { - /* Nothing to do, Just to avoid the warning... */ - } - server.stat_rejected_conn++; - freeClient(c); - return; - } - server.stat_numconnections++; - c->flags |= flags; -} - -void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) { - int cport, cfd; - char cip[128]; - REDIS_NOTUSED(el); - REDIS_NOTUSED(mask); - REDIS_NOTUSED(privdata); - - cfd = anetTcpAccept(server.neterr, fd, cip, &cport); - if (cfd == AE_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; - REDIS_NOTUSED(el); - REDIS_NOTUSED(mask); - REDIS_NOTUSED(privdata); - - 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++) - decrRefCount(c->argv[j]); - c->argc = 0; - c->cmd = NULL; -} - -/* Close all the slaves connections. This is useful in chained replication - * when we resync with our own master and want to force all our slaves to - * resync with us as well. */ -void disconnectSlaves(void) { - while (listLength(server.slaves)) { - listNode *ln = listFirst(server.slaves); - freeClient((redisClient*)ln->value); - } -} - -void freeClient(redisClient *c) { - listNode *ln; - - /* If this is marked as current client unset it */ - if (server.current_client == c) server.current_client = NULL; - - /* Note that if the client we are freeing is blocked into a blocking - * call, we have to set querybuf to NULL *before* to call - * unblockClientWaitingData() to avoid processInputBuffer() will get - * called. Also it is important to remove the file events after - * this, because this call adds the READABLE event. */ - sdsfree(c->querybuf); - c->querybuf = NULL; - if (c->flags & REDIS_BLOCKED) - unblockClientWaitingData(c); - dictRelease(c->bpop.keys); - - /* UNWATCH all the keys */ - unwatchAllKeys(c); - listRelease(c->watched_keys); - /* Unsubscribe from all the pubsub channels */ - pubsubUnsubscribeAllChannels(c,0); - pubsubUnsubscribeAllPatterns(c,0); - dictRelease(c->pubsub_channels); - listRelease(c->pubsub_patterns); - /* Obvious cleanup */ - aeDeleteFileEvent(server.el,c->fd,AE_READABLE); - aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); - listRelease(c->reply); - freeClientArgv(c); - close(c->fd); - /* Remove from the list of clients */ - ln = listSearchKey(server.clients,c); - redisAssert(ln != NULL); - listDelNode(server.clients,ln); - /* When client was just unblocked because of a blocking operation, - * remove it from the list with unblocked clients. */ - if (c->flags & REDIS_UNBLOCKED) { - ln = listSearchKey(server.unblocked_clients,c); - redisAssert(ln != NULL); - listDelNode(server.unblocked_clients,ln); - } - listRelease(c->io_keys); - /* Master/slave cleanup. - * Case 1: we lost the connection with a slave. */ - if (c->flags & REDIS_SLAVE) { - if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1) - close(c->repldbfd); - list *l = (c->flags & REDIS_MONITOR) ? server.monitors : server.slaves; - ln = listSearchKey(l,c); - redisAssert(ln != NULL); - listDelNode(l,ln); - } - - /* Case 2: we lost the connection with the master. */ - if (c->flags & REDIS_MASTER) { - server.master = NULL; - server.repl_state = REDIS_REPL_CONNECT; - server.repl_down_since = server.unixtime; - /* We lost connection with our master, force our slaves to resync - * with us as well to load the new data set. - * - * If server.masterhost is NULL the user called SLAVEOF NO ONE so - * slave resync is not needed. */ - if (server.masterhost != NULL) disconnectSlaves(); - } - - /* If this client was scheduled for async freeing we need to remove it - * from the queue. */ - if (c->flags & REDIS_CLOSE_ASAP) { - ln = listSearchKey(server.clients_to_close,c); - redisAssert(ln != NULL); - listDelNode(server.clients_to_close,ln); - } - - /* Release memory */ - zfree(c->argv); - freeClientMultiState(c); - zfree(c); -} - -/* Schedule a client to free it at a safe time in the serverCron() function. - * This function is useful when we need to terminate a client but we are in - * a context where calling freeClient() is not possible, because the client - * should be valid for the continuation of the flow of the program. */ -void freeClientAsync(redisClient *c) { - if (c->flags & REDIS_CLOSE_ASAP) return; - c->flags |= REDIS_CLOSE_ASAP; - listAddNodeTail(server.clients_to_close,c); -} - -void freeClientsInAsyncFreeQueue(void) { - while (listLength(server.clients_to_close)) { - listNode *ln = listFirst(server.clients_to_close); - redisClient *c = listNodeValue(ln); - - c->flags &= ~REDIS_CLOSE_ASAP; - freeClient(c); - listDelNode(server.clients_to_close,ln); - } -} - -void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { - redisClient *c = privdata; - int nwritten = 0, totwritten = 0, objlen; - size_t objmem; - robj *o; - REDIS_NOTUSED(el); - REDIS_NOTUSED(mask); - - while(c->bufpos > 0 || listLength(c->reply)) { - if (c->bufpos > 0) { - if (c->flags & REDIS_MASTER) { - /* Don't reply to a master */ - nwritten = c->bufpos - c->sentlen; - } else { - nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen); - if (nwritten <= 0) break; - } - c->sentlen += nwritten; - totwritten += nwritten; - - /* If the buffer was sent, set bufpos to zero to continue with - * the remainder of the reply. */ - if (c->sentlen == c->bufpos) { - c->bufpos = 0; - c->sentlen = 0; - } - } else { - o = listNodeValue(listFirst(c->reply)); - objlen = sdslen(o->ptr); - objmem = zmalloc_size_sds(o->ptr); - - if (objlen == 0) { - listDelNode(c->reply,listFirst(c->reply)); - continue; - } - - if (c->flags & REDIS_MASTER) { - /* Don't reply to a master */ - nwritten = objlen - c->sentlen; - } else { - nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen); - if (nwritten <= 0) break; - } - c->sentlen += nwritten; - totwritten += nwritten; - - /* If we fully sent the object on head go to the next one */ - if (c->sentlen == objlen) { - listDelNode(c->reply,listFirst(c->reply)); - c->sentlen = 0; - c->reply_bytes -= objmem; - } - } - /* Note that we avoid to send more than REDIS_MAX_WRITE_PER_EVENT - * bytes, in a single threaded server it's a good idea to serve - * other clients as well, even if a very large request comes from - * super fast link that is always able to accept data (in real world - * scenario think about 'KEYS *' against the loopback interface). - * - * However if we are over the maxmemory limit we ignore that and - * just deliver as much data as it is possible to deliver. */ - if (totwritten > REDIS_MAX_WRITE_PER_EVENT && - (server.maxmemory == 0 || - zmalloc_used_memory() < server.maxmemory)) break; - } - if (nwritten == -1) { - if (errno == EAGAIN) { - nwritten = 0; - } else { - redisLog(REDIS_VERBOSE, - "Error writing to client: %s", strerror(errno)); - freeClient(c); - return; - } - } - if (totwritten > 0) c->lastinteraction = server.unixtime; - if (c->bufpos == 0 && listLength(c->reply) == 0) { - c->sentlen = 0; - aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); - - /* Close connection after entire reply has been sent. */ - if (c->flags & REDIS_CLOSE_AFTER_REPLY) freeClient(c); - } -} - -/* resetClient prepare the client to process the next command */ -void resetClient(redisClient *c) { - freeClientArgv(c); - c->reqtype = 0; - c->multibulklen = 0; - c->bulklen = -1; - /* We clear the ASKING flag as well if we are not inside a MULTI. */ - if (!(c->flags & REDIS_MULTI)) c->flags &= (~REDIS_ASKING); -} - -int processInlineBuffer(redisClient *c) { - char *newline = strstr(c->querybuf,"\r\n"); - int argc, j; - sds *argv; - size_t querylen; - - /* Nothing to do without a \r\n */ - if (newline == NULL) { - if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) { - addReplyError(c,"Protocol error: too big inline request"); - setProtocolError(c,0); - } - return REDIS_ERR; - } - - /* Split the input buffer up to the \r\n */ - querylen = newline-(c->querybuf); - argv = sdssplitlen(c->querybuf,querylen," ",1,&argc); - - /* Leave data after the first line of the query in the buffer */ - c->querybuf = sdsrange(c->querybuf,querylen+2,-1); - - /* Setup argv array on client structure */ - if (c->argv) zfree(c->argv); - c->argv = zmalloc(sizeof(robj*)*argc); - - /* Create redis objects for all arguments. */ - for (c->argc = 0, j = 0; j < argc; j++) { - if (sdslen(argv[j])) { - c->argv[c->argc] = createObject(REDIS_STRING,argv[j]); - c->argc++; - } else { - sdsfree(argv[j]); - } - } - zfree(argv); - return REDIS_OK; -} - -/* Helper function. Trims query buffer to make the function that processes - * multi bulk requests idempotent. */ -static void setProtocolError(redisClient *c, int pos) { - if (server.verbosity >= REDIS_VERBOSE) { - sds client = getClientInfoString(c); - redisLog(REDIS_VERBOSE, - "Protocol error from client: %s", client); - sdsfree(client); - } - c->flags |= REDIS_CLOSE_AFTER_REPLY; - c->querybuf = sdsrange(c->querybuf,pos,-1); -} - -int processMultibulkBuffer(redisClient *c) { - char *newline = NULL; - int pos = 0, ok; - long long ll; - - if (c->multibulklen == 0) { - /* The client should have been reset */ - redisAssertWithInfo(c,NULL,c->argc == 0); - - /* Multi bulk length cannot be read without a \r\n */ - newline = strchr(c->querybuf,'\r'); - if (newline == NULL) { - if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) { - addReplyError(c,"Protocol error: too big mbulk count string"); - setProtocolError(c,0); - } - return REDIS_ERR; - } - - /* Buffer should also contain \n */ - if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2)) - return REDIS_ERR; - - /* We know for sure there is a whole line since newline != NULL, - * so go ahead and find out the multi bulk length. */ - redisAssertWithInfo(c,NULL,c->querybuf[0] == '*'); - ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll); - if (!ok || ll > 1024*1024) { - addReplyError(c,"Protocol error: invalid multibulk length"); - setProtocolError(c,pos); - return REDIS_ERR; - } - - pos = (newline-c->querybuf)+2; - if (ll <= 0) { - c->querybuf = sdsrange(c->querybuf,pos,-1); - return REDIS_OK; - } - - c->multibulklen = ll; - - /* Setup argv array on client structure */ - if (c->argv) zfree(c->argv); - c->argv = zmalloc(sizeof(robj*)*c->multibulklen); - } - - redisAssertWithInfo(c,NULL,c->multibulklen > 0); - while(c->multibulklen) { - /* Read bulk length if unknown */ - if (c->bulklen == -1) { - 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"); - setProtocolError(c,0); - } - break; - } - - /* Buffer should also contain \n */ - if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2)) - break; - - if (c->querybuf[pos] != '$') { - addReplyErrorFormat(c, - "Protocol error: expected '$', got '%c'", - c->querybuf[pos]); - setProtocolError(c,pos); - return REDIS_ERR; - } - - ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll); - if (!ok || ll < 0 || ll > 512*1024*1024) { - addReplyError(c,"Protocol error: invalid bulk length"); - setProtocolError(c,pos); - return REDIS_ERR; - } - - pos += newline-(c->querybuf+pos)+2; - if (ll >= REDIS_MBULK_BIG_ARG) { - /* If we are going to read a large object from network - * try to make it likely that it will start at c->querybuf - * boundary so that we can optimized object creation - * avoiding a large copy of data. */ - c->querybuf = sdsrange(c->querybuf,pos,-1); - pos = 0; - /* Hint the sds library about the amount of bytes this string is - * going to contain. */ - c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2); - } - c->bulklen = ll; - } - - /* Read bulk argument */ - if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) { - /* Not enough data (+2 == trailing \r\n) */ - break; - } else { - /* Optimization: if the buffer contanins JUST our bulk element - * instead of creating a new object by *copying* the sds we - * just use the current sds string. */ - if (pos == 0 && - c->bulklen >= REDIS_MBULK_BIG_ARG && - (signed) sdslen(c->querybuf) == c->bulklen+2) - { - c->argv[c->argc++] = createObject(REDIS_STRING,c->querybuf); - sdsIncrLen(c->querybuf,-2); /* remove CRLF */ - c->querybuf = sdsempty(); - /* Assume that if we saw a fat argument we'll see another one - * likely... */ - c->querybuf = sdsMakeRoomFor(c->querybuf,c->bulklen+2); - pos = 0; - } else { - c->argv[c->argc++] = - createStringObject(c->querybuf+pos,c->bulklen); - pos += c->bulklen+2; - } - c->bulklen = -1; - c->multibulklen--; - } - } - - /* Trim to pos */ - if (pos) c->querybuf = sdsrange(c->querybuf,pos,-1); - - /* We're done when c->multibulk == 0 */ - if (c->multibulklen == 0) return REDIS_OK; - - /* Still not read to process the command */ - return REDIS_ERR; -} - -void processInputBuffer(redisClient *c) { - /* Keep processing while there is something in the input buffer */ - while(sdslen(c->querybuf)) { - /* Immediately abort if the client is in the middle of something. */ - if (c->flags & REDIS_BLOCKED) return; - - /* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is - * written to the client. Make sure to not let the reply grow after - * this flag has been set (i.e. don't process more commands). */ - if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; - - /* Determine request type when unknown. */ - if (!c->reqtype) { - if (c->querybuf[0] == '*') { - c->reqtype = REDIS_REQ_MULTIBULK; - } else { - c->reqtype = REDIS_REQ_INLINE; - } - } - - if (c->reqtype == REDIS_REQ_INLINE) { - if (processInlineBuffer(c) != REDIS_OK) break; - } else if (c->reqtype == REDIS_REQ_MULTIBULK) { - if (processMultibulkBuffer(c) != REDIS_OK) break; - } else { - redisPanic("Unknown request type"); - } - - /* Multibulk processing could see a <= 0 length. */ - if (c->argc == 0) { - resetClient(c); - } else { - /* Only reset the client when the command was executed. */ - if (processCommand(c) == REDIS_OK) - resetClient(c); - } - } -} - -void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { - redisClient *c = (redisClient*) privdata; - int nread, readlen; - size_t qblen; - REDIS_NOTUSED(el); - REDIS_NOTUSED(mask); - - server.current_client = c; - readlen = REDIS_IOBUF_LEN; - /* If this is a multi bulk request, and we are processing a bulk reply - * that is large enough, try to maximize the probability that the query - * buffer contains exactly the SDS string representing the object, even - * at the risk of requiring more read(2) calls. This way the function - * processMultiBulkBuffer() can avoid copying buffers to create the - * Redis Object representing the argument. */ - if (c->reqtype == REDIS_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1 - && c->bulklen >= REDIS_MBULK_BIG_ARG) - { - int remaining = (unsigned)(c->bulklen+2)-sdslen(c->querybuf); - - if (remaining < readlen) readlen = remaining; - } - - qblen = sdslen(c->querybuf); - if (c->querybuf_peak < qblen) c->querybuf_peak = qblen; - c->querybuf = sdsMakeRoomFor(c->querybuf, readlen); - nread = read(fd, c->querybuf+qblen, readlen); - if (nread == -1) { - if (errno == EAGAIN) { - nread = 0; - } else { - redisLog(REDIS_VERBOSE, "Reading from client: %s",strerror(errno)); - freeClient(c); - return; - } - } else if (nread == 0) { - redisLog(REDIS_VERBOSE, "Client closed connection"); - freeClient(c); - return; - } - if (nread) { - sdsIncrLen(c->querybuf,nread); - c->lastinteraction = server.unixtime; - } else { - server.current_client = NULL; - return; - } - if (sdslen(c->querybuf) > server.client_max_querybuf_len) { - 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); - sdsfree(ci); - sdsfree(bytes); - freeClient(c); - return; - } - processInputBuffer(c); - server.current_client = NULL; -} - -void getClientsMaxBuffers(unsigned long *longest_output_list, - unsigned long *biggest_input_buffer) { - redisClient *c; - listNode *ln; - listIter li; - unsigned long lol = 0, bib = 0; - - listRewind(server.clients,&li); - while ((ln = listNext(&li)) != NULL) { - c = listNodeValue(ln); - - if (listLength(c->reply) > lol) lol = listLength(c->reply); - if (sdslen(c->querybuf) > bib) bib = sdslen(c->querybuf); - } - *longest_output_list = lol; - *biggest_input_buffer = bib; -} - -/* Turn a Redis client into an sds string representing its state. */ -sds getClientInfoString(redisClient *client) { - char ip[32], flags[16], events[3], *p; - int port = 0; /* initialized to zero for the unix socket case. */ - int emask; - - if (!(client->flags & REDIS_UNIX_SOCKET)) - anetPeerToString(client->fd,ip,&port); - p = flags; - if (client->flags & REDIS_SLAVE) { - if (client->flags & REDIS_MONITOR) - *p++ = 'O'; - else - *p++ = 'S'; - } - if (client->flags & REDIS_MASTER) *p++ = 'M'; - if (client->flags & REDIS_MULTI) *p++ = 'x'; - if (client->flags & REDIS_BLOCKED) *p++ = 'b'; - if (client->flags & REDIS_DIRTY_CAS) *p++ = 'd'; - if (client->flags & REDIS_CLOSE_AFTER_REPLY) *p++ = 'c'; - if (client->flags & REDIS_UNBLOCKED) *p++ = 'u'; - if (client->flags & REDIS_CLOSE_ASAP) *p++ = 'A'; - if (client->flags & REDIS_UNIX_SOCKET) *p++ = 'U'; - if (p == flags) *p++ = 'N'; - *p++ = '\0'; - - emask = client->fd == -1 ? 0 : aeGetFileEvents(server.el,client->fd); - p = events; - if (emask & AE_READABLE) *p++ = 'r'; - if (emask & AE_WRITABLE) *p++ = 'w'; - *p = '\0'; - return sdscatprintf(sdsempty(), - "addr=%s:%d fd=%d 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", - (client->flags & REDIS_UNIX_SOCKET) ? server.unixsocket : ip, - port,client->fd, - (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) 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"); -} - -sds getAllClientsInfoString(void) { - listNode *ln; - listIter li; - redisClient *client; - sds o = sdsempty(); - - listRewind(server.clients,&li); - while ((ln = listNext(&li)) != NULL) { - sds cs; - - client = listNodeValue(ln); - cs = getClientInfoString(client); - o = sdscatsds(o,cs); - sdsfree(cs); - o = sdscatlen(o,"\n",1); - } - return o; -} - -void clientCommand(redisClient *c) { - listNode *ln; - listIter li; - redisClient *client; - - if (!strcasecmp(c->argv[1]->ptr,"list") && c->argc == 2) { - sds o = getAllClientsInfoString(); - addReplyBulkCBuffer(c,o,sdslen(o)); - sdsfree(o); - } else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) { - listRewind(server.clients,&li); - while ((ln = listNext(&li)) != NULL) { - char ip[32], addr[64]; - int port; - - client = listNodeValue(ln); - if (anetPeerToString(client->fd,ip,&port) == -1) continue; - snprintf(addr,sizeof(addr),"%s:%d",ip,port); - if (strcmp(addr,c->argv[2]->ptr) == 0) { - addReply(c,shared.ok); - if (c == client) { - client->flags |= REDIS_CLOSE_AFTER_REPLY; - } else { - freeClient(client); - } - return; - } - } - addReplyError(c,"No such client"); - } else { - addReplyError(c, "Syntax error, try CLIENT (LIST | KILL ip:port)"); - } -} - -/* Rewrite the command vector of the client. All the new objects ref count - * is incremented. The old command vector is freed, and the old objects - * ref count is decremented. */ -void rewriteClientCommandVector(redisClient *c, int argc, ...) { - va_list ap; - int j; - robj **argv; /* The new argument vector */ - - argv = zmalloc(sizeof(robj*)*argc); - va_start(ap,argc); - for (j = 0; j < argc; j++) { - robj *a; - - a = va_arg(ap, robj*); - argv[j] = a; - incrRefCount(a); - } - /* We free the objects in the original vector at the end, so we are - * sure that if the same objects are reused in the new vector the - * refcount gets incremented before it gets decremented. */ - for (j = 0; j < c->argc; j++) decrRefCount(c->argv[j]); - zfree(c->argv); - /* Replace argv and argc with our new versions. */ - c->argv = argv; - c->argc = argc; - c->cmd = lookupCommand(c->argv[0]->ptr); - redisAssertWithInfo(c,NULL,c->cmd != NULL); - va_end(ap); -} - -/* Rewrite a single item in the command vector. - * The new val ref count is incremented, and the old decremented. */ -void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) { - robj *oldval; - - redisAssertWithInfo(c,NULL,i < c->argc); - oldval = c->argv[i]; - c->argv[i] = newval; - incrRefCount(newval); - decrRefCount(oldval); - - /* If this is the command name make sure to fix c->cmd. */ - if (i == 0) { - c->cmd = lookupCommand(c->argv[0]->ptr); - redisAssertWithInfo(c,NULL,c->cmd != NULL); - } -} - -/* This function returns the number of bytes that Redis is virtually - * using to store the reply still not read by the client. - * It is "virtual" since the reply output list may contain objects that - * are shared and are not really using additional memory. - * - * The function returns the total sum of the length of all the objects - * stored in the output list, plus the memory used to allocate every - * list node. The static reply buffer is not taken into account since it - * is allocated anyway. - * - * Note: this function is very fast so can be called as many time as - * the caller wishes. The main usage of this function currently is - * enforcing the client output length limits. */ -unsigned long getClientOutputBufferMemoryUsage(redisClient *c) { - unsigned long list_item_size = sizeof(listNode)+sizeof(robj); - - return c->reply_bytes + (list_item_size*listLength(c->reply)); -} - -/* Get the class of a client, used in order to enforce limits to different - * classes of clients. - * - * The function will return one of the following: - * REDIS_CLIENT_LIMIT_CLASS_NORMAL -> Normal client - * REDIS_CLIENT_LIMIT_CLASS_SLAVE -> Slave or client executing MONITOR command - * REDIS_CLIENT_LIMIT_CLASS_PUBSUB -> Client subscribed to Pub/Sub channels - */ -int getClientLimitClass(redisClient *c) { - if (c->flags & REDIS_SLAVE) return REDIS_CLIENT_LIMIT_CLASS_SLAVE; - if (dictSize(c->pubsub_channels) || listLength(c->pubsub_patterns)) - return REDIS_CLIENT_LIMIT_CLASS_PUBSUB; - return REDIS_CLIENT_LIMIT_CLASS_NORMAL; -} - -int getClientLimitClassByName(char *name) { - if (!strcasecmp(name,"normal")) return REDIS_CLIENT_LIMIT_CLASS_NORMAL; - else if (!strcasecmp(name,"slave")) return REDIS_CLIENT_LIMIT_CLASS_SLAVE; - else if (!strcasecmp(name,"pubsub")) return REDIS_CLIENT_LIMIT_CLASS_PUBSUB; - else return -1; -} - -char *getClientLimitClassName(int class) { - switch(class) { - case REDIS_CLIENT_LIMIT_CLASS_NORMAL: return "normal"; - case REDIS_CLIENT_LIMIT_CLASS_SLAVE: return "slave"; - case REDIS_CLIENT_LIMIT_CLASS_PUBSUB: return "pubsub"; - default: return NULL; - } -} - -/* The function checks if the client reached output buffer soft or hard - * limit, and also update the state needed to check the soft limit as - * a side effect. - * - * Return value: non-zero if the client reached the soft or the hard limit. - * Otherwise zero is returned. */ -int checkClientOutputBufferLimits(redisClient *c) { - int soft = 0, hard = 0, class; - unsigned long used_mem = getClientOutputBufferMemoryUsage(c); - - class = getClientLimitClass(c); - if (server.client_obuf_limits[class].hard_limit_bytes && - used_mem >= server.client_obuf_limits[class].hard_limit_bytes) - hard = 1; - if (server.client_obuf_limits[class].soft_limit_bytes && - used_mem >= server.client_obuf_limits[class].soft_limit_bytes) - soft = 1; - - /* We need to check if the soft limit is reached continuously for the - * specified amount of seconds. */ - if (soft) { - if (c->obuf_soft_limit_reached_time == 0) { - c->obuf_soft_limit_reached_time = server.unixtime; - soft = 0; /* First time we see the soft limit reached */ - } else { - time_t elapsed = server.unixtime - c->obuf_soft_limit_reached_time; - - if (elapsed <= - server.client_obuf_limits[class].soft_limit_seconds) { - soft = 0; /* The client still did not reached the max number of - seconds for the soft limit to be considered - reached. */ - } - } - } else { - c->obuf_soft_limit_reached_time = 0; - } - return soft || hard; -} - -/* Asynchronously close a client if soft or hard limit is reached on the - * output buffer size. The caller can check if the client will be closed - * checking if the client REDIS_CLOSE_ASAP flag is set. - * - * Note: we need to close the client asynchronously because this function is - * called from contexts where the client can't be freed safely, i.e. from the - * lower level functions pushing data inside the client output buffers. */ -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 = getClientInfoString(c); - - freeClientAsync(c); - redisLog(REDIS_WARNING,"Client %s scheduled to be closed ASAP for overcoming of output buffer limits.", client); - sdsfree(client); - } -} - -/* Helper function used by freeMemoryIfNeeded() in order to flush slaves - * output buffers without returning control to the event loop. */ -void flushSlavesOutputBuffers(void) { - listIter li; - listNode *ln; - - listRewind(server.slaves,&li); - while((ln = listNext(&li))) { - redisClient *slave = listNodeValue(ln); - int events; - - events = aeGetFileEvents(server.el,slave->fd); - if (events & AE_WRITABLE && - slave->replstate == REDIS_REPL_ONLINE && - listLength(slave->reply)) - { - sendReplyToClient(server.el,slave->fd,slave,0); - } - } -} +/* + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "redis.h" +#include + +static void setProtocolError(redisClient *c, int pos); + +/* To evaluate the output buffer size of a client we need to get size of + * allocated objects, however we can't used zmalloc_size() directly on sds + * strings because of the trick they use to work (the header is before the + * returned pointer), so we use this helper function. */ +size_t zmalloc_size_sds(sds s) { + return zmalloc_size(s-sizeof(struct sdshdr)); +} + +void *dupClientReplyValue(void *o) { + incrRefCount((robj*)o); + return o; +} + +int listMatchObjects(void *a, void *b) { + return equalStringObjects(a,b); +} + +redisClient *createClient(int fd) { + redisClient *c = zmalloc(sizeof(redisClient)); + + /* passing -1 as fd it is possible to create a non connected client. + * This is useful since all the Redis commands needs to be executed + * in the context of a client. When commands are executed in other + * contexts (for instance a Lua script) we need a non connected client. */ + if (fd != -1) { + anetNonBlock(NULL,fd); + anetTcpNoDelay(NULL,fd); + if (aeCreateFileEvent(server.el,fd,AE_READABLE, + readQueryFromClient, c) == AE_ERR) + { + close(fd); + zfree(c); + return NULL; + } + } + + selectDb(c,0); + c->fd = fd; + c->name = NULL; + c->bufpos = 0; + c->querybuf = sdsempty(); + c->querybuf_peak = 0; + c->reqtype = 0; + c->argc = 0; + c->argv = NULL; + c->cmd = c->lastcmd = NULL; + c->multibulklen = 0; + c->bulklen = -1; + c->sentlen = 0; + c->flags = 0; + c->ctime = c->lastinteraction = server.unixtime; + c->authenticated = 0; + c->replstate = REDIS_REPL_NONE; + c->slave_listening_port = 0; + c->reply = listCreate(); + c->reply_bytes = 0; + c->obuf_soft_limit_reached_time = 0; + listSetFreeMethod(c->reply,decrRefCount); + listSetDupMethod(c->reply,dupClientReplyValue); + c->bpop.keys = dictCreate(&setDictType,NULL); + c->bpop.timeout = 0; + c->bpop.target = NULL; + c->io_keys = listCreate(); + c->watched_keys = listCreate(); + listSetFreeMethod(c->io_keys,decrRefCount); + c->pubsub_channels = dictCreate(&setDictType,NULL); + c->pubsub_patterns = listCreate(); + listSetFreeMethod(c->pubsub_patterns,decrRefCount); + listSetMatchMethod(c->pubsub_patterns,listMatchObjects); + if (fd != -1) listAddNodeTail(server.clients,c); + initClientMultiState(c); + return c; +} + +/* This function is called every time we are going to transmit new data + * to the client. The behavior is the following: + * + * If the client should receive new data (normal clients will) the function + * returns REDIS_OK, and make sure to install the write handler in our event + * loop so that when the socket is writable new data gets written. + * + * If the client should not receive new data, because it is a fake client + * or a slave, or because the setup of the write handler failed, the function + * returns REDIS_ERR. + * + * Typically gets called every time a reply is built, before adding more + * data to the clients output buffers. If the function returns REDIS_ERR no + * data should be appended to the output buffers. */ +int prepareClientToWrite(redisClient *c) { + if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK; + if (c->fd <= 0) return REDIS_ERR; /* Fake client */ + if (c->bufpos == 0 && listLength(c->reply) == 0 && + (c->replstate == REDIS_REPL_NONE || + c->replstate == REDIS_REPL_ONLINE) && + aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, + sendReplyToClient, c) == AE_ERR) return REDIS_ERR; + return REDIS_OK; +} + +/* Create a duplicate of the last object in the reply list when + * it is not exclusively owned by the reply list. */ +robj *dupLastObjectIfNeeded(list *reply) { + robj *new, *cur; + listNode *ln; + redisAssert(listLength(reply) > 0); + ln = listLast(reply); + cur = listNodeValue(ln); + if (cur->refcount > 1) { + new = dupStringObject(cur); + decrRefCount(cur); + listNodeValue(ln) = new; + } + return listNodeValue(ln); +} + +/* ----------------------------------------------------------------------------- + * Low level functions to add more data to output buffers. + * -------------------------------------------------------------------------- */ + +int _addReplyToBuffer(redisClient *c, char *s, size_t len) { + size_t available = sizeof(c->buf)-c->bufpos; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK; + + /* If there already are entries in the reply list, we cannot + * add anything more to the static buffer. */ + if (listLength(c->reply) > 0) return REDIS_ERR; + + /* Check that the buffer has enough space available for this string. */ + if (len > available) return REDIS_ERR; + + memcpy(c->buf+c->bufpos,s,len); + c->bufpos+=len; + return REDIS_OK; +} + +void _addReplyObjectToList(redisClient *c, robj *o) { + robj *tail; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; + + if (listLength(c->reply) == 0) { + incrRefCount(o); + listAddNodeTail(c->reply,o); + c->reply_bytes += zmalloc_size_sds(o->ptr); + } else { + tail = listNodeValue(listLast(c->reply)); + + /* Append to this object when possible. */ + if (tail->ptr != NULL && + sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES) + { + c->reply_bytes -= zmalloc_size_sds(tail->ptr); + tail = dupLastObjectIfNeeded(c->reply); + tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr)); + c->reply_bytes += zmalloc_size_sds(tail->ptr); + } else { + incrRefCount(o); + listAddNodeTail(c->reply,o); + c->reply_bytes += zmalloc_size_sds(o->ptr); + } + } + asyncCloseClientOnOutputBufferLimitReached(c); +} + +/* This method takes responsibility over the sds. When it is no longer + * needed it will be free'd, otherwise it ends up in a robj. */ +void _addReplySdsToList(redisClient *c, sds s) { + robj *tail; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) { + sdsfree(s); + return; + } + + if (listLength(c->reply) == 0) { + listAddNodeTail(c->reply,createObject(REDIS_STRING,s)); + c->reply_bytes += zmalloc_size_sds(s); + } else { + tail = listNodeValue(listLast(c->reply)); + + /* Append to this object when possible. */ + if (tail->ptr != NULL && + sdslen(tail->ptr)+sdslen(s) <= REDIS_REPLY_CHUNK_BYTES) + { + c->reply_bytes -= zmalloc_size_sds(tail->ptr); + tail = dupLastObjectIfNeeded(c->reply); + tail->ptr = sdscatlen(tail->ptr,s,sdslen(s)); + c->reply_bytes += zmalloc_size_sds(tail->ptr); + sdsfree(s); + } else { + listAddNodeTail(c->reply,createObject(REDIS_STRING,s)); + c->reply_bytes += zmalloc_size_sds(s); + } + } + asyncCloseClientOnOutputBufferLimitReached(c); +} + +void _addReplyStringToList(redisClient *c, char *s, size_t len) { + robj *tail; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; + + if (listLength(c->reply) == 0) { + robj *o = createStringObject(s,len); + + listAddNodeTail(c->reply,o); + c->reply_bytes += zmalloc_size_sds(o->ptr); + } else { + tail = listNodeValue(listLast(c->reply)); + + /* Append to this object when possible. */ + if (tail->ptr != NULL && + sdslen(tail->ptr)+len <= REDIS_REPLY_CHUNK_BYTES) + { + c->reply_bytes -= zmalloc_size_sds(tail->ptr); + tail = dupLastObjectIfNeeded(c->reply); + tail->ptr = sdscatlen(tail->ptr,s,len); + c->reply_bytes += zmalloc_size_sds(tail->ptr); + } else { + robj *o = createStringObject(s,len); + + listAddNodeTail(c->reply,o); + c->reply_bytes += zmalloc_size_sds(o->ptr); + } + } + asyncCloseClientOnOutputBufferLimitReached(c); +} + +/* ----------------------------------------------------------------------------- + * Higher level functions to queue data on the client output buffer. + * The following functions are the ones that commands implementations will call. + * -------------------------------------------------------------------------- */ + +void addReply(redisClient *c, robj *obj) { + if (prepareClientToWrite(c) != REDIS_OK) return; + + /* This is an important place where we can avoid copy-on-write + * when there is a saving child running, avoiding touching the + * refcount field of the object if it's not needed. + * + * If the encoding is RAW and there is room in the static buffer + * we'll be able to send the object to the client without + * messing with its page. */ + if (obj->encoding == REDIS_ENCODING_RAW) { + if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK) + _addReplyObjectToList(c,obj); + } else if (obj->encoding == REDIS_ENCODING_INT) { + /* Optimization: if there is room in the static buffer for 32 bytes + * (more than the max chars a 64 bit integer can take as string) we + * avoid decoding the object and go for the lower level approach. */ + if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) { + char buf[32]; + int len; + + len = ll2string(buf,sizeof(buf),(long)obj->ptr); + if (_addReplyToBuffer(c,buf,len) == REDIS_OK) + return; + /* else... continue with the normal code path, but should never + * happen actually since we verified there is room. */ + } + 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()"); + } +} + +void addReplySds(redisClient *c, sds s) { + if (prepareClientToWrite(c) != REDIS_OK) { + /* The caller expects the sds to be free'd. */ + sdsfree(s); + return; + } + if (_addReplyToBuffer(c,s,sdslen(s)) == REDIS_OK) { + sdsfree(s); + } else { + /* This method free's the sds when it is no longer needed. */ + _addReplySdsToList(c,s); + } +} + +void addReplyString(redisClient *c, char *s, size_t len) { + if (prepareClientToWrite(c) != REDIS_OK) return; + if (_addReplyToBuffer(c,s,len) != REDIS_OK) + _addReplyStringToList(c,s,len); +} + +void addReplyErrorLength(redisClient *c, char *s, size_t len) { + addReplyString(c,"-ERR ",5); + addReplyString(c,s,len); + addReplyString(c,"\r\n",2); +} + +void addReplyError(redisClient *c, char *err) { + addReplyErrorLength(c,err,strlen(err)); +} + +void addReplyErrorFormat(redisClient *c, const char *fmt, ...) { + size_t l, j; + va_list ap; + va_start(ap,fmt); + sds s = sdscatvprintf(sdsempty(),fmt,ap); + va_end(ap); + /* Make sure there are no newlines in the string, otherwise invalid protocol + * is emitted. */ + l = sdslen(s); + for (j = 0; j < l; j++) { + if (s[j] == '\r' || s[j] == '\n') s[j] = ' '; + } + addReplyErrorLength(c,s,sdslen(s)); + sdsfree(s); +} + +void addReplyStatusLength(redisClient *c, char *s, size_t len) { + addReplyString(c,"+",1); + addReplyString(c,s,len); + addReplyString(c,"\r\n",2); +} + +void addReplyStatus(redisClient *c, char *status) { + addReplyStatusLength(c,status,strlen(status)); +} + +void addReplyStatusFormat(redisClient *c, const char *fmt, ...) { + va_list ap; + va_start(ap,fmt); + sds s = sdscatvprintf(sdsempty(),fmt,ap); + va_end(ap); + addReplyStatusLength(c,s,sdslen(s)); + sdsfree(s); +} + +/* Adds an empty object to the reply list that will contain the multi bulk + * length, which is not known when this function is called. */ +void *addDeferredMultiBulkLength(redisClient *c) { + /* Note that we install the write event here even if the object is not + * ready to be sent, since we are sure that before returning to the + * event loop setDeferredMultiBulkLength() will be called. */ + if (prepareClientToWrite(c) != REDIS_OK) return NULL; + listAddNodeTail(c->reply,createObject(REDIS_STRING,NULL)); + return listLast(c->reply); +} + +/* Populate the length object and try glueing it to the next chunk. */ +void setDeferredMultiBulkLength(redisClient *c, void *node, long length) { + listNode *ln = (listNode*)node; + robj *len, *next; + + /* Abort when *node is NULL (see addDeferredMultiBulkLength). */ + if (node == NULL) return; + + len = listNodeValue(ln); + len->ptr = sdscatprintf(sdsempty(),"*%ld\r\n",length); + c->reply_bytes += zmalloc_size_sds(len->ptr); + if (ln->next != NULL) { + next = listNodeValue(ln->next); + + /* Only glue when the next node is non-NULL (an sds in this case) */ + if (next->ptr != NULL) { + c->reply_bytes -= zmalloc_size_sds(len->ptr); + c->reply_bytes -= zmalloc_size_sds(next->ptr); + len->ptr = sdscatlen(len->ptr,next->ptr,sdslen(next->ptr)); + c->reply_bytes += zmalloc_size_sds(len->ptr); + listDelNode(c->reply,ln->next); + } + } + asyncCloseClientOnOutputBufferLimitReached(c); +} + +/* Add a duble as a bulk reply */ +void addReplyDouble(redisClient *c, double d) { + char dbuf[128], sbuf[128]; + int dlen, slen; + dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d); + slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf); + addReplyString(c,sbuf,slen); +} + +/* Add a long long as integer reply or bulk len / multi bulk count. + * Basically this is used to output . */ +void addReplyLongLongWithPrefix(redisClient *c, long long ll, char prefix) { + char buf[128]; + int len; + + /* Things like $3\r\n or *2\r\n are emitted very often by the protocol + * so we have a few shared objects to use if the integer is small + * like it is most of the times. */ + if (prefix == '*' && ll < REDIS_SHARED_BULKHDR_LEN) { + addReply(c,shared.mbulkhdr[ll]); + return; + } else if (prefix == '$' && ll < REDIS_SHARED_BULKHDR_LEN) { + addReply(c,shared.bulkhdr[ll]); + return; + } + + buf[0] = prefix; + len = ll2string(buf+1,sizeof(buf)-1,ll); + buf[len+1] = '\r'; + buf[len+2] = '\n'; + addReplyString(c,buf,len+3); +} + +void addReplyLongLong(redisClient *c, long long ll) { + if (ll == 0) + addReply(c,shared.czero); + else if (ll == 1) + addReply(c,shared.cone); + else + addReplyLongLongWithPrefix(c,ll,':'); +} + +void addReplyMultiBulkLen(redisClient *c, long length) { + addReplyLongLongWithPrefix(c,length,'*'); +} + +/* Create the length prefix of a bulk reply, example: $2234 */ +void addReplyBulkLen(redisClient *c, robj *obj) { + size_t len; + + if (obj->encoding == REDIS_ENCODING_RAW) { + len = sdslen(obj->ptr); + } else { + long n = (long)obj->ptr; + + /* Compute how many bytes will take this integer as a radix 10 string */ + len = 1; + if (n < 0) { + len++; + n = -n; + } + while((n = n/10) != 0) { + len++; + } + } + addReplyLongLongWithPrefix(c,len,'$'); +} + +/* Add a Redis Object as a bulk reply */ +void addReplyBulk(redisClient *c, robj *obj) { + addReplyBulkLen(c,obj); + addReply(c,obj); + addReply(c,shared.crlf); +} + +/* Add a C buffer as bulk reply */ +void addReplyBulkCBuffer(redisClient *c, void *p, size_t len) { + addReplyLongLongWithPrefix(c,len,'$'); + addReplyString(c,p,len); + addReply(c,shared.crlf); +} + +/* Add a C nul term string as bulk reply */ +void addReplyBulkCString(redisClient *c, char *s) { + if (s == NULL) { + addReply(c,shared.nullbulk); + } else { + addReplyBulkCBuffer(c,s,strlen(s)); + } +} + +/* Add a long long as a bulk reply */ +void addReplyBulkLongLong(redisClient *c, long long ll) { + char buf[64]; + int len; + + len = ll2string(buf,64,ll); + addReplyBulkCBuffer(c,buf,len); +} + +/* Copy 'src' client output buffers into 'dst' client output buffers. + * The function takes care of freeing the old output buffers of the + * destination client. */ +void copyClientOutputBuffer(redisClient *dst, redisClient *src) { + listRelease(dst->reply); + dst->reply = listDup(src->reply); + memcpy(dst->buf,src->buf,src->bufpos); + dst->bufpos = src->bufpos; + dst->reply_bytes = src->reply_bytes; +} + +static void acceptCommonHandler(int fd, int flags) { + redisClient *c; + if ((c = createClient(fd)) == NULL) { + redisLog(REDIS_WARNING, + "Error registering fd event for the new client: %s (fd=%d)", + strerror(errno),fd); + close(fd); /* May be already closed, just ignore errors */ + return; + } + /* If maxclient directive is set and this is one client more... close the + * connection. Note that we create the client instead to check before + * for this condition, since now the socket is already set in nonblocking + * mode and we can send an error for free using the Kernel I/O */ + if (listLength(server.clients) > server.maxclients) { + char *err = "-ERR max number of clients reached\r\n"; + + /* That's a best effort error message, don't check write errors */ + if (write(c->fd,err,strlen(err)) == -1) { + /* Nothing to do, Just to avoid the warning... */ + } + server.stat_rejected_conn++; + freeClient(c); + return; + } + server.stat_numconnections++; + c->flags |= flags; +} + +void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) { + int cport, cfd; + char cip[128]; + REDIS_NOTUSED(el); + REDIS_NOTUSED(mask); + REDIS_NOTUSED(privdata); + + cfd = anetTcpAccept(server.neterr, fd, cip, &cport); + if (cfd == AE_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; + REDIS_NOTUSED(el); + REDIS_NOTUSED(mask); + REDIS_NOTUSED(privdata); + + 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++) + decrRefCount(c->argv[j]); + c->argc = 0; + c->cmd = NULL; +} + +/* Close all the slaves connections. This is useful in chained replication + * when we resync with our own master and want to force all our slaves to + * resync with us as well. */ +void disconnectSlaves(void) { + while (listLength(server.slaves)) { + listNode *ln = listFirst(server.slaves); + freeClient((redisClient*)ln->value); + } +} + +void freeClient(redisClient *c) { + listNode *ln; + + /* If this is marked as current client unset it */ + if (server.current_client == c) server.current_client = NULL; + + /* Note that if the client we are freeing is blocked into a blocking + * call, we have to set querybuf to NULL *before* to call + * unblockClientWaitingData() to avoid processInputBuffer() will get + * called. Also it is important to remove the file events after + * this, because this call adds the READABLE event. */ + sdsfree(c->querybuf); + c->querybuf = NULL; + if (c->flags & REDIS_BLOCKED) + unblockClientWaitingData(c); + dictRelease(c->bpop.keys); + + /* UNWATCH all the keys */ + unwatchAllKeys(c); + listRelease(c->watched_keys); + /* Unsubscribe from all the pubsub channels */ + pubsubUnsubscribeAllChannels(c,0); + pubsubUnsubscribeAllPatterns(c,0); + dictRelease(c->pubsub_channels); + listRelease(c->pubsub_patterns); + /* Obvious cleanup */ + aeDeleteFileEvent(server.el,c->fd,AE_READABLE); + aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); + listRelease(c->reply); + freeClientArgv(c); + close(c->fd); + /* Remove from the list of clients */ + ln = listSearchKey(server.clients,c); + redisAssert(ln != NULL); + listDelNode(server.clients,ln); + /* When client was just unblocked because of a blocking operation, + * remove it from the list with unblocked clients. */ + if (c->flags & REDIS_UNBLOCKED) { + ln = listSearchKey(server.unblocked_clients,c); + redisAssert(ln != NULL); + listDelNode(server.unblocked_clients,ln); + } + listRelease(c->io_keys); + /* Master/slave cleanup. + * Case 1: we lost the connection with a slave. */ + if (c->flags & REDIS_SLAVE) { + if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1) + close(c->repldbfd); + list *l = (c->flags & REDIS_MONITOR) ? server.monitors : server.slaves; + ln = listSearchKey(l,c); + redisAssert(ln != NULL); + listDelNode(l,ln); + } + + /* Case 2: we lost the connection with the master. */ + if (c->flags & REDIS_MASTER) { + server.master = NULL; + server.repl_state = REDIS_REPL_CONNECT; + server.repl_down_since = server.unixtime; + /* We lost connection with our master, force our slaves to resync + * with us as well to load the new data set. + * + * If server.masterhost is NULL the user called SLAVEOF NO ONE so + * slave resync is not needed. */ + if (server.masterhost != NULL) disconnectSlaves(); + } + + /* If this client was scheduled for async freeing we need to remove it + * from the queue. */ + if (c->flags & REDIS_CLOSE_ASAP) { + ln = listSearchKey(server.clients_to_close,c); + redisAssert(ln != NULL); + listDelNode(server.clients_to_close,ln); + } + + /* Release memory */ + if (c->name) decrRefCount(c->name); + zfree(c->argv); + freeClientMultiState(c); + zfree(c); +} + +/* Schedule a client to free it at a safe time in the serverCron() function. + * This function is useful when we need to terminate a client but we are in + * a context where calling freeClient() is not possible, because the client + * should be valid for the continuation of the flow of the program. */ +void freeClientAsync(redisClient *c) { + if (c->flags & REDIS_CLOSE_ASAP) return; + c->flags |= REDIS_CLOSE_ASAP; + listAddNodeTail(server.clients_to_close,c); +} + +void freeClientsInAsyncFreeQueue(void) { + while (listLength(server.clients_to_close)) { + listNode *ln = listFirst(server.clients_to_close); + redisClient *c = listNodeValue(ln); + + c->flags &= ~REDIS_CLOSE_ASAP; + freeClient(c); + listDelNode(server.clients_to_close,ln); + } +} + +void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { + redisClient *c = privdata; + int nwritten = 0, totwritten = 0, objlen; + size_t objmem; + robj *o; + REDIS_NOTUSED(el); + REDIS_NOTUSED(mask); + + while(c->bufpos > 0 || listLength(c->reply)) { + if (c->bufpos > 0) { + if (c->flags & REDIS_MASTER) { + /* Don't reply to a master */ + nwritten = c->bufpos - c->sentlen; + } else { + nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen); + if (nwritten <= 0) break; + } + c->sentlen += nwritten; + totwritten += nwritten; + + /* If the buffer was sent, set bufpos to zero to continue with + * the remainder of the reply. */ + if (c->sentlen == c->bufpos) { + c->bufpos = 0; + c->sentlen = 0; + } + } else { + o = listNodeValue(listFirst(c->reply)); + objlen = sdslen(o->ptr); + objmem = zmalloc_size_sds(o->ptr); + + if (objlen == 0) { + listDelNode(c->reply,listFirst(c->reply)); + continue; + } + + if (c->flags & REDIS_MASTER) { + /* Don't reply to a master */ + nwritten = objlen - c->sentlen; + } else { + nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen); + if (nwritten <= 0) break; + } + c->sentlen += nwritten; + totwritten += nwritten; + + /* If we fully sent the object on head go to the next one */ + if (c->sentlen == objlen) { + listDelNode(c->reply,listFirst(c->reply)); + c->sentlen = 0; + c->reply_bytes -= objmem; + } + } + /* Note that we avoid to send more than REDIS_MAX_WRITE_PER_EVENT + * bytes, in a single threaded server it's a good idea to serve + * other clients as well, even if a very large request comes from + * super fast link that is always able to accept data (in real world + * scenario think about 'KEYS *' against the loopback interface). + * + * However if we are over the maxmemory limit we ignore that and + * just deliver as much data as it is possible to deliver. */ + if (totwritten > REDIS_MAX_WRITE_PER_EVENT && + (server.maxmemory == 0 || + zmalloc_used_memory() < server.maxmemory)) break; + } + if (nwritten == -1) { + if (errno == EAGAIN) { + nwritten = 0; + } else { + redisLog(REDIS_VERBOSE, + "Error writing to client: %s", strerror(errno)); + freeClient(c); + return; + } + } + if (totwritten > 0) c->lastinteraction = server.unixtime; + if (c->bufpos == 0 && listLength(c->reply) == 0) { + c->sentlen = 0; + aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); + + /* Close connection after entire reply has been sent. */ + if (c->flags & REDIS_CLOSE_AFTER_REPLY) freeClient(c); + } +} + +/* resetClient prepare the client to process the next command */ +void resetClient(redisClient *c) { + freeClientArgv(c); + c->reqtype = 0; + c->multibulklen = 0; + c->bulklen = -1; + /* We clear the ASKING flag as well if we are not inside a MULTI. */ + if (!(c->flags & REDIS_MULTI)) c->flags &= (~REDIS_ASKING); +} + +int processInlineBuffer(redisClient *c) { + char *newline = strstr(c->querybuf,"\r\n"); + int argc, j; + sds *argv; + size_t querylen; + + /* Nothing to do without a \r\n */ + if (newline == NULL) { + if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) { + addReplyError(c,"Protocol error: too big inline request"); + setProtocolError(c,0); + } + return REDIS_ERR; + } + + /* Split the input buffer up to the \r\n */ + querylen = newline-(c->querybuf); + argv = sdssplitlen(c->querybuf,querylen," ",1,&argc); + + /* Leave data after the first line of the query in the buffer */ + c->querybuf = sdsrange(c->querybuf,querylen+2,-1); + + /* Setup argv array on client structure */ + if (c->argv) zfree(c->argv); + c->argv = zmalloc(sizeof(robj*)*argc); + + /* Create redis objects for all arguments. */ + for (c->argc = 0, j = 0; j < argc; j++) { + if (sdslen(argv[j])) { + c->argv[c->argc] = createObject(REDIS_STRING,argv[j]); + c->argc++; + } else { + sdsfree(argv[j]); + } + } + zfree(argv); + return REDIS_OK; +} + +/* Helper function. Trims query buffer to make the function that processes + * multi bulk requests idempotent. */ +static void setProtocolError(redisClient *c, int pos) { + if (server.verbosity >= REDIS_VERBOSE) { + sds client = getClientInfoString(c); + redisLog(REDIS_VERBOSE, + "Protocol error from client: %s", client); + sdsfree(client); + } + c->flags |= REDIS_CLOSE_AFTER_REPLY; + c->querybuf = sdsrange(c->querybuf,pos,-1); +} + +int processMultibulkBuffer(redisClient *c) { + char *newline = NULL; + int pos = 0, ok; + long long ll; + + if (c->multibulklen == 0) { + /* The client should have been reset */ + redisAssertWithInfo(c,NULL,c->argc == 0); + + /* Multi bulk length cannot be read without a \r\n */ + newline = strchr(c->querybuf,'\r'); + if (newline == NULL) { + if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) { + addReplyError(c,"Protocol error: too big mbulk count string"); + setProtocolError(c,0); + } + return REDIS_ERR; + } + + /* Buffer should also contain \n */ + if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2)) + return REDIS_ERR; + + /* We know for sure there is a whole line since newline != NULL, + * so go ahead and find out the multi bulk length. */ + redisAssertWithInfo(c,NULL,c->querybuf[0] == '*'); + ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll); + if (!ok || ll > 1024*1024) { + addReplyError(c,"Protocol error: invalid multibulk length"); + setProtocolError(c,pos); + return REDIS_ERR; + } + + pos = (newline-c->querybuf)+2; + if (ll <= 0) { + c->querybuf = sdsrange(c->querybuf,pos,-1); + return REDIS_OK; + } + + c->multibulklen = ll; + + /* Setup argv array on client structure */ + if (c->argv) zfree(c->argv); + c->argv = zmalloc(sizeof(robj*)*c->multibulklen); + } + + redisAssertWithInfo(c,NULL,c->multibulklen > 0); + while(c->multibulklen) { + /* Read bulk length if unknown */ + if (c->bulklen == -1) { + 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"); + setProtocolError(c,0); + } + break; + } + + /* Buffer should also contain \n */ + if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2)) + break; + + if (c->querybuf[pos] != '$') { + addReplyErrorFormat(c, + "Protocol error: expected '$', got '%c'", + c->querybuf[pos]); + setProtocolError(c,pos); + return REDIS_ERR; + } + + ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll); + if (!ok || ll < 0 || ll > 512*1024*1024) { + addReplyError(c,"Protocol error: invalid bulk length"); + setProtocolError(c,pos); + return REDIS_ERR; + } + + pos += newline-(c->querybuf+pos)+2; + if (ll >= REDIS_MBULK_BIG_ARG) { + /* If we are going to read a large object from network + * try to make it likely that it will start at c->querybuf + * boundary so that we can optimized object creation + * avoiding a large copy of data. */ + c->querybuf = sdsrange(c->querybuf,pos,-1); + pos = 0; + /* Hint the sds library about the amount of bytes this string is + * going to contain. */ + c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2); + } + c->bulklen = ll; + } + + /* Read bulk argument */ + if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) { + /* Not enough data (+2 == trailing \r\n) */ + break; + } else { + /* Optimization: if the buffer contanins JUST our bulk element + * instead of creating a new object by *copying* the sds we + * just use the current sds string. */ + if (pos == 0 && + c->bulklen >= REDIS_MBULK_BIG_ARG && + (signed) sdslen(c->querybuf) == c->bulklen+2) + { + c->argv[c->argc++] = createObject(REDIS_STRING,c->querybuf); + sdsIncrLen(c->querybuf,-2); /* remove CRLF */ + c->querybuf = sdsempty(); + /* Assume that if we saw a fat argument we'll see another one + * likely... */ + c->querybuf = sdsMakeRoomFor(c->querybuf,c->bulklen+2); + pos = 0; + } else { + c->argv[c->argc++] = + createStringObject(c->querybuf+pos,c->bulklen); + pos += c->bulklen+2; + } + c->bulklen = -1; + c->multibulklen--; + } + } + + /* Trim to pos */ + if (pos) c->querybuf = sdsrange(c->querybuf,pos,-1); + + /* We're done when c->multibulk == 0 */ + if (c->multibulklen == 0) return REDIS_OK; + + /* Still not read to process the command */ + return REDIS_ERR; +} + +void processInputBuffer(redisClient *c) { + /* Keep processing while there is something in the input buffer */ + while(sdslen(c->querybuf)) { + /* Immediately abort if the client is in the middle of something. */ + if (c->flags & REDIS_BLOCKED) return; + + /* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is + * written to the client. Make sure to not let the reply grow after + * this flag has been set (i.e. don't process more commands). */ + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; + + /* Determine request type when unknown. */ + if (!c->reqtype) { + if (c->querybuf[0] == '*') { + c->reqtype = REDIS_REQ_MULTIBULK; + } else { + c->reqtype = REDIS_REQ_INLINE; + } + } + + if (c->reqtype == REDIS_REQ_INLINE) { + if (processInlineBuffer(c) != REDIS_OK) break; + } else if (c->reqtype == REDIS_REQ_MULTIBULK) { + if (processMultibulkBuffer(c) != REDIS_OK) break; + } else { + redisPanic("Unknown request type"); + } + + /* Multibulk processing could see a <= 0 length. */ + if (c->argc == 0) { + resetClient(c); + } else { + /* Only reset the client when the command was executed. */ + if (processCommand(c) == REDIS_OK) + resetClient(c); + } + } +} + +void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { + redisClient *c = (redisClient*) privdata; + int nread, readlen; + size_t qblen; + REDIS_NOTUSED(el); + REDIS_NOTUSED(mask); + + server.current_client = c; + readlen = REDIS_IOBUF_LEN; + /* If this is a multi bulk request, and we are processing a bulk reply + * that is large enough, try to maximize the probability that the query + * buffer contains exactly the SDS string representing the object, even + * at the risk of requiring more read(2) calls. This way the function + * processMultiBulkBuffer() can avoid copying buffers to create the + * Redis Object representing the argument. */ + if (c->reqtype == REDIS_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1 + && c->bulklen >= REDIS_MBULK_BIG_ARG) + { + int remaining = (unsigned)(c->bulklen+2)-sdslen(c->querybuf); + + if (remaining < readlen) readlen = remaining; + } + + qblen = sdslen(c->querybuf); + if (c->querybuf_peak < qblen) c->querybuf_peak = qblen; + c->querybuf = sdsMakeRoomFor(c->querybuf, readlen); + nread = read(fd, c->querybuf+qblen, readlen); + if (nread == -1) { + if (errno == EAGAIN) { + nread = 0; + } else { + redisLog(REDIS_VERBOSE, "Reading from client: %s",strerror(errno)); + freeClient(c); + return; + } + } else if (nread == 0) { + redisLog(REDIS_VERBOSE, "Client closed connection"); + freeClient(c); + return; + } + if (nread) { + sdsIncrLen(c->querybuf,nread); + c->lastinteraction = server.unixtime; + } else { + server.current_client = NULL; + return; + } + if (sdslen(c->querybuf) > server.client_max_querybuf_len) { + 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); + sdsfree(ci); + sdsfree(bytes); + freeClient(c); + return; + } + processInputBuffer(c); + server.current_client = NULL; +} + +void getClientsMaxBuffers(unsigned long *longest_output_list, + unsigned long *biggest_input_buffer) { + redisClient *c; + listNode *ln; + listIter li; + unsigned long lol = 0, bib = 0; + + listRewind(server.clients,&li); + while ((ln = listNext(&li)) != NULL) { + c = listNodeValue(ln); + + if (listLength(c->reply) > lol) lol = listLength(c->reply); + if (sdslen(c->querybuf) > bib) bib = sdslen(c->querybuf); + } + *longest_output_list = lol; + *biggest_input_buffer = bib; +} + +/* Turn a Redis client into an sds string representing its state. */ +sds getClientInfoString(redisClient *client) { + char ip[32], flags[16], events[3], *p; + int port = 0; /* initialized to zero for the unix socket case. */ + int emask; + + if (!(client->flags & REDIS_UNIX_SOCKET)) + anetPeerToString(client->fd,ip,&port); + p = flags; + if (client->flags & REDIS_SLAVE) { + if (client->flags & REDIS_MONITOR) + *p++ = 'O'; + else + *p++ = 'S'; + } + if (client->flags & REDIS_MASTER) *p++ = 'M'; + if (client->flags & REDIS_MULTI) *p++ = 'x'; + if (client->flags & REDIS_BLOCKED) *p++ = 'b'; + if (client->flags & REDIS_DIRTY_CAS) *p++ = 'd'; + if (client->flags & REDIS_CLOSE_AFTER_REPLY) *p++ = 'c'; + if (client->flags & REDIS_UNBLOCKED) *p++ = 'u'; + if (client->flags & REDIS_CLOSE_ASAP) *p++ = 'A'; + if (client->flags & REDIS_UNIX_SOCKET) *p++ = 'U'; + if (p == flags) *p++ = 'N'; + *p++ = '\0'; + + emask = client->fd == -1 ? 0 : aeGetFileEvents(server.el,client->fd); + p = events; + if (emask & AE_READABLE) *p++ = 'r'; + if (emask & AE_WRITABLE) *p++ = 'w'; + *p = '\0'; + return sdscatprintf(sdsempty(), + "addr=%s:%d 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", + (client->flags & REDIS_UNIX_SOCKET) ? server.unixsocket : ip, + port, + client->fd, + client->name ? (char*)client->name->ptr : "", + (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) 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"); +} + +sds getAllClientsInfoString(void) { + listNode *ln; + listIter li; + redisClient *client; + sds o = sdsempty(); + + listRewind(server.clients,&li); + while ((ln = listNext(&li)) != NULL) { + sds cs; + + client = listNodeValue(ln); + cs = getClientInfoString(client); + o = sdscatsds(o,cs); + sdsfree(cs); + o = sdscatlen(o,"\n",1); + } + return o; +} + +void clientCommand(redisClient *c) { + listNode *ln; + listIter li; + redisClient *client; + + if (!strcasecmp(c->argv[1]->ptr,"list") && c->argc == 2) { + sds o = getAllClientsInfoString(); + addReplyBulkCBuffer(c,o,sdslen(o)); + sdsfree(o); + } else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) { + listRewind(server.clients,&li); + while ((ln = listNext(&li)) != NULL) { + char ip[32], addr[64]; + int port; + + client = listNodeValue(ln); + if (anetPeerToString(client->fd,ip,&port) == -1) continue; + snprintf(addr,sizeof(addr),"%s:%d",ip,port); + if (strcmp(addr,c->argv[2]->ptr) == 0) { + addReply(c,shared.ok); + if (c == client) { + client->flags |= REDIS_CLOSE_AFTER_REPLY; + } else { + freeClient(client); + } + return; + } + } + addReplyError(c,"No such client"); + } else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) { + int j, len = sdslen(c->argv[2]->ptr); + char *p = c->argv[2]->ptr; + + /* Setting the client name to an empty string actually removes + * the current name. */ + if (len == 0) { + if (c->name) decrRefCount(c->name); + c->name = NULL; + addReply(c,shared.ok); + return; + } + + /* Otherwise check if the charset is ok. We need to do this otherwise + * CLIENT LIST format will break. You should always be able to + * split by space to get the different fields. */ + for (j = 0; j < len; j++) { + if (p[j] < '!' || p[j] > '~') { /* ASCII is assumed. */ + addReplyError(c, + "Client names cannot contain spaces, " + "newlines or special characters."); + return; + } + } + if (c->name) decrRefCount(c->name); + c->name = c->argv[2]; + incrRefCount(c->name); + addReply(c,shared.ok); + } else if (!strcasecmp(c->argv[1]->ptr,"getname") && c->argc == 2) { + if (c->name) + addReplyBulk(c,c->name); + else + addReply(c,shared.nullbulk); + } else { + addReplyError(c, "Syntax error, try CLIENT (LIST | KILL ip:port)"); + } +} + +/* Rewrite the command vector of the client. All the new objects ref count + * is incremented. The old command vector is freed, and the old objects + * ref count is decremented. */ +void rewriteClientCommandVector(redisClient *c, int argc, ...) { + va_list ap; + int j; + robj **argv; /* The new argument vector */ + + argv = zmalloc(sizeof(robj*)*argc); + va_start(ap,argc); + for (j = 0; j < argc; j++) { + robj *a; + + a = va_arg(ap, robj*); + argv[j] = a; + incrRefCount(a); + } + /* We free the objects in the original vector at the end, so we are + * sure that if the same objects are reused in the new vector the + * refcount gets incremented before it gets decremented. */ + for (j = 0; j < c->argc; j++) decrRefCount(c->argv[j]); + zfree(c->argv); + /* Replace argv and argc with our new versions. */ + c->argv = argv; + c->argc = argc; + c->cmd = lookupCommand(c->argv[0]->ptr); + redisAssertWithInfo(c,NULL,c->cmd != NULL); + va_end(ap); +} + +/* Rewrite a single item in the command vector. + * The new val ref count is incremented, and the old decremented. */ +void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) { + robj *oldval; + + redisAssertWithInfo(c,NULL,i < c->argc); + oldval = c->argv[i]; + c->argv[i] = newval; + incrRefCount(newval); + decrRefCount(oldval); + + /* If this is the command name make sure to fix c->cmd. */ + if (i == 0) { + c->cmd = lookupCommand(c->argv[0]->ptr); + redisAssertWithInfo(c,NULL,c->cmd != NULL); + } +} + +/* This function returns the number of bytes that Redis is virtually + * using to store the reply still not read by the client. + * It is "virtual" since the reply output list may contain objects that + * are shared and are not really using additional memory. + * + * The function returns the total sum of the length of all the objects + * stored in the output list, plus the memory used to allocate every + * list node. The static reply buffer is not taken into account since it + * is allocated anyway. + * + * Note: this function is very fast so can be called as many time as + * the caller wishes. The main usage of this function currently is + * enforcing the client output length limits. */ +unsigned long getClientOutputBufferMemoryUsage(redisClient *c) { + unsigned long list_item_size = sizeof(listNode)+sizeof(robj); + + return c->reply_bytes + (list_item_size*listLength(c->reply)); +} + +/* Get the class of a client, used in order to enforce limits to different + * classes of clients. + * + * The function will return one of the following: + * REDIS_CLIENT_LIMIT_CLASS_NORMAL -> Normal client + * REDIS_CLIENT_LIMIT_CLASS_SLAVE -> Slave or client executing MONITOR command + * REDIS_CLIENT_LIMIT_CLASS_PUBSUB -> Client subscribed to Pub/Sub channels + */ +int getClientLimitClass(redisClient *c) { + if (c->flags & REDIS_SLAVE) return REDIS_CLIENT_LIMIT_CLASS_SLAVE; + if (dictSize(c->pubsub_channels) || listLength(c->pubsub_patterns)) + return REDIS_CLIENT_LIMIT_CLASS_PUBSUB; + return REDIS_CLIENT_LIMIT_CLASS_NORMAL; +} + +int getClientLimitClassByName(char *name) { + if (!strcasecmp(name,"normal")) return REDIS_CLIENT_LIMIT_CLASS_NORMAL; + else if (!strcasecmp(name,"slave")) return REDIS_CLIENT_LIMIT_CLASS_SLAVE; + else if (!strcasecmp(name,"pubsub")) return REDIS_CLIENT_LIMIT_CLASS_PUBSUB; + else return -1; +} + +char *getClientLimitClassName(int class) { + switch(class) { + case REDIS_CLIENT_LIMIT_CLASS_NORMAL: return "normal"; + case REDIS_CLIENT_LIMIT_CLASS_SLAVE: return "slave"; + case REDIS_CLIENT_LIMIT_CLASS_PUBSUB: return "pubsub"; + default: return NULL; + } +} + +/* The function checks if the client reached output buffer soft or hard + * limit, and also update the state needed to check the soft limit as + * a side effect. + * + * Return value: non-zero if the client reached the soft or the hard limit. + * Otherwise zero is returned. */ +int checkClientOutputBufferLimits(redisClient *c) { + int soft = 0, hard = 0, class; + unsigned long used_mem = getClientOutputBufferMemoryUsage(c); + + class = getClientLimitClass(c); + if (server.client_obuf_limits[class].hard_limit_bytes && + used_mem >= server.client_obuf_limits[class].hard_limit_bytes) + hard = 1; + if (server.client_obuf_limits[class].soft_limit_bytes && + used_mem >= server.client_obuf_limits[class].soft_limit_bytes) + soft = 1; + + /* We need to check if the soft limit is reached continuously for the + * specified amount of seconds. */ + if (soft) { + if (c->obuf_soft_limit_reached_time == 0) { + c->obuf_soft_limit_reached_time = server.unixtime; + soft = 0; /* First time we see the soft limit reached */ + } else { + time_t elapsed = server.unixtime - c->obuf_soft_limit_reached_time; + + if (elapsed <= + server.client_obuf_limits[class].soft_limit_seconds) { + soft = 0; /* The client still did not reached the max number of + seconds for the soft limit to be considered + reached. */ + } + } + } else { + c->obuf_soft_limit_reached_time = 0; + } + return soft || hard; +} + +/* Asynchronously close a client if soft or hard limit is reached on the + * output buffer size. The caller can check if the client will be closed + * checking if the client REDIS_CLOSE_ASAP flag is set. + * + * Note: we need to close the client asynchronously because this function is + * called from contexts where the client can't be freed safely, i.e. from the + * lower level functions pushing data inside the client output buffers. */ +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 = getClientInfoString(c); + + freeClientAsync(c); + redisLog(REDIS_WARNING,"Client %s scheduled to be closed ASAP for overcoming of output buffer limits.", client); + sdsfree(client); + } +} + +/* Helper function used by freeMemoryIfNeeded() in order to flush slaves + * output buffers without returning control to the event loop. */ +void flushSlavesOutputBuffers(void) { + listIter li; + listNode *ln; + + listRewind(server.slaves,&li); + while((ln = listNext(&li))) { + redisClient *slave = listNodeValue(ln); + int events; + + events = aeGetFileEvents(server.el,slave->fd); + if (events & AE_WRITABLE && + slave->replstate == REDIS_REPL_ONLINE && + listLength(slave->reply)) + { + sendReplyToClient(server.el,slave->fd,slave,0); + } + } +} diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c index 8d72573..d46b69a 100644 --- a/src/redis-benchmark.c +++ b/src/redis-benchmark.c @@ -1,681 +1,681 @@ -/* Redis benchmark utility. - * - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ae.h" -#include "hiredis.h" -#include "sds.h" -#include "adlist.h" -#include "zmalloc.h" - -#define REDIS_NOTUSED(V) ((void) V) - -static struct config { - aeEventLoop *el; - const char *hostip; - int hostport; - const char *hostsocket; - int numclients; - int liveclients; - int requests; - int requests_issued; - int requests_finished; - int keysize; - int datasize; - int randomkeys; - int randomkeys_keyspacelen; - int keepalive; - int pipeline; - long long start; - long long totlatency; - long long *latency; - const char *title; - list *clients; - int quiet; - int csv; - int loop; - int idlemode; - char *tests; -} config; - -typedef struct _client { - redisContext *context; - sds obuf; - char *randptr[32]; /* needed for MSET against 10 keys */ - size_t randlen; - unsigned int written; /* bytes of 'obuf' already written */ - long long start; /* start time of a request */ - long long latency; /* request latency */ - int pending; /* Number of pending requests (sent but no reply received) */ -} *client; - -/* Prototypes */ -static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask); -static void createMissingClients(client c); - -/* Implementation */ -static long long ustime(void) { - struct timeval tv; - long long ust; - - gettimeofday(&tv, NULL); - ust = ((long)tv.tv_sec)*1000000; - ust += tv.tv_usec; - return ust; -} - -static long long mstime(void) { - struct timeval tv; - long long mst; - - gettimeofday(&tv, NULL); - mst = ((long)tv.tv_sec)*1000; - mst += tv.tv_usec/1000; - return mst; -} - -static void freeClient(client c) { - listNode *ln; - aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); - aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE); - redisFree(c->context); - sdsfree(c->obuf); - zfree(c); - config.liveclients--; - ln = listSearchKey(config.clients,c); - assert(ln != NULL); - listDelNode(config.clients,ln); -} - -static void freeAllClients(void) { - listNode *ln = config.clients->head, *next; - - while(ln) { - next = ln->next; - freeClient(ln->value); - ln = next; - } -} - -static void resetClient(client c) { - aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); - aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE); - aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c); - c->written = 0; - c->pending = config.pipeline; -} - -static void randomizeClientKey(client c) { - char buf[32]; - size_t i, r; - - for (i = 0; i < c->randlen; i++) { - r = random() % config.randomkeys_keyspacelen; - snprintf(buf,sizeof(buf),"%012zu",r); - memcpy(c->randptr[i],buf,12); - } -} - -static void clientDone(client c) { - if (config.requests_finished == config.requests) { - freeClient(c); - aeStop(config.el); - return; - } - if (config.keepalive) { - resetClient(c); - } else { - config.liveclients--; - createMissingClients(c); - config.liveclients++; - freeClient(c); - } -} - -static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { - client c = privdata; - void *reply = NULL; - REDIS_NOTUSED(el); - REDIS_NOTUSED(fd); - REDIS_NOTUSED(mask); - - /* Calculate latency only for the first read event. This means that the - * server already sent the reply and we need to parse it. Parsing overhead - * is not part of the latency, so calculate it only once, here. */ - if (c->latency < 0) c->latency = ustime()-(c->start); - - if (redisBufferRead(c->context) != REDIS_OK) { - fprintf(stderr,"Error: %s\n",c->context->errstr); - exit(1); - } else { - while(c->pending) { - if (redisGetReply(c->context,&reply) != REDIS_OK) { - fprintf(stderr,"Error: %s\n",c->context->errstr); - exit(1); - } - if (reply != NULL) { - if (reply == (void*)REDIS_REPLY_ERROR) { - fprintf(stderr,"Unexpected error reply, exiting...\n"); - exit(1); - } - - freeReplyObject(reply); - - if (config.requests_finished < config.requests) - config.latency[config.requests_finished++] = c->latency; - c->pending--; - if (c->pending == 0) { - clientDone(c); - break; - } - } else { - break; - } - } - } -} - -static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) { - client c = privdata; - REDIS_NOTUSED(el); - REDIS_NOTUSED(fd); - REDIS_NOTUSED(mask); - - /* Initialize request when nothing was written. */ - if (c->written == 0) { - /* Enforce upper bound to number of requests. */ - if (config.requests_issued++ >= config.requests) { - freeClient(c); - return; - } - - /* Really initialize: randomize keys and set start time. */ - if (config.randomkeys) randomizeClientKey(c); - c->start = ustime(); - c->latency = -1; - } - - if (sdslen(c->obuf) > c->written) { - void *ptr = c->obuf+c->written; - int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written); - if (nwritten == -1) { - if (errno != EPIPE) - fprintf(stderr, "Writing to socket: %s\n", strerror(errno)); - freeClient(c); - return; - } - c->written += nwritten; - if (sdslen(c->obuf) == c->written) { - aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); - aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c); - } - } -} - -static client createClient(char *cmd, size_t len) { - int j; - client c = zmalloc(sizeof(struct _client)); - - if (config.hostsocket == NULL) { - c->context = redisConnectNonBlock(config.hostip,config.hostport); - } else { - c->context = redisConnectUnixNonBlock(config.hostsocket); - } - if (c->context->err) { - fprintf(stderr,"Could not connect to Redis at "); - if (config.hostsocket == NULL) - fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,c->context->errstr); - else - fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr); - exit(1); - } - /* Suppress hiredis cleanup of unused buffers for max speed. */ - c->context->reader->maxbuf = 0; - /* Queue N requests accordingly to the pipeline size. */ - c->obuf = sdsempty(); - for (j = 0; j < config.pipeline; j++) - c->obuf = sdscatlen(c->obuf,cmd,len); - c->randlen = 0; - c->written = 0; - c->pending = config.pipeline; - - /* Find substrings in the output buffer that need to be randomized. */ - if (config.randomkeys) { - char *p = c->obuf; - while ((p = strstr(p,":rand:")) != NULL) { - assert(c->randlen < (signed)(sizeof(c->randptr)/sizeof(char*))); - c->randptr[c->randlen++] = p+6; - p += 6; - } - } - -/* redisSetReplyObjectFunctions(c->context,NULL); */ - aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c); - listAddNodeTail(config.clients,c); - config.liveclients++; - return c; -} - -static void createMissingClients(client c) { - int n = 0; - - while(config.liveclients < config.numclients) { - createClient(c->obuf,sdslen(c->obuf)/config.pipeline); - - /* Listen backlog is quite limited on most systems */ - if (++n > 64) { - usleep(50000); - n = 0; - } - } -} - -static int compareLatency(const void *a, const void *b) { - return (*(long long*)a)-(*(long long*)b); -} - -static void showLatencyReport(void) { - int i, curlat = 0; - float perc, reqpersec; - - reqpersec = (float)config.requests_finished/((float)config.totlatency/1000); - if (!config.quiet && !config.csv) { - printf("====== %s ======\n", config.title); - printf(" %d requests completed in %.2f seconds\n", config.requests_finished, - (float)config.totlatency/1000); - printf(" %d parallel clients\n", config.numclients); - printf(" %d bytes payload\n", config.datasize); - printf(" keep alive: %d\n", config.keepalive); - printf("\n"); - - qsort(config.latency,config.requests,sizeof(long long),compareLatency); - for (i = 0; i < config.requests; i++) { - if (config.latency[i]/1000 != curlat || i == (config.requests-1)) { - curlat = config.latency[i]/1000; - perc = ((float)(i+1)*100)/config.requests; - printf("%.2f%% <= %d milliseconds\n", perc, curlat); - } - } - printf("%.2f requests per second\n\n", reqpersec); - } else if (config.csv) { - printf("\"%s\",\"%.2f\"\n", config.title, reqpersec); - } else { - printf("%s: %.2f requests per second\n", config.title, reqpersec); - } -} - -static void benchmark(char *title, char *cmd, int len) { - client c; - - config.title = title; - config.requests_issued = 0; - config.requests_finished = 0; - - c = createClient(cmd,len); - createMissingClients(c); - - config.start = mstime(); - aeMain(config.el); - config.totlatency = mstime()-config.start; - - showLatencyReport(); - freeAllClients(); -} - -/* Returns number of consumed options. */ -int parseOptions(int argc, const char **argv) { - int i; - int lastarg; - int exit_status = 1; - - for (i = 1; i < argc; i++) { - lastarg = (i == (argc-1)); - - if (!strcmp(argv[i],"-c")) { - if (lastarg) goto invalid; - config.numclients = atoi(argv[++i]); - } else if (!strcmp(argv[i],"-n")) { - if (lastarg) goto invalid; - config.requests = atoi(argv[++i]); - } else if (!strcmp(argv[i],"-k")) { - if (lastarg) goto invalid; - config.keepalive = atoi(argv[++i]); - } else if (!strcmp(argv[i],"-h")) { - if (lastarg) goto invalid; - config.hostip = strdup(argv[++i]); - } else if (!strcmp(argv[i],"-p")) { - if (lastarg) goto invalid; - config.hostport = atoi(argv[++i]); - } else if (!strcmp(argv[i],"-s")) { - if (lastarg) goto invalid; - config.hostsocket = strdup(argv[++i]); - } else if (!strcmp(argv[i],"-d")) { - if (lastarg) goto invalid; - config.datasize = atoi(argv[++i]); - if (config.datasize < 1) config.datasize=1; - if (config.datasize > 1024*1024*1024) config.datasize = 1024*1024*1024; - } else if (!strcmp(argv[i],"-P")) { - if (lastarg) goto invalid; - config.pipeline = atoi(argv[++i]); - if (config.pipeline <= 0) config.pipeline=1; - } else if (!strcmp(argv[i],"-r")) { - if (lastarg) goto invalid; - config.randomkeys = 1; - config.randomkeys_keyspacelen = atoi(argv[++i]); - if (config.randomkeys_keyspacelen < 0) - config.randomkeys_keyspacelen = 0; - } else if (!strcmp(argv[i],"-q")) { - config.quiet = 1; - } else if (!strcmp(argv[i],"--csv")) { - config.csv = 1; - } else if (!strcmp(argv[i],"-l")) { - config.loop = 1; - } else if (!strcmp(argv[i],"-I")) { - config.idlemode = 1; - } else if (!strcmp(argv[i],"-t")) { - if (lastarg) goto invalid; - /* We get the list of tests to run as a string in the form - * get,set,lrange,...,test_N. Then we add a comma before and - * after the string in order to make sure that searching - * for ",testname," will always get a match if the test is - * enabled. */ - config.tests = sdsnew(","); - config.tests = sdscat(config.tests,(char*)argv[++i]); - config.tests = sdscat(config.tests,","); - sdstolower(config.tests); - } else if (!strcmp(argv[i],"--help")) { - exit_status = 0; - goto usage; - } else { - /* Assume the user meant to provide an option when the arg starts - * with a dash. We're done otherwise and should use the remainder - * as the command and arguments for running the benchmark. */ - if (argv[i][0] == '-') goto invalid; - return i; - } - } - - return i; - -invalid: - printf("Invalid option \"%s\" or option argument missing\n\n",argv[i]); - -usage: - printf( -"Usage: redis-benchmark [-h ] [-p ] [-c ] [-n [-k ]\n\n" -" -h Server hostname (default 127.0.0.1)\n" -" -p Server port (default 6379)\n" -" -s Server socket (overrides host and port)\n" -" -c Number of parallel connections (default 50)\n" -" -n Total number of requests (default 10000)\n" -" -d Data size of SET/GET value in bytes (default 2)\n" -" -k 1=keep alive 0=reconnect (default 1)\n" -" -r Use random keys for SET/GET/INCR, random values for SADD\n" -" Using this option the benchmark will get/set keys\n" -" in the form mykey_rand:000000012456 instead of constant\n" -" keys, the argument determines the max\n" -" number of values for the random number. For instance\n" -" if set to 10 only rand:000000000000 - rand:000000000009\n" -" range will be allowed.\n" -" -P Pipeline requests. Default 1 (no pipeline).\n" -" -q Quiet. Just show query/sec values\n" -" --csv Output in CSV format\n" -" -l Loop. Run the tests forever\n" -" -t Only run the comma separated list of tests. The test\n" -" names are the same as the ones produced as output.\n" -" -I Idle mode. Just open N idle connections and wait.\n\n" -"Examples:\n\n" -" Run the benchmark with the default configuration against 127.0.0.1:6379:\n" -" $ redis-benchmark\n\n" -" Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:\n" -" $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20\n\n" -" Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:\n" -" $ redis-benchmark -t set -n 1000000 -r 100000000\n\n" -" Benchmark 127.0.0.1:6379 for a few commands producing CSV output:\n" -" $ redis-benchmark -t ping,set,get -n 100000 --csv\n\n" -" Fill a list with 10000 random elements:\n" -" $ redis-benchmark -r 10000 -n 10000 lpush mylist ele:rand:000000000000\n\n" - ); - exit(exit_status); -} - -int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) { - REDIS_NOTUSED(eventLoop); - REDIS_NOTUSED(id); - REDIS_NOTUSED(clientData); - - if (config.csv) return 250; - float dt = (float)(mstime()-config.start)/1000.0; - float rps = (float)config.requests_finished/dt; - printf("%s: %.2f\r", config.title, rps); - fflush(stdout); - return 250; /* every 250ms */ -} - -/* Return true if the named test was selected using the -t command line - * switch, or if all the tests are selected (no -t passed by user). */ -int test_is_selected(char *name) { - char buf[256]; - int l = strlen(name); - - if (config.tests == NULL) return 1; - buf[0] = ','; - memcpy(buf+1,name,l); - buf[l+1] = ','; - buf[l+2] = '\0'; - return strstr(config.tests,buf) != NULL; -} - -int main(int argc, const char **argv) { - int i; - char *data, *cmd; - int len; - - client c; - - signal(SIGHUP, SIG_IGN); - signal(SIGPIPE, SIG_IGN); - - config.numclients = 50; - config.requests = 10000; - config.liveclients = 0; - config.el = aeCreateEventLoop(1024*10); - aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL); - config.keepalive = 1; - config.datasize = 3; - config.pipeline = 1; - config.randomkeys = 0; - config.randomkeys_keyspacelen = 0; - config.quiet = 0; - config.csv = 0; - config.loop = 0; - config.idlemode = 0; - config.latency = NULL; - config.clients = listCreate(); - config.hostip = "127.0.0.1"; - config.hostport = 6379; - config.hostsocket = NULL; - config.tests = NULL; - - i = parseOptions(argc,argv); - argc -= i; - argv += i; - - config.latency = zmalloc(sizeof(long long)*config.requests); - - if (config.keepalive == 0) { - printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' for Linux and 'sudo sysctl -w net.inet.tcp.msl=1000' for Mac OS X in order to use a lot of clients/requests\n"); - } - - if (config.idlemode) { - printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients); - c = createClient("",0); /* will never receive a reply */ - createMissingClients(c); - aeMain(config.el); - /* and will wait for every */ - } - - /* Run benchmark with command in the remainder of the arguments. */ - if (argc) { - sds title = sdsnew(argv[0]); - for (i = 1; i < argc; i++) { - title = sdscatlen(title, " ", 1); - title = sdscatlen(title, (char*)argv[i], strlen(argv[i])); - } - - do { - len = redisFormatCommandArgv(&cmd,argc,argv,NULL); - benchmark(title,cmd,len); - free(cmd); - } while(config.loop); - - return 0; - } - - /* Run default benchmark suite. */ - do { - data = zmalloc(config.datasize+1); - memset(data,'x',config.datasize); - data[config.datasize] = '\0'; - - if (test_is_selected("ping_inline") || test_is_selected("ping")) - benchmark("PING_INLINE","PING\r\n",6); - - if (test_is_selected("ping_mbulk") || test_is_selected("ping")) { - len = redisFormatCommand(&cmd,"PING"); - benchmark("PING_BULK",cmd,len); - free(cmd); - } - - if (test_is_selected("set")) { - len = redisFormatCommand(&cmd,"SET foo:rand:000000000000 %s",data); - benchmark("SET",cmd,len); - free(cmd); - } - - if (test_is_selected("get")) { - len = redisFormatCommand(&cmd,"GET foo:rand:000000000000"); - benchmark("GET",cmd,len); - free(cmd); - } - - if (test_is_selected("incr")) { - len = redisFormatCommand(&cmd,"INCR counter:rand:000000000000"); - benchmark("INCR",cmd,len); - free(cmd); - } - - if (test_is_selected("lpush")) { - len = redisFormatCommand(&cmd,"LPUSH mylist %s",data); - benchmark("LPUSH",cmd,len); - free(cmd); - } - - if (test_is_selected("lpop")) { - len = redisFormatCommand(&cmd,"LPOP mylist"); - benchmark("LPOP",cmd,len); - free(cmd); - } - - if (test_is_selected("sadd")) { - len = redisFormatCommand(&cmd, - "SADD myset counter:rand:000000000000"); - benchmark("SADD",cmd,len); - free(cmd); - } - - if (test_is_selected("spop")) { - len = redisFormatCommand(&cmd,"SPOP myset"); - benchmark("SPOP",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || - test_is_selected("lrange_100") || - test_is_selected("lrange_300") || - test_is_selected("lrange_500") || - test_is_selected("lrange_600")) - { - len = redisFormatCommand(&cmd,"LPUSH mylist %s",data); - benchmark("LPUSH (needed to benchmark LRANGE)",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || test_is_selected("lrange_100")) { - len = redisFormatCommand(&cmd,"LRANGE mylist 0 99"); - benchmark("LRANGE_100 (first 100 elements)",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || test_is_selected("lrange_300")) { - len = redisFormatCommand(&cmd,"LRANGE mylist 0 299"); - benchmark("LRANGE_300 (first 300 elements)",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || test_is_selected("lrange_500")) { - len = redisFormatCommand(&cmd,"LRANGE mylist 0 449"); - benchmark("LRANGE_500 (first 450 elements)",cmd,len); - free(cmd); - } - - if (test_is_selected("lrange") || test_is_selected("lrange_600")) { - len = redisFormatCommand(&cmd,"LRANGE mylist 0 599"); - benchmark("LRANGE_600 (first 600 elements)",cmd,len); - free(cmd); - } - - if (test_is_selected("mset")) { - const char *argv[21]; - argv[0] = "MSET"; - for (i = 1; i < 21; i += 2) { - argv[i] = "foo:rand:000000000000"; - argv[i+1] = data; - } - len = redisFormatCommandArgv(&cmd,21,argv,NULL); - benchmark("MSET (10 keys)",cmd,len); - free(cmd); - } - - if (!config.csv) printf("\n"); - } while(config.loop); - - return 0; -} +/* Redis benchmark utility. + * + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ae.h" +#include "hiredis.h" +#include "sds.h" +#include "adlist.h" +#include "zmalloc.h" + +#define REDIS_NOTUSED(V) ((void) V) + +static struct config { + aeEventLoop *el; + const char *hostip; + int hostport; + const char *hostsocket; + int numclients; + int liveclients; + int requests; + int requests_issued; + int requests_finished; + int keysize; + int datasize; + int randomkeys; + int randomkeys_keyspacelen; + int keepalive; + int pipeline; + long long start; + long long totlatency; + long long *latency; + const char *title; + list *clients; + int quiet; + int csv; + int loop; + int idlemode; + char *tests; +} config; + +typedef struct _client { + redisContext *context; + sds obuf; + char *randptr[32]; /* needed for MSET against 10 keys */ + size_t randlen; + unsigned int written; /* bytes of 'obuf' already written */ + long long start; /* start time of a request */ + long long latency; /* request latency */ + int pending; /* Number of pending requests (sent but no reply received) */ +} *client; + +/* Prototypes */ +static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask); +static void createMissingClients(client c); + +/* Implementation */ +static long long ustime(void) { + struct timeval tv; + long long ust; + + gettimeofday(&tv, NULL); + ust = ((long)tv.tv_sec)*1000000; + ust += tv.tv_usec; + return ust; +} + +static long long mstime(void) { + struct timeval tv; + long long mst; + + gettimeofday(&tv, NULL); + mst = ((long long)tv.tv_sec)*1000; + mst += tv.tv_usec/1000; + return mst; +} + +static void freeClient(client c) { + listNode *ln; + aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); + aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE); + redisFree(c->context); + sdsfree(c->obuf); + zfree(c); + config.liveclients--; + ln = listSearchKey(config.clients,c); + assert(ln != NULL); + listDelNode(config.clients,ln); +} + +static void freeAllClients(void) { + listNode *ln = config.clients->head, *next; + + while(ln) { + next = ln->next; + freeClient(ln->value); + ln = next; + } +} + +static void resetClient(client c) { + aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); + aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE); + aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c); + c->written = 0; + c->pending = config.pipeline; +} + +static void randomizeClientKey(client c) { + char buf[32]; + size_t i, r; + + for (i = 0; i < c->randlen; i++) { + r = random() % config.randomkeys_keyspacelen; + snprintf(buf,sizeof(buf),"%012zu",r); + memcpy(c->randptr[i],buf,12); + } +} + +static void clientDone(client c) { + if (config.requests_finished == config.requests) { + freeClient(c); + aeStop(config.el); + return; + } + if (config.keepalive) { + resetClient(c); + } else { + config.liveclients--; + createMissingClients(c); + config.liveclients++; + freeClient(c); + } +} + +static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { + client c = privdata; + void *reply = NULL; + REDIS_NOTUSED(el); + REDIS_NOTUSED(fd); + REDIS_NOTUSED(mask); + + /* Calculate latency only for the first read event. This means that the + * server already sent the reply and we need to parse it. Parsing overhead + * is not part of the latency, so calculate it only once, here. */ + if (c->latency < 0) c->latency = ustime()-(c->start); + + if (redisBufferRead(c->context) != REDIS_OK) { + fprintf(stderr,"Error: %s\n",c->context->errstr); + exit(1); + } else { + while(c->pending) { + if (redisGetReply(c->context,&reply) != REDIS_OK) { + fprintf(stderr,"Error: %s\n",c->context->errstr); + exit(1); + } + if (reply != NULL) { + if (reply == (void*)REDIS_REPLY_ERROR) { + fprintf(stderr,"Unexpected error reply, exiting...\n"); + exit(1); + } + + freeReplyObject(reply); + + if (config.requests_finished < config.requests) + config.latency[config.requests_finished++] = c->latency; + c->pending--; + if (c->pending == 0) { + clientDone(c); + break; + } + } else { + break; + } + } + } +} + +static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) { + client c = privdata; + REDIS_NOTUSED(el); + REDIS_NOTUSED(fd); + REDIS_NOTUSED(mask); + + /* Initialize request when nothing was written. */ + if (c->written == 0) { + /* Enforce upper bound to number of requests. */ + if (config.requests_issued++ >= config.requests) { + freeClient(c); + return; + } + + /* Really initialize: randomize keys and set start time. */ + if (config.randomkeys) randomizeClientKey(c); + c->start = ustime(); + c->latency = -1; + } + + if (sdslen(c->obuf) > c->written) { + void *ptr = c->obuf+c->written; + int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written); + if (nwritten == -1) { + if (errno != EPIPE) + fprintf(stderr, "Writing to socket: %s\n", strerror(errno)); + freeClient(c); + return; + } + c->written += nwritten; + if (sdslen(c->obuf) == c->written) { + aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); + aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c); + } + } +} + +static client createClient(char *cmd, size_t len) { + int j; + client c = zmalloc(sizeof(struct _client)); + + if (config.hostsocket == NULL) { + c->context = redisConnectNonBlock(config.hostip,config.hostport); + } else { + c->context = redisConnectUnixNonBlock(config.hostsocket); + } + if (c->context->err) { + fprintf(stderr,"Could not connect to Redis at "); + if (config.hostsocket == NULL) + fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,c->context->errstr); + else + fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr); + exit(1); + } + /* Suppress hiredis cleanup of unused buffers for max speed. */ + c->context->reader->maxbuf = 0; + /* Queue N requests accordingly to the pipeline size. */ + c->obuf = sdsempty(); + for (j = 0; j < config.pipeline; j++) + c->obuf = sdscatlen(c->obuf,cmd,len); + c->randlen = 0; + c->written = 0; + c->pending = config.pipeline; + + /* Find substrings in the output buffer that need to be randomized. */ + if (config.randomkeys) { + char *p = c->obuf; + while ((p = strstr(p,":rand:")) != NULL) { + assert(c->randlen < (signed)(sizeof(c->randptr)/sizeof(char*))); + c->randptr[c->randlen++] = p+6; + p += 6; + } + } + +/* redisSetReplyObjectFunctions(c->context,NULL); */ + aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c); + listAddNodeTail(config.clients,c); + config.liveclients++; + return c; +} + +static void createMissingClients(client c) { + int n = 0; + + while(config.liveclients < config.numclients) { + createClient(c->obuf,sdslen(c->obuf)/config.pipeline); + + /* Listen backlog is quite limited on most systems */ + if (++n > 64) { + usleep(50000); + n = 0; + } + } +} + +static int compareLatency(const void *a, const void *b) { + return (*(long long*)a)-(*(long long*)b); +} + +static void showLatencyReport(void) { + int i, curlat = 0; + float perc, reqpersec; + + reqpersec = (float)config.requests_finished/((float)config.totlatency/1000); + if (!config.quiet && !config.csv) { + printf("====== %s ======\n", config.title); + printf(" %d requests completed in %.2f seconds\n", config.requests_finished, + (float)config.totlatency/1000); + printf(" %d parallel clients\n", config.numclients); + printf(" %d bytes payload\n", config.datasize); + printf(" keep alive: %d\n", config.keepalive); + printf("\n"); + + qsort(config.latency,config.requests,sizeof(long long),compareLatency); + for (i = 0; i < config.requests; i++) { + if (config.latency[i]/1000 != curlat || i == (config.requests-1)) { + curlat = config.latency[i]/1000; + perc = ((float)(i+1)*100)/config.requests; + printf("%.2f%% <= %d milliseconds\n", perc, curlat); + } + } + printf("%.2f requests per second\n\n", reqpersec); + } else if (config.csv) { + printf("\"%s\",\"%.2f\"\n", config.title, reqpersec); + } else { + printf("%s: %.2f requests per second\n", config.title, reqpersec); + } +} + +static void benchmark(char *title, char *cmd, int len) { + client c; + + config.title = title; + config.requests_issued = 0; + config.requests_finished = 0; + + c = createClient(cmd,len); + createMissingClients(c); + + config.start = mstime(); + aeMain(config.el); + config.totlatency = mstime()-config.start; + + showLatencyReport(); + freeAllClients(); +} + +/* Returns number of consumed options. */ +int parseOptions(int argc, const char **argv) { + int i; + int lastarg; + int exit_status = 1; + + for (i = 1; i < argc; i++) { + lastarg = (i == (argc-1)); + + if (!strcmp(argv[i],"-c")) { + if (lastarg) goto invalid; + config.numclients = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-n")) { + if (lastarg) goto invalid; + config.requests = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-k")) { + if (lastarg) goto invalid; + config.keepalive = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-h")) { + if (lastarg) goto invalid; + config.hostip = strdup(argv[++i]); + } else if (!strcmp(argv[i],"-p")) { + if (lastarg) goto invalid; + config.hostport = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-s")) { + if (lastarg) goto invalid; + config.hostsocket = strdup(argv[++i]); + } else if (!strcmp(argv[i],"-d")) { + if (lastarg) goto invalid; + config.datasize = atoi(argv[++i]); + if (config.datasize < 1) config.datasize=1; + if (config.datasize > 1024*1024*1024) config.datasize = 1024*1024*1024; + } else if (!strcmp(argv[i],"-P")) { + if (lastarg) goto invalid; + config.pipeline = atoi(argv[++i]); + if (config.pipeline <= 0) config.pipeline=1; + } else if (!strcmp(argv[i],"-r")) { + if (lastarg) goto invalid; + config.randomkeys = 1; + config.randomkeys_keyspacelen = atoi(argv[++i]); + if (config.randomkeys_keyspacelen < 0) + config.randomkeys_keyspacelen = 0; + } else if (!strcmp(argv[i],"-q")) { + config.quiet = 1; + } else if (!strcmp(argv[i],"--csv")) { + config.csv = 1; + } else if (!strcmp(argv[i],"-l")) { + config.loop = 1; + } else if (!strcmp(argv[i],"-I")) { + config.idlemode = 1; + } else if (!strcmp(argv[i],"-t")) { + if (lastarg) goto invalid; + /* We get the list of tests to run as a string in the form + * get,set,lrange,...,test_N. Then we add a comma before and + * after the string in order to make sure that searching + * for ",testname," will always get a match if the test is + * enabled. */ + config.tests = sdsnew(","); + config.tests = sdscat(config.tests,(char*)argv[++i]); + config.tests = sdscat(config.tests,","); + sdstolower(config.tests); + } else if (!strcmp(argv[i],"--help")) { + exit_status = 0; + goto usage; + } else { + /* Assume the user meant to provide an option when the arg starts + * with a dash. We're done otherwise and should use the remainder + * as the command and arguments for running the benchmark. */ + if (argv[i][0] == '-') goto invalid; + return i; + } + } + + return i; + +invalid: + printf("Invalid option \"%s\" or option argument missing\n\n",argv[i]); + +usage: + printf( +"Usage: redis-benchmark [-h ] [-p ] [-c ] [-n [-k ]\n\n" +" -h Server hostname (default 127.0.0.1)\n" +" -p Server port (default 6379)\n" +" -s Server socket (overrides host and port)\n" +" -c Number of parallel connections (default 50)\n" +" -n Total number of requests (default 10000)\n" +" -d Data size of SET/GET value in bytes (default 2)\n" +" -k 1=keep alive 0=reconnect (default 1)\n" +" -r Use random keys for SET/GET/INCR, random values for SADD\n" +" Using this option the benchmark will get/set keys\n" +" in the form mykey_rand:000000012456 instead of constant\n" +" keys, the argument determines the max\n" +" number of values for the random number. For instance\n" +" if set to 10 only rand:000000000000 - rand:000000000009\n" +" range will be allowed.\n" +" -P Pipeline requests. Default 1 (no pipeline).\n" +" -q Quiet. Just show query/sec values\n" +" --csv Output in CSV format\n" +" -l Loop. Run the tests forever\n" +" -t Only run the comma separated list of tests. The test\n" +" names are the same as the ones produced as output.\n" +" -I Idle mode. Just open N idle connections and wait.\n\n" +"Examples:\n\n" +" Run the benchmark with the default configuration against 127.0.0.1:6379:\n" +" $ redis-benchmark\n\n" +" Use 20 parallel clients, for a total of 100k requests, against 192.168.1.1:\n" +" $ redis-benchmark -h 192.168.1.1 -p 6379 -n 100000 -c 20\n\n" +" Fill 127.0.0.1:6379 with about 1 million keys only using the SET test:\n" +" $ redis-benchmark -t set -n 1000000 -r 100000000\n\n" +" Benchmark 127.0.0.1:6379 for a few commands producing CSV output:\n" +" $ redis-benchmark -t ping,set,get -n 100000 --csv\n\n" +" Fill a list with 10000 random elements:\n" +" $ redis-benchmark -r 10000 -n 10000 lpush mylist ele:rand:000000000000\n\n" + ); + exit(exit_status); +} + +int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) { + REDIS_NOTUSED(eventLoop); + REDIS_NOTUSED(id); + REDIS_NOTUSED(clientData); + + if (config.csv) return 250; + float dt = (float)(mstime()-config.start)/1000.0; + float rps = (float)config.requests_finished/dt; + printf("%s: %.2f\r", config.title, rps); + fflush(stdout); + return 250; /* every 250ms */ +} + +/* Return true if the named test was selected using the -t command line + * switch, or if all the tests are selected (no -t passed by user). */ +int test_is_selected(char *name) { + char buf[256]; + int l = strlen(name); + + if (config.tests == NULL) return 1; + buf[0] = ','; + memcpy(buf+1,name,l); + buf[l+1] = ','; + buf[l+2] = '\0'; + return strstr(config.tests,buf) != NULL; +} + +int main(int argc, const char **argv) { + int i; + char *data, *cmd; + int len; + + client c; + + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); + + config.numclients = 50; + config.requests = 10000; + config.liveclients = 0; + config.el = aeCreateEventLoop(1024*10); + aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL); + config.keepalive = 1; + config.datasize = 3; + config.pipeline = 1; + config.randomkeys = 0; + config.randomkeys_keyspacelen = 0; + config.quiet = 0; + config.csv = 0; + config.loop = 0; + config.idlemode = 0; + config.latency = NULL; + config.clients = listCreate(); + config.hostip = "127.0.0.1"; + config.hostport = 6379; + config.hostsocket = NULL; + config.tests = NULL; + + i = parseOptions(argc,argv); + argc -= i; + argv += i; + + config.latency = zmalloc(sizeof(long long)*config.requests); + + if (config.keepalive == 0) { + printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' for Linux and 'sudo sysctl -w net.inet.tcp.msl=1000' for Mac OS X in order to use a lot of clients/requests\n"); + } + + if (config.idlemode) { + printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients); + c = createClient("",0); /* will never receive a reply */ + createMissingClients(c); + aeMain(config.el); + /* and will wait for every */ + } + + /* Run benchmark with command in the remainder of the arguments. */ + if (argc) { + sds title = sdsnew(argv[0]); + for (i = 1; i < argc; i++) { + title = sdscatlen(title, " ", 1); + title = sdscatlen(title, (char*)argv[i], strlen(argv[i])); + } + + do { + len = redisFormatCommandArgv(&cmd,argc,argv,NULL); + benchmark(title,cmd,len); + free(cmd); + } while(config.loop); + + return 0; + } + + /* Run default benchmark suite. */ + do { + data = zmalloc(config.datasize+1); + memset(data,'x',config.datasize); + data[config.datasize] = '\0'; + + if (test_is_selected("ping_inline") || test_is_selected("ping")) + benchmark("PING_INLINE","PING\r\n",6); + + if (test_is_selected("ping_mbulk") || test_is_selected("ping")) { + len = redisFormatCommand(&cmd,"PING"); + benchmark("PING_BULK",cmd,len); + free(cmd); + } + + if (test_is_selected("set")) { + len = redisFormatCommand(&cmd,"SET foo:rand:000000000000 %s",data); + benchmark("SET",cmd,len); + free(cmd); + } + + if (test_is_selected("get")) { + len = redisFormatCommand(&cmd,"GET foo:rand:000000000000"); + benchmark("GET",cmd,len); + free(cmd); + } + + if (test_is_selected("incr")) { + len = redisFormatCommand(&cmd,"INCR counter:rand:000000000000"); + benchmark("INCR",cmd,len); + free(cmd); + } + + if (test_is_selected("lpush")) { + len = redisFormatCommand(&cmd,"LPUSH mylist %s",data); + benchmark("LPUSH",cmd,len); + free(cmd); + } + + if (test_is_selected("lpop")) { + len = redisFormatCommand(&cmd,"LPOP mylist"); + benchmark("LPOP",cmd,len); + free(cmd); + } + + if (test_is_selected("sadd")) { + len = redisFormatCommand(&cmd, + "SADD myset counter:rand:000000000000"); + benchmark("SADD",cmd,len); + free(cmd); + } + + if (test_is_selected("spop")) { + len = redisFormatCommand(&cmd,"SPOP myset"); + benchmark("SPOP",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || + test_is_selected("lrange_100") || + test_is_selected("lrange_300") || + test_is_selected("lrange_500") || + test_is_selected("lrange_600")) + { + len = redisFormatCommand(&cmd,"LPUSH mylist %s",data); + benchmark("LPUSH (needed to benchmark LRANGE)",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || test_is_selected("lrange_100")) { + len = redisFormatCommand(&cmd,"LRANGE mylist 0 99"); + benchmark("LRANGE_100 (first 100 elements)",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || test_is_selected("lrange_300")) { + len = redisFormatCommand(&cmd,"LRANGE mylist 0 299"); + benchmark("LRANGE_300 (first 300 elements)",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || test_is_selected("lrange_500")) { + len = redisFormatCommand(&cmd,"LRANGE mylist 0 449"); + benchmark("LRANGE_500 (first 450 elements)",cmd,len); + free(cmd); + } + + if (test_is_selected("lrange") || test_is_selected("lrange_600")) { + len = redisFormatCommand(&cmd,"LRANGE mylist 0 599"); + benchmark("LRANGE_600 (first 600 elements)",cmd,len); + free(cmd); + } + + if (test_is_selected("mset")) { + const char *argv[21]; + argv[0] = "MSET"; + for (i = 1; i < 21; i += 2) { + argv[i] = "foo:rand:000000000000"; + argv[i+1] = data; + } + len = redisFormatCommandArgv(&cmd,21,argv,NULL); + benchmark("MSET (10 keys)",cmd,len); + free(cmd); + } + + if (!config.csv) printf("\n"); + } while(config.loop); + + return 0; +} diff --git a/src/redis-cli.c b/src/redis-cli.c index e8c6be5..962b22e 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -1,1261 +1,1334 @@ -/* Redis CLI (command line interface) - * - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include "fmacros.h" -#include "version.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "hiredis.h" -#include "sds.h" -#include "zmalloc.h" -#include "linenoise.h" -#include "help.h" -#include "anet.h" -#include "ae.h" - -#define REDIS_NOTUSED(V) ((void) V) - -#define OUTPUT_STANDARD 0 -#define OUTPUT_RAW 1 -#define OUTPUT_CSV 2 - -static redisContext *context; -static struct config { - char *hostip; - int hostport; - char *hostsocket; - long repeat; - long interval; - int dbnum; - int interactive; - int shutdown; - int monitor_mode; - int pubsub_mode; - int latency_mode; - int cluster_mode; - int cluster_reissue_command; - int slave_mode; - int pipe_mode; - int bigkeys; - int stdinarg; /* get last arg from stdin. (-x option) */ - char *auth; - int output; /* output mode, see OUTPUT_* defines */ - sds mb_delim; - char prompt[128]; - char *eval; -} config; - -static void usage(); -char *redisGitSHA1(void); -char *redisGitDirty(void); - -/*------------------------------------------------------------------------------ - * Utility functions - *--------------------------------------------------------------------------- */ - -static long long mstime(void) { - struct timeval tv; - long long mst; - - gettimeofday(&tv, NULL); - mst = ((long)tv.tv_sec)*1000; - mst += tv.tv_usec/1000; - return mst; -} - -static void cliRefreshPrompt(void) { - int len; - - if (config.hostsocket != NULL) - len = snprintf(config.prompt,sizeof(config.prompt),"redis %s", - config.hostsocket); - else - len = snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d", - config.hostip, config.hostport); - /* Add [dbnum] if needed */ - if (config.dbnum != 0) - len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]", - config.dbnum); - snprintf(config.prompt+len,sizeof(config.prompt)-len,"> "); -} - -/*------------------------------------------------------------------------------ - * Help functions - *--------------------------------------------------------------------------- */ - -#define CLI_HELP_COMMAND 1 -#define CLI_HELP_GROUP 2 - -typedef struct { - int type; - int argc; - sds *argv; - sds full; - - /* Only used for help on commands */ - struct commandHelp *org; -} helpEntry; - -static helpEntry *helpEntries; -static int helpEntriesLen; - -static sds cliVersion() { - sds version; - version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); - - /* Add git commit and working tree status when available */ - if (strtoll(redisGitSHA1(),NULL,16)) { - version = sdscatprintf(version, " (git:%s", redisGitSHA1()); - if (strtoll(redisGitDirty(),NULL,10)) - version = sdscatprintf(version, "-dirty"); - version = sdscat(version, ")"); - } - return version; -} - -static void cliInitHelp() { - int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); - int groupslen = sizeof(commandGroups)/sizeof(char*); - int i, len, pos = 0; - helpEntry tmp; - - helpEntriesLen = len = commandslen+groupslen; - helpEntries = malloc(sizeof(helpEntry)*len); - - for (i = 0; i < groupslen; i++) { - tmp.argc = 1; - tmp.argv = malloc(sizeof(sds)); - tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); - tmp.full = tmp.argv[0]; - tmp.type = CLI_HELP_GROUP; - tmp.org = NULL; - helpEntries[pos++] = tmp; - } - - for (i = 0; i < commandslen; i++) { - tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); - tmp.full = sdsnew(commandHelp[i].name); - tmp.type = CLI_HELP_COMMAND; - tmp.org = &commandHelp[i]; - helpEntries[pos++] = tmp; - } -} - -/* Output command help to stdout. */ -static void cliOutputCommandHelp(struct commandHelp *help, int group) { - printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); - printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); - printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); - if (group) { - printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); - } -} - -/* Print generic help. */ -static void cliOutputGenericHelp() { - sds version = cliVersion(); - printf( - "redis-cli %s\r\n" - "Type: \"help @\" to get a list of commands in \r\n" - " \"help \" for help on \r\n" - " \"help \" to get a list of possible help topics\r\n" - " \"quit\" to exit\r\n", - version - ); - sdsfree(version); -} - -/* Output all command help, filtering by group or command name. */ -static void cliOutputHelp(int argc, char **argv) { - int i, j, len; - int group = -1; - helpEntry *entry; - struct commandHelp *help; - - if (argc == 0) { - cliOutputGenericHelp(); - return; - } else if (argc > 0 && argv[0][0] == '@') { - len = sizeof(commandGroups)/sizeof(char*); - for (i = 0; i < len; i++) { - if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { - group = i; - break; - } - } - } - - assert(argc > 0); - for (i = 0; i < helpEntriesLen; i++) { - entry = &helpEntries[i]; - if (entry->type != CLI_HELP_COMMAND) continue; - - help = entry->org; - if (group == -1) { - /* Compare all arguments */ - if (argc == entry->argc) { - for (j = 0; j < argc; j++) { - if (strcasecmp(argv[j],entry->argv[j]) != 0) break; - } - if (j == argc) { - cliOutputCommandHelp(help,1); - } - } - } else { - if (group == help->group) { - cliOutputCommandHelp(help,0); - } - } - } - printf("\r\n"); -} - -static void completionCallback(const char *buf, linenoiseCompletions *lc) { - size_t startpos = 0; - int mask; - int i; - size_t matchlen; - sds tmp; - - if (strncasecmp(buf,"help ",5) == 0) { - startpos = 5; - while (isspace(buf[startpos])) startpos++; - mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; - } else { - mask = CLI_HELP_COMMAND; - } - - for (i = 0; i < helpEntriesLen; i++) { - if (!(helpEntries[i].type & mask)) continue; - - matchlen = strlen(buf+startpos); - if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { - tmp = sdsnewlen(buf,startpos); - tmp = sdscat(tmp,helpEntries[i].full); - linenoiseAddCompletion(lc,tmp); - sdsfree(tmp); - } - } -} - -/*------------------------------------------------------------------------------ - * Networking / parsing - *--------------------------------------------------------------------------- */ - -/* Send AUTH command to the server */ -static int cliAuth() { - redisReply *reply; - if (config.auth == NULL) return REDIS_OK; - - reply = redisCommand(context,"AUTH %s",config.auth); - if (reply != NULL) { - freeReplyObject(reply); - return REDIS_OK; - } - return REDIS_ERR; -} - -/* Send SELECT dbnum to the server */ -static int cliSelect() { - redisReply *reply; - if (config.dbnum == 0) return REDIS_OK; - - reply = redisCommand(context,"SELECT %d",config.dbnum); - if (reply != NULL) { - freeReplyObject(reply); - return REDIS_OK; - } - return REDIS_ERR; -} - -/* Connect to the client. If force is not zero the connection is performed - * even if there is already a connected socket. */ -static int cliConnect(int force) { - if (context == NULL || force) { - if (context != NULL) - redisFree(context); - - if (config.hostsocket == NULL) { - context = redisConnect(config.hostip,config.hostport); - } else { - context = redisConnectUnix(config.hostsocket); - } - - if (context->err) { - fprintf(stderr,"Could not connect to Redis at "); - if (config.hostsocket == NULL) - fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr); - else - fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr); - redisFree(context); - context = NULL; - return REDIS_ERR; - } - - /* Do AUTH and select the right DB. */ - if (cliAuth() != REDIS_OK) - return REDIS_ERR; - if (cliSelect() != REDIS_OK) - return REDIS_ERR; - } - return REDIS_OK; -} - -static void cliPrintContextError() { - if (context == NULL) return; - fprintf(stderr,"Error: %s\n",context->errstr); -} - -static sds cliFormatReplyTTY(redisReply *r, char *prefix) { - sds out = sdsempty(); - switch (r->type) { - case REDIS_REPLY_ERROR: - out = sdscatprintf(out,"(error) %s\n", r->str); - break; - case REDIS_REPLY_STATUS: - out = sdscat(out,r->str); - out = sdscat(out,"\n"); - break; - case REDIS_REPLY_INTEGER: - out = sdscatprintf(out,"(integer) %lld\n",r->integer); - break; - case REDIS_REPLY_STRING: - /* If you are producing output for the standard output we want - * a more interesting output with quoted characters and so forth */ - out = sdscatrepr(out,r->str,r->len); - out = sdscat(out,"\n"); - break; - case REDIS_REPLY_NIL: - out = sdscat(out,"(nil)\n"); - break; - case REDIS_REPLY_ARRAY: - if (r->elements == 0) { - out = sdscat(out,"(empty list or set)\n"); - } else { - unsigned int i, idxlen = 0; - char _prefixlen[16]; - char _prefixfmt[16]; - sds _prefix; - sds tmp; - - /* Calculate chars needed to represent the largest index */ - i = r->elements; - do { - idxlen++; - i /= 10; - } while(i); - - /* Prefix for nested multi bulks should grow with idxlen+2 spaces */ - memset(_prefixlen,' ',idxlen+2); - _prefixlen[idxlen+2] = '\0'; - _prefix = sdscat(sdsnew(prefix),_prefixlen); - - /* Setup prefix format for every entry */ - snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen); - - for (i = 0; i < r->elements; i++) { - /* Don't use the prefix for the first element, as the parent - * caller already prepended the index number. */ - out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1); - - /* Format the multi bulk entry */ - tmp = cliFormatReplyTTY(r->element[i],_prefix); - out = sdscatlen(out,tmp,sdslen(tmp)); - sdsfree(tmp); - } - sdsfree(_prefix); - } - break; - default: - fprintf(stderr,"Unknown reply type: %d\n", r->type); - exit(1); - } - return out; -} - -static sds cliFormatReplyRaw(redisReply *r) { - sds out = sdsempty(), tmp; - size_t i; - - switch (r->type) { - case REDIS_REPLY_NIL: - /* Nothing... */ - break; - case REDIS_REPLY_ERROR: - out = sdscatlen(out,r->str,r->len); - out = sdscatlen(out,"\n",1); - break; - case REDIS_REPLY_STATUS: - case REDIS_REPLY_STRING: - out = sdscatlen(out,r->str,r->len); - break; - case REDIS_REPLY_INTEGER: - out = sdscatprintf(out,"%lld",r->integer); - break; - case REDIS_REPLY_ARRAY: - for (i = 0; i < r->elements; i++) { - if (i > 0) out = sdscat(out,config.mb_delim); - tmp = cliFormatReplyRaw(r->element[i]); - out = sdscatlen(out,tmp,sdslen(tmp)); - sdsfree(tmp); - } - break; - default: - fprintf(stderr,"Unknown reply type: %d\n", r->type); - exit(1); - } - return out; -} - -static sds cliFormatReplyCSV(redisReply *r) { - unsigned int i; - - sds out = sdsempty(); - switch (r->type) { - case REDIS_REPLY_ERROR: - out = sdscat(out,"ERROR,"); - out = sdscatrepr(out,r->str,strlen(r->str)); - break; - case REDIS_REPLY_STATUS: - out = sdscatrepr(out,r->str,r->len); - break; - case REDIS_REPLY_INTEGER: - out = sdscatprintf(out,"%lld",r->integer); - break; - case REDIS_REPLY_STRING: - out = sdscatrepr(out,r->str,r->len); - break; - case REDIS_REPLY_NIL: - out = sdscat(out,"NIL\n"); - break; - case REDIS_REPLY_ARRAY: - for (i = 0; i < r->elements; i++) { - sds tmp = cliFormatReplyCSV(r->element[i]); - out = sdscatlen(out,tmp,sdslen(tmp)); - if (i != r->elements-1) out = sdscat(out,","); - sdsfree(tmp); - } - break; - default: - fprintf(stderr,"Unknown reply type: %d\n", r->type); - exit(1); - } - return out; -} - -static int cliReadReply(int output_raw_strings) { - void *_reply; - redisReply *reply; - sds out = NULL; - int output = 1; - - if (redisGetReply(context,&_reply) != REDIS_OK) { - if (config.shutdown) - return REDIS_OK; - if (config.interactive) { - /* Filter cases where we should reconnect */ - if (context->err == REDIS_ERR_IO && errno == ECONNRESET) - return REDIS_ERR; - if (context->err == REDIS_ERR_EOF) - return REDIS_ERR; - } - cliPrintContextError(); - exit(1); - return REDIS_ERR; /* avoid compiler warning */ - } - - reply = (redisReply*)_reply; - - /* Check if we need to connect to a different node and reissue the - * request. */ - if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR && - (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK"))) - { - char *p = reply->str, *s; - int slot; - - output = 0; - /* Comments show the position of the pointer as: - * - * [S] for pointer 's' - * [P] for pointer 'p' - */ - s = strchr(p,' '); /* MOVED[S]3999 127.0.0.1:6381 */ - p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */ - *p = '\0'; - slot = atoi(s+1); - s = strchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */ - *s = '\0'; - sdsfree(config.hostip); - config.hostip = sdsnew(p+1); - config.hostport = atoi(s+1); - if (config.interactive) - printf("-> Redirected to slot [%d] located at %s:%d\n", - slot, config.hostip, config.hostport); - config.cluster_reissue_command = 1; - } - - if (output) { - if (output_raw_strings) { - out = cliFormatReplyRaw(reply); - } else { - if (config.output == OUTPUT_RAW) { - out = cliFormatReplyRaw(reply); - out = sdscat(out,"\n"); - } else if (config.output == OUTPUT_STANDARD) { - out = cliFormatReplyTTY(reply,""); - } else if (config.output == OUTPUT_CSV) { - out = cliFormatReplyCSV(reply); - out = sdscat(out,"\n"); - } - } - fwrite(out,sdslen(out),1,stdout); - sdsfree(out); - } - freeReplyObject(reply); - return REDIS_OK; -} - -static int cliSendCommand(int argc, char **argv, int repeat) { - char *command = argv[0]; - size_t *argvlen; - int j, output_raw; - - if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { - cliOutputHelp(--argc, ++argv); - return REDIS_OK; - } - - if (context == NULL) return REDIS_ERR; - - output_raw = 0; - if (!strcasecmp(command,"info") || - (argc == 2 && !strcasecmp(command,"cluster") && - (!strcasecmp(argv[1],"nodes") || - !strcasecmp(argv[1],"info"))) || - (argc == 2 && !strcasecmp(command,"client") && - !strcasecmp(argv[1],"list"))) - - { - output_raw = 1; - } - - if (!strcasecmp(command,"shutdown")) config.shutdown = 1; - if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; - if (!strcasecmp(command,"subscribe") || - !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; - - /* Setup argument length */ - argvlen = malloc(argc*sizeof(size_t)); - for (j = 0; j < argc; j++) - argvlen[j] = sdslen(argv[j]); - - while(repeat--) { - redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); - while (config.monitor_mode) { - if (cliReadReply(output_raw) != REDIS_OK) exit(1); - fflush(stdout); - } - - if (config.pubsub_mode) { - if (config.output != OUTPUT_RAW) - printf("Reading messages... (press Ctrl-C to quit)\n"); - while (1) { - if (cliReadReply(output_raw) != REDIS_OK) exit(1); - } - } - - if (cliReadReply(output_raw) != REDIS_OK) { - free(argvlen); - return REDIS_ERR; - } else { - /* Store database number when SELECT was successfully executed. */ - if (!strcasecmp(command,"select") && argc == 2) { - config.dbnum = atoi(argv[1]); - cliRefreshPrompt(); - } - } - if (config.interval) usleep(config.interval); - fflush(stdout); /* Make it grep friendly */ - } - - free(argvlen); - return REDIS_OK; -} - -/*------------------------------------------------------------------------------ - * User interface - *--------------------------------------------------------------------------- */ - -static int parseOptions(int argc, char **argv) { - int i; - - for (i = 1; i < argc; i++) { - int lastarg = i==argc-1; - - if (!strcmp(argv[i],"-h") && !lastarg) { - sdsfree(config.hostip); - config.hostip = sdsnew(argv[++i]); - } else if (!strcmp(argv[i],"-h") && lastarg) { - usage(); - } else if (!strcmp(argv[i],"--help")) { - usage(); - } else if (!strcmp(argv[i],"-x")) { - config.stdinarg = 1; - } else if (!strcmp(argv[i],"-p") && !lastarg) { - config.hostport = atoi(argv[++i]); - } else if (!strcmp(argv[i],"-s") && !lastarg) { - config.hostsocket = argv[++i]; - } else if (!strcmp(argv[i],"-r") && !lastarg) { - config.repeat = strtoll(argv[++i],NULL,10); - } else if (!strcmp(argv[i],"-i") && !lastarg) { - double seconds = atof(argv[++i]); - config.interval = seconds*1000000; - } else if (!strcmp(argv[i],"-n") && !lastarg) { - config.dbnum = atoi(argv[++i]); - } else if (!strcmp(argv[i],"-a") && !lastarg) { - config.auth = argv[++i]; - } else if (!strcmp(argv[i],"--raw")) { - config.output = OUTPUT_RAW; - } else if (!strcmp(argv[i],"--csv")) { - config.output = OUTPUT_CSV; - } else if (!strcmp(argv[i],"--latency")) { - config.latency_mode = 1; - } else if (!strcmp(argv[i],"--slave")) { - config.slave_mode = 1; - } else if (!strcmp(argv[i],"--pipe")) { - config.pipe_mode = 1; - } else if (!strcmp(argv[i],"--bigkeys")) { - config.bigkeys = 1; - } else if (!strcmp(argv[i],"--eval") && !lastarg) { - config.eval = argv[++i]; - } else if (!strcmp(argv[i],"-c")) { - config.cluster_mode = 1; - } else if (!strcmp(argv[i],"-d") && !lastarg) { - sdsfree(config.mb_delim); - config.mb_delim = sdsnew(argv[++i]); - } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { - sds version = cliVersion(); - printf("redis-cli %s\n", version); - sdsfree(version); - exit(0); - } else { - break; - } - } - return i; -} - -static sds readArgFromStdin(void) { - char buf[1024]; - sds arg = sdsempty(); - - while(1) { - int nread = read(fileno(stdin),buf,1024); - - if (nread == 0) break; - else if (nread == -1) { - perror("Reading from standard input"); - exit(1); - } - arg = sdscatlen(arg,buf,nread); - } - return arg; -} - -static void usage() { - sds version = cliVersion(); - fprintf(stderr, -"redis-cli %s\n" -"\n" -"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" -" -h Server hostname (default: 127.0.0.1)\n" -" -p Server port (default: 6379)\n" -" -s Server socket (overrides hostname and port)\n" -" -a Password to use when connecting to the server\n" -" -r Execute specified command N times\n" -" -i When -r is used, waits seconds per command.\n" -" It is possible to specify sub-second times like -i 0.1\n" -" -n Database number\n" -" -x Read last argument from STDIN\n" -" -d Multi-bulk delimiter in for raw formatting (default: \\n)\n" -" -c Enable cluster mode (follow -ASK and -MOVED redirections)\n" -" --raw Use raw formatting for replies (default when STDOUT is not a tty)\n" -" --latency Enter a special mode continuously sampling latency\n" -" --slave Simulate a slave showing commands received from the master\n" -" --pipe Transfer raw Redis protocol from stdin to server\n" -" --bigkeys Sample Redis keys looking for big keys\n" -" --eval Send an EVAL command using the Lua script at \n" -" --help Output this help and exit\n" -" --version Output version and exit\n" -"\n" -"Examples:\n" -" cat /etc/passwd | redis-cli -x set mypasswd\n" -" redis-cli get mypasswd\n" -" redis-cli -r 100 lpush mylist x\n" -" redis-cli -r 100 -i 1 info | grep used_memory_human:\n" -" redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n" -" (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n" -"\n" -"When no command is given, redis-cli starts in interactive mode.\n" -"Type \"help\" in interactive mode for information on available commands.\n" -"\n", - version); - sdsfree(version); - exit(1); -} - -/* Turn the plain C strings into Sds strings */ -static char **convertToSds(int count, char** args) { - int j; - char **sds = zmalloc(sizeof(char*)*count); - - for(j = 0; j < count; j++) - sds[j] = sdsnew(args[j]); - - return sds; -} - -#define LINE_BUFLEN 4096 -static void repl() { - sds historyfile = NULL; - int history = 0; - char *line; - int argc; - sds *argv; - - config.interactive = 1; - linenoiseSetCompletionCallback(completionCallback); - - /* Only use history when stdin is a tty. */ - if (isatty(fileno(stdin))) { - history = 1; - - if (getenv("HOME") != NULL) { - historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME")); - linenoiseHistoryLoad(historyfile); - } - } - - cliRefreshPrompt(); - while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) { - if (line[0] != '\0') { - argv = sdssplitargs(line,&argc); - if (history) linenoiseHistoryAdd(line); - if (historyfile) linenoiseHistorySave(historyfile); - - if (argv == NULL) { - printf("Invalid argument(s)\n"); - free(line); - continue; - } else if (argc > 0) { - if (strcasecmp(argv[0],"quit") == 0 || - strcasecmp(argv[0],"exit") == 0) - { - exit(0); - } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { - sdsfree(config.hostip); - config.hostip = sdsnew(argv[1]); - config.hostport = atoi(argv[2]); - cliConnect(1); - } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { - linenoiseClearScreen(); - } else { - long long start_time = mstime(), elapsed; - int repeat, skipargs = 0; - - repeat = atoi(argv[0]); - if (argc > 1 && repeat) { - skipargs = 1; - } else { - repeat = 1; - } - - while (1) { - config.cluster_reissue_command = 0; - if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) - != REDIS_OK) - { - cliConnect(1); - - /* If we still cannot send the command print error. - * We'll try to reconnect the next time. */ - if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) - != REDIS_OK) - cliPrintContextError(); - } - /* Issue the command again if we got redirected in cluster mode */ - if (config.cluster_mode && config.cluster_reissue_command) { - cliConnect(1); - } else { - break; - } - } - elapsed = mstime()-start_time; - if (elapsed >= 500) { - printf("(%.2fs)\n",(double)elapsed/1000); - } - } - } - /* Free the argument vector */ - while(argc--) sdsfree(argv[argc]); - zfree(argv); - } - /* linenoise() returns malloc-ed lines like readline() */ - free(line); - } - exit(0); -} - -static int noninteractive(int argc, char **argv) { - int retval = 0; - if (config.stdinarg) { - argv = zrealloc(argv, (argc+1)*sizeof(char*)); - argv[argc] = readArgFromStdin(); - retval = cliSendCommand(argc+1, argv, config.repeat); - } else { - /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ - retval = cliSendCommand(argc, argv, config.repeat); - } - return retval; -} - -static int evalMode(int argc, char **argv) { - sds script = sdsempty(); - FILE *fp; - char buf[1024]; - size_t nread; - char **argv2; - int j, got_comma = 0, keys = 0; - - /* Load the script from the file, as an sds string. */ - fp = fopen(config.eval,"r"); - if (!fp) { - fprintf(stderr, - "Can't open file '%s': %s\n", config.eval, strerror(errno)); - exit(1); - } - while((nread = fread(buf,1,sizeof(buf),fp)) != 0) { - script = sdscatlen(script,buf,nread); - } - fclose(fp); - - /* Create our argument vector */ - argv2 = zmalloc(sizeof(sds)*(argc+3)); - argv2[0] = sdsnew("EVAL"); - argv2[1] = script; - for (j = 0; j < argc; j++) { - if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) { - got_comma = 1; - continue; - } - argv2[j+3-got_comma] = sdsnew(argv[j]); - if (!got_comma) keys++; - } - argv2[2] = sdscatprintf(sdsempty(),"%d",keys); - - /* Call it */ - return cliSendCommand(argc+3-got_comma, argv2, config.repeat); -} - -static void latencyMode(void) { - redisReply *reply; - long long start, latency, min = 0, max = 0, tot = 0, count = 0; - double avg; - - if (!context) exit(1); - while(1) { - start = mstime(); - reply = redisCommand(context,"PING"); - if (reply == NULL) { - fprintf(stderr,"\nI/O error\n"); - exit(1); - } - latency = mstime()-start; - freeReplyObject(reply); - count++; - if (count == 1) { - min = max = tot = latency; - avg = (double) latency; - } else { - if (latency < min) min = latency; - if (latency > max) max = latency; - tot += latency; - avg = (double) tot/count; - } - printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)", - min, max, avg, count); - fflush(stdout); - usleep(10000); - } -} - -static void slaveMode(void) { - /* To start we need to send the SYNC command and return the payload. - * The hiredis client lib does not understand this part of the protocol - * and we don't want to mess with its buffers, so everything is performed - * using direct low-level I/O. */ - int fd = context->fd; - char buf[1024], *p; - ssize_t nread; - unsigned long long payload; - - /* Send the SYNC command. */ - if (write(fd,"SYNC\r\n",6) != 6) { - fprintf(stderr,"Error writing to master\n"); - exit(1); - } - - /* Read $\r\n, making sure to read just up to "\n" */ - p = buf; - while(1) { - nread = read(fd,p,1); - if (nread <= 0) { - fprintf(stderr,"Error reading bulk length while SYNCing\n"); - exit(1); - } - if (*p == '\n') break; - p++; - } - *p = '\0'; - payload = strtoull(buf+1,NULL,10); - fprintf(stderr,"SYNC with master, discarding %lld bytes of bulk tranfer...\n", - payload); - - /* Discard the payload. */ - while(payload) { - nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); - if (nread <= 0) { - fprintf(stderr,"Error reading RDB payload while SYNCing\n"); - exit(1); - } - payload -= nread; - } - fprintf(stderr,"SYNC done. Logging commands from master.\n"); - - /* Now we can use the hiredis to read the incoming protocol. */ - config.output = OUTPUT_CSV; - while (cliReadReply(0) == REDIS_OK); -} - -static void pipeMode(void) { - int fd = context->fd; - long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0; - char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */ - char aneterr[ANET_ERR_LEN]; - redisReader *reader = redisReaderCreate(); - redisReply *reply; - int eof = 0; /* True once we consumed all the standard input. */ - int done = 0; - char magic[20]; /* Special reply we recognize. */ - - srand(time(NULL)); - - /* Use non blocking I/O. */ - if (anetNonBlock(aneterr,fd) == ANET_ERR) { - fprintf(stderr, "Can't set the socket in non blocking mode: %s\n", - aneterr); - exit(1); - } - - /* Transfer raw protocol and read replies from the server at the same - * time. */ - while(!done) { - int mask = AE_READABLE; - - if (!eof || obuf_len != 0) mask |= AE_WRITABLE; - mask = aeWait(fd,mask,1000); - - /* Handle the readable state: we can read replies from the server. */ - if (mask & AE_READABLE) { - ssize_t nread; - - /* Read from socket and feed the hiredis reader. */ - do { - nread = read(fd,ibuf,sizeof(ibuf)); - if (nread == -1 && errno != EAGAIN && errno != EINTR) { - fprintf(stderr, "Error reading from the server: %s\n", - strerror(errno)); - exit(1); - } - if (nread > 0) redisReaderFeed(reader,ibuf,nread); - } while(nread > 0); - - /* Consume replies. */ - do { - if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) { - fprintf(stderr, "Error reading replies from server\n"); - exit(1); - } - if (reply) { - if (reply->type == REDIS_REPLY_ERROR) { - fprintf(stderr,"%s\n", reply->str); - errors++; - } else if (eof && reply->type == REDIS_REPLY_STRING && - reply->len == 20) { - /* Check if this is the reply to our final ECHO - * command. If so everything was received - * from the server. */ - if (memcmp(reply->str,magic,20) == 0) { - printf("Last reply received from server.\n"); - done = 1; - replies--; - } - } - replies++; - freeReplyObject(reply); - } - } while(reply); - } - - /* Handle the writable state: we can send protocol to the server. */ - if (mask & AE_WRITABLE) { - while(1) { - /* Transfer current buffer to server. */ - if (obuf_len != 0) { - ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len); - - if (nwritten == -1) { - if (errno != EAGAIN && errno != EINTR) { - fprintf(stderr, "Error writing to the server: %s\n", - strerror(errno)); - exit(1); - } else { - nwritten = 0; - } - } - obuf_len -= nwritten; - obuf_pos += nwritten; - if (obuf_len != 0) break; /* Can't accept more data. */ - } - /* If buffer is empty, load from stdin. */ - if (obuf_len == 0 && !eof) { - ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf)); - - if (nread == 0) { - char echo[] = - "*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n"; - int j; - - eof = 1; - /* Everything transfered, so we queue a special - * ECHO command that we can match in the replies - * to make sure everything was read from the server. */ - for (j = 0; j < 20; j++) - magic[j] = rand() & 0xff; - memcpy(echo+19,magic,20); - memcpy(obuf,echo,sizeof(echo)-1); - obuf_len = sizeof(echo)-1; - obuf_pos = 0; - printf("All data transferred. Waiting for the last reply...\n"); - } else if (nread == -1) { - fprintf(stderr, "Error reading from stdin: %s\n", - strerror(errno)); - exit(1); - } else { - obuf_len = nread; - obuf_pos = 0; - } - } - if (obuf_len == 0 && eof) break; - } - } - } - redisReaderFree(reader); - printf("errors: %lld, replies: %lld\n", errors, replies); - if (errors) - exit(1); - else - exit(0); -} - -#define TYPE_STRING 0 -#define TYPE_LIST 1 -#define TYPE_SET 2 -#define TYPE_HASH 3 -#define TYPE_ZSET 4 - -static void findBigKeys(void) { - unsigned long long biggest[5] = {0,0,0,0,0}; - unsigned long long samples = 0; - redisReply *reply1, *reply2, *reply3 = NULL; - char *sizecmd, *typename[] = {"string","list","set","hash","zset"}; - int type; - - printf("\n# Press ctrl+c when you have had enough of it... :)\n"); - printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n"); - printf("# in order to reduce server load (usually not needed).\n\n"); - while(1) { - /* Sample with RANDOMKEY */ - reply1 = redisCommand(context,"RANDOMKEY"); - if (reply1 == NULL) { - fprintf(stderr,"\nI/O error\n"); - exit(1); - } else if (reply1->type == REDIS_REPLY_ERROR) { - fprintf(stderr, "RANDOMKEY error: %s\n", - reply1->str); - exit(1); - } - /* Get the key type */ - reply2 = redisCommand(context,"TYPE %s",reply1->str); - assert(reply2 && reply2->type == REDIS_REPLY_STATUS); - samples++; - - /* Get the key "size" */ - if (!strcmp(reply2->str,"string")) { - sizecmd = "STRLEN"; - type = TYPE_STRING; - } else if (!strcmp(reply2->str,"list")) { - sizecmd = "LLEN"; - type = TYPE_LIST; - } else if (!strcmp(reply2->str,"set")) { - sizecmd = "SCARD"; - type = TYPE_SET; - } else if (!strcmp(reply2->str,"hash")) { - sizecmd = "HLEN"; - type = TYPE_HASH; - } else if (!strcmp(reply2->str,"zset")) { - sizecmd = "ZCARD"; - type = TYPE_ZSET; - } else if (!strcmp(reply2->str,"none")) { - freeReplyObject(reply1); - freeReplyObject(reply2); - freeReplyObject(reply3); - continue; - } else { - fprintf(stderr, "Unknown key type '%s' for key '%s'\n", - reply2->str, reply1->str); - exit(1); - } - - reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str); - if (reply3 && reply3->type == REDIS_REPLY_INTEGER) { - if (biggest[type] < reply3->integer) { - printf("[%6s] %s | biggest so far with size %llu\n", - typename[type], reply1->str, - (unsigned long long) reply3->integer); - biggest[type] = reply3->integer; - } - } - - if ((samples % 1000000) == 0) - printf("(%llu keys sampled)\n", samples); - - if ((samples % 100) == 0 && config.interval) - usleep(config.interval); - - freeReplyObject(reply1); - freeReplyObject(reply2); - if (reply3) freeReplyObject(reply3); - } -} - -int main(int argc, char **argv) { - int firstarg; - - config.hostip = sdsnew("127.0.0.1"); - config.hostport = 6379; - config.hostsocket = NULL; - config.repeat = 1; - config.interval = 0; - config.dbnum = 0; - config.interactive = 0; - config.shutdown = 0; - config.monitor_mode = 0; - config.pubsub_mode = 0; - config.latency_mode = 0; - config.cluster_mode = 0; - config.slave_mode = 0; - config.pipe_mode = 0; - config.bigkeys = 0; - config.stdinarg = 0; - config.auth = NULL; - config.eval = NULL; - if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL)) - config.output = OUTPUT_RAW; - else - config.output = OUTPUT_STANDARD; - config.mb_delim = sdsnew("\n"); - cliInitHelp(); - - firstarg = parseOptions(argc,argv); - argc -= firstarg; - argv += firstarg; - - /* Latency mode */ - if (config.latency_mode) { - cliConnect(0); - latencyMode(); - } - - /* Slave mode */ - if (config.slave_mode) { - cliConnect(0); - slaveMode(); - } - - /* Pipe mode */ - if (config.pipe_mode) { - if (cliConnect(0) == REDIS_ERR) exit(1); - pipeMode(); - } - - /* Find big keys */ - if (config.bigkeys) { - cliConnect(0); - findBigKeys(); - } - - /* Start interactive mode when no command is provided */ - if (argc == 0 && !config.eval) { - /* Note that in repl mode we don't abort on connection error. - * A new attempt will be performed for every command send. */ - cliConnect(0); - repl(); - } - - /* Otherwise, we have some arguments to execute */ - if (cliConnect(0) != REDIS_OK) exit(1); - if (config.eval) { - return evalMode(argc,argv); - } else { - return noninteractive(argc,convertToSds(argc,argv)); - } -} +/* Redis CLI (command line interface) + * + * Copyright (c) 2009-2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "fmacros.h" +#include "version.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hiredis.h" +#include "sds.h" +#include "zmalloc.h" +#include "linenoise.h" +#include "help.h" +#include "anet.h" +#include "ae.h" + +#define REDIS_NOTUSED(V) ((void) V) + +#define OUTPUT_STANDARD 0 +#define OUTPUT_RAW 1 +#define OUTPUT_CSV 2 + +static redisContext *context; +static struct config { + char *hostip; + int hostport; + char *hostsocket; + long repeat; + long interval; + int dbnum; + int interactive; + int shutdown; + int monitor_mode; + int pubsub_mode; + int latency_mode; + int cluster_mode; + int cluster_reissue_command; + int slave_mode; + int pipe_mode; + int getrdb_mode; + char *rdb_filename; + int bigkeys; + int stdinarg; /* get last arg from stdin. (-x option) */ + char *auth; + int output; /* output mode, see OUTPUT_* defines */ + sds mb_delim; + char prompt[128]; + char *eval; +} config; + +static void usage(); +char *redisGitSHA1(void); +char *redisGitDirty(void); + +/*------------------------------------------------------------------------------ + * Utility functions + *--------------------------------------------------------------------------- */ + +static long long mstime(void) { + struct timeval tv; + long long mst; + + gettimeofday(&tv, NULL); + mst = ((long long)tv.tv_sec)*1000; + mst += tv.tv_usec/1000; + return mst; +} + +static void cliRefreshPrompt(void) { + int len; + + if (config.hostsocket != NULL) + len = snprintf(config.prompt,sizeof(config.prompt),"redis %s", + config.hostsocket); + else + len = snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d", + config.hostip, config.hostport); + /* Add [dbnum] if needed */ + if (config.dbnum != 0) + len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]", + config.dbnum); + snprintf(config.prompt+len,sizeof(config.prompt)-len,"> "); +} + +/*------------------------------------------------------------------------------ + * Help functions + *--------------------------------------------------------------------------- */ + +#define CLI_HELP_COMMAND 1 +#define CLI_HELP_GROUP 2 + +typedef struct { + int type; + int argc; + sds *argv; + sds full; + + /* Only used for help on commands */ + struct commandHelp *org; +} helpEntry; + +static helpEntry *helpEntries; +static int helpEntriesLen; + +static sds cliVersion() { + sds version; + version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); + + /* Add git commit and working tree status when available */ + if (strtoll(redisGitSHA1(),NULL,16)) { + version = sdscatprintf(version, " (git:%s", redisGitSHA1()); + if (strtoll(redisGitDirty(),NULL,10)) + version = sdscatprintf(version, "-dirty"); + version = sdscat(version, ")"); + } + return version; +} + +static void cliInitHelp() { + int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); + int groupslen = sizeof(commandGroups)/sizeof(char*); + int i, len, pos = 0; + helpEntry tmp; + + helpEntriesLen = len = commandslen+groupslen; + helpEntries = malloc(sizeof(helpEntry)*len); + + for (i = 0; i < groupslen; i++) { + tmp.argc = 1; + tmp.argv = malloc(sizeof(sds)); + tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); + tmp.full = tmp.argv[0]; + tmp.type = CLI_HELP_GROUP; + tmp.org = NULL; + helpEntries[pos++] = tmp; + } + + for (i = 0; i < commandslen; i++) { + tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); + tmp.full = sdsnew(commandHelp[i].name); + tmp.type = CLI_HELP_COMMAND; + tmp.org = &commandHelp[i]; + helpEntries[pos++] = tmp; + } +} + +/* Output command help to stdout. */ +static void cliOutputCommandHelp(struct commandHelp *help, int group) { + printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); + printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); + printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); + if (group) { + printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); + } +} + +/* Print generic help. */ +static void cliOutputGenericHelp() { + sds version = cliVersion(); + printf( + "redis-cli %s\r\n" + "Type: \"help @\" to get a list of commands in \r\n" + " \"help \" for help on \r\n" + " \"help \" to get a list of possible help topics\r\n" + " \"quit\" to exit\r\n", + version + ); + sdsfree(version); +} + +/* Output all command help, filtering by group or command name. */ +static void cliOutputHelp(int argc, char **argv) { + int i, j, len; + int group = -1; + helpEntry *entry; + struct commandHelp *help; + + if (argc == 0) { + cliOutputGenericHelp(); + return; + } else if (argc > 0 && argv[0][0] == '@') { + len = sizeof(commandGroups)/sizeof(char*); + for (i = 0; i < len; i++) { + if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { + group = i; + break; + } + } + } + + assert(argc > 0); + for (i = 0; i < helpEntriesLen; i++) { + entry = &helpEntries[i]; + if (entry->type != CLI_HELP_COMMAND) continue; + + help = entry->org; + if (group == -1) { + /* Compare all arguments */ + if (argc == entry->argc) { + for (j = 0; j < argc; j++) { + if (strcasecmp(argv[j],entry->argv[j]) != 0) break; + } + if (j == argc) { + cliOutputCommandHelp(help,1); + } + } + } else { + if (group == help->group) { + cliOutputCommandHelp(help,0); + } + } + } + printf("\r\n"); +} + +static void completionCallback(const char *buf, linenoiseCompletions *lc) { + size_t startpos = 0; + int mask; + int i; + size_t matchlen; + sds tmp; + + if (strncasecmp(buf,"help ",5) == 0) { + startpos = 5; + while (isspace(buf[startpos])) startpos++; + mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; + } else { + mask = CLI_HELP_COMMAND; + } + + for (i = 0; i < helpEntriesLen; i++) { + if (!(helpEntries[i].type & mask)) continue; + + matchlen = strlen(buf+startpos); + if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { + tmp = sdsnewlen(buf,startpos); + tmp = sdscat(tmp,helpEntries[i].full); + linenoiseAddCompletion(lc,tmp); + sdsfree(tmp); + } + } +} + +/*------------------------------------------------------------------------------ + * Networking / parsing + *--------------------------------------------------------------------------- */ + +/* Send AUTH command to the server */ +static int cliAuth() { + redisReply *reply; + if (config.auth == NULL) return REDIS_OK; + + reply = redisCommand(context,"AUTH %s",config.auth); + if (reply != NULL) { + freeReplyObject(reply); + return REDIS_OK; + } + return REDIS_ERR; +} + +/* Send SELECT dbnum to the server */ +static int cliSelect() { + redisReply *reply; + if (config.dbnum == 0) return REDIS_OK; + + reply = redisCommand(context,"SELECT %d",config.dbnum); + if (reply != NULL) { + freeReplyObject(reply); + return REDIS_OK; + } + return REDIS_ERR; +} + +/* Connect to the client. If force is not zero the connection is performed + * even if there is already a connected socket. */ +static int cliConnect(int force) { + if (context == NULL || force) { + if (context != NULL) + redisFree(context); + + if (config.hostsocket == NULL) { + context = redisConnect(config.hostip,config.hostport); + } else { + context = redisConnectUnix(config.hostsocket); + } + + if (context->err) { + fprintf(stderr,"Could not connect to Redis at "); + if (config.hostsocket == NULL) + fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr); + else + fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr); + redisFree(context); + context = NULL; + return REDIS_ERR; + } + + /* Do AUTH and select the right DB. */ + if (cliAuth() != REDIS_OK) + return REDIS_ERR; + if (cliSelect() != REDIS_OK) + return REDIS_ERR; + } + return REDIS_OK; +} + +static void cliPrintContextError() { + if (context == NULL) return; + fprintf(stderr,"Error: %s\n",context->errstr); +} + +static sds cliFormatReplyTTY(redisReply *r, char *prefix) { + sds out = sdsempty(); + switch (r->type) { + case REDIS_REPLY_ERROR: + out = sdscatprintf(out,"(error) %s\n", r->str); + break; + case REDIS_REPLY_STATUS: + out = sdscat(out,r->str); + out = sdscat(out,"\n"); + break; + case REDIS_REPLY_INTEGER: + out = sdscatprintf(out,"(integer) %lld\n",r->integer); + break; + case REDIS_REPLY_STRING: + /* If you are producing output for the standard output we want + * a more interesting output with quoted characters and so forth */ + out = sdscatrepr(out,r->str,r->len); + out = sdscat(out,"\n"); + break; + case REDIS_REPLY_NIL: + out = sdscat(out,"(nil)\n"); + break; + case REDIS_REPLY_ARRAY: + if (r->elements == 0) { + out = sdscat(out,"(empty list or set)\n"); + } else { + unsigned int i, idxlen = 0; + char _prefixlen[16]; + char _prefixfmt[16]; + sds _prefix; + sds tmp; + + /* Calculate chars needed to represent the largest index */ + i = r->elements; + do { + idxlen++; + i /= 10; + } while(i); + + /* Prefix for nested multi bulks should grow with idxlen+2 spaces */ + memset(_prefixlen,' ',idxlen+2); + _prefixlen[idxlen+2] = '\0'; + _prefix = sdscat(sdsnew(prefix),_prefixlen); + + /* Setup prefix format for every entry */ + snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen); + + for (i = 0; i < r->elements; i++) { + /* Don't use the prefix for the first element, as the parent + * caller already prepended the index number. */ + out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1); + + /* Format the multi bulk entry */ + tmp = cliFormatReplyTTY(r->element[i],_prefix); + out = sdscatlen(out,tmp,sdslen(tmp)); + sdsfree(tmp); + } + sdsfree(_prefix); + } + break; + default: + fprintf(stderr,"Unknown reply type: %d\n", r->type); + exit(1); + } + return out; +} + +static sds cliFormatReplyRaw(redisReply *r) { + sds out = sdsempty(), tmp; + size_t i; + + switch (r->type) { + case REDIS_REPLY_NIL: + /* Nothing... */ + break; + case REDIS_REPLY_ERROR: + out = sdscatlen(out,r->str,r->len); + out = sdscatlen(out,"\n",1); + break; + case REDIS_REPLY_STATUS: + case REDIS_REPLY_STRING: + out = sdscatlen(out,r->str,r->len); + break; + case REDIS_REPLY_INTEGER: + out = sdscatprintf(out,"%lld",r->integer); + break; + case REDIS_REPLY_ARRAY: + for (i = 0; i < r->elements; i++) { + if (i > 0) out = sdscat(out,config.mb_delim); + tmp = cliFormatReplyRaw(r->element[i]); + out = sdscatlen(out,tmp,sdslen(tmp)); + sdsfree(tmp); + } + break; + default: + fprintf(stderr,"Unknown reply type: %d\n", r->type); + exit(1); + } + return out; +} + +static sds cliFormatReplyCSV(redisReply *r) { + unsigned int i; + + sds out = sdsempty(); + switch (r->type) { + case REDIS_REPLY_ERROR: + out = sdscat(out,"ERROR,"); + out = sdscatrepr(out,r->str,strlen(r->str)); + break; + case REDIS_REPLY_STATUS: + out = sdscatrepr(out,r->str,r->len); + break; + case REDIS_REPLY_INTEGER: + out = sdscatprintf(out,"%lld",r->integer); + break; + case REDIS_REPLY_STRING: + out = sdscatrepr(out,r->str,r->len); + break; + case REDIS_REPLY_NIL: + out = sdscat(out,"NIL\n"); + break; + case REDIS_REPLY_ARRAY: + for (i = 0; i < r->elements; i++) { + sds tmp = cliFormatReplyCSV(r->element[i]); + out = sdscatlen(out,tmp,sdslen(tmp)); + if (i != r->elements-1) out = sdscat(out,","); + sdsfree(tmp); + } + break; + default: + fprintf(stderr,"Unknown reply type: %d\n", r->type); + exit(1); + } + return out; +} + +static int cliReadReply(int output_raw_strings) { + void *_reply; + redisReply *reply; + sds out = NULL; + int output = 1; + + if (redisGetReply(context,&_reply) != REDIS_OK) { + if (config.shutdown) + return REDIS_OK; + if (config.interactive) { + /* Filter cases where we should reconnect */ + if (context->err == REDIS_ERR_IO && errno == ECONNRESET) + return REDIS_ERR; + if (context->err == REDIS_ERR_EOF) + return REDIS_ERR; + } + cliPrintContextError(); + exit(1); + return REDIS_ERR; /* avoid compiler warning */ + } + + reply = (redisReply*)_reply; + + /* Check if we need to connect to a different node and reissue the + * request. */ + if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR && + (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK"))) + { + char *p = reply->str, *s; + int slot; + + output = 0; + /* Comments show the position of the pointer as: + * + * [S] for pointer 's' + * [P] for pointer 'p' + */ + s = strchr(p,' '); /* MOVED[S]3999 127.0.0.1:6381 */ + p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */ + *p = '\0'; + slot = atoi(s+1); + s = strchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */ + *s = '\0'; + sdsfree(config.hostip); + config.hostip = sdsnew(p+1); + config.hostport = atoi(s+1); + if (config.interactive) + printf("-> Redirected to slot [%d] located at %s:%d\n", + slot, config.hostip, config.hostport); + config.cluster_reissue_command = 1; + } + + if (output) { + if (output_raw_strings) { + out = cliFormatReplyRaw(reply); + } else { + if (config.output == OUTPUT_RAW) { + out = cliFormatReplyRaw(reply); + out = sdscat(out,"\n"); + } else if (config.output == OUTPUT_STANDARD) { + out = cliFormatReplyTTY(reply,""); + } else if (config.output == OUTPUT_CSV) { + out = cliFormatReplyCSV(reply); + out = sdscat(out,"\n"); + } + } + fwrite(out,sdslen(out),1,stdout); + sdsfree(out); + } + freeReplyObject(reply); + return REDIS_OK; +} + +static int cliSendCommand(int argc, char **argv, int repeat) { + char *command = argv[0]; + size_t *argvlen; + int j, output_raw; + + if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { + cliOutputHelp(--argc, ++argv); + return REDIS_OK; + } + + if (context == NULL) return REDIS_ERR; + + output_raw = 0; + if (!strcasecmp(command,"info") || + (argc == 2 && !strcasecmp(command,"cluster") && + (!strcasecmp(argv[1],"nodes") || + !strcasecmp(argv[1],"info"))) || + (argc == 2 && !strcasecmp(command,"client") && + !strcasecmp(argv[1],"list"))) + + { + output_raw = 1; + } + + if (!strcasecmp(command,"shutdown")) config.shutdown = 1; + if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; + if (!strcasecmp(command,"subscribe") || + !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; + + /* Setup argument length */ + argvlen = malloc(argc*sizeof(size_t)); + for (j = 0; j < argc; j++) + argvlen[j] = sdslen(argv[j]); + + while(repeat--) { + redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); + while (config.monitor_mode) { + if (cliReadReply(output_raw) != REDIS_OK) exit(1); + fflush(stdout); + } + + if (config.pubsub_mode) { + if (config.output != OUTPUT_RAW) + printf("Reading messages... (press Ctrl-C to quit)\n"); + while (1) { + if (cliReadReply(output_raw) != REDIS_OK) exit(1); + } + } + + if (cliReadReply(output_raw) != REDIS_OK) { + free(argvlen); + return REDIS_ERR; + } else { + /* Store database number when SELECT was successfully executed. */ + if (!strcasecmp(command,"select") && argc == 2) { + config.dbnum = atoi(argv[1]); + cliRefreshPrompt(); + } + } + if (config.interval) usleep(config.interval); + fflush(stdout); /* Make it grep friendly */ + } + + free(argvlen); + return REDIS_OK; +} + +/*------------------------------------------------------------------------------ + * User interface + *--------------------------------------------------------------------------- */ + +static int parseOptions(int argc, char **argv) { + int i; + + for (i = 1; i < argc; i++) { + int lastarg = i==argc-1; + + if (!strcmp(argv[i],"-h") && !lastarg) { + sdsfree(config.hostip); + config.hostip = sdsnew(argv[++i]); + } else if (!strcmp(argv[i],"-h") && lastarg) { + usage(); + } else if (!strcmp(argv[i],"--help")) { + usage(); + } else if (!strcmp(argv[i],"-x")) { + config.stdinarg = 1; + } else if (!strcmp(argv[i],"-p") && !lastarg) { + config.hostport = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-s") && !lastarg) { + config.hostsocket = argv[++i]; + } else if (!strcmp(argv[i],"-r") && !lastarg) { + config.repeat = strtoll(argv[++i],NULL,10); + } else if (!strcmp(argv[i],"-i") && !lastarg) { + double seconds = atof(argv[++i]); + config.interval = seconds*1000000; + } else if (!strcmp(argv[i],"-n") && !lastarg) { + config.dbnum = atoi(argv[++i]); + } else if (!strcmp(argv[i],"-a") && !lastarg) { + config.auth = argv[++i]; + } else if (!strcmp(argv[i],"--raw")) { + config.output = OUTPUT_RAW; + } else if (!strcmp(argv[i],"--csv")) { + config.output = OUTPUT_CSV; + } else if (!strcmp(argv[i],"--latency")) { + config.latency_mode = 1; + } else if (!strcmp(argv[i],"--slave")) { + config.slave_mode = 1; + } else if (!strcmp(argv[i],"--rdb") && !lastarg) { + config.getrdb_mode = 1; + config.rdb_filename = argv[++i]; + } else if (!strcmp(argv[i],"--pipe")) { + config.pipe_mode = 1; + } else if (!strcmp(argv[i],"--bigkeys")) { + config.bigkeys = 1; + } else if (!strcmp(argv[i],"--eval") && !lastarg) { + config.eval = argv[++i]; + } else if (!strcmp(argv[i],"-c")) { + config.cluster_mode = 1; + } else if (!strcmp(argv[i],"-d") && !lastarg) { + sdsfree(config.mb_delim); + config.mb_delim = sdsnew(argv[++i]); + } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { + sds version = cliVersion(); + printf("redis-cli %s\n", version); + sdsfree(version); + exit(0); + } else { + break; + } + } + return i; +} + +static sds readArgFromStdin(void) { + char buf[1024]; + sds arg = sdsempty(); + + while(1) { + int nread = read(fileno(stdin),buf,1024); + + if (nread == 0) break; + else if (nread == -1) { + perror("Reading from standard input"); + exit(1); + } + arg = sdscatlen(arg,buf,nread); + } + return arg; +} + +static void usage() { + sds version = cliVersion(); + fprintf(stderr, +"redis-cli %s\n" +"\n" +"Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" +" -h Server hostname (default: 127.0.0.1)\n" +" -p Server port (default: 6379)\n" +" -s Server socket (overrides hostname and port)\n" +" -a Password to use when connecting to the server\n" +" -r Execute specified command N times\n" +" -i When -r is used, waits seconds per command.\n" +" It is possible to specify sub-second times like -i 0.1\n" +" -n Database number\n" +" -x Read last argument from STDIN\n" +" -d Multi-bulk delimiter in for raw formatting (default: \\n)\n" +" -c Enable cluster mode (follow -ASK and -MOVED redirections)\n" +" --raw Use raw formatting for replies (default when STDOUT is not a tty)\n" +" --latency Enter a special mode continuously sampling latency\n" +" --slave Simulate a slave showing commands received from the master\n" +" --rdb Transfer an RDB dump from remote server to local file.\n" +" --pipe Transfer raw Redis protocol from stdin to server\n" +" --bigkeys Sample Redis keys looking for big keys\n" +" --eval Send an EVAL command using the Lua script at \n" +" --help Output this help and exit\n" +" --version Output version and exit\n" +"\n" +"Examples:\n" +" cat /etc/passwd | redis-cli -x set mypasswd\n" +" redis-cli get mypasswd\n" +" redis-cli -r 100 lpush mylist x\n" +" redis-cli -r 100 -i 1 info | grep used_memory_human:\n" +" redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n" +" (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n" +"\n" +"When no command is given, redis-cli starts in interactive mode.\n" +"Type \"help\" in interactive mode for information on available commands.\n" +"\n", + version); + sdsfree(version); + exit(1); +} + +/* Turn the plain C strings into Sds strings */ +static char **convertToSds(int count, char** args) { + int j; + char **sds = zmalloc(sizeof(char*)*count); + + for(j = 0; j < count; j++) + sds[j] = sdsnew(args[j]); + + return sds; +} + +#define LINE_BUFLEN 4096 +static void repl() { + sds historyfile = NULL; + int history = 0; + char *line; + int argc; + sds *argv; + + config.interactive = 1; + linenoiseSetCompletionCallback(completionCallback); + + /* Only use history when stdin is a tty. */ + if (isatty(fileno(stdin))) { + history = 1; + + if (getenv("HOME") != NULL) { + historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME")); + linenoiseHistoryLoad(historyfile); + } + } + + cliRefreshPrompt(); + while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) { + if (line[0] != '\0') { + argv = sdssplitargs(line,&argc); + if (history) linenoiseHistoryAdd(line); + if (historyfile) linenoiseHistorySave(historyfile); + + if (argv == NULL) { + printf("Invalid argument(s)\n"); + free(line); + continue; + } else if (argc > 0) { + if (strcasecmp(argv[0],"quit") == 0 || + strcasecmp(argv[0],"exit") == 0) + { + exit(0); + } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { + sdsfree(config.hostip); + config.hostip = sdsnew(argv[1]); + config.hostport = atoi(argv[2]); + cliConnect(1); + } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { + linenoiseClearScreen(); + } else { + long long start_time = mstime(), elapsed; + int repeat, skipargs = 0; + + repeat = atoi(argv[0]); + if (argc > 1 && repeat) { + skipargs = 1; + } else { + repeat = 1; + } + + while (1) { + config.cluster_reissue_command = 0; + if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) + != REDIS_OK) + { + cliConnect(1); + + /* If we still cannot send the command print error. + * We'll try to reconnect the next time. */ + if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) + != REDIS_OK) + cliPrintContextError(); + } + /* Issue the command again if we got redirected in cluster mode */ + if (config.cluster_mode && config.cluster_reissue_command) { + cliConnect(1); + } else { + break; + } + } + elapsed = mstime()-start_time; + if (elapsed >= 500) { + printf("(%.2fs)\n",(double)elapsed/1000); + } + } + } + /* Free the argument vector */ + while(argc--) sdsfree(argv[argc]); + zfree(argv); + } + /* linenoise() returns malloc-ed lines like readline() */ + free(line); + } + exit(0); +} + +static int noninteractive(int argc, char **argv) { + int retval = 0; + if (config.stdinarg) { + argv = zrealloc(argv, (argc+1)*sizeof(char*)); + argv[argc] = readArgFromStdin(); + retval = cliSendCommand(argc+1, argv, config.repeat); + } else { + /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ + retval = cliSendCommand(argc, argv, config.repeat); + } + return retval; +} + +static int evalMode(int argc, char **argv) { + sds script = sdsempty(); + FILE *fp; + char buf[1024]; + size_t nread; + char **argv2; + int j, got_comma = 0, keys = 0; + + /* Load the script from the file, as an sds string. */ + fp = fopen(config.eval,"r"); + if (!fp) { + fprintf(stderr, + "Can't open file '%s': %s\n", config.eval, strerror(errno)); + exit(1); + } + while((nread = fread(buf,1,sizeof(buf),fp)) != 0) { + script = sdscatlen(script,buf,nread); + } + fclose(fp); + + /* Create our argument vector */ + argv2 = zmalloc(sizeof(sds)*(argc+3)); + argv2[0] = sdsnew("EVAL"); + argv2[1] = script; + for (j = 0; j < argc; j++) { + if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) { + got_comma = 1; + continue; + } + argv2[j+3-got_comma] = sdsnew(argv[j]); + if (!got_comma) keys++; + } + argv2[2] = sdscatprintf(sdsempty(),"%d",keys); + + /* Call it */ + return cliSendCommand(argc+3-got_comma, argv2, config.repeat); +} + +static void latencyMode(void) { + redisReply *reply; + long long start, latency, min = 0, max = 0, tot = 0, count = 0; + double avg; + + if (!context) exit(1); + while(1) { + start = mstime(); + reply = redisCommand(context,"PING"); + if (reply == NULL) { + fprintf(stderr,"\nI/O error\n"); + exit(1); + } + latency = mstime()-start; + freeReplyObject(reply); + count++; + if (count == 1) { + min = max = tot = latency; + avg = (double) latency; + } else { + if (latency < min) min = latency; + if (latency > max) max = latency; + tot += latency; + avg = (double) tot/count; + } + printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)", + min, max, avg, count); + fflush(stdout); + usleep(10000); + } +} + +/* Sends SYNC and reads the number of bytes in the payload. Used both by + * slaveMode() and getRDB(). */ +unsigned long long sendSync(int fd) { + /* To start we need to send the SYNC command and return the payload. + * The hiredis client lib does not understand this part of the protocol + * and we don't want to mess with its buffers, so everything is performed + * using direct low-level I/O. */ + char buf[4096], *p; + ssize_t nread; + + /* Send the SYNC command. */ + if (write(fd,"SYNC\r\n",6) != 6) { + fprintf(stderr,"Error writing to master\n"); + exit(1); + } + + /* Read $\r\n, making sure to read just up to "\n" */ + p = buf; + while(1) { + nread = read(fd,p,1); + if (nread <= 0) { + fprintf(stderr,"Error reading bulk length while SYNCing\n"); + exit(1); + } + if (*p == '\n') break; + p++; + } + *p = '\0'; + if (buf[0] == '-') { + printf("SYNC with master failed: %s\n", buf); + exit(1); + } + return strtoull(buf+1,NULL,10); +} + +static void slaveMode(void) { + int fd = context->fd; + unsigned long long payload = sendSync(fd); + char buf[1024]; + + fprintf(stderr,"SYNC with master, discarding %llu " + "bytes of bulk tranfer...\n", payload); + + /* Discard the payload. */ + while(payload) { + ssize_t nread; + + nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); + if (nread <= 0) { + fprintf(stderr,"Error reading RDB payload while SYNCing\n"); + exit(1); + } + payload -= nread; + } + fprintf(stderr,"SYNC done. Logging commands from master.\n"); + + /* Now we can use hiredis to read the incoming protocol. */ + config.output = OUTPUT_CSV; + while (cliReadReply(0) == REDIS_OK); +} + +/* This function implements --rdb, so it uses the replication protocol in order + * to fetch the RDB file from a remote server. */ +static void getRDB(void) { + int s = context->fd; + int fd; + unsigned long long payload = sendSync(s); + char buf[4096]; + + fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n", + payload, config.rdb_filename); + + /* Write to file. */ + if (!strcmp(config.rdb_filename,"-")) { + fd = STDOUT_FILENO; + } else { + fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644); + if (fd == -1) { + fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename, + strerror(errno)); + exit(1); + } + } + + while(payload) { + ssize_t nread, nwritten; + + nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); + if (nread <= 0) { + fprintf(stderr,"I/O Error reading RDB payload from socket\n"); + exit(1); + } + nwritten = write(fd, buf, nread); + if (nwritten != nread) { + fprintf(stderr,"Error writing data to file: %s\n", + strerror(errno)); + exit(1); + } + payload -= nread; + } + close(s); /* Close the file descriptor ASAP as fsync() may take time. */ + fsync(fd); + fprintf(stderr,"Transfer finished with success.\n"); + exit(0); +} + +static void pipeMode(void) { + int fd = context->fd; + long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0; + char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */ + char aneterr[ANET_ERR_LEN]; + redisReader *reader = redisReaderCreate(); + redisReply *reply; + int eof = 0; /* True once we consumed all the standard input. */ + int done = 0; + char magic[20]; /* Special reply we recognize. */ + + srand(time(NULL)); + + /* Use non blocking I/O. */ + if (anetNonBlock(aneterr,fd) == ANET_ERR) { + fprintf(stderr, "Can't set the socket in non blocking mode: %s\n", + aneterr); + exit(1); + } + + /* Transfer raw protocol and read replies from the server at the same + * time. */ + while(!done) { + int mask = AE_READABLE; + + if (!eof || obuf_len != 0) mask |= AE_WRITABLE; + mask = aeWait(fd,mask,1000); + + /* Handle the readable state: we can read replies from the server. */ + if (mask & AE_READABLE) { + ssize_t nread; + + /* Read from socket and feed the hiredis reader. */ + do { + nread = read(fd,ibuf,sizeof(ibuf)); + if (nread == -1 && errno != EAGAIN && errno != EINTR) { + fprintf(stderr, "Error reading from the server: %s\n", + strerror(errno)); + exit(1); + } + if (nread > 0) redisReaderFeed(reader,ibuf,nread); + } while(nread > 0); + + /* Consume replies. */ + do { + if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) { + fprintf(stderr, "Error reading replies from server\n"); + exit(1); + } + if (reply) { + if (reply->type == REDIS_REPLY_ERROR) { + fprintf(stderr,"%s\n", reply->str); + errors++; + } else if (eof && reply->type == REDIS_REPLY_STRING && + reply->len == 20) { + /* Check if this is the reply to our final ECHO + * command. If so everything was received + * from the server. */ + if (memcmp(reply->str,magic,20) == 0) { + printf("Last reply received from server.\n"); + done = 1; + replies--; + } + } + replies++; + freeReplyObject(reply); + } + } while(reply); + } + + /* Handle the writable state: we can send protocol to the server. */ + if (mask & AE_WRITABLE) { + while(1) { + /* Transfer current buffer to server. */ + if (obuf_len != 0) { + ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len); + + if (nwritten == -1) { + if (errno != EAGAIN && errno != EINTR) { + fprintf(stderr, "Error writing to the server: %s\n", + strerror(errno)); + exit(1); + } else { + nwritten = 0; + } + } + obuf_len -= nwritten; + obuf_pos += nwritten; + if (obuf_len != 0) break; /* Can't accept more data. */ + } + /* If buffer is empty, load from stdin. */ + if (obuf_len == 0 && !eof) { + ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf)); + + if (nread == 0) { + char echo[] = + "*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n"; + int j; + + eof = 1; + /* Everything transfered, so we queue a special + * ECHO command that we can match in the replies + * to make sure everything was read from the server. */ + for (j = 0; j < 20; j++) + magic[j] = rand() & 0xff; + memcpy(echo+19,magic,20); + memcpy(obuf,echo,sizeof(echo)-1); + obuf_len = sizeof(echo)-1; + obuf_pos = 0; + printf("All data transferred. Waiting for the last reply...\n"); + } else if (nread == -1) { + fprintf(stderr, "Error reading from stdin: %s\n", + strerror(errno)); + exit(1); + } else { + obuf_len = nread; + obuf_pos = 0; + } + } + if (obuf_len == 0 && eof) break; + } + } + } + redisReaderFree(reader); + printf("errors: %lld, replies: %lld\n", errors, replies); + if (errors) + exit(1); + else + exit(0); +} + +#define TYPE_STRING 0 +#define TYPE_LIST 1 +#define TYPE_SET 2 +#define TYPE_HASH 3 +#define TYPE_ZSET 4 + +static void findBigKeys(void) { + unsigned long long biggest[5] = {0,0,0,0,0}; + unsigned long long samples = 0; + redisReply *reply1, *reply2, *reply3 = NULL; + char *sizecmd, *typename[] = {"string","list","set","hash","zset"}; + int type; + + printf("\n# Press ctrl+c when you have had enough of it... :)\n"); + printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n"); + printf("# in order to reduce server load (usually not needed).\n\n"); + while(1) { + /* Sample with RANDOMKEY */ + reply1 = redisCommand(context,"RANDOMKEY"); + if (reply1 == NULL) { + fprintf(stderr,"\nI/O error\n"); + exit(1); + } else if (reply1->type == REDIS_REPLY_ERROR) { + fprintf(stderr, "RANDOMKEY error: %s\n", + reply1->str); + exit(1); + } + /* Get the key type */ + reply2 = redisCommand(context,"TYPE %s",reply1->str); + assert(reply2 && reply2->type == REDIS_REPLY_STATUS); + samples++; + + /* Get the key "size" */ + if (!strcmp(reply2->str,"string")) { + sizecmd = "STRLEN"; + type = TYPE_STRING; + } else if (!strcmp(reply2->str,"list")) { + sizecmd = "LLEN"; + type = TYPE_LIST; + } else if (!strcmp(reply2->str,"set")) { + sizecmd = "SCARD"; + type = TYPE_SET; + } else if (!strcmp(reply2->str,"hash")) { + sizecmd = "HLEN"; + type = TYPE_HASH; + } else if (!strcmp(reply2->str,"zset")) { + sizecmd = "ZCARD"; + type = TYPE_ZSET; + } else if (!strcmp(reply2->str,"none")) { + freeReplyObject(reply1); + freeReplyObject(reply2); + freeReplyObject(reply3); + continue; + } else { + fprintf(stderr, "Unknown key type '%s' for key '%s'\n", + reply2->str, reply1->str); + exit(1); + } + + reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str); + if (reply3 && reply3->type == REDIS_REPLY_INTEGER) { + if (biggest[type] < reply3->integer) { + printf("[%6s] %s | biggest so far with size %llu\n", + typename[type], reply1->str, + (unsigned long long) reply3->integer); + biggest[type] = reply3->integer; + } + } + + if ((samples % 1000000) == 0) + printf("(%llu keys sampled)\n", samples); + + if ((samples % 100) == 0 && config.interval) + usleep(config.interval); + + freeReplyObject(reply1); + freeReplyObject(reply2); + if (reply3) freeReplyObject(reply3); + } +} + +int main(int argc, char **argv) { + int firstarg; + + config.hostip = sdsnew("127.0.0.1"); + config.hostport = 6379; + config.hostsocket = NULL; + config.repeat = 1; + config.interval = 0; + config.dbnum = 0; + config.interactive = 0; + config.shutdown = 0; + config.monitor_mode = 0; + config.pubsub_mode = 0; + config.latency_mode = 0; + config.cluster_mode = 0; + config.slave_mode = 0; + config.getrdb_mode = 0; + config.rdb_filename = NULL; + config.pipe_mode = 0; + config.bigkeys = 0; + config.stdinarg = 0; + config.auth = NULL; + config.eval = NULL; + if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL)) + config.output = OUTPUT_RAW; + else + config.output = OUTPUT_STANDARD; + config.mb_delim = sdsnew("\n"); + cliInitHelp(); + + firstarg = parseOptions(argc,argv); + argc -= firstarg; + argv += firstarg; + + /* Latency mode */ + if (config.latency_mode) { + cliConnect(0); + latencyMode(); + } + + /* Slave mode */ + if (config.slave_mode) { + cliConnect(0); + slaveMode(); + } + + /* Get RDB mode. */ + if (config.getrdb_mode) { + cliConnect(0); + getRDB(); + } + + /* Pipe mode */ + if (config.pipe_mode) { + if (cliConnect(0) == REDIS_ERR) exit(1); + pipeMode(); + } + + /* Find big keys */ + if (config.bigkeys) { + cliConnect(0); + findBigKeys(); + } + + /* Start interactive mode when no command is provided */ + if (argc == 0 && !config.eval) { + /* Note that in repl mode we don't abort on connection error. + * A new attempt will be performed for every command send. */ + cliConnect(0); + repl(); + } + + /* Otherwise, we have some arguments to execute */ + if (cliConnect(0) != REDIS_OK) exit(1); + if (config.eval) { + return evalMode(argc,argv); + } else { + return noninteractive(argc,convertToSds(argc,argv)); + } +} diff --git a/src/redis.h b/src/redis.h index 983c09d..cb8a747 100644 --- a/src/redis.h +++ b/src/redis.h @@ -383,6 +383,7 @@ typedef struct redisClient { int fd; redisDb *db; int dictid; + robj *name; /* As set by CLIENT SETNAME */ sds querybuf; size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size */ int argc; diff --git a/src/replication.c b/src/replication.c index 720cd4c..726b4f6 100644 --- a/src/replication.c +++ b/src/replication.c @@ -1,807 +1,825 @@ -/* Asynchronous replication implementation. - * - * Copyright (c) 2009-2012, Salvatore Sanfilippo - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of Redis nor the names of its contributors may be used - * to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - - -#include "redis.h" - -#include -#include -#include -#include -#include - -/* ---------------------------------- MASTER -------------------------------- */ - -void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) { - listNode *ln; - listIter li; - int j; - - listRewind(slaves,&li); - while((ln = listNext(&li))) { - redisClient *slave = ln->value; - - /* Don't feed slaves that are still waiting for BGSAVE to start */ - if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) continue; - - /* Feed slaves that are waiting for the initial SYNC (so these commands - * are queued in the output buffer until the intial SYNC completes), - * or are already in sync with the master. */ - if (slave->slaveseldb != dictid) { - robj *selectcmd; - - if (dictid >= 0 && dictid < REDIS_SHARED_SELECT_CMDS) { - selectcmd = shared.select[dictid]; - incrRefCount(selectcmd); - } else { - selectcmd = createObject(REDIS_STRING, - sdscatprintf(sdsempty(),"select %d\r\n",dictid)); - } - addReply(slave,selectcmd); - decrRefCount(selectcmd); - slave->slaveseldb = dictid; - } - addReplyMultiBulkLen(slave,argc); - for (j = 0; j < argc; j++) addReplyBulk(slave,argv[j]); - } -} - -void replicationFeedMonitors(redisClient *c, list *monitors, int dictid, robj **argv, int argc) { - listNode *ln; - listIter li; - int j, port; - sds cmdrepr = sdsnew("+"); - robj *cmdobj; - char ip[32]; - struct timeval tv; - - gettimeofday(&tv,NULL); - cmdrepr = sdscatprintf(cmdrepr,"%ld.%06ld ",(long)tv.tv_sec,(long)tv.tv_usec); - if (c->flags & REDIS_LUA_CLIENT) { - cmdrepr = sdscatprintf(cmdrepr,"[%d lua] ",dictid); - } else if (c->flags & REDIS_UNIX_SOCKET) { - cmdrepr = sdscatprintf(cmdrepr,"[%d unix:%s] ",dictid,server.unixsocket); - } else { - anetPeerToString(c->fd,ip,&port); - cmdrepr = sdscatprintf(cmdrepr,"[%d %s:%d] ",dictid,ip,port); - } - - for (j = 0; j < argc; j++) { - if (argv[j]->encoding == REDIS_ENCODING_INT) { - cmdrepr = sdscatprintf(cmdrepr, "\"%ld\"", (long)argv[j]->ptr); - } else { - cmdrepr = sdscatrepr(cmdrepr,(char*)argv[j]->ptr, - sdslen(argv[j]->ptr)); - } - if (j != argc-1) - cmdrepr = sdscatlen(cmdrepr," ",1); - } - cmdrepr = sdscatlen(cmdrepr,"\r\n",2); - cmdobj = createObject(REDIS_STRING,cmdrepr); - - listRewind(monitors,&li); - while((ln = listNext(&li))) { - redisClient *monitor = ln->value; - addReply(monitor,cmdobj); - } - decrRefCount(cmdobj); -} - -void syncCommand(redisClient *c) { - /* ignore SYNC if aleady slave or in monitor mode */ - if (c->flags & REDIS_SLAVE) return; - - /* Refuse SYNC requests if we are a slave but the link with our master - * is not ok... */ - if (server.masterhost && server.repl_state != REDIS_REPL_CONNECTED) { - addReplyError(c,"Can't SYNC while not connected with my master"); - return; - } - - /* SYNC can't be issued when the server has pending data to send to - * the client about already issued commands. We need a fresh reply - * buffer registering the differences between the BGSAVE and the current - * dataset, so that we can copy to other slaves if needed. */ - if (listLength(c->reply) != 0) { - addReplyError(c,"SYNC is invalid with pending input"); - return; - } - - redisLog(REDIS_NOTICE,"Slave ask for synchronization"); - /* Here we need to check if there is a background saving operation - * in progress, or if it is required to start one */ - if (server.rdb_child_pid != -1) { - /* Ok a background save is in progress. Let's check if it is a good - * one for replication, i.e. if there is another slave that is - * registering differences since the server forked to save */ - redisClient *slave; - listNode *ln; - listIter li; - - listRewind(server.slaves,&li); - while((ln = listNext(&li))) { - slave = ln->value; - if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) break; - } - if (ln) { - /* Perfect, the server is already registering differences for - * another slave. Set the right state, and copy the buffer. */ - copyClientOutputBuffer(c,slave); - c->replstate = REDIS_REPL_WAIT_BGSAVE_END; - redisLog(REDIS_NOTICE,"Waiting for end of BGSAVE for SYNC"); - } else { - /* No way, we need to wait for the next BGSAVE in order to - * register differences */ - c->replstate = REDIS_REPL_WAIT_BGSAVE_START; - redisLog(REDIS_NOTICE,"Waiting for next BGSAVE for SYNC"); - } - } else { - /* Ok we don't have a BGSAVE in progress, let's start one */ - redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC"); - if (rdbSaveBackground(server.rdb_filename) != REDIS_OK) { - redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE"); - addReplyError(c,"Unable to perform background save"); - return; - } - c->replstate = REDIS_REPL_WAIT_BGSAVE_END; - } - c->repldbfd = -1; - c->flags |= REDIS_SLAVE; - c->slaveseldb = 0; - listAddNodeTail(server.slaves,c); - return; -} - -/* REPLCONF