1. Overview
The asyncDNSResolver.hpp header defines the AsyncDNSResolver class, a lightweight singleton component that performs non-blocking DNS resolution for hostnames and maintains a short-lived in-memory cache. The design is optimized for UI-driven or latency-sensitive workflows where blocking name resolution would negatively impact responsiveness.
Functionally, AsyncDNSResolver provides:
- Asynchronous hostname-to-IPv4 resolution using background threads
- Future-based results via
std::future<std::string> - TTL-based caching of resolved IP addresses (default: 5 minutes)
- De-duplication of concurrent lookups (shared pending promises)
- Thread-safe access using a mutex-protected cache and pending map
2. Dependencies and Includes
#include <string>
#include <unordered_map>
#include <future>
#include <mutex>
#include <thread>
#include <chrono>
#include <netdb.h>
#include <arpa/inet.h>
- <string> – hostname input and IP output representation
- <unordered_map> – cache and pending lookup maps
- <future> – asynchronous result handling (
std::future,std::promise) - <mutex> – synchronization primitives for thread-safe state
- <thread> – background worker threads for resolver execution
- <chrono> – TTL timing and monotonic time measurement
- <netdb.h> –
getaddrinfo()for DNS resolution - <arpa/inet.h> –
inet_ntop()for IPv4 address formatting
3. Class Declaration and Scope
class AsyncDNSResolver
The class is implemented as a process-wide singleton accessible via AsyncDNSResolver::instance(). The constructor is private to prevent direct instantiation.
4. Singleton Access
static AsyncDNSResolver& instance();
Returns the singleton instance. The implementation uses a function-local static object, which is thread-safe in modern C++ compilers and ensures one-time initialization.
5. Public Interface
5.1 Asynchronous Resolution
std::future<std::string> resolve_async(const std::string& host);
Starts hostname resolution in a background thread and returns a future that will be fulfilled with the resolved IPv4 address (as a string) or an empty string on failure.
Behavioral characteristics:
- Cache hit: if a non-expired cache entry exists, returns an already satisfied future with the cached IP address
- Pending hit: if a lookup is already in progress for the same host, returns the same pending future (de-duplication)
- Cache miss: starts a detached worker thread, stores a shared promise in the pending map, and fulfills it when resolution completes
5.2 Cached Resolution Lookup
std::string resolve_cached(const std::string& host);
Returns the currently cached IP address for the given host without blocking. If the entry does not exist or has expired, returns an empty string.
6. Internal State and Data Model
struct Entry { std::string ip; uint64_t expires; };
static constexpr uint64_t TTL_MS = 5 * 60 * 1000;
std::unordered_map<std::string, Entry> cache_;
std::unordered_map<std::string, std::shared_ptr<std::promise<std::string>>> pending_;
std::mutex mutex_;
- Entry – cache record containing the IP string and expiration timestamp
- TTL_MS – time-to-live for cache entries (default: 5 minutes)
- cache_ – map of host to cached
Entry - pending_ – map of host to in-flight promise (deduplicates concurrent lookups)
- mutex_ – protects
cache_andpending_against races
7. Timing Helper
static uint64_t now_ms();
Computes a monotonic millisecond timestamp based on std::chrono::steady_clock. This avoids issues caused by system clock changes when determining cache expiration.
8. Resolution Mechanics
DNS resolution is performed using:
getaddrinfo()constrained to IPv4 (AF_INET)- Conversion of the resolved address to string form via
inet_ntop() - Release of resolver resources via
freeaddrinfo()
9. Threading Model
The asynchronous lookup uses a detached std::thread per cache miss. On completion:
- The result is written to the cache with a TTL-based expiration
- The corresponding pending promise is removed from
pending_ - The promise is fulfilled to complete all waiting futures
10. Security and Reliability Considerations
- Thread lifecycle: detached threads simplify usage but make lifecycle management and shutdown coordination more complex; ensure the process is not terminating while work is in flight
- Unbounded concurrency: a new thread per cache miss may lead to resource pressure under high load; consider a bounded thread pool for intensive workloads
- Empty-string semantics: resolution failures return an empty string; callers should treat empty results as failure and log or retry accordingly
- IPv4-only behavior: the resolver is constrained to
AF_INET; if IPv6 support is required, extend hints and output formatting accordingly - Input validation: callers should validate hostnames to avoid unnecessary resolver calls or handling of malformed inputs
- Cache correctness: TTL-based caching improves performance but can produce stale results in rapidly changing DNS environments; tune
TTL_MSbased on operational needs