From 6b57e8b1f3716f81dbeebe69d1fa12e0aa219093 Mon Sep 17 00:00:00 2001 From: Phil Kunz Date: Mon, 10 May 2021 14:26:32 +0000 Subject: [PATCH] fix(caching): properly respect ttl for all cache levels --- test/test.ts | 18 +++++++++++- ts/levelcache.abstract.classes.cache.ts | 21 ++++++++++--- ts/levelcache.classes.cache.diskmanager.ts | 14 +++++---- ts/levelcache.classes.cache.memorymanager.ts | 8 ++++- ts/levelcache.classes.cache.s3manager.ts | 13 +++++++- ts/levelcache.classes.cacheentry.ts | 3 ++ ts/levelcache.classes.levelcache.ts | 31 +++++++++++++++----- 7 files changed, 88 insertions(+), 20 deletions(-) diff --git a/test/test.ts b/test/test.ts index aa39b91..a52fbca 100644 --- a/test/test.ts +++ b/test/test.ts @@ -21,7 +21,23 @@ tap.test('should cache a value', async () => { }) ); const result = await testLevelCache.retrieveCacheEntryByKey('mykey'); - console.log(result); + expect(result.contents.toString()).to.equal('heythere'); +}); + +tap.test('should respect ttl', async (tools) => { + await testLevelCache.storeCacheEntryByKey( + 'mykey', + new CacheEntry({ + contents: Buffer.from('heythere'), + ttl: 1000, + typeInfo: 'string', + }) + ); + const result = await testLevelCache.retrieveCacheEntryByKey('mykey'); + expect(result.contents.toString()).to.equal('heythere'); + await tools.delayFor(1100); + const result2 = await testLevelCache.retrieveCacheEntryByKey('mykey'); + expect(result2).to.be.null; }); tap.start(); diff --git a/ts/levelcache.abstract.classes.cache.ts b/ts/levelcache.abstract.classes.cache.ts index 3e5389d..326e88e 100644 --- a/ts/levelcache.abstract.classes.cache.ts +++ b/ts/levelcache.abstract.classes.cache.ts @@ -4,22 +4,35 @@ export abstract class AbstractCache { public abstract ready: Promise; public abstract status: 'active' | 'inactive'; - // Blobs + // Cache Entries /** * store a Blob */ public abstract storeCacheEntryByKey(keyArg: string, valueArg: CacheEntry): Promise; - // Cache Entries /** * retrieve cache entry */ public abstract retrieveCacheEntryByKey(keyArg: string): Promise; + /** + * checks for the presence of a key + * @param keyArg + */ public abstract checkKeyPresence(keyArg: string): Promise; /** - * cleans the cache + * delete a key */ - public abstract clean(): Promise; + public abstract deleteCacheEntryByKey(keyArg: string): Promise; + + /** + * clean the cache + */ + public abstract cleanOutdated(): Promise; + + /** + * cleans the complete cache + */ + public abstract cleanAll(): Promise; } diff --git a/ts/levelcache.classes.cache.diskmanager.ts b/ts/levelcache.classes.cache.diskmanager.ts index c7314d3..052a291 100644 --- a/ts/levelcache.classes.cache.diskmanager.ts +++ b/ts/levelcache.classes.cache.diskmanager.ts @@ -51,14 +51,18 @@ export class CacheDiskManager extends AbstractCache { ); } - public async checkKeyPresence(keyArg): Promise { + public async checkKeyPresence(keyArg: string): Promise { return plugins.smartfile.fs.isFile(plugins.path.join(this.fsPath, encodeURIComponent(keyArg))); } - /** - * cleans the DiskCache directory - */ - public async clean() { + public async deleteCacheEntryByKey(keyArg: string) { + await plugins.smartfile.fs.remove(plugins.path.join(this.fsPath, encodeURIComponent(keyArg))); + } + + + public async cleanOutdated() {} + + public async cleanAll() { if (this.status === 'active') { if (plugins.smartfile.fs.isDirectory(this.fsPath)) { await plugins.smartfile.fs.ensureEmptyDir(this.fsPath); diff --git a/ts/levelcache.classes.cache.memorymanager.ts b/ts/levelcache.classes.cache.memorymanager.ts index ffe29a1..7294a60 100644 --- a/ts/levelcache.classes.cache.memorymanager.ts +++ b/ts/levelcache.classes.cache.memorymanager.ts @@ -38,7 +38,13 @@ export class CacheMemoryManager extends AbstractCache { } } - public async clean() { + public async deleteCacheEntryByKey(keyArg: string) { + this.fastMap.removeFromMap(keyArg); + } + + public async cleanOutdated() {} + + public async cleanAll() { this.fastMap.clean(); } } diff --git a/ts/levelcache.classes.cache.s3manager.ts b/ts/levelcache.classes.cache.s3manager.ts index 486ddc7..a1dab09 100644 --- a/ts/levelcache.classes.cache.s3manager.ts +++ b/ts/levelcache.classes.cache.s3manager.ts @@ -64,7 +64,18 @@ export class CacheS3Manager extends AbstractCache { return false; } - public async clean() { + public async deleteCacheEntryByKey(keyArg: string) { + if(this.status === 'active') { + await this.s3CacheDir.fastRemove(encodeURIComponent(keyArg)); + } + } + + /** + * clean outdated + */ + public async cleanOutdated() {} + + public async cleanAll() { await this.s3CacheDir.deleteWithAllContents(); } } diff --git a/ts/levelcache.classes.cacheentry.ts b/ts/levelcache.classes.cacheentry.ts index b974a4f..ef6fcad 100644 --- a/ts/levelcache.classes.cacheentry.ts +++ b/ts/levelcache.classes.cacheentry.ts @@ -29,6 +29,9 @@ export class CacheEntry @plugins.smartjson.foldDec() contents: Buffer; + @plugins.smartjson.foldDec() + public createdAt: number; + public toStorageJsonString(): string { return this.foldToJson(); } diff --git a/ts/levelcache.classes.levelcache.ts b/ts/levelcache.classes.levelcache.ts index dae91ce..d6cb785 100644 --- a/ts/levelcache.classes.levelcache.ts +++ b/ts/levelcache.classes.levelcache.ts @@ -62,6 +62,7 @@ export class LevelCache extends AbstractCache { public async storeCacheEntryByKey(keyArg: string, cacheEntryArg: CacheEntry): Promise { cacheEntryArg.key = keyArg; const targetCache = await this.cacheRouter.getCacheForStoreAction(keyArg, cacheEntryArg); + cacheEntryArg.createdAt = Date.now(); await targetCache.storeCacheEntryByKey(keyArg, cacheEntryArg); } @@ -73,6 +74,10 @@ export class LevelCache extends AbstractCache { const targetCache = await this.cacheRouter.getCacheForRetrieveAction(keyArg); if (targetCache) { const cacheEntry = await targetCache.retrieveCacheEntryByKey(keyArg); + if (cacheEntry.createdAt + cacheEntry.ttl < Date.now()) { + await this.deleteCacheEntryByKey(keyArg).catch(); + return null; + } return cacheEntry; } else { return null; @@ -87,15 +92,25 @@ export class LevelCache extends AbstractCache { ]); } - // cache maintenance - /** - * cleans the cache - */ - public async clean(): Promise { + public async deleteCacheEntryByKey(keyArg) { await Promise.all([ - this.cacheDiskManager.clean(), - this.cacheDiskManager.clean(), - this.cacheS3Manager.clean(), + this.cacheMemoryManager.deleteCacheEntryByKey(keyArg), + this.cacheDiskManager.deleteCacheEntryByKey(keyArg), + this.cacheS3Manager.deleteCacheEntryByKey(keyArg), + ]); + } + + // cache maintenance + public async cleanOutdated() {} + + /** + * cleans the complete cache + */ + public async cleanAll(): Promise { + await Promise.all([ + this.cacheDiskManager.cleanAll(), + this.cacheDiskManager.cleanAll(), + this.cacheS3Manager.cleanAll(), ]); } }