acache.c revision 0c27b3fe77ac1d5094ba3521e8142d9e7973133f
/*
* Copyright (C) 2004-2008, 2012, 2013, 2015, 2016 Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/* $Id: acache.c,v 1.22 2008/02/07 23:46:54 tbox Exp $ */
#include <config.h>
#include <isc/refcount.h>
#include <dns/rdataset.h>
#define DBBUCKETS 67
#if 0
ISC_LOG_DEBUG(3), \
"acache %p: %s", acache, (m))
ISC_LOG_DEBUG(3), \
"acache %p: %s", (a), (m))
#else
#define ATRACE(m)
#define AATRACE(a, m)
#endif
/*
* The following variables control incremental cleaning.
* MINSIZE is how many bytes is the floor for dns_acache_setcachesize().
* CLEANERINCREMENT is how many entries are examined in one pass.
* (XXX simply derived from definitions in cache.c There may be better
* constants here.)
*/
#if defined(ISC_RWLOCK_USEATOMIC) && defined(ISC_PLATFORM_HAVEATOMICSTORE)
#define ACACHE_USE_RWLOCK 1
#endif
#ifdef ACACHE_USE_RWLOCK
#define ACACHE_INITLOCK(l) isc_rwlock_init((l), 0, 0)
#define ACACHE_DESTROYLOCK(l) isc_rwlock_destroy(l)
#define ACACHE_LOCK(l, t) RWLOCK((l), (t))
#define ACACHE_UNLOCK(l, t) RWUNLOCK((l), (t))
#define acache_storetime(entry, t) \
#else
#define ACACHE_INITLOCK(l) isc_mutex_init(l)
#define ACACHE_DESTROYLOCK(l) DESTROYLOCK(l)
#define ACACHE_LOCK(l, t) LOCK(l)
#define ACACHE_UNLOCK(l, t) UNLOCK(l)
#endif
/* Locked by acache lock */
typedef struct dbentry {
} dbentry_t;
typedef struct acache_cleaner acache_cleaner_t;
typedef enum {
cleaner_s_idle, /* Waiting for cleaning-interval to expire. */
cleaner_s_busy, /* Currently cleaning. */
cleaner_s_done /* Freed enough memory after being overmem. */
/*
* Convenience macros for comprehensive assertion checking.
*/
(c)->resched_event != NULL)
(c)->resched_event == NULL)
struct acache_cleaner {
/*
* Locks overmem_event, overmem. (See cache.c)
*/
unsigned int cleaning_interval; /* The cleaning-interval
from named.conf,
in seconds. */
cleanup task completed */
itself to reschedule */
restart the cleaning.
Locked by acache lock. */
int increment; /* Number of entries to
clean in one increment */
unsigned long ncleaned; /* Number of entries cleaned
up (for logging purposes) */
state. */
};
struct dns_acachestats {
unsigned int hits;
unsigned int queries;
unsigned int misses;
unsigned int adds;
unsigned int deleted;
unsigned int cleaned;
unsigned int cleaner_runs;
unsigned int overmem;
unsigned int overmem_nocreates;
unsigned int nomem;
};
/*
* The actual acache object.
*/
struct dns_acache {
unsigned int magic;
#ifdef ACACHE_USE_RWLOCK
#else
#endif
int live_cleaners;
unsigned int dbentries;
};
struct dns_acacheentry {
unsigned int magic;
unsigned int locknum;
/* Data for Management of cache entries */
holding this entry */
/* Cache data */
belongs to */
belongs to */
and rdataset */
/* Callback function and its argument */
void (*callback)(dns_acacheentry_t *, void **);
void *cbarg;
/* Timestamp of the last time this entry is referred to */
};
/*
* Internal functions (and prototypes).
*/
isc_event_t *event);
isc_event_t *event);
isc_event_t *event);
/*
* acache should be locked. If it is not, the stats can get out of whack,
* which is not a big deal for us since this is for debugging / stats
*/
static void
}
/*
* The acache must be locked before calling.
*/
static inline isc_boolean_t
return (ISC_TRUE);
}
return (ISC_FALSE);
}
/*
* The acache must be locked before calling.
*/
static void
/*
* Release the dependency of all entries, and detach them.
*/
entry = entry_next) {
/*
* If the cleaner holds this entry, it will be unlinked and
* freed in the cleaner later.
*/
}
}
}
/*
* The acache must be locked before calling.
*/
static void
int i;
for (i = 0; i < DBBUCKETS; i++) {
}
}
}
static void
isc_event_free(&ev);
}
/* The acache and the entry must be locked before calling. */
static inline void
}
}
}
/* There must not be a reference to this entry. */
static void
/*
* Since there is no reference to this entry, it is safe to call
* clear_entry() here.
*/
}
static void
int i;
ATRACE("destroy");
for (i = 0; i < DEFAULT_ACACHE_ENTRY_LOCK_COUNT; i++)
sizeof(*acache->entrylocks) *
}
static inline isc_result_t
int bucket;
/*
* The caller must be holding the acache lock.
*/
break;
}
return (ISC_R_NOTFOUND);
else
return (ISC_R_SUCCESS);
}
static inline void
/*
* The caller must be holing the entry lock.
*/
rdataset = rdataset_next) {
}
}
}
}
}
static isc_result_t
{
int result;
ATRACE("acache cleaner init");
if (result != ISC_R_SUCCESS)
goto fail;
acache);
if (result != ISC_R_SUCCESS) {
"acache cleaner: "
"isc_task_onshutdown() failed: %s",
goto cleanup;
}
if (result != ISC_R_SUCCESS) {
"isc_timer_create() failed: %s",
goto cleanup;
}
cleaner, sizeof(isc_event_t));
goto cleanup;
}
cleaner, sizeof(isc_event_t));
goto cleanup;
}
}
return (ISC_R_SUCCESS);
fail:
return (result);
}
static void
/*
* This function does not have to lock the cleaner, since critical
* parameters (except current_entry, which is locked by acache lock,)
* are only used in a single task context.
*/
"begin acache cleaning, mem inuse %lu",
}
return;
}
static void
/* No need to lock the cleaner (see begin_cleaning()). */
/*
* Even if the cleaner has the last reference to the entry, which means
* the entry has been unused, it may still be linked if unlinking the
* entry has been delayed due to the reference.
*/
}
}
"acache %p stats: hits=%d misses=%d queries=%d "
"adds=%d deleted=%d "
"cleaned=%d cleaner_runs=%d overmem=%d "
"overmem_nocreates=%d nomem=%d",
"%lu entries cleaned, mem inuse %lu",
"acache is still in overmem state "
"after cleaning");
}
}
/*
* This is run once for every acache-cleaning-interval as defined
* in named.conf.
*/
static void
}
/* The caller must hold entry lock. */
static inline isc_boolean_t
{
/*
* If the callback has been canceled, we definitely do not need the
* entry.
*/
return (ISC_TRUE);
return (ISC_TRUE);
/*
* If the acache is in the overmem state, probabilistically decide if
* the entry should be purged, based on the time passed from its last
* use and the cleaning interval.
*/
unsigned int passed;
else
passed = 0;
return (ISC_TRUE);
}
return (ISC_FALSE);
}
/*
* Do incremental cleaning.
*/
static void
int n_entries;
unsigned int interval;
return;
}
else
interval = 0;
while (n_entries-- > 0) {
if (is_stale) {
}
if (is_stale)
/*
* If we are still in the overmem
* state, keep cleaning. In case we
* exit from the loop immediately after
* this, reset next to the head entry
* as we'll expect it will be never
* NULL.
*/
ISC_LOG_DEBUG(1),
"acache cleaner: "
"still overmem, "
"reset and try again");
continue;
}
}
return;
}
}
/*
* We have successfully performed a cleaning increment but have
* not gone through the entire cache. Remember the entry that will
* be the starting point in the next clean-up, and reschedule another
* batch. If it fails, just try to continue anyway.
*/
return;
}
/*
* This is called when the acache either surpasses its upper limit
* or shrinks beyond its lower limit.
*/
static void
} else {
/*
* end_cleaning() can't be called here because
* then both cleaner->overmem_event and
* cleaner->resched_event will point to this
* event. Set the state to done, and then
* when the acache_incremental_cleaning_action() event
* is posted, it will handle the end_cleaning.
*/
}
if (want_cleaning)
}
static void
"acache memory reaches %s watermark, mem inuse %lu",
}
}
/*
* The cleaner task is shutting down; do the necessary cleanup.
*/
static void
ATRACE("acache cleaner shutdown");
else
acache->live_cleaners--;
}
/*
* By detaching the timer in the context of its task,
* we are guaranteed that there will be no further timer
* events.
*/
/* Make sure we don't reschedule anymore. */
if (should_free)
}
/*
* Public functions.
*/
{
int i;
return (ISC_R_NOMEMORY);
ATRACE("create");
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS) {
return (result);
}
if (result != ISC_R_SUCCESS) {
"isc_task_create() failed(): %s",
goto cleanup;
}
for (i = 0; i < DBBUCKETS; i++)
goto cleanup;
}
for (i = 0; i < DEFAULT_ACACHE_ENTRY_LOCK_COUNT; i++) {
if (result != ISC_R_SUCCESS) {
while (i-- > 0)
sizeof(*acache->entrylocks) *
goto cleanup;
}
}
acache->live_cleaners = 0;
if (result != ISC_R_SUCCESS)
goto cleanup;
return (ISC_R_SUCCESS);
for (i = 0; i < DEFAULT_ACACHE_ENTRY_LOCK_COUNT; i++)
sizeof(*acache->entrylocks) *
}
return (result);
}
void
}
void
}
void
unsigned int refs;
ATRACE("detach");
if (refs == 0) {
}
/*
* If we're exiting and the cleaner task exists, let it free the cache.
*/
}
if (should_free)
}
void
ATRACE("shutdown");
if (!acache->shutting_down) {
/*
* Self attach the object in order to prevent it from being
* destroyed while waiting for the event.
*/
}
}
int bucket;
ATRACE("setdb");
if (result == ISC_R_SUCCESS) {
goto end;
}
goto end;
}
end:
return (result);
}
int bucket;
ATRACE("putdb");
if (result != ISC_R_SUCCESS) {
/*
* The entry may have not been created due to memory shortage.
*/
return (ISC_R_NOTFOUND);
}
/*
* Release corresponding cache entries: for each entry, release all
* links the entry has, and then callback to the entry holder (if any).
* If no other external references exist (this can happen if the
* original holder has canceled callback,) destroy it here.
*/
/*
* Releasing olink first would avoid finddbent() in
* unlink_dbentries().
*/
}
}
return (ISC_R_SUCCESS);
}
void (*callback)(dns_acacheentry_t *, void **),
{
isc_uint32_t r;
/*
* Should we exceed our memory limit for some reason (for
* example, if the cleaner does not run aggressively enough),
* then we will not create additional entries.
*
* XXXSK: It might be better to lock the acache->cleaner->lock,
* but locking may be an expensive bottleneck. If we misread
* the value, we will occasionally refuse to create a few
* cache entries, or create a few that we should not. I do not
* expect this to happen often, and it will not have very bad
* effects when it does. So no lock for now.
*/
return (ISC_R_NORESOURCES);
}
return (ISC_R_NOMEMORY);
}
isc_random_get(&r);
if (result != ISC_R_SUCCESS) {
return (result);
};
return (ISC_R_SUCCESS);
}
{
int locknum;
} else {
}
else {
if (result != ISC_R_SUCCESS) {
goto fail;
}
/*
* XXXJT: if we simply clone the rdataset, we'll get
* lost wrt cyclic ordering. We'll need an additional
* trick to get the latest counter from the original
* header.
*/
}
}
return (result);
fail:
}
return (result);
}
{
/* Set zone */
/* Set DB */
/*
* Set DB version. If the version is not given by the caller,
* which is the case for glue or cache DBs, use the current version.
*/
}
}
}
if (close_version)
/* Set DB node. */
}
/*
* Set list of the corresponding rdatasets, if given.
* To minimize the overhead and memory consumption, we'll do this for
* positive cache only, in which case the DB node is non NULL.
* We do not want to cache incomplete information, so give up the
* entire entry when a memory shortage happen during the process.
*/
goto fail;
}
if (result != ISC_R_SUCCESS)
goto fail;
sizeof(*crdataset));
goto fail;
}
link);
}
}
if (result != ISC_R_SUCCESS)
goto fail;
if (result != ISC_R_SUCCESS)
goto fail;
}
/*
* The additional cache needs an implicit reference to entries in its
* link.
*/
return (ISC_R_SUCCESS);
fail:
return (result);
}
/*
* Release dependencies stored in this entry as much as possible.
* The main link cannot be released, since the acache object has
* a reference to this entry; the empty entry will be released in
* the next cleaning action.
*/
return (callback_active);
}
void
{
}
void
unsigned int refs;
/*
* If there are no references to the entry, the entry must have been
* unlinked and can be destroyed safely.
*/
if (refs == 0) {
}
}
void
ATRACE("dns_acache_setcleaninginterval");
/*
* It may be the case that the acache has already shut down.
* If so, it has no timer. (Not sure if this can really happen.)
*/
goto unlock;
if (t == 0) {
} else {
0);
}
if (result != ISC_R_SUCCESS)
"could not set acache cleaning interval: %s",
else
"acache %p cleaning interval set to %d.",
acache, t);
}
/*
* This function was derived from cache.c:dns_cache_setcachesize(). See the
* function for more details about the logic.
*/
void
else
}