David van Moolenbroek 00b67f09dd Import NetBSD named(8)
Also known as ISC bind.  This import adds utilities such as host(1),
dig(1), and nslookup(1), as well as many other tools and libraries.

Change-Id: I035ca46e64f1965d57019e773f4ff0ef035e4aa3
2017-03-21 22:00:06 +00:00

1803 lines
46 KiB
C

/* $NetBSD: acache.c,v 1.7 2015/07/08 17:28:58 christos Exp $ */
/*
* Copyright (C) 2004-2008, 2012, 2013, 2015 Internet Systems Consortium, Inc. ("ISC")
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/* Id: acache.c,v 1.22 2008/02/07 23:46:54 tbox Exp */
#include <config.h>
#include <isc/atomic.h>
#include <isc/event.h>
#include <isc/hash.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/mutex.h>
#include <isc/random.h>
#include <isc/refcount.h>
#include <isc/rwlock.h>
#include <isc/serial.h>
#include <isc/task.h>
#include <isc/time.h>
#include <isc/timer.h>
#include <dns/acache.h>
#include <dns/db.h>
#include <dns/events.h>
#include <dns/log.h>
#include <dns/message.h>
#include <dns/name.h>
#include <dns/rdataset.h>
#include <dns/result.h>
#include <dns/zone.h>
#define ACACHE_MAGIC ISC_MAGIC('A', 'C', 'H', 'E')
#define DNS_ACACHE_VALID(acache) ISC_MAGIC_VALID(acache, ACACHE_MAGIC)
#define ACACHEENTRY_MAGIC ISC_MAGIC('A', 'C', 'E', 'T')
#define DNS_ACACHEENTRY_VALID(entry) ISC_MAGIC_VALID(entry, ACACHEENTRY_MAGIC)
#define DBBUCKETS 67
#if 0
#define ATRACE(m) isc_log_write(dns_lctx, \
DNS_LOGCATEGORY_DATABASE, \
DNS_LOGMODULE_ACACHE, \
ISC_LOG_DEBUG(3), \
"acache %p: %s", acache, (m))
#define AATRACE(a,m) isc_log_write(dns_lctx, \
DNS_LOGCATEGORY_DATABASE, \
DNS_LOGMODULE_ACACHE, \
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.)
*/
#define DNS_ACACHE_MINSIZE 2097152U /* Bytes. 2097152 = 2 MB */
#define DNS_ACACHE_CLEANERINCREMENT 1000 /* Number of entries. */
#define DEFAULT_ACACHE_ENTRY_LOCK_COUNT 1009 /*%< Should be prime. */
#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) \
(isc_atomic_store((isc_int32_t *)&(entry)->lastused, (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)
#define acache_storetime(entry, t) ((entry)->lastused = (t))
#endif
/* Locked by acache lock */
typedef struct dbentry {
ISC_LINK(struct dbentry) link;
dns_db_t *db;
ISC_LIST(dns_acacheentry_t) originlist;
ISC_LIST(dns_acacheentry_t) referlist;
} dbentry_t;
typedef ISC_LIST(dbentry_t) dbentrylist_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. */
} cleaner_state_t;
/*
* Convenience macros for comprehensive assertion checking.
*/
#define CLEANER_IDLE(c) ((c)->state == cleaner_s_idle && \
(c)->resched_event != NULL)
#define CLEANER_BUSY(c) ((c)->state == cleaner_s_busy && \
(c)->resched_event == NULL)
struct acache_cleaner {
isc_mutex_t lock;
/*
* Locks overmem_event, overmem. (See cache.c)
*/
dns_acache_t *acache;
unsigned int cleaning_interval; /* The cleaning-interval
from named.conf,
in seconds. */
isc_stdtime_t last_cleanup_time; /* The time when the last
cleanup task completed */
isc_timer_t *cleaning_timer;
isc_event_t *resched_event; /* Sent by cleaner task to
itself to reschedule */
isc_event_t *overmem_event;
dns_acacheentry_t *current_entry; /* The bookmark entry to
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) */
cleaner_state_t state; /* Idle/Busy/Done. */
isc_boolean_t overmem; /* The acache is in an overmem
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;
isc_mem_t *mctx;
isc_refcount_t refs;
#ifdef ACACHE_USE_RWLOCK
isc_rwlock_t *entrylocks;
#else
isc_mutex_t *entrylocks;
#endif
isc_mutex_t lock;
int live_cleaners;
acache_cleaner_t cleaner;
ISC_LIST(dns_acacheentry_t) entries;
unsigned int dbentries;
dbentrylist_t dbbucket[DBBUCKETS];
isc_boolean_t shutting_down;
isc_task_t *task;
isc_event_t cevent;
isc_boolean_t cevent_sent;
dns_acachestats_t stats;
};
struct dns_acacheentry {
unsigned int magic;
unsigned int locknum;
isc_refcount_t references;
dns_acache_t *acache;
/* Data for Management of cache entries */
ISC_LINK(dns_acacheentry_t) link;
ISC_LINK(dns_acacheentry_t) olink;
ISC_LINK(dns_acacheentry_t) rlink;
dns_db_t *origdb; /* reference to the DB
holding this entry */
/* Cache data */
dns_zone_t *zone; /* zone this entry
belongs to */
dns_db_t *db; /* DB this entry belongs to */
dns_dbversion_t *version; /* the version of the DB */
dns_dbnode_t *node; /* node this entry
belongs to */
dns_name_t *foundname; /* corresponding DNS name
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 */
isc_stdtime32_t lastused;
};
/*
* Internal functions (and prototypes).
*/
static inline isc_boolean_t check_noentry(dns_acache_t *acache);
static void destroy(dns_acache_t *acache);
static void shutdown_entries(dns_acache_t *acache);
static void shutdown_buckets(dns_acache_t *acache);
static void destroy_entry(dns_acacheentry_t *ent);
static inline void unlink_dbentries(dns_acache_t *acache,
dns_acacheentry_t *ent);
static inline isc_result_t finddbent(dns_acache_t *acache,
dns_db_t *db, dbentry_t **dbentryp);
static inline void clear_entry(dns_acache_t *acache, dns_acacheentry_t *entry);
static isc_result_t acache_cleaner_init(dns_acache_t *acache,
isc_timermgr_t *timermgr,
acache_cleaner_t *cleaner);
static void acache_cleaning_timer_action(isc_task_t *task, isc_event_t *event);
static void acache_incremental_cleaning_action(isc_task_t *task,
isc_event_t *event);
static void acache_overmem_cleaning_action(isc_task_t *task,
isc_event_t *event);
static void acache_cleaner_shutdown_action(isc_task_t *task,
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
reset_stats(dns_acache_t *acache) {
acache->stats.hits = 0;
acache->stats.queries = 0;
acache->stats.misses = 0;
acache->stats.adds = 0;
acache->stats.deleted = 0;
acache->stats.cleaned = 0;
acache->stats.overmem = 0;
acache->stats.overmem_nocreates = 0;
acache->stats.nomem = 0;
}
/*
* The acache must be locked before calling.
*/
static inline isc_boolean_t
check_noentry(dns_acache_t *acache) {
if (ISC_LIST_EMPTY(acache->entries) && acache->dbentries == 0) {
return (ISC_TRUE);
}
return (ISC_FALSE);
}
/*
* The acache must be locked before calling.
*/
static void
shutdown_entries(dns_acache_t *acache) {
dns_acacheentry_t *entry, *entry_next;
REQUIRE(DNS_ACACHE_VALID(acache));
INSIST(acache->shutting_down);
/*
* Release the dependency of all entries, and detach them.
*/
for (entry = ISC_LIST_HEAD(acache->entries);
entry != NULL;
entry = entry_next) {
entry_next = ISC_LIST_NEXT(entry, link);
ACACHE_LOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
/*
* If the cleaner holds this entry, it will be unlinked and
* freed in the cleaner later.
*/
if (acache->cleaner.current_entry != entry)
ISC_LIST_UNLINK(acache->entries, entry, link);
unlink_dbentries(acache, entry);
if (entry->callback != NULL) {
(entry->callback)(entry, &entry->cbarg);
entry->callback = NULL;
}
ACACHE_UNLOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
if (acache->cleaner.current_entry != entry)
dns_acache_detachentry(&entry);
}
}
/*
* The acache must be locked before calling.
*/
static void
shutdown_buckets(dns_acache_t *acache) {
int i;
dbentry_t *dbent;
REQUIRE(DNS_ACACHE_VALID(acache));
INSIST(acache->shutting_down);
for (i = 0; i < DBBUCKETS; i++) {
while ((dbent = ISC_LIST_HEAD(acache->dbbucket[i])) != NULL) {
INSIST(ISC_LIST_EMPTY(dbent->originlist) &&
ISC_LIST_EMPTY(dbent->referlist));
ISC_LIST_UNLINK(acache->dbbucket[i], dbent, link);
dns_db_detach(&dbent->db);
isc_mem_put(acache->mctx, dbent, sizeof(*dbent));
acache->dbentries--;
}
}
INSIST(acache->dbentries == 0);
}
static void
shutdown_task(isc_task_t *task, isc_event_t *ev) {
dns_acache_t *acache;
UNUSED(task);
acache = ev->ev_arg;
INSIST(DNS_ACACHE_VALID(acache));
isc_event_free(&ev);
LOCK(&acache->lock);
shutdown_entries(acache);
shutdown_buckets(acache);
UNLOCK(&acache->lock);
dns_acache_detach(&acache);
}
/* The acache and the entry must be locked before calling. */
static inline void
unlink_dbentries(dns_acache_t *acache, dns_acacheentry_t *ent) {
isc_result_t result;
dbentry_t *dbent;
if (ISC_LINK_LINKED(ent, olink)) {
INSIST(ent->origdb != NULL);
dbent = NULL;
result = finddbent(acache, ent->origdb, &dbent);
INSIST(result == ISC_R_SUCCESS);
ISC_LIST_UNLINK(dbent->originlist, ent, olink);
}
if (ISC_LINK_LINKED(ent, rlink)) {
INSIST(ent->db != NULL);
dbent = NULL;
result = finddbent(acache, ent->db, &dbent);
INSIST(result == ISC_R_SUCCESS);
ISC_LIST_UNLINK(dbent->referlist, ent, rlink);
}
}
/* There must not be a reference to this entry. */
static void
destroy_entry(dns_acacheentry_t *entry) {
dns_acache_t *acache;
REQUIRE(DNS_ACACHEENTRY_VALID(entry));
acache = entry->acache;
REQUIRE(DNS_ACACHE_VALID(acache));
/*
* Since there is no reference to this entry, it is safe to call
* clear_entry() here.
*/
clear_entry(acache, entry);
isc_mem_put(acache->mctx, entry, sizeof(*entry));
dns_acache_detach(&acache);
}
static void
destroy(dns_acache_t *acache) {
int i;
REQUIRE(DNS_ACACHE_VALID(acache));
ATRACE("destroy");
isc_mem_setwater(acache->mctx, NULL, NULL, 0, 0);
if (acache->cleaner.overmem_event != NULL)
isc_event_free(&acache->cleaner.overmem_event);
if (acache->cleaner.resched_event != NULL)
isc_event_free(&acache->cleaner.resched_event);
if (acache->task != NULL)
isc_task_detach(&acache->task);
for (i = 0; i < DEFAULT_ACACHE_ENTRY_LOCK_COUNT; i++)
ACACHE_DESTROYLOCK(&acache->entrylocks[i]);
isc_mem_put(acache->mctx, acache->entrylocks,
sizeof(*acache->entrylocks) *
DEFAULT_ACACHE_ENTRY_LOCK_COUNT);
DESTROYLOCK(&acache->cleaner.lock);
DESTROYLOCK(&acache->lock);
acache->magic = 0;
isc_mem_putanddetach(&acache->mctx, acache, sizeof(*acache));
}
static inline isc_result_t
finddbent(dns_acache_t *acache, dns_db_t *db, dbentry_t **dbentryp) {
int bucket;
dbentry_t *dbentry;
REQUIRE(DNS_ACACHE_VALID(acache));
REQUIRE(db != NULL);
REQUIRE(dbentryp != NULL && *dbentryp == NULL);
/*
* The caller must be holding the acache lock.
*/
bucket = isc_hash_calc((const unsigned char *)&db,
sizeof(db), ISC_TRUE) % DBBUCKETS;
for (dbentry = ISC_LIST_HEAD(acache->dbbucket[bucket]);
dbentry != NULL;
dbentry = ISC_LIST_NEXT(dbentry, link)) {
if (dbentry->db == db)
break;
}
*dbentryp = dbentry;
if (dbentry == NULL)
return (ISC_R_NOTFOUND);
else
return (ISC_R_SUCCESS);
}
static inline void
clear_entry(dns_acache_t *acache, dns_acacheentry_t *entry) {
REQUIRE(DNS_ACACHE_VALID(acache));
REQUIRE(DNS_ACACHEENTRY_VALID(entry));
/*
* The caller must be holing the entry lock.
*/
if (entry->foundname) {
dns_rdataset_t *rdataset, *rdataset_next;
for (rdataset = ISC_LIST_HEAD(entry->foundname->list);
rdataset != NULL;
rdataset = rdataset_next) {
rdataset_next = ISC_LIST_NEXT(rdataset, link);
ISC_LIST_UNLINK(entry->foundname->list,
rdataset, link);
dns_rdataset_disassociate(rdataset);
isc_mem_put(acache->mctx, rdataset, sizeof(*rdataset));
}
if (dns_name_dynamic(entry->foundname))
dns_name_free(entry->foundname, acache->mctx);
isc_mem_put(acache->mctx, entry->foundname,
sizeof(*entry->foundname));
entry->foundname = NULL;
}
if (entry->node != NULL) {
INSIST(entry->db != NULL);
dns_db_detachnode(entry->db, &entry->node);
}
if (entry->version != NULL) {
INSIST(entry->db != NULL);
dns_db_closeversion(entry->db, &entry->version, ISC_FALSE);
}
if (entry->db != NULL)
dns_db_detach(&entry->db);
if (entry->zone != NULL)
dns_zone_detach(&entry->zone);
if (entry->origdb != NULL)
dns_db_detach(&entry->origdb);
}
static isc_result_t
acache_cleaner_init(dns_acache_t *acache, isc_timermgr_t *timermgr,
acache_cleaner_t *cleaner)
{
int result;
ATRACE("acache cleaner init");
result = isc_mutex_init(&cleaner->lock);
if (result != ISC_R_SUCCESS)
goto fail;
cleaner->increment = DNS_ACACHE_CLEANERINCREMENT;
cleaner->state = cleaner_s_idle;
cleaner->acache = acache;
cleaner->overmem = ISC_FALSE;
cleaner->cleaning_timer = NULL;
cleaner->resched_event = NULL;
cleaner->overmem_event = NULL;
cleaner->current_entry = NULL;
if (timermgr != NULL) {
cleaner->acache->live_cleaners++;
result = isc_task_onshutdown(acache->task,
acache_cleaner_shutdown_action,
acache);
if (result != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"acache cleaner: "
"isc_task_onshutdown() failed: %s",
dns_result_totext(result));
goto cleanup;
}
cleaner->cleaning_interval = 0; /* Initially turned off. */
isc_stdtime_get(&cleaner->last_cleanup_time);
result = isc_timer_create(timermgr, isc_timertype_inactive,
NULL, NULL,
acache->task,
acache_cleaning_timer_action,
cleaner, &cleaner->cleaning_timer);
if (result != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"isc_timer_create() failed: %s",
dns_result_totext(result));
result = ISC_R_UNEXPECTED;
goto cleanup;
}
cleaner->resched_event =
isc_event_allocate(acache->mctx, cleaner,
DNS_EVENT_ACACHECLEAN,
acache_incremental_cleaning_action,
cleaner, sizeof(isc_event_t));
if (cleaner->resched_event == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
cleaner->overmem_event =
isc_event_allocate(acache->mctx, cleaner,
DNS_EVENT_ACACHEOVERMEM,
acache_overmem_cleaning_action,
cleaner, sizeof(isc_event_t));
if (cleaner->overmem_event == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
}
return (ISC_R_SUCCESS);
cleanup:
if (cleaner->overmem_event != NULL)
isc_event_free(&cleaner->overmem_event);
if (cleaner->resched_event != NULL)
isc_event_free(&cleaner->resched_event);
if (cleaner->cleaning_timer != NULL)
isc_timer_detach(&cleaner->cleaning_timer);
cleaner->acache->live_cleaners--;
DESTROYLOCK(&cleaner->lock);
fail:
return (result);
}
static void
begin_cleaning(acache_cleaner_t *cleaner) {
dns_acacheentry_t *head;
dns_acache_t *acache = cleaner->acache;
/*
* 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.
*/
REQUIRE(CLEANER_IDLE(cleaner));
INSIST(DNS_ACACHE_VALID(acache));
INSIST(cleaner->current_entry == NULL);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_ACACHE, ISC_LOG_DEBUG(1),
"begin acache cleaning, mem inuse %lu",
(unsigned long)isc_mem_inuse(cleaner->acache->mctx));
LOCK(&acache->lock);
head = ISC_LIST_HEAD(acache->entries);
if (head != NULL)
dns_acache_attachentry(head, &cleaner->current_entry);
UNLOCK(&acache->lock);
if (cleaner->current_entry != NULL) {
cleaner->ncleaned = 0;
cleaner->state = cleaner_s_busy;
isc_task_send(acache->task, &cleaner->resched_event);
}
return;
}
static void
end_cleaning(acache_cleaner_t *cleaner, isc_event_t *event) {
dns_acache_t *acache = cleaner->acache;
REQUIRE(CLEANER_BUSY(cleaner));
REQUIRE(event != NULL);
REQUIRE(DNS_ACACHEENTRY_VALID(cleaner->current_entry));
/* No need to lock the cleaner (see begin_cleaning()). */
LOCK(&acache->lock);
/*
* 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.
*/
if (isc_refcount_current(&cleaner->current_entry->references) == 1) {
INSIST(cleaner->current_entry->callback == NULL);
if (ISC_LINK_LINKED(cleaner->current_entry, link)) {
ISC_LIST_UNLINK(acache->entries,
cleaner->current_entry, link);
}
}
dns_acache_detachentry(&cleaner->current_entry);
if (cleaner->overmem)
acache->stats.overmem++;
acache->stats.cleaned += cleaner->ncleaned;
acache->stats.cleaner_runs++;
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE,
ISC_LOG_NOTICE,
"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",
acache,
acache->stats.hits, acache->stats.misses,
acache->stats.queries,
acache->stats.adds, acache->stats.deleted,
acache->stats.cleaned, acache->stats.cleaner_runs,
acache->stats.overmem, acache->stats.overmem_nocreates,
acache->stats.nomem);
reset_stats(acache);
isc_stdtime_get(&cleaner->last_cleanup_time);
UNLOCK(&acache->lock);
dns_acache_setcleaninginterval(cleaner->acache,
cleaner->cleaning_interval);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE,
ISC_LOG_DEBUG(1), "end acache cleaning, "
"%lu entries cleaned, mem inuse %lu",
cleaner->ncleaned,
(unsigned long)isc_mem_inuse(cleaner->acache->mctx));
if (cleaner->overmem) {
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_ACACHE, ISC_LOG_NOTICE,
"acache is still in overmem state "
"after cleaning");
}
cleaner->ncleaned = 0;
cleaner->state = cleaner_s_idle;
cleaner->resched_event = event;
}
/*
* This is run once for every acache-cleaning-interval as defined
* in named.conf.
*/
static void
acache_cleaning_timer_action(isc_task_t *task, isc_event_t *event) {
acache_cleaner_t *cleaner = event->ev_arg;
UNUSED(task);
INSIST(event->ev_type == ISC_TIMEREVENT_TICK);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE,
ISC_LOG_DEBUG(1), "acache cleaning timer fired, "
"cleaner state = %d", cleaner->state);
if (cleaner->state == cleaner_s_idle)
begin_cleaning(cleaner);
isc_event_free(&event);
}
/* The caller must hold entry lock. */
static inline isc_boolean_t
entry_stale(acache_cleaner_t *cleaner, dns_acacheentry_t *entry,
isc_stdtime32_t now32, unsigned int interval)
{
/*
* If the callback has been canceled, we definitely do not need the
* entry.
*/
if (entry->callback == NULL)
return (ISC_TRUE);
if (interval > cleaner->cleaning_interval)
interval = cleaner->cleaning_interval;
if (entry->lastused + interval < now32)
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.
*/
if (cleaner->overmem) {
unsigned int passed;
isc_uint32_t val;
if (isc_serial_ge(now32, entry->lastused))
passed = now32 - entry->lastused; /* <= interval */
else
passed = 0;
if (passed > interval / 2)
return (ISC_TRUE);
isc_random_get(&val);
if (passed > interval / 4)
return (ISC_TF(val % 4 == 0));
return (ISC_TF(val % 8 == 0));
}
return (ISC_FALSE);
}
/*
* Do incremental cleaning.
*/
static void
acache_incremental_cleaning_action(isc_task_t *task, isc_event_t *event) {
acache_cleaner_t *cleaner = event->ev_arg;
dns_acache_t *acache = cleaner->acache;
dns_acacheentry_t *entry, *next = NULL;
int n_entries;
isc_stdtime32_t now32, last32;
isc_stdtime_t now;
unsigned int interval;
INSIST(DNS_ACACHE_VALID(acache));
INSIST(task == acache->task);
INSIST(event->ev_type == DNS_EVENT_ACACHECLEAN);
if (cleaner->state == cleaner_s_done) {
cleaner->state = cleaner_s_busy;
end_cleaning(cleaner, event);
return;
}
INSIST(CLEANER_BUSY(cleaner));
n_entries = cleaner->increment;
isc_stdtime_get(&now);
isc_stdtime_convert32(now, &now32);
LOCK(&acache->lock);
entry = cleaner->current_entry;
isc_stdtime_convert32(cleaner->last_cleanup_time, &last32);
if (isc_serial_ge(now32, last32))
interval = now32 - last32;
else
interval = 0;
while (n_entries-- > 0) {
isc_boolean_t is_stale = ISC_FALSE;
INSIST(entry != NULL);
next = ISC_LIST_NEXT(entry, link);
ACACHE_LOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
is_stale = entry_stale(cleaner, entry, now32, interval);
if (is_stale) {
ISC_LIST_UNLINK(acache->entries, entry, link);
unlink_dbentries(acache, entry);
if (entry->callback != NULL)
(entry->callback)(entry, &entry->cbarg);
entry->callback = NULL;
cleaner->ncleaned++;
}
ACACHE_UNLOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
if (is_stale)
dns_acache_detachentry(&entry);
if (next == NULL) {
if (cleaner->overmem) {
entry = ISC_LIST_HEAD(acache->entries);
if (entry != NULL) {
/*
* 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_write(dns_lctx,
DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_ACACHE,
ISC_LOG_DEBUG(1),
"acache cleaner: "
"still overmem, "
"reset and try again");
next = entry;
continue;
}
}
UNLOCK(&acache->lock);
end_cleaning(cleaner, event);
return;
}
entry = next;
}
/*
* 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.
*/
INSIST(next != NULL);
dns_acache_detachentry(&cleaner->current_entry);
dns_acache_attachentry(next, &cleaner->current_entry);
UNLOCK(&acache->lock);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE,
ISC_LOG_DEBUG(1), "acache cleaner: checked %d entries, "
"mem inuse %lu, sleeping", cleaner->increment,
(unsigned long)isc_mem_inuse(cleaner->acache->mctx));
isc_task_send(task, &event);
INSIST(CLEANER_BUSY(cleaner));
return;
}
/*
* This is called when the acache either surpasses its upper limit
* or shrinks beyond its lower limit.
*/
static void
acache_overmem_cleaning_action(isc_task_t *task, isc_event_t *event) {
acache_cleaner_t *cleaner = event->ev_arg;
isc_boolean_t want_cleaning = ISC_FALSE;
UNUSED(task);
INSIST(event->ev_type == DNS_EVENT_ACACHEOVERMEM);
INSIST(cleaner->overmem_event == NULL);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ACACHE,
ISC_LOG_DEBUG(1), "overmem_cleaning_action called, "
"overmem = %d, state = %d", cleaner->overmem,
cleaner->state);
LOCK(&cleaner->lock);
if (cleaner->overmem) {
if (cleaner->state == cleaner_s_idle)
want_cleaning = ISC_TRUE;
} else {
if (cleaner->state == cleaner_s_busy)
/*
* 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.
*/
cleaner->state = cleaner_s_done;
}
cleaner->overmem_event = event;
UNLOCK(&cleaner->lock);
if (want_cleaning)
begin_cleaning(cleaner);
}
static void
water(void *arg, int mark) {
dns_acache_t *acache = arg;
isc_boolean_t overmem = ISC_TF(mark == ISC_MEM_HIWATER);
REQUIRE(DNS_ACACHE_VALID(acache));
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_ACACHE, ISC_LOG_DEBUG(1),
"acache memory reaches %s watermark, mem inuse %lu",
overmem ? "high" : "low",
(unsigned long)isc_mem_inuse(acache->mctx));
LOCK(&acache->cleaner.lock);
if (acache->cleaner.overmem != overmem) {
acache->cleaner.overmem = overmem;
if (acache->cleaner.overmem_event != NULL)
isc_task_send(acache->task,
&acache->cleaner.overmem_event);
isc_mem_waterack(acache->mctx, mark);
}
UNLOCK(&acache->cleaner.lock);
}
/*
* The cleaner task is shutting down; do the necessary cleanup.
*/
static void
acache_cleaner_shutdown_action(isc_task_t *task, isc_event_t *event) {
dns_acache_t *acache = event->ev_arg;
isc_boolean_t should_free = ISC_FALSE;
INSIST(task == acache->task);
INSIST(event->ev_type == ISC_TASKEVENT_SHUTDOWN);
INSIST(DNS_ACACHE_VALID(acache));
ATRACE("acache cleaner shutdown");
if (CLEANER_BUSY(&acache->cleaner))
end_cleaning(&acache->cleaner, event);
else
isc_event_free(&event);
LOCK(&acache->lock);
acache->live_cleaners--;
INSIST(acache->live_cleaners == 0);
if (isc_refcount_current(&acache->refs) == 0) {
INSIST(check_noentry(acache) == ISC_TRUE);
should_free = ISC_TRUE;
}
/*
* By detaching the timer in the context of its task,
* we are guaranteed that there will be no further timer
* events.
*/
if (acache->cleaner.cleaning_timer != NULL)
isc_timer_detach(&acache->cleaner.cleaning_timer);
/* Make sure we don't reschedule anymore. */
(void)isc_task_purge(task, NULL, DNS_EVENT_ACACHECLEAN, NULL);
UNLOCK(&acache->lock);
if (should_free)
destroy(acache);
}
/*
* Public functions.
*/
isc_result_t
dns_acache_create(dns_acache_t **acachep, isc_mem_t *mctx,
isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr)
{
int i;
isc_result_t result;
dns_acache_t *acache;
REQUIRE(acachep != NULL && *acachep == NULL);
REQUIRE(mctx != NULL);
REQUIRE(taskmgr != NULL);
acache = isc_mem_get(mctx, sizeof(*acache));
if (acache == NULL)
return (ISC_R_NOMEMORY);
ATRACE("create");
result = isc_refcount_init(&acache->refs, 1);
if (result != ISC_R_SUCCESS) {
isc_mem_put(mctx, acache, sizeof(*acache));
return (result);
}
result = isc_mutex_init(&acache->lock);
if (result != ISC_R_SUCCESS) {
isc_refcount_decrement(&acache->refs, NULL);
isc_refcount_destroy(&acache->refs);
isc_mem_put(mctx, acache, sizeof(*acache));
return (result);
}
acache->mctx = NULL;
isc_mem_attach(mctx, &acache->mctx);
ISC_LIST_INIT(acache->entries);
acache->shutting_down = ISC_FALSE;
acache->task = NULL;
acache->entrylocks = NULL;
result = isc_task_create(taskmgr, 1, &acache->task);
if (result != ISC_R_SUCCESS) {
UNEXPECTED_ERROR(__FILE__, __LINE__,
"isc_task_create() failed(): %s",
dns_result_totext(result));
result = ISC_R_UNEXPECTED;
goto cleanup;
}
isc_task_setname(acache->task, "acachetask", acache);
ISC_EVENT_INIT(&acache->cevent, sizeof(acache->cevent), 0, NULL,
DNS_EVENT_ACACHECONTROL, shutdown_task, NULL,
NULL, NULL, NULL);
acache->cevent_sent = ISC_FALSE;
acache->dbentries = 0;
for (i = 0; i < DBBUCKETS; i++)
ISC_LIST_INIT(acache->dbbucket[i]);
acache->entrylocks = isc_mem_get(mctx, sizeof(*acache->entrylocks) *
DEFAULT_ACACHE_ENTRY_LOCK_COUNT);
if (acache->entrylocks == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
for (i = 0; i < DEFAULT_ACACHE_ENTRY_LOCK_COUNT; i++) {
result = ACACHE_INITLOCK(&acache->entrylocks[i]);
if (result != ISC_R_SUCCESS) {
while (i-- > 0)
ACACHE_DESTROYLOCK(&acache->entrylocks[i]);
isc_mem_put(mctx, acache->entrylocks,
sizeof(*acache->entrylocks) *
DEFAULT_ACACHE_ENTRY_LOCK_COUNT);
acache->entrylocks = NULL;
goto cleanup;
}
}
acache->live_cleaners = 0;
result = acache_cleaner_init(acache, timermgr, &acache->cleaner);
if (result != ISC_R_SUCCESS)
goto cleanup;
acache->stats.cleaner_runs = 0;
reset_stats(acache);
acache->magic = ACACHE_MAGIC;
*acachep = acache;
return (ISC_R_SUCCESS);
cleanup:
if (acache->task != NULL)
isc_task_detach(&acache->task);
DESTROYLOCK(&acache->lock);
isc_refcount_decrement(&acache->refs, NULL);
isc_refcount_destroy(&acache->refs);
if (acache->entrylocks != NULL) {
for (i = 0; i < DEFAULT_ACACHE_ENTRY_LOCK_COUNT; i++)
ACACHE_DESTROYLOCK(&acache->entrylocks[i]);
isc_mem_put(mctx, acache->entrylocks,
sizeof(*acache->entrylocks) *
DEFAULT_ACACHE_ENTRY_LOCK_COUNT);
}
isc_mem_put(mctx, acache, sizeof(*acache));
isc_mem_detach(&mctx);
return (result);
}
void
dns_acache_attach(dns_acache_t *source, dns_acache_t **targetp) {
REQUIRE(DNS_ACACHE_VALID(source));
REQUIRE(targetp != NULL && *targetp == NULL);
AATRACE(source, "attach");
isc_refcount_increment(&source->refs, NULL);
*targetp = source;
}
void
dns_acache_countquerymiss(dns_acache_t *acache) {
acache->stats.misses++; /* XXXSK danger: unlocked! */
acache->stats.queries++; /* XXXSK danger: unlocked! */
}
void
dns_acache_detach(dns_acache_t **acachep) {
dns_acache_t *acache;
unsigned int refs;
isc_boolean_t should_free = ISC_FALSE;
REQUIRE(acachep != NULL && DNS_ACACHE_VALID(*acachep));
acache = *acachep;
ATRACE("detach");
isc_refcount_decrement(&acache->refs, &refs);
if (refs == 0) {
INSIST(check_noentry(acache) == ISC_TRUE);
should_free = ISC_TRUE;
}
*acachep = NULL;
/*
* If we're exiting and the cleaner task exists, let it free the cache.
*/
if (should_free && acache->live_cleaners > 0) {
isc_task_shutdown(acache->task);
should_free = ISC_FALSE;
}
if (should_free)
destroy(acache);
}
void
dns_acache_shutdown(dns_acache_t *acache) {
REQUIRE(DNS_ACACHE_VALID(acache));
LOCK(&acache->lock);
ATRACE("shutdown");
if (!acache->shutting_down) {
isc_event_t *event;
dns_acache_t *acache_evarg = NULL;
INSIST(!acache->cevent_sent);
acache->shutting_down = ISC_TRUE;
isc_mem_setwater(acache->mctx, NULL, NULL, 0, 0);
/*
* Self attach the object in order to prevent it from being
* destroyed while waiting for the event.
*/
dns_acache_attach(acache, &acache_evarg);
event = &acache->cevent;
event->ev_arg = acache_evarg;
isc_task_send(acache->task, &event);
acache->cevent_sent = ISC_TRUE;
}
UNLOCK(&acache->lock);
}
isc_result_t
dns_acache_setdb(dns_acache_t *acache, dns_db_t *db) {
int bucket;
dbentry_t *dbentry;
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(DNS_ACACHE_VALID(acache));
REQUIRE(db != NULL);
ATRACE("setdb");
LOCK(&acache->lock);
dbentry = NULL;
result = finddbent(acache, db, &dbentry);
if (result == ISC_R_SUCCESS) {
result = ISC_R_EXISTS;
goto end;
}
result = ISC_R_SUCCESS;
dbentry = isc_mem_get(acache->mctx, sizeof(*dbentry));
if (dbentry == NULL) {
result = ISC_R_NOMEMORY;
goto end;
}
ISC_LINK_INIT(dbentry, link);
ISC_LIST_INIT(dbentry->originlist);
ISC_LIST_INIT(dbentry->referlist);
dbentry->db = NULL;
dns_db_attach(db, &dbentry->db);
bucket = isc_hash_calc((const unsigned char *)&db,
sizeof(db), ISC_TRUE) % DBBUCKETS;
ISC_LIST_APPEND(acache->dbbucket[bucket], dbentry, link);
acache->dbentries++;
end:
UNLOCK(&acache->lock);
return (result);
}
isc_result_t
dns_acache_putdb(dns_acache_t *acache, dns_db_t *db) {
int bucket;
isc_result_t result;
dbentry_t *dbentry;
dns_acacheentry_t *entry;
REQUIRE(DNS_ACACHE_VALID(acache));
REQUIRE(db != NULL);
ATRACE("putdb");
LOCK(&acache->lock);
dbentry = NULL;
result = finddbent(acache, db, &dbentry);
if (result != ISC_R_SUCCESS) {
/*
* The entry may have not been created due to memory shortage.
*/
UNLOCK(&acache->lock);
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.
*/
while ((entry = ISC_LIST_HEAD(dbentry->originlist)) != NULL) {
ACACHE_LOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
/*
* Releasing olink first would avoid finddbent() in
* unlink_dbentries().
*/
ISC_LIST_UNLINK(dbentry->originlist, entry, olink);
if (acache->cleaner.current_entry != entry)
ISC_LIST_UNLINK(acache->entries, entry, link);
unlink_dbentries(acache, entry);
if (entry->callback != NULL)
(entry->callback)(entry, &entry->cbarg);
entry->callback = NULL;
ACACHE_UNLOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
if (acache->cleaner.current_entry != entry)
dns_acache_detachentry(&entry);
}
while ((entry = ISC_LIST_HEAD(dbentry->referlist)) != NULL) {
ACACHE_LOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
ISC_LIST_UNLINK(dbentry->referlist, entry, rlink);
if (acache->cleaner.current_entry != entry)
ISC_LIST_UNLINK(acache->entries, entry, link);
unlink_dbentries(acache, entry);
if (entry->callback != NULL)
(entry->callback)(entry, &entry->cbarg);
entry->callback = NULL;
ACACHE_UNLOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
if (acache->cleaner.current_entry != entry)
dns_acache_detachentry(&entry);
}
INSIST(ISC_LIST_EMPTY(dbentry->originlist) &&
ISC_LIST_EMPTY(dbentry->referlist));
bucket = isc_hash_calc((const unsigned char *)&db,
sizeof(db), ISC_TRUE) % DBBUCKETS;
ISC_LIST_UNLINK(acache->dbbucket[bucket], dbentry, link);
dns_db_detach(&dbentry->db);
isc_mem_put(acache->mctx, dbentry, sizeof(*dbentry));
acache->dbentries--;
acache->stats.deleted++;
UNLOCK(&acache->lock);
return (ISC_R_SUCCESS);
}
isc_result_t
dns_acache_createentry(dns_acache_t *acache, dns_db_t *origdb,
void (*callback)(dns_acacheentry_t *, void **),
void *cbarg, dns_acacheentry_t **entryp)
{
dns_acacheentry_t *newentry;
isc_result_t result;
isc_uint32_t r;
REQUIRE(DNS_ACACHE_VALID(acache));
REQUIRE(entryp != NULL && *entryp == NULL);
REQUIRE(origdb != NULL);
/*
* 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.
*/
if (acache->cleaner.overmem) {
acache->stats.overmem_nocreates++; /* XXXSK danger: unlocked! */
return (ISC_R_NORESOURCES);
}
newentry = isc_mem_get(acache->mctx, sizeof(*newentry));
if (newentry == NULL) {
acache->stats.nomem++; /* XXXMLG danger: unlocked! */
return (ISC_R_NOMEMORY);
}
isc_random_get(&r);
newentry->locknum = r % DEFAULT_ACACHE_ENTRY_LOCK_COUNT;
result = isc_refcount_init(&newentry->references, 1);
if (result != ISC_R_SUCCESS) {
isc_mem_put(acache->mctx, newentry, sizeof(*newentry));
return (result);
};
ISC_LINK_INIT(newentry, link);
ISC_LINK_INIT(newentry, olink);
ISC_LINK_INIT(newentry, rlink);
newentry->acache = NULL;
dns_acache_attach(acache, &newentry->acache);
newentry->zone = NULL;
newentry->db = NULL;
newentry->version = NULL;
newentry->node = NULL;
newentry->foundname = NULL;
newentry->callback = callback;
newentry->cbarg = cbarg;
newentry->origdb = NULL;
dns_db_attach(origdb, &newentry->origdb);
isc_stdtime_get(&newentry->lastused);
newentry->magic = ACACHEENTRY_MAGIC;
*entryp = newentry;
return (ISC_R_SUCCESS);
}
isc_result_t
dns_acache_getentry(dns_acacheentry_t *entry, dns_zone_t **zonep,
dns_db_t **dbp, dns_dbversion_t **versionp,
dns_dbnode_t **nodep, dns_name_t *fname,
dns_message_t *msg, isc_stdtime_t now)
{
isc_result_t result = ISC_R_SUCCESS;
dns_rdataset_t *erdataset;
isc_stdtime32_t now32;
dns_acache_t *acache;
int locknum;
REQUIRE(DNS_ACACHEENTRY_VALID(entry));
REQUIRE(zonep == NULL || *zonep == NULL);
REQUIRE(dbp != NULL && *dbp == NULL);
REQUIRE(versionp != NULL && *versionp == NULL);
REQUIRE(nodep != NULL && *nodep == NULL);
REQUIRE(fname != NULL);
REQUIRE(msg != NULL);
acache = entry->acache;
REQUIRE(DNS_ACACHE_VALID(acache));
locknum = entry->locknum;
ACACHE_LOCK(&acache->entrylocks[locknum], isc_rwlocktype_read);
isc_stdtime_convert32(now, &now32);
acache_storetime(entry, now32);
if (entry->zone != NULL && zonep != NULL)
dns_zone_attach(entry->zone, zonep);
if (entry->db == NULL) {
*dbp = NULL;
*versionp = NULL;
} else {
dns_db_attach(entry->db, dbp);
dns_db_attachversion(entry->db, entry->version, versionp);
}
if (entry->node == NULL)
*nodep = NULL;
else {
dns_db_attachnode(entry->db, entry->node, nodep);
INSIST(entry->foundname != NULL);
dns_name_copy(entry->foundname, fname, NULL);
for (erdataset = ISC_LIST_HEAD(entry->foundname->list);
erdataset != NULL;
erdataset = ISC_LIST_NEXT(erdataset, link)) {
dns_rdataset_t *ardataset;
ardataset = NULL;
result = dns_message_gettemprdataset(msg, &ardataset);
if (result != ISC_R_SUCCESS) {
ACACHE_UNLOCK(&acache->entrylocks[locknum],
isc_rwlocktype_read);
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.
*/
dns_rdataset_clone(erdataset, ardataset);
ISC_LIST_APPEND(fname->list, ardataset, link);
}
}
entry->acache->stats.hits++; /* XXXMLG danger: unlocked! */
entry->acache->stats.queries++;
ACACHE_UNLOCK(&acache->entrylocks[locknum], isc_rwlocktype_read);
return (result);
fail:
while ((erdataset = ISC_LIST_HEAD(fname->list)) != NULL) {
ISC_LIST_UNLINK(fname->list, erdataset, link);
dns_rdataset_disassociate(erdataset);
dns_message_puttemprdataset(msg, &erdataset);
}
if (*nodep != NULL)
dns_db_detachnode(*dbp, nodep);
if (*versionp != NULL)
dns_db_closeversion(*dbp, versionp, ISC_FALSE);
if (*dbp != NULL)
dns_db_detach(dbp);
if (zonep != NULL && *zonep != NULL)
dns_zone_detach(zonep);
return (result);
}
isc_result_t
dns_acache_setentry(dns_acache_t *acache, dns_acacheentry_t *entry,
dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version,
dns_dbnode_t *node, dns_name_t *fname)
{
isc_result_t result;
dbentry_t *odbent;
dbentry_t *rdbent = NULL;
isc_boolean_t close_version = ISC_FALSE;
dns_acacheentry_t *dummy_entry = NULL;
REQUIRE(DNS_ACACHE_VALID(acache));
REQUIRE(DNS_ACACHEENTRY_VALID(entry));
LOCK(&acache->lock); /* XXX: need to lock it here for ordering */
ACACHE_LOCK(&acache->entrylocks[entry->locknum], isc_rwlocktype_write);
/* Set zone */
if (zone != NULL)
dns_zone_attach(zone, &entry->zone);
/* Set DB */
if (db != NULL)
dns_db_attach(db, &entry->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 (version == NULL) {
if (db != NULL) {
dns_db_currentversion(db, &version);
close_version = ISC_TRUE;
}
}
if (version != NULL) {
INSIST(db != NULL);
dns_db_attachversion(db, version, &entry->version);
}
if (close_version)
dns_db_closeversion(db, &version, ISC_FALSE);
/* Set DB node. */
if (node != NULL) {
INSIST(db != NULL);
dns_db_attachnode(db, node, &entry->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.
*/
if (node != NULL) {
dns_rdataset_t *ardataset, *crdataset;
entry->foundname = isc_mem_get(acache->mctx,
sizeof(*entry->foundname));
if (entry->foundname == NULL) {
result = ISC_R_NOMEMORY;
goto fail;
}
dns_name_init(entry->foundname, NULL);
result = dns_name_dup(fname, acache->mctx,
entry->foundname);
if (result != ISC_R_SUCCESS)
goto fail;
for (ardataset = ISC_LIST_HEAD(fname->list);
ardataset != NULL;
ardataset = ISC_LIST_NEXT(ardataset, link)) {
crdataset = isc_mem_get(acache->mctx,
sizeof(*crdataset));
if (crdataset == NULL) {
result = ISC_R_NOMEMORY;
goto fail;
}
dns_rdataset_init(crdataset);
dns_rdataset_clone(ardataset, crdataset);
ISC_LIST_APPEND(entry->foundname->list, crdataset,
link);
}
}
odbent = NULL;
result = finddbent(acache, entry->origdb, &odbent);
if (result != ISC_R_SUCCESS)
goto fail;
if (db != NULL) {
rdbent = NULL;
result = finddbent(acache, db, &rdbent);
if (result != ISC_R_SUCCESS)
goto fail;
}
ISC_LIST_APPEND(acache->entries, entry, link);
ISC_LIST_APPEND(odbent->originlist, entry, olink);
if (rdbent != NULL)
ISC_LIST_APPEND(rdbent->referlist, entry, rlink);
/*
* The additional cache needs an implicit reference to entries in its
* link.
*/
dns_acache_attachentry(entry, &dummy_entry);
ACACHE_UNLOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
acache->stats.adds++;
UNLOCK(&acache->lock);
return (ISC_R_SUCCESS);
fail:
clear_entry(acache, entry);
ACACHE_UNLOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
UNLOCK(&acache->lock);
return (result);
}
isc_boolean_t
dns_acache_cancelentry(dns_acacheentry_t *entry) {
dns_acache_t *acache;
isc_boolean_t callback_active;
REQUIRE(DNS_ACACHEENTRY_VALID(entry));
acache = entry->acache;
INSIST(DNS_ACACHE_VALID(entry->acache));
LOCK(&acache->lock);
ACACHE_LOCK(&acache->entrylocks[entry->locknum], isc_rwlocktype_write);
callback_active = ISC_TF(entry->cbarg != NULL);
/*
* 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.
*/
unlink_dbentries(acache, entry);
clear_entry(entry->acache, entry);
entry->callback = NULL;
entry->cbarg = NULL;
ACACHE_UNLOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
UNLOCK(&acache->lock);
return (callback_active);
}
void
dns_acache_attachentry(dns_acacheentry_t *source,
dns_acacheentry_t **targetp)
{
REQUIRE(DNS_ACACHEENTRY_VALID(source));
REQUIRE(targetp != NULL && *targetp == NULL);
isc_refcount_increment(&source->references, NULL);
*targetp = source;
}
void
dns_acache_detachentry(dns_acacheentry_t **entryp) {
dns_acacheentry_t *entry;
unsigned int refs;
REQUIRE(entryp != NULL && DNS_ACACHEENTRY_VALID(*entryp));
entry = *entryp;
isc_refcount_decrement(&entry->references, &refs);
/*
* If there are no references to the entry, the entry must have been
* unlinked and can be destroyed safely.
*/
if (refs == 0) {
INSIST(!ISC_LINK_LINKED(entry, link));
(*entryp)->acache->stats.deleted++;
destroy_entry(entry);
}
*entryp = NULL;
}
void
dns_acache_setcleaninginterval(dns_acache_t *acache, unsigned int t) {
isc_interval_t interval;
isc_result_t result;
REQUIRE(DNS_ACACHE_VALID(acache));
ATRACE("dns_acache_setcleaninginterval");
LOCK(&acache->lock);
/*
* 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.)
*/
if (acache->cleaner.cleaning_timer == NULL)
goto unlock;
acache->cleaner.cleaning_interval = t;
if (t == 0) {
result = isc_timer_reset(acache->cleaner.cleaning_timer,
isc_timertype_inactive,
NULL, NULL, ISC_TRUE);
} else {
isc_interval_set(&interval, acache->cleaner.cleaning_interval,
0);
result = isc_timer_reset(acache->cleaner.cleaning_timer,
isc_timertype_ticker,
NULL, &interval, ISC_FALSE);
}
if (result != ISC_R_SUCCESS)
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_ACACHE, ISC_LOG_WARNING,
"could not set acache cleaning interval: %s",
isc_result_totext(result));
else
isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_ACACHE, ISC_LOG_NOTICE,
"acache %p cleaning interval set to %d.",
acache, t);
unlock:
UNLOCK(&acache->lock);
}
/*
* This function was derived from cache.c:dns_cache_setcachesize(). See the
* function for more details about the logic.
*/
void
dns_acache_setcachesize(dns_acache_t *acache, size_t size) {
size_t hiwater, lowater;
REQUIRE(DNS_ACACHE_VALID(acache));
if (size != 0U && size < DNS_ACACHE_MINSIZE)
size = DNS_ACACHE_MINSIZE;
hiwater = size - (size >> 3);
lowater = size - (size >> 2);
if (size == 0U || hiwater == 0U || lowater == 0U)
isc_mem_setwater(acache->mctx, water, acache, 0, 0);
else
isc_mem_setwater(acache->mctx, water, acache,
hiwater, lowater);
}