1/**
2 * \file
3 * \brief NFS-populated file cache for HTTP server
4 *
5 * This very stupid "cache" assumes that:
6 *  All regular files in a hardcoded NFS mount point are cached at startup
7 *  The contents of the cache never change nor expire
8 */
9
10/*
11 * Copyright (c) 2008, ETH Zurich.
12 * All rights reserved.
13 *
14 * This file is distributed under the terms in the attached LICENSE file.
15 * If you do not find this file, copies can be found by writing to:
16 * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
17 */
18
19#include <stdio.h>
20#include <barrelfish/barrelfish.h>
21#include <nfs/nfs.h>
22#include <net_sockets/net_sockets.h>
23#include <trace/trace.h>
24#include <trace_definitions/trace_defs.h>
25#include <timer/timer.h>
26#include <netbench/netbench.h>
27#include "webserver_network.h"
28#include "webserver_debug.h"
29#include "webserver_session.h"
30#include "http_cache.h"
31
32/* NOTE: enable following #def if you want cache to be preloaded */
33#define PRELOAD_WEB_CACHE 1
34
35/* Enable tracing only when it is globally enabled */
36#if CONFIG_TRACE && NETWORK_STACK_TRACE
37//#define ENABLE_WEB_TRACING 1
38#endif // CONFIG_TRACE && NETWORK_STACK_TRACE
39
40//#define MAX_NFS_READ       14000
41#define MAX_NFS_READ      1330 /* 14000 */ /* to avoid packet reassembly inside driver */
42
43/* Maximum staleness allowed */
44#define MAX_STALENESS ((cycles_t)9000000)
45
46static void (*init_callback)(void);
47
48
49struct http_cache_entry {
50    int                 valid;      /* flag for validity of the data */
51    char                *name;      /* name of the cached file */
52    size_t              copied;     /* how much data is copied? */
53    int                 loading;    /* flag indicating if data is loading */
54    struct buff_holder  *hbuff;      /* holder for buffer */
55    struct nfs_fh3      file_handle;    /* for NFS purpose */
56    struct http_conn *conn;     /* list of connections waiting for data */
57    struct http_conn *last;        /* for quick insertions at end */
58    struct http_cache_entry *next;   /* for linked list */
59};
60
61/* global states */
62static struct nfs_fh3 nfs_root_fh;  /* reference to the root dir of NFS */
63static struct nfs_client *my_nfs_client; /* The NFS client */
64
65static struct http_cache_entry *cache_table = NULL; /*root cached entry array */
66static struct http_cache_entry *error_cache = NULL; /* cache entry for error */
67
68
69#ifdef PRELOAD_WEB_CACHE
70/* Initial cache loading state variables */
71static bool cache_loading_phase = false;
72static bool readdir_complete = false;
73static int cache_lookups_started = 0;
74static int cache_loaded_counter = 0;
75static int cache_loading_probs = 0;
76static void handle_cache_load_done(void);
77#endif // PRELOAD_WEB_CACHE
78
79// Variables for time measurement for performance
80static uint64_t last_ts = 0;
81
82/* allocate the buffer and initialize it. */
83static struct buff_holder *allocate_buff_holder (size_t len)
84{
85    struct buff_holder *result = NULL;
86    result = (struct buff_holder *) malloc (sizeof (struct buff_holder));
87    assert (result != NULL );
88    memset (result, 0, sizeof(struct buff_holder));
89    if ( len > 0) {
90        result->data = malloc (len);
91        assert (result->data != NULL);
92    }
93    /* NOTE: 0 is valid length and used by error_cache */
94    result->len = len;
95    result->r_counter = 1; /* initiating ref_counter to 1, and using it as ref
96            for free */
97    return result;
98} /* end function: allocate_buff_holder */
99
100/* increments and returns the incremented value of the ref_counter for bh */
101static long increment_buff_holder_ref (struct buff_holder *bh)
102{
103    ++bh->r_counter;
104    return (bh->r_counter);
105} /* end Function: increment_buff_holder_ref */
106
107/* Decrements value of the ref_counter for bh
108    if r_counter reaches zero then free all the memory */
109long decrement_buff_holder_ref (struct buff_holder *bh)
110{
111    if (bh == NULL) {
112        return 0;
113    }
114
115    --bh->r_counter;
116    if (bh->r_counter > 0) {
117        return (bh->r_counter);
118    }
119
120    if (bh->len > 0 && bh->data != NULL) {
121        free (bh->data);
122    }
123    free (bh);
124    return 0;
125} /* end Function: increment_buff_holder_ref */
126
127
128/* allocates the memory for the cacheline */
129static struct http_cache_entry * cache_entry_allocate (void)
130{
131    struct http_cache_entry *e;
132    e = (struct http_cache_entry *)
133        malloc (sizeof(struct http_cache_entry));
134    assert (e != NULL );
135    memset (e, 0, sizeof(struct http_cache_entry));
136    return e;
137}
138
139/* Function create_404_page_cache  creates error_cache entry,
140    this entry will be used to reply when file is not found */
141static void create_404_page_cache (void)
142{
143    error_cache = cache_entry_allocate();
144    error_cache->hbuff = allocate_buff_holder (0);
145    increment_buff_holder_ref (error_cache->hbuff);
146    error_cache->valid = 1;
147} /* end function: create_404_page_cache */
148
149/* adds the http connection cs at the end of the list of connections waiting
150    for the arrival of the data on this cacheline e. */
151static void add_connection(struct http_cache_entry *e, struct http_conn *cs)
152{
153    assert (e != NULL);
154    assert (cs != NULL);
155    DEBUGPRINT ("%d: adding conn to waiting list of cacheline [%s]\n",
156        cs->request_no, e->name);
157    increment_http_conn_reference (cs);
158    /* maintaining linked list with O(c) time and space complexity */
159    if (e->conn == NULL) { /* list is empty, this is first element */
160        assert(e->last == NULL); /* NOTE: just for safety, not actually needed*/
161        e->conn = cs;
162        e->last = cs;
163        return;
164    }
165    /* adding element at end of the list */
166    e->last->next = cs;
167    e->last = cs;
168} /* end function: add_connection */
169
170/* Finds the cacheline associated with given name
171 if no cacheline exists, it will create one,
172 copy name as the key for cacheline */
173static struct http_cache_entry *find_cacheline (const char *name)
174{
175    struct http_cache_entry *e;
176    int l;
177    // FIXME: simple linear search
178    for (e = cache_table; e != NULL; e = e->next) {
179        if (strcmp(name, e->name) == 0) {
180            DEBUGPRINT ("cache-hit for [%s] == [%s]\n", name, e->name);
181            return e;
182        }
183    } /* end for : for each cacheline */
184    /* create new cacheline */
185    e = cache_entry_allocate();
186    /* copying the filename */
187    l = strlen (name);
188    e->name = (char *)malloc(sizeof(char)*(l+1));
189    assert(e->name != NULL);
190    strcpy(e->name, name);
191    DEBUGPRINT ("cache-miss for [%s] so, created [%s]\n", name, e->name);
192    e->next = cache_table;
193    cache_table = e;
194    return e;
195} /* end function: find_cacheline */
196
197static void delete_cacheline_from_cachelist (struct http_cache_entry *target)
198{
199    struct http_cache_entry *prev;
200    struct http_cache_entry *e;
201
202    if (cache_table == target){
203        cache_table = target->next;
204        return;
205    }
206
207    // FIXME: simple linear search
208    prev = NULL;
209    for (e = cache_table; e != NULL; e = e->next) {
210        if (e == target) {
211            prev->next = e->next;
212            return;
213        }
214        prev = e;
215    } /* end for : for each cacheline */
216} /* end function: delete_cacheline_from_cachelist */
217
218
219static void trigger_callback (struct http_conn *cs, struct http_cache_entry *e)
220{
221
222    if ( cs->mark_invalid ){
223        /* This http_conn is not good anymore (most probably, it received RST)*/
224        DEBUGPRINT ("%d: ERROR: callback triggered on invalid conn\n",
225            cs->request_no );
226        return;
227    }
228
229    /* making the callback */
230    cs->hbuff = e->hbuff;
231    /* increasing the reference */
232    increment_buff_holder_ref (cs->hbuff);
233    cs->callback(cs);
234} /* end function: trigger_callback */
235
236
237/* send callback for each pending http_conn */
238static void handle_pending_list(struct http_cache_entry *e)
239{
240    struct http_conn *cs = NULL;
241    struct http_conn *next_cs = NULL;
242    if(e == NULL) {
243    	printf("ERROR: handle_pending_list: null passed for cache entry\n");
244    	return;
245    }
246    if(e->conn == NULL){
247//    	printf("ERROR: handle_pending_list: no one waiting\n");
248    	return;
249    }
250    cs = e->conn;
251    while (cs != NULL) {
252    	e->conn = cs->next;
253        trigger_callback(cs, e);
254        next_cs = cs->next;
255        /* Copying the next of cs as following function can release the cs. */
256        decrement_http_conn_reference(cs);
257        cs = next_cs;
258    }
259
260    /* clear the list attached to the cache */
261    e->conn = NULL;
262    e->last = NULL;
263} /* end function : handle_pending_list */
264
265
266static void read_callback (void *arg, struct nfs_client *client,
267                          READ3res *result)
268{
269    struct http_cache_entry *e = arg;
270    assert( e != NULL);
271
272    assert (result != NULL);
273    assert (result->status == NFS3_OK);
274    READ3resok *res = &result->READ3res_u.resok;
275    assert(res->count == res->data.data_len);
276
277    assert (e->hbuff != NULL);
278    assert (e->hbuff->data != NULL );
279
280    assert (e->hbuff->len >= e->copied + res->data.data_len);
281    memcpy (e->hbuff->data + e->copied, res->data.data_val, res->data.data_len);
282    e->copied += res->data.data_len;
283
284    DEBUGPRINT ("got response of len %d, filesize %lu for file %s\n",
285    			res->data.data_len, e->copied, e->name);
286
287    // free arguments
288    xdr_READ3res(&xdr_free, result);
289
290    if (!res->eof) {
291        // more data to come, read the next chunk
292        errval_t err = nfs_read(client, e->file_handle, e->copied, MAX_NFS_READ,
293                        read_callback, e);
294        assert(err == SYS_ERR_OK);
295        return;
296    }
297
298    /* This is the end-of-file, so deal with it. */
299    e->valid = 1;
300    e->loading = 0;
301    decrement_buff_holder_ref (e->hbuff);
302
303#ifdef PRELOAD_WEB_CACHE
304    if (!cache_loading_phase) {
305        handle_pending_list (e); /* done! */
306        return;
307    }
308
309    /* This is cache loading going on... */
310    printf("Copied %zu bytes for file [%s] of length: %zu\n",
311            e->copied, e->name, e->hbuff->len);
312    ++cache_loaded_counter;
313    handle_cache_load_done();
314
315#else // PRELOAD_WEB_CACHE
316    handle_pending_list(e); /* done! */
317#endif // PRELOAD_WEB_CACHE
318}
319
320
321static void lookup_callback (void *arg, struct nfs_client *client,
322                            LOOKUP3res *result)
323{
324    LOOKUP3resok *resok = &result->LOOKUP3res_u.resok;
325    errval_t r;
326    struct http_cache_entry *e = arg;
327
328    DEBUGPRINT ("inside lookup_callback_file for file %s\n", e->name);
329    assert(result != NULL );
330
331    /* initiate a read for every regular file */
332    if ( result->status == NFS3_OK &&
333        resok->obj_attributes.attributes_follow &&
334        resok->obj_attributes.post_op_attr_u.attributes.type == NF3REG) {
335
336    	/* FIXME: check if the file is recently modified or not. */
337		// resok->obj_attributes.post_op_attr_u.attributes.ctime;
338
339
340        DEBUGPRINT("Copying %s of size %lu\n", e->name,
341                    resok->obj_attributes.post_op_attr_u.attributes.size );
342
343        /* Store the nfs directory handle in current_session (cs) */
344        nfs_copyfh (&e->file_handle, resok->object);
345        /* GLOBAL: Storing the global reference for cache entry */
346        /* NOTE: this memory is freed in reset_cache_entry() */
347
348        /* Allocate memory for holding the file-content */
349        /* Allocate the buff_holder */
350        e->hbuff = allocate_buff_holder(
351            resok->obj_attributes.post_op_attr_u.attributes.size );
352        /* NOTE: this memory will be freed by decrement_buff_holder_ref */
353
354        /* increment buff_holder reference */
355        increment_buff_holder_ref (e->hbuff);
356
357        e->hbuff->len = resok->obj_attributes.post_op_attr_u.attributes.size;
358
359        /* Set the size of the how much data is read till now. */
360        e->copied = 0;
361
362        r = nfs_read(client, e->file_handle, 0, MAX_NFS_READ,
363                read_callback, e);
364        assert (r == SYS_ERR_OK);
365
366        // free arguments
367        xdr_LOOKUP3res(&xdr_free, result);
368        return;
369    }
370
371    /* Most probably the file does not exist */
372    DEBUGPRINT ("Error: file [%s] does not exist, or wrong type\n", e->name);
373
374#ifdef PRELOAD_WEB_CACHE
375    if (cache_loading_phase){
376    	++cache_loading_probs;
377    	handle_cache_load_done();
378    	return;
379    }
380#endif // PRELOAD_WEB_CACHE
381
382    if (e->conn == NULL) {
383    	/* No connection is waiting for this loading */
384    	return;
385    }
386
387    /*	as file does not exist, send all the http_conns to error page. */
388    error_cache->conn = e->conn;
389    error_cache->last = e->last;
390    handle_pending_list (error_cache); /* done! */
391
392    /* free this cache entry as it is pointing to invalid page */
393    e->conn = NULL;
394    e->last = NULL;
395    delete_cacheline_from_cachelist (e);
396    if (e->name != NULL ) free(e->name);
397    free(e);
398
399    // free arguments
400    xdr_LOOKUP3res(&xdr_free, result);
401    return;
402} /* end function: lookup_callback_file */
403
404static errval_t async_load_cache_entry(struct http_cache_entry *e)
405{
406    errval_t r;
407    assert(e != NULL);
408
409    // FIXME: currently only works for files in root directory.
410    // Do lookup for given filename in root dir
411    DEBUGPRINT ("pageloading starting with nfs_lookup\n");
412    r = nfs_lookup(my_nfs_client, nfs_root_fh, e->name,
413                lookup_callback, e);
414    assert(r == SYS_ERR_OK);
415    return SYS_ERR_OK;
416} /* end function : async_load_cache_entry */
417
418
419errval_t http_cache_lookup (const char *name, struct http_conn *cs)
420{
421    struct http_cache_entry *e;
422    assert(cs != NULL);
423
424    e = find_cacheline(name);
425    if (e->valid == 1) {
426        /* fresh matching cache-entry found */
427        DEBUGPRINT ("%d: Fresh cache-entry, returning page [%s]\n",
428                cs->request_no, name);
429        trigger_callback (cs, e);
430        return SYS_ERR_OK;
431    } /* end if: valid cacheline */
432
433    /* data not in cache */
434    /* add this connection to the list of waiting on cacheline */
435    add_connection (e, cs);
436
437    /* Check if no-one is loading the data */
438    if (e->loading == 0 ) {
439        e->loading = 1;
440        DEBUGPRINT ("%d: starting the page loading\n", cs->request_no);
441        async_load_cache_entry(e);
442    } /* end if: no-one else is loading the data */
443    else {
444        DEBUGPRINT ("%d: someone else is loading the page, so just waiting\n",
445            cs->request_no);
446    }
447
448    return SYS_ERR_OK;
449} /* end function: http_cache_lookup */
450
451
452
453
454static void cache_timeout_event (struct timer *timer, void *arg)
455{
456    struct http_cache_entry *cache = arg;
457    struct http_cache_entry *e;
458
459    DEBUGPRINT ("CACHE_TIMEOUT: marking all cache entries for reload\n");
460    for (e = cache; e != NULL; e = e->next) {
461        if (e->valid == 1) {
462            /* this if is to avoid any complications that might come because
463                of getting cache_timeout when cacheline was in loading */
464            e->valid = 0;
465            e->loading = 0;
466
467            decrement_buff_holder_ref (e->hbuff); /* Decrement buffer ref.  */
468            e->hbuff = NULL;    /* loosing the pointer to buff */
469        } /* end if: if cacheline is valid  */
470    } /* end for : for each cacheline */
471    DEBUGPRINT ("CACHE_TIMEOUT: Done with invalidating the cache entries\n");
472} /* end function : cache_timeout_event */
473
474
475#ifdef PRELOAD_WEB_CACHE
476
477static void readdir_callback(void *arg, struct nfs_client *client,
478                             READDIR3res *result)
479{
480    READDIR3resok *resok = &result->READDIR3res_u.resok;
481    struct http_cache_entry *ce;
482    entry3 *last = NULL;
483    errval_t r;
484
485    DEBUGPRINT ("readdir_callback came in\n");
486    assert(result != NULL && result->status == NFS3_OK);
487
488    // FIXME: start here the measurement of file loading time
489
490    // FIXME: Start the trace
491#if ENABLE_WEB_TRACING
492    printf("Starting tracing\n");
493
494    errval_t err = trace_control(TRACE_EVENT(TRACE_SUBSYS_NNET,
495                                    TRACE_EVENT_NNET_START, 0),
496                        TRACE_EVENT(TRACE_SUBSYS_NNET,
497                                    TRACE_EVENT_NNET_STOP, 0), 0);
498    if(err_is_fail(err)) {
499        USER_PANIC_ERR(err, "trace_control failed");
500    }
501    trace_event(TRACE_SUBSYS_NNET, TRACE_EVENT_NNET_START, 0);
502#else // ENABLE_WEB_TRACING
503    printf("Tracing not enabled\n");
504#endif // ENABLE_WEB_TRACING
505
506
507    last_ts = rdtsc();
508//    lwip_benchmark_control(1, BMS_START_REQUEST, 0, 0);
509    // initiate a lookup for every entry
510    for (entry3 *e = resok->reply.entries; e != NULL; e = e->nextentry) {
511        if (strlen(e->name) < 3 ) {
512            printf("ignoring the file %s\n", e->name);
513            continue;
514        }
515        ++cache_lookups_started;
516        printf("Loading the file %s\n", e->name);
517        ce = find_cacheline(e->name);
518        ce->loading = 1;
519        async_load_cache_entry(ce);
520        e->name = NULL; // prevent freeing by XDR
521        last = e;
522
523        // continue handling events till this page is not completly loaded.
524
525/*
526        struct waitset *ws = get_default_waitset();
527        errval_t err;
528
529        DEBUGPRINT ("looping for events inside loop of readdir_callback\n");
530        while (ce->valid != 1) {
531            err = event_dispatch(ws);
532            if (err_is_fail(err)) {
533                DEBUG_ERR(err, "in event_dispatch");
534                break;
535            }
536        }
537        DEBUGPRINT ("readdir_callback: %s file loaded\n", e->name);
538*/
539
540    }
541
542    /* more in the directory: repeat call */
543    if (!resok->reply.eof) {
544        assert(last != NULL);
545        r = nfs_readdir(client, nfs_root_fh, last->cookie,
546                        resok->cookieverf, readdir_callback, NULL);
547        assert(r == SYS_ERR_OK);
548    } else {
549        readdir_complete = true;
550        handle_cache_load_done();
551    }
552
553    // free arguments
554    xdr_READDIR3res(&xdr_free, result);
555}
556
557
558
559static bool are_all_pages_loaded(void)
560{
561	if (readdir_complete == false ) {
562		return false;
563	}
564	return (cache_lookups_started == (cache_loaded_counter + cache_loading_probs));
565}
566
567static void handle_cache_load_done(void)
568{
569	if (!are_all_pages_loaded()) {
570		return;
571	}
572    DEBUGPRINT("initial_cache_load: entire cache loaded done\n");
573    cache_loading_phase = false;
574
575    // lwip_benchmark_control(1, BMS_STOP_REQUEST, 0, 0);
576    //
577    // Report the cache loading time
578
579    uint64_t total_loading_time = rdtsc() - last_ts;
580    printf("Cache loading time %"PU", %"PRIu64"\n",
581            in_seconds(total_loading_time), total_loading_time);
582
583//    lwip_print_interesting_stats();
584
585    /* stop the trace. */
586#if ENABLE_WEB_TRACING
587    trace_event(TRACE_SUBSYS_NNET, TRACE_EVENT_NNET_STOP, 0);
588
589    char *trace_buf_area = malloc(CONSOLE_DUMP_BUFLEN);
590    assert(trace_buf_area);
591    size_t used_bytes = 0;
592    trace_dump_core(trace_buf_area, CONSOLE_DUMP_BUFLEN, &used_bytes, NULL,
593            disp_get_core_id(),  true, true);
594
595    printf("\n%s\n", "dump trac buffers: Start");
596    printf("\n%s\n", trace_buf_area);
597    printf("\n%s\n", "dump trac buffers: Stop");
598    trace_reset_all();
599//    abort();
600
601    printf("Cache loading time %"PU", %"PRIu64"\n",
602            in_seconds(total_loading_time), total_loading_time);
603
604#endif // ENABLE_WEB_TRACING
605
606
607    /* continue with the web-server initialization. */
608    init_callback(); /* do remaining initialization! */
609}
610
611static void initial_cache_load(struct nfs_client *client)
612{
613    errval_t r;
614	cache_loading_phase = true;
615	cache_lookups_started = 0;
616	cache_loaded_counter = 0;
617	cache_loading_probs = 0;
618
619	//my_nfs_client
620    r = nfs_readdir(client, nfs_root_fh, NFS_READDIR_COOKIE,
621                         NFS_READDIR_COOKIEVERF, readdir_callback, NULL);
622    assert(r == SYS_ERR_OK);
623}
624
625#endif // PRELOAD_WEB_CACHE
626
627
628static void mount_callback(void *arg, struct nfs_client *client,
629                 enum mountstat3 mountstat, struct nfs_fh3 fhandle)
630{
631    assert(mountstat == MNT3_OK);
632    nfs_root_fh = fhandle; /* GLOBAL: assigning root of filesystem handle */
633#ifdef PRELOAD_WEB_CACHE
634    DEBUGPRINT ("nfs_mount successful, loading initial cache.\n");
635    initial_cache_load(client);	/* Initial load of files for cache. */
636#else //     PRELOAD_WEB_CACHE
637    DEBUGPRINT ("nfs_mount successful\n");
638    init_callback(); /* done! */
639#endif // PRELOAD_WEB_CACHE
640} /* end function: mount_callback */
641
642errval_t http_cache_init(struct in_addr server, const char *path,
643                     void (*callback)(void))
644{
645    struct timer *cache_timer;      /* timer for triggering cache timeouts */
646    init_callback = callback;
647
648    DEBUGPRINT ("nfs_mount calling.\n");
649    my_nfs_client = nfs_mount(server, path, mount_callback, NULL);
650    DEBUGPRINT ("nfs_mount calling done.\n");
651
652    assert(my_nfs_client != NULL);
653    /* creating the empty cache */
654    cache_table = NULL;
655    create_404_page_cache();
656
657
658    cache_timer = timer_create(MAX_STALENESS, true, cache_timeout_event,
659            cache_table);
660    assert (cache_timer != NULL);
661    if (cache_timer == NULL) {
662        printf ("http_cache_init failed in timer_create\n");
663        return LWIP_ERR_MEM;
664    }
665    timer_start(cache_timer);
666    DEBUGPRINT ("http_cache_init done\n");
667    return SYS_ERR_OK;
668} /* end function: http_cache_init */
669