rework stat_cache
parent
1c9d95c37a
commit
ddb0448a96
|
@ -1,16 +1,15 @@
|
|||
/*
|
||||
* stat cache - speeding up stat()s
|
||||
*
|
||||
* The basic idea behind the stat cache is to reduce calls to stat() which might be slow due to disk io (some ms).
|
||||
* The basic idea behind the stat cache is to reduce calls to stat() which might block due to disk io (some ms).
|
||||
* Each worker thread has its own cache so no locking contention between threads happens which could be slow.
|
||||
* This means that there will be more stat() calls than there would be with only one shared cache but since there
|
||||
* This means that there will be more blocking stat() calls than there would be with only one shared cache but since there
|
||||
* should be mostly hits in most cases (few items requested frequently) it will outweight the locking contention.
|
||||
* To prevent the stat() from blocking all other requests of that worker, we hand it over to another thread.
|
||||
*
|
||||
* Entries are removed after 10 seconds (adjustable through stat_cache.ttl setup)
|
||||
*
|
||||
* TODO:
|
||||
* - stat_cache.ttl setup
|
||||
* - create ETAGs
|
||||
* - get content type from xattr
|
||||
* - add support for inotify (linux). TTL for entries can be increased to 60s
|
||||
|
@ -19,13 +18,14 @@
|
|||
* If a stat is requested, the following procedure takes place:
|
||||
* - a cache lookup is performed
|
||||
* - in case of a cache HIT:
|
||||
* - if state is FINISHED and entry is fresh then return entry
|
||||
* - if state is FINISHED but entry old then reset entry, create new job and return NULL
|
||||
* - if state is WAITING then add vrequest to entry and return NULL (looks like a cache miss)
|
||||
* - if state is FINISHED and entry is fresh then return HANDLER_GO_ON
|
||||
* - if state is WAITING then add vrequest to entry and return HANDLER_WAIT_FOR_EVENT (looks like a cache miss)
|
||||
* - in case of a cache MISS:
|
||||
* - a new entry is allocated and inserted into the cache, state is set to WAITING
|
||||
* - the entry is inserted into the delete queue
|
||||
* - a new job is created and NULL returned
|
||||
* - a new job is created and HANDLER_WAIT_FOR_EVENT returned
|
||||
* - in case of an ERROR:
|
||||
* - return HANDLER_ERROR
|
||||
*
|
||||
* In the delete queue callback we check if no vrequests are working on that entry. If yes, we free it. If not then we requeue it.
|
||||
* Locking only happens in two cases: 1) a new job is send to the stat thread 2) the stat thread sends the info back to the worker.
|
||||
|
@ -60,15 +60,16 @@ struct stat_cache_entry {
|
|||
} state;
|
||||
|
||||
stat_cache_entry_data data;
|
||||
GArray *dirlist; /* array of stat_cache_dirlist_entry, used together with STAT_CACHE_ENTRY_DIR */
|
||||
ev_tstamp ts; /* timestamp the entry was created (not when the stat() was done) */
|
||||
GArray *dirlist; /* array of stat_cache_entry_data, used together with STAT_CACHE_ENTRY_DIR */
|
||||
|
||||
GPtrArray *vrequests; /* vrequests waiting for this info */
|
||||
guint refcount;
|
||||
waitqueue_elem queue_elem; /* queue element for the delete_queue */
|
||||
gboolean in_cache;
|
||||
gboolean cached;
|
||||
};
|
||||
|
||||
struct stat_cache {
|
||||
GHashTable *dirlists;
|
||||
GHashTable *entries;
|
||||
GAsyncQueue *job_queue_out; /* elements waiting for stat */
|
||||
GAsyncQueue *job_queue_in; /* elements with finished stat */
|
||||
|
@ -87,14 +88,19 @@ void stat_cache_free(stat_cache *sc);
|
|||
|
||||
/*
|
||||
gets a stat_cache_entry for a specified path
|
||||
returns NULL in case of a cache MISS and you should return HANDLER_WAIT_FOR_EVENT
|
||||
if fd is set, a new fd is acquired via open() and stat info via fstat(), otherwise only a stat() is performed
|
||||
returns HANDLER_WAIT_FOR_EVENT in case of a cache MISS, HANDLER_GO_ON in case of a hit and HANDLER_ERROR in case of an error
|
||||
*/
|
||||
LI_API stat_cache_entry *stat_cache_get(vrequest *vr, GString *path);
|
||||
LI_API handler_t stat_cache_get(vrequest *vr, GString *path, struct stat *st, int *err, int *fd);
|
||||
|
||||
/* like stat_cache_entry_get but gets stat info for a whole directory */
|
||||
LI_API stat_cache_entry *stat_cache_get_dir(vrequest *vr, GString *path);
|
||||
/*
|
||||
sce->dirlist will contain a list of stat_cache_entry_data upon success
|
||||
returns HANDLER_WAIT_FOR_EVENT in case of a cache MISS, HANDLER_GO_ON in case of a hit and HANDLER_ERROR in case of an error
|
||||
*/
|
||||
LI_API handler_t stat_cache_get_dirlist(vrequest *vr, GString *path, stat_cache_entry **result);
|
||||
|
||||
LI_API void stat_cache_entry_acquire(vrequest *vr, stat_cache_entry *sce);
|
||||
/* release a stat_cache_entry so it can be cleaned up */
|
||||
LI_API void stat_cache_entry_release(vrequest *vr);
|
||||
LI_API void stat_cache_entry_release(vrequest *vr, stat_cache_entry *sce);
|
||||
|
||||
#endif
|
|
@ -84,7 +84,7 @@ struct vrequest {
|
|||
|
||||
GList *job_queue_link;
|
||||
|
||||
stat_cache_entry *stat_cache_entry;
|
||||
GPtrArray *stat_cache_entries;
|
||||
};
|
||||
|
||||
#define VREQUEST_WAIT_FOR_RESPONSE_HEADERS(vr) \
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
* dirlist ("include-header" => true, "hide-header" => true, "hide->suffix" => (".bak"));
|
||||
* }
|
||||
* - shows a directory listing including the content of HEADER.txt above the list and hiding itself from it
|
||||
* also hides all files all files ending in ".bak"
|
||||
* also hides all files ending in ".bak"
|
||||
*
|
||||
* Tip:
|
||||
* xyz
|
||||
|
@ -166,45 +166,44 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
|
|||
stat_cache_entry *sce;
|
||||
dirlist_data *dd;
|
||||
dirlist_plugin_data *pd;
|
||||
|
||||
UNUSED(context);
|
||||
|
||||
|
||||
if (vrequest_is_handled(vr)) return HANDLER_GO_ON;
|
||||
|
||||
if (!vr->stat_cache_entry) {
|
||||
if (vr->physical.path->len == 0) return HANDLER_GO_ON;
|
||||
if (vr->physical.path->len == 0) return HANDLER_GO_ON;
|
||||
|
||||
switch (stat_cache_get_dirlist(vr, vr->physical.path, &sce)) {
|
||||
case HANDLER_GO_ON: break;
|
||||
case HANDLER_WAIT_FOR_EVENT: return HANDLER_WAIT_FOR_EVENT;
|
||||
default: return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
dd = param;
|
||||
pd = dd->plugin->data;
|
||||
|
||||
sce = stat_cache_get_dir(vr, vr->physical.path);
|
||||
if (!sce)
|
||||
return HANDLER_WAIT_FOR_EVENT;
|
||||
|
||||
if (sce->data.failed) {
|
||||
/* stat failed */
|
||||
stat_cache_entry_release(vr, sce);VR_ERROR(vr, "stat('%s') failed: %s", sce->data.path->str, g_strerror(sce->data.err));
|
||||
switch (sce->data.err) {
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
stat_cache_entry_release(vr);
|
||||
return HANDLER_GO_ON;
|
||||
case EACCES:
|
||||
if (!vrequest_handle_direct(vr)) return HANDLER_ERROR;
|
||||
vr->response.http_status = 403;
|
||||
stat_cache_entry_release(vr);
|
||||
return HANDLER_GO_ON;
|
||||
default:
|
||||
VR_ERROR(vr, "stat('%s') failed: %s", sce->data.path->str, g_strerror(sce->data.err));
|
||||
stat_cache_entry_release(vr);
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
} else if (!S_ISDIR(sce->data.st.st_mode)) {
|
||||
stat_cache_entry_release(vr);
|
||||
stat_cache_entry_release(vr, sce);VR_DEBUG(vr, "%s", "no dir");
|
||||
return HANDLER_GO_ON;
|
||||
} else if (vr->request.uri.path->str[vr->request.uri.path->len-1] != G_DIR_SEPARATOR) {
|
||||
GString *host, *uri;
|
||||
if (!vrequest_handle_direct(vr)) {
|
||||
stat_cache_entry_release(vr);
|
||||
stat_cache_entry_release(vr, sce);
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
/* redirect to scheme + host + path + / + querystring if directory without trailing slash */
|
||||
|
@ -230,7 +229,7 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
|
|||
vr->response.http_status = 301;
|
||||
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Location"), GSTR_LEN(uri));
|
||||
g_string_free(uri, TRUE);
|
||||
stat_cache_entry_release(vr);
|
||||
stat_cache_entry_release(vr, sce);
|
||||
return HANDLER_GO_ON;
|
||||
} else {
|
||||
/* everything ok, we have the directory listing */
|
||||
|
@ -246,7 +245,7 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
|
|||
gboolean hide;
|
||||
|
||||
if (!vrequest_handle_direct(vr)) {
|
||||
stat_cache_entry_release(vr);
|
||||
stat_cache_entry_release(vr, sce);
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
vr->response.http_status = 200;
|
||||
|
@ -258,7 +257,7 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
|
|||
etag_set_header(vr, &sce->data.st, &cachable);
|
||||
if (cachable) {
|
||||
vr->response.http_status = 304;
|
||||
stat_cache_entry_release(vr);
|
||||
stat_cache_entry_release(vr, sce);
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
|
@ -408,7 +407,7 @@ static handler_t dirlist(vrequest *vr, gpointer param, gpointer *context) {
|
|||
g_array_free(files, TRUE);
|
||||
}
|
||||
|
||||
stat_cache_entry_release(vr);
|
||||
stat_cache_entry_release(vr, sce);
|
||||
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
|
|
@ -186,7 +186,9 @@ static action* core_docroot(server *srv, plugin* p, value *val) {
|
|||
|
||||
static handler_t core_handle_static(vrequest *vr, gpointer param, gpointer *context) {
|
||||
int fd;
|
||||
stat_cache_entry *sce;
|
||||
struct stat st;
|
||||
int err;
|
||||
handler_t res;
|
||||
|
||||
UNUSED(param);
|
||||
UNUSED(context);
|
||||
|
@ -201,63 +203,45 @@ static handler_t core_handle_static(vrequest *vr, gpointer param, gpointer *cont
|
|||
|
||||
if (vrequest_is_handled(vr)) return HANDLER_GO_ON;
|
||||
|
||||
if (!vr->stat_cache_entry) {
|
||||
if (vr->physical.path->len == 0) return HANDLER_GO_ON;
|
||||
}
|
||||
if (vr->physical.path->len == 0) return HANDLER_GO_ON;
|
||||
|
||||
sce = stat_cache_get(vr, vr->physical.path);
|
||||
if (!sce)
|
||||
return HANDLER_WAIT_FOR_EVENT;
|
||||
res = stat_cache_get(vr, vr->physical.path, &st, &err, &fd);
|
||||
if (res == HANDLER_WAIT_FOR_EVENT)
|
||||
return res;
|
||||
|
||||
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
|
||||
VR_DEBUG(vr, "try serving static file: '%s'", vr->physical.path->str);
|
||||
}
|
||||
|
||||
if (sce->data.failed) {
|
||||
/* stat failed */
|
||||
stat_cache_entry_release(vr);
|
||||
if (res == HANDLER_ERROR) {
|
||||
/* open or fstat failed */
|
||||
|
||||
switch (sce->data.err) {
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
return HANDLER_GO_ON;
|
||||
case EACCES:
|
||||
if (!vrequest_handle_direct(vr)) {
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
vr->response.http_status = 403;
|
||||
VR_DEBUG(vr, "stat('%s') failed: %s", sce->data.path->str, g_strerror(sce->data.err));
|
||||
return HANDLER_GO_ON;
|
||||
default:
|
||||
VR_ERROR(vr, "stat('%s') failed: %s", sce->data.path->str, g_strerror(sce->data.err));
|
||||
if (!vrequest_handle_direct(vr)) {
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
} else if (S_ISDIR(sce->data.st.st_mode)) {
|
||||
stat_cache_entry_release(vr);
|
||||
|
||||
switch (err) {
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
vr->response.http_status = 404;
|
||||
return HANDLER_GO_ON;
|
||||
case EACCES:
|
||||
vr->response.http_status = 403;
|
||||
return HANDLER_GO_ON;
|
||||
default:
|
||||
VR_ERROR(vr, "stat() or open() for '%s' failed: %s", vr->physical.path->str, g_strerror(err));
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
} else if (S_ISDIR(st.st_mode)) {
|
||||
return HANDLER_GO_ON;
|
||||
} else if (!S_ISREG(sce->data.st.st_mode)) {
|
||||
} else if (!S_ISREG(st.st_mode)) {
|
||||
if (CORE_OPTION(CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) {
|
||||
VR_DEBUG(vr, "not a regular file: '%s'", vr->physical.path->str);
|
||||
}
|
||||
if (!vrequest_handle_direct(vr)) {
|
||||
stat_cache_entry_release(vr);
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
vr->response.http_status = 403;
|
||||
} else if ((fd = open(vr->physical.path->str, O_RDONLY)) == -1) {
|
||||
stat_cache_entry_release(vr);
|
||||
switch (errno) {
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
return HANDLER_GO_ON;
|
||||
case EACCES:
|
||||
if (!vrequest_handle_direct(vr)) return HANDLER_ERROR;
|
||||
vr->response.http_status = 403;
|
||||
return HANDLER_GO_ON;
|
||||
default:
|
||||
VR_DEBUG(vr, "open('%s') failed: %s", vr->physical.path->str, g_strerror(errno));
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
} else {
|
||||
GString *mime_str;
|
||||
gboolean cachable;
|
||||
|
@ -267,15 +251,13 @@ static handler_t core_handle_static(vrequest *vr, gpointer param, gpointer *cont
|
|||
|
||||
if (!vrequest_handle_direct(vr)) {
|
||||
close(fd);
|
||||
stat_cache_entry_release(vr);
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
|
||||
etag_set_header(vr, &sce->data.st, &cachable);
|
||||
etag_set_header(vr, &st, &cachable);
|
||||
if (cachable) {
|
||||
vr->response.http_status = 304;
|
||||
close(fd);
|
||||
stat_cache_entry_release(vr);
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
|
@ -285,11 +267,9 @@ static handler_t core_handle_static(vrequest *vr, gpointer param, gpointer *cont
|
|||
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(mime_str));
|
||||
else
|
||||
http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("application/octet-stream"));
|
||||
chunkqueue_append_file_fd(vr->out, NULL, 0, sce->data.st.st_size, fd);
|
||||
chunkqueue_append_file_fd(vr->out, NULL, 0, st.st_size, fd);
|
||||
}
|
||||
|
||||
stat_cache_entry_release(vr);
|
||||
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
|
|
227
src/stat_cache.c
227
src/stat_cache.c
|
@ -1,11 +1,12 @@
|
|||
#include <lighttpd/base.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
|
||||
static void stat_cache_job_cb(struct ev_loop *loop, ev_async *w, int revents);
|
||||
static void stat_cache_delete_cb(struct ev_loop *loop, ev_timer *w, int revents);
|
||||
static gpointer stat_cache_thread(gpointer data);
|
||||
static void stat_cache_entry_free(stat_cache_entry *sce);
|
||||
static stat_cache_entry *stat_cache_get_internal(vrequest *vr, GString *path, gboolean dir);
|
||||
|
||||
void stat_cache_new(worker *wrk, gdouble ttl) {
|
||||
stat_cache *sc;
|
||||
|
@ -18,6 +19,7 @@ void stat_cache_new(worker *wrk, gdouble ttl) {
|
|||
sc = g_slice_new0(stat_cache);
|
||||
sc->ttl = ttl;
|
||||
sc->entries = g_hash_table_new_full((GHashFunc)g_string_hash, (GEqualFunc)g_string_equal, NULL, NULL);
|
||||
sc->dirlists = g_hash_table_new_full((GHashFunc)g_string_hash, (GEqualFunc)g_string_equal, NULL, NULL);
|
||||
sc->job_queue_in = g_async_queue_new();
|
||||
sc->job_queue_out = g_async_queue_new();
|
||||
|
||||
|
@ -61,6 +63,7 @@ void stat_cache_free(stat_cache *sc) {
|
|||
g_async_queue_unref(sc->job_queue_in);
|
||||
g_async_queue_unref(sc->job_queue_out);
|
||||
g_hash_table_destroy(sc->entries);
|
||||
g_hash_table_destroy(sc->dirlists);
|
||||
g_slice_free(stat_cache, sc);
|
||||
}
|
||||
|
||||
|
@ -75,13 +78,21 @@ static void stat_cache_delete_cb(struct ev_loop *loop, ev_timer *w, int revents)
|
|||
while ((wqe = waitqueue_pop(&sc->delete_queue)) != NULL) {
|
||||
/* stat cache entry TTL over */
|
||||
sce = wqe->data;
|
||||
|
||||
if (sce->cached) {
|
||||
if (sce->type == STAT_CACHE_ENTRY_SINGLE)
|
||||
g_hash_table_remove(sc->entries, sce->data.path);
|
||||
else
|
||||
g_hash_table_remove(sc->dirlists, sce->data.path);
|
||||
|
||||
sce->cached = FALSE;
|
||||
}
|
||||
|
||||
if (sce->refcount) {
|
||||
/* if there are still vrequests using this entry just requeue it */
|
||||
waitqueue_push(&sc->delete_queue, wqe);
|
||||
} else {
|
||||
/* no more vrequests using this entry, finally free it */
|
||||
if (sce->in_cache)
|
||||
g_hash_table_remove(sc->entries, sce->data.path);
|
||||
stat_cache_entry_free(sce);
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +100,7 @@ static void stat_cache_delete_cb(struct ev_loop *loop, ev_timer *w, int revents)
|
|||
waitqueue_update(&sc->delete_queue);
|
||||
}
|
||||
|
||||
/* called whenever an async stat job has finished */
|
||||
static void stat_cache_job_cb(struct ev_loop *loop, ev_async *w, int revents) {
|
||||
guint i;
|
||||
stat_cache_entry *sce;
|
||||
|
@ -102,12 +114,18 @@ static void stat_cache_job_cb(struct ev_loop *loop, ev_async *w, int revents) {
|
|||
if (sce->data.failed)
|
||||
sc->errors++;
|
||||
|
||||
/* queue pending vrequests */
|
||||
for (i = 0; i < sce->vrequests->len; i++) {
|
||||
vr = g_ptr_array_index(sce->vrequests, i);
|
||||
vrequest_joblist_append(vr);
|
||||
if (sce->type == STAT_CACHE_ENTRY_SINGLE) {
|
||||
sce->refcount--;
|
||||
g_ptr_array_remove_fast(vr->stat_cache_entries, sce);
|
||||
}
|
||||
}
|
||||
|
||||
g_ptr_array_set_size(sce->vrequests, 0);
|
||||
sce->refcount--;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,7 +201,7 @@ static gpointer stat_cache_thread(gpointer data) {
|
|||
|
||||
g_string_truncate(str, sce->data.path->len);
|
||||
/* make sure the path ends with / (or whatever) */
|
||||
if (sce->data.path->str[sce->data.path->len-1] != G_DIR_SEPARATOR)
|
||||
if (!sce->data.path->len || sce->data.path->str[sce->data.path->len-1] != G_DIR_SEPARATOR)
|
||||
g_string_append_c(str, G_DIR_SEPARATOR);
|
||||
g_string_append_len(str, GSTR_LEN(sced.path));
|
||||
|
||||
|
@ -216,126 +234,121 @@ static gpointer stat_cache_thread(gpointer data) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static stat_cache_entry *stat_cache_get_internal(vrequest *vr, GString *path, gboolean dir) {
|
||||
stat_cache *sc;
|
||||
static stat_cache_entry *stat_cache_entry_new(GString *path) {
|
||||
stat_cache_entry *sce;
|
||||
|
||||
sc = vr->con->wrk->stat_cache;
|
||||
sce = g_slice_new0(stat_cache_entry);
|
||||
sce->data.path = g_string_new_len(GSTR_LEN(path));
|
||||
sce->vrequests = g_ptr_array_sized_new(8);
|
||||
sce->state = STAT_CACHE_ENTRY_WAITING;
|
||||
sce->queue_elem.data = sce;
|
||||
sce->refcount = 1;
|
||||
sce->cached = TRUE;
|
||||
|
||||
/* lookup entry in cache */
|
||||
return sce;
|
||||
}
|
||||
|
||||
handler_t stat_cache_get_dirlist(vrequest *vr, GString *path, stat_cache_entry **result) {
|
||||
stat_cache *sc;
|
||||
stat_cache_entry *sce;
|
||||
guint i;
|
||||
|
||||
sc = vr->con->wrk->stat_cache;
|
||||
sce = g_hash_table_lookup(sc->dirlists, path);
|
||||
|
||||
if (sce) {
|
||||
/* cache hit, check state */
|
||||
if (g_atomic_int_get(&sce->state) == STAT_CACHE_ENTRY_WAITING) {
|
||||
/* already waiting for it? */
|
||||
for (i = 0; i < vr->stat_cache_entries->len; i++) {
|
||||
if (g_ptr_array_index(vr->stat_cache_entries, i) == sce)
|
||||
return HANDLER_WAIT_FOR_EVENT;
|
||||
}
|
||||
stat_cache_entry_acquire(vr, sce);
|
||||
return HANDLER_WAIT_FOR_EVENT;
|
||||
}
|
||||
|
||||
sce->refcount++;
|
||||
g_ptr_array_add(vr->stat_cache_entries, sce);
|
||||
sc->hits++;
|
||||
*result = sce;
|
||||
return HANDLER_GO_ON;
|
||||
} else {
|
||||
/* cache miss, allocate new entry */
|
||||
sce = stat_cache_entry_new(path);
|
||||
sce->type = STAT_CACHE_ENTRY_DIR;
|
||||
sce->dirlist = g_array_sized_new(FALSE, FALSE, sizeof(stat_cache_entry_data), 32);
|
||||
stat_cache_entry_acquire(vr, sce);
|
||||
waitqueue_push(&sc->delete_queue, &sce->queue_elem);
|
||||
g_hash_table_insert(sc->dirlists, sce->data.path, sce);
|
||||
g_async_queue_push(sc->job_queue_out, sce);
|
||||
sc->misses++;
|
||||
return HANDLER_WAIT_FOR_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
handler_t stat_cache_get(vrequest *vr, GString *path, struct stat *st, int *err, int *fd) {
|
||||
stat_cache *sc;
|
||||
stat_cache_entry *sce;
|
||||
guint i;
|
||||
|
||||
sc = vr->con->wrk->stat_cache;
|
||||
sce = g_hash_table_lookup(sc->entries, path);
|
||||
|
||||
if (sce) {
|
||||
/* cache hit, check state */
|
||||
if (g_atomic_int_get(&sce->state) == STAT_CACHE_ENTRY_FINISHED) {
|
||||
/* stat info available, check if it is fresh */
|
||||
if (!((sce->type == STAT_CACHE_ENTRY_DIR) ^ dir) && sce->ts >= (CUR_TS(vr->con->wrk) - (ev_tstamp)sc->ttl)) {
|
||||
/* entry fresh */
|
||||
if (!vr->stat_cache_entry) {
|
||||
sc->hits++;
|
||||
vr->stat_cache_entry = sce;
|
||||
sce->refcount++;
|
||||
}
|
||||
|
||||
return sce;
|
||||
} else {
|
||||
/* entry old */
|
||||
if (sce->refcount == 0) {
|
||||
/* no vrequests working on the entry, reuse it */
|
||||
if (sce->type == STAT_CACHE_ENTRY_DIR) {
|
||||
guint i;
|
||||
|
||||
/* free old entries */
|
||||
for (i = 0; i < sce->dirlist->len; i++) {
|
||||
g_string_free(g_array_index(sce->dirlist, stat_cache_entry_data, i).path, TRUE);
|
||||
}
|
||||
|
||||
if (!dir) {
|
||||
g_array_free(sce->dirlist, TRUE);
|
||||
sce->type = STAT_CACHE_ENTRY_SINGLE;
|
||||
} else {
|
||||
g_array_set_size(sce->dirlist, 0);
|
||||
}
|
||||
} else {
|
||||
/* single file */
|
||||
if (dir) {
|
||||
sce->dirlist = g_array_sized_new(FALSE, FALSE, sizeof(stat_cache_entry_data), 32);
|
||||
sce->type = STAT_CACHE_ENTRY_DIR;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* there are still vrequests using this entry, replace with a new one */
|
||||
sce->in_cache = FALSE;
|
||||
sce = g_slice_new0(stat_cache_entry);
|
||||
sce->data.path = g_string_new_len(GSTR_LEN(path));
|
||||
sce->vrequests = g_ptr_array_sized_new(8);
|
||||
sce->in_cache = TRUE;
|
||||
sce->queue_elem.data = sce;
|
||||
g_hash_table_replace(sc->entries, sce->data.path, sce);
|
||||
if (dir) {
|
||||
sce->type = STAT_CACHE_ENTRY_DIR;
|
||||
sce->dirlist = g_array_sized_new(FALSE, FALSE, sizeof(stat_cache_entry_data), 32);
|
||||
} else {
|
||||
sce->type = STAT_CACHE_ENTRY_SINGLE;
|
||||
}
|
||||
}
|
||||
|
||||
sce->ts = CUR_TS(vr->con->wrk);
|
||||
vr->stat_cache_entry = sce;
|
||||
g_ptr_array_add(sce->vrequests, vr);
|
||||
sce->refcount++;
|
||||
waitqueue_push(&sc->delete_queue, &sce->queue_elem);
|
||||
sce->state = STAT_CACHE_ENTRY_WAITING;
|
||||
g_async_queue_push(sc->job_queue_out, sce);
|
||||
sc->misses++;
|
||||
return NULL;
|
||||
if (g_atomic_int_get(&sce->state) == STAT_CACHE_ENTRY_WAITING) {
|
||||
/* already waiting for it? */
|
||||
for (i = 0; i < vr->stat_cache_entries->len; i++) {
|
||||
if (g_ptr_array_index(vr->stat_cache_entries, i) == sce)
|
||||
return HANDLER_WAIT_FOR_EVENT;
|
||||
}
|
||||
} else {
|
||||
/* stat info not available (state is STAT_CACHE_ENTRY_WAITING) */
|
||||
vr->stat_cache_entry = sce;
|
||||
g_ptr_array_add(sce->vrequests, vr);
|
||||
sce->refcount++;
|
||||
sc->misses++;
|
||||
return NULL;
|
||||
stat_cache_entry_acquire(vr, sce);
|
||||
return HANDLER_WAIT_FOR_EVENT;
|
||||
}
|
||||
|
||||
sc->hits++;
|
||||
} else {
|
||||
/* cache miss, allocate new entry */
|
||||
sce = g_slice_new0(stat_cache_entry);
|
||||
sce->data.path = g_string_new_len(GSTR_LEN(path));
|
||||
sce->vrequests = g_ptr_array_sized_new(8);
|
||||
sce->ts = CUR_TS(vr->con->wrk);
|
||||
sce->state = STAT_CACHE_ENTRY_WAITING;
|
||||
sce->in_cache = TRUE;
|
||||
sce->queue_elem.data = sce;
|
||||
vr->stat_cache_entry = sce;
|
||||
g_ptr_array_add(sce->vrequests, vr);
|
||||
sce->refcount = 1;
|
||||
sc->misses++;
|
||||
|
||||
if (dir) {
|
||||
sce->type = STAT_CACHE_ENTRY_DIR;
|
||||
sce->dirlist = g_array_sized_new(FALSE, FALSE, sizeof(stat_cache_entry_data), 32);
|
||||
} else {
|
||||
sce->type = STAT_CACHE_ENTRY_SINGLE;
|
||||
}
|
||||
|
||||
sce = stat_cache_entry_new(path);
|
||||
sce->type = STAT_CACHE_ENTRY_SINGLE;
|
||||
stat_cache_entry_acquire(vr, sce);
|
||||
waitqueue_push(&sc->delete_queue, &sce->queue_elem);
|
||||
g_hash_table_insert(sc->entries, sce->data.path, sce);
|
||||
g_async_queue_push(sc->job_queue_out, sce);
|
||||
|
||||
return NULL;
|
||||
sc->misses++;
|
||||
return HANDLER_WAIT_FOR_EVENT;
|
||||
}
|
||||
|
||||
if (fd) {
|
||||
/* open + fstat */
|
||||
if (-1 == (*fd = open(path->str, O_RDONLY))) {
|
||||
*err = errno;
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
if (-1 == fstat(*fd, st)) {
|
||||
*err = errno;
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
} else {
|
||||
/* stat */
|
||||
if (-1 == stat(path->str, st)) {
|
||||
*err = errno;
|
||||
return HANDLER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return HANDLER_GO_ON;
|
||||
}
|
||||
|
||||
stat_cache_entry *stat_cache_get(vrequest *vr, GString *path) {
|
||||
return stat_cache_get_internal(vr, path, FALSE);
|
||||
void stat_cache_entry_acquire(vrequest *vr, stat_cache_entry *sce) {
|
||||
sce->refcount++;
|
||||
g_ptr_array_add(vr->stat_cache_entries, sce);
|
||||
g_ptr_array_add(sce->vrequests, vr);
|
||||
}
|
||||
|
||||
stat_cache_entry *stat_cache_get_dir(vrequest *vr, GString *path) {
|
||||
return stat_cache_get_internal(vr, path, TRUE);
|
||||
}
|
||||
|
||||
void stat_cache_entry_release(vrequest *vr) {
|
||||
vr->stat_cache_entry->refcount--;
|
||||
vr->stat_cache_entry = NULL;
|
||||
void stat_cache_entry_release(vrequest *vr, stat_cache_entry *sce) {
|
||||
sce->refcount--;
|
||||
g_ptr_array_remove_fast(sce->vrequests, vr);
|
||||
g_ptr_array_remove_fast(vr->stat_cache_entries, sce);
|
||||
}
|
||||
|
|
|
@ -126,12 +126,16 @@ vrequest* vrequest_new(connection *con, vrequest_handler handle_response_headers
|
|||
vr->out = vr->filters_out.in;
|
||||
vr->vr_out = vr->filters_out.out;
|
||||
|
||||
vr->stat_cache_entries = g_ptr_array_sized_new(2);
|
||||
|
||||
action_stack_init(&vr->action_stack);
|
||||
|
||||
return vr;
|
||||
}
|
||||
|
||||
void vrequest_free(vrequest* vr) {
|
||||
guint i;
|
||||
|
||||
action_stack_clear(vr, &vr->action_stack);
|
||||
plugins_handle_vrclose(vr);
|
||||
g_ptr_array_free(vr->plugin_ctx, TRUE);
|
||||
|
@ -151,10 +155,19 @@ void vrequest_free(vrequest* vr) {
|
|||
|
||||
g_slice_free1(vr->con->srv->option_def_values->len * sizeof(option_value), vr->options);
|
||||
|
||||
|
||||
for (i = 0; i < vr->stat_cache_entries->len; i++) {
|
||||
stat_cache_entry *sce = g_ptr_array_index(vr->stat_cache_entries, i);
|
||||
stat_cache_entry_release(vr, sce);
|
||||
}
|
||||
g_ptr_array_free(vr->stat_cache_entries, TRUE);
|
||||
|
||||
g_slice_free(vrequest, vr);
|
||||
}
|
||||
|
||||
void vrequest_reset(vrequest *vr) {
|
||||
guint i;
|
||||
|
||||
action_stack_reset(vr, &vr->action_stack);
|
||||
plugins_handle_vrclose(vr);
|
||||
{
|
||||
|
@ -180,9 +193,9 @@ void vrequest_reset(vrequest *vr) {
|
|||
vr->job_queue_link = NULL;
|
||||
}
|
||||
|
||||
if (vr->stat_cache_entry) {
|
||||
g_ptr_array_remove_fast(vr->stat_cache_entry->vrequests, vr);
|
||||
stat_cache_entry_release(vr);
|
||||
for (i = 0; i < vr->stat_cache_entries->len; i++) {
|
||||
stat_cache_entry *sce = g_ptr_array_index(vr->stat_cache_entries, i);
|
||||
stat_cache_entry_release(vr, sce);
|
||||
}
|
||||
|
||||
memcpy(vr->options, vr->con->srv->option_def_values->data, vr->con->srv->option_def_values->len * sizeof(option_value));
|
||||
|
|
Loading…
Reference in New Issue