Skip to content

Commit af4f397

Browse files
committed
fix: proper cache eviction and avoid repeat computation
1 parent c640790 commit af4f397

2 files changed

Lines changed: 63 additions & 40 deletions

File tree

packages/codegen-core/src/symbols/registry.ts

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export class SymbolRegistry implements ISymbolRegistry {
1313
private _queryCache: Map<QueryCacheKey, ReadonlyArray<Symbol>> = new Map();
1414
/** Reverse index: serialized index entry → set of cache keys that depend on it. */
1515
private _dependencyToCache: Map<QueryCacheKey, Set<QueryCacheKey>> = new Map();
16+
/** Forward index: cache key → serialized entries it depends on, for clean eviction. */
17+
private _cacheDependencies: Map<QueryCacheKey, ReadonlyArray<string>> = new Map();
1618
private _registered: Set<SymbolId> = new Set();
1719
private _stubs: Set<SymbolId> = new Set();
1820
private _stubCache: Map<QueryCacheKey, SymbolId> = new Map();
@@ -36,6 +38,52 @@ export class SymbolRegistry implements ISymbolRegistry {
3638
query(filter: ISymbolMeta): ReadonlyArray<Symbol> {
3739
const indexKeySpace = this.buildIndexKeySpace(filter);
3840
const cacheKey = this.cacheKeyFromKeySpace(indexKeySpace);
41+
return this.queryByKeySpace(indexKeySpace, cacheKey);
42+
}
43+
44+
reference(meta: ISymbolMeta): Symbol {
45+
const indexKeySpace = this.buildIndexKeySpace(meta);
46+
const cacheKey = this.cacheKeyFromKeySpace(indexKeySpace);
47+
const [registered] = this.queryByKeySpace(indexKeySpace, cacheKey);
48+
if (registered) return registered;
49+
50+
const cachedId = this._stubCache.get(cacheKey);
51+
if (cachedId !== undefined) return this._values.get(cachedId)!;
52+
53+
const stub = new Symbol({ meta, name: '' }, this.nextId);
54+
55+
this._values.set(stub.id, stub);
56+
this._stubs.add(stub.id);
57+
this._stubCache.set(cacheKey, stub.id);
58+
return stub;
59+
}
60+
61+
register(symbol: ISymbolIn): Symbol {
62+
const result = new Symbol(symbol, this.nextId);
63+
64+
this._values.set(result.id, result);
65+
this._registered.add(result.id);
66+
67+
if (result.meta) {
68+
const indexKeySpace = this.buildIndexKeySpace(result.meta);
69+
this.indexSymbol(result.id, indexKeySpace);
70+
this.invalidateCache(indexKeySpace);
71+
this.replaceStubs(result, indexKeySpace);
72+
}
73+
74+
return result;
75+
}
76+
77+
*registered(): IterableIterator<Symbol> {
78+
for (const id of this._registered.values()) {
79+
yield this._values.get(id)!;
80+
}
81+
}
82+
83+
private queryByKeySpace(
84+
indexKeySpace: IndexKeySpace,
85+
cacheKey: QueryCacheKey,
86+
): ReadonlyArray<Symbol> {
3987
const cached = this._queryCache.get(cacheKey);
4088
if (cached) {
4189
return cached;
@@ -84,45 +132,6 @@ export class SymbolRegistry implements ISymbolRegistry {
84132
return symbols;
85133
}
86134

87-
reference(meta: ISymbolMeta): Symbol {
88-
const [registered] = this.query(meta);
89-
if (registered) return registered;
90-
91-
const indexKeySpace = this.buildIndexKeySpace(meta);
92-
const cacheKey = this.cacheKeyFromKeySpace(indexKeySpace);
93-
const cachedId = this._stubCache.get(cacheKey);
94-
if (cachedId !== undefined) return this._values.get(cachedId)!;
95-
96-
const stub = new Symbol({ meta, name: '' }, this.nextId);
97-
98-
this._values.set(stub.id, stub);
99-
this._stubs.add(stub.id);
100-
this._stubCache.set(cacheKey, stub.id);
101-
return stub;
102-
}
103-
104-
register(symbol: ISymbolIn): Symbol {
105-
const result = new Symbol(symbol, this.nextId);
106-
107-
this._values.set(result.id, result);
108-
this._registered.add(result.id);
109-
110-
if (result.meta) {
111-
const indexKeySpace = this.buildIndexKeySpace(result.meta);
112-
this.indexSymbol(result.id, indexKeySpace);
113-
this.invalidateCache(indexKeySpace);
114-
this.replaceStubs(result, indexKeySpace);
115-
}
116-
117-
return result;
118-
}
119-
120-
*registered(): IterableIterator<Symbol> {
121-
for (const id of this._registered.values()) {
122-
yield this._values.get(id)!;
123-
}
124-
}
125-
126135
/**
127136
* Derives a stable, order-insensitive cache key from a pre-built key space.
128137
* Avoids rebuilding the key space when it's already available.
@@ -139,15 +148,18 @@ export class SymbolRegistry implements ISymbolRegistry {
139148
* are invalidated, the cache key is evicted in O(1) per entry.
140149
*/
141150
private registerCacheDependencies(cacheKey: QueryCacheKey, indexKeySpace: IndexKeySpace): void {
151+
const serializedEntries: Array<string> = [];
142152
for (const indexEntry of indexKeySpace) {
143153
const serialized = this.serializeIndexEntry(indexEntry);
154+
serializedEntries.push(serialized);
144155
let cacheKeys = this._dependencyToCache.get(serialized);
145156
if (!cacheKeys) {
146157
cacheKeys = new Set();
147158
this._dependencyToCache.set(serialized, cacheKeys);
148159
}
149160
cacheKeys.add(cacheKey);
150161
}
162+
this._cacheDependencies.set(cacheKey, serializedEntries);
151163
}
152164

153165
private buildIndexKeySpace(meta: ISymbolMeta, prefix = ''): IndexKeySpace {
@@ -180,6 +192,17 @@ export class SymbolRegistry implements ISymbolRegistry {
180192
if (!cacheKeys) continue;
181193
for (const cacheKey of cacheKeys) {
182194
this._queryCache.delete(cacheKey);
195+
// Clean up stale reverse-index references so _dependencyToCache doesn't
196+
// accumulate orphaned entries for evicted cache keys.
197+
const deps = this._cacheDependencies.get(cacheKey);
198+
if (deps) {
199+
for (const dep of deps) {
200+
if (dep !== serialized) {
201+
this._dependencyToCache.get(dep)?.delete(cacheKey);
202+
}
203+
}
204+
this._cacheDependencies.delete(cacheKey);
205+
}
183206
}
184207
this._dependencyToCache.delete(serialized);
185208
}

packages/shared/src/graph/walk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ const walkTopological: WalkFn = (graph, callback, options) => {
115115
}
116116

117117
// emit remaining nodes (cycles) in declaration order
118-
const remaining = pointers.filter((pointer) => !emitted.has(pointer));
118+
const remaining = pointers.filter((pointer) => !emitted.has(pointer));
119119
remaining.sort((a, b) => declIndex.get(a)! - declIndex.get(b)!);
120120
for (const pointer of remaining) {
121121
emitted.add(pointer);

0 commit comments

Comments
 (0)