Concurrent resolutions of the same host now share one query. Outcomes are reused briefly: answers for their record TTL (capped at 30s), failures for 2s.
Stacked on #18. Independent of #19.
Why
With #18, DNS no longer blocks libuv's thread pool, but every request still sends its own queries. A burst of requests to one slow or unresponsive host means one outstanding query per request until each times out. Requests retried right after a failure query that host again immediately. Both cost resolver capacity and latency for no new information.
Approach
resolveHost is the single DNS entry point for the preflight check and the connect-time lookup. Sharing and caching outcomes there, keyed by host and family, should cut repeated queries to one per host per TTL.
- Sharing: callers that arrive while a query is in flight join it. Each caller keeps its own timeout and
AbortSignal, so one caller aborting does not fail the others. - Answers are reused for the lowest record TTL, capped at 30s. A zero TTL is not cached.
- Failures (NXDOMAIN, timeouts, server failures) are reused for 2s, so retries against a dead host fail fast instead of piling up.
- Limits: the cache holds at most 1000 entries and evicts the oldest first. It is cleared when
dns.setServers()changes the servers, and answers from replaced servers are not stored. - Copies: callers get their own copy of the address list, so one caller cannot change what another receives.
Security: every caller still runs its own address checks on whatever it receives, so a reused answer is validated exactly like a fresh one. A rebinding nameserver can still use TTL 0 to avoid caching; the connect-time check covers that as before. Within the TTL, the preflight and the connect-time lookup now see the same answer, which tightens the rebinding window rather than widening it.
Limitation: random subdomains of a hostile zone each miss the cache. That case is covered by #18 (no thread pool use) and #19 (one resolution per hop), not here.
Validation
- New tests in
src/resolve-host.test.tsrun against the real UDP DNS stub. The stub gains adelayMsoption so callers can overlap.- 10 concurrent callers send 1 query
- one aborted caller does not affect another waiting on the same query
- a TTL of 10s is reused at 9s and queried again after 10s
- a 3600s TTL is capped at 30s
- a zero TTL is not cached
- a failure is reused for 2s, then retried
- families are kept apart
- cached lists are copied per caller
dns.setServers()drops cached answers
- The sharing, TTL, and failure-caching tests fail with the cache and in-flight map disabled.
pnpm validate: typecheck, lint, and format clean; 287 tests passing.
Activity
1This pull request is a draft
Merging is unavailable until it is marked ready for review.