From e3ba7a4e26103ab35303e177a3d9cadd7771dd2c Mon Sep 17 00:00:00 2001 From: joyc-bq Date: Mon, 1 Apr 2024 16:47:29 -0700 Subject: [PATCH] add cleanup --- common/lib/utils/concurrent_map.ts | 151 +++++++++++-------- common/lib/utils/sliding_expiration_cache.ts | 94 ++++++------ 2 files changed, 134 insertions(+), 111 deletions(-) diff --git a/common/lib/utils/concurrent_map.ts b/common/lib/utils/concurrent_map.ts index 74acf171..bee0135b 100644 --- a/common/lib/utils/concurrent_map.ts +++ b/common/lib/utils/concurrent_map.ts @@ -15,78 +15,97 @@ */ export class ConcurrentMap { - protected map: Map = new Map(); - - size() { - return this.map.size; + protected map: Map = new Map(); + + size() { + return this.map.size; + } + + get(key: K, defaultItemValue?: any, itemExpirationNano?: any): V | undefined { + return this.map.get(key); + } + + clear() { + this.map.clear(); + } + + computeIfPresent(key: K, defaultValue: V | null = null, remappingFunc: (key: K, existingValue: V) => V | null): V | null { + const existingValue: V | undefined = this.map.get(key); + if (existingValue == null) { + return null; } - - get(key: K, defaultItemValue?: any, itemExpirationNano?: any): V | undefined { - return this.map.get(key); - } - - clear() { - this.map.clear(); - } - - computeIfPresent(key: K, defaultValue: V | null = null, remappingFunc: (key: K, existingValue: V) => V | null): V | null { - let existingValue: V | undefined = this.map.get(key); - if (existingValue == null) { - return null; - } - let newValue: any = remappingFunc(key, existingValue) - if (newValue != null) { - this.map.set(key, newValue); - return newValue; - } - else { - this.map.delete(key); - return null; - } + const newValue: any = remappingFunc(key, existingValue); + if (newValue != null) { + this.map.set(key, newValue); + return newValue; + } else { + this.map.delete(key); + return null; } - - computeIfAbsent(key: K, mappingFunc: (key: K) => V | null): V | null { - let value: V | undefined = this.map.get(key); - if (value === undefined) { - let newValue: V | null = mappingFunc(key); - if (newValue !== null) { - this.map.set(key, newValue); - return newValue; - } - return null; - } - return value; - } - - putIfAbsent(key: K, newValue: V): V | null { - let existingValue: V | undefined = this.map.get(key) - if (existingValue == null) { + } + + computeIfAbsent(key: K, mappingFunc: (key: K) => V | null): V | null { + const value: V | undefined = this.map.get(key); + if (value === undefined) { + const newValue: V | null = mappingFunc(key); + if (newValue !== null) { this.map.set(key, newValue); return newValue; } - return existingValue; - } - - remove(key: K): boolean { - return this.map.delete(key); + return null; } + return value; + } - removeIf(predicate: (v: any, k: any) => V): boolean { - let originalSize = this.size() - this.map.forEach((v, k) => { - if (!predicate(v, k)) { - this.remove(k) - } - }); - return this.size() < originalSize - } - - get keys() { - return this.map.keys - } - - get values() { - return this.map.values + putIfAbsent(key: K, newValue: V): V | null { + const existingValue: V | undefined = this.map.get(key); + if (existingValue == null) { + this.map.set(key, newValue); + return newValue; } + return existingValue; + } + + remove(key: K): V | undefined { + const value = this.map.get(key); + this.map.delete(key); + return value; + } + + removeIf(predicate: (v: any, k: any) => V): boolean { + const originalSize = this.size(); + this.map.forEach((v, k) => { + if (!predicate(v, k)) { + this.remove(k); + } + }); + return this.size() < originalSize; + } + + removeMatchingValues(removalValues: Array): boolean { + const originalSize = this.size(); + this.map.forEach((v, k) => { + if (removalValues.includes(v)) { + this.remove(k); + } + }); + return this.size() < originalSize; + } + + applyIf(predicate: (k: any, v: any) => V, apply: (k: any, v: any) => V): void { + const originalSize = this.size(); + this.map.forEach((v, k) => { + if (predicate(k, v)) { + apply(k, v); + } + }); + } + + get keys() { + return this.map.keys; + } + + get entries() { + return this.map.entries; } - \ No newline at end of file +} diff --git a/common/lib/utils/sliding_expiration_cache.ts b/common/lib/utils/sliding_expiration_cache.ts index d01f7629..33c79848 100644 --- a/common/lib/utils/sliding_expiration_cache.ts +++ b/common/lib/utils/sliding_expiration_cache.ts @@ -14,7 +14,6 @@ limitations under the License. */ -import { chain } from "lodash"; import { ConcurrentMap } from "./concurrent_map"; class CacheItem { @@ -34,85 +33,90 @@ class CacheItem { return this.expirationTimeNanos; } - updateExpiration(expirationIntervalNanos: bigint): bigint { - this.expirationTimeNanos = process.hrtime.bigint() + expirationIntervalNanos; - return this.expirationTimeNanos + updateExpiration(expirationIntervalNanos: number): CacheItem { + this.expirationTimeNanos = process.hrtime.bigint() + BigInt(expirationIntervalNanos); + return this; } } export class SlidingExpirationCache { - - private _cleanupIntervalNanos: bigint = BigInt(10 * 60 * 1_000_000_000); // 10 minutes - private shouldDisposeFunc: () => Promise; - private itemDisposalFunc: () => Promise; - protected _map: ConcurrentMap> = new ConcurrentMap>; + private _cleanupIntervalNanos: bigint = BigInt(10 * 60 * 1_000_000_000); // 10 minutes + private _shouldDisposeFunc: (item: V) => boolean; + private _itemDisposalFunc: (item: V) => void; + protected _map: ConcurrentMap> = new ConcurrentMap>(); private _cleanupTimeNanos: bigint; - constructor(cleanupIntervalNanos: number, shouldDisposeFunc: () => Promise, itemDisposalFunc: () => Promise) { + constructor(cleanupIntervalNanos: number, shouldDisposeFunc: (item: V) => boolean, itemDisposalFunc: (item: V) => void | null) { this._cleanupIntervalNanos = BigInt(cleanupIntervalNanos); - this.shouldDisposeFunc = shouldDisposeFunc; - this.itemDisposalFunc = itemDisposalFunc; + this._shouldDisposeFunc = shouldDisposeFunc; + this._itemDisposalFunc = itemDisposalFunc; this._cleanupTimeNanos = process.hrtime.bigint() + this._cleanupIntervalNanos; } get size(): number { - return this._map.size() - } - - set cleanupIntervalNs(value: bigint) { - this._cleanupIntervalNanos = value; + return this._map.size(); } - get keys(): IterableIterator{ - return this._map.keys(); + set cleanupIntervalNs(value: number) { + this._cleanupIntervalNanos = BigInt(value); } - get items(): IterableIterator> { - return this._map.values(); + computeIfAbsent(key: K, defaultValue: V | null = null, mappingFunc: (key: K) => V, itemExpirationNanos: number) { + this.cleanUp(); + const cacheItem = this._map.computeIfAbsent(key, (k) => new CacheItem(mappingFunc(k), process.hrtime.bigint() + BigInt(itemExpirationNanos))); + if (cacheItem === null) return null; + else cacheItem.updateExpiration(itemExpirationNanos).item; } get(key: K): V | undefined { this.cleanUp(); - let cacheItem = this._map.get(key) - if (cacheItem != null) return cacheItem?.item; else null; + const cacheItem = this._map.get(key); + if (cacheItem != null) return cacheItem?.item; + else null; } - computeIfAbsent(key: K, defaultValue: V | null = null, mappingFunc: (key: K) => Promise, itemExpirationNanos: bigint) { + remove(key: K): void { + this.removeAndDispose(key); this.cleanUp(); - let cacheItem = this._map.computeIfAbsent(key, () => new CacheItem(mappingFunc(key), process.hrtime.bigint() + itemExpirationNanos)) - return - } - remove(key: K): boolean { - return this._map.remove(key); + removeAndDispose(key: K): void { + const cacheItem = this._map.remove(key); + if (cacheItem != null && this._itemDisposalFunc !== null) { + this._itemDisposalFunc(cacheItem.item); + } } - removeAndDispose(key: K): boolean { - return this._map.remove(key); + removeIfExpired(key: K): void { + const cacheItem = this._map.get(key); + if (cacheItem == null || this.shouldCleanupItem(cacheItem)) { + this.removeAndDispose(key); + } } - removeIfExpired(key: K): boolean { - let cacheItem = this._map.get(key) - if (cacheItem == null || this.shouldDisposeFunc) - return this._map.remove(key); + shouldCleanupItem(cacheItem: CacheItem): boolean { + if (this._shouldDisposeFunc !== null) { + return process.hrtime.bigint() > cacheItem.expirationTimeNs && this._shouldDisposeFunc(cacheItem.item); + } + return process.hrtime.bigint() > cacheItem.expirationTimeNs; } - shouldCleanupItem(cacheItem: CacheItem): boolean { - if (this.shouldDisposeFunc != null) { - return process.hrtime.bigint() > (cacheItem.expirationTimeNs && this.shouldDisposeFunc(cacheItem.item)) + clear(): void { + for (const [key, val] of this._map.entries()) { + if (val !== null && this._itemDisposalFunc != null) { + this._itemDisposalFunc(val.item); + } } - return process.hrtime.bigint() > cacheItem.expirationTimeNs } protected cleanUp() { - let currentTime = process.hrtime.bigint(); - if (this._cleanupTimeNanos < currentTime) { + const currentTime = process.hrtime.bigint(); + if (this._cleanupTimeNanos > currentTime) { return; } this._cleanupTimeNanos = process.hrtime.bigint() + this._cleanupIntervalNanos; - this._map.forEach((v, k) => { - this.removeIfExpired(k); - }); + for (const k of this._map.keys()) { + this.removeIfExpired(k); + } } -} +}