#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/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
#define DNS_ACACHE_MINSIZE 2097152
#define DNS_ACACHE_CLEANERINCREMENT 1000
#define DEFAULT_ACACHE_ENTRY_LOCK_COUNT 1009
#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
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,
cleaner_s_busy,
cleaner_s_done
} cleaner_state_t;
#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;
dns_acache_t *acache;
unsigned int cleaning_interval;
isc_stdtime_t last_cleanup_time;
isc_timer_t *cleaning_timer;
isc_event_t *resched_event;
isc_event_t *overmem_event;
dns_acacheentry_t *current_entry;
int increment;
unsigned long ncleaned;
cleaner_state_t state;
isc_boolean_t overmem;
};
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;
};
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;
ISC_LINK(dns_acacheentry_t) link;
ISC_LINK(dns_acacheentry_t) olink;
ISC_LINK(dns_acacheentry_t) rlink;
dns_db_t *origdb;
dns_zone_t *zone;
dns_db_t *db;
dns_dbversion_t *version;
dns_dbnode_t *node;
dns_name_t *foundname;
void (*callback)(dns_acacheentry_t *, void **);
void *cbarg;
isc_stdtime32_t lastused;
};
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);
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;
}
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);
}
static void
shutdown_entries(dns_acache_t *acache) {
dns_acacheentry_t *entry, *entry_next;
REQUIRE(DNS_ACACHE_VALID(acache));
INSIST(acache->shutting_down);
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 (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);
}
}
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);
}
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);
}
}
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));
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);
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));
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;
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;
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));
LOCK(&acache->lock);
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;
}
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);
}
static inline isc_boolean_t
entry_stale(acache_cleaner_t *cleaner, dns_acacheentry_t *entry,
isc_stdtime32_t now32, unsigned int interval)
{
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 (cleaner->overmem) {
unsigned int passed =
now32 - entry->lastused;
isc_uint32_t val;
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);
}
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);
INSIST(now32 > last32);
interval = now32 - last32;
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) {
isc_log_write(dns_lctx,
DNS_LOGCATEGORY_DATABASE,
DNS_LOGMODULE_ACACHE,
ISC_LOG_DEBUG(1),
"acache cleaner: "
"still overmem, "
"reset and try again");
continue;
}
}
UNLOCK(&acache->lock);
end_cleaning(cleaner, event);
return;
}
entry = next;
}
INSIST(next != NULL && next != cleaner->current_entry);
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;
}
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)
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);
acache->cleaner.overmem = overmem;
if (acache->cleaner.overmem_event != NULL)
isc_task_send(acache->task, &acache->cleaner.overmem_event);
UNLOCK(&acache->cleaner.lock);
}
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;
}
if (acache->cleaner.cleaning_timer != NULL)
isc_timer_detach(&acache->cleaner.cleaning_timer);
(void)isc_task_purge(task, NULL, DNS_EVENT_ACACHECLEAN, NULL);
UNLOCK(&acache->lock);
if (should_free)
destroy(acache);
}
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++;
acache->stats.queries++;
}
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 (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);
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) {
UNLOCK(&acache->lock);
return (ISC_R_NOTFOUND);
}
while ((entry = ISC_LIST_HEAD(dbentry->originlist)) != NULL) {
ACACHE_LOCK(&acache->entrylocks[entry->locknum],
isc_rwlocktype_write);
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);
if (acache->cleaner.overmem) {
acache->stats.overmem_nocreates++;
return (ISC_R_NORESOURCES);
}
newentry = isc_mem_get(acache->mctx, sizeof(*newentry));
if (newentry == NULL) {
acache->stats.nomem++;
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;
}
dns_rdataset_init(ardataset);
dns_rdataset_clone(erdataset, ardataset);
ISC_LIST_APPEND(fname->list, ardataset, link);
}
}
entry->acache->stats.hits++;
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);
ACACHE_LOCK(&acache->entrylocks[entry->locknum], isc_rwlocktype_write);
if (zone != NULL)
dns_zone_attach(zone, &entry->zone);
if (db != NULL)
dns_db_attach(db, &entry->db);
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);
if (node != NULL) {
INSIST(db != NULL);
dns_db_attachnode(db, node, &entry->node);
}
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);
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);
}
void
dns_acache_cancelentry(dns_acacheentry_t *entry) {
dns_acache_t *acache = entry->acache;
REQUIRE(DNS_ACACHEENTRY_VALID(entry));
INSIST(DNS_ACACHE_VALID(acache));
LOCK(&acache->lock);
ACACHE_LOCK(&acache->entrylocks[entry->locknum], isc_rwlocktype_write);
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);
}
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 (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);
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);
}
void
dns_acache_setcachesize(dns_acache_t *acache, isc_uint32_t size) {
isc_uint32_t lowater;
isc_uint32_t hiwater;
REQUIRE(DNS_ACACHE_VALID(acache));
if (size != 0 && size < DNS_ACACHE_MINSIZE)
size = DNS_ACACHE_MINSIZE;
hiwater = size - (size >> 3);
lowater = size - (size >> 2);
if (size == 0 || hiwater == 0 || lowater == 0)
isc_mem_setwater(acache->mctx, water, acache, 0, 0);
else
isc_mem_setwater(acache->mctx, water, acache,
hiwater, lowater);
}