lpereira / lwan

Experimental, scalable, high performance HTTP server
https://lwan.ws
GNU General Public License v2.0
5.92k stars 549 forks source link

Disable cache but still make use of coroutine #74

Closed whatvn closed 9 years ago

whatvn commented 9 years ago

Hello,

I write a simple handler with lwan, this handler do a simple thing: when client make a request which ask server to delay x miliseconds then response, server sleeps x miliseconds then response ok to client. The reason I do this is we are trying to demonstrate real time bidding issue, when server have to response within 120ms for a bidding request, and client must close request after 120ms even if it does not receive response.

Write a handler which does not use coroutine is easy, but it causes blocking when many concurrent delay client make requests to lwan, it just can reply only some first requests, then cannot response at all.

Using coroutine is better, but I dont know how to use it without passing it through cache. Source code below:

/*
 * lwan - simple web server
 * Copyright (c) 2012 Leandro A. F. Pereira <leandro@hardinfo.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include <string.h>
#include <limits.h>
#include <dirent.h>

#include "lwan.h"
#include "lwan-serve-files.h"
#include "lwan-template.h"
#include "lwan-cache.h"
#include "lwan-io-wrappers.h"
#include <stdio.h>
#include <stdlib.h>
#include <uthash.h>
#include "murmur3.h"
#include "sys/stat.h"
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <inttypes.h>

lwan_t l;

struct delay_info_t {
    struct cache_entry_t base;
    char *key;
    const char *callback;
};

static struct cache_t *cache = NULL;

static struct cache_entry_t *create_delay_info(const char *input,
        void *context __attribute__((unused))) {
    unsigned int i;
    uintmax_t delay = 0;
    bool valid = true;
    for (i = 0; i < strlen(input); i++) {
        if (!isdigit(input[i])) {
            valid = false;
            break;
        }
    }
    if (valid) {
        delay = strtoumax(input, NULL, 10);
    }
    if (!valid || (delay == UINTMAX_MAX && errno == ERANGE)) {
        return NULL;
    }
    time_t rawtime;
    struct tm * timeinfo;
    time ( &rawtime );
    timeinfo = localtime ( &rawtime );
    usleep((__useconds_t)delay * 1000);
    struct delay_info_t *delay_info;
    delay_info = calloc(1, sizeof (struct delay_info_t));
    delay_info->key = asctime(timeinfo);
    return (struct cache_entry_t *) delay_info;
}

static void destroy_delayinfo(struct cache_entry_t *entry,
        void *context __attribute__((unused))) {
    struct delay_info_t *delay_info = (struct delay_info_t *) entry;
    if (!delay_info) return;
    free(delay_info);
}

lwan_http_status_t
    delay_handler(lwan_request_t *request,
        lwan_response_t *response, void *data __attribute__((unused))) {
    const char *name = lwan_request_get_query_param(request, "delay");
    struct cache_entry_t *dl;
    dl = calloc(1, sizeof (struct cache_entry_t));
    dl = cache_coro_get_and_ref_entry(cache, request->conn->coro,
            name);
    if (dl) {
        response->mime_type = "text/html";
        strbuf_set_static(response->buffer, (char*) "ok", sizeof ("ok") - 1);
        cache_entry_unref(cache, dl);
        return HTTP_OK;
    }
    return HTTP_BAD_REQUEST;
}

int
main(void) {
    lwan_init(&l);
    // init cache
    cache = cache_create(create_fileinfo, destroy_fileinfo, NULL, 10);
    printf("Config root path: %s\n", l.config.root);
    lwan_main_loop(&l);
    lwan_shutdown(&l);
    return 0;
}

As you can see, I try to make uniq cache key by current request time in order to disable caching, but lwan still cache request. If I make 2 request to server: http://localhost/d?delay=1000, 1 request will receive response in 1012 ms, the second one receive response in 10 ms (did not sleep)

So, the issue is: how to disable the cache in this case? I also try to remove assert(cache->time_to_live > 0) in lwan_cache.c to create cache with time_to_live = 0, but request is still cached.

Thanks

lpereira commented 9 years ago

I'm not sure why you're using cache for this in the first place. What you're trying to do is closer to what needs to be done in #36; with that implemented, you would need only something like this in your handler:

lwan_http_status_t delay_handler(...) {
    long time = parse_long(lwan_request_get_query_param(request, "delay"), 0);
    if (time <= 0 || time > 5000) return HTTP_BAD_REQUEST;
    lwan_delay(request, time);
    strbuf_set_static(response->buffer, "ok", 2);
    return HTTP_OK;
}

The mentioned ticket talks about timerfd which would be the most efficient way to implement this. But, for testing purposes, you could just implement lwan_delay() as (or something similar, I haven't tested this):

void lwan_delay(request, how_much) {
    long stop_when = get_current_time_somehow() + how_much;
    while (get_current_time_somehow() < stop_when)
        coro_yield(request->conn->coro, CORO_MAY_RESUME);
}

This isn't as efficient (will yield a lot until it's time to complete), but will be fair as in it'll let other requests to be handled in the time it's not executing.

whatvn commented 9 years ago

I follow your guide and it works as expect.

Thanks