SimpleLRU: respect AbortSignal

This commit is contained in:
Alex Gleason 2025-02-09 17:22:53 -06:00
parent a597eae674
commit 16f3a13364
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
2 changed files with 35 additions and 30 deletions

View file

@ -4,7 +4,7 @@ import { assertEquals, assertRejects } from '@std/assert';
Deno.test("SimpleLRU doesn't repeat failed calls", async () => { Deno.test("SimpleLRU doesn't repeat failed calls", async () => {
let calls = 0; let calls = 0;
const cache = new SimpleLRU( using cache = new SimpleLRU(
// deno-lint-ignore require-await // deno-lint-ignore require-await
async () => { async () => {
calls++; calls++;

View file

@ -3,50 +3,55 @@
import { LRUCache } from 'lru-cache'; import { LRUCache } from 'lru-cache';
import { type Gauge } from 'prom-client'; import { type Gauge } from 'prom-client';
type FetchFn<K extends {}, V extends {}, O extends {}> = (key: K, opts: O) => Promise<V>; type FetchFn<K extends {}, V extends {}> = (key: K, opts: { signal?: AbortSignal }) => Promise<V>;
interface FetchFnOpts {
signal?: AbortSignal | null;
}
type SimpleLRUOpts<K extends {}, V extends {}> = LRUCache.Options<K, V, void> & { type SimpleLRUOpts<K extends {}, V extends {}> = LRUCache.Options<K, V, void> & {
gauge?: Gauge; gauge?: Gauge;
errorRefresh?: number;
}; };
export class SimpleLRU< export class SimpleLRU<
K extends {}, K extends {},
V extends {}, V extends {},
O extends {} = FetchFnOpts,
> { > {
protected cache: LRUCache<K, V, void>; protected cache: LRUCache<K, Promise<V>, void>;
private tids = new Set<number>();
constructor(fetchFn: FetchFn<K, V, { signal: AbortSignal }>, private opts: SimpleLRUOpts<K, V>) { constructor(private fetchFn: FetchFn<K, V>, private opts: SimpleLRUOpts<K, Promise<V>>) {
this.cache = new LRUCache({ this.cache = new LRUCache({ ...opts });
async fetchMethod(key, _staleValue, { signal }) {
try {
return await fetchFn(key, { signal: signal as unknown as AbortSignal });
} catch {
return null as unknown as V;
}
},
...opts,
});
} }
async fetch(key: K, opts?: O): Promise<V> { async fetch(key: K, opts?: { signal?: AbortSignal }): Promise<V> {
const result = await this.cache.fetch(key, opts); if (opts?.signal?.aborted) {
throw new DOMException('The signal has been aborted', 'AbortError');
}
const cached = await this.cache.get(key);
if (cached) {
return cached;
}
const promise = this.fetchFn(key, { signal: opts?.signal });
this.cache.set(key, promise);
promise.then(() => {
this.opts.gauge?.set(this.cache.size); this.opts.gauge?.set(this.cache.size);
}).catch(() => {
const tid = setTimeout(() => {
this.cache.delete(key);
this.tids.delete(tid);
}, this.opts.errorRefresh ?? 10_000);
this.tids.add(tid);
});
if (result === undefined || result === null) { return promise;
throw new Error('SimpleLRU: fetch failed');
} }
return result; [Symbol.dispose](): void {
} for (const tid of this.tids) {
clearTimeout(tid);
put(key: K, value: V): Promise<void> { }
this.cache.set(key, value);
return Promise.resolve();
} }
} }