Coverage Report

Created: 2026-09-28 10:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/util/lru_multi_cache.inline.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
// This file is copied from
19
// https://github.com/apache/impala/blob/master/be/src/util/lru-multi-cache.inline.h
20
// and modified by Doris
21
22
#pragma once
23
24
#include <glog/logging.h>
25
26
#include "util/hash_util.hpp" // IWYU pragma: keep
27
#include "util/lru_multi_cache.h"
28
#include "util/time.h"
29
30
namespace doris {
31
32
template <typename KeyType, typename ValueType>
33
template <typename... Args>
34
LruMultiCache<KeyType, ValueType>::ValueType_internal::ValueType_internal(
35
        LruMultiCache& cache, const KeyType& key, Container_internal& container, Args&&... args)
36
18
        : cache(cache),
37
18
          key(key),
38
18
          container(container),
39
18
          value(std::forward<Args>(args)...),
40
18
          timestamp_seconds(MonotonicSeconds()) {}
41
42
template <typename KeyType, typename ValueType>
43
22
bool LruMultiCache<KeyType, ValueType>::ValueType_internal::is_available() {
44
22
    return member_hook.is_linked();
45
22
}
46
47
template <typename KeyType, typename ValueType>
48
LruMultiCache<KeyType, ValueType>::Accessor::Accessor(ValueType_internal* p_value_internal)
49
58
        : _p_value_internal(p_value_internal) {}
50
51
template <typename KeyType, typename ValueType>
52
24
LruMultiCache<KeyType, ValueType>::Accessor::Accessor(Accessor&& rhs) {
53
24
    _p_value_internal = std::move(rhs._p_value_internal);
54
24
    rhs._p_value_internal = nullptr;
55
24
}
56
template <typename KeyType, typename ValueType>
57
20
auto LruMultiCache<KeyType, ValueType>::Accessor::operator=(Accessor&& rhs) -> Accessor& {
58
20
    _p_value_internal = std::move(rhs._p_value_internal);
59
20
    rhs._p_value_internal = nullptr;
60
20
    return (*this);
61
20
}
62
63
template <typename KeyType, typename ValueType>
64
82
LruMultiCache<KeyType, ValueType>::Accessor::~Accessor() {
65
82
    release();
66
82
}
67
68
template <typename KeyType, typename ValueType>
69
140
ValueType* LruMultiCache<KeyType, ValueType>::Accessor::get() {
70
140
    if (_p_value_internal) {
71
96
        return &(_p_value_internal->value);
72
96
    }
73
74
44
    return nullptr;
75
140
}
76
77
template <typename KeyType, typename ValueType>
78
0
const KeyType* LruMultiCache<KeyType, ValueType>::Accessor::get_key() const {
79
0
    if (_p_value_internal) {
80
0
        return &(_p_value_internal->key);
81
0
    }
82
83
0
    return nullptr;
84
0
}
85
86
template <typename KeyType, typename ValueType>
87
86
void LruMultiCache<KeyType, ValueType>::Accessor::release() {
88
    /// Nullptr check as it has to be dereferenced to get the cache reference
89
    /// No nullptr check is needed inside LruMultiCache::Release()
90
86
    if (_p_value_internal) {
91
4
        LruMultiCache& cache = _p_value_internal->cache;
92
4
        cache.release(_p_value_internal);
93
4
        _p_value_internal = nullptr;
94
4
    }
95
86
}
96
97
template <typename KeyType, typename ValueType>
98
16
void LruMultiCache<KeyType, ValueType>::Accessor::destroy() {
99
    /// Nullptr check as it has to be dereferenced to get the cache reference
100
    /// No nullptr check is needed inside LruMultiCache::destroy()
101
16
    if (_p_value_internal) {
102
16
        LruMultiCache& cache = _p_value_internal->cache;
103
16
        cache.destroy(_p_value_internal);
104
16
        _p_value_internal = nullptr;
105
16
    }
106
16
}
107
108
template <typename KeyType, typename ValueType>
109
42
LruMultiCache<KeyType, ValueType>::LruMultiCache(size_t capacity) : _capacity(capacity), _size(0) {}
110
111
template <typename KeyType, typename ValueType>
112
size_t LruMultiCache<KeyType, ValueType>::size() {
113
    std::lock_guard<std::mutex> g(_lock);
114
    return _size;
115
}
116
117
template <typename KeyType, typename ValueType>
118
size_t LruMultiCache<KeyType, ValueType>::number_of_keys() {
119
    std::lock_guard<std::mutex> g(_lock);
120
    return _hash_table.size();
121
}
122
123
template <typename KeyType, typename ValueType>
124
42
void LruMultiCache<KeyType, ValueType>::set_capacity(size_t new_capacity) {
125
42
    std::lock_guard<std::mutex> g(_lock);
126
42
    _capacity = new_capacity;
127
42
}
128
129
template <typename KeyType, typename ValueType>
130
20
auto LruMultiCache<KeyType, ValueType>::get(const KeyType& key) -> Accessor {
131
20
    std::lock_guard<std::mutex> g(_lock);
132
20
    auto hash_table_it = _hash_table.find(key);
133
134
    // No owning list found with this key, the caller will have to create a new object
135
    // with EmplaceAndGet()
136
20
    if (hash_table_it == _hash_table.end()) return Accessor();
137
138
2
    Container& container = hash_table_it->second;
139
140
    // Empty containers are deleted automatiacally
141
2
    DCHECK(!container.empty());
142
143
    // All the available elements are in the front, only need to check the first
144
2
    auto container_it = container.begin();
145
146
    // No available object found, the caller will have to create a new one with
147
    // EmplaceAndGet()
148
2
    if (!container_it->is_available()) return Accessor();
149
150
    // Move the object to the back of the owning list as it is no longer available.
151
2
    container.splice(container.end(), container, container_it);
152
153
    // Remove the element from the LRU list as it is no longer available
154
2
    container_it->member_hook.unlink();
155
156
2
    return Accessor(&(*container_it));
157
2
}
158
159
template <typename KeyType, typename ValueType>
160
template <typename... Args>
161
auto LruMultiCache<KeyType, ValueType>::emplace_and_get(const KeyType& key, Args&&... args)
162
18
        -> Accessor {
163
18
    std::lock_guard<std::mutex> g(_lock);
164
165
    // creates default container if there isn't one
166
18
    Container& container = _hash_table[key];
167
168
    // Get the reference of the key stored in unordered_map, the parameter could be
169
    // temporary object but std::unordered_map has stable references
170
18
    const KeyType& stored_key = _hash_table.find(key)->first;
171
172
    // Place it as the last entry for the owning list, as it just got reserved
173
18
    auto container_it = container.emplace(container.end(), (*this), stored_key, container,
174
18
                                          std::forward<Args>(args)...);
175
176
    // Only can set this after emplace
177
18
    container_it->it = container_it;
178
179
18
    _size++;
180
181
    // Need to remove the oldest available if the cache is over the capacity
182
18
    _evict_one_if_needed();
183
184
18
    return Accessor(&(*container_it));
185
18
}
186
187
template <typename KeyType, typename ValueType>
188
4
void LruMultiCache<KeyType, ValueType>::release(ValueType_internal* p_value_internal) {
189
4
    std::lock_guard<std::mutex> g(_lock);
190
191
    // This only can be used by the accessor, which already checks for nullptr
192
4
    DCHECK(p_value_internal);
193
194
    // Has to be currently not available
195
4
    DCHECK(!p_value_internal->is_available());
196
197
    // DO NOT update timestamp_seconds when release.
198
    // Because we are about to evict cache value after a certain period.
199
4
    p_value_internal->timestamp_seconds = MonotonicSeconds();
200
201
4
    Container& container = p_value_internal->container;
202
203
    // Move the object to the front, keep LRU relation in owning list too to
204
    // be able to age out unused objects
205
4
    container.splice(container.begin(), container, p_value_internal->it);
206
207
    // Add the object to LRU list too as it is now available for usage
208
4
    _lru_list.push_front(container.front());
209
210
    // In case we overshot the capacity already, the cache can evict the oldest one
211
4
    _evict_one_if_needed();
212
4
}
213
214
template <typename KeyType, typename ValueType>
215
16
void LruMultiCache<KeyType, ValueType>::destroy(ValueType_internal* p_value_internal) {
216
16
    std::lock_guard<std::mutex> g(_lock);
217
218
    // This only can be used by the accessor, which already checks for nullptr
219
16
    DCHECK(p_value_internal);
220
221
    // Has to be currently not available
222
16
    DCHECK(!p_value_internal->is_available());
223
224
16
    Container& container = p_value_internal->container;
225
226
16
    if (container.size() == 1) {
227
        // Last element, owning list can be removed to prevent aging
228
16
        _hash_table.erase(p_value_internal->key);
229
16
    } else {
230
        // Remove from owning list
231
0
        container.erase(p_value_internal->it);
232
0
    }
233
234
16
    _size--;
235
16
}
236
237
template <typename KeyType, typename ValueType>
238
size_t LruMultiCache<KeyType, ValueType>::number_of_available_objects() {
239
    std::lock_guard<std::mutex> g(_lock);
240
    return _lru_list.size();
241
}
242
243
template <typename KeyType, typename ValueType>
244
void LruMultiCache<KeyType, ValueType>::rehash() {
245
    std::lock_guard<std::mutex> g(_lock);
246
    _hash_table.rehash(_hash_table.bucket_count() + 1);
247
}
248
249
template <typename KeyType, typename ValueType>
250
0
void LruMultiCache<KeyType, ValueType>::_evict_one(ValueType_internal& value_internal) {
251
    // std::mutex is locked by the caller evicting function
252
    // _lock.DCheckLocked();
253
254
    // Has to be available to evict
255
0
    DCHECK(value_internal.is_available());
256
257
    // Remove from LRU cache
258
0
    value_internal.member_hook.unlink();
259
260
0
    Container& container = value_internal.container;
261
262
0
    if (container.size() == 1) {
263
        // Last element, owning list can be removed to prevent aging
264
0
        _hash_table.erase(value_internal.key);
265
0
    } else {
266
        // Remove from owning list
267
0
        container.erase(value_internal.it);
268
0
    }
269
270
0
    _size--;
271
0
}
272
273
template <typename KeyType, typename ValueType>
274
22
void LruMultiCache<KeyType, ValueType>::_evict_one_if_needed() {
275
    // std::mutex is locked by the caller public function
276
    // _lock.DCheckLocked();
277
278
22
    if (!_lru_list.empty() && _size > _capacity) {
279
0
        _evict_one(_lru_list.back());
280
0
    }
281
22
}
282
283
template <typename KeyType, typename ValueType>
284
96.3k
void LruMultiCache<KeyType, ValueType>::evict_older_than(uint64_t oldest_allowed_timestamp) {
285
96.3k
    std::lock_guard<std::mutex> g(_lock);
286
287
    // Stop eviction if
288
    //   - there are no more available (i.e. evictable) objects
289
    //   - cache size is below capacity and the oldest object is not older than the limit
290
96.3k
    while (!_lru_list.empty() &&
291
96.3k
           (_size > _capacity || _lru_list.back().timestamp_seconds < oldest_allowed_timestamp)) {
292
0
        _evict_one(_lru_list.back());
293
0
    }
294
96.3k
}
295
296
} // namespace doris