memcache/server.c
author Tero Marttila <terom@fixme.fi>
Fri, 29 Aug 2008 23:31:17 +0300
changeset 48 1c67f512779b
parent 46 8a832c0e01ee
child 49 10c7dce1a043
permissions -rw-r--r--
fix doc tpyos, rename some enums, fix printf format len for non-zero terminated strings (hg status), pass args to memcache_cmd_format_header via memcache_req_*, handle zero-length STORE requests, memcache_req is_buf_ours + free, other function name typos (keymemcache_req_key), fix req state behaviour re *_DATA_* for STORE requests and FETCH/END, better memcache_server connpool events/management, modular memcache_test with a working benchmark. This is a long commit message.

#include <stdlib.h>
#include <assert.h>

#include "server.h"
#include "connection.h"
#include "request.h"
#include "../memcache.h"
#include "../common.h"

struct memcache_server *memcache_server_alloc (struct config_endpoint *endpoint, int max_connections) {
    struct memcache_server *server = NULL;

    if ((server = calloc(1, sizeof(*server))) == NULL)
        ERROR("calloc");
    
    // store the vars
    server->endpoint = endpoint;
    server->max_connections = max_connections;

    // init lists
    LIST_INIT(&server->conn_list);
    TAILQ_INIT(&server->req_queue);
    
    // grow connpool so as to have a connection ready
    memcache_server_grow_connpool(server);

    // success
    return server;

error:
    free(server);

    return NULL;
}

void memcache_server_grow_connpool (struct memcache_server *server) {
    struct memcache_conn *conn;
    int count = 0;

    // count connections
    LIST_FOREACH(conn, &server->conn_list, connlist_node) {
        count++;
    }

    // room for more?
    if (count < server->max_connections) {
        // create a new one
        if ((conn = memcache_conn_open(server)) == NULL)
            ERROR("failed to grow the connpool");
       
        // enlist it
        LIST_INSERT_HEAD(&server->conn_list, conn, connlist_node);

        // the connection will call memcache_server_coon_ready once it's ready for use...
    }

    // ok
    return;

error:
    // XXX: we might be deadlocked now... requests queued, but no connections!
    if (LIST_EMPTY(&server->conn_list) && !TAILQ_EMPTY(&server->req_queue))
        FATAL("deadlock; requests queued, but no connections");

    // XXX: harmless... but need some retry logic
}

int memcache_server_add_req (struct memcache_server *server, struct memcache_req *req) {
    struct memcache_conn *conn;
    
    // look for an idle connection
    LIST_FOREACH(conn, &server->conn_list, connlist_node) {
        if (memcache_conn_is_available(conn)) {
            // we found an idle connection
            break;
        }
    }

    if (conn != NULL) {
        // we found an available connection
        // if the request fails, then we will know via conn_dead
        memcache_conn_do_req(conn, req);

        return 0;

    } else {
        // enqueue the request until a connection is available
        // XXX: queue size limits

        TAILQ_INSERT_TAIL(&server->req_queue, req, reqqueue_node);
        
        // notify the req
        memcache_req_queued(req);

        // grow the connpool, as we apparently don't have enough connections
        memcache_server_grow_connpool(server);

        return 0;
    }
}

/*
 * We might have available connections, process any queued requests, or try and grow the connpool if non available
 */
void memache_server_dequeue (struct memcache_server *server) {
    struct memcache_conn *conn;
    struct memcache_req *req;

    // if no requests are queued, nothing needs doing
    if ((req = TAILQ_FIRST(&server->req_queue)) == NULL)
        return;
    
    // look for idle connections to service the request
    LIST_FOREACH(conn, &server->conn_list, connlist_node) {
        if (memcache_conn_is_available(conn)) {
            // remove the req from the queue and execute it
            TAILQ_REMOVE(&server->req_queue, req, reqqueue_node);
            
            // this will take care of any error handling by itself
            memcache_conn_do_req(conn, req);
            
            // if that was the last req, return, otherwise continue
            if ((req = TAILQ_FIRST(&server->req_queue)) == NULL)
                return;
        }
    }
    
    // no idle connections remaining, try and grow if applicable
    memcache_server_grow_connpool(server);
}

void memcache_server_conn_ready (struct memcache_server *server, struct memcache_conn *conn) {
    assert(server == conn->server);
    
    // grab the next queued request
    memache_server_dequeue(server);
}

void memcache_server_conn_dead (struct memcache_server *server, struct memcache_conn *conn) {
    assert(server == conn->server);

    // remove it from the list
    LIST_REMOVE(conn, connlist_node);

    // free it
    memcache_conn_free(conn);

    // this should grow the connpool back again
    memache_server_dequeue(server);
}