#include <isc/async.h>
#include <isc/atomic.h>
+#include <isc/attributes.h>
#include <isc/hash.h>
#include <isc/hashmap.h>
#include <isc/list.h>
#include <isc/random.h>
#include <isc/result.h>
#include <isc/rwlock.h>
+#include <isc/sieve.h>
#include <isc/stats.h>
#include <isc/string.h>
#include <isc/tid.h>
#include <dns/rdatatype.h>
#include <dns/stats.h>
#include <dns/transport.h>
+#include <dns/types.h>
#define DNS_ADB_MAGIC ISC_MAGIC('D', 'a', 'd', 'b')
#define DNS_ADB_VALID(x) ISC_MAGIC_VALID(x, DNS_ADB_MAGIC)
isc_refcount_t references;
- dns_adbnamelist_t names_lru;
- isc_stdtime_t names_last_update;
+ ISC_SIEVE(dns_adbname_t) names_lru;
isc_hashmap_t *names;
isc_rwlock_t names_lock;
- dns_adbentrylist_t entries_lru;
- isc_stdtime_t entries_last_update;
+ ISC_SIEVE(dns_adbentry_t) entries_lru;
isc_hashmap_t *entries;
isc_rwlock_t entries_lock;
unsigned int fetch6_err;
dns_adbfindlist_t finds;
isc_mutex_t lock;
- isc_stdtime_t last_used;
- /* for LRU-based management */
+ /* for LRU-based management */
ISC_LINK(dns_adbname_t) link;
+ bool visited;
};
#if DNS_ADB_TRACE
dns_adb_t *adb;
isc_mutex_t lock;
- isc_stdtime_t last_used;
isc_refcount_t references;
dns_adbnamehooklist_t nhs;
*/
ISC_LINK(dns_adbentry_t) link;
+ bool visited;
};
#if DNS_ADB_TRACE
static void
free_adbfetch(dns_adb_t *, dns_adbfetch_t **);
static void
-purge_stale_names(dns_adb_t *adb, isc_stdtime_t now);
-static void
purge_names_overmem(dns_adb_t *adb, size_t requested);
static dns_adbname_t *
get_attached_and_locked_name(dns_adb_t *, const dns_name_t *, unsigned int type,
isc_stdtime_t now);
static void
-purge_stale_entries(dns_adb_t *adb, isc_stdtime_t now);
-static void
purge_entries_overmem(dns_adb_t *adb, size_t requested);
static dns_adbentry_t *
get_attached_and_locked_entry(dns_adb_t *adb, isc_stdtime_t now,
clean_finds_at_name(dns_adbname_t *, dns_adbstatus_t, unsigned int);
static void
maybe_expire_namehooks(dns_adbname_t *, isc_stdtime_t);
-static void
+static bool
+name_expired(dns_adbname_t *adbname, isc_stdtime_t now);
+static bool
maybe_expire_name(dns_adbname_t *adbname, isc_stdtime_t now);
static void
expire_name(dns_adbname_t *adbname, dns_adbstatus_t astat);
match_ptr, adbname);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
/* ... and LRU list */
- ISC_LIST_UNLINK(adb->names_lru, adbname, link);
+ ISC_SIEVE_UNLINK(adb->names_lru, adbname, link);
dns_adbname_unref(adbname);
}
static void
shutdown_names(dns_adb_t *adb) {
RWLOCK(&adb->names_lock, isc_rwlocktype_write);
- ISC_LIST_FOREACH (adb->names_lru, name, link) {
- dns_adbname_ref(name);
- LOCK(&name->lock);
+ ISC_SIEVE_FOREACH(adb->names_lru, adbname, link) {
+ dns_adbname_ref(adbname);
+ LOCK(&adbname->lock);
/*
* Run through the list. For each name, clean up finds
* found there, and cancel any fetches running. When
* all the fetches are canceled, the name will destroy
* itself.
*/
- expire_name(name, DNS_ADB_SHUTTINGDOWN);
- UNLOCK(&name->lock);
- dns_adbname_detach(&name);
+ expire_name(adbname, DNS_ADB_SHUTTINGDOWN);
+ UNLOCK(&adbname->lock);
+ dns_adbname_detach(&adbname);
}
RWUNLOCK(&adb->names_lock, isc_rwlocktype_write);
}
static void
shutdown_entries(dns_adb_t *adb) {
RWLOCK(&adb->entries_lock, isc_rwlocktype_write);
- ISC_LIST_FOREACH (adb->entries_lru, adbentry, link) {
+ ISC_SIEVE_FOREACH(adb->entries_lru, adbentry, link) {
expire_entry(adbentry);
}
RWUNLOCK(&adb->entries_lock, isc_rwlocktype_write);
unsigned int type, isc_stdtime_t now) {
isc_result_t result;
dns_adbname_t *adbname = NULL;
- isc_time_t timenow;
- isc_stdtime_t last_update;
dns_adbname_t key = {
.name = UNCONST(name),
.type = type,
};
uint32_t hashval = hash_adbname(&key);
isc_rwlocktype_t locktype = isc_rwlocktype_read;
- bool overmem = isc_mem_isovermem(adb->mctx);
-
- isc_time_set(&timenow, now, 0);
RWLOCK(&adb->names_lock, locktype);
- last_update = adb->names_last_update;
- if (last_update + ADB_STALE_MARGIN >= now || overmem) {
- last_update = now;
+ if (isc_mem_isovermem(adb->mctx)) {
UPGRADELOCK(&adb->names_lock, locktype);
- if (overmem) {
- purge_names_overmem(adb, 2 * sizeof(*adbname));
- } else {
- purge_stale_names(adb, now);
- }
- adb->names_last_update = last_update;
+ purge_names_overmem(adb, 2 * sizeof(*adbname));
}
result = isc_hashmap_find(adb->names, hashval, match_adbname,
(void *)&key, (void **)&adbname);
- switch (result) {
- case ISC_R_NOTFOUND:
+ if (result == ISC_R_NOTFOUND) {
UPGRADELOCK(&adb->names_lock, locktype);
+ create:
+ INSIST(locktype == isc_rwlocktype_write);
/* Allocate a new name and add it to the hash table. */
adbname = new_adbname(adb, name, key.type);
+ LOCK(&adbname->lock);
+
void *found = NULL;
result = isc_hashmap_add(adb->names, hashval, match_adbname,
(void *)&key, adbname, &found);
- if (result == ISC_R_EXISTS) {
- destroy_adbname(adbname);
- adbname = found;
- result = ISC_R_SUCCESS;
- ISC_LIST_UNLINK(adb->names_lru, adbname, link);
+ if (result == ISC_R_SUCCESS) {
+ dns_adbname_ref(adbname);
+ ISC_SIEVE_INSERT(adb->names_lru, adbname, link);
+ goto unlock;
}
- INSIST(result == ISC_R_SUCCESS);
- break;
- case ISC_R_SUCCESS:
- if (locktype == isc_rwlocktype_write) {
- ISC_LIST_UNLINK(adb->names_lru, adbname, link);
- }
- break;
- default:
- UNREACHABLE();
+ UNLOCK(&adbname->lock);
+ destroy_adbname(adbname);
+ adbname = found;
+ result = ISC_R_SUCCESS;
}
+ INSIST(!NAME_DEAD(adbname));
+
+ LOCK(&adbname->lock);
+ ISC_SIEVE_MARK(adbname, visited);
+
dns_adbname_ref(adbname);
- LOCK(&adbname->lock); /* Must be unlocked by the caller */
- if (adbname->last_used + ADB_CACHE_MINIMUM <= last_update) {
- adbname->last_used = now;
- }
- if (locktype == isc_rwlocktype_write) {
- ISC_LIST_PREPEND(adb->names_lru, adbname, link);
+ switch (locktype) {
+ case isc_rwlocktype_read:
+ /* Is the pre-existing name we found already expired */
+ if (!name_expired(adbname, now)) {
+ break;
+ }
+
+ /* We need to upgrade the LRU lock */
+ UNLOCK(&adbname->lock);
+ UPGRADELOCK(&adb->names_lock, locktype);
+ LOCK(&adbname->lock);
+ FALLTHROUGH;
+ case isc_rwlocktype_write:
+ if (NAME_DEAD(adbname) || maybe_expire_name(adbname, now)) {
+ UNLOCK(&adbname->lock);
+ dns_adbname_detach(&adbname);
+ goto create;
+ }
+ break;
+ default:
+ UNREACHABLE();
}
+unlock:
/*
* The refcount is now 2 and the final detach will happen in
* expire_name() - the unused adbname stored in the hashtable and lru
const isc_sockaddr_t *addr) {
isc_result_t result;
dns_adbentry_t *adbentry = NULL;
- isc_time_t timenow;
- isc_stdtime_t last_update;
uint32_t hashval = isc_sockaddr_hash(addr, true);
isc_rwlocktype_t locktype = isc_rwlocktype_read;
- bool overmem = isc_mem_isovermem(adb->mctx);
-
- isc_time_set(&timenow, now, 0);
RWLOCK(&adb->entries_lock, locktype);
- last_update = adb->entries_last_update;
-
- if (now - last_update > ADB_STALE_MARGIN || overmem) {
- last_update = now;
+ if (isc_mem_isovermem(adb->mctx)) {
UPGRADELOCK(&adb->entries_lock, locktype);
- if (overmem) {
- purge_entries_overmem(adb, 2 * sizeof(*adbentry));
- } else {
- purge_stale_entries(adb, now);
- }
- adb->entries_last_update = now;
+ purge_entries_overmem(adb, 2 * sizeof(*adbentry));
}
result = isc_hashmap_find(adb->entries, hashval, match_adbentry,
create:
INSIST(locktype == isc_rwlocktype_write);
-
/* Allocate a new entry and add it to the hash table. */
adbentry = new_adbentry(adb, addr, now);
+ LOCK(&adbentry->lock);
+
void *found = NULL;
result = isc_hashmap_add(adb->entries, hashval, match_adbentry,
&adbentry->sockaddr, adbentry, &found);
if (result == ISC_R_SUCCESS) {
- ISC_LIST_PREPEND(adb->entries_lru, adbentry, link);
- } else if (result == ISC_R_EXISTS) {
- dns_adbentry_detach(&adbentry);
- adbentry = found;
- result = ISC_R_SUCCESS;
+ dns_adbentry_ref(adbentry);
+ ISC_SIEVE_INSERT(adb->entries_lru, adbentry, link);
+ goto unlock;
}
+
+ UNLOCK(&adbentry->lock);
+ dns_adbentry_detach(&adbentry);
+ adbentry = found;
}
- INSIST(result == ISC_R_SUCCESS);
- /*
- * The dns_adbentry_ref() must stay here before trying to expire
- * the ADB entry, so it is not destroyed under the lock.
- */
+ INSIST(!ENTRY_DEAD(adbentry));
+
+ LOCK(&adbentry->lock);
+ ISC_SIEVE_MARK(adbentry, visited);
+
dns_adbentry_ref(adbentry);
- LOCK(&adbentry->lock); /* Must be unlocked by the caller */
+
switch (locktype) {
case isc_rwlocktype_read:
+ /* Is the pre-existing entry we found already expired */
if (!entry_expired(adbentry, now)) {
break;
}
UNREACHABLE();
}
- /* Did enough time pass to update the LRU? */
- if (adbentry->last_used + ADB_CACHE_MINIMUM <= last_update) {
- adbentry->last_used = now;
- if (locktype == isc_rwlocktype_write) {
- ISC_LIST_UNLINK(adb->entries_lru, adbentry, link);
- ISC_LIST_PREPEND(adb->entries_lru, adbentry, link);
- }
- }
-
+unlock:
+ /*
+ * The refcount is now 2 and the final detach will happen in
+ * expire_entry() - the unused adbentry stored in the hashtable and lru
+ * has always refcount == 1
+ */
RWUNLOCK(&adb->entries_lock, locktype);
return adbentry;
/*
* The name must be locked and write lock on adb->names_lock must be held.
*/
-static void
-maybe_expire_name(dns_adbname_t *adbname, isc_stdtime_t now) {
+static bool
+name_expired(dns_adbname_t *adbname, isc_stdtime_t now) {
REQUIRE(DNS_ADBNAME_VALID(adbname));
/* Leave this name alone if it still has active namehooks... */
if (NAME_HAS_V4(adbname) || NAME_HAS_V6(adbname)) {
- return;
+ return false;
}
/* ...an active fetch in progres... */
if (NAME_FETCH(adbname)) {
- return;
+ return false;
}
/* ... or is not yet expired. */
if (!EXPIRE_OK(adbname->expire_v4, now) ||
!EXPIRE_OK(adbname->expire_v6, now))
{
- return;
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+maybe_expire_name(dns_adbname_t *adbname, isc_stdtime_t now) {
+ if (name_expired(adbname, now)) {
+ expire_name(adbname, DNS_ADB_EXPIRED);
+ true;
}
- expire_name(adbname, DNS_ADB_EXPIRED);
+ return false;
}
static void
isc_sockaddr_hash(&adbentry->sockaddr, true), match_ptr,
adbentry);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
- ISC_LIST_UNLINK(adb->entries_lru, adbentry, link);
+ ISC_SIEVE_UNLINK(adb->entries_lru, adbentry, link);
}
dns_adbentry_detach(&adbentry);
return false;
}
-/*%
- * Examine the tail entry of the LRU list to see if it expires or is stale
- * (unused for some period); if so, the name entry will be freed. If the ADB
- * is in the overmem condition, the tail and the next to tail entries
- * will be unconditionally removed (unless they have an outstanding fetch).
- * We don't care about a race on 'overmem' at the risk of causing some
- * collateral damage or a small delay in starting cleanup.
- *
- * adb->names_lock MUST be write locked
- */
-static void
-purge_stale_names(dns_adb_t *adb, isc_stdtime_t now) {
- dns_adbname_t *adbname = ISC_LIST_TAIL(adb->names_lru);
-
- if (adbname == NULL) {
- return;
- }
-
- dns_adbname_ref(adbname);
- LOCK(&adbname->lock);
-
- /*
- * Remove the name if it's expired or unused, has no address data.
- */
- maybe_expire_namehooks(adbname, now);
- maybe_expire_name(adbname, now);
-
- UNLOCK(&adbname->lock);
- dns_adbname_detach(&adbname);
-}
-
static void
purge_names_overmem(dns_adb_t *adb, size_t requested) {
- dns_adbname_t *prev = NULL;
size_t expired = 0;
- for (dns_adbname_t *adbname = ISC_LIST_TAIL(adb->names_lru);
- adbname != NULL && expired < requested; adbname = prev)
- {
- prev = ISC_LIST_PREV(adbname, link);
+ do {
+ dns_adbname_t *adbname = ISC_SIEVE_NEXT(adb->names_lru, visited,
+ link);
+ if (adbname == NULL) {
+ return;
+ }
dns_adbname_ref(adbname);
LOCK(&adbname->lock);
maybe_expire_namehooks(adbname, INT_MAX);
expire_name(adbname, DNS_ADB_CANCELED);
expired += sizeof(*adbname);
+
UNLOCK(&adbname->lock);
dns_adbname_detach(&adbname);
- }
+ } while (expired < requested);
}
static void
cleanup_names(dns_adb_t *adb, isc_stdtime_t now) {
RWLOCK(&adb->names_lock, isc_rwlocktype_write);
- ISC_LIST_FOREACH (adb->names_lru, adbname, link) {
+ ISC_SIEVE_FOREACH(adb->names_lru, adbname, link) {
dns_adbname_ref(adbname);
LOCK(&adbname->lock);
/*
* fetches, we can remove this name from the bucket.
*/
maybe_expire_namehooks(adbname, now);
- maybe_expire_name(adbname, now);
+ (void)maybe_expire_name(adbname, now);
UNLOCK(&adbname->lock);
dns_adbname_detach(&adbname);
}
RWUNLOCK(&adb->names_lock, isc_rwlocktype_write);
}
-/*%
- * Examine the tail entry of the LRU list to see if it expires or is stale
- * (unused for some period); if so, the name entry will be freed. If the ADB
- * is in the overmem condition, the tail and the next to tail entries
- * will be unconditionally removed (unless they have an outstanding fetch).
- * We don't care about a race on 'overmem' at the risk of causing some
- * collateral damage or a small delay in starting cleanup.
- *
- * adb->entries_lock MUST be write locked
- */
-static void
-purge_stale_entries(dns_adb_t *adb, isc_stdtime_t now) {
- dns_adbentry_t *adbentry = ISC_LIST_TAIL(adb->entries_lru);
-
- if (adbentry == NULL) {
- return;
- }
-
- dns_adbentry_ref(adbentry);
- LOCK(&adbentry->lock);
-
- /*
- * Remove the entry if it's expired and unused.
- */
- (void)maybe_expire_entry(adbentry, now);
-
- UNLOCK(&adbentry->lock);
- dns_adbentry_detach(&adbentry);
-}
-
static void
purge_entries_overmem(dns_adb_t *adb, size_t requested) {
- dns_adbentry_t *prev = NULL;
size_t expired = 0;
- /*
- * We limit the number of scanned entries to 10 (arbitrary choice)
- * in order to avoid examining too many entries when there are many
- * tail entries that have fetches (this should be rare, but could
- * happen).
- */
-
- for (dns_adbentry_t *adbentry = ISC_LIST_TAIL(adb->entries_lru);
- adbentry != NULL && expired < requested; adbentry = prev)
- {
- prev = ISC_LIST_PREV(adbentry, link);
+ do {
+ dns_adbentry_t *adbentry = ISC_SIEVE_NEXT(adb->entries_lru,
+ visited, link);
+ if (adbentry == NULL) {
+ return;
+ }
dns_adbentry_ref(adbentry);
LOCK(&adbentry->lock);
UNLOCK(&adbentry->lock);
dns_adbentry_detach(&adbentry);
- }
+ } while (expired < requested);
}
static void
cleanup_entries(dns_adb_t *adb, isc_stdtime_t now) {
RWLOCK(&adb->entries_lock, isc_rwlocktype_write);
- ISC_LIST_FOREACH (adb->entries_lru, adbentry, link) {
+ ISC_SIEVE_FOREACH(adb->entries_lru, adbentry, link) {
dns_adbentry_ref(adbentry);
LOCK(&adbentry->lock);
maybe_expire_entry(adbentry, now);
adb = isc_mem_get(mem, sizeof(dns_adb_t));
*adb = (dns_adb_t){
- .names_lru = ISC_LIST_INITIALIZER,
- .entries_lru = ISC_LIST_INITIALIZER,
+ .references = 1,
};
+ ISC_SIEVE_INIT(adb->names_lru);
+ ISC_SIEVE_INIT(adb->entries_lru);
+
/*
* Initialize things here that cannot fail, and especially things
* that must be NULL for the error return to work properly.
fprintf(stderr, "dns_adb__init:%s:%s:%d:%p->references = 1\n", __func__,
__FILE__, __LINE__ + 1, adb);
#endif
- isc_refcount_init(&adb->references, 1);
dns_view_weakattach(view, &adb->view);
dns_resolver_attach(view->resolver, &adb->res);
isc_mem_attach(mem, &adb->mctx);
dns_name_format(name, namebuf, sizeof(namebuf));
}
-again:
/* Try to see if we know anything about this name at all. */
adbname = get_attached_and_locked_name(
adb, name, ADBNAME_TYPE(find->options), now);
-
- if (NAME_DEAD(adbname)) {
- UNLOCK(&adbname->lock);
- dns_adbname_detach(&adbname);
- goto again;
- }
+ INSIST(!NAME_DEAD(adbname));
/*
* Name hooks expire after the address record's TTL or 30 minutes,
*/
RWLOCK(&adb->names_lock, isc_rwlocktype_write);
- ISC_LIST_FOREACH (adb->names_lru, name, link) {
+ ISC_SIEVE_FOREACH(adb->names_lru, name, link) {
LOCK(&name->lock);
/*
* Dump the names
RWLOCK(&adb->entries_lock, isc_rwlocktype_write);
fprintf(f, ";\n; Unassociated entries\n;\n");
- ISC_LIST_FOREACH (adb->entries_lru, adbentry, link) {
+ ISC_SIEVE_FOREACH(adb->entries_lru, adbentry, link) {
LOCK(&adbentry->lock);
if (ISC_LIST_EMPTY(adbentry->nhs)) {
dump_entry(f, adb, adbentry, debug, now);
}
RWLOCK(&adb->names_lock, isc_rwlocktype_write);
- ISC_LIST_FOREACH (adb->names_lru, adbname, link) {
+ ISC_SIEVE_FOREACH(adb->names_lru, adbname, link) {
dns_adbname_ref(adbname);
LOCK(&adbname->lock);
if (dns_name_issubdomain(adbname->name, name)) {