Merge branch 'reuse-caches-on-reload' of https://github.com/jinmeiib/unbound-1 into jinmeiib-reuse-caches-on-reload

This commit is contained in:
George Thessalonikefs 2022-12-13 16:42:38 +01:00
commit 857d6ce3a1
7 changed files with 111 additions and 17 deletions

View File

@ -387,7 +387,7 @@ move_into_cache(struct ub_packed_rrset_key* k,
struct rrset_ref ref; struct rrset_ref ref;
uint8_t* p; uint8_t* p;
ak = alloc_special_obtain(&worker->alloc); ak = alloc_special_obtain(worker->alloc);
if(!ak) { if(!ak) {
log_warn("error out of memory"); log_warn("error out of memory");
return 0; return 0;
@ -398,7 +398,7 @@ move_into_cache(struct ub_packed_rrset_key* k,
ak->rk.dname = (uint8_t*)memdup(k->rk.dname, k->rk.dname_len); ak->rk.dname = (uint8_t*)memdup(k->rk.dname, k->rk.dname_len);
if(!ak->rk.dname) { if(!ak->rk.dname) {
log_warn("error out of memory"); log_warn("error out of memory");
ub_packed_rrset_parsedelete(ak, &worker->alloc); ub_packed_rrset_parsedelete(ak, worker->alloc);
return 0; return 0;
} }
s = sizeof(*ad) + (sizeof(size_t) + sizeof(uint8_t*) + s = sizeof(*ad) + (sizeof(size_t) + sizeof(uint8_t*) +
@ -408,7 +408,7 @@ move_into_cache(struct ub_packed_rrset_key* k,
ad = (struct packed_rrset_data*)malloc(s); ad = (struct packed_rrset_data*)malloc(s);
if(!ad) { if(!ad) {
log_warn("error out of memory"); log_warn("error out of memory");
ub_packed_rrset_parsedelete(ak, &worker->alloc); ub_packed_rrset_parsedelete(ak, worker->alloc);
return 0; return 0;
} }
p = (uint8_t*)ad; p = (uint8_t*)ad;
@ -431,7 +431,8 @@ move_into_cache(struct ub_packed_rrset_key* k,
ref.key = ak; ref.key = ak;
ref.id = ak->id; ref.id = ak->id;
(void)rrset_cache_update(worker->env.rrset_cache, &ref, (void)rrset_cache_update(worker->env.rrset_cache, &ref,
&worker->alloc, *worker->env.now); worker->alloc, *worker->env.now);
return 1; return 1;
} }

View File

@ -488,6 +488,27 @@ static int daemon_get_shufport(struct daemon* daemon, int* shufport)
return avail; return avail;
} }
/**
* Clear and delete per-worker alloc caches, and free memory maintained in
* superalloc.
* The rrset and message caches must be empty at the time of call.
* @param daemon: the daemon that maintains the alloc caches to be cleared.
*/
static void
daemon_clear_allocs(struct daemon* daemon)
{
int i;
for(i=0; i<daemon->num; i++) {
alloc_clear(daemon->worker_allocs[i]);
free(daemon->worker_allocs[i]);
}
free(daemon->worker_allocs);
daemon->worker_allocs = NULL;
alloc_clear_special(&daemon->superalloc);
}
/** /**
* Allocate empty worker structures. With backptr and thread-number, * Allocate empty worker structures. With backptr and thread-number,
* from 0..numthread initialised. Used as user arguments to new threads. * from 0..numthread initialised. Used as user arguments to new threads.
@ -540,6 +561,21 @@ daemon_create_workers(struct daemon* daemon)
/* the above is not ports/numthr, due to rounding */ /* the above is not ports/numthr, due to rounding */
fatal_exit("could not create worker"); fatal_exit("could not create worker");
} }
/* create per-worker alloc caches if not reusing existing ones. */
if(!daemon->worker_allocs) {
daemon->worker_allocs = (struct alloc_cache**)calloc(
(size_t)daemon->num, sizeof(struct alloc_cache*));
if(!daemon->worker_allocs)
fatal_exit("could not allocate worker allocs");
for(i=0; i<daemon->num; i++) {
struct alloc_cache* alloc = calloc(1,
sizeof(struct alloc_cache));
if (!alloc)
fatal_exit("could not allocate worker alloc");
alloc_init(alloc, &daemon->superalloc, i);
daemon->worker_allocs[i] = alloc;
}
}
free(shufport); free(shufport);
} }
@ -771,6 +807,7 @@ daemon_fork(struct daemon* daemon)
/* Shutdown SHM */ /* Shutdown SHM */
shm_main_shutdown(daemon); shm_main_shutdown(daemon);
daemon->reuse_cache = daemon->workers[0]->reuse_cache;
daemon->need_to_exit = daemon->workers[0]->need_to_exit; daemon->need_to_exit = daemon->workers[0]->need_to_exit;
} }
@ -785,9 +822,16 @@ daemon_cleanup(struct daemon* daemon)
log_thread_set(NULL); log_thread_set(NULL);
/* clean up caches because /* clean up caches because
* a) RRset IDs will be recycled after a reload, causing collisions * a) RRset IDs will be recycled after a reload, causing collisions
* b) validation config can change, thus rrset, msg, keycache clear */ * b) validation config can change, thus rrset, msg, keycache clear
slabhash_clear(&daemon->env->rrset_cache->table); *
slabhash_clear(daemon->env->msg_cache); * If we are trying to keep the cache as long as possible, we should
* defer the cleanup until we know whether the new configuration allows
* the reuse. (If we're exiting, cleanup should be done here). */
if(!daemon->reuse_cache || daemon->need_to_exit) {
slabhash_clear(&daemon->env->rrset_cache->table);
slabhash_clear(daemon->env->msg_cache);
}
daemon->old_num = daemon->num; /* save the current num */
local_zones_delete(daemon->local_zones); local_zones_delete(daemon->local_zones);
daemon->local_zones = NULL; daemon->local_zones = NULL;
respip_set_delete(daemon->respip_set); respip_set_delete(daemon->respip_set);
@ -802,8 +846,13 @@ daemon_cleanup(struct daemon* daemon)
worker_delete(daemon->workers[i]); worker_delete(daemon->workers[i]);
free(daemon->workers); free(daemon->workers);
daemon->workers = NULL; daemon->workers = NULL;
/* Unless we're trying to keep the cache, worker alloc_caches should be
* cleared and freed here. We do this after deleting workers to
* guarantee that the alloc caches are valid throughout the lifetime
* of workers. */
if(!daemon->reuse_cache || daemon->need_to_exit)
daemon_clear_allocs(daemon);
daemon->num = 0; daemon->num = 0;
alloc_clear_special(&daemon->superalloc);
#ifdef USE_DNSTAP #ifdef USE_DNSTAP
dt_delete(daemon->dtenv); dt_delete(daemon->dtenv);
daemon->dtenv = NULL; daemon->dtenv = NULL;
@ -900,8 +949,42 @@ daemon_delete(struct daemon* daemon)
void daemon_apply_cfg(struct daemon* daemon, struct config_file* cfg) void daemon_apply_cfg(struct daemon* daemon, struct config_file* cfg)
{ {
int new_num = cfg->num_threads?cfg->num_threads:1;
daemon->cfg = cfg; daemon->cfg = cfg;
config_apply(cfg); config_apply(cfg);
/* If this is a reload and we deferred the decision on whether to
* reuse the alloc, RRset, and message caches, then check to see if
* it's safe to keep the caches:
* - changing the number of threads is obviously incompatible with
* keeping the per-thread alloc caches. It also means we have to
* clear RRset and message caches. (note that 'new_num' may be
* adjusted in daemon_create_workers, but for our purpose we can
* simply compare it with 'old_num'; if they are equal here,
* 'new_num' won't be adjusted to a different value than 'old_num').
* - changing RRset cache size effectively clears any remaining cache
* entries. We could keep their keys in alloc caches, but it would
* be more consistent with the sense of the change to clear allocs
* and free memory. To do so we also have to clear message cache.
* - only changing message cache size does not necessarily affect
* RRset or alloc cache. But almost all new subsequent queries will
* require recursive resolution anyway, so it doesn't help much to
* just keep RRset and alloc caches. For simplicity we clear/free
* the other two, too. */
if(daemon->worker_allocs &&
(new_num != daemon->old_num ||
!slabhash_is_size(daemon->env->msg_cache, cfg->msg_cache_size,
cfg->msg_cache_slabs) ||
!slabhash_is_size(&daemon->env->rrset_cache->table,
cfg->rrset_cache_size, cfg->rrset_cache_slabs)))
{
log_warn("cannot reuse caches due to critical config change");
slabhash_clear(&daemon->env->rrset_cache->table);
slabhash_clear(daemon->env->msg_cache);
daemon_clear_allocs(daemon);
}
if(!slabhash_is_size(daemon->env->msg_cache, cfg->msg_cache_size, if(!slabhash_is_size(daemon->env->msg_cache, cfg->msg_cache_size,
cfg->msg_cache_slabs)) { cfg->msg_cache_slabs)) {
slabhash_delete(daemon->env->msg_cache); slabhash_delete(daemon->env->msg_cache);

View File

@ -99,8 +99,12 @@ struct daemon {
void* listen_sslctx, *connect_sslctx; void* listen_sslctx, *connect_sslctx;
/** num threads allocated */ /** num threads allocated */
int num; int num;
/** num threads allocated in the previous config or 0 at first */
int old_num;
/** the worker entries */ /** the worker entries */
struct worker** workers; struct worker** workers;
/** per-worker allocation cache */
struct alloc_cache **worker_allocs;
/** do we need to exit unbound (or is it only a reload?) */ /** do we need to exit unbound (or is it only a reload?) */
int need_to_exit; int need_to_exit;
/** master random table ; used for port div between threads on reload*/ /** master random table ; used for port div between threads on reload*/
@ -140,6 +144,8 @@ struct daemon {
/** the dnscrypt environment */ /** the dnscrypt environment */
struct dnsc_env* dnscenv; struct dnsc_env* dnscenv;
#endif #endif
/** reuse existing cache on reload if other conditions allow it. */
int reuse_cache;
}; };
/** /**

View File

@ -682,8 +682,10 @@ do_stop(RES* ssl, struct worker* worker)
/** do the reload command */ /** do the reload command */
static void static void
do_reload(RES* ssl, struct worker* worker) do_reload(RES* ssl, struct worker* worker, char* arg)
{ {
arg = skipwhite(arg);
worker->reuse_cache = (strcmp(arg, "+keep-cache") == 0);
worker->need_to_exit = 0; worker->need_to_exit = 0;
comm_base_exit(worker->base); comm_base_exit(worker->base);
send_ok(ssl); send_ok(ssl);
@ -3030,7 +3032,7 @@ execute_cmd(struct daemon_remote* rc, RES* ssl, char* cmd,
do_stop(ssl, worker); do_stop(ssl, worker);
return; return;
} else if(cmdcmp(p, "reload", 6)) { } else if(cmdcmp(p, "reload", 6)) {
do_reload(ssl, worker); do_reload(ssl, worker, skipwhite(p+6));
return; return;
} else if(cmdcmp(p, "stats_noreset", 13)) { } else if(cmdcmp(p, "stats_noreset", 13)) {
do_stats(ssl, worker, 0); do_stats(ssl, worker, 0);

View File

@ -133,7 +133,7 @@ worker_mem_report(struct worker* ATTR_UNUSED(worker),
rrset = slabhash_get_mem(&worker->env.rrset_cache->table); rrset = slabhash_get_mem(&worker->env.rrset_cache->table);
infra = infra_get_mem(worker->env.infra_cache); infra = infra_get_mem(worker->env.infra_cache);
mesh = mesh_get_mem(worker->env.mesh); mesh = mesh_get_mem(worker->env.mesh);
ac = alloc_get_mem(&worker->alloc); ac = alloc_get_mem(worker->alloc);
superac = alloc_get_mem(&worker->daemon->superalloc); superac = alloc_get_mem(&worker->daemon->superalloc);
anch = anchors_get_mem(worker->env.anchors); anch = anchors_get_mem(worker->env.anchors);
iter = 0; iter = 0;
@ -2065,15 +2065,14 @@ worker_init(struct worker* worker, struct config_file *cfg,
} }
server_stats_init(&worker->stats, cfg); server_stats_init(&worker->stats, cfg);
alloc_init(&worker->alloc, &worker->daemon->superalloc, worker->alloc = worker->daemon->worker_allocs[worker->thread_num];
worker->thread_num); alloc_set_id_cleanup(worker->alloc, &worker_alloc_cleanup, worker);
alloc_set_id_cleanup(&worker->alloc, &worker_alloc_cleanup, worker);
worker->env = *worker->daemon->env; worker->env = *worker->daemon->env;
comm_base_timept(worker->base, &worker->env.now, &worker->env.now_tv); comm_base_timept(worker->base, &worker->env.now, &worker->env.now_tv);
worker->env.worker = worker; worker->env.worker = worker;
worker->env.worker_base = worker->base; worker->env.worker_base = worker->base;
worker->env.send_query = &worker_send_query; worker->env.send_query = &worker_send_query;
worker->env.alloc = &worker->alloc; worker->env.alloc = worker->alloc;
worker->env.outnet = worker->back; worker->env.outnet = worker->back;
worker->env.rnd = worker->rndstate; worker->env.rnd = worker->rndstate;
/* If case prefetch is triggered, the corresponding mesh will clear /* If case prefetch is triggered, the corresponding mesh will clear
@ -2217,7 +2216,7 @@ worker_delete(struct worker* worker)
#endif /* USE_DNSTAP */ #endif /* USE_DNSTAP */
comm_base_delete(worker->base); comm_base_delete(worker->base);
ub_randfree(worker->rndstate); ub_randfree(worker->rndstate);
alloc_clear(&worker->alloc); /* don't touch worker->alloc, as it's maintained in daemon */
regional_destroy(worker->env.scratch); regional_destroy(worker->env.scratch);
regional_destroy(worker->scratchpad); regional_destroy(worker->scratchpad);
free(worker); free(worker);

View File

@ -118,7 +118,7 @@ struct worker {
/** do we need to restart or quit (on signal) */ /** do we need to restart or quit (on signal) */
int need_to_exit; int need_to_exit;
/** allocation cache for this thread */ /** allocation cache for this thread */
struct alloc_cache alloc; struct alloc_cache *alloc;
/** per thread statistics */ /** per thread statistics */
struct ub_server_stats stats; struct ub_server_stats stats;
/** thread scratch regional */ /** thread scratch regional */
@ -131,6 +131,8 @@ struct worker {
/** dnstap environment, changed for this thread */ /** dnstap environment, changed for this thread */
struct dt_env dtenv; struct dt_env dtenv;
#endif #endif
/** reuse existing cache on reload if other conditions allow it. */
int reuse_cache;
}; };
/** /**

View File

@ -102,6 +102,7 @@ usage(void)
printf(" stop stops the server\n"); printf(" stop stops the server\n");
printf(" reload reloads the server\n"); printf(" reload reloads the server\n");
printf(" (this flushes data, stats, requestlist)\n"); printf(" (this flushes data, stats, requestlist)\n");
printf(" reload +keep-cache ditto but keep RRset and message cache\n");
printf(" stats print statistics\n"); printf(" stats print statistics\n");
printf(" stats_noreset peek at statistics\n"); printf(" stats_noreset peek at statistics\n");
#ifdef HAVE_SHMGET #ifdef HAVE_SHMGET