@@ -38,10 +38,9 @@ namespace paimon {
3838// / A generic LRU cache with support for weight-based eviction, time-based expiration,
3939// / and removal callbacks.
4040// /
41- // / Uses std::list + unordered_map for O(1) lookup and LRU updates :
41+ // / Uses std::list + unordered_map for O(1) get/put/evict :
4242// / - list stores entries in LRU order (most recently used at front)
4343// / - map stores key -> list::iterator for O(1) lookup
44- // / When write expiration is enabled, insertion and replacement also scan for expired entries.
4544// /
4645// / @tparam K Key type
4746// / @tparam V Value type
@@ -57,13 +56,12 @@ class GenericLruCache {
5756 enum class RemovalCause {
5857 EXPLICIT , // Removed by Invalidate() or InvalidateAll()
5958 SIZE , // Evicted because total weight exceeded max_weight
60- EXPIRED , // Evicted because the entry expired
59+ EXPIRED , // Evicted because the entry expired (expireAfterAccess)
6160 REPLACED // Replaced by a new value for the same key via Put()
6261 };
6362
6463 using WeighFunc = std::function<int64_t (const K&, const V&)>;
6564 using RemovalCallback = std::function<void (const K&, const V&, RemovalCause)>;
66- using Clock = std::function<std::chrono::steady_clock::time_point()>;
6765
6866 // / Configuration options for the cache.
6967 struct Options {
@@ -80,17 +78,9 @@ class GenericLruCache {
8078 // / Callback invoked when an entry is removed (evicted, invalidated, or replaced).
8179 // / If nullptr, no callback is invoked.
8280 RemovalCallback removal_callback = nullptr ;
83-
84- // / Time in milliseconds after insertion or Put() before an entry expires.
85- // / Reads do not extend this deadline. A value < 0 disables expiration.
86- int64_t expire_after_write_ms = -1 ;
8781 };
8882
89- explicit GenericLruCache (Options options)
90- : GenericLruCache(std::move(options), std::chrono::steady_clock::now) {}
91-
92- GenericLruCache (Options options, Clock clock)
93- : options_(std::move(options)), clock_(std::move(clock)) {}
83+ explicit GenericLruCache (Options options) : options_(std::move(options)) {}
9484
9585 // / Look up a key in the cache. On hit, promotes the entry to the front (most recently
9686 // / used) and updates its access time. Returns std::nullopt on miss or if the entry
@@ -102,17 +92,13 @@ class GenericLruCache {
10292
10393 // / Look up a key. On miss, load via the supplier, insert into cache, and return.
10494 // / If the supplier returns an error, the error is propagated and nothing is cached.
105- // / Invalidation during a load prevents its result from being cached, but the result is
106- // / still returned to the caller whose load was already in progress.
10795 Result<V> Get (const K& key, std::function<Result<V>(const K&)> supplier) {
108- uint64_t generation;
10996 {
11097 std::unique_lock<std::shared_mutex> lock (mutex_);
11198 auto cached = FindPromoteOrExpire (key);
11299 if (cached.has_value ()) {
113100 return std::move (cached.value ());
114101 }
115- generation = generation_;
116102 }
117103
118104 // Cache miss: load via supplier outside the lock
@@ -123,9 +109,6 @@ class GenericLruCache {
123109 }
124110
125111 std::unique_lock<std::shared_mutex> lock (mutex_);
126- if (generation != generation_) {
127- return value;
128- }
129112 // Double-check: another thread may have inserted while we were loading
130113 auto cached = FindPromoteOrExpire (key);
131114 if (cached.has_value ()) {
@@ -152,7 +135,6 @@ class GenericLruCache {
152135 auto it = lru_map_.find (key);
153136 if (it != lru_map_.end ()) {
154137 if (ValuesEqual (it->second ->value , value)) {
155- it->second ->last_write_time = clock_ ();
156138 Promote (it->second );
157139 return Status::OK ();
158140 }
@@ -168,7 +150,6 @@ class GenericLruCache {
168150 // / Remove a specific entry. Invokes the EXPLICIT removal callback if the key exists.
169151 void Invalidate (const K& key) {
170152 std::unique_lock<std::shared_mutex> lock (mutex_);
171- ++generation_;
172153 auto it = lru_map_.find (key);
173154 if (it != lru_map_.end ()) {
174155 RemoveEntry (it->second , RemovalCause::EXPLICIT );
@@ -178,7 +159,6 @@ class GenericLruCache {
178159 // / Remove all entries. Each entry's EXPLICIT removal callback is invoked.
179160 void InvalidateAll () {
180161 std::unique_lock<std::shared_mutex> lock (mutex_);
181- ++generation_;
182162 while (!lru_list_.empty ()) {
183163 RemoveEntry (std::prev (lru_list_.end ()), RemovalCause::EXPLICIT );
184164 }
@@ -208,7 +188,6 @@ class GenericLruCache {
208188 V value;
209189 int64_t weight;
210190 std::chrono::steady_clock::time_point last_access_time;
211- std::chrono::steady_clock::time_point last_write_time;
212191 };
213192
214193 using EntryList = std::list<CacheEntry>;
@@ -223,7 +202,7 @@ class GenericLruCache {
223202 return std::nullopt ;
224203 }
225204 auto list_it = it->second ;
226- if (IsExpired (* list_it, clock_ () )) {
205+ if (IsExpired (list_it-> last_access_time )) {
227206 RemoveEntry (list_it, RemovalCause::EXPIRED );
228207 return std::nullopt ;
229208 }
@@ -233,14 +212,14 @@ class GenericLruCache {
233212
234213 // / Move an entry to the front of the LRU list and update its access time.
235214 void Promote (typename EntryList::iterator list_it) {
236- list_it->last_access_time = clock_ ();
215+ list_it->last_access_time = std::chrono::steady_clock::now ();
237216 lru_list_.splice (lru_list_.begin (), lru_list_, list_it);
238217 }
239218
240219 // / Insert a new entry at the front of the LRU list.
241220 void InsertEntry (const K& key, V value, int64_t weight) {
242- auto now = clock_ ();
243- lru_list_. emplace_front ( CacheEntry{key, std::move (value), weight, now, now });
221+ lru_list_. emplace_front (
222+ CacheEntry{key, std::move (value), weight, std::chrono::steady_clock:: now() });
244223 lru_map_[key] = lru_list_.begin ();
245224 current_weight_ += weight;
246225 }
@@ -271,8 +250,7 @@ class GenericLruCache {
271250 V old_value = std::move (list_it->value );
272251 list_it->value = std::move (new_value);
273252 list_it->weight = new_weight;
274- list_it->last_access_time = clock_ ();
275- list_it->last_write_time = list_it->last_access_time ;
253+ list_it->last_access_time = std::chrono::steady_clock::now ();
276254 current_weight_ += new_weight;
277255 lru_list_.splice (lru_list_.begin (), lru_list_, list_it);
278256
@@ -289,33 +267,25 @@ class GenericLruCache {
289267 InvokeCallback (key, value, cause);
290268 }
291269
292- // / Evict expired entries, then evict by weight if still over capacity.
270+ // / Evict expired entries from the tail , then evict by weight if still over capacity.
293271 void EvictIfNeeded () {
294272 EvictExpired ();
295273 while (current_weight_ > options_.max_weight && !lru_list_.empty ()) {
296274 RemoveEntry (std::prev (lru_list_.end ()), RemovalCause::SIZE );
297275 }
298276 }
299277
300- // / Write deadlines are not ordered by LRU position, so write expiration requires a scan.
301- // / With only access expiration, the oldest access time is at the tail.
278+ // / Evict expired entries from the tail of the LRU list.
279+ // / Since the tail has the oldest access time, we can stop as soon as we find
280+ // / a non-expired entry.
302281 void EvictExpired () {
303- auto now = clock_ ();
304- if (options_.expire_after_write_ms >= 0 ) {
305- for (auto it = lru_list_.begin (); it != lru_list_.end ();) {
306- auto current = it++;
307- if (IsExpired (*current, now)) {
308- RemoveEntry (current, RemovalCause::EXPIRED );
309- }
310- }
311- return ;
312- }
313282 if (options_.expire_after_access_ms < 0 ) {
314283 return ;
315284 }
285+ auto now = std::chrono::steady_clock::now ();
316286 while (!lru_list_.empty ()) {
317287 auto it = std::prev (lru_list_.end ());
318- if (!IsExpired (*it , now)) {
288+ if (!IsExpired (it-> last_access_time , now)) {
319289 break ;
320290 }
321291 RemoveEntry (it, RemovalCause::EXPIRED );
@@ -330,16 +300,16 @@ class GenericLruCache {
330300 return 1 ;
331301 }
332302
333- bool IsExpired ( const CacheEntry& entry,
334- const std::chrono::steady_clock::time_point& now) const {
335- auto access_elapsed =
336- std::chrono::duration_cast< std::chrono::milliseconds>( now - entry. last_access_time );
337- auto write_elapsed =
338- std::chrono::duration_cast<std::chrono::milliseconds>(now - entry. last_write_time ) ;
339- return (options_. expire_after_access_ms >= 0 &&
340- access_elapsed. count () >= options_. expire_after_access_ms ) ||
341- (options_. expire_after_write_ms >= 0 &&
342- write_elapsed .count () >= options_.expire_after_write_ms ) ;
303+ // / Check if an entry has expired based on its last access time.
304+ bool IsExpired (
305+ const std::chrono::steady_clock::time_point& last_access_time,
306+ const std::chrono::steady_clock::time_point& now = std::chrono::steady_clock:: now()) const {
307+ if (options_. expire_after_access_ms < 0 ) {
308+ return false ;
309+ }
310+ auto elapsed =
311+ std::chrono::duration_cast<std::chrono::milliseconds>(now - last_access_time);
312+ return elapsed .count () >= options_.expire_after_access_ms ;
343313 }
344314
345315 // / Invoke the removal callback if one is configured.
@@ -350,9 +320,6 @@ class GenericLruCache {
350320 }
351321
352322 Options options_;
353- Clock clock_;
354- // A single generation avoids retaining per-key state for loads that have not completed.
355- uint64_t generation_ = 0 ;
356323 int64_t current_weight_ = 0 ;
357324 EntryList lru_list_;
358325 EntryMap lru_map_;
0 commit comments