mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-04 08:30:39 +00:00
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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user