diff --git a/bin/named/server.c b/bin/named/server.c
index 80c5b5f1bb8..67cafe633f7 100644
--- a/bin/named/server.c
+++ b/bin/named/server.c
@@ -5455,6 +5455,21 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
 	INSIST(result == ISC_R_SUCCESS);
 	dns_resolver_setmaxqueries(view->resolver, cfg_obj_asuint32(obj));
 
+	obj = NULL;
+	result = named_config_get(maps, "max-validations-per-fetch", &obj);
+	if (result == ISC_R_SUCCESS) {
+		dns_resolver_setmaxvalidations(view->resolver,
+					       cfg_obj_asuint32(obj));
+	}
+
+	obj = NULL;
+	result = named_config_get(maps, "max-validation-failures-per-fetch",
+				  &obj);
+	if (result == ISC_R_SUCCESS) {
+		dns_resolver_setmaxvalidationfails(view->resolver,
+						   cfg_obj_asuint32(obj));
+	}
+
 	obj = NULL;
 	result = named_config_get(maps, "fetches-per-zone", &obj);
 	INSIST(result == ISC_R_SUCCESS);
diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst
index 8468a785eaf..6fb5937fa22 100644
--- a/doc/arm/reference.rst
+++ b/doc/arm/reference.rst
@@ -3691,6 +3691,21 @@ system.
    set to zero, :any:`max-clients-per-query` no longer applies and there is no
    upper bound, other than that imposed by :any:`recursive-clients`.
 
+.. namedconf:statement:: max-validations-per-fetch
+   :tags: server
+   :short: Set the maximum number of DNSSEC validations that can happen in single fetch
+
+   This is an **experimental** setting to set the maximum number of DNSSEC
+   validations that can happen in a single resolver fetch.  The default is 16.
+
+.. namedconf:statement:: max-validation-failures-per-fetch
+   :tags: server
+   :short: Set the maximum number of DNSSEC validation failures that can happen in single fetch
+
+   This is an **experimental** setting to set the maximum number of DNSSEC
+   validation failures that can happen in a single resolver fetch.  The default
+   is 1.
+
 .. namedconf:statement:: fetches-per-zone
    :tags: server, query
    :short: Sets the maximum number of simultaneous iterative queries allowed to any one domain before the server blocks new queries for data in or beneath that zone.
diff --git a/doc/misc/options b/doc/misc/options
index 297822121af..1c0b47f49a4 100644
--- a/doc/misc/options
+++ b/doc/misc/options
@@ -188,6 +188,8 @@ options {
 	max-transfer-time-in <integer>;
 	max-transfer-time-out <integer>;
 	max-udp-size <integer>;
+	max-validation-failures-per-fetch <integer>; // experimental
+	max-validations-per-fetch <integer>; // experimental
 	max-zone-ttl ( unlimited | <duration> ); // deprecated
 	memstatistics <boolean>;
 	memstatistics-file <quoted_string>;
@@ -469,6 +471,8 @@ view <string> [ <class> ] {
 	max-transfer-time-in <integer>;
 	max-transfer-time-out <integer>;
 	max-udp-size <integer>;
+	max-validation-failures-per-fetch <integer>; // experimental
+	max-validations-per-fetch <integer>; // experimental
 	max-zone-ttl ( unlimited | <duration> ); // deprecated
 	message-compression <boolean>;
 	min-cache-ttl <duration>;
diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c
index af53947ac65..ce41b99e5d8 100644
--- a/lib/dns/dst_api.c
+++ b/lib/dns/dst_api.c
@@ -164,7 +164,8 @@ computeid(dst_key_t *key);
 static isc_result_t
 frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
 	   unsigned int protocol, dns_rdataclass_t rdclass,
-	   isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
+	   isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
+	   dst_key_t **keyp);
 
 static isc_result_t
 algorithm_status(unsigned int alg);
@@ -750,6 +751,13 @@ dst_key_todns(const dst_key_t *key, isc_buffer_t *target) {
 isc_result_t
 dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
 		isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+	return (dst_key_fromdns_ex(name, rdclass, source, mctx, false, keyp));
+}
+
+isc_result_t
+dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass,
+		   isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
+		   dst_key_t **keyp) {
 	uint8_t alg, proto;
 	uint32_t flags, extflags;
 	dst_key_t *key = NULL;
@@ -780,7 +788,7 @@ dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
 	}
 
 	result = frombuffer(name, alg, flags, proto, rdclass, source, mctx,
-			    &key);
+			    no_rdata, &key);
 	if (result != ISC_R_SUCCESS) {
 		return (result);
 	}
@@ -801,7 +809,7 @@ dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
 	REQUIRE(dst_initialized);
 
 	result = frombuffer(name, alg, flags, protocol, rdclass, source, mctx,
-			    &key);
+			    false, &key);
 	if (result != ISC_R_SUCCESS) {
 		return (result);
 	}
@@ -2302,7 +2310,8 @@ computeid(dst_key_t *key) {
 static isc_result_t
 frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
 	   unsigned int protocol, dns_rdataclass_t rdclass,
-	   isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) {
+	   isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
+	   dst_key_t **keyp) {
 	dst_key_t *key;
 	isc_result_t ret;
 
@@ -2324,10 +2333,12 @@ frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags,
 			return (DST_R_UNSUPPORTEDALG);
 		}
 
-		ret = key->func->fromdns(key, source);
-		if (ret != ISC_R_SUCCESS) {
-			dst_key_free(&key);
-			return (ret);
+		if (!no_rdata) {
+			ret = key->func->fromdns(key, source);
+			if (ret != ISC_R_SUCCESS) {
+				dst_key_free(&key);
+				return (ret);
+			}
 		}
 	}
 
diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h
index e9258827e47..7f0dde65a55 100644
--- a/lib/dns/include/dns/resolver.h
+++ b/lib/dns/include/dns/resolver.h
@@ -578,6 +578,14 @@ dns_resolver_printbadcache(dns_resolver_t *resolver, FILE *fp);
  * \li	resolver to be valid.
  */
 
+void
+dns_resolver_setmaxvalidations(dns_resolver_t *resolver, uint32_t max);
+void
+dns_resolver_setmaxvalidationfails(dns_resolver_t *resolver, uint32_t max);
+/*%
+ * Set maximum numbers of validations and maximum validation failures per fetch.
+ */
+
 void
 dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth);
 unsigned int
diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h
index d42cdca6376..0b0222c5c6d 100644
--- a/lib/dns/include/dns/validator.h
+++ b/lib/dns/include/dns/validator.h
@@ -53,6 +53,7 @@
 #include <isc/refcount.h>
 
 #include <dns/fixedname.h>
+#include <dns/rdata.h>
 #include <dns/rdataset.h>
 #include <dns/rdatastruct.h> /* for dns_rdata_rrsig_t */
 #include <dns/types.h>
@@ -144,6 +145,13 @@ struct dns_validator {
 	unsigned int  authcount;
 	unsigned int  authfail;
 	isc_stdtime_t start;
+
+	bool	    digest_sha1;
+	bool	    supported_algorithm;
+	dns_rdata_t rdata;
+	bool	    resume;
+	uint32_t   *nvalidations;
+	uint32_t   *nfails;
 };
 
 /*%
@@ -161,6 +169,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
 		     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
 		     dns_message_t *message, unsigned int options,
 		     isc_loop_t *loop, isc_job_cb cb, void *arg,
+		     uint32_t *nvalidations, uint32_t *nfails,
 		     dns_validator_t **validatorp);
 /*%<
  * Start a DNSSEC validation.
diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h
index c0912f3e67a..6c7a4e50db4 100644
--- a/lib/dns/include/dst/dst.h
+++ b/lib/dns/include/dst/dst.h
@@ -482,6 +482,10 @@ dst_key_tofile(const dst_key_t *key, int type, const char *directory);
  */
 
 isc_result_t
+dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass,
+		   isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata,
+		   dst_key_t **keyp);
+isc_result_t
 dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass,
 		isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp);
 /*%<
diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
index f0f48d990df..442dfabce0a 100644
--- a/lib/dns/resolver.c
+++ b/lib/dns/resolver.c
@@ -178,6 +178,16 @@
  */
 #define MINIMUM_QUERY_TIMEOUT (MAX_SINGLE_QUERY_TIMEOUT + 1000U)
 
+/*
+ * The default maximum number of validations and validation failures per-fetch
+ */
+#ifndef DEFAULT_MAX_VALIDATIONS
+#define DEFAULT_MAX_VALIDATIONS 16
+#endif
+#ifndef DEFAULT_MAX_VALIDATION_FAILURES
+#define DEFAULT_MAX_VALIDATION_FAILURES 1
+#endif
+
 /* The default time in seconds for the whole query to live. */
 #ifndef DEFAULT_QUERY_TIMEOUT
 #define DEFAULT_QUERY_TIMEOUT MINIMUM_QUERY_TIMEOUT
@@ -457,6 +467,9 @@ struct fetchctx {
 	dns_adbaddrinfo_t *addrinfo;
 	unsigned int depth;
 	char clientstr[ISC_SOCKADDR_FORMATSIZE];
+
+	uint32_t nvalidations;
+	uint32_t nfails;
 };
 
 #define FCTX_MAGIC	 ISC_MAGIC('F', '!', '!', '!')
