2010-06-21 18:07:48 -04:00
/*
* Copyright ( c ) 2009 - 2010 , Salvatore Sanfilippo < antirez at gmail dot com >
* 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"
# ifdef HAVE_BACKTRACE
# include <execinfo.h>
# include <ucontext.h>
# endif /* HAVE_BACKTRACE */
# include <time.h>
# include <signal.h>
# include <sys/wait.h>
# include <errno.h>
# include <assert.h>
# include <ctype.h>
# include <stdarg.h>
# include <arpa/inet.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <sys/time.h>
# include <sys/resource.h>
# include <sys/uio.h>
# include <limits.h>
# include <float.h>
# include <math.h>
# include <pthread.h>
2010-09-16 07:28:58 -04:00
# include <sys/resource.h>
2010-06-21 18:07:48 -04:00
/* Our shared "common" objects */
struct sharedObjectsStruct shared ;
/* Global vars that are actally 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 ;
struct redisCommand readonlyCommandTable [ ] = {
2010-10-17 11:31:40 -04:00
{ " get " , getCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " set " , setCommand , 3 , REDIS_CMD_DENYOOM , NULL , 0 , 0 , 0 } ,
{ " setnx " , setnxCommand , 3 , REDIS_CMD_DENYOOM , NULL , 0 , 0 , 0 } ,
{ " setex " , setexCommand , 4 , REDIS_CMD_DENYOOM , NULL , 0 , 0 , 0 } ,
{ " append " , appendCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " substr " , substrCommand , 4 , 0 , NULL , 1 , 1 , 1 } ,
{ " strlen " , strlenCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " del " , delCommand , - 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " exists " , existsCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " incr " , incrCommand , 2 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " decr " , decrCommand , 2 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " mget " , mgetCommand , - 2 , 0 , NULL , 1 , - 1 , 1 } ,
{ " rpush " , rpushCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " lpush " , lpushCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " rpushx " , rpushxCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " lpushx " , lpushxCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " linsert " , linsertCommand , 5 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " rpop " , rpopCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " lpop " , lpopCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " brpop " , brpopCommand , - 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " blpop " , blpopCommand , - 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " llen " , llenCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " lindex " , lindexCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " lset " , lsetCommand , 4 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " lrange " , lrangeCommand , 4 , 0 , NULL , 1 , 1 , 1 } ,
{ " ltrim " , ltrimCommand , 4 , 0 , NULL , 1 , 1 , 1 } ,
{ " lrem " , lremCommand , 4 , 0 , NULL , 1 , 1 , 1 } ,
{ " rpoplpush " , rpoplpushcommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 2 , 1 } ,
{ " sadd " , saddCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " srem " , sremCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " smove " , smoveCommand , 4 , 0 , NULL , 1 , 2 , 1 } ,
{ " sismember " , sismemberCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " scard " , scardCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " spop " , spopCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " srandmember " , srandmemberCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " sinter " , sinterCommand , - 2 , REDIS_CMD_DENYOOM , NULL , 1 , - 1 , 1 } ,
{ " sinterstore " , sinterstoreCommand , - 3 , REDIS_CMD_DENYOOM , NULL , 2 , - 1 , 1 } ,
{ " sunion " , sunionCommand , - 2 , REDIS_CMD_DENYOOM , NULL , 1 , - 1 , 1 } ,
{ " sunionstore " , sunionstoreCommand , - 3 , REDIS_CMD_DENYOOM , NULL , 2 , - 1 , 1 } ,
{ " sdiff " , sdiffCommand , - 2 , REDIS_CMD_DENYOOM , NULL , 1 , - 1 , 1 } ,
{ " sdiffstore " , sdiffstoreCommand , - 3 , REDIS_CMD_DENYOOM , NULL , 2 , - 1 , 1 } ,
{ " smembers " , sinterCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " zadd " , zaddCommand , 4 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " zincrby " , zincrbyCommand , 4 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " zrem " , zremCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " zremrangebyscore " , zremrangebyscoreCommand , 4 , 0 , NULL , 1 , 1 , 1 } ,
{ " zremrangebyrank " , zremrangebyrankCommand , 4 , 0 , NULL , 1 , 1 , 1 } ,
{ " zunionstore " , zunionstoreCommand , - 4 , REDIS_CMD_DENYOOM , zunionInterBlockClientOnSwappedKeys , 0 , 0 , 0 } ,
{ " zinterstore " , zinterstoreCommand , - 4 , REDIS_CMD_DENYOOM , zunionInterBlockClientOnSwappedKeys , 0 , 0 , 0 } ,
{ " zrange " , zrangeCommand , - 4 , 0 , NULL , 1 , 1 , 1 } ,
{ " zrangebyscore " , zrangebyscoreCommand , - 4 , 0 , NULL , 1 , 1 , 1 } ,
2010-10-28 16:59:47 -04:00
{ " zrevrangebyscore " , zrevrangebyscoreCommand , - 4 , 0 , NULL , 1 , 1 , 1 } ,
2010-10-17 11:31:40 -04:00
{ " zcount " , zcountCommand , 4 , 0 , NULL , 1 , 1 , 1 } ,
{ " zrevrange " , zrevrangeCommand , - 4 , 0 , NULL , 1 , 1 , 1 } ,
{ " zcard " , zcardCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " zscore " , zscoreCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " zrank " , zrankCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " zrevrank " , zrevrankCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " hset " , hsetCommand , 4 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " hsetnx " , hsetnxCommand , 4 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " hget " , hgetCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " hmset " , hmsetCommand , - 4 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " hmget " , hmgetCommand , - 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " hincrby " , hincrbyCommand , 4 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " hdel " , hdelCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " hlen " , hlenCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " hkeys " , hkeysCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " hvals " , hvalsCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " hgetall " , hgetallCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " hexists " , hexistsCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " incrby " , incrbyCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " decrby " , decrbyCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " getset " , getsetCommand , 3 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " mset " , msetCommand , - 3 , REDIS_CMD_DENYOOM , NULL , 1 , - 1 , 2 } ,
{ " msetnx " , msetnxCommand , - 3 , REDIS_CMD_DENYOOM , NULL , 1 , - 1 , 2 } ,
{ " randomkey " , randomkeyCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " select " , selectCommand , 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " move " , moveCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " rename " , renameCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " renamenx " , renamenxCommand , 3 , 0 , NULL , 1 , 1 , 1 } ,
{ " expire " , expireCommand , 3 , 0 , NULL , 0 , 0 , 0 } ,
{ " expireat " , expireatCommand , 3 , 0 , NULL , 0 , 0 , 0 } ,
{ " keys " , keysCommand , 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " dbsize " , dbsizeCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " auth " , authCommand , 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " ping " , pingCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " echo " , echoCommand , 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " save " , saveCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " bgsave " , bgsaveCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " bgrewriteaof " , bgrewriteaofCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " shutdown " , shutdownCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " lastsave " , lastsaveCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " type " , typeCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " multi " , multiCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " exec " , execCommand , 1 , REDIS_CMD_DENYOOM , execBlockClientOnSwappedKeys , 0 , 0 , 0 } ,
{ " discard " , discardCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " sync " , syncCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " flushdb " , flushdbCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " flushall " , flushallCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " sort " , sortCommand , - 2 , REDIS_CMD_DENYOOM , NULL , 1 , 1 , 1 } ,
{ " info " , infoCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " monitor " , monitorCommand , 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " ttl " , ttlCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " persist " , persistCommand , 2 , 0 , NULL , 1 , 1 , 1 } ,
{ " slaveof " , slaveofCommand , 3 , 0 , NULL , 0 , 0 , 0 } ,
{ " debug " , debugCommand , - 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " config " , configCommand , - 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " subscribe " , subscribeCommand , - 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " unsubscribe " , unsubscribeCommand , - 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " psubscribe " , psubscribeCommand , - 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " punsubscribe " , punsubscribeCommand , - 1 , 0 , NULL , 0 , 0 , 0 } ,
{ " publish " , publishCommand , 3 , REDIS_CMD_FORCE_REPLICATION , NULL , 0 , 0 , 0 } ,
{ " watch " , watchCommand , - 2 , 0 , NULL , 0 , 0 , 0 } ,
{ " unwatch " , unwatchCommand , 1 , 0 , NULL , 0 , 0 , 0 }
2010-06-21 18:07:48 -04:00
} ;
/*============================ Utility functions ============================ */
void redisLog ( int level , const char * fmt , . . . ) {
va_list ap ;
FILE * fp ;
2010-07-22 17:31:40 -04:00
char * c = " .-*# " ;
char buf [ 64 ] ;
time_t now ;
if ( level < server . verbosity ) return ;
2010-06-21 18:07:48 -04:00
fp = ( server . logfile = = NULL ) ? stdout : fopen ( server . logfile , " a " ) ;
if ( ! fp ) return ;
va_start ( ap , fmt ) ;
2010-07-22 17:31:40 -04:00
now = time ( NULL ) ;
strftime ( buf , 64 , " %d %b %H:%M:%S " , localtime ( & now ) ) ;
fprintf ( fp , " [%d] %s %c " , ( int ) getpid ( ) , buf , c [ level ] ) ;
vfprintf ( fp , fmt , ap ) ;
fprintf ( fp , " \n " ) ;
fflush ( fp ) ;
2010-06-21 18:07:48 -04:00
va_end ( ap ) ;
if ( server . logfile ) fclose ( fp ) ;
}
/* Redis generally does not try to recover from out of memory conditions
* when allocating objects or strings , it is not clear if it will be possible
* to report this condition to the client since the networking layer itself
* is based on heap allocation for send buffers , so we simply abort .
* At least the code will be simpler to read . . . */
void oom ( const char * msg ) {
redisLog ( REDIS_WARNING , " %s: Out of memory \n " , msg ) ;
sleep ( 1 ) ;
abort ( ) ;
}
/*====================== 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 ;
}
2010-11-03 06:23:59 -04:00
/* A case insensitive version used for the command lookup table. */
int dictSdsKeyCaseCompare ( void * privdata , const void * key1 ,
const void * key2 )
{
DICT_NOTUSED ( privdata ) ;
return strcasecmp ( key1 , key2 ) = = 0 ;
}
2010-06-21 18:07:48 -04:00
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 ) ) ;
}
2010-11-03 06:23:59 -04:00
unsigned int dictSdsCaseHash ( const void * key ) {
return dictGenCaseHashFunction ( ( unsigned char * ) key , sdslen ( ( char * ) key ) ) ;
}
2010-06-21 18:07:48 -04:00
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 */
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 */
2010-08-03 14:49:53 -04:00
NULL /* val destructor */
2010-06-21 18:07:48 -04:00
} ;
/* 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 */
} ;
/* Db->expires */
dictType keyptrDictType = {
dictSdsHash , /* hash function */
NULL , /* key dup */
NULL , /* val dup */
dictSdsKeyCompare , /* key compare */
NULL , /* key destructor */
NULL /* val destructor */
} ;
2010-11-03 06:23:59 -04:00
/* 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 */
} ;
2010-06-21 18:07:48 -04:00
/* 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 + + ) {
if ( dictIsRehashing ( server . db [ j ] . dict ) ) {
dictRehashMilliseconds ( server . db [ j ] . dict , 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 . bgsavechildpid = = - 1 & & server . bgrewritechildpid = = - 1 )
dictEnableResize ( ) ;
else
dictDisableResize ( ) ;
}
/* ======================= Cron: called every 100 ms ======================== */
2010-08-02 12:13:39 -04:00
/* 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 ;
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 {
long num = dictSize ( db - > expires ) ;
time_t now = time ( NULL ) ;
expired = 0 ;
if ( num > REDIS_EXPIRELOOKUPS_PER_CRON )
num = REDIS_EXPIRELOOKUPS_PER_CRON ;
while ( num - - ) {
dictEntry * de ;
time_t t ;
if ( ( de = dictGetRandomKey ( db - > expires ) ) = = NULL ) break ;
t = ( time_t ) dictGetEntryVal ( de ) ;
if ( now > t ) {
sds key = dictGetEntryKey ( de ) ;
robj * keyobj = createStringObject ( key , sdslen ( key ) ) ;
propagateExpire ( db , keyobj ) ;
dbDelete ( db , keyobj ) ;
decrRefCount ( keyobj ) ;
expired + + ;
server . stat_expiredkeys + + ;
}
}
} while ( expired > REDIS_EXPIRELOOKUPS_PER_CRON / 4 ) ;
}
}
2010-10-14 15:22:21 -04:00
void updateLRUClock ( void ) {
server . lruclock = ( time ( NULL ) / REDIS_LRU_CLOCK_RESOLUTION ) &
REDIS_LRU_CLOCK_MAX ;
}
2010-08-02 12:13:39 -04:00
2010-06-21 18:07:48 -04:00
int serverCron ( struct aeEventLoop * eventLoop , long long id , void * clientData ) {
int j , loops = server . cronloops + + ;
REDIS_NOTUSED ( eventLoop ) ;
REDIS_NOTUSED ( id ) ;
REDIS_NOTUSED ( clientData ) ;
/* 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 ) ;
2010-10-14 07:52:58 -04:00
/* We have just 22 bits per object for LRU information.
2010-10-14 15:22:21 -04:00
* 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 .
2010-06-21 18:07:48 -04:00
*
2010-10-14 15:22:21 -04:00
* Note that even if this will wrap after 1.5 years it ' s not a problem ,
2010-10-14 07:52:58 -04:00
* everything will still work but just some object will appear younger
2010-10-14 15:22:21 -04:00
* 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 .
2010-06-21 18:07:48 -04:00
*/
2010-10-14 15:22:21 -04:00
updateLRUClock ( ) ;
2010-06-21 18:07:48 -04:00
/* 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 ( ) = = 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 */
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 ( ! ( loops % 50 ) & & ( 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 . bgsavechildpid = = - 1 & & server . bgrewritechildpid = = - 1 ) {
if ( ! ( loops % 10 ) ) tryResizeHashTables ( ) ;
if ( server . activerehashing ) incrementallyRehash ( ) ;
}
/* Show information about connected clients */
if ( ! ( loops % 50 ) ) {
redisLog ( REDIS_VERBOSE , " %d clients connected (%d slaves), %zu bytes in use " ,
listLength ( server . clients ) - listLength ( server . slaves ) ,
listLength ( server . slaves ) ,
2010-11-02 07:09:59 -04:00
zmalloc_used_memory ( ) ) ;
2010-06-21 18:07:48 -04:00
}
/* Close connections of timedout clients */
if ( ( server . maxidletime & & ! ( loops % 100 ) ) | | server . blpop_blocked_clients )
closeTimedoutClients ( ) ;
/* Check if a background saving or AOF rewrite in progress terminated */
if ( server . bgsavechildpid ! = - 1 | | server . bgrewritechildpid ! = - 1 ) {
int statloc ;
pid_t pid ;
if ( ( pid = wait3 ( & statloc , WNOHANG , NULL ) ) ! = 0 ) {
if ( pid = = server . bgsavechildpid ) {
backgroundSaveDoneHandler ( statloc ) ;
} else {
backgroundRewriteDoneHandler ( statloc ) ;
}
updateDictResizePolicy ( ) ;
}
} else {
/* If there is not a background saving in progress check if
* we have to save now */
time_t now = time ( NULL ) ;
for ( j = 0 ; j < server . saveparamslen ; j + + ) {
struct saveparam * sp = server . saveparams + j ;
if ( server . dirty > = sp - > changes & &
now - server . lastsave > sp - > seconds ) {
redisLog ( REDIS_NOTICE , " %d changes in %d seconds. Saving... " ,
sp - > changes , sp - > seconds ) ;
rdbSaveBackground ( server . dbfilename ) ;
break ;
}
}
}
2010-08-02 12:13:39 -04:00
/* 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 ( ) ;
2010-06-21 18:07:48 -04:00
/* Swap a few keys on disk if we are over the memory limit and VM
* is enbled . Try to free objects from the free list first . */
if ( vmCanSwapOut ( ) ) {
2010-11-02 07:09:59 -04:00
while ( server . vm_enabled & & zmalloc_used_memory ( ) >
server . vm_max_memory )
{
2010-06-21 18:07:48 -04:00
int retval ;
if ( tryFreeOneObjectFromFreelist ( ) = = REDIS_OK ) continue ;
retval = ( server . vm_max_threads = = 0 ) ?
vmSwapOneObjectBlocking ( ) :
vmSwapOneObjectThreaded ( ) ;
if ( retval = = REDIS_ERR & & ! ( loops % 300 ) & &
2010-11-02 07:09:59 -04:00
zmalloc_used_memory ( ) >
2010-06-21 18:07:48 -04:00
( server . vm_max_memory + server . vm_max_memory / 10 ) )
{
redisLog ( REDIS_WARNING , " WARNING: vm-max-memory limit exceeded by more than 10%% but unable to swap more objects out! " ) ;
}
/* Note that when using threade I/O we free just one object,
* because anyway when the I / O thread in charge to swap this
* object out will finish , the handler of completed jobs
* will try to swap more objects if we are still out of memory . */
if ( retval = = REDIS_ERR | | server . vm_max_threads > 0 ) break ;
}
}
/* Check if we should connect to a MASTER */
if ( server . replstate = = REDIS_REPL_CONNECT & & ! ( loops % 10 ) ) {
redisLog ( REDIS_NOTICE , " Connecting to MASTER... " ) ;
if ( syncWithMaster ( ) = = REDIS_OK ) {
redisLog ( REDIS_NOTICE , " MASTER <-> SLAVE sync succeeded " ) ;
if ( server . appendonly ) rewriteAppendOnlyFileBackground ( ) ;
}
}
return 100 ;
}
/* 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 ) ;
/* Awake clients that got all the swapped keys they requested */
if ( server . vm_enabled & & listLength ( server . io_ready_clients ) ) {
listIter li ;
listNode * ln ;
listRewind ( server . io_ready_clients , & li ) ;
while ( ( ln = listNext ( & li ) ) ) {
redisClient * c = ln - > value ;
struct redisCommand * cmd ;
/* Resume the client. */
listDelNode ( server . io_ready_clients , ln ) ;
c - > flags & = ( ~ REDIS_IO_WAIT ) ;
server . vm_blocked_clients - - ;
aeCreateFileEvent ( server . el , c - > fd , AE_READABLE ,
readQueryFromClient , c ) ;
cmd = lookupCommand ( c - > argv [ 0 ] - > ptr ) ;
redisAssert ( cmd ! = NULL ) ;
call ( c , cmd ) ;
resetClient ( c ) ;
/* There may be more data to process in the input buffer. */
if ( c - > querybuf & & sdslen ( c - > querybuf ) > 0 )
processInputBuffer ( c ) ;
}
}
/* Write the AOF buffer on disk */
flushAppendOnlyFile ( ) ;
}
/* =========================== 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 . space = createObject ( REDIS_STRING , sdsnew ( " " ) ) ;
shared . colon = createObject ( REDIS_STRING , sdsnew ( " : " ) ) ;
shared . plus = createObject ( REDIS_STRING , sdsnew ( " + " ) ) ;
shared . select0 = createStringObject ( " select 0 \r \n " , 10 ) ;
shared . select1 = createStringObject ( " select 1 \r \n " , 10 ) ;
shared . select2 = createStringObject ( " select 2 \r \n " , 10 ) ;
shared . select3 = createStringObject ( " select 3 \r \n " , 10 ) ;
shared . select4 = createStringObject ( " select 4 \r \n " , 10 ) ;
shared . select5 = createStringObject ( " select 5 \r \n " , 10 ) ;
shared . select6 = createStringObject ( " select 6 \r \n " , 10 ) ;
shared . select7 = createStringObject ( " select 7 \r \n " , 10 ) ;
shared . select8 = createStringObject ( " select 8 \r \n " , 10 ) ;
shared . select9 = createStringObject ( " select 9 \r \n " , 10 ) ;
shared . messagebulk = createStringObject ( " $7 \r \n message \r \n " , 13 ) ;
shared . pmessagebulk = createStringObject ( " $8 \r \n pmessage \r \n " , 14 ) ;
shared . subscribebulk = createStringObject ( " $9 \r \n subscribe \r \n " , 15 ) ;
shared . unsubscribebulk = createStringObject ( " $11 \r \n unsubscribe \r \n " , 18 ) ;
shared . psubscribebulk = createStringObject ( " $10 \r \n psubscribe \r \n " , 17 ) ;
shared . punsubscribebulk = createStringObject ( " $12 \r \n punsubscribe \r \n " , 19 ) ;
shared . mbulk3 = createStringObject ( " *3 \r \n " , 4 ) ;
shared . mbulk4 = createStringObject ( " *4 \r \n " , 4 ) ;
for ( j = 0 ; j < REDIS_SHARED_INTEGERS ; j + + ) {
shared . integers [ j ] = createObject ( REDIS_STRING , ( void * ) ( long ) j ) ;
shared . integers [ j ] - > encoding = REDIS_ENCODING_INT ;
}
}
void initServerConfig ( ) {
server . port = REDIS_SERVERPORT ;
2010-08-03 07:33:12 -04:00
server . bindaddr = NULL ;
2010-10-13 11:17:56 -04:00
server . unixsocket = NULL ;
2010-08-03 07:33:12 -04:00
server . ipfd = - 1 ;
server . sofd = - 1 ;
server . dbnum = REDIS_DEFAULT_DBNUM ;
2010-06-21 18:07:48 -04:00
server . verbosity = REDIS_VERBOSE ;
server . maxidletime = REDIS_MAXIDLETIME ;
server . saveparams = NULL ;
server . logfile = NULL ; /* NULL = log on standard output */
server . glueoutputbuf = 1 ;
server . daemonize = 0 ;
server . appendonly = 0 ;
server . appendfsync = APPENDFSYNC_EVERYSEC ;
server . no_appendfsync_on_rewrite = 0 ;
server . lastfsync = time ( NULL ) ;
server . appendfd = - 1 ;
server . appendseldb = - 1 ; /* Make sure the first time will not match */
server . pidfile = zstrdup ( " /var/run/redis.pid " ) ;
server . dbfilename = zstrdup ( " dump.rdb " ) ;
server . appendfilename = zstrdup ( " appendonly.aof " ) ;
server . requirepass = NULL ;
server . rdbcompression = 1 ;
server . activerehashing = 1 ;
server . maxclients = 0 ;
server . blpop_blocked_clients = 0 ;
server . maxmemory = 0 ;
2010-10-14 15:22:21 -04:00
server . maxmemory_policy = REDIS_MAXMEMORY_VOLATILE_LRU ;
server . maxmemory_samples = 3 ;
2010-06-21 18:07:48 -04:00
server . vm_enabled = 0 ;
server . vm_swap_file = zstrdup ( " /tmp/redis-%p.vm " ) ;
server . vm_page_size = 256 ; /* 256 bytes per page */
server . vm_pages = 1024 * 1024 * 100 ; /* 104 millions of pages */
server . vm_max_memory = 1024LL * 1024 * 1024 * 1 ; /* 1 GB of RAM */
server . vm_max_threads = 4 ;
server . vm_blocked_clients = 0 ;
server . hash_max_zipmap_entries = REDIS_HASH_MAX_ZIPMAP_ENTRIES ;
server . hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE ;
server . list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES ;
server . list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE ;
2010-07-02 13:57:12 -04:00
server . set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES ;
2010-06-21 18:07:48 -04:00
server . shutdown_asap = 0 ;
2010-10-15 06:22:48 -04:00
updateLRUClock ( ) ;
2010-06-21 18:07:48 -04:00
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 . isslave = 0 ;
server . masterauth = NULL ;
server . masterhost = NULL ;
server . masterport = 6379 ;
server . master = NULL ;
server . replstate = REDIS_REPL_NONE ;
/* 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 ;
}
void initServer ( ) {
int j ;
signal ( SIGHUP , SIG_IGN ) ;
signal ( SIGPIPE , SIG_IGN ) ;
setupSigSegvAction ( ) ;
2010-07-22 07:08:02 -04:00
server . mainthread = pthread_self ( ) ;
2010-06-21 18:07:48 -04:00
server . devnull = fopen ( " /dev/null " , " w " ) ;
if ( server . devnull = = NULL ) {
redisLog ( REDIS_WARNING , " Can't open /dev/null: %s " , server . neterr ) ;
exit ( 1 ) ;
}
2010-11-03 06:23:59 -04:00
server . commands = dictCreate ( & commandTableDictType , NULL ) ;
populateCommandTable ( ) ;
server . delCommand = lookupCommandByCString ( " del " ) ;
server . multiCommand = lookupCommandByCString ( " multi " ) ;
2010-06-21 18:07:48 -04:00
server . clients = listCreate ( ) ;
server . slaves = listCreate ( ) ;
server . monitors = listCreate ( ) ;
server . objfreelist = listCreate ( ) ;
createSharedObjects ( ) ;
server . el = aeCreateEventLoop ( ) ;
server . db = zmalloc ( sizeof ( redisDb ) * server . dbnum ) ;
2010-10-13 11:18:58 -04:00
server . ipfd = anetTcpServer ( server . neterr , server . port , server . bindaddr ) ;
if ( server . ipfd = = ANET_ERR ) {
redisLog ( REDIS_WARNING , " Opening port: %s " , server . neterr ) ;
exit ( 1 ) ;
2010-08-03 07:33:12 -04:00
}
2010-10-13 11:17:56 -04:00
if ( server . unixsocket ! = NULL ) {
unlink ( server . unixsocket ) ; /* don't care if this fails */
server . sofd = anetUnixServer ( server . neterr , server . unixsocket ) ;
2010-08-03 07:33:12 -04:00
if ( server . sofd = = ANET_ERR ) {
redisLog ( REDIS_WARNING , " Opening socket: %s " , server . neterr ) ;
exit ( 1 ) ;
}
2010-08-01 16:55:24 -04:00
}
2010-08-03 07:33:12 -04:00
if ( server . ipfd < 0 & & server . sofd < 0 ) {
redisLog ( REDIS_WARNING , " Configured to not listen anywhere, exiting. " ) ;
2010-06-21 18:07:48 -04:00
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 ] . watched_keys = dictCreate ( & keylistDictType , NULL ) ;
if ( server . vm_enabled )
server . db [ j ] . io_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 . bgsavechildpid = - 1 ;
server . bgrewritechildpid = - 1 ;
server . bgrewritebuf = sdsempty ( ) ;
server . aofbuf = sdsempty ( ) ;
server . lastsave = time ( NULL ) ;
server . dirty = 0 ;
server . stat_numcommands = 0 ;
server . stat_numconnections = 0 ;
server . stat_expiredkeys = 0 ;
server . stat_starttime = time ( NULL ) ;
2010-10-15 06:19:21 -04:00
server . stat_keyspace_misses = 0 ;
server . stat_keyspace_hits = 0 ;
2010-06-21 18:07:48 -04:00
server . unixtime = time ( NULL ) ;
aeCreateTimeEvent ( server . el , 1 , serverCron , NULL , NULL ) ;
2010-08-03 07:33:12 -04:00
if ( server . ipfd > 0 & & aeCreateFileEvent ( server . el , server . ipfd , AE_READABLE ,
2010-10-13 12:34:24 -04:00
acceptTcpHandler , NULL ) = = AE_ERR ) oom ( " creating file event " ) ;
2010-08-03 07:33:12 -04:00
if ( server . sofd > 0 & & aeCreateFileEvent ( server . el , server . sofd , AE_READABLE ,
2010-10-13 12:34:24 -04:00
acceptUnixHandler , NULL ) = = AE_ERR ) oom ( " creating file event " ) ;
2010-06-21 18:07:48 -04:00
if ( server . appendonly ) {
server . appendfd = open ( server . appendfilename , O_WRONLY | O_APPEND | O_CREAT , 0644 ) ;
if ( server . appendfd = = - 1 ) {
redisLog ( REDIS_WARNING , " Can't open the append-only file: %s " ,
strerror ( errno ) ) ;
exit ( 1 ) ;
}
}
if ( server . vm_enabled ) vmInit ( ) ;
}
2010-11-03 06:23:59 -04:00
/* 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 ( readonlyCommandTable ) / sizeof ( struct redisCommand ) ;
for ( j = 0 ; j < numcommands ; j + + ) {
struct redisCommand * c = readonlyCommandTable + j ;
int retval ;
2010-06-21 18:07:48 -04:00
2010-11-03 06:23:59 -04:00
retval = dictAdd ( server . commands , sdsnew ( c - > name ) , c ) ;
assert ( retval = = DICT_OK ) ;
}
2010-06-21 18:07:48 -04:00
}
/* ====================== Commands lookup and execution ===================== */
2010-11-03 06:23:59 -04:00
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 ;
2010-06-21 18:07:48 -04:00
}
/* Call() is the core of Redis execution of a command */
void call ( redisClient * c , struct redisCommand * cmd ) {
long long dirty ;
dirty = server . dirty ;
cmd - > proc ( c ) ;
dirty = server . dirty - dirty ;
if ( server . appendonly & & dirty )
feedAppendOnlyFile ( cmd , c - > db - > id , c - > argv , c - > argc ) ;
if ( ( dirty | | cmd - > flags & REDIS_CMD_FORCE_REPLICATION ) & &
listLength ( server . slaves ) )
replicationFeedSlaves ( server . slaves , c - > db - > id , c - > argv , c - > argc ) ;
if ( listLength ( server . monitors ) )
replicationFeedMonitors ( server . monitors , c - > db - > id , c - > argv , c - > argc ) ;
server . stat_numcommands + + ;
}
/* If this function gets called we already read a whole
* command , argments 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 ) {
struct redisCommand * cmd ;
2010-10-13 05:25:40 -04:00
/* 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 . */
2010-06-21 18:07:48 -04:00
if ( ! strcasecmp ( c - > argv [ 0 ] - > ptr , " quit " ) ) {
2010-10-13 05:25:40 -04:00
addReply ( c , shared . ok ) ;
2010-10-28 10:07:45 -04:00
c - > flags | = REDIS_CLOSE_AFTER_REPLY ;
2010-10-15 09:40:25 -04:00
return REDIS_ERR ;
2010-06-21 18:07:48 -04:00
}
/* Now lookup the command and check ASAP about trivial error conditions
* such wrong arity , bad command name and so forth . */
cmd = lookupCommand ( c - > argv [ 0 ] - > ptr ) ;
if ( ! cmd ) {
2010-09-02 13:52:24 -04:00
addReplyErrorFormat ( c , " unknown command '%s' " ,
( char * ) c - > argv [ 0 ] - > ptr ) ;
2010-10-15 09:40:25 -04:00
return REDIS_OK ;
2010-06-21 18:07:48 -04:00
} else if ( ( cmd - > arity > 0 & & cmd - > arity ! = c - > argc ) | |
( c - > argc < - cmd - > arity ) ) {
2010-09-02 13:52:24 -04:00
addReplyErrorFormat ( c , " wrong number of arguments for '%s' command " ,
cmd - > name ) ;
2010-10-15 09:40:25 -04:00
return REDIS_OK ;
2010-06-21 18:07:48 -04:00
}
/* Check if the user is authenticated */
if ( server . requirepass & & ! c - > authenticated & & cmd - > proc ! = authCommand ) {
2010-09-02 13:52:24 -04:00
addReplyError ( c , " operation not permitted " ) ;
2010-10-15 09:40:25 -04:00
return REDIS_OK ;
2010-06-21 18:07:48 -04:00
}
2010-10-11 07:05:09 -04:00
/* 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 ) freeMemoryIfNeeded ( ) ;
2010-06-21 18:07:48 -04:00
if ( server . maxmemory & & ( cmd - > flags & REDIS_CMD_DENYOOM ) & &
2010-11-02 07:09:59 -04:00
zmalloc_used_memory ( ) > server . maxmemory )
2010-06-21 18:07:48 -04:00
{
2010-09-02 13:52:24 -04:00
addReplyError ( c , " command not allowed when used memory > 'maxmemory' " ) ;
2010-10-15 09:40:25 -04:00
return REDIS_OK ;
2010-06-21 18:07:48 -04:00
}
/* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */
if ( ( dictSize ( c - > pubsub_channels ) > 0 | | listLength ( c - > pubsub_patterns ) > 0 )
& &
cmd - > proc ! = subscribeCommand & & cmd - > proc ! = unsubscribeCommand & &
cmd - > proc ! = psubscribeCommand & & cmd - > proc ! = punsubscribeCommand ) {
2010-09-02 13:52:24 -04:00
addReplyError ( c , " only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context " ) ;
2010-10-15 09:40:25 -04:00
return REDIS_OK ;
2010-06-21 18:07:48 -04:00
}
/* Exec the command */
if ( c - > flags & REDIS_MULTI & &
cmd - > proc ! = execCommand & & cmd - > proc ! = discardCommand & &
cmd - > proc ! = multiCommand & & cmd - > proc ! = watchCommand )
{
queueMultiCommand ( c , cmd ) ;
addReply ( c , shared . queued ) ;
} else {
if ( server . vm_enabled & & server . vm_max_threads > 0 & &
2010-10-15 11:27:05 -04:00
blockClientOnSwappedKeys ( c , cmd ) ) return REDIS_ERR ;
2010-06-21 18:07:48 -04:00
call ( c , cmd ) ;
}
2010-10-15 09:40:25 -04:00
return REDIS_OK ;
2010-06-21 18:07:48 -04:00
}
/*================================== Shutdown =============================== */
int prepareForShutdown ( ) {
redisLog ( REDIS_WARNING , " User requested shutdown, saving DB... " ) ;
/* 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 . bgsavechildpid ! = - 1 ) {
redisLog ( REDIS_WARNING , " There is a live saving child. Killing it! " ) ;
kill ( server . bgsavechildpid , SIGKILL ) ;
rdbRemoveTempFile ( server . bgsavechildpid ) ;
}
if ( server . appendonly ) {
/* Append only file: fsync() the AOF and exit */
aof_fsync ( server . appendfd ) ;
if ( server . vm_enabled ) unlink ( server . vm_swap_file ) ;
2010-09-30 14:53:34 -04:00
} else if ( server . saveparamslen > 0 ) {
2010-06-21 18:07:48 -04:00
/* Snapshotting. Perform a SYNC SAVE and exit */
2010-08-24 11:09:25 -04:00
if ( rdbSave ( server . dbfilename ) ! = REDIS_OK ) {
2010-06-21 18:07:48 -04:00
/* 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 ;
}
2010-09-30 14:53:34 -04:00
} else {
redisLog ( REDIS_WARNING , " Not saving DB. " ) ;
2010-06-21 18:07:48 -04:00
}
2010-08-24 11:09:25 -04:00
if ( server . daemonize ) unlink ( server . pidfile ) ;
2010-06-21 18:07:48 -04:00
redisLog ( REDIS_WARNING , " Server exit now, bye bye... " ) ;
return REDIS_OK ;
}
/*================================== Commands =============================== */
void authCommand ( redisClient * c ) {
if ( ! server . requirepass | | ! strcmp ( c - > argv [ 1 ] - > ptr , server . requirepass ) ) {
c - > authenticated = 1 ;
addReply ( c , shared . ok ) ;
} else {
c - > authenticated = 0 ;
2010-09-02 13:52:24 -04:00
addReplyError ( c , " invalid password " ) ;
2010-06-21 18:07:48 -04:00
}
}
void pingCommand ( redisClient * c ) {
addReply ( c , shared . pong ) ;
}
void echoCommand ( redisClient * c ) {
addReplyBulk ( c , c - > argv [ 1 ] ) ;
}
/* Convert an amount of bytes into a human readable string in the form
* of 100 B , 2 G , 100 M , 4 K , 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 ( void ) {
sds info ;
time_t uptime = time ( NULL ) - server . stat_starttime ;
int j ;
char hmem [ 64 ] ;
2010-09-16 07:28:58 -04:00
struct rusage self_ru , c_ru ;
getrusage ( RUSAGE_SELF , & self_ru ) ;
getrusage ( RUSAGE_CHILDREN , & c_ru ) ;
2010-06-21 18:07:48 -04:00
2010-11-02 07:09:59 -04:00
bytesToHuman ( hmem , zmalloc_used_memory ( ) ) ;
2010-06-21 18:07:48 -04:00
info = sdscatprintf ( sdsempty ( ) ,
" redis_version:%s \r \n "
" redis_git_sha1:%s \r \n "
" redis_git_dirty:%d \r \n "
" arch_bits:%s \r \n "
" multiplexing_api:%s \r \n "
" process_id:%ld \r \n "
" uptime_in_seconds:%ld \r \n "
" uptime_in_days:%ld \r \n "
2010-10-14 07:52:58 -04:00
" lru_clock:%ld \r \n "
2010-09-16 07:28:58 -04:00
" used_cpu_sys:%.2f \r \n "
" used_cpu_user:%.2f \r \n "
" used_cpu_sys_childrens:%.2f \r \n "
" used_cpu_user_childrens:%.2f \r \n "
2010-06-21 18:07:48 -04:00
" connected_clients:%d \r \n "
" connected_slaves:%d \r \n "
" blocked_clients:%d \r \n "
" used_memory:%zu \r \n "
" used_memory_human:%s \r \n "
2010-11-02 17:47:10 -04:00
" used_memory_rss:%zu \r \n "
2010-09-02 04:34:39 -04:00
" mem_fragmentation_ratio:%.2f \r \n "
2010-10-21 18:10:17 -04:00
" use_tcmalloc:%d \r \n "
2010-06-21 18:07:48 -04:00
" changes_since_last_save:%lld \r \n "
" bgsave_in_progress:%d \r \n "
" last_save_time:%ld \r \n "
" bgrewriteaof_in_progress:%d \r \n "
" total_connections_received:%lld \r \n "
" total_commands_processed:%lld \r \n "
" expired_keys:%lld \r \n "
2010-10-15 06:19:21 -04:00
" keyspace_hits:%lld \r \n "
" keyspace_misses:%lld \r \n "
2010-06-21 18:07:48 -04:00
" hash_max_zipmap_entries:%zu \r \n "
" hash_max_zipmap_value:%zu \r \n "
" pubsub_channels:%ld \r \n "
" pubsub_patterns:%u \r \n "
" vm_enabled:%d \r \n "
" role:%s \r \n "
, REDIS_VERSION ,
redisGitSHA1 ( ) ,
strtol ( redisGitDirty ( ) , NULL , 10 ) > 0 ,
( sizeof ( long ) = = 8 ) ? " 64 " : " 32 " ,
aeGetApiName ( ) ,
( long ) getpid ( ) ,
uptime ,
uptime / ( 3600 * 24 ) ,
2010-10-14 07:52:58 -04:00
( unsigned long ) server . lruclock ,
2010-09-16 07:28:58 -04:00
( float ) self_ru . ru_utime . tv_sec + ( float ) self_ru . ru_utime . tv_usec / 1000000 ,
( float ) self_ru . ru_stime . tv_sec + ( float ) self_ru . ru_stime . tv_usec / 1000000 ,
( float ) c_ru . ru_utime . tv_sec + ( float ) c_ru . ru_utime . tv_usec / 1000000 ,
( float ) c_ru . ru_stime . tv_sec + ( float ) c_ru . ru_stime . tv_usec / 1000000 ,
2010-06-21 18:07:48 -04:00
listLength ( server . clients ) - listLength ( server . slaves ) ,
listLength ( server . slaves ) ,
server . blpop_blocked_clients ,
2010-11-02 06:50:55 -04:00
zmalloc_used_memory ( ) ,
2010-11-02 07:09:59 -04:00
hmem ,
2010-11-02 17:47:10 -04:00
zmalloc_get_rss ( ) ,
2010-09-02 04:34:39 -04:00
zmalloc_get_fragmentation_ratio ( ) ,
2010-10-21 18:10:17 -04:00
# ifdef USE_TCMALLOC
1 ,
# else
0 ,
# endif
2010-06-21 18:07:48 -04:00
server . dirty ,
server . bgsavechildpid ! = - 1 ,
server . lastsave ,
server . bgrewritechildpid ! = - 1 ,
server . stat_numconnections ,
server . stat_numcommands ,
server . stat_expiredkeys ,
2010-10-15 06:19:21 -04:00
server . stat_keyspace_hits ,
server . stat_keyspace_misses ,
2010-06-21 18:07:48 -04:00
server . hash_max_zipmap_entries ,
server . hash_max_zipmap_value ,
dictSize ( server . pubsub_channels ) ,
listLength ( server . pubsub_patterns ) ,
server . vm_enabled ! = 0 ,
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 "
, server . masterhost ,
server . masterport ,
( server . replstate = = REDIS_REPL_CONNECTED ) ?
" up " : " down " ,
server . master ? ( ( int ) ( time ( NULL ) - server . master - > lastinteraction ) ) : - 1
) ;
}
if ( server . vm_enabled ) {
lockThreadedIO ( ) ;
info = sdscatprintf ( info ,
" vm_conf_max_memory:%llu \r \n "
" vm_conf_page_size:%llu \r \n "
" vm_conf_pages:%llu \r \n "
" vm_stats_used_pages:%llu \r \n "
" vm_stats_swapped_objects:%llu \r \n "
" vm_stats_swappin_count:%llu \r \n "
" vm_stats_swappout_count:%llu \r \n "
" vm_stats_io_newjobs_len:%lu \r \n "
" vm_stats_io_processing_len:%lu \r \n "
" vm_stats_io_processed_len:%lu \r \n "
" vm_stats_io_active_threads:%lu \r \n "
" vm_stats_blocked_clients:%lu \r \n "
, ( unsigned long long ) server . vm_max_memory ,
( unsigned long long ) server . vm_page_size ,
( unsigned long long ) server . vm_pages ,
( unsigned long long ) server . vm_stats_used_pages ,
( unsigned long long ) server . vm_stats_swapped_objects ,
( unsigned long long ) server . vm_stats_swapins ,
( unsigned long long ) server . vm_stats_swapouts ,
( unsigned long ) listLength ( server . io_newjobs ) ,
( unsigned long ) listLength ( server . io_processing ) ,
( unsigned long ) listLength ( server . io_processed ) ,
( unsigned long ) server . io_active_threads ,
( unsigned long ) server . vm_blocked_clients
) ;
unlockThreadedIO ( ) ;
}
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 ) {
sds info = genRedisInfoString ( ) ;
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 aleady 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 ======================== */
/* Try to free one object form the pre-allocated objects free list.
* This is useful under low mem conditions as by default we take 1 million
* free objects allocated . On success REDIS_OK is returned , otherwise
* REDIS_ERR . */
int tryFreeOneObjectFromFreelist ( void ) {
robj * o ;
if ( server . vm_enabled ) pthread_mutex_lock ( & server . obj_freelist_mutex ) ;
if ( listLength ( server . objfreelist ) ) {
listNode * head = listFirst ( server . objfreelist ) ;
o = listNodeValue ( head ) ;
listDelNode ( server . objfreelist , head ) ;
if ( server . vm_enabled ) pthread_mutex_unlock ( & server . obj_freelist_mutex ) ;
zfree ( o ) ;
return REDIS_OK ;
} else {
if ( server . vm_enabled ) pthread_mutex_unlock ( & server . obj_freelist_mutex ) ;
return REDIS_ERR ;
}
}
/* This function gets called when 'maxmemory' is set on the config file to limit
* the max memory used by the server , and we are out of memory .
* This function will try to , in order :
*
* - Free objects from the free list
* - Try to remove keys with an EXPIRE set
*
* It is not possible to free enough memory to reach used - memory < maxmemory
* the server will start refusing commands that will enlarge even more the
* memory usage .
*/
void freeMemoryIfNeeded ( void ) {
2010-10-14 15:22:21 -04:00
/* Remove keys accordingly to the active policy as long as we are
* over the memory limit . */
2010-11-02 07:09:59 -04:00
while ( server . maxmemory & & zmalloc_used_memory ( ) > server . maxmemory ) {
2010-06-21 18:07:48 -04:00
int j , k , freed = 0 ;
2010-10-14 15:22:21 -04:00
/* Basic strategy -- remove objects from the free list. */
2010-06-21 18:07:48 -04:00
if ( tryFreeOneObjectFromFreelist ( ) = = REDIS_OK ) continue ;
2010-10-14 15:22:21 -04:00
for ( j = 0 ; j < server . dbnum ; j + + ) {
2010-11-02 06:15:09 -04:00
long bestval = 0 ; /* just to prevent warning */
2010-10-14 15:22:21 -04:00
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 = dictGetEntryKey ( 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 = dictGetEntryKey ( de ) ;
o = dictGetEntryVal ( 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 = dictGetEntryKey ( de ) ;
thisval = ( long ) dictGetEntryVal ( 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 ) {
robj * keyobj = createStringObject ( bestkey , sdslen ( bestkey ) ) ;
dbDelete ( db , keyobj ) ;
server . stat_expiredkeys + + ;
decrRefCount ( keyobj ) ;
freed + + ;
}
}
if ( ! freed ) return ; /* nothing to free... */
}
while ( 0 ) {
int j , k , freed = 0 ;
2010-06-21 18:07:48 -04:00
for ( j = 0 ; j < server . dbnum ; j + + ) {
int minttl = - 1 ;
2010-08-27 05:01:03 -04:00
sds minkey = NULL ;
robj * keyobj = NULL ;
2010-06-21 18:07:48 -04:00
struct dictEntry * de ;
if ( dictSize ( server . db [ j ] . expires ) ) {
freed = 1 ;
/* From a sample of three keys drop the one nearest to
* the natural expire */
for ( k = 0 ; k < 3 ; k + + ) {
time_t t ;
de = dictGetRandomKey ( server . db [ j ] . expires ) ;
t = ( time_t ) dictGetEntryVal ( de ) ;
if ( minttl = = - 1 | | t < minttl ) {
minkey = dictGetEntryKey ( de ) ;
minttl = t ;
}
}
2010-08-27 05:01:03 -04:00
keyobj = createStringObject ( minkey , sdslen ( minkey ) ) ;
dbDelete ( server . db + j , keyobj ) ;
2010-09-15 08:09:41 -04:00
server . stat_expiredkeys + + ;
2010-08-27 05:01:03 -04:00
decrRefCount ( keyobj ) ;
2010-06-21 18:07:48 -04:00
}
}
if ( ! freed ) return ; /* nothing to free... */
}
}
/* =================================== 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__ */
2010-08-24 11:09:25 -04:00
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 " , getpid ( ) ) ;
fclose ( fp ) ;
}
}
2010-06-21 18:07:48 -04:00
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 version %s (%s:%d) \n " , REDIS_VERSION ,
redisGitSHA1 ( ) , atoi ( redisGitDirty ( ) ) > 0 ) ;
exit ( 0 ) ;
}
void usage ( ) {
fprintf ( stderr , " Usage: ./redis-server [/path/to/redis.conf] \n " ) ;
fprintf ( stderr , " ./redis-server - (read config from stdin) \n " ) ;
exit ( 1 ) ;
}
int main ( int argc , char * * argv ) {
time_t start ;
initServerConfig ( ) ;
if ( argc = = 2 ) {
if ( strcmp ( argv [ 1 ] , " -v " ) = = 0 | |
strcmp ( argv [ 1 ] , " --version " ) = = 0 ) version ( ) ;
if ( strcmp ( argv [ 1 ] , " --help " ) = = 0 ) usage ( ) ;
resetServerSaveParams ( ) ;
loadServerConfig ( argv [ 1 ] ) ;
} else if ( ( argc > 2 ) ) {
usage ( ) ;
} else {
redisLog ( REDIS_WARNING , " Warning: no config file specified, using the default config. In order to specify a config file use 'redis-server /path/to/redis.conf' " ) ;
}
if ( server . daemonize ) daemonize ( ) ;
initServer ( ) ;
2010-08-24 11:09:25 -04:00
if ( server . daemonize ) createPidFile ( ) ;
2010-06-21 18:07:48 -04:00
redisLog ( REDIS_NOTICE , " Server started, Redis version " REDIS_VERSION ) ;
# ifdef __linux__
linuxOvercommitMemoryWarning ( ) ;
# endif
start = time ( NULL ) ;
if ( server . appendonly ) {
if ( loadAppendOnlyFile ( server . appendfilename ) = = REDIS_OK )
redisLog ( REDIS_NOTICE , " DB loaded from append only file: %ld seconds " , time ( NULL ) - start ) ;
} else {
if ( rdbLoad ( server . dbfilename ) = = REDIS_OK )
redisLog ( REDIS_NOTICE , " DB loaded from disk: %ld seconds " , time ( NULL ) - start ) ;
}
2010-08-03 07:33:12 -04:00
if ( server . ipfd > 0 )
redisLog ( REDIS_NOTICE , " The server is now ready to accept connections on port %d " , server . port ) ;
if ( server . sofd > 0 )
2010-10-13 11:17:56 -04:00
redisLog ( REDIS_NOTICE , " The server is now ready to accept connections at %s " , server . unixsocket ) ;
2010-06-21 18:07:48 -04:00
aeSetBeforeSleepProc ( server . el , beforeSleep ) ;
aeMain ( server . el ) ;
aeDeleteEventLoop ( server . el ) ;
return 0 ;
}
/* ============================= Backtrace support ========================= */
# ifdef HAVE_BACKTRACE
void * getMcontextEip ( ucontext_t * uc ) {
# if defined(__FreeBSD__)
return ( void * ) uc - > uc_mcontext . mc_eip ;
# elif defined(__dietlibc__)
return ( void * ) uc - > uc_mcontext . eip ;
# elif defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
# if __x86_64__
return ( void * ) uc - > uc_mcontext - > __ss . __rip ;
# else
return ( void * ) uc - > uc_mcontext - > __ss . __eip ;
# endif
# elif defined(__APPLE__) && defined(MAC_OS_X_VERSION_10_6)
# if defined(_STRUCT_X86_THREAD_STATE64) && !defined(__i386__)
return ( void * ) uc - > uc_mcontext - > __ss . __rip ;
# else
return ( void * ) uc - > uc_mcontext - > __ss . __eip ;
# endif
2010-07-01 15:13:38 -04:00
# elif defined(__i386__)
return ( void * ) uc - > uc_mcontext . gregs [ 14 ] ; /* Linux 32 */
# elif defined(__X86_64__) || defined(__x86_64__)
return ( void * ) uc - > uc_mcontext . gregs [ 16 ] ; /* Linux 64 */
2010-06-21 18:07:48 -04:00
# elif defined(__ia64__) /* Linux IA64 */
return ( void * ) uc - > uc_mcontext . sc_ip ;
# else
return NULL ;
# endif
}
void segvHandler ( int sig , siginfo_t * info , void * secret ) {
void * trace [ 100 ] ;
char * * messages = NULL ;
int i , trace_size = 0 ;
ucontext_t * uc = ( ucontext_t * ) secret ;
sds infostring ;
2010-10-22 17:30:48 -04:00
struct sigaction act ;
2010-06-21 18:07:48 -04:00
REDIS_NOTUSED ( info ) ;
redisLog ( REDIS_WARNING ,
" ======= Ooops! Redis %s got signal: -%d- ======= " , REDIS_VERSION , sig ) ;
infostring = genRedisInfoString ( ) ;
redisLog ( REDIS_WARNING , " %s " , infostring ) ;
/* It's not safe to sdsfree() the returned string under memory
* corruption conditions . Let it leak as we are going to abort */
trace_size = backtrace ( trace , 100 ) ;
/* overwrite sigaction with caller's address */
if ( getMcontextEip ( uc ) ! = NULL ) {
trace [ 1 ] = getMcontextEip ( uc ) ;
}
messages = backtrace_symbols ( trace , trace_size ) ;
for ( i = 1 ; i < trace_size ; + + i )
redisLog ( REDIS_WARNING , " %s " , messages [ i ] ) ;
/* free(messages); Don't call free() with possibly corrupted memory. */
2010-08-24 11:09:25 -04:00
if ( server . daemonize ) unlink ( server . pidfile ) ;
2010-10-22 17:30:48 -04:00
/* Make sure we exit with the right signal at the end. So for instance
* the core will be dumped if enabled . */
sigemptyset ( & act . sa_mask ) ;
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction
* is used . Otherwise , sa_handler is used */
act . sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND ;
act . sa_handler = SIG_DFL ;
sigaction ( sig , & act , NULL ) ;
kill ( getpid ( ) , sig ) ;
2010-06-21 18:07:48 -04:00
}
void sigtermHandler ( int sig ) {
REDIS_NOTUSED ( sig ) ;
redisLog ( REDIS_WARNING , " SIGTERM received, scheduling shutting down... " ) ;
server . shutdown_asap = 1 ;
}
void setupSigSegvAction ( void ) {
struct sigaction act ;
sigemptyset ( & act . sa_mask ) ;
/* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction
* is used . Otherwise , sa_handler is used */
act . sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND | SA_SIGINFO ;
act . sa_sigaction = segvHandler ;
sigaction ( SIGSEGV , & act , NULL ) ;
sigaction ( SIGBUS , & act , NULL ) ;
sigaction ( SIGFPE , & act , NULL ) ;
sigaction ( SIGILL , & act , NULL ) ;
sigaction ( SIGBUS , & act , NULL ) ;
act . sa_flags = SA_NODEFER | SA_ONSTACK | SA_RESETHAND ;
act . sa_handler = sigtermHandler ;
sigaction ( SIGTERM , & act , NULL ) ;
return ;
}
# else /* HAVE_BACKTRACE */
void setupSigSegvAction ( void ) {
}
# endif /* HAVE_BACKTRACE */
/* The End */