memcache/server.c
author Tero Marttila <terom@fixme.fi>
Sat, 30 Aug 2008 19:13:15 +0300
changeset 49 10c7dce1a043
parent 48 1c67f512779b
permissions -rw-r--r--
autogenerate the memcache_test help output, and pipeline memcache requests

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

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

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

    // warn if nonsensical arguments
    if (max_connections > 1 && mc->pipeline_requests)
        WARNING("only one connection will ever be used with pipeline_requests");

    if ((server = calloc(1, sizeof(*server))) == NULL)
        ERROR("calloc");
    
    // store the vars
    server->mc = mc;
    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) {
        // handle pipelining as well
        while (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);
}