Skip to content

Commit 0b5c922

Browse files
committed
fix(MapLru): use internal Set to track order
1 parent f52d761 commit 0b5c922

File tree

2 files changed

+93
-30
lines changed

2 files changed

+93
-30
lines changed

.changeset/forty-feet-worry.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@ckb-ccc/core": patch
3+
---
4+
5+
fix(MapLru): use internal Set to track order
6+

packages/core/src/client/cache/memory.advanced.ts

Lines changed: 87 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -145,20 +145,40 @@ export function filterCell(
145145
}
146146

147147
/**
148-
* A Least Recently Used (LRU) cache implemented using a Map.
148+
* A Least Recently Used (LRU) cache implemented by extending the built-in Map.
149149
*
150-
* This class extends the built-in Map to provide an LRU cache with a fixed capacity.
151-
* When the cache is full, the least recently used entry is automatically evicted.
150+
* This class preserves all Map behaviors while adding LRU eviction semantics:
151+
* - When an entry is accessed via get() it becomes the most recently used.
152+
* - When an entry is inserted via set() it becomes the most recently used.
153+
* - If insertion causes the cache to exceed its capacity, the least recently used
154+
* entry is evicted automatically.
152155
*
153-
* @template K The type of the keys in the cache.
154-
* @template V The type of the values in the cache.
156+
* Implementation notes:
157+
* - The Map (super) stores key-value pairs and provides O(1) get/set/delete semantics.
158+
* - A Set named `lru` maintains usage order: the iteration order of the Set goes from
159+
* least-recently-used (first) to most-recently-used (last). We update that Set on
160+
* accesses and insertions to keep order correct.
161+
*
162+
* @template K Type of keys in the cache.
163+
* @template V Type of values in the cache.
155164
*/
156165
export class MapLru<K, V> extends Map<K, V> {
157166
/**
158-
* Constructs a new MapLru instance.
167+
* Internal ordered set used to track key usage.
168+
*
169+
* The Set preserves insertion order; keys are re-inserted on access so that the
170+
* first element in the Set is always the least recently used key.
171+
*/
172+
private readonly lru: Set<K> = new Set();
173+
174+
/**
175+
* Create a new MapLru with a fixed capacity.
176+
*
177+
* The capacity is the maximum number of entries the cache will hold. When the cache
178+
* grows beyond this capacity the least recently used entry is removed.
159179
*
160-
* @param capacity The maximum number of entries the cache can hold. Must be a positive integer.
161-
* @throws {Error} If the capacity is not a positive integer.
180+
* @param capacity Maximum number of entries allowed in the cache.
181+
* @throws {Error} If capacity is not a positive integer.
162182
*/
163183
constructor(private readonly capacity: number) {
164184
super();
@@ -168,50 +188,87 @@ export class MapLru<K, V> extends Map<K, V> {
168188
}
169189

170190
/**
171-
* Retrieves a value from the cache.
191+
* Retrieve a value from the cache and mark the key as most-recently-used.
172192
*
173-
* If the key is present in the cache, the value is moved to the most-recently-used position.
193+
* Behavior details:
194+
* - If the key is present, it is moved to the most-recently-used position in the
195+
* internal LRU tracking Set and its associated value is returned.
196+
* - If the key is not present, undefined is returned and the LRU order is unchanged.
174197
*
175-
* @param key The key of the value to retrieve.
176-
* @returns The value associated with the key, or undefined if the key is not present.
198+
* @param key Key whose associated value is to be returned.
199+
* @returns The value associated with the specified key, or **undefined** if not present.
177200
*/
178201
override get(key: K): V | undefined {
179-
// Check if the key exists. If not, return undefined.
202+
// If the Map does not contain the key, return undefined without changing LRU order.
180203
if (!super.has(key)) {
181204
return undefined;
182205
}
183206

184-
const value = super.get(key) as V;
207+
// Move to most-recently-used position by deleting then re-adding the key.
208+
this.lru.delete(key);
209+
this.lru.add(key);
185210

186-
// Move to most-recently-used position
187-
super.delete(key);
188-
super.set(key, value);
189-
190-
return value;
211+
// super.get is safe to cast because we just confirmed the key exists.
212+
return super.get(key) as V;
191213
}
192214

193215
/**
194-
* Inserts a new value into the cache, or updates an existing value.
216+
* Insert or update a key/value pair and mark the key as most-recently-used.
195217
*
196-
* If the key is already present in the cache, it is first deleted so that the re-insertion
197-
* moves it to the most-recently-used position.
198-
* If the cache is over capacity after the insertion, the least recently used entry is evicted.
218+
* Behavior details:
219+
* - If the key already exists, it's updated and moved to the most-recently-used position.
220+
* - If insertion causes the cache size to exceed capacity, the least-recently-used key
221+
* (the first key in the LRU Set) is evicted from both the Map and the LRU Set.
199222
*
200-
* @param key The key of the value to insert or update.
201-
* @param value The value to associate with the key.
202-
* @returns This MapLru instance.
223+
* @param key Key to insert or update.
224+
* @param value Value to associate with the key.
225+
* @returns This MapLru instance (allows chaining).
203226
*/
204227
override set(key: K, value: V): this {
205-
// Delete and re-insert to move key to the end (most-recently-used)
206-
super.delete(key);
228+
// Store/update the value in the underlying Map.
207229
super.set(key, value);
208230

209-
// Evict oldest if over capacity
231+
// Ensure key is at the most-recently-used position.
232+
this.lru.delete(key);
233+
this.lru.add(key);
234+
235+
// If over capacity, evict the least-recently-used key (first key in Set iteration).
210236
if (super.size > this.capacity) {
211-
const oldestKey = super.keys().next().value!;
237+
// .next().value is guaranteed to exist here because size > capacity >= 1
238+
const oldestKey = this.lru.keys().next().value!;
212239
super.delete(oldestKey);
240+
this.lru.delete(oldestKey);
213241
}
214242

215243
return this;
216244
}
245+
246+
/**
247+
* Remove a key and its associated value from the cache.
248+
*
249+
* This removes the key from both the underlying Map and the LRU tracking Set.
250+
*
251+
* @param key Key to remove.
252+
* @returns **true** if the key was present and removed; **false** if the key was not present.
253+
*/
254+
override delete(key: K): boolean {
255+
// Attempt to delete from the underlying Map first; if it didn't exist, no changes are needed.
256+
if (!super.delete(key)) {
257+
return false;
258+
}
259+
// Ensure LRU tracking no longer references the deleted key.
260+
this.lru.delete(key);
261+
return true;
262+
}
263+
264+
/**
265+
* Remove all entries from the cache.
266+
*
267+
* This clears both the underlying Map storage and the internal LRU tracking Set,
268+
* ensuring no stale keys remain in the LRU structure after the cache is emptied.
269+
*/
270+
override clear(): void {
271+
super.clear();
272+
this.lru.clear();
273+
}
217274
}

0 commit comments

Comments
 (0)