April 15, 2015

Reading Redis #2

Redis’s src/ directory contains multiple main() functions, because src/Makefile builds multiple executable files from the directory.

all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME)
	@echo ""
	@echo "Hint: It's a good idea to run 'make test' ;)"
	@echo ""

In addition to that, a few files have main() as a test function.

% git grep 'TEST_*MAIN' src
src/crc64.c:184:#ifdef TEST_MAIN
src/endianconv.c:104:#ifdef TESTMAIN
src/intset.c:284:#ifdef INTSET_TEST_MAIN
src/sds.c:965:#ifdef SDS_TEST_MAIN
src/util.c:577:#ifdef UTIL_TEST_MAIN
src/ziplist.c:955:#ifdef ZIPLIST_TEST_MAIN
src/zipmap.c:373:#ifdef ZIPMAP_TEST_MAIN
%

src/redis.c

Let’s read redis-server first. Its main() is in src/redis.c and this file is pretty long. Actually that’s the third longest file under src/.

% wc -l src/*.c | sort -n | tail
    1556 src/hyperloglog.c
    1764 src/networking.c
    1929 src/config.c
    2104 src/replication.c
    2298 src/redis-cli.c
    2856 src/t_zset.c
    3690 src/redis.c
    3900 src/sentinel.c
    5007 src/cluster.c
   51031 total
%

The main() is calling aeSetBeforeSleepProc(), aeMain() and aeDeleteEventLoop() in the end. These functions are defined under ae.c, Redis’ event-driven programming library.

int main(int argc, char **argv) {
    struct timeval tv;

    /* We need to initialize our libraries, and the server configuration. */
#ifdef INIT_SETPROCTITLE_REPLACEMENT
    spt_init(argc, argv);
#endif
    setlocale(LC_COLLATE,"");
    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,arg ev);
    initServerConfig();
...
    aeSetBeforeSleepProc(server.el,beforeSleep);
    aeMain(server.el);
    aeDeleteEventLoop(server.el);
    return 0;
}

The purpose of the library is similar to libevent. It abstracts the difference between Solaris’ event ports, Linux’s epoll, *BSD’s (incl. OS X) kqueue, and good old-fashioned select().

/* 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 <antirez at gmail dot com>
 * All rights reserved.
 *
 ...
 */

...

/* 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

redis.c is initializing this event library from initServer().

void initServer(void) {
    int j;

...

    createSharedObjects();
    adjustOpenFilesLimit();
    server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
    server.db = zmalloc(sizeof(redisDb)*server.dbnum);

...

    /* Create the serverCron() time event, that's our main way to process
     * background operations. */
    if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        redisPanic("Can't create the serverCron time event.");
        exit(1);
    }

    /* Create an event handler for accepting new connections in TCP and Unix
     * domain sockets. */
    for (j = 0; j < server.ipfd_count; j++) {
        if (aeCreateFileEvent(server.el, server.ipfd[j], 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.cluster_enabled) clusterInit();
    replicationScriptCacheInit();
    scriptingInit();
    slowlogInit();
    latencyMonitorInit();
    bioInit();
}
...

Both acceptTcpHandler() and acceptUnixHandler() are defined under networking.c, and it delegates the process to acceptCommonHandler().

What’s next?

I will read networking.c, where we can see redis-server’s protocol parser.