Browse Source

[mod_auth,mod_vhostdb] add caching option (fixes #2805)

auth.cache    = ("max-age" => "600")
vhostdb.cache = ("max-age" => "600")

If specified with an empty array, default max-age is 600 secs (10 mins)
auth.cache    = ()
vhostdb.cache = ()

(Note: cache expiration occurs every 8 seconds, so maximum cache time
 might be up to max-age + 8 seconds)

x-ref:
  "mod_auth caching"
  https://redmine.lighttpd.net/issues/2805
personal/stbuehler/ci-build
Glenn Strauss 12 months ago
parent
commit
e11514b086
  1. 271
      src/mod_auth.c
  2. 244
      src/mod_vhostdb.c
  3. 8
      src/splaytree.h
  4. 64
      src/stat_cache.c

271
src/mod_auth.c

@ -10,14 +10,22 @@
#include "http_auth.h"
#include "http_header.h"
#include "log.h"
#include "safe_memclear.h"
#include "splaytree.h"
/**
* auth framework
*/
typedef struct {
splay_tree *sptree; /* data in nodes of tree are (http_auth_cache_entry *)*/
time_t max_age;
} http_auth_cache;
typedef struct {
const http_auth_backend_t *auth_backend;
const array *auth_require;
http_auth_cache *auth_cache;
unsigned int auth_extern_authn;
} plugin_config;
@ -27,23 +35,191 @@ typedef struct {
plugin_config conf;
} plugin_data;
typedef struct {
const struct http_auth_require_t *require;
time_t ctime;
int dalgo;
uint32_t dlen;
uint32_t ulen;
char *username;
char *pwdigest;
} http_auth_cache_entry;
static http_auth_cache_entry *
http_auth_cache_entry_init (const struct http_auth_require_t * const require, const int dalgo, const char *username, const uint32_t ulen, const char *pw, const uint32_t pwlen)
{
/*(similar to buffer_copy_string_len() for each element,
* but allocate exact lengths in single chunk of memory
* for cache to avoid wasting space and for memory locality)*/
/* http_auth_require_t is stored instead of copying realm
*(store pointer to http_auth_require_t, which is persistent
* and will be different for each realm + permissions combo)*/
http_auth_cache_entry * const ae =
malloc(sizeof(http_auth_cache_entry) + ulen + pwlen);
force_assert(ae);
ae->require = require;
ae->ctime = log_epoch_secs;
ae->dalgo = dalgo;
ae->ulen = ulen;
ae->dlen = pwlen;
ae->username = (char *)(ae + 1);
ae->pwdigest = ae->username + ulen;
memcpy(ae->username, username, ulen);
memcpy(ae->pwdigest, pw, pwlen);
return ae;
}
static void
http_auth_cache_entry_free (void *data)
{
http_auth_cache_entry * const ae = data;
safe_memclear(ae->pwdigest, ae->dlen);
free(ae);
}
static void
http_auth_cache_free (http_auth_cache *ac)
{
splay_tree *sptree = ac->sptree;
while (sptree) {
http_auth_cache_entry_free(sptree->data);
sptree = splaytree_delete(sptree, sptree->key);
}
free(ac);
}
static http_auth_cache *
http_auth_cache_init (const array *opts)
{
http_auth_cache *ac = malloc(sizeof(http_auth_cache));
force_assert(ac);
ac->sptree = NULL;
ac->max_age = 600; /* 10 mins */
for (uint32_t i = 0, used = opts->used; i < used; ++i) {
data_string *ds = (data_string *)opts->data[i];
if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("max-age"))) {
if (ds->type == TYPE_STRING)
ac->max_age = (time_t)strtol(ds->value.ptr, NULL, 10);
else if (ds->type == TYPE_INTEGER)
ac->max_age = (time_t)((data_integer *)ds)->value;
}
}
return ac;
}
static int
http_auth_cache_hash (const struct http_auth_require_t * const require, const char *username, const uint32_t ulen)
{
uint32_t h = /*(hash pointer value, which includes realm and permissions)*/
djbhash((char *)(intptr_t)require, sizeof(intptr_t), DJBHASH_INIT);
h = djbhash(username, ulen, h);
/* strip highest bit of hash value for splaytree (see splaytree_djbhash())*/
return (int32_t)(h & ~(((uint32_t)1) << 31));
}
static http_auth_cache_entry *
http_auth_cache_query (splay_tree ** const sptree, const int ndx)
{
*sptree = splaytree_splay(*sptree, ndx);
return (*sptree && (*sptree)->key == ndx) ? (*sptree)->data : NULL;
}
static void
http_auth_cache_insert (splay_tree ** const sptree, const int ndx, void * const data, void(data_free_fn)(void *))
{
/*(not necessary to re-splay (with current usage) since single-threaded
* and splaytree has not been modified since http_auth_cache_query())*/
/* *sptree = splaytree_splay(*sptree, ndx); */
if (NULL == *sptree || (*sptree)->key != ndx)
*sptree = splaytree_insert(*sptree, ndx, data);
else { /* collision; replace old entry */
data_free_fn((*sptree)->data);
(*sptree)->data = data;
}
}
/* walk though cache, collect expired ids, and remove them in a second loop */
static void
mod_auth_tag_old_entries (splay_tree * const t, int * const keys, int * const ndx, const time_t max_age, const time_t cur_ts)
{
if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
if (t->left)
mod_auth_tag_old_entries(t->left, keys, ndx, max_age, cur_ts);
if (t->right)
mod_auth_tag_old_entries(t->right, keys, ndx, max_age, cur_ts);
if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
const http_auth_cache_entry * const ae = t->data;
if (cur_ts - ae->ctime > max_age)
keys[(*ndx)++] = t->key;
}
__attribute_noinline__
static void
mod_auth_periodic_cleanup(splay_tree **sptree_ptr, const time_t max_age, const time_t cur_ts)
{
splay_tree *sptree = *sptree_ptr;
int max_ndx, i;
int keys[8192]; /* 32k size on stack */
do {
if (!sptree) break;
max_ndx = 0;
mod_auth_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts);
for (i = 0; i < max_ndx; ++i) {
int ndx = keys[i];
sptree = splaytree_splay(sptree, ndx);
if (sptree && sptree->key == ndx) {
http_auth_cache_entry_free(sptree->data);
sptree = splaytree_delete(sptree, ndx);
}
}
} while (max_ndx == sizeof(keys)/sizeof(int));
*sptree_ptr = sptree;
}
TRIGGER_FUNC(mod_auth_periodic)
{
const plugin_data * const p = p_d;
const time_t cur_ts = log_epoch_secs;
if (cur_ts & 0x7) return HANDLER_GO_ON; /*(continue once each 8 sec)*/
UNUSED(srv);
/* future: might construct array of (http_auth_cache *) at startup
* to avoid the need to search for them here */
for (int i = 0, used = p->nconfig; i < used; ++i) {
const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; cpv->k_id != -1; ++cpv) {
if (cpv->k_id != 3) continue; /* k_id == 3 for auth.cache */
if (cpv->vtype != T_CONFIG_LOCAL) continue;
http_auth_cache *ac = cpv->v.v;
mod_auth_periodic_cleanup(&ac->sptree, ac->max_age, cur_ts);
}
}
return HANDLER_GO_ON;
}
static handler_t mod_auth_check_basic(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend);
static handler_t mod_auth_check_digest(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend);
static handler_t mod_auth_check_extern(request_st *r, void *p_d, const struct http_auth_require_t *require, const struct http_auth_backend_t *backend);
INIT_FUNC(mod_auth_init) {
static const http_auth_scheme_t http_auth_scheme_basic = { "basic", mod_auth_check_basic, NULL };
static const http_auth_scheme_t http_auth_scheme_digest = { "digest", mod_auth_check_digest, NULL };
static http_auth_scheme_t http_auth_scheme_basic = { "basic", mod_auth_check_basic, NULL };
static http_auth_scheme_t http_auth_scheme_digest = { "digest", mod_auth_check_digest, NULL };
static const http_auth_scheme_t http_auth_scheme_extern = { "extern", mod_auth_check_extern, NULL };
plugin_data *p;
plugin_data *p = calloc(1, sizeof(*p));
force_assert(p);
/* register http_auth_scheme_* */
http_auth_scheme_basic.p_d = p;
http_auth_scheme_set(&http_auth_scheme_basic);
http_auth_scheme_digest.p_d = p;
http_auth_scheme_set(&http_auth_scheme_digest);
http_auth_scheme_set(&http_auth_scheme_extern);
p = calloc(1, sizeof(*p));
return p;
}
@ -59,6 +235,9 @@ FREE_FUNC(mod_auth_free) {
case 1: /* auth.require */
array_free(cpv->v.v);
break;
case 3: /* auth.cache */
http_auth_cache_free(cpv->v.v);
break;
default:
break;
}
@ -372,6 +551,11 @@ static void mod_auth_merge_config_cpv(plugin_config * const pconf, const config_
break;
case 2: /* auth.extern-authn */
pconf->auth_extern_authn = cpv->v.u;
break;
case 3: /* auth.cache */
if (cpv->vtype == T_CONFIG_LOCAL)
pconf->auth_cache = cpv->v.v;
break;
default:/* should not happen */
return;
}
@ -403,6 +587,9 @@ SETDEFAULTS_FUNC(mod_auth_set_defaults) {
,{ CONST_STR_LEN("auth.extern-authn"),
T_CONFIG_BOOL,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("auth.cache"),
T_CONFIG_ARRAY,
T_CONFIG_SCOPE_CONNECTION }
,{ NULL, 0,
T_CONFIG_UNSET,
T_CONFIG_SCOPE_UNSET }
@ -445,6 +632,10 @@ SETDEFAULTS_FUNC(mod_auth_set_defaults) {
break;
case 2: /* auth.extern-authn */
break;
case 3: /* auth.cache */
cpv->v.v = http_auth_cache_init(cpv->v.a);
cpv->vtype = T_CONFIG_LOCAL;
break;
default:/* should not happen */
break;
}
@ -488,12 +679,14 @@ static handler_t mod_auth_uri_handler(request_st * const r, void *p_d) {
}
}
int mod_auth_plugin_init(plugin *p);
int mod_auth_plugin_init(plugin *p) {
p->version = LIGHTTPD_VERSION_ID;
p->name = "auth";
p->init = mod_auth_init;
p->set_defaults = mod_auth_set_defaults;
p->handle_trigger = mod_auth_periodic;
p->handle_uri_clean = mod_auth_uri_handler;
p->cleanup = mod_auth_free;
@ -544,8 +737,6 @@ static handler_t mod_auth_check_basic(request_st * const r, void *p_d, const str
char *pw;
handler_t rc = HANDLER_UNSET;
UNUSED(p_d);
if (NULL == backend) {
log_error(r->conf.errh, __FILE__, __LINE__, "auth.backend not configured for %s", r->uri.path.ptr);
r->http_status = 500;
@ -584,13 +775,41 @@ static handler_t mod_auth_check_basic(request_st * const r, void *p_d, const str
return mod_auth_send_400_bad_request(r);
}
uint32_t pwlen = buffer_string_length(username);
buffer_string_set_length(username, pw - username->ptr);
pw++;
pwlen -= (pw - username->ptr);
plugin_data * const p = p_d;
splay_tree ** sptree = p->conf.auth_cache
? &p->conf.auth_cache->sptree
: NULL;
http_auth_cache_entry *ae = NULL;
int ndx = -1;
if (sptree) {
ndx = http_auth_cache_hash(require, CONST_BUF_LEN(username));
ae = http_auth_cache_query(sptree, ndx);
if (ae && ae->require == require
&& buffer_is_equal_string(username, ae->username, ae->ulen))
rc = http_auth_const_time_memeq_pad(ae->pwdigest, ae->dlen,
pw, pwlen)
? HANDLER_GO_ON
: HANDLER_ERROR;
else /*(not found or hash collision)*/
ae = NULL;
}
if (NULL == ae) /* (HANDLER_UNSET == rc) */
rc = backend->basic(r, backend->p_d, require, username, pw);
rc = backend->basic(r, backend->p_d, require, username, pw);
switch (rc) {
case HANDLER_GO_ON:
http_auth_setenv(r, CONST_BUF_LEN(username), CONST_STR_LEN("Basic"));
if (sptree && NULL == ae) { /*(cache (new) successful result)*/
ae = http_auth_cache_entry_init(require, 0, CONST_BUF_LEN(username),
pw, pwlen);
http_auth_cache_insert(sptree, ndx, ae, http_auth_cache_entry_free);
}
break;
case HANDLER_WAIT_FOR_EVENT:
case HANDLER_FINISHED:
@ -988,8 +1207,6 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st
dkv[7].ptr = &nc;
dkv[8].ptr = &respons;
UNUSED(p_d);
if (NULL == backend) {
log_error(r->conf.errh, __FILE__, __LINE__,
"auth.backend not configured for %s", r->uri.path.ptr);
@ -1192,7 +1409,33 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st
}
}
switch (backend->digest(r, backend->p_d, &ai)) {
handler_t rc = HANDLER_UNSET;
plugin_data * const p = p_d;
splay_tree ** sptree = p->conf.auth_cache
? &p->conf.auth_cache->sptree
: NULL;
http_auth_cache_entry *ae = NULL;
int ndx = -1;
if (sptree) {
ndx = http_auth_cache_hash(require, ai.username, ai.ulen);
ae = http_auth_cache_query(sptree, ndx);
if (ae && ae->require == require
&& ae->dalgo == ai.dalgo
&& ae->dlen == ai.dlen
&& ae->ulen == ai.ulen
&& 0 == memcmp(ae->username, ai.username, ai.ulen)) {
rc = HANDLER_GO_ON;
memcpy(ai.digest, ae->pwdigest, ai.dlen);
}
else /*(not found or hash collision)*/
ae = NULL;
}
if (HANDLER_UNSET == rc)
rc = backend->digest(r, backend->p_d, &ai);
switch (rc) {
case HANDLER_GO_ON:
break;
case HANDLER_WAIT_FOR_EVENT:
@ -1208,6 +1451,12 @@ static handler_t mod_auth_check_digest(request_st * const r, void *p_d, const st
return mod_auth_send_401_unauthorized_digest(r, require, 0);
}
if (sptree && NULL == ae) { /*(cache digest from backend)*/
ae = http_auth_cache_entry_init(require, ai.dalgo, ai.username, ai.ulen,
(char *)ai.digest, ai.dlen);
http_auth_cache_insert(sptree, ndx, ae, http_auth_cache_entry_free);
}
const char *m = get_http_method_name(r->http_method);
force_assert(m);

244
src/mod_vhostdb.c

@ -11,6 +11,7 @@
#include "http_vhostdb.h"
#include "log.h"
#include "stat_cache.h"
#include "splaytree.h"
#include <stdlib.h>
#include <string.h>
@ -19,8 +20,14 @@
* vhostdb framework
*/
typedef struct {
splay_tree *sptree; /* data in nodes of tree are (vhostdb_cache_entry *) */
time_t max_age;
} vhostdb_cache;
typedef struct {
const http_vhostdb_backend_t *vhostdb_backend;
vhostdb_cache *vhostdb_cache;
} plugin_config;
typedef struct {
@ -31,6 +38,98 @@ typedef struct {
buffer tmp_buf;
} plugin_data;
typedef struct {
char *server_name;
char *document_root;
uint32_t slen;
uint32_t dlen;
time_t ctime;
} vhostdb_cache_entry;
static vhostdb_cache_entry *
vhostdb_cache_entry_init (const buffer * const server_name, const buffer * const docroot)
{
const uint32_t slen = buffer_string_length(server_name);
const uint32_t dlen = buffer_string_length(docroot);
vhostdb_cache_entry * const ve =
malloc(sizeof(vhostdb_cache_entry) + slen + dlen);
ve->ctime = log_epoch_secs;
ve->slen = slen;
ve->dlen = dlen;
ve->server_name = (char *)(ve + 1);
ve->document_root = ve->server_name + slen;
memcpy(ve->server_name, server_name->ptr, slen);
memcpy(ve->document_root, docroot->ptr, dlen);
return ve;
}
static void
vhostdb_cache_entry_free (vhostdb_cache_entry *ve)
{
free(ve);
}
static void
vhostdb_cache_free (vhostdb_cache *vc)
{
splay_tree *sptree = vc->sptree;
while (sptree) {
vhostdb_cache_entry_free(sptree->data);
sptree = splaytree_delete(sptree, sptree->key);
}
free(vc);
}
static vhostdb_cache *
vhostdb_cache_init (const array *opts)
{
vhostdb_cache *vc = malloc(sizeof(vhostdb_cache));
force_assert(vc);
vc->sptree = NULL;
vc->max_age = 600; /* 10 mins */
for (uint32_t i = 0, used = opts->used; i < used; ++i) {
data_string *ds = (data_string *)opts->data[i];
if (buffer_is_equal_string(&ds->key, CONST_STR_LEN("max-age"))) {
if (ds->type == TYPE_STRING)
vc->max_age = (time_t)strtol(ds->value.ptr, NULL, 10);
else if (ds->type == TYPE_INTEGER)
vc->max_age = (time_t)((data_integer *)ds)->value;
}
}
return vc;
}
static vhostdb_cache_entry *
mod_vhostdb_cache_query (request_st * const r, plugin_data * const p)
{
const int ndx = splaytree_djbhash(CONST_BUF_LEN(&r->uri.authority));
splay_tree ** const sptree = &p->conf.vhostdb_cache->sptree;
*sptree = splaytree_splay(*sptree, ndx);
vhostdb_cache_entry * const ve =
(*sptree && (*sptree)->key == ndx) ? (*sptree)->data : NULL;
return ve
&& buffer_is_equal_string(&r->uri.authority, ve->server_name, ve->slen)
? ve
: NULL;
}
static void
mod_vhostdb_cache_insert (request_st * const r, plugin_data * const p, vhostdb_cache_entry * const ve)
{
const int ndx = splaytree_djbhash(CONST_BUF_LEN(&r->uri.authority));
splay_tree ** const sptree = &p->conf.vhostdb_cache->sptree;
/*(not necessary to re-splay (with current usage) since single-threaded
* and splaytree has not been modified since mod_vhostdb_cache_query())*/
/* *sptree = splaytree_splay(*sptree, ndx); */
if (NULL == *sptree || (*sptree)->key != ndx)
*sptree = splaytree_insert(*sptree, ndx, ve);
else { /* collision; replace old entry */
vhostdb_cache_entry_free((*sptree)->data);
(*sptree)->data = ve;
}
}
INIT_FUNC(mod_vhostdb_init) {
return calloc(1, sizeof(plugin_data));
}
@ -38,6 +137,22 @@ INIT_FUNC(mod_vhostdb_init) {
FREE_FUNC(mod_vhostdb_free) {
plugin_data *p = p_d;
free(p->tmp_buf.ptr);
if (NULL == p->cvlist) return;
/* (init i to 0 if global context; to 1 to skip empty global context) */
for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; -1 != cpv->k_id; ++cpv) {
if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
switch (cpv->k_id) {
case 1: /* vhostdb.cache */
vhostdb_cache_free(cpv->v.v);
break;
default:
break;
}
}
}
}
static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
@ -46,6 +161,10 @@ static void mod_vhostdb_merge_config_cpv(plugin_config * const pconf, const conf
if (cpv->vtype == T_CONFIG_LOCAL)
pconf->vhostdb_backend = cpv->v.v;
break;
case 1: /* vhostdb.cache */
if (cpv->vtype == T_CONFIG_LOCAL)
pconf->vhostdb_cache = cpv->v.v;
break;
default:/* should not happen */
return;
}
@ -70,6 +189,9 @@ SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {
{ CONST_STR_LEN("vhostdb.backend"),
T_CONFIG_STRING,
T_CONFIG_SCOPE_CONNECTION }
,{ CONST_STR_LEN("vhostdb.cache"),
T_CONFIG_ARRAY,
T_CONFIG_SCOPE_CONNECTION }
,{ NULL, 0,
T_CONFIG_UNSET,
T_CONFIG_SCOPE_UNSET }
@ -97,6 +219,10 @@ SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {
cpv->vtype = T_CONFIG_LOCAL;
}
break;
case 1: /* vhostdb.cache */
cpv->v.v = vhostdb_cache_init(cpv->v.a);
cpv->vtype = T_CONFIG_LOCAL;
break;
default:/* should not happen */
break;
}
@ -113,33 +239,13 @@ SETDEFAULTS_FUNC(mod_vhostdb_set_defaults) {
return HANDLER_GO_ON;
}
typedef struct {
buffer *server_name;
buffer *document_root;
} vhostdb_entry;
static vhostdb_entry * vhostdb_entry_init (void)
{
vhostdb_entry *ve = calloc(1, sizeof(*ve));
ve->server_name = buffer_init();
ve->document_root = buffer_init();
return ve;
}
static void vhostdb_entry_free (vhostdb_entry *ve)
{
buffer_free(ve->server_name);
buffer_free(ve->document_root);
free(ve);
}
REQUEST_FUNC(mod_vhostdb_handle_connection_reset) {
plugin_data *p = p_d;
vhostdb_entry *ve;
vhostdb_cache_entry *ve;
if ((ve = r->plugin_ctx[p->id])) {
r->plugin_ctx[p->id] = NULL;
vhostdb_entry_free(ve);
vhostdb_cache_entry_free(ve);
}
return HANDLER_GO_ON;
@ -153,18 +259,18 @@ static handler_t mod_vhostdb_error_500 (request_st * const r)
return HANDLER_FINISHED;
}
static handler_t mod_vhostdb_found (request_st * const r, vhostdb_entry * const ve)
static handler_t mod_vhostdb_found (request_st * const r, vhostdb_cache_entry * const ve)
{
/* fix virtual server and docroot */
r->server_name = &r->server_name_buf;
buffer_copy_buffer(&r->server_name_buf, ve->server_name);
buffer_copy_buffer(&r->physical.doc_root, ve->document_root);
buffer_copy_string_len(&r->server_name_buf, ve->server_name, ve->slen);
buffer_copy_string_len(&r->physical.doc_root, ve->document_root, ve->dlen);
return HANDLER_GO_ON;
}
REQUEST_FUNC(mod_vhostdb_handle_docroot) {
plugin_data *p = p_d;
vhostdb_entry *ve;
vhostdb_cache_entry *ve;
const http_vhostdb_backend_t *backend;
buffer *b;
stat_cache_entry *sce;
@ -172,18 +278,18 @@ REQUEST_FUNC(mod_vhostdb_handle_docroot) {
/* no host specified? */
if (buffer_string_is_empty(&r->uri.authority)) return HANDLER_GO_ON;
/* XXX: future: implement larger, managed cache
* of database responses (positive and negative) */
/* check if cached this connection */
ve = r->plugin_ctx[p->id];
if (ve && buffer_is_equal(ve->server_name, &r->uri.authority)) {
if (ve
&& buffer_is_equal_string(&r->uri.authority, ve->server_name, ve->slen))
return mod_vhostdb_found(r, ve); /* HANDLER_GO_ON */
}
mod_vhostdb_patch_config(r, p);
if (!p->conf.vhostdb_backend) return HANDLER_GO_ON;
if (p->conf.vhostdb_cache && (ve = mod_vhostdb_cache_query(r, p)))
return mod_vhostdb_found(r, ve); /* HANDLER_GO_ON */
b = &p->tmp_buf;
backend = p->conf.vhostdb_backend;
if (0 != backend->query(r, backend->p_d, b)) {
@ -208,14 +314,81 @@ REQUEST_FUNC(mod_vhostdb_handle_docroot) {
return mod_vhostdb_error_500(r); /* HANDLER_FINISHED */
}
/* cache the data */
if (!ve) r->plugin_ctx[p->id] = ve = vhostdb_entry_init();
buffer_copy_buffer(ve->server_name, &r->uri.authority);
buffer_copy_buffer(ve->document_root, b);
if (ve && !p->conf.vhostdb_cache)
vhostdb_cache_entry_free(ve);
ve = vhostdb_cache_entry_init(&r->uri.authority, b);
if (!p->conf.vhostdb_cache)
r->plugin_ctx[p->id] = ve;
else
mod_vhostdb_cache_insert(r, p, ve);
return mod_vhostdb_found(r, ve); /* HANDLER_GO_ON */
}
/* walk though cache, collect expired ids, and remove them in a second loop */
static void
mod_vhostdb_tag_old_entries (splay_tree * const t, int * const keys, int * const ndx, const time_t max_age, const time_t cur_ts)
{
if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
if (t->left)
mod_vhostdb_tag_old_entries(t->left, keys, ndx, max_age, cur_ts);
if (t->right)
mod_vhostdb_tag_old_entries(t->right, keys, ndx, max_age, cur_ts);
if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
const vhostdb_cache_entry * const ve = t->data;
if (cur_ts - ve->ctime > max_age)
keys[(*ndx)++] = t->key;
}
__attribute_noinline__
static void
mod_vhostdb_periodic_cleanup(splay_tree **sptree_ptr, const time_t max_age, const time_t cur_ts)
{
splay_tree *sptree = *sptree_ptr;
int max_ndx, i;
int keys[8192]; /* 32k size on stack */
do {
if (!sptree) break;
max_ndx = 0;
mod_vhostdb_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts);
for (i = 0; i < max_ndx; ++i) {
int ndx = keys[i];
sptree = splaytree_splay(sptree, ndx);
if (sptree && sptree->key == ndx) {
vhostdb_cache_entry_free(sptree->data);
sptree = splaytree_delete(sptree, ndx);
}
}
} while (max_ndx == sizeof(keys)/sizeof(int));
*sptree_ptr = sptree;
}
TRIGGER_FUNC(mod_vhostdb_periodic)
{
const plugin_data * const p = p_d;
const time_t cur_ts = log_epoch_secs;
if (cur_ts & 0x7) return HANDLER_GO_ON; /*(continue once each 8 sec)*/
UNUSED(srv);
/* future: might construct array of (vhostdb_cache *) at startup
* to avoid the need to search for them here */
for (int i = 0, used = p->nconfig; i < used; ++i) {
const config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
for (; cpv->k_id != -1; ++cpv) {
if (cpv->k_id != 1) continue; /* k_id == 1 for vhostdb.cache */
if (cpv->vtype != T_CONFIG_LOCAL) continue;
vhostdb_cache *vc = cpv->v.v;
mod_vhostdb_periodic_cleanup(&vc->sptree, vc->max_age, cur_ts);
}
}
return HANDLER_GO_ON;
}
int mod_vhostdb_plugin_init(plugin *p);
int mod_vhostdb_plugin_init(plugin *p) {
p->version = LIGHTTPD_VERSION_ID;
@ -223,6 +396,7 @@ int mod_vhostdb_plugin_init(plugin *p) {
p->init = mod_vhostdb_init;
p->cleanup = mod_vhostdb_free;
p->set_defaults = mod_vhostdb_set_defaults;
p->handle_trigger = mod_vhostdb_periodic;
p->handle_docroot = mod_vhostdb_handle_docroot;
p->connection_reset = mod_vhostdb_handle_connection_reset;

8
src/splaytree.h

@ -23,17 +23,17 @@ splay_tree * splaytree_size(splay_tree *t);
__attribute_pure__
static inline uint32_t djbhash(const char *str, const uint32_t len);
static inline uint32_t djbhash(const char *str, const uint32_t len, uint32_t hash);
__attribute_pure__
static inline int32_t splaytree_djbhash(const char *str, const uint32_t len);
/* the famous DJB hash function for strings */
static inline uint32_t djbhash(const char *str, const uint32_t len)
#define DJBHASH_INIT 5381
static inline uint32_t djbhash(const char *str, const uint32_t len, uint32_t hash)
{
const unsigned char * const s = (const unsigned char *)str;
uint32_t hash = 5381;
for (uint32_t i = 0; i < len; ++i) hash = ((hash << 5) + hash) ^ s[i];
return hash;
}
@ -42,7 +42,7 @@ static inline uint32_t djbhash(const char *str, const uint32_t len)
static inline int32_t splaytree_djbhash(const char *str, const uint32_t len)
{
/* strip highest bit of hash value for splaytree */
return (int32_t)(djbhash(str,len) & ~(((uint32_t)1) << 31));
return (int32_t)(djbhash(str,len,DJBHASH_INIT) & ~(((uint32_t)1) << 31));
}

64
src/stat_cache.c

@ -192,12 +192,13 @@ static void fam_dir_tag_refcnt(splay_tree *t, int *keys, int *ndx)
}
}
__attribute_noinline__
static void fam_dir_periodic_cleanup() {
stat_cache_fam * const scf = sc.scf;
int max_ndx, i;
int keys[8192]; /* 32k size on stack */
stat_cache_fam * const scf = sc.scf;
do {
if (!scf->dirs) return;
if (!scf->dirs) break;
max_ndx = 0;
fam_dir_tag_refcnt(scf->dirs, keys, &max_ndx);
for (i = 0; i < max_ndx; ++i) {
@ -858,13 +859,14 @@ static void stat_cache_tag_dir_tree(splay_tree *t, const char *name, size_t len,
keys[(*ndx)++] = t->key;
}
__attribute_noinline__
static void stat_cache_prune_dir_tree(const char *name, size_t len)
{
splay_tree *sptree = sc.files;
int max_ndx, i;
int keys[8192]; /* 32k size on stack */
splay_tree *sptree = sc.files;
do {
if (!sptree) return;
if (!sptree) break;
max_ndx = 0;
stat_cache_tag_dir_tree(sptree, name, len, keys, &max_ndx);
for (i = 0; i < max_ndx; ++i) {
@ -1101,41 +1103,37 @@ int stat_cache_open_rdonly_fstat (const buffer *name, struct stat *st, int symli
* and remove them in a second loop
*/
static void stat_cache_tag_old_entries(splay_tree * const t, int * const keys, uint32_t * const ndx, const time_t max_age, const time_t cur_ts) {
if (!t) return;
stat_cache_tag_old_entries(t->left, keys, ndx, max_age, cur_ts);
stat_cache_tag_old_entries(t->right, keys, ndx, max_age, cur_ts);
static void stat_cache_tag_old_entries(splay_tree * const t, int * const keys, int * const ndx, const time_t max_age, const time_t cur_ts) {
if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
if (t->left)
stat_cache_tag_old_entries(t->left, keys, ndx, max_age, cur_ts);
if (t->right)
stat_cache_tag_old_entries(t->right, keys, ndx, max_age, cur_ts);
if (*ndx == 8192) return; /*(must match num array entries in keys[])*/
const stat_cache_entry * const sce = t->data;
if (cur_ts - sce->stat_ts > max_age) {
if (cur_ts - sce->stat_ts > max_age)
keys[(*ndx)++] = t->key;
}
}
static void stat_cache_periodic_cleanup(const time_t max_age, const time_t cur_ts) {
splay_tree *sptree = sc.files;
if (!sptree) return;
int * const keys = calloc(1, sizeof(int) * sptree->size);
force_assert(NULL != keys);
uint32_t max_ndx = 0;
stat_cache_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts);
for (uint32_t i = 0; i < max_ndx; ++i) {
int ndx = keys[i];
sptree = splaytree_splay(sptree, ndx);
if (sptree && sptree->key == ndx) {
stat_cache_entry_free(sptree->data);
sptree = splaytree_delete(sptree, ndx);
}
}
sc.files = sptree;
free(keys);
splay_tree *sptree = sc.files;
int max_ndx, i;
int keys[8192]; /* 32k size on stack */
do {
if (!sptree) break;
max_ndx = 0;
stat_cache_tag_old_entries(sptree, keys, &max_ndx, max_age, cur_ts);
for (i = 0; i < max_ndx; ++i) {
int ndx = keys[i];
sptree = splaytree_splay(sptree, ndx);
if (sptree && sptree->key == ndx) {
stat_cache_entry_free(sptree->data);
sptree = splaytree_delete(sptree, ndx);
}
}
} while (max_ndx == sizeof(keys)/sizeof(int));
sc.files = sptree;
}
void stat_cache_trigger_cleanup(void) {

Loading…
Cancel
Save