@@ -567,6 +580,9 @@ struct dns_resolver {
 	atomic_bool exiting;
 	atomic_bool priming;
 
+	atomic_uint_fast32_t maxvalidations;
+	atomic_uint_fast32_t maxvalidationfails;
+
 	/* Locked by lock. */
 	unsigned int spillat; /* clients-per-query */
 
@@ -961,7 +977,8 @@ valcreate(fetchctx_t *fctx, dns_message_t *message, dns_adbaddrinfo_t *addrinfo,
 
 	result = dns_validator_create(
 		fctx->res->view, name, type, rdataset, sigrdataset, message,
-		valoptions, fctx->loop, validated, valarg, &validator);
+		valoptions, fctx->loop, validated, valarg, &fctx->nvalidations,
+		&fctx->nfails, &validator);
 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
 	inc_stats(fctx->res, dns_resstatscounter_val);
 	if ((valoptions & DNS_VALIDATOR_DEFER) == 0) {
@@ -4518,6 +4535,8 @@ fctx_create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
 		.fwdpolicy = dns_fwdpolicy_none,
 		.result = ISC_R_FAILURE,
 		.loop = loop,
+		.nvalidations = atomic_load_relaxed(&res->maxvalidations),
+		.nfails = atomic_load_relaxed(&res->maxvalidationfails),
 	};
 
 	isc_mem_attach(mctx, &fctx->mctx);
@@ -9960,6 +9979,8 @@ dns_resolver_create(dns_view_t *view, isc_loopmgr_t *loopmgr, isc_nm_t *nm,
 		.maxqueries = DEFAULT_MAX_QUERIES,
 		.alternates = ISC_LIST_INITIALIZER,
 		.nloops = isc_loopmgr_nloops(loopmgr),
+		.maxvalidations = DEFAULT_MAX_VALIDATIONS,
+		.maxvalidationfails = DEFAULT_MAX_VALIDATION_FAILURES,
 	};
 
 	RTRACE("create");
@@ -10925,6 +10946,18 @@ dns_resolver_settimeout(dns_resolver_t *resolver, unsigned int timeout) {
 	resolver->query_timeout = timeout;
 }
 
+void
+dns_resolver_setmaxvalidations(dns_resolver_t *resolver, uint32_t max) {
+	REQUIRE(VALID_RESOLVER(resolver));
+	atomic_store(&resolver->maxvalidations, max);
+}
+
+void
+dns_resolver_setmaxvalidationfails(dns_resolver_t *resolver, uint32_t max) {
+	REQUIRE(VALID_RESOLVER(resolver));
+	atomic_store(&resolver->maxvalidationfails, max);
+}
+
 void
 dns_resolver_setmaxdepth(dns_resolver_t *resolver, unsigned int maxdepth) {
 	REQUIRE(VALID_RESOLVER(resolver));
diff --git a/lib/dns/validator.c b/lib/dns/validator.c
index b679392c49e..9afd2ea11fc 100644
--- a/lib/dns/validator.c
+++ b/lib/dns/validator.c
@@ -24,6 +24,7 @@
 #include <isc/string.h>
 #include <isc/tid.h>
 #include <isc/util.h>
+#include <isc/work.h>
 
 #include <dns/client.h>
 #include <dns/db.h>
@@ -55,7 +56,7 @@
  *     validator_start -> proveunsecure
  *
  * \li When called with no rdataset or sigrdataset:
- *     validator_start -> validate_nx-> proveunsecure
+ *     validator_start -> validate_nx -> proveunsecure
  *
  * validator_start:   determine what type of validation to do.
  * validate_answer:   attempt to perform a positive validation.
@@ -66,29 +67,32 @@
 #define VALIDATOR_MAGIC	   ISC_MAGIC('V', 'a', 'l', '?')
 #define VALID_VALIDATOR(v) ISC_MAGIC_VALID(v, VALIDATOR_MAGIC)
 
-#define VALATTR_CANCELED 0x0002 /*%< Canceled. */
-#define VALATTR_TRIEDVERIFY                                    \
-	0x0004			  /*%< We have found a key and \
-				   * have attempted a verify. */
-#define VALATTR_COMPLETE   0x0008 /*%< Completion event sent. */
-#define VALATTR_INSECURITY 0x0010 /*%< Attempting proveunsecure. */
-
-/*!
- * NSEC proofs to be looked for.
- */
-#define VALATTR_NEEDNOQNAME    0x00000100
-#define VALATTR_NEEDNOWILDCARD 0x00000200
-#define VALATTR_NEEDNODATA     0x00000400
+enum valattr {
+	VALATTR_CANCELED = 1 << 1,	     /*%< Canceled. */
+	VALATTR_TRIEDVERIFY = 1 << 2,	     /*%< We have found a key and have
+						attempted a verify. */
+	VALATTR_COMPLETE = 1 << 3,	     /*%< Completion event sent. */
+	VALATTR_INSECURITY = 1 << 4,	     /*%< Attempting proveunsecure. */
+	VALATTR_MAXVALIDATIONS = 1 << 5,     /*%< Max validations quota */
+	VALATTR_MAXVALIDATIONFAILS = 1 << 6, /*%< Max validation fails quota */
+
+	/*!
+	 * NSEC proofs to be looked for.
+	 */
+	VALATTR_NEEDNOQNAME = 1 << 8,
+	VALATTR_NEEDNOWILDCARD = 1 << 9,
+	VALATTR_NEEDNODATA = 1 << 10,
 
-/*!
- * NSEC proofs that have been found.
- */
-#define VALATTR_FOUNDNOQNAME	0x00001000
-#define VALATTR_FOUNDNOWILDCARD 0x00002000
-#define VALATTR_FOUNDNODATA	0x00004000
-#define VALATTR_FOUNDCLOSEST	0x00008000
-#define VALATTR_FOUNDOPTOUT	0x00010000
-#define VALATTR_FOUNDUNKNOWN	0x00020000
+	/*!
+	 * NSEC proofs that have been found.
+	 */
+	VALATTR_FOUNDNOQNAME = 1 << 12,
+	VALATTR_FOUNDNOWILDCARD = 1 << 13,
+	VALATTR_FOUNDNODATA = 1 << 14,
+	VALATTR_FOUNDCLOSEST = 1 << 15,
+	VALATTR_FOUNDOPTOUT = 1 << 16,
+	VALATTR_FOUNDUNKNOWN = 1 << 17,
+};
 
 #define NEEDNODATA(val)	     ((val->attributes & VALATTR_NEEDNODATA) != 0)
 #define NEEDNOQNAME(val)     ((val->attributes & VALATTR_NEEDNOQNAME) != 0)
@@ -105,17 +109,27 @@
 #define NEGATIVE(r) (((r)->attributes & DNS_RDATASETATTR_NEGATIVE) != 0)
 #define NXDOMAIN(r) (((r)->attributes & DNS_RDATASETATTR_NXDOMAIN) != 0)
 
+#define MAXVALIDATIONS(r) (((r)->attributes & VALATTR_MAXVALIDATIONS) != 0)
+#define MAXVALIDATIONFAILS(r) \
+	(((r)->attributes & VALATTR_MAXVALIDATIONFAILS) != 0)
+
 static void
 destroy_validator(dns_validator_t *val);
 
 static isc_result_t
 select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset);
 
+static void
+resume_answer(void *arg);
+static void
+validate_async_done(dns_validator_t *val, isc_result_t result);
 static isc_result_t
-validate_answer(dns_validator_t *val, bool resume);
+validate_async_run(dns_validator_t *val, isc_job_cb cb);
 
-static isc_result_t
-validate_dnskey(dns_validator_t *val);
+static void
+validate_dnskey(void *arg);
+static void
+validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result);
 
 static isc_result_t
 validate_nx(dns_validator_t *val, bool resume);
@@ -204,18 +218,9 @@ marksecure(dns_validator_t *val) {
 	val->secure = true;
 }
 
