* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id: adb.c,v 1.245 2009/01/27 23:47:54 tbox Exp $ */
+/* $Id: adb.c,v 1.246 2009/01/28 23:20:23 jinmei Exp $ */
/*! \file
*
* XXXRTH Have a per-bucket structure that contains all of these?
*/
dns_adbnamelist_t names[NBUCKETS];
+ dns_adbnamelist_t deadnames[NBUCKETS];
/*% See dns_adbnamelist_t */
isc_mutex_t namelocks[NBUCKETS];
/*% See dns_adbnamelist_t */
* XXXRTH Have a per-bucket structure that contains all of these?
*/
dns_adbentrylist_t entries[NBUCKETS];
+ dns_adbentrylist_t deadentries[NBUCKETS];
isc_mutex_t entrylocks[NBUCKETS];
isc_boolean_t entry_sd[NBUCKETS]; /*%< shutting down */
unsigned int entry_refcnt[NBUCKETS];
static inline dns_adbname_t *find_name_and_lock(dns_adb_t *, dns_name_t *,
unsigned int, int *);
static inline dns_adbentry_t *find_entry_and_lock(dns_adb_t *,
- isc_sockaddr_t *, int *);
+ isc_sockaddr_t *, int *,
+ isc_stdtime_t);
static void dump_adb(dns_adb_t *, FILE *, isc_boolean_t debug, isc_stdtime_t);
static void print_dns_name(FILE *, dns_name_t *);
static void print_namehook_list(FILE *, const char *legend,
static inline isc_boolean_t dec_entry_refcnt(dns_adb_t *, dns_adbentry_t *,
isc_boolean_t);
static inline void violate_locking_hierarchy(isc_mutex_t *, isc_mutex_t *);
-static isc_boolean_t clean_namehooks(dns_adb_t *, dns_adbnamehooklist_t *,
- isc_boolean_t);
+static isc_boolean_t clean_namehooks(dns_adb_t *, dns_adbnamehooklist_t *);
static void clean_target(dns_adb_t *, dns_name_t *);
static void clean_finds_at_name(dns_adbname_t *, isc_eventtype_t,
unsigned int);
static isc_boolean_t check_expire_namehooks(dns_adbname_t *, isc_stdtime_t);
+static isc_boolean_t check_expire_entry(dns_adb_t *, dns_adbentry_t **,
+ isc_stdtime_t);
static void cancel_fetches_at_name(dns_adbname_t *);
static isc_result_t dbfind_name(dns_adbname_t *, isc_stdtime_t,
dns_rdatatype_t);
static inline isc_boolean_t unlink_name(dns_adb_t *, dns_adbname_t *);
static inline void link_entry(dns_adb_t *, int, dns_adbentry_t *);
static inline isc_boolean_t unlink_entry(dns_adb_t *, dns_adbentry_t *);
-static isc_boolean_t kill_name(dns_adbname_t **, isc_eventtype_t,
- isc_boolean_t);
+static isc_boolean_t kill_name(dns_adbname_t **, isc_eventtype_t);
static void water(void *, int);
static void dump_entry(FILE *, dns_adbentry_t *, isc_boolean_t, isc_stdtime_t);
#define NAME_GLUEOK(n) (((n)->flags & NAME_GLUE_OK) != 0)
#define NAME_HINTOK(n) (((n)->flags & NAME_HINT_OK) != 0)
+/*
+ * Private flag(s) for entries.
+ * MUST NOT overlap FCTX_ADDRINFO_xxx and DNS_FETCHOPT_NOEDNS0.
+ */
+#define ENTRY_IS_DEAD 0x80000000
+
/*
* To the name, address classes are all that really exist. If it has a
* V6 address it doesn't care if it came from a AAAA query.
goto fail;
}
- foundentry = find_entry_and_lock(adb, &sockaddr, &addr_bucket);
+ foundentry = find_entry_and_lock(adb, &sockaddr, &addr_bucket,
+ now);
if (foundentry == NULL) {
dns_adbentry_t *entry;
* Requires the name's bucket be locked.
*/
static isc_boolean_t
-kill_name(dns_adbname_t **n, isc_eventtype_t ev, isc_boolean_t is_purge) {
+kill_name(dns_adbname_t **n, isc_eventtype_t ev) {
dns_adbname_t *name;
isc_boolean_t result = ISC_FALSE;
isc_boolean_t result4, result6;
+ int bucket;
dns_adb_t *adb;
INSIST(n != NULL);
* in that they will always empty the list.
*/
clean_finds_at_name(name, ev, DNS_ADBFIND_ADDRESSMASK);
- result4 = clean_namehooks(adb, &name->v4, is_purge);
- result6 = clean_namehooks(adb, &name->v6, is_purge);
+ result4 = clean_namehooks(adb, &name->v4);
+ result6 = clean_namehooks(adb, &name->v6);
clean_target(adb, &name->target);
result = ISC_TF(result4 || result6);
if (result)
result = dec_adb_irefcnt(adb);
} else {
- name->flags |= NAME_IS_DEAD;
cancel_fetches_at_name(name);
+ if (!NAME_DEAD(name)) {
+ bucket = name->lock_bucket;
+ ISC_LIST_UNLINK(adb->names[bucket], name, plink);
+ ISC_LIST_APPEND(adb->deadnames[bucket], name, plink);
+ name->flags |= NAME_IS_DEAD;
+ }
}
return (result);
}
if (!NAME_FETCH_V4(name) && EXPIRE_OK(name->expire_v4, now)) {
if (NAME_HAS_V4(name)) {
DP(DEF_LEVEL, "expiring v4 for name %p", name);
- result4 = clean_namehooks(adb, &name->v4, ISC_FALSE);
+ result4 = clean_namehooks(adb, &name->v4);
name->partial_result &= ~DNS_ADBFIND_INET;
}
name->expire_v4 = INT_MAX;
if (!NAME_FETCH_V6(name) && EXPIRE_OK(name->expire_v6, now)) {
if (NAME_HAS_V6(name)) {
DP(DEF_LEVEL, "expiring v6 for name %p", name);
- result6 = clean_namehooks(adb, &name->v6, ISC_FALSE);
+ result6 = clean_namehooks(adb, &name->v6);
name->partial_result &= ~DNS_ADBFIND_INET6;
}
name->expire_v6 = INT_MAX;
bucket = name->lock_bucket;
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
- ISC_LIST_UNLINK(adb->names[bucket], name, plink);
+ if (NAME_DEAD(name))
+ ISC_LIST_UNLINK(adb->deadnames[bucket], name, plink);
+ else
+ ISC_LIST_UNLINK(adb->names[bucket], name, plink);
name->lock_bucket = DNS_ADB_INVALIDBUCKET;
INSIST(adb->name_refcnt[bucket] > 0);
adb->name_refcnt[bucket]--;
*/
static inline void
link_entry(dns_adb_t *adb, int bucket, dns_adbentry_t *entry) {
+ int i;
+ dns_adbentry_t *e;
+
+ if (adb->overmem) {
+ for (i = 0; i < 2; i++) {
+ e = ISC_LIST_TAIL(adb->entries[bucket]);
+ if (e == NULL)
+ break;
+ if (e->refcnt == 0) {
+ unlink_entry(adb, e);
+ free_adbentry(adb, &e);
+ continue;
+ }
+ INSIST((e->flags & ENTRY_IS_DEAD) == 0);
+ e->flags |= ENTRY_IS_DEAD;
+ ISC_LIST_UNLINK(adb->entries[bucket], e, plink);
+ ISC_LIST_PREPEND(adb->deadentries[bucket], e, plink);
+ }
+ }
+
ISC_LIST_PREPEND(adb->entries[bucket], entry, plink);
entry->lock_bucket = bucket;
adb->entry_refcnt[bucket]++;
bucket = entry->lock_bucket;
INSIST(bucket != DNS_ADB_INVALIDBUCKET);
- ISC_LIST_UNLINK(adb->entries[bucket], entry, plink);
+ if ((entry->flags & ENTRY_IS_DEAD) != 0)
+ ISC_LIST_UNLINK(adb->deadentries[bucket], entry, plink);
+ else
+ ISC_LIST_UNLINK(adb->entries[bucket], entry, plink);
entry->lock_bucket = DNS_ADB_INVALIDBUCKET;
INSIST(adb->entry_refcnt[bucket] > 0);
adb->entry_refcnt[bucket]--;
next_name = ISC_LIST_NEXT(name, plink);
INSIST(result == ISC_FALSE);
result = kill_name(&name,
- DNS_EVENT_ADBSHUTDOWN,
- ISC_FALSE);
+ DNS_EVENT_ADBSHUTDOWN);
name = next_name;
}
}
adb->entry_sd[bucket] = ISC_TRUE;
entry = ISC_LIST_HEAD(adb->entries[bucket]);
- if (entry == NULL) {
+ if (adb->entry_refcnt[bucket] == 0) {
/*
* This bucket has no entries. We must decrement the
* irefcnt ourselves, since it will not be
* Assumes the name bucket is locked.
*/
static isc_boolean_t
-clean_namehooks(dns_adb_t *adb, dns_adbnamehooklist_t *namehooks,
- isc_boolean_t is_purge)
-{
+clean_namehooks(dns_adb_t *adb, dns_adbnamehooklist_t *namehooks) {
dns_adbentry_t *entry;
dns_adbnamehook_t *namehook;
int addr_bucket;
LOCK(&adb->entrylocks[addr_bucket]);
}
- /*
- * If we are in an overmem situation, force expiration
- * so that # of names and # of entries are well
- * balanced.
- */
- if (is_purge)
- entry->expires = 0;
result = dec_entry_refcnt(adb, entry, ISC_FALSE);
}
destroy_entry = ISC_FALSE;
if (entry->refcnt == 0 &&
- (adb->entry_sd[bucket] || entry->expires == 0)) {
+ (adb->entry_sd[bucket] || entry->expires == 0 || adb->overmem ||
+ (entry->flags & ENTRY_IS_DEAD) != 0)) {
destroy_entry = ISC_TRUE;
result = unlink_entry(adb, entry);
}
* the bucket changes.
*/
static inline dns_adbentry_t *
-find_entry_and_lock(dns_adb_t *adb, isc_sockaddr_t *addr, int *bucketp) {
- dns_adbentry_t *entry;
+find_entry_and_lock(dns_adb_t *adb, isc_sockaddr_t *addr, int *bucketp,
+ isc_stdtime_t now)
+{
+ dns_adbentry_t *entry, *entry_next;
int bucket;
bucket = isc_sockaddr_hash(addr, ISC_TRUE) % NBUCKETS;
*bucketp = bucket;
}
- entry = ISC_LIST_HEAD(adb->entries[bucket]);
- while (entry != NULL) {
- if (isc_sockaddr_equal(addr, &entry->sockaddr))
+ /* Search the list, while cleaning up expired entries. */
+ for (entry = ISC_LIST_HEAD(adb->entries[bucket]);
+ entry != NULL;
+ entry = entry_next) {
+ entry_next = ISC_LIST_NEXT(entry, plink);
+ (void)check_expire_entry(adb, &entry, now);
+ if (entry != NULL &&
+ isc_sockaddr_equal(addr, &entry->sockaddr)) {
+ ISC_LIST_UNLINK(adb->entries[bucket], entry, plink);
+ ISC_LIST_PREPEND(adb->entries[bucket], entry, plink);
return (entry);
- entry = ISC_LIST_NEXT(entry, plink);
+ }
}
return (NULL);
/*
* The name is empty. Delete it.
*/
- result = kill_name(&name, DNS_EVENT_ADBEXPIRED, ISC_FALSE);
+ result = kill_name(&name, DNS_EVENT_ADBEXPIRED);
*namep = NULL;
/*
for (victims = 0;
victim != NULL && victims < max_victims && scans < 10;
victim = next_victim) {
+ INSIST(!NAME_DEAD(victim));
scans++;
next_victim = ISC_LIST_PREV(victim, plink);
-
- /*
- * If the victim is already dead, it simply waits for some
- * final events. Ignore it.
- */
- if (NAME_DEAD(victim))
- goto next;
-
result = check_expire_name(&victim, now);
if (victim == NULL) {
victims++;
if (!NAME_FETCH(victim) &&
(overmem || victim->last_used + ADB_STALE_MARGIN <= now)) {
RUNTIME_CHECK(kill_name(&victim,
- DNS_EVENT_ADBCANCELED,
- ISC_TRUE) ==
+ DNS_EVENT_ADBCANCELED) ==
ISC_FALSE);
victims++;
}
goto fail1;
for (i = 0; i < NBUCKETS; i++) {
ISC_LIST_INIT(adb->names[i]);
+ ISC_LIST_INIT(adb->deadnames[i]);
adb->name_sd[i] = ISC_FALSE;
adb->name_refcnt[i] = 0;
adb->irefcnt++;
}
for (i = 0; i < NBUCKETS; i++) {
ISC_LIST_INIT(adb->entries[i]);
+ ISC_LIST_INIT(adb->deadentries[i]);
adb->entry_sd[i] = ISC_FALSE;
adb->entry_refcnt[i] = 0;
adb->irefcnt++;
free_adbfetch(adb, &fetch);
isc_event_free(&ev);
- want_check_exit = kill_name(&name, DNS_EVENT_ADBCANCELED,
- ISC_FALSE);
+ want_check_exit = kill_name(&name, DNS_EVENT_ADBCANCELED);
UNLOCK(&adb->namelocks[bucket]);
result = ISC_R_SUCCESS;
bucket = DNS_ADB_INVALIDBUCKET;
- entry = find_entry_and_lock(adb, sa, &bucket);
+ entry = find_entry_and_lock(adb, sa, &bucket, now);
if (adb->entry_sd[bucket]) {
result = ISC_R_SHUTTINGDOWN;
goto unlock;
if (!NAME_DEAD(adbname) &&
dns_name_equal(name, &adbname->name)) {
RUNTIME_CHECK(kill_name(&adbname,
- DNS_EVENT_ADBCANCELED,
- ISC_TRUE) ==
+ DNS_EVENT_ADBCANCELED) ==
ISC_FALSE);
}
adbname = nextname;
* PERFORMANCE OF THIS SOFTWARE.
*/
-/* $Id: rbtdb.c,v 1.272 2009/01/17 23:47:42 tbox Exp $ */
+/* $Id: rbtdb.c,v 1.273 2009/01/28 23:20:23 jinmei Exp $ */
/*! \file */
#define NODE_WEAKDOWNGRADE(l) ((void)0)
#endif
+/*%
+ * Whether to rate-limit updating the LRU to avoid possible thread contention.
+ * Our performance measurement has shown the cost is marginal, so it's defined
+ * to be 0 by default either with or without threads.
+ */
+#ifndef DNS_RBTDB_LIMITLRUUPDATE
+#define DNS_RBTDB_LIMITLRUUPDATE 0
+#endif
+
/*
* Allow clients with a virtual time of up to 5 minutes in the past to see
* records that would have otherwise have expired.
/*%
* See if a given cache entry that is being reused needs to be updated
- * in the LRU-list. For the non-threaded case this is always true unless the
- * entry has already been marked as stale; for the threaded case, updating
- * the entry every time it is referenced might be expensive because it requires
- * a node write lock. Thus this function returns true if the entry has not been
- * updated for some period of time. We differentiate the NS or glue address
- * case and the others since experiments have shown that the former tends to be
- * accessed relatively infrequently and the cost of cache miss is higher
- * (e.g., a missing NS records may cause external queries at a higher level
- * zone, involving more transactions).
+ * in the LRU-list. From the LRU management point of view, this function is
+ * expected to return true for almost all cases. When used with threads,
+ * however, this may cause a non-negligible performance penalty because a
+ * writer lock will have to be acquired before updating the list.
+ * If DNS_RBTDB_LIMITLRUUPDATE is defined to be non 0 at compilation time, this
+ * function returns true if the entry has not been updated for some period of
+ * time. We differentiate the NS or glue address case and the others since
+ * experiments have shown that the former tends to be accessed relatively
+ * infrequently and the cost of cache miss is higher (e.g., a missing NS records
+ * may cause external queries at a higher level zone, involving more
+ * transactions).
*
* Caller must hold the node (read or write) lock.
*/
(RDATASET_ATTR_NONEXISTENT|RDATASET_ATTR_STALE)) != 0)
return (ISC_FALSE);
-#ifdef ISC_PLATFORM_USETHREADS
+#if DNS_RBTDB_LIMITLRUUPDATE
if (header->type == dns_rdatatype_ns ||
(header->trust == dns_trust_glue &&
(header->type == dns_rdatatype_a ||
header != NULL && purgecount > 0;
header = header_prev) {
header_prev = ISC_LIST_PREV(header, lru_link);
+ /*
+ * Unlink the entry at this point to avoid checking it
+ * again even if it's currently used someone else and
+ * cannot be purged at this moment. This entry won't be
+ * referenced any more (so unlinking is safe) since the
+ * TTL was reset to 0.
+ */
+ ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header,
+ lru_link);
expire_header(rbtdb, header, tree_locked);
purgecount--;
}