Coverage Report

Created: 2026-03-13 05:13

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
7.31k
        : cache(cache),
37
7.31k
          key(key),
38
7.31k
          container(container),
39
7.31k
          value(std::forward<Args>(args)...),
40
7.31k
          timestamp_seconds(MonotonicSeconds()) {}
41
42
template <typename KeyType, typename ValueType>
43
97.0k
bool LruMultiCache<KeyType, ValueType>::ValueType_internal::is_available() {
44
97.0k
    return member_hook.is_linked();
45
97.0k
}
46
47
template <typename KeyType, typename ValueType>
48
LruMultiCache<KeyType, ValueType>::Accessor::Accessor(ValueType_internal* p_value_internal)
49
110k
        : _p_value_internal(p_value_internal) {}
50
51
template <typename KeyType, typename ValueType>
52
155k
LruMultiCache<KeyType, ValueType>::Accessor::Accessor(Accessor&& rhs) {
53
155k
    _p_value_internal = std::move(rhs._p_value_internal);
54
155k
    rhs._p_value_internal = nullptr;
55
155k
}
56
template <typename KeyType, typename ValueType>
57
51.7k
auto LruMultiCache<KeyType, ValueType>::Accessor::operator=(Accessor&& rhs) -> Accessor& {
58
51.7k
    _p_value_internal = std::move(rhs._p_value_internal);
59
51.7k
    rhs._p_value_internal = nullptr;
60
51.7k
    return (*this);
61
51.7k
}
62
63
template <typename KeyType, typename ValueType>
64
265k
LruMultiCache<KeyType, ValueType>::Accessor::~Accessor() {
65
265k
    release();
66
265k
}
67
68
template <typename KeyType, typename ValueType>
69
420k
ValueType* LruMultiCache<KeyType, ValueType>::Accessor::get() {
70
420k
    if (_p_value_internal) {
71
258k
        return &(_p_value_internal->value);
72
258k
    }
73
74
162k
    return nullptr;
75
420k
}
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
317k
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
317k
    if (_p_value_internal) {
91
51.7k
        LruMultiCache& cache = _p_value_internal->cache;
92
51.7k
        cache.release(_p_value_internal);
93
51.7k
        _p_value_internal = nullptr;
94
51.7k
    }
95
317k
}
96
97
template <typename KeyType, typename ValueType>
98
0
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
0
    if (_p_value_internal) {
102
0
        LruMultiCache& cache = _p_value_internal->cache;
103
0
        cache.destroy(_p_value_internal);
104
0
        _p_value_internal = nullptr;
105
0
    }
106
0
}
107
108
template <typename KeyType, typename ValueType>
109
32
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
32
void LruMultiCache<KeyType, ValueType>::set_capacity(size_t new_capacity) {
125
32
    std::lock_guard<std::mutex> g(_lock);
126
32
    _capacity = new_capacity;
127
32
}
128
129
template <typename KeyType, typename ValueType>
130
51.7k
auto LruMultiCache<KeyType, ValueType>::get(const KeyType& key) -> Accessor {
131
51.7k
    std::lock_guard<std::mutex> g(_lock);
132
51.7k
    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
51.7k
    if (hash_table_it == _hash_table.end()) return Accessor();
137
138
45.3k
    Container& container = hash_table_it->second;
139
140
    // Empty containers are deleted automatiacally
141
45.3k
    DCHECK(!container.empty());
142
143
    // All the available elements are in the front, only need to check the first
144
45.3k
    auto container_it = container.begin();
145
146
    // No available object found, the caller will have to create a new one with
147
    // EmplaceAndGet()
148
45.3k
    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
44.4k
    container.splice(container.end(), container, container_it);
152
153
    // Remove the element from the LRU list as it is no longer available
154
44.4k
    container_it->member_hook.unlink();
155
156
44.4k
    return Accessor(&(*container_it));
157
45.3k
}
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
7.31k
        -> Accessor {
163
7.31k
    std::lock_guard<std::mutex> g(_lock);
164
165
    // creates default container if there isn't one
166
7.31k
    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
7.31k
    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
7.31k
    auto container_it = container.emplace(container.end(), (*this), stored_key, container,
174
7.31k
                                          std::forward<Args>(args)...);
175
176
    // Only can set this after emplace
177
7.31k
    container_it->it = container_it;
178
179
7.31k
    _size++;
180
181
    // Need to remove the oldest available if the cache is over the capacity
182
7.31k
    _evict_one_if_needed();
183
184
7.31k
    return Accessor(&(*container_it));
185
7.31k
}
186
187
template <typename KeyType, typename ValueType>
188
51.7k
void LruMultiCache<KeyType, ValueType>::release(ValueType_internal* p_value_internal) {
189
51.7k
    std::lock_guard<std::mutex> g(_lock);
190
191
    // This only can be used by the accessor, which already checks for nullptr
192
51.7k
    DCHECK(p_value_internal);
193
194
    // Has to be currently not available
195
51.7k
    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
51.7k
    p_value_internal->timestamp_seconds = MonotonicSeconds();
200
201
51.7k
    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
51.7k
    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
51.7k
    _lru_list.push_front(container.front());
209
210
    // In case we overshot the capacity already, the cache can evict the oldest one
211
51.7k
    _evict_one_if_needed();
212
51.7k
}
213
214
template <typename KeyType, typename ValueType>
215
0
void LruMultiCache<KeyType, ValueType>::destroy(ValueType_internal* p_value_internal) {
216
0
    std::lock_guard<std::mutex> g(_lock);
217
218
    // This only can be used by the accessor, which already checks for nullptr
219
0
    DCHECK(p_value_internal);
220
221
    // Has to be currently not available
222
0
    DCHECK(!p_value_internal->is_available());
223
224
0
    Container& container = p_value_internal->container;
225
226
0
    if (container.size() == 1) {
227
        // Last element, owning list can be removed to prevent aging
228
0
        _hash_table.erase(p_value_internal->key);
229
0
    } else {
230
        // Remove from owning list
231
0
        container.erase(p_value_internal->it);
232
0
    }
233
234
0
    _size--;
235
0
}
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
59.0k
void LruMultiCache<KeyType, ValueType>::_evict_one_if_needed() {
275
    // std::mutex is locked by the caller public function
276
    // _lock.DCheckLocked();
277
278
59.0k
    if (!_lru_list.empty() && _size > _capacity) {
279
0
        _evict_one(_lru_list.back());
280
0
    }
281
59.0k
}
282
283
template <typename KeyType, typename ValueType>
284
78.4k
void LruMultiCache<KeyType, ValueType>::evict_older_than(uint64_t oldest_allowed_timestamp) {
285
78.4k
    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
78.4k
    while (!_lru_list.empty() &&
291
78.4k
           (_size > _capacity || _lru_list.back().timestamp_seconds < oldest_allowed_timestamp)) {
292
0
        _evict_one(_lru_list.back());
293
0
    }
294
78.4k
}
295
296
} // namespace doris