blob: 4adec2f720623835dd4a006cb10ce499ff62c654 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <cstdint>
#include <functional>
#include <iostream>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include "common/status.h"
namespace doris {
// Same as
// fe/fe-core/src/main/java/org/apache/doris/common/DNSCache.java
class DNSCache {
public:
// (hostname, out_ip, is_ipv6, out_gai_err) -> Status.
// out_gai_err receives the raw getaddrinfo() return code so the cache can tell an
// authoritative "no such host" (EAI_NONAME) apart from a transient resolver failure.
using Resolver = std::function<Status(const std::string&, std::string&, bool, int*)>;
// Per-hostname failure bookkeeping. Only tracked for hostnames currently present in
// `cache`, which keeps the map bounded (invariant: keys(failure_count) ⊆ keys(cache)).
struct FailureState {
// Consecutive resolution failures of any kind. Drives the log throttle.
uint32_t count = 0;
// Whether the most recent failure was an authoritative NXDOMAIN. Eviction requires
// this to be true, so that a DNS-server outage (which yields EAI_AGAIN for every
// hostname at once) degrades to the stale cached IP instead of wiping the cache.
bool last_authoritative = false;
};
DNSCache();
// Test-only constructor: uses a custom resolver and does NOT start the
// background refresh thread. Call refresh_for_test() to drive one cycle.
explicit DNSCache(Resolver resolver);
~DNSCache();
// get ip by hostname
Status get(const std::string& hostname, std::string* ip);
private:
// Resolve hostname to IP address.
// If resolution fails, falls back to cached IP if available.
// Returns the resolved IP, or cached IP on failure, or empty string if no cache available.
// *is_fresh is set to true when DNS returned a live result, false when the
// returned IP is the stale cached fallback from a failed lookup.
std::string _resolve_hostname(const std::string& hostname, bool* is_fresh = nullptr);
// update the ip of hostname in cache; out_state (if non-null) is set to the
// current failure bookkeeping read under the same lock; out_ip (if non-null)
// receives the resolved IP so callers can use it without a second cache lookup
// (avoids operator[] mutation under shared_lock).
Status _update(const std::string& hostname, FailureState* out_state = nullptr,
std::string* out_ip = nullptr);
// erase a hostname from cache unconditionally (with unique_lock)
void _erase(const std::string& hostname);
// Erase a hostname from cache only if it still meets the eviction criteria:
// failure_count >= threshold AND the most recent failure was authoritative.
// Re-reads the live state under the same lock that performs the erase, so a
// concurrent successful resolution that cleared it is not lost.
// Returns true if the host was erased, false otherwise.
bool _erase_if_still_failing(const std::string& hostname, uint32_t threshold);
// Drop a hostname from the cache and write a negative-cache tombstone (subject to
// dns_cache_negative_ttl_seconds). Caller must already hold a unique_lock on `mutex`.
void _evict_locked(const std::string& hostname);
// Record a tombstone for a hostname that could not be resolved and has no cached IP,
// so repeated get() calls do not each pay a full blocking getaddrinfo. Uses
// try_emplace so an entry just re-armed by get()'s single-flight path is preserved.
void _remember_unresolvable(const std::string& hostname);
// one refresh cycle: update every cached hostname and evict if needed
void _refresh_once();
// a function for refresh daemon thread
// update cache at fix internal
void _refresh_cache();
// ── test helpers (accessible via friend class DNSCacheTest) ──────────────
size_t size_for_test() const {
std::shared_lock<std::shared_mutex> lock(mutex);
return cache.size();
}
size_t negative_cache_size_for_test() const {
std::shared_lock<std::shared_mutex> lock(mutex);
return _negative_cache.size();
}
uint32_t failure_count_for_test(const std::string& hostname) const {
std::shared_lock<std::shared_mutex> lock(mutex);
auto it = failure_count.find(hostname);
return it != failure_count.end() ? it->second.count : 0;
}
// Run one refresh cycle synchronously (no sleep). Only meaningful when
// the object was constructed with the test constructor (no background thread).
void refresh_for_test() { _refresh_once(); }
// Backdate all negative-cache entries far into the past so they appear
// expired without removing them. Use this to simulate TTL expiry in tests
// that need the re-arm path in get() to trigger.
// Backdating relative to now() (rather than to steady_clock's epoch, which is
// typically boot time) keeps this correct on a freshly booted host.
void _expire_negative_cache_for_test() {
std::unique_lock<std::shared_mutex> lock(mutex);
auto backdated = std::chrono::steady_clock::now() - std::chrono::hours(24 * 365);
for (auto& [k, v] : _negative_cache) {
v = backdated;
}
}
friend class DNSCacheTest;
private:
Resolver _resolver; // null → use global hostname_to_ip
// hostname -> ip
std::unordered_map<std::string, std::string> cache;
// hostname -> consecutive resolution failure bookkeeping
std::unordered_map<std::string, FailureState> failure_count;
// hostname -> eviction timestamp; effective deadline is computed as
// eviction_time + dns_cache_negative_ttl_seconds to honor live config changes.
std::unordered_map<std::string, std::chrono::steady_clock::time_point> _negative_cache;
mutable std::shared_mutex mutex;
std::thread refresh_thread;
// Protects stop_refresh and signals _refresh_cache to wake early on destroy.
std::mutex _cv_mutex;
std::condition_variable _cv;
std::atomic<bool> stop_refresh {false};
};
} // end of namespace doris