perf: execution context memoization map for filesystem

This commit adds an object called ECMAP. This object is a transient
memoization store for use by FSNodeContext.
This commit is contained in:
KernelDeimos
2025-09-12 18:13:41 -04:00
parent 43a0be33ca
commit b3e2b69c4d
5 changed files with 134 additions and 3 deletions
+95
View File
@@ -0,0 +1,95 @@
const { Context } = require("../util/context");
const { NodeUIDSelector, NodePathSelector, NodeInternalIDSelector } = require("./node/selectors");
const LOG_PREFIX = '\x1B[31;1m[[\x1B[33;1mEC\x1B[32;1mMAP\x1B[31;1m]]\x1B[0m';
/**
* The ECMAP class is a memoization structure used by FSNodeContext
* whenever it is present in the execution context (AsyncLocalStorage).
* It is assumed that this object is transient and invalidation of stale
* entries is not necessary.
*
* The name ECMAP simple means Execution Context Map, because the map
* exists in memory at a particular frame of the execution context.
*/
class ECMAP {
static SYMBOL = Symbol('ECMAP');
constructor () {
this.identifier = require('uuid').v4();
// entry caches
this.uuid_to_fsNodeContext = {};
this.path_to_fsNodeContext = {};
this.id_to_fsNodeContext = {};
// identifier association caches
this.path_to_uuid = {};
this.uuid_to_path = {};
}
get logPrefix () {
return `${LOG_PREFIX} \x1B[36[1m${this.identifier}\x1B[0m`;
}
log (...a) {
if ( ! process.env.LOG_ECMAP ) return;
console.log(this.logPrefix, ...a);
}
get_fsNodeContext_from_selector (selector) {
this.log('GET', selector.describe());
const retvalue = (() => {
let value;
if ( selector instanceof NodeUIDSelector ) {
value = this.uuid_to_fsNodeContext[selector.value];
if ( value ) return value;
let maybe_path = this.uuid_to_path[value];
if ( ! maybe_path ) return;
value = this.path_to_fsNodeContext[maybe_path];
if ( value ) return value;
}
else
if ( selector instanceof NodePathSelector ) {
value = this.path_to_fsNodeContext[maybe_path];
if ( value ) return value;
let maybe_uid = this.path_to_uuid[value];
value = this.uuid_to_fsNodeContext[maybe_uid];
if ( value ) return value;
}
})();
if ( retvalue ) {
this.log('\x1B[32;1m <<<<< ECMAP HIT >>>>> \x1B[0m');
} else {
this.log('\x1B[31;1m <<<<< ECMAP MISS >>>>> \x1B[0m');
}
return retvalue;
}
store_fsNodeContext_to_selector (selector, node) {
this.log('STORE', selector.describe());
if ( selector instanceof NodeUIDSelector ) {
this.uuid_to_fsNodeContext[selector.value] = node;
}
if ( selector instanceof NodePathSelector ) {
this.path_to_fsNodeContext[selector.value] = node;
}
if ( selector instanceof NodeInternalIDSelector ) {
this.id_to_fsNodeContext[selector.service+':'+selector.id] = node;
}
}
static async arun (cb) {
let context = Context.get();
if ( ! context.get(this.SYMBOL) ) {
context = context.sub({
[this.SYMBOL]: new this(),
});
}
return await context.arun(cb);
}
}
module.exports = { ECMAP };
@@ -27,6 +27,7 @@ const { NodeRawEntrySelector } = require("./node/selectors");
const { DB_READ } = require("../services/database/consts");
const { UserActorType, AppUnderUserActorType, Actor } = require("../services/auth/Actor");
const { PermissionUtil } = require("../services/auth/PermissionService");
const { ECMAP } = require("./ECMAP");
/**
* Container for information collected about a node
@@ -77,6 +78,19 @@ module.exports = class FSNodeContext {
provider,
fs
}) {
const ecmap = Context.get(ECMAP.SYMBOL);
if ( ecmap ) {
// We might return an existing FSNodeContext
const maybe_node = ecmap?.
get_fsNodeContext_from_selector?.(selector);
if ( maybe_node ) return maybe_node;
} else {
if ( process.env.LOG_ECMAP ) {
console.log('\x1B[31;1m !!! NO ECMAP !!! \x1B[0m');
}
}
this.log = services.get('log-service').create('fsnode-context', {
concern: this.constructor.CONCERN,
});
@@ -128,6 +142,12 @@ module.exports = class FSNodeContext {
for ( const selector of this.selectors_ ) {
if ( selector instanceof new_selector.constructor ) return;
}
const ecmap = Context.get(ECMAP.SYMBOL);
if ( ecmap ) {
ecmap.store_fsNodeContext_to_selector(new_selector, this);
}
this.selectors_.push(new_selector);
this.selector_ = new_selector;
}
@@ -18,6 +18,7 @@
*/
const APIError = require("../../api/APIError");
const { stream_to_buffer } = require("../../util/streamutil");
const { ECMAP } = require("../ECMAP");
const { TYPE_DIRECTORY, TYPE_SYMLINK } = require("../FSNodeContext");
const { LLListUsers } = require("../ll_operations/ll_listusers");
const { LLReadDir } = require("../ll_operations/ll_readdir");
@@ -26,7 +27,12 @@ const { HLFilesystemOperation } = require("./definitions");
class HLReadDir extends HLFilesystemOperation {
static CONCERN = 'filesystem';
async _run () {
async _run() {
return ECMAP.arun(async () => {
return await this.__run();
});
}
async __run () {
const { subject: subject_let, user, no_thumbs, no_assocs, actor } = this.values;
let subject = subject_let;
@@ -18,6 +18,7 @@
*/
const APIError = require("../../api/APIError");
const fsCapabilities = require("../definitions/capabilities");
const { ECMAP } = require("../ECMAP");
const { TYPE_SYMLINK } = require("../FSNodeContext");
const { RootNodeSelector } = require("../node/selectors");
const { NodeUIDSelector, NodeChildSelector } = require("../node/selectors");
@@ -25,7 +26,12 @@ const { LLFilesystemOperation } = require("./definitions");
class LLReadDir extends LLFilesystemOperation {
static CONCERN = 'filesystem';
async _run () {
async _run() {
return ECMAP.arun(async () => {
return await this.__run();
});
}
async __run () {
const { context } = this;
const { subject: subject_let, actor, no_acl } = this.values;
let subject = subject_let;
@@ -19,7 +19,9 @@
*/
const APIError = require("../../api/APIError");
const { ECMAP } = require("../../filesystem/ECMAP");
const { get_user, get_app } = require("../../helpers");
const { Context } = require("../../util/context");
const BaseService = require("../BaseService");
const { DB_WRITE } = require("../database/consts");
const { UserActorType, Actor, AppUnderUserActorType } = require("./Actor");
@@ -354,7 +356,9 @@ class PermissionService extends BaseService {
async scan (actor, permission_options, _reserved, state) {
const svc_trace = this.services.get('traceService');
return await svc_trace.spanify(`permission:scan`, async () => {
return await this.scan_(actor, permission_options, _reserved, state);
return await ECMAP.arun(async () => {
return await this.scan_(actor, permission_options, _reserved, state);
});
}, { attributes: { permission_options }, actor: actor.uid });
}
async scan_ (actor, permission_options, _reserved, state) {