-static void
-validator_done_cb(void *arg) {
-	dns_validator_t *val = arg;
-	val->cb(val);
-	dns_validator_detach(&val);
-}
-
 /*
  * Validator 'val' is finished; send the completion event to the loop
  * that called dns_validator_create(), with result `result`.
- *
- * Caller must be holding the validator lock.
  */
 static void
 validator_done(dns_validator_t *val, isc_result_t result) {
@@ -226,8 +231,7 @@ validator_done(dns_validator_t *val, isc_result_t result) {
 	val->attributes |= VALATTR_COMPLETE;
 	val->result = result;
 
-	dns_validator_ref(val);
-	isc_async_run(val->loop, validator_done_cb, val);
+	isc_async_run(val->loop, val->cb, val);
 }
 
 /*%
@@ -349,6 +353,24 @@ isdelegation(dns_name_t *name, dns_rdataset_t *rdataset,
 	return (found);
 }
 
+static void
+resume_answer_with_key(void *arg) {
+	dns_validator_t *val = arg;
+	dns_rdataset_t *rdataset = &val->frdataset;
+
+	isc_result_t result = select_signing_key(val, rdataset);
+	if (result == ISC_R_SUCCESS) {
+		val->keyset = &val->frdataset;
+	}
+}
+
+static void
+resume_answer_with_key_done(void *arg) {
+	dns_validator_t *val = arg;
+
+	resume_answer(val);
+}
+
 /*%
  * We have been asked to look for a key.
  * If found, resume the validation process.
@@ -361,8 +383,6 @@ fetch_callback_dnskey(void *arg) {
 	dns_rdataset_t *rdataset = &val->frdataset;
 	isc_result_t eresult = resp->result;
 	isc_result_t result;
-	isc_result_t saved_result;
-	dns_fetch_t *fetch = NULL;
 
 	INSIST(resp->type == FETCHDONE);
 
@@ -376,14 +396,18 @@ fetch_callback_dnskey(void *arg) {
 	if (dns_rdataset_isassociated(&val->fsigrdataset)) {
 		dns_rdataset_disassociate(&val->fsigrdataset);
 	}
-	isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp));
 
 	validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_dnskey");
-	fetch = val->fetch;
-	val->fetch = NULL;
+	dns_resolver_destroyfetch(&val->fetch);
+
 	if (CANCELED(val)) {
-		validator_done(val, ISC_R_CANCELED);
-	} else if (eresult == ISC_R_SUCCESS || eresult == DNS_R_NCACHENXRRSET) {
+		result = ISC_R_CANCELED;
+		goto cleanup;
+	}
+
+	switch (eresult) {
+	case ISC_R_SUCCESS:
+	case DNS_R_NCACHENXRRSET:
 		/*
 		 * We have an answer to our DNSKEY query.  Either the DNSKEY
 		 * RRset or a NODATA response.
@@ -398,41 +422,23 @@ fetch_callback_dnskey(void *arg) {
 		if (eresult == ISC_R_SUCCESS &&
 		    rdataset->trust >= dns_trust_secure)
 		{
-			result = select_signing_key(val, rdataset);
-			if (result == ISC_R_SUCCESS) {
-				val->keyset = &val->frdataset;
-			}
-		}
-		result = validate_answer(val, true);
-		if (result == DNS_R_NOVALIDSIG &&
-		    (val->attributes & VALATTR_TRIEDVERIFY) == 0)
-		{
-			saved_result = result;
-			validator_log(val, ISC_LOG_DEBUG(3),
-				      "falling back to insecurity proof");
-			result = proveunsecure(val, false, false);
-			if (result == DNS_R_NOTINSECURE) {
-				result = saved_result;
-			}
-		}
-		if (result != DNS_R_WAIT) {
-			validator_done(val, result);
+			isc_work_enqueue(val->loop, resume_answer_with_key,
+					 resume_answer, val);
+			result = DNS_R_WAIT;
+		} else {
+			result = validate_async_run(val, resume_answer);
 		}
-	} else {
+		break;
+	default:
 		validator_log(val, ISC_LOG_DEBUG(3),
 			      "fetch_callback_dnskey: got %s",
 			      isc_result_totext(eresult));
-		if (eresult == ISC_R_CANCELED) {
-			validator_done(val, eresult);
-		} else {
-			validator_done(val, DNS_R_BROKENCHAIN);
-		}
-	}
-
-	if (fetch != NULL) {
-		dns_resolver_destroyfetch(&fetch);
+		result = DNS_R_BROKENCHAIN;
 	}
 
+cleanup:
+	isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp));
+	validate_async_done(val, result);
 	dns_validator_detach(&val);
 }
 
@@ -447,7 +453,6 @@ fetch_callback_ds(void *arg) {
 	dns_rdataset_t *rdataset = &val->frdataset;
 	isc_result_t eresult = resp->result;
 	isc_result_t result;
-	dns_fetch_t *fetch = NULL;
 	bool trustchain;
 
 	INSIST(resp->type == FETCHDONE);
@@ -470,28 +475,16 @@ fetch_callback_ds(void *arg) {
 	}
 
 	validator_log(val, ISC_LOG_DEBUG(3), "in fetch_callback_ds");
-	fetch = val->fetch;
-	val->fetch = NULL;
+	dns_resolver_destroyfetch(&val->fetch);
 
 	if (CANCELED(val)) {
-		validator_done(val, ISC_R_CANCELED);
-		goto done;
+		result = ISC_R_CANCELED;
+		goto cleanup;
 	}
 
-	switch (eresult) {
-	case DNS_R_NXDOMAIN:
-	case DNS_R_NCACHENXDOMAIN:
-		/*
-		 * These results only make sense if we're attempting
-		 * an insecurity proof, not when walking a chain of trust.
-		 */
-		if (trustchain) {
-			goto unexpected;
-		}
-
-		FALLTHROUGH;
-	case ISC_R_SUCCESS:
-		if (trustchain) {
+	if (trustchain) {
+		switch (eresult) {
+		case ISC_R_SUCCESS:
 			/*
 			 * We looked for a DS record as part of
 			 * following a key chain upwards; resume following
@@ -501,29 +494,12 @@ fetch_callback_ds(void *arg) {
 				      "dsset with trust %s",
 				      dns_trust_totext(rdataset->trust));
 			val->dsset = &val->frdataset;
-			result = validate_dnskey(val);
-			if (result != DNS_R_WAIT) {
-				validator_done(val, result);
-			}
-		} else {
-			/*
-			 * There is a DS which may or may not be a zone cut.
-			 * In either case we are still in a secure zone,
-			 * so keep looking for the break in the chain
-			 * of trust.
-			 */
-			result = proveunsecure(val, (eresult == ISC_R_SUCCESS),
-					       true);
-			if (result != DNS_R_WAIT) {
-				validator_done(val, result);
-			}
-		}
-		break;
-	case DNS_R_CNAME:
-	case DNS_R_NXRRSET:
-	case DNS_R_NCACHENXRRSET:
-	case DNS_R_SERVFAIL: /* RFC 1034 parent? */
-		if (trustchain) {
+			result = validate_async_run(val, validate_dnskey);
+			break;
+		case DNS_R_CNAME:
+		case DNS_R_NXRRSET:
+		case DNS_R_NCACHENXRRSET:
+		case DNS_R_SERVFAIL: /* RFC 1034 parent? */
 			/*
 			 * Failed to find a DS while following the
 			 * chain of trust; now we need to prove insecurity.
@@ -532,54 +508,69 @@ fetch_callback_ds(void *arg) {
 				      "falling back to insecurity proof (%s)",
 				      isc_result_totext(eresult));
 			result = proveunsecure(val, false, false);
-			if (result != DNS_R_WAIT) {
-				validator_done(val, result);
-			}
-		} else if (eresult == DNS_R_SERVFAIL) {
-			goto unexpected;
-		} else if (eresult != DNS_R_CNAME &&
-			   isdelegation(resp->foundname, &val->frdataset,
-					eresult))
-		{
+			break;
+		default:
+			validator_log(val, ISC_LOG_DEBUG(3),
+				      "fetch_callback_ds: got %s",
+				      isc_result_totext(eresult));
+			result = DNS_R_BROKENCHAIN;
+			break;
+		}
+	} else {
+		switch (eresult) {
+		case DNS_R_NXDOMAIN:
+		case DNS_R_NCACHENXDOMAIN:
 			/*
-			 * Failed to find a DS while trying to prove
-			 * insecurity. If this is a zone cut, that
-			 * means we're insecure.
+			 * These results only make sense if we're attempting
+			 * an insecurity proof, not when walking a chain of
+			 * trust.
 			 */
-			result = markanswer(val, "fetch_callback_ds",
-					    "no DS and this is a delegation");
-			validator_done(val, result);
-		} else {
+
+			result = proveunsecure(val, false, true);
+			break;
+		case ISC_R_SUCCESS:
+			/*
+			 * There is a DS which may or may not be a zone cut.
+			 * In either case we are still in a secure zone,
+			 * so keep looking for the break in the chain
+			 * of trust.
+			 */
+			result = proveunsecure(val, true, true);
+			break;
+		case DNS_R_NXRRSET:
+		case DNS_R_NCACHENXRRSET:
+			if (isdelegation(resp->foundname, &val->frdataset,
+					 eresult))
+			{
+				/*
+				 * Failed to find a DS while trying to prove
+				 * insecurity. If this is a zone cut, that
+				 * means we're insecure.
+				 */
+				result = markanswer(
+					val, "fetch_callback_ds",
+					"no DS and this is a delegation");
+				break;
+			}
+			FALLTHROUGH;
+		case DNS_R_CNAME:
 			/*
 			 * Not a zone cut, so we have to keep looking for
 			 * the break point in the chain of trust.
 			 */
 			result = proveunsecure(val, false, true);
-			if (result != DNS_R_WAIT) {
-				validator_done(val, result);
-			}
-		}
-		break;
-
-	default:
-	unexpected:
-		validator_log(val, ISC_LOG_DEBUG(3),
-			      "fetch_callback_ds: got %s",
-			      isc_result_totext(eresult));
-		if (eresult == ISC_R_CANCELED) {
-			validator_done(val, eresult);
-		} else {
-			validator_done(val, DNS_R_BROKENCHAIN);
+			break;
+		default:
+			validator_log(val, ISC_LOG_DEBUG(3),
+				      "fetch_callback_ds: got %s",
+				      isc_result_totext(eresult));
+			result = DNS_R_BROKENCHAIN;
 		}
 	}
-done:
 
+cleanup:
 	isc_mem_putanddetach(&resp->mctx, resp, sizeof(*resp));
-
-	if (fetch != NULL) {
-		dns_resolver_destroyfetch(&fetch);
-	}
-
+	validate_async_done(val, result);
 	dns_validator_detach(&val);
 }
 
@@ -592,52 +583,43 @@ static void
 validator_callback_dnskey(void *arg) {
 	dns_validator_t *subvalidator = (dns_validator_t *)arg;
 	dns_validator_t *val = subvalidator->parent;
-	isc_result_t result;
-	isc_result_t eresult = subvalidator->result;
-	isc_result_t saved_result;
+	isc_result_t result = subvalidator->result;
 
 	val->subvalidator = NULL;
-	subvalidator->parent = NULL;
 
-	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey");
 	if (CANCELED(val)) {
-		validator_done(val, ISC_R_CANCELED);
-	} else if (eresult == ISC_R_SUCCESS) {
+		result = ISC_R_CANCELED;
+		goto cleanup;
+	}
+
+	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_dnskey");
+	if (result == ISC_R_SUCCESS) {
 		validator_log(val, ISC_LOG_DEBUG(3), "keyset with trust %s",
 			      dns_trust_totext(val->frdataset.trust));
 		/*
 		 * Only extract the dst key if the keyset is secure.
 		 */
 		if (val->frdataset.trust >= dns_trust_secure) {
-			(void)select_signing_key(val, &val->frdataset);
-		}
-		result = validate_answer(val, true);
-		if (result == DNS_R_NOVALIDSIG &&
-		    (val->attributes & VALATTR_TRIEDVERIFY) == 0)
-		{
-			saved_result = result;
-			validator_log(val, ISC_LOG_DEBUG(3),
-				      "falling back to insecurity proof");
-			result = proveunsecure(val, false, false);
-			if (result == DNS_R_NOTINSECURE) {
-				result = saved_result;
-			}
-		}
-		if (result != DNS_R_WAIT) {
-			validator_done(val, result);
+			isc_work_enqueue(val->loop, resume_answer_with_key,
+					 resume_answer_with_key_done, val);
+			result = DNS_R_WAIT;
+		} else {
+			result = validate_async_run(val, resume_answer);
 		}
 	} else {
-		if (eresult != DNS_R_BROKENCHAIN) {
+		if (result != DNS_R_BROKENCHAIN) {
 			expire_rdatasets(val);
 		}
 		validator_log(val, ISC_LOG_DEBUG(3),
 			      "validator_callback_dnskey: got %s",
-			      isc_result_totext(eresult));
-		validator_done(val, DNS_R_BROKENCHAIN);
+			      isc_result_totext(result));
+		result = DNS_R_BROKENCHAIN;
 	}
 
+cleanup:
+	dns_validator_detach(&subvalidator->parent);
 	dns_validator_destroy(&subvalidator);
-	dns_validator_detach(&val);
+	validate_async_done(val, result);
 }
 
 /*%
@@ -653,12 +635,14 @@ validator_callback_ds(void *arg) {
 	isc_result_t eresult = subvalidator->result;
 
 	val->subvalidator = NULL;
-	subvalidator->parent = NULL;
 
-	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds");
 	if (CANCELED(val)) {
-		validator_done(val, ISC_R_CANCELED);
-	} else if (eresult == ISC_R_SUCCESS) {
+		result = ISC_R_CANCELED;
+		goto cleanup;
+	}
+
+	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_ds");
+	if (eresult == ISC_R_SUCCESS) {
 		bool have_dsset;
 		dns_name_t *name;
 		validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s",
@@ -678,10 +662,7 @@ validator_callback_ds(void *arg) {
 		} else if ((val->attributes & VALATTR_INSECURITY) != 0) {
 			result = proveunsecure(val, have_dsset, true);
 		} else {
-			result = validate_dnskey(val);
-		}
-		if (result != DNS_R_WAIT) {
-			validator_done(val, result);
+			result = validate_async_run(val, validate_dnskey);
 		}
 	} else {
 		if (eresult != DNS_R_BROKENCHAIN) {
@@ -690,11 +671,13 @@ validator_callback_ds(void *arg) {
 		validator_log(val, ISC_LOG_DEBUG(3),
 			      "validator_callback_ds: got %s",
 			      isc_result_totext(eresult));
-		validator_done(val, DNS_R_BROKENCHAIN);
+		result = DNS_R_BROKENCHAIN;
 	}
 
+cleanup:
+	dns_validator_detach(&subvalidator->parent);
 	dns_validator_destroy(&subvalidator);
-	dns_validator_detach(&val);
+	validate_async_done(val, result);
 }
 
 /*%
@@ -713,16 +696,16 @@ validator_callback_cname(void *arg) {
 
 	val->subvalidator = NULL;
 
-	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname");
 	if (CANCELED(val)) {
-		validator_done(val, ISC_R_CANCELED);
-	} else if (eresult == ISC_R_SUCCESS) {
+		result = ISC_R_CANCELED;
+		goto cleanup;
+	}
+
+	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_cname");
+	if (eresult == ISC_R_SUCCESS) {
 		validator_log(val, ISC_LOG_DEBUG(3), "cname with trust %s",
 			      dns_trust_totext(val->frdataset.trust));
 		result = proveunsecure(val, false, true);
-		if (result != DNS_R_WAIT) {
-			validator_done(val, result);
-		}
 	} else {
 		if (eresult != DNS_R_BROKENCHAIN) {
 			expire_rdatasets(val);
@@ -730,11 +713,13 @@ validator_callback_cname(void *arg) {
 		validator_log(val, ISC_LOG_DEBUG(3),
 			      "validator_callback_cname: got %s",
 			      isc_result_totext(eresult));
-		validator_done(val, DNS_R_BROKENCHAIN);
+		result = DNS_R_BROKENCHAIN;
 	}
 
+cleanup:
+	dns_validator_detach(&subvalidator->parent);
 	dns_validator_destroy(&subvalidator);
-	dns_validator_detach(&val);
+	validate_async_done(val, result);
 }
 
 /*%
@@ -749,30 +734,19 @@ validator_callback_nsec(void *arg) {
 	dns_validator_t *subvalidator = (dns_validator_t *)arg;
 	dns_validator_t *val = subvalidator->parent;
 	dns_rdataset_t *rdataset = subvalidator->rdataset;
-	isc_result_t result = subvalidator->result;
+	isc_result_t result;
+	isc_result_t eresult = subvalidator->result;
 	bool exists, data;
 
 	val->subvalidator = NULL;
 
-	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec");
 	if (CANCELED(val)) {
-		validator_done(val, ISC_R_CANCELED);
-	} else if (result != ISC_R_SUCCESS) {
-		validator_log(val, ISC_LOG_DEBUG(3),
-			      "validator_callback_nsec: got %s",
-			      isc_result_totext(result));
-		if (result == DNS_R_BROKENCHAIN) {
-			val->authfail++;
-		}
-		if (result == ISC_R_CANCELED) {
-			validator_done(val, result);
-		} else {
-			result = validate_nx(val, true);
-			if (result != DNS_R_WAIT) {
-				validator_done(val, result);
-			}
-		}
-	} else {
+		result = ISC_R_CANCELED;
+		goto cleanup;
+	}
+
+	validator_log(val, ISC_LOG_DEBUG(3), "in validator_callback_nsec");
+	if (eresult == ISC_R_SUCCESS) {
 		dns_name_t **proofs = val->proofs;
 		dns_name_t *wild = dns_fixedname_name(&val->wild);
 
@@ -824,13 +798,27 @@ validator_callback_nsec(void *arg) {
 		}
 
 		result = validate_nx(val, true);
-		if (result != DNS_R_WAIT) {
-			validator_done(val, result);
+	} else {
+		validator_log(val, ISC_LOG_DEBUG(3),
+			      "validator_callback_nsec: got %s",
+			      isc_result_totext(eresult));
+		switch (eresult) {
+		case ISC_R_CANCELED:
+		case ISC_R_SHUTTINGDOWN:
+			result = eresult;
+			break;
+		case DNS_R_BROKENCHAIN:
+			val->authfail++;
+			FALLTHROUGH;
+		default:
+			result = validate_nx(val, true);
 		}
 	}
 
+cleanup:
+	dns_validator_detach(&subvalidator->parent);
 	dns_validator_destroy(&subvalidator);
-	dns_validator_detach(&val);
+	validate_async_done(val, result);
 }
 
 /*%
@@ -949,7 +937,6 @@ create_fetch(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
 	validator_logcreate(val, name, type, caller, "fetch");
 
 	dns_validator_ref(val);
-
 	result = dns_resolver_createfetch(
 		val->view->resolver, name, type, NULL, NULL, NULL, NULL, 0,
 		fopts, 0, NULL, val->loop, callback, val, &val->frdataset,
@@ -987,9 +974,9 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type,
 		  (DNS_VALIDATOR_NOCDFLAG | DNS_VALIDATOR_NONTA));
 
 	validator_logcreate(val, name, type, caller, "validator");
-	result = dns_validator_create(val->view, name, type, rdataset, sig,
-				      NULL, vopts, val->loop, cb, val,
-				      &val->subvalidator);
+	result = dns_validator_create(
+		val->view, name, type, rdataset, sig, NULL, vopts, val->loop,
+		cb, val, val->nvalidations, val->nfails, &val->subvalidator);
 	if (result == ISC_R_SUCCESS) {
 		dns_validator_attach(val, &val->subvalidator->parent);
 		val->subvalidator->depth = val->depth + 1;
@@ -1016,59 +1003,59 @@ select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) {
 	isc_buffer_t b;
 	dns_rdata_t rdata = DNS_RDATA_INIT;
 	dst_key_t *oldkey = val->key;
-	bool foundold;
+	bool no_rdata = false;
 
 	if (oldkey == NULL) {
-		foundold = true;
+		result = dns_rdataset_first(rdataset);
 	} else {
-		foundold = false;
+		dst_key_free(&oldkey);
 		val->key = NULL;
+		result = dns_rdataset_next(rdataset);
 	}
-
-	result = dns_rdataset_first(rdataset);
 	if (result != ISC_R_SUCCESS) {
-		goto failure;
+		goto done;
 	}
+
 	do {
 		dns_rdataset_current(rdataset, &rdata);
 
 		isc_buffer_init(&b, rdata.data, rdata.length);
 		isc_buffer_add(&b, rdata.length);
 		INSIST(val->key == NULL);
-		result = dst_key_fromdns(&siginfo->signer, rdata.rdclass, &b,
-					 val->view->mctx, &val->key);
+		result = dst_key_fromdns_ex(&siginfo->signer, rdata.rdclass, &b,
+					    val->view->mctx, no_rdata,
+					    &val->key);
 		if (result == ISC_R_SUCCESS) {
 			if (siginfo->algorithm ==
 				    (dns_secalg_t)dst_key_alg(val->key) &&
 			    siginfo->keyid ==
 				    (dns_keytag_t)dst_key_id(val->key) &&
+			    (dst_key_flags(val->key) & DNS_KEYFLAG_REVOKE) ==
+				    0 &&
 			    dst_key_iszonekey(val->key))
 			{
-				if (foundold) {
-					/*
-					 * This is the key we're looking for.
-					 */
-					return (ISC_R_SUCCESS);
-				} else if (dst_key_compare(oldkey, val->key)) {
-					foundold = true;
-					dst_key_free(&oldkey);
+				if (no_rdata) {
+					/* Retry with full key */
+					dns_rdata_reset(&rdata);
+					dst_key_free(&val->key);
+					no_rdata = false;
+					continue;
 				}
+				/* This is the key we're looking for. */
+				goto done;
 			}
 			dst_key_free(&val->key);
 		}
 		dns_rdata_reset(&rdata);
 		result = dns_rdataset_next(rdataset);
