Convert cache to Disposable

This commit is contained in:
Daniel Imms
2026-05-02 05:48:08 -07:00
parent 6287c6ddfe
commit 3dbfa7ce3e
3 changed files with 64 additions and 42 deletions
+43 -23
View File
@@ -1182,32 +1182,52 @@ describe('Buffer', () => {
});
describe('line string cache cleanup', () => {
it('should clear shared cache entries with a single timer', async () => {
buffer.fillViewportRows();
buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 1));
buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 1));
it('should clear shared cache entries with a single timer', () => {
const originalSetTimeout = globalThis.setTimeout;
const originalClearTimeout = globalThis.clearTimeout;
let timeoutId = 0;
const scheduledTimeouts = new Map<number, () => void>();
(globalThis as any).setTimeout = ((handler: (...args: any[]) => void) => {
const id = ++timeoutId;
scheduledTimeouts.set(id, () => handler());
return id as ReturnType<typeof setTimeout>;
}) as typeof setTimeout;
(globalThis as any).clearTimeout = ((id: ReturnType<typeof setTimeout>) => {
scheduledTimeouts.delete(id as unknown as number);
}) as typeof clearTimeout;
try {
buffer.fillViewportRows();
buffer.lines.get(0)!.setCell(0, createCellData(0, 'a', 1));
buffer.lines.get(1)!.setCell(0, createCellData(0, 'b', 1));
assert.equal(buffer.translateBufferLineToString(0, false), `a${' '.repeat(INIT_COLS - 1)}`);
assert.equal(buffer.translateBufferLineToString(1, false), `b${' '.repeat(INIT_COLS - 1)}`);
assert.equal(buffer.translateBufferLineToString(0, false), `a${' '.repeat(INIT_COLS - 1)}`);
assert.equal(buffer.translateBufferLineToString(1, false), `b${' '.repeat(INIT_COLS - 1)}`);
const cache = (buffer as any)._stringCache;
assert.equal(cache.entries.size, 2);
const previousTimer = (cache as any)._clearTimeout;
assert.notEqual(previousTimer, undefined);
const cache = (buffer as any)._stringCache;
assert.equal(cache.entries.size, 2);
const previousTimer = (cache as any)._clearTimeout.value;
assert.notEqual(previousTimer, undefined);
// Another translation should refresh the same shared timer, not create a second queue.
assert.equal(buffer.translateBufferLineToString(0, false), `a${' '.repeat(INIT_COLS - 1)}`);
assert.notEqual((cache as any)._clearTimeout, undefined);
assert.notEqual((cache as any)._clearTimeout, previousTimer);
// Another translation should refresh the same shared timer, not create a second queue.
assert.equal(buffer.translateBufferLineToString(0, false), `a${' '.repeat(INIT_COLS - 1)}`);
assert.notEqual((cache as any)._clearTimeout.value, undefined);
assert.notEqual((cache as any)._clearTimeout.value, previousTimer);
await new Promise(r => setTimeout(r, 70));
for (const timeout of scheduledTimeouts.values()) {
timeout();
}
scheduledTimeouts.clear();
assert.equal(cache.entries.size, 0);
assert.equal((cache as any)._clearTimeout, undefined);
assert.equal(cache.entries.size, 0);
assert.equal((cache as any)._clearTimeout.value, undefined);
// Cache entries should be recreated lazily after a timer-based reset.
assert.equal(buffer.translateBufferLineToString(0, false), `a${' '.repeat(INIT_COLS - 1)}`);
assert.equal(cache.entries.size, 1);
// Cache entries should be recreated lazily after a timer-based reset.
assert.equal(buffer.translateBufferLineToString(0, false), `a${' '.repeat(INIT_COLS - 1)}`);
assert.equal(cache.entries.size, 1);
} finally {
globalThis.setTimeout = originalSetTimeout;
globalThis.clearTimeout = originalClearTimeout;
}
});
it('should reset line string cache state on clear and resize', () => {
@@ -1217,11 +1237,11 @@ describe('Buffer', () => {
const cache = (buffer as any)._stringCache;
assert.equal(cache.entries.size, 1);
assert.notEqual((cache as any)._clearTimeout, undefined);
assert.notEqual((cache as any)._clearTimeout.value, undefined);
buffer.clear();
assert.equal(cache.entries.size, 0);
assert.equal((cache as any)._clearTimeout, undefined);
assert.equal((cache as any)._clearTimeout.value, undefined);
buffer.fillViewportRows();
buffer.lines.get(0)!.setCell(0, createCellData(0, 'b', 1));
@@ -1230,7 +1250,7 @@ describe('Buffer', () => {
buffer.resize(INIT_COLS - 1, INIT_ROWS);
assert.equal(cache.entries.size, 0);
assert.equal((cache as any)._clearTimeout, undefined);
assert.equal((cache as any)._clearTimeout.value, undefined);
});
});
+15 -13
View File
@@ -5,14 +5,22 @@
import type { IBufferLineStringCache, IBufferLineStringCacheEntry } from 'common/buffer/BufferLine';
import { disposableTimeout } from 'common/Async';
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import type { IDisposable } from 'common/Lifecycle';
const STRING_CACHE_CLEAR_DELAY_MS = 50;
const enum Constants {
CacheTtlMs = 15000
}
export class BufferLineStringCache implements IBufferLineStringCache, IDisposable {
export class BufferLineStringCache extends Disposable implements IBufferLineStringCache {
public generation: number = 0;
public readonly entries: Set<IBufferLineStringCacheEntry> = new Set();
private _clearTimeout: IDisposable | undefined;
private readonly _clearTimeout = this._register(new MutableDisposable<IDisposable>());
constructor() {
super();
this._register(toDisposable(() => this.entries.clear()));
}
public touch(): void {
this._scheduleClear();
@@ -30,8 +38,7 @@ export class BufferLineStringCache implements IBufferLineStringCache, IDisposabl
}
public clear(): void {
this._clearTimeout?.dispose();
this._clearTimeout = undefined;
this._clearTimeout.clear();
this.generation++;
for (const entry of this.entries) {
entry.value = undefined;
@@ -40,15 +47,10 @@ export class BufferLineStringCache implements IBufferLineStringCache, IDisposabl
this.entries.clear();
}
public dispose(): void {
this.clear();
}
private _scheduleClear(): void {
this._clearTimeout?.dispose();
this._clearTimeout = disposableTimeout(() => {
this._clearTimeout = undefined;
this._clearTimeout.value = disposableTimeout(() => {
this._clearTimeout.clear();
this.clear();
}, STRING_CACHE_CLEAR_DELAY_MS);
}, Constants.CacheTtlMs);
}
}
+6 -6
View File
@@ -91,13 +91,13 @@ describe('BufferSet', () => {
const oldCache = oldNormal._stringCache;
assert.equal(oldCache.entries.size, 1);
assert.notEqual(oldCache._clearTimeout, undefined);
assert.notEqual(oldCache._clearTimeout.value, undefined);
bufferSet.reset();
assert.notEqual(bufferSet.normal, oldNormal);
assert.equal(oldCache.entries.size, 0);
assert.equal(oldCache._clearTimeout, undefined);
assert.equal(oldCache._clearTimeout.value, undefined);
});
it('should dispose both buffers when disposed', () => {
@@ -112,15 +112,15 @@ describe('BufferSet', () => {
const normalCache = normal._stringCache;
const altCache = alt._stringCache;
assert.notEqual(normalCache._clearTimeout, undefined);
assert.notEqual(altCache._clearTimeout, undefined);
assert.notEqual(normalCache._clearTimeout.value, undefined);
assert.notEqual(altCache._clearTimeout.value, undefined);
bufferSet.dispose();
assert.equal(normalCache.entries.size, 0);
assert.equal(altCache.entries.size, 0);
assert.equal(normalCache._clearTimeout, undefined);
assert.equal(altCache._clearTimeout, undefined);
assert.equal(normalCache._clearTimeout.value, undefined);
assert.equal(altCache._clearTimeout.value, undefined);
});
});
});