Coverage Report

Created: 2026-08-06 13:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/dns_cache.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <atomic>
21
#include <chrono>
22
#include <condition_variable>
23
#include <cstdint>
24
#include <functional>
25
#include <iostream>
26
#include <mutex>
27
#include <shared_mutex>
28
#include <string>
29
#include <thread>
30
#include <unordered_map>
31
32
#include "common/status.h"
33
34
namespace doris {
35
36
// Same as
37
// fe/fe-core/src/main/java/org/apache/doris/common/DNSCache.java
38
class DNSCache {
39
public:
40
    // (hostname, out_ip, is_ipv6, out_gai_err) -> Status.
41
    // out_gai_err receives the raw getaddrinfo() return code so the cache can tell an
42
    // authoritative "no such host" (EAI_NONAME) apart from a transient resolver failure.
43
    using Resolver = std::function<Status(const std::string&, std::string&, bool, int*)>;
44
45
    // Per-hostname failure bookkeeping. Only tracked for hostnames currently present in
46
    // `cache`, which keeps the map bounded (invariant: keys(failure_count) ⊆ keys(cache)).
47
    struct FailureState {
48
        // Consecutive resolution failures of any kind. Drives the log throttle.
49
        uint32_t count = 0;
50
        // Whether the most recent failure was an authoritative NXDOMAIN. Eviction requires
51
        // this to be true, so that a DNS-server outage (which yields EAI_AGAIN for every
52
        // hostname at once) degrades to the stale cached IP instead of wiping the cache.
53
        bool last_authoritative = false;
54
    };
55
56
    DNSCache();
57
58
    // Test-only constructor: uses a custom resolver and does NOT start the
59
    // background refresh thread.  Call refresh_for_test() to drive one cycle.
60
    explicit DNSCache(Resolver resolver);
61
62
    ~DNSCache();
63
64
    // get ip by hostname
65
    Status get(const std::string& hostname, std::string* ip);
66
67
private:
68
    // Resolve hostname to IP address.
69
    // If resolution fails, falls back to cached IP if available.
70
    // Returns the resolved IP, or cached IP on failure, or empty string if no cache available.
71
    // *is_fresh is set to true when DNS returned a live result, false when the
72
    // returned IP is the stale cached fallback from a failed lookup.
73
    std::string _resolve_hostname(const std::string& hostname, bool* is_fresh = nullptr);
74
75
    // update the ip of hostname in cache; out_state (if non-null) is set to the
76
    // current failure bookkeeping read under the same lock; out_ip (if non-null)
77
    // receives the resolved IP so callers can use it without a second cache lookup
78
    // (avoids operator[] mutation under shared_lock).
79
    Status _update(const std::string& hostname, FailureState* out_state = nullptr,
80
                   std::string* out_ip = nullptr);
81
82
    // erase a hostname from cache unconditionally (with unique_lock)
83
    void _erase(const std::string& hostname);
84
85
    // Erase a hostname from cache only if it still meets the eviction criteria:
86
    // failure_count >= threshold AND the most recent failure was authoritative.
87
    // Re-reads the live state under the same lock that performs the erase, so a
88
    // concurrent successful resolution that cleared it is not lost.
89
    // Returns true if the host was erased, false otherwise.
90
    bool _erase_if_still_failing(const std::string& hostname, uint32_t threshold);
91
92
    // Drop a hostname from the cache and write a negative-cache tombstone (subject to
93
    // dns_cache_negative_ttl_seconds). Caller must already hold a unique_lock on `mutex`.
94
    void _evict_locked(const std::string& hostname);
95
96
    // Record a tombstone for a hostname that could not be resolved and has no cached IP,
97
    // so repeated get() calls do not each pay a full blocking getaddrinfo. Uses
98
    // try_emplace so an entry just re-armed by get()'s single-flight path is preserved.
99
    void _remember_unresolvable(const std::string& hostname);
100
101
    // one refresh cycle: update every cached hostname and evict if needed
102
    void _refresh_once();
103
104
    // a function for refresh daemon thread
105
    // update cache at fix internal
106
    void _refresh_cache();
107
108
    // ── test helpers (accessible via friend class DNSCacheTest) ──────────────
109
105
    size_t size_for_test() const {
110
105
        std::shared_lock<std::shared_mutex> lock(mutex);
111
105
        return cache.size();
112
105
    }
113
114
22
    size_t negative_cache_size_for_test() const {
115
22
        std::shared_lock<std::shared_mutex> lock(mutex);
116
22
        return _negative_cache.size();
117
22
    }
118
119
8
    uint32_t failure_count_for_test(const std::string& hostname) const {
120
8
        std::shared_lock<std::shared_mutex> lock(mutex);
121
8
        auto it = failure_count.find(hostname);
122
8
        return it != failure_count.end() ? it->second.count : 0;
123
8
    }
124
125
    // Run one refresh cycle synchronously (no sleep).  Only meaningful when
126
    // the object was constructed with the test constructor (no background thread).
127
98
    void refresh_for_test() { _refresh_once(); }
128
129
    // Backdate all negative-cache entries far into the past so they appear
130
    // expired without removing them.  Use this to simulate TTL expiry in tests
131
    // that need the re-arm path in get() to trigger.
132
    // Backdating relative to now() (rather than to steady_clock's epoch, which is
133
    // typically boot time) keeps this correct on a freshly booted host.
134
6
    void _expire_negative_cache_for_test() {
135
6
        std::unique_lock<std::shared_mutex> lock(mutex);
136
6
        auto backdated = std::chrono::steady_clock::now() - std::chrono::hours(24 * 365);
137
6
        for (auto& [k, v] : _negative_cache) {
138
6
            v = backdated;
139
6
        }
140
6
    }
141
142
    friend class DNSCacheTest;
143
144
private:
145
    Resolver _resolver; // null → use global hostname_to_ip
146
    // hostname -> ip
147
    std::unordered_map<std::string, std::string> cache;
148
    // hostname -> consecutive resolution failure bookkeeping
149
    std::unordered_map<std::string, FailureState> failure_count;
150
    // hostname -> eviction timestamp; effective deadline is computed as
151
    // eviction_time + dns_cache_negative_ttl_seconds to honor live config changes.
152
    std::unordered_map<std::string, std::chrono::steady_clock::time_point> _negative_cache;
153
    mutable std::shared_mutex mutex;
154
    std::thread refresh_thread;
155
    // Protects stop_refresh and signals _refresh_cache to wake early on destroy.
156
    std::mutex _cv_mutex;
157
    std::condition_variable _cv;
158
    std::atomic<bool> stop_refresh {false};
159
};
160
161
} // end of namespace doris