+		no_rdata = true;
 	} while (result == ISC_R_SUCCESS);
 
+done:
 	if (result == ISC_R_NOMORE) {
 		result = ISC_R_NOTFOUND;
 	}
 
-failure:
-	if (oldkey != NULL) {
-		dst_key_free(&oldkey);
-	}
-
 	return (result);
 }
 
@@ -1181,15 +1168,22 @@ seek_dnskey(dns_validator_t *val) {
 			validator_log(val, ISC_LOG_DEBUG(3),
 				      "keyset with trust %s",
 				      dns_trust_totext(val->frdataset.trust));
-			result = select_signing_key(val, val->keyset);
-			if (result != ISC_R_SUCCESS) {
-				/*
-				 * Either the key we're looking for is not
-				 * in the rrset, or something bad happened.
-				 * Give up.
-				 */
-				result = DNS_R_CONTINUE;
+
+			/*
+			 * Cleanup before passing control to the offload thread
+			 */
+			if (dns_rdataset_isassociated(&val->frdataset) &&
+			    val->keyset != &val->frdataset)
+			{
+				dns_rdataset_disassociate(&val->frdataset);
+			}
+			if (dns_rdataset_isassociated(&val->fsigrdataset)) {
+				dns_rdataset_disassociate(&val->fsigrdataset);
 			}
+
+			isc_work_enqueue(val->loop, resume_answer_with_key,
+					 resume_answer_with_key_done, val);
+			return (DNS_R_WAIT);
 		}
 		break;
 
@@ -1246,20 +1240,47 @@ compute_keytag(dns_rdata_t *rdata) {
 	return (dst_region_computeid(&r));
 }
 
+static bool
+over_max_validations(dns_validator_t *val) {
+	if (val->nvalidations == NULL) {
+		return (false);
+	}
+	if (*val->nvalidations > 0) {
+		(*val->nvalidations)--;
+		return (false);
+	}
+
+	val->attributes |= VALATTR_MAXVALIDATIONS;
+	return (true);
+}
+
+static bool
+over_max_fails(dns_validator_t *val) {
+	if (val->nfails == NULL) {
+		return (false);
+	}
+	if (*val->nfails > 0) {
+		(*val->nfails)--;
+		return (false);
+	}
+
+	val->attributes |= VALATTR_MAXVALIDATIONFAILS;
+	return (true);
+}
+
 /*%
  * Is the DNSKEY rrset in val->rdataset self-signed?
  */
-static bool
+static isc_result_t
 selfsigned_dnskey(dns_validator_t *val) {
 	dns_rdataset_t *rdataset = val->rdataset;
 	dns_rdataset_t *sigrdataset = val->sigrdataset;
 	dns_name_t *name = val->name;
 	isc_result_t result;
 	isc_mem_t *mctx = val->view->mctx;
-	bool answer = false;
 
 	if (rdataset->type != dns_rdatatype_dnskey) {
-		return (false);
+		return (DNS_R_NOKEYMATCH);
 	}
 
 	for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
@@ -1301,8 +1322,7 @@ selfsigned_dnskey(dns_validator_t *val) {
 			 * This will be verified later.
 			 */
 			if ((key.flags & DNS_KEYFLAG_REVOKE) == 0) {
-				answer = true;
-				continue;
+				return (ISC_R_SUCCESS);
 			}
 
 			result = dns_dnssec_keyfromrdata(name, &keyrdata, mctx,
@@ -1318,6 +1338,10 @@ selfsigned_dnskey(dns_validator_t *val) {
 			if (DNS_TRUST_PENDING(rdataset->trust) &&
 			    dns_view_istrusted(val->view, name, &key))
 			{
+				if (over_max_validations(val)) {
+					dst_key_free(&dstkey);
+					return (ISC_R_QUOTA);
+				}
 				result = dns_dnssec_verify(
 					name, rdataset, dstkey, true,
 					val->view->maxbits, mctx, &sigrdata,
@@ -1329,6 +1353,9 @@ selfsigned_dnskey(dns_validator_t *val) {
 					 * good.
 					 */
 					dns_view_untrust(val->view, name, &key);
+				} else if (over_max_fails(val)) {
+					dst_key_free(&dstkey);
+					return (ISC_R_QUOTA);
 				}
 			} else if (rdataset->trust >= dns_trust_secure) {
 				/*
@@ -1342,7 +1369,7 @@ selfsigned_dnskey(dns_validator_t *val) {
 		}
 	}
 
-	return (answer);
+	return (DNS_R_NOKEYMATCH);
 }
 
 /*%
@@ -1365,6 +1392,9 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata,
 	val->attributes |= VALATTR_TRIEDVERIFY;
 	wild = dns_fixedname_initname(&fixed);
 again:
+	if (over_max_validations(val)) {
+		return (ISC_R_QUOTA);
+	}
 	result = dns_dnssec_verify(val->name, val->rdataset, key, ignore,
 				   val->view->maxbits, val->view->mctx, rdata,
 				   wild);
@@ -1408,6 +1438,10 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata,
 		}
 		result = ISC_R_SUCCESS;
 	}
+
+	if (result != ISC_R_SUCCESS && over_max_fails(val)) {
+		result = ISC_R_QUOTA;
+	}
 	return (result);
 }
 
@@ -1420,132 +1454,323 @@ verify(dns_validator_t *val, dst_key_t *key, dns_rdata_t *rdata,
  *			for an event.
  * \li	Other return codes are possible and all indicate failure.
  */
-static isc_result_t
-validate_answer(dns_validator_t *val, bool resume) {
-	isc_result_t result, vresult = DNS_R_NOVALIDSIG;
-	dns_rdata_t rdata = DNS_RDATA_INIT;
+
+static void
+validate_answer_iter_next(void *arg);
+static void
+validate_answer_process(void *arg);
+static void
+validate_answer_iter_done(dns_validator_t *val, isc_result_t result);
+
+static void
+validate_answer_iter_start(dns_validator_t *val) {
+	isc_result_t result = ISC_R_SUCCESS;
 
 	/*
 	 * Caller must be holding the validator lock.
 	 */
 
-	if (resume) {
-		/*
-		 * We already have a sigrdataset.
-		 */
+	if (CANCELED(val)) {
+		result = ISC_R_CANCELED;
+		goto cleanup;
+	}
+
+	if (val->resume) {
+		/* We already have a sigrdataset. */
 		result = ISC_R_SUCCESS;
 		validator_log(val, ISC_LOG_DEBUG(3), "resuming validate");
 	} else {
 		result = dns_rdataset_first(val->sigrdataset);
 	}
 
-	for (; result == ISC_R_SUCCESS;
-	     result = dns_rdataset_next(val->sigrdataset))
-	{
-		dns_rdata_reset(&rdata);
-		dns_rdataset_current(val->sigrdataset, &rdata);
-		if (val->siginfo == NULL) {
-			val->siginfo = isc_mem_get(val->view->mctx,
-						   sizeof(*val->siginfo));
-		}
-		result = dns_rdata_tostruct(&rdata, val->siginfo, NULL);
-		if (result != ISC_R_SUCCESS) {
-			return (result);
-		}
+cleanup:
+	if (result != ISC_R_SUCCESS) {
+		validate_answer_iter_done(val, result);
+		return;
+	}
 
-		/*
-		 * At this point we could check that the signature algorithm
-		 * was known and "sufficiently good".
-		 */
-		if (!dns_resolver_algorithm_supported(val->view->resolver,
-						      val->name,
-						      val->siginfo->algorithm))
-		{
-			resume = false;
-			continue;
-		}
+	result = validate_async_run(val, validate_answer_process);
+	INSIST(result == DNS_R_WAIT);
+}
 
-		if (!resume) {
-			result = seek_dnskey(val);
-			if (result == DNS_R_CONTINUE) {
-				continue; /* Try the next SIG RR. */
-			}
-			if (result != ISC_R_SUCCESS) {
-				return (result);
-			}
-		}
+static void
+validate_answer_iter_next(void *arg) {
+	dns_validator_t *val = arg;
+	isc_result_t result;
 
-		/*
-		 * There isn't a secure DNSKEY for this signature so move
-		 * onto the next RRSIG.
-		 */
-		if (val->key == NULL) {
-			resume = false;
-			continue;
+	if (CANCELED(val)) {
+		result = ISC_R_CANCELED;
+		goto cleanup;
+	}
+
+	val->resume = false;
+	result = dns_rdataset_next(val->sigrdataset);
+
+cleanup:
+	if (result != ISC_R_SUCCESS) {
+		validate_answer_iter_done(val, result);
+		return;
+	}
+
+	(void)validate_async_run(val, validate_answer_process);
+}
+
+static void
+validate_answer_finish(void *arg);
+
+static void
+validate_answer_signing_key(void *arg) {
+	dns_validator_t *val = arg;
+	isc_result_t result = ISC_R_NOTFOUND;
+
+	if (CANCELED(val)) {
+		val->result = ISC_R_CANCELED;
+	} else {
+		val->result = verify(val, val->key, &val->rdata,
+				     val->siginfo->keyid);
+	}
+
+	switch (val->result) {
+	case ISC_R_CANCELED:	 /* Validation was canceled */
+	case ISC_R_SHUTTINGDOWN: /* Server shutting down */
+	case ISC_R_QUOTA:	 /* Validation fails quota reached */
+	case ISC_R_SUCCESS: /* We found our valid signature, we are done! */
+		if (val->key != NULL) {
+			dst_key_free(&val->key);
+			val->key = NULL;
 		}
 
-		do {
-			isc_result_t tresult;
-			vresult = verify(val, val->key, &rdata,
-					 val->siginfo->keyid);
-			if (vresult == ISC_R_SUCCESS) {
-				break;
-			}
+		break;
+	default:
+		/* Select next signing key */
+		result = select_signing_key(val, val->keyset);
+		break;
+	}
 
-			tresult = select_signing_key(val, val->keyset);
-			if (tresult != ISC_R_SUCCESS) {
-				break;
-			}
-		} while (1);
-		if (vresult != ISC_R_SUCCESS) {
-			validator_log(val, ISC_LOG_DEBUG(3),
-				      "failed to verify rdataset");
-		} else {
-			dns_rdataset_trimttl(val->rdataset, val->sigrdataset,
-					     val->siginfo, val->start,
-					     val->view->acceptexpired);
+	if (result == ISC_R_SUCCESS) {
+		INSIST(val->key != NULL);
+	} else {
+		INSIST(val->key == NULL);
+	}
+}
+
+static void
+validate_answer_signing_key_done(void *arg) {
+	dns_validator_t *val = arg;
+
+	if (CANCELED(val)) {
+		val->result = ISC_R_CANCELED;
+	} else if (val->key != NULL) {
+		/* Process with next key if we selected one */
+		isc_work_enqueue(val->loop, validate_answer_signing_key,
+				 validate_answer_signing_key_done, val);
+		return;
+	}
+
+	validate_answer_finish(val);
+}
+
+static void
+validate_answer_process(void *arg) {
+	dns_validator_t *val = arg;
+	isc_result_t result;
+
+	if (CANCELED(val)) {
+		result = ISC_R_CANCELED;
+		goto cleanup;
+	}
+
+	dns_rdata_reset(&val->rdata);
+
+	dns_rdataset_current(val->sigrdataset, &val->rdata);
+	if (val->siginfo == NULL) {
+		val->siginfo = isc_mem_get(val->view->mctx,
+					   sizeof(*val->siginfo));
+	}
+	result = dns_rdata_tostruct(&val->rdata, val->siginfo, NULL);
+	if (result != ISC_R_SUCCESS) {
+		goto cleanup;
+	}
+
+	/*
+	 * At this point we could check that the signature algorithm
+	 * was known and "sufficiently good".
+	 */
+	if (!dns_resolver_algorithm_supported(val->view->resolver, val->name,
+					      val->siginfo->algorithm))
+	{
+		goto next_key;
+	}
+
+	if (!val->resume) {
+		result = seek_dnskey(val);
+		switch (result) {
+		case ISC_R_SUCCESS:
+			break;
+		case DNS_R_CONTINUE:
+			goto next_key;
+		case DNS_R_WAIT:
+			goto cleanup;
+		default:
+			goto cleanup;
 		}
+	}
+
+	/*
+	 * There isn't a secure DNSKEY for this signature so move
+	 * onto the next RRSIG.
+	 */
+	if (val->key == NULL) {
+		val->resume = false;
+		goto next_key;
+	}
+
+	isc_work_enqueue(val->loop, validate_answer_signing_key,
+			 validate_answer_signing_key_done, val);
+	return;
+
+next_key:
+	result = validate_async_run(val, validate_answer_iter_next);
+	goto cleanup;
+
+cleanup:
+	validate_async_done(val, result);
+}
+
+static void
+validate_answer_finish(void *arg) {
+	dns_validator_t *val = arg;
+	isc_result_t result = ISC_R_UNSET;
+
+	if (val->result != ISC_R_SUCCESS) {
+		validator_log(val, ISC_LOG_DEBUG(3),
+			      "failed to verify rdataset: %s",
+			      isc_result_totext(val->result));
+	} else {
+		dns_rdataset_trimttl(val->rdataset, val->sigrdataset,
+				     val->siginfo, val->start,
+				     val->view->acceptexpired);
+	}
 
-		if (val->key != NULL) {
-			dst_key_free(&val->key);
-		}
-		if (val->keyset != NULL) {
-			dns_rdataset_disassociate(val->keyset);
-			val->keyset = NULL;
-		}
+	if (val->key != NULL) {
+		dst_key_free(&val->key);
 		val->key = NULL;
-		if (NEEDNOQNAME(val)) {
-			if (val->message == NULL) {
-				validator_log(val, ISC_LOG_DEBUG(3),
-					      "no message available "
-					      "for noqname proof");
-				return (DNS_R_NOVALIDSIG);
-			}
+	}
+	if (val->keyset != NULL) {
+		dns_rdataset_disassociate(val->keyset);
+		val->keyset = NULL;
+	}
+
+	switch (val->result) {
+	case ISC_R_CANCELED:
+		/* The validation was canceled */
+		validator_log(val, ISC_LOG_DEBUG(3), "validation was canceled");
+		validate_async_done(val, val->result);
+		return;
+	case ISC_R_SHUTTINGDOWN:
+		validator_log(val, ISC_LOG_DEBUG(3), "server is shutting down");
+		validate_async_done(val, val->result);
+		return;
+	case ISC_R_QUOTA:
+		if (MAXVALIDATIONS(val)) {
 			validator_log(val, ISC_LOG_DEBUG(3),
-				      "looking for noqname proof");
-			return (validate_nx(val, false));
-		} else if (vresult == ISC_R_SUCCESS) {
-			marksecure(val);
+				      "maximum number of validations exceeded");
+		} else if (MAXVALIDATIONFAILS(val)) {
 			validator_log(val, ISC_LOG_DEBUG(3),
-				      "marking as secure, "
-				      "noqname proof not needed");
-			return (ISC_R_SUCCESS);
+				      "maximum number of validation failures "
+				      "exceeded");
 		} else {
+			validator_log(
+				val, ISC_LOG_DEBUG(3),
+				"unknown error: validation quota exceeded");
+		}
+		validate_async_done(val, val->result);
+		return;
+	default:
+		break;
+	}
+
+	if (NEEDNOQNAME(val)) {
+		if (val->message == NULL) {
 			validator_log(val, ISC_LOG_DEBUG(3),
-				      "verify failure: %s",
-				      isc_result_totext(result));
-			resume = false;
+				      "no message available for noqname proof");
+			validate_async_done(val, DNS_R_NOVALIDSIG);
+			return;
 		}
+
+		validator_log(val, ISC_LOG_DEBUG(3),
+			      "looking for noqname proof");
+		result = validate_nx(val, false);
+		validate_async_done(val, result);
+		return;
+	}
+
+	if (val->result == ISC_R_SUCCESS) {
+		marksecure(val);
+		validator_log(val, ISC_LOG_DEBUG(3),
+			      "marking as secure, noqname proof not needed");
+		validate_async_done(val, val->result);
+		return;
 	}
+
+	validator_log(val, ISC_LOG_DEBUG(3), "verify failure: %s",
+		      isc_result_totext(val->result));
+	(void)validate_async_run(val, validate_answer_iter_next);
+}
+
+static void
+validate_answer_iter_done(dns_validator_t *val, isc_result_t result) {
 	if (result != ISC_R_NOMORE) {
 		validator_log(val, ISC_LOG_DEBUG(3),
 			      "failed to iterate signatures: %s",
 			      isc_result_totext(result));
-		return (result);
+		validate_async_done(val, result);
+		return;
 	}
 
 	validator_log(val, ISC_LOG_INFO, "no valid signature found");
-	return (vresult);
+	validate_async_done(val, val->result);
+}
+
+static void
+resume_answer(void *arg) {
+	dns_validator_t *val = arg;
+	val->resume = true;
+	validate_answer_iter_start(val);
+}
+
+static void
+validate_answer(void *arg) {
+	dns_validator_t *val = arg;
+	val->resume = false;
+	validate_answer_iter_start(val);
+}
+
+static isc_result_t
+validate_async_run(dns_validator_t *val, isc_job_cb cb) {
+	isc_async_run(val->loop, cb, val);
+	return (DNS_R_WAIT);
+}
+
+static void
+validate_async_done(dns_validator_t *val, isc_result_t result) {
+	if (result == DNS_R_NOVALIDSIG &&
+	    (val->attributes & VALATTR_TRIEDVERIFY) == 0)
+	{
+		isc_result_t saved_result = result;
+		validator_log(val, ISC_LOG_DEBUG(3),
+			      "falling back to insecurity proof");
+		result = proveunsecure(val, false, false);
+		if (result == DNS_R_NOTINSECURE) {
+			result = saved_result;
+		}
+	}
+
+	if (result != DNS_R_WAIT) {
+		/* We are still continuing */
+		validator_done(val, result);
+		dns_validator_detach(&val);
+	}
 }
 
 /*%
@@ -1582,7 +1807,7 @@ check_signer(dns_validator_t *val, dns_rdata_t *keyrdata, uint16_t keyid,
 			}
 		}
 		result = verify(val, dstkey, &rdata, sig.keyid);
-		if (result == ISC_R_SUCCESS) {
+		if (result == ISC_R_SUCCESS || result == ISC_R_QUOTA) {
 			break;
 		}
 	}
@@ -1683,27 +1908,163 @@ get_dsset(dns_validator_t *val, dns_name_t *tname, isc_result_t *resp) {
 	return (DNS_R_CONTINUE);
 }
 
-/*%
- * Attempts positive response validation of an RRset containing zone keys
- * (i.e. a DNSKEY rrset).
- *
- * Caller must be holding the validator lock.
- *
- * Returns:
- * \li	ISC_R_SUCCESS	Validation completed successfully
- * \li	DNS_R_WAIT	Validation has started but is waiting
- *			for an event.
- * \li	Other return codes are possible and all indicate failure.
- */
+static void
+validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result) {
+	if (result == ISC_R_SUCCESS) {
+		marksecure(val);
+		validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)");
+	} else if (result == ISC_R_NOMORE && !val->supported_algorithm) {
+		validator_log(val, ISC_LOG_DEBUG(3),
+			      "no supported algorithm/digest (DS)");
+		result = markanswer(val, "validate_dnskey (3)",
+				    "no supported algorithm/digest (DS)");
+	} else {
+		validator_log(val, ISC_LOG_INFO,
+			      "no valid signature found (DS)");
+		result = DNS_R_NOVALIDSIG;
+	}
+
+	if (val->dsset == &val->fdsset) {
+		val->dsset = NULL;
+		dns_rdataset_disassociate(&val->fdsset);
+	}
+
+	validate_async_done(val, result);
+}
+
 static isc_result_t
-validate_dnskey(dns_validator_t *val) {
-	isc_result_t result;
+validate_dnskey_dsset(dns_validator_t *val) {
 	dns_rdata_t dsrdata = DNS_RDATA_INIT;
 	dns_rdata_t keyrdata = DNS_RDATA_INIT;
+	isc_result_t result;
+	dns_rdata_ds_t ds;
+
+	dns_rdata_reset(&dsrdata);
+	dns_rdataset_current(val->dsset, &dsrdata);
+	result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
+	RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+	if (ds.digest_type == DNS_DSDIGEST_SHA1 && val->digest_sha1 == false) {
+		return (DNS_R_BADALG);
+	}
+
+	if (!dns_resolver_ds_digest_supported(val->view->resolver, val->name,
+					      ds.digest_type))
+	{
+		return (DNS_R_BADALG);
+	}
+
+	if (!dns_resolver_algorithm_supported(val->view->resolver, val->name,
+					      ds.algorithm))
+	{
+		return (DNS_R_BADALG);
+	}
+
+	val->supported_algorithm = true;
+
+	/*
+	 * Find the DNSKEY matching the DS...
+	 */
+	result = dns_dnssec_matchdskey(val->name, &dsrdata, val->rdataset,
+				       &keyrdata);
+	if (result != ISC_R_SUCCESS) {
+		validator_log(val, ISC_LOG_DEBUG(3), "no DNSKEY matching DS");
+		return (DNS_R_NOKEYMATCH);
+	}
+
+	/*
+	 * ... and check that it signed the DNSKEY RRset.
+	 */
+	result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm);
+	if (result != ISC_R_SUCCESS) {
+		validator_log(val, ISC_LOG_DEBUG(3),
+			      "no RRSIG matching DS key");
+
+		return (result);
+	}
+
+	return (ISC_R_SUCCESS);
+}
+
+static void
+validate_dnskey_dsset_next(void *arg) {
+	dns_validator_t *val = arg;
+
+	if (CANCELED(val)) {
+		val->result = ISC_R_CANCELED;
+	} else {
+		val->result = dns_rdataset_next(val->dsset);
+	}
+
+	if (val->result == ISC_R_SUCCESS) {
+		/* continue async run */
+		val->result = validate_dnskey_dsset(val);
+	}
+}
+
+static void
+validate_dnskey_dsset_next_done(void *arg) {
+	dns_validator_t *val = arg;
+	isc_result_t result = val->result;
+
+	if (CANCELED(val)) {
+		result = ISC_R_CANCELED;
+	}
+
+	switch (result) {
+	case ISC_R_CANCELED:
+	case ISC_R_SHUTTINGDOWN:
+		/* Abort, abort, abort! */
+		break;
+	case ISC_R_SUCCESS:
+	case ISC_R_NOMORE:
+		/* We are done */
+		break;
+	default:
+		/* Continue validation until we have success or no more data */
+		isc_work_enqueue(val->loop, validate_dnskey_dsset_next,
+				 validate_dnskey_dsset_next_done, val);
+		return;
+	}
+
+	validate_dnskey_dsset_done(val, result);
+	return;
+}
+
+static void
+validate_dnskey_dsset_first(dns_validator_t *val) {
+	isc_result_t result;
+
+	if (CANCELED(val)) {
+		result = ISC_R_CANCELED;
+	} else {
+		result = dns_rdataset_first(val->dsset);
+	}
+
+	if (result == ISC_R_SUCCESS) {
+		/* continue async run */
+		result = validate_dnskey_dsset(val);
+		if (result != ISC_R_SUCCESS) {
+			isc_work_enqueue(val->loop, validate_dnskey_dsset_next,
+					 validate_dnskey_dsset_next_done, val);
+			return;
+		}
+	}
+
+	validate_dnskey_dsset_done(val, result);
+}
+
+static void
+validate_dnskey(void *arg) {
+	dns_validator_t *val = arg;
+	isc_result_t result = ISC_R_SUCCESS;
 	dns_keynode_t *keynode = NULL;
 	dns_rdata_ds_t ds;
-	bool supported_algorithm;
-	char digest_types[256];
+
+	if (CANCELED(val)) {
+		result = ISC_R_CANCELED;
+		goto cleanup;
+	}
 
 	/*
 	 * If we don't already have a DS RRset, check to see if there's
@@ -1767,7 +2128,7 @@ validate_dnskey(dns_validator_t *val) {
 	 * verification.
 	 */
 
-	supported_algorithm = false;
+	val->supported_algorithm = false;
 
 	/*
 	 * If DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present we
@@ -1775,7 +2136,8 @@ validate_dnskey(dns_validator_t *val) {
 	 * practice means that we need to ignore DNS_DSDIGEST_SHA1 if a
 	 * DNS_DSDIGEST_SHA256 or DNS_DSDIGEST_SHA384 is present.
 	 */
-	memset(digest_types, 1, sizeof(digest_types));
+	val->digest_sha1 = true;
+	dns_rdata_t dsrdata = DNS_RDATA_INIT;
 	for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS;
 	     result = dns_rdataset_next(val->dsset))
 	{
@@ -1801,80 +2163,20 @@ validate_dnskey(dns_validator_t *val) {
 		    (ds.digest_type == DNS_DSDIGEST_SHA384 &&
 		     ds.length == ISC_SHA384_DIGESTLENGTH))
 		{
-			digest_types[DNS_DSDIGEST_SHA1] = 0;
-			break;
-		}
-	}
-
-	for (result = dns_rdataset_first(val->dsset); result == ISC_R_SUCCESS;
-	     result = dns_rdataset_next(val->dsset))
-	{
-		dns_rdata_reset(&dsrdata);
-		dns_rdataset_current(val->dsset, &dsrdata);
-		result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
-		RUNTIME_CHECK(result == ISC_R_SUCCESS);
-
-		if (digest_types[ds.digest_type] == 0) {
-			continue;
-		}
-
-		if (!dns_resolver_ds_digest_supported(
-			    val->view->resolver, val->name, ds.digest_type))
-		{
-			continue;
-		}
-
-		if (!dns_resolver_algorithm_supported(val->view->resolver,
-						      val->name, ds.algorithm))
-		{
-			continue;
-		}
-
-		supported_algorithm = true;
-
-		/*
-		 * Find the DNSKEY matching the DS...
-		 */
-		result = dns_dnssec_matchdskey(val->name, &dsrdata,
-					       val->rdataset, &keyrdata);
-		if (result != ISC_R_SUCCESS) {
-			validator_log(val, ISC_LOG_DEBUG(3),
-				      "no DNSKEY matching DS");
-			continue;
-		}
-
-		/*
-		 * ... and check that it signed the DNSKEY RRset.
-		 */
-		result = check_signer(val, &keyrdata, ds.key_tag, ds.algorithm);
-		if (result == ISC_R_SUCCESS) {
+			val->digest_sha1 = false;
 			break;
 		}
-		validator_log(val, ISC_LOG_DEBUG(3),
-			      "no RRSIG matching DS key");
 	}
 
-	if (result == ISC_R_SUCCESS) {
-		marksecure(val);
-		validator_log(val, ISC_LOG_DEBUG(3), "marking as secure (DS)");
-	} else if (result == ISC_R_NOMORE && !supported_algorithm) {
-		validator_log(val, ISC_LOG_DEBUG(3),
-			      "no supported algorithm/digest (DS)");
-		result = markanswer(val, "validate_dnskey (3)",
-				    "no supported algorithm/digest (DS)");
-	} else {
-		validator_log(val, ISC_LOG_INFO,
-			      "no valid signature found (DS)");
-		result = DNS_R_NOVALIDSIG;
-	}
+	validate_dnskey_dsset_first(val);
+	return;
 
 cleanup:
 	if (val->dsset == &val->fdsset) {
 		val->dsset = NULL;
 		dns_rdataset_disassociate(&val->fdsset);
 	}
-
-	return (result);
+	validate_async_done(val, result);
 }
 
 /*%
@@ -2521,7 +2823,6 @@ validate_nx(dns_validator_t *val, bool resume) {
 		return (DNS_R_BROKENCHAIN);
 	}
 
-	validator_log(val, ISC_LOG_DEBUG(3), "nonexistence proof(s) not found");
 	return (proveunsecure(val, false, false));
 }
 
@@ -2597,10 +2898,10 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) {
 		 */
 		if (val->frdataset.trust >= dns_trust_secure) {
 			if (!check_ds_algs(val, tname, &val->frdataset)) {
-				validator_log(val, ISC_LOG_DEBUG(3),
-					      "no supported algorithm/"
-					      "digest (%s/DS)",
-					      namebuf);
+				validator_log(
+					val, ISC_LOG_DEBUG(3),
+					"no supported algorithm/digest (%s/DS)",
+					namebuf);
 				*resp = markanswer(val, "proveunsecure (5)",
 						   "no supported "
 						   "algorithm/digest (DS)");
@@ -2912,14 +3213,13 @@ validator_start(void *arg) {
 	isc_result_t result = ISC_R_FAILURE;
 
 	if (CANCELED(val)) {
-		return;
+		result = ISC_R_CANCELED;
+		goto cleanup;
 	}
 
 	validator_log(val, ISC_LOG_DEBUG(3), "starting");
 
 	if (val->rdataset != NULL && val->sigrdataset != NULL) {
-		isc_result_t saved_result;
-
 		/*
 		 * This looks like a simple validation.  We say "looks like"
 		 * because it might end up requiring an insecurity proof.
@@ -2929,21 +3229,19 @@ validator_start(void *arg) {
 
 		INSIST(dns_rdataset_isassociated(val->rdataset));
 		INSIST(dns_rdataset_isassociated(val->sigrdataset));
-		if (selfsigned_dnskey(val)) {
-			result = validate_dnskey(val);
-		} else {
-			result = validate_answer(val, false);
-		}
-		if (result == DNS_R_NOVALIDSIG &&
-		    (val->attributes & VALATTR_TRIEDVERIFY) == 0)
-		{
-			saved_result = result;
-			validator_log(val, ISC_LOG_DEBUG(3),
-				      "falling back to insecurity proof");
-			result = proveunsecure(val, false, false);
-			if (result == DNS_R_NOTINSECURE) {
-				result = saved_result;
-			}
+
+		result = selfsigned_dnskey(val);
+		switch (result) {
+		case ISC_R_QUOTA:
+			goto cleanup;
+		case ISC_R_SUCCESS:
+			result = validate_async_run(val, validate_dnskey);
+			break;
+		case DNS_R_NOKEYMATCH:
+			result = validate_async_run(val, validate_answer);
+			break;
+		default:
+			UNREACHABLE();
 		}
 	} else if (val->rdataset != NULL && val->rdataset->type != 0) {
 		/*
@@ -2996,11 +3294,8 @@ validator_start(void *arg) {
 		UNREACHABLE();
 	}
 
-	if (result != DNS_R_WAIT) {
-		validator_done(val, result);
-	}
-
-	dns_validator_detach(&val);
+cleanup:
+	validate_async_done(val, result);
 }
 
 isc_result_t
@@ -3008,6 +3303,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
 		     dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
 		     dns_message_t *message, unsigned int options,
 		     isc_loop_t *loop, isc_job_cb cb, void *arg,
+		     uint32_t *nvalidations, uint32_t *nfails,
 		     dns_validator_t **validatorp) {
 	isc_result_t result = ISC_R_FAILURE;
 	dns_validator_t *val = NULL;
@@ -3024,18 +3320,23 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
 	}
 
 	val = isc_mem_get(view->mctx, sizeof(*val));
-	*val = (dns_validator_t){ .tid = isc_tid(),
-				  .result = ISC_R_FAILURE,
-				  .rdataset = rdataset,
-				  .sigrdataset = sigrdataset,
-				  .name = name,
-				  .type = type,
-				  .options = options,
-				  .keytable = kt,
-				  .link = ISC_LINK_INITIALIZER,
-				  .loop = loop,
-				  .cb = cb,
-				  .arg = arg };
+	*val = (dns_validator_t){
+		.tid = isc_tid(),
+		.result = DNS_R_NOVALIDSIG,
+		.rdataset = rdataset,
+		.sigrdataset = sigrdataset,
+		.name = name,
+		.type = type,
+		.options = options,
+		.keytable = kt,
+		.link = ISC_LINK_INITIALIZER,
+		.loop = loop,
+		.cb = cb,
+		.arg = arg,
+		.rdata = DNS_RDATA_INIT,
+		.nvalidations = nvalidations,
+		.nfails = nfails,
+	};
 
 	isc_refcount_init(&val->references, 1);
 	dns_view_attach(view, &val->view);
@@ -3054,7 +3355,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
 
 	if ((options & DNS_VALIDATOR_DEFER) == 0) {
 		dns_validator_ref(val);
-		isc_async_run(val->loop, validator_start, val);
+		(void)validate_async_run(val, validator_start);
 	}
 
 	*validatorp = val;
@@ -3063,15 +3364,15 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type,
 }
 
 void
-dns_validator_send(dns_validator_t *validator) {
-	REQUIRE(VALID_VALIDATOR(validator));
-	REQUIRE(validator->tid == isc_tid());
+dns_validator_send(dns_validator_t *val) {
+	REQUIRE(VALID_VALIDATOR(val));
+	REQUIRE(val->tid == isc_tid());
 
-	INSIST((validator->options & DNS_VALIDATOR_DEFER) != 0);
-	validator->options &= ~DNS_VALIDATOR_DEFER;
+	INSIST((val->options & DNS_VALIDATOR_DEFER) != 0);
+	val->options &= ~DNS_VALIDATOR_DEFER;
 
-	dns_validator_ref(validator);
-	isc_async_run(validator->loop, validator_start, validator);
+	dns_validator_ref(val);
+	(void)validate_async_run(val, validator_start);
 }
 
 void
@@ -3092,7 +3393,6 @@ dns_validator_cancel(dns_validator_t *validator) {
 			validator->options &= ~DNS_VALIDATOR_DEFER;
 			validator_done(validator, ISC_R_CANCELED);
 		}
-
 		validator->attributes |= VALATTR_CANCELED;
 	}
 }
@@ -3111,9 +3411,6 @@ destroy_validator(dns_validator_t *val) {
 	if (val->keytable != NULL) {
 		dns_keytable_detach(&val->keytable);
 	}
-	if (val->subvalidator != NULL) {
-		dns_validator_destroy(&val->subvalidator);
-	}
 	disassociate_rdatasets(val);
 	mctx = val->view->mctx;
 	if (val->siginfo != NULL) {
diff --git a/lib/isc/loop.c b/lib/isc/loop.c
index e31563b63d5..829cf168a46 100644
--- a/lib/isc/loop.c
+++ b/lib/isc/loop.c
@@ -138,6 +138,8 @@ static void
 shutdown_trigger_close_cb(uv_handle_t *handle) {
 	isc_loop_t *loop = uv_handle_get_data(handle);
 
+	loop->shuttingdown = true;
+
 	isc_loop_detach(&loop);
 }
 
diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c
index 002d4d43a14..e9fc6831092 100644
--- a/lib/isccfg/namedconf.c
+++ b/lib/isccfg/namedconf.c
@@ -2103,6 +2103,10 @@ static cfg_clausedef_t view_clauses[] = {
 	{ "max-recursion-queries", &cfg_type_uint32, 0 },
 	{ "max-stale-ttl", &cfg_type_duration, 0 },
 	{ "max-udp-size", &cfg_type_uint32, 0 },
+	{ "max-validations-per-fetch", &cfg_type_uint32,
+	  CFG_CLAUSEFLAG_EXPERIMENTAL },
+	{ "max-validation-failures-per-fetch", &cfg_type_uint32,
+	  CFG_CLAUSEFLAG_EXPERIMENTAL },
 	{ "message-compression", &cfg_type_boolean, 0 },
 	{ "min-cache-ttl", &cfg_type_duration, 0 },
 	{ "min-ncache-ttl", &cfg_type_duration, 0 },
