dev: apply TTL cache for stat entries

Adds CachedFilesystem layer to client filesystem chain. Currently only
stat is implemented. The stat implementation will hold onto an entry for
3 seconds as per hardcoded configuration. Eventually, once invalidation
via websockets is working, this TTL should be extended.
This commit is contained in:
KernelDeimos 2024-10-10 19:29:22 -04:00
parent 52d5c4706c
commit 76e64249a6
2 changed files with 129 additions and 1 deletions

View File

@ -0,0 +1,124 @@
import putility from "@heyputer/putility";
import { RWLock } from "@heyputer/putility/src/libs/promise";
import { ProxyFilesystem, TFilesystem } from "./definitions";
export const ROOT_UUID = '00000000-0000-0000-0000-000000000000';
export class CacheFS extends putility.AdvancedBase {
static PROPERTIES = {
// 'internal_uuid' maps a path or external UUID to
// the respective cache entry UUID
// (which for now is the same as the public UUID)
internal_uuid: () => ({}),
entries: () => ({}),
};
get_entry_ei (external_identifier) {
const internal_identifier = this.internal_uuid[external_identifier];
if ( ! internal_identifier ) {
return;
}
return this.entries[internal_identifier];
}
add_entry ({
external_identifiers,
internal_identifier,
}) {
const entry = {
stat_has: {},
stat_exp: 0,
locks: {
stat: new RWLock(),
},
};
for ( const ident of external_identifiers ) {
this.internal_uuid[ident] = internal_identifier;
}
this.entries[internal_identifier] = entry;
console.log('cREATED ENTRY', this.internal_uuid, this.entries);
return entry;
}
}
export class CachedFilesystem extends ProxyFilesystem {
constructor (o) {
super(o);
// this.cacheFS = cacheFS;
this.cacheFS = new CacheFS();
}
static IMPLEMENTS = {
[TFilesystem]: {
stat: async function (o) {
let cent = this.cacheFS.get_entry_ei(o.path ?? o.uid);
const modifiers = [
'subdomains',
'permissions',
'versions',
'size',
];
let values_requested = {};
for ( const mod of modifiers ) {
const optionsKey = 'return' +
mod.charAt(0).toUpperCase() +
mod.slice(1);
if ( ! o[optionsKey] ) continue;
values_requested[mod] = true;
}
const satisfactory_cache = cent => {
for ( const mod of modifiers ) {
if ( ! values_requested[mod] ) continue;
if ( ! cent.stat_has[mod] ) {
return false;
}
}
return true;
}
let cached_stat;
if ( cent && cent.stat && cent.stat_exp > Date.now() ) {
const l = await cent.locks.stat.rlock();
if ( satisfactory_cache(cent) ) {
cached_stat = cent.stat;
}
l.unlock();
}
if ( cached_stat ) {
console.log('CACHE HIT');
return cached_stat;
}
console.log('CACHE MISS');
let l;
if ( cent ) {
l = await cent.locks.stat.wlock();
}
console.log('DOING THE STAT', o);
const entry = await this.delegate.stat(o);
if ( ! cent ) {
cent = this.cacheFS.add_entry({
external_identifiers: [entry.path, entry.uid],
internal_identifier: entry.uid,
});
l = await cent.locks.stat.wlock();
}
cent.stat = entry;
cent.stat_has = { ...values_requested };
// TODO: increase cache TTL once invalidation works
cent.stat_exp = Date.now() + 1000*3;
l.unlock();
console.log('RETRUNING THE ENTRY', entry);
return entry;
}
}
}
}

View File

@ -15,6 +15,7 @@ import sign from "./operations/sign.js";
import deleteFSEntry from "./operations/deleteFSEntry.js";
import { ProxyFilesystem, PuterAPIFilesystem, TFilesystem } from './definitions.js';
import { AdvancedBase } from '../../../../putility/index.js';
import { CachedFilesystem } from './CacheFS.js';
export class PuterJSFileSystemModule extends AdvancedBase {
@ -34,12 +35,14 @@ export class PuterJSFileSystemModule extends AdvancedBase {
static NARI_METHODS = {
stat: {
positional: ['path'],
fn (parameters) {
firstarg_options: true,
async fn (parameters) {
return this.filesystem.stat(parameters);
}
},
readdir: {
positional: ['path'],
firstarg_options: true,
fn (parameters) {
return this.filesystem.readdir(parameters);
}
@ -75,6 +78,7 @@ export class PuterJSFileSystemModule extends AdvancedBase {
// Construct the decorator chain for the client-side filesystem.
let fs = new PuterAPIFilesystem({ api_info }).as(TFilesystem);
fs = new CachedFilesystem({ delegate: fs }).as(TFilesystem);
fs = new ProxyFilesystem({ delegate: fs }).as(TFilesystem);
this.filesystem = fs;
}