From 733b6a653efe23d627074dd4bc2d4e57a8776129 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 14 Sep 2025 01:59:44 -0400 Subject: [PATCH] perf: further readdir improvements --- src/backend/src/filesystem/ECMAP.js | 3 +- src/backend/src/filesystem/FSNodeContext.js | 12 ++++-- .../filesystem/hl_operations/hl_readdir.js | 13 ++++++- src/backend/src/helpers.js | 37 ++++++++++--------- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/backend/src/filesystem/ECMAP.js b/src/backend/src/filesystem/ECMAP.js index f6c9150f6..466cadcff 100644 --- a/src/backend/src/filesystem/ECMAP.js +++ b/src/backend/src/filesystem/ECMAP.js @@ -91,8 +91,9 @@ class ECMAP { context = context.sub({ [this.SYMBOL]: new this(), }); + return await context.arun(cb); } - return await context.arun(cb); + return await cb(); } } diff --git a/src/backend/src/filesystem/FSNodeContext.js b/src/backend/src/filesystem/FSNodeContext.js index a8227479e..38df8973b 100644 --- a/src/backend/src/filesystem/FSNodeContext.js +++ b/src/backend/src/filesystem/FSNodeContext.js @@ -256,8 +256,8 @@ module.exports = class FSNodeContext { return components.length; } - async exists (fetch_options = {}) { - await this.fetchEntry(); + async exists ({ fetch_options } = {}) { + await this.fetchEntry(fetch_options); if ( ! this.found ) { this.log.debug( 'here\'s why it doesn\'t exist: ' + @@ -289,7 +289,13 @@ module.exports = class FSNodeContext { * @void */ async fetchEntry (fetch_entry_options = {}) { - if ( this.fetching !== null ) await this.fetching; + if ( this.fetching !== null ) { + await Context.get('services').get('traceService').spanify(async () => { + // ???: does this need to be double-checked? I'm not actually sure... + if ( this.fetching === null ) return; + await this.fetching; + }); + } this.fetching = new putility.libs.promise.TeePromise(); if ( diff --git a/src/backend/src/filesystem/hl_operations/hl_readdir.js b/src/backend/src/filesystem/hl_operations/hl_readdir.js index 88cf23a06..c325767a9 100644 --- a/src/backend/src/filesystem/hl_operations/hl_readdir.js +++ b/src/backend/src/filesystem/hl_operations/hl_readdir.js @@ -17,6 +17,7 @@ * along with this program. If not, see . */ const APIError = require("../../api/APIError"); +const { Context } = require("../../util/context"); const { stream_to_buffer } = require("../../util/streamutil"); const { ECMAP } = require("../ECMAP"); const { TYPE_DIRECTORY, TYPE_SYMLINK } = require("../FSNodeContext"); @@ -29,6 +30,8 @@ class HLReadDir extends HLFilesystemOperation { static CONCERN = 'filesystem'; async _run() { return ECMAP.arun(async () => { + const ecmap = Context.get(ECMAP.SYMBOL); + ecmap.store_fsNodeContext(this.values.subject); return await this.__run(); }); } @@ -83,12 +86,18 @@ class HLReadDir extends HLFilesystemOperation { } return Promise.all(children.map(async child => { - // await child.fetchAll(null, user); + // When thumbnails are requested, fetching before the call to + // .getSafeEntry prevents .fetchEntry (possibly called by + // .fetchSuggestedApps or .fetchSubdomains) + if ( ! no_thumbs ) { + await child.fetchEntry({ thumbnail: true }); + } + if ( ! no_assocs ) { await child.fetchSuggestedApps(user); await child.fetchSubdomains(user); } - const entry = await child.getSafeEntry({ thumbnail: ! no_thumbs }); + const entry = await child.getSafeEntry(); if ( ! no_thumbs && entry.associated_app ) { const svc_appIcon = this.context.get('services').get('app-icon'); const icon_result = await svc_appIcon.get_icon_stream({ diff --git a/src/backend/src/helpers.js b/src/backend/src/helpers.js index e5ca9d1cc..6cacc773a 100644 --- a/src/backend/src/helpers.js +++ b/src/backend/src/helpers.js @@ -1315,7 +1315,7 @@ function seconds_to_string(seconds) { async function suggest_app_for_fsentry(fsentry, options){ const svc_performanceMonitor = services.get('performance-monitor'); const monitor = svc_performanceMonitor.createContext("suggest_app_for_fsentry"); - const suggested_apps = []; + const suggested_apps_promises = []; let content_type = mime.contentType(fsentry.name); if( ! content_type ) content_type = ''; @@ -1400,8 +1400,8 @@ async function suggest_app_for_fsentry(fsentry, options){ ]; if ( any_of(exts_code, fsname) || !fsname.includes('.') ) { - suggested_apps.push(await get_app({name: 'code'})) - suggested_apps.push(await get_app({name: 'editor'})) + suggested_apps_promises.push(get_app({name: 'code'})) + suggested_apps_promises.push(get_app({name: 'editor'})) } //--------------------------------------------- @@ -1412,14 +1412,14 @@ async function suggest_app_for_fsentry(fsentry, options){ // files with no extension !fsname.includes('.') ){ - suggested_apps.push(await get_app({name: 'editor'})) - suggested_apps.push(await get_app({name: 'code'})) + suggested_apps_promises.push(get_app({name: 'editor'})) + suggested_apps_promises.push(get_app({name: 'code'})) } //--------------------------------------------- // Markus //--------------------------------------------- if(fsname.endsWith('.md')){ - suggested_apps.push(await get_app({name: 'markus'})) + suggested_apps_promises.push(get_app({name: 'markus'})) } //--------------------------------------------- // Viewer @@ -1432,7 +1432,7 @@ async function suggest_app_for_fsentry(fsentry, options){ fsname.endsWith('.bmp') || fsname.endsWith('.jpeg') ){ - suggested_apps.push(await get_app({name: 'viewer'})); + suggested_apps_promises.push(get_app({name: 'viewer'})); } //--------------------------------------------- // Draw @@ -1441,13 +1441,13 @@ async function suggest_app_for_fsentry(fsentry, options){ fsname.endsWith('.bmp') || content_type.startsWith('image/') ){ - suggested_apps.push(await get_app({name: 'draw'})); + suggested_apps_promises.push(get_app({name: 'draw'})); } //--------------------------------------------- // PDF //--------------------------------------------- if(fsname.endsWith('.pdf')){ - suggested_apps.push(await get_app({name: 'pdf'})); + suggested_apps_promises.push(get_app({name: 'pdf'})); } //--------------------------------------------- // Player @@ -1461,7 +1461,7 @@ async function suggest_app_for_fsentry(fsentry, options){ fsname.endsWith('.m4a') || fsname.endsWith('.ogg') ){ - suggested_apps.push(await get_app({name: 'player'})); + suggested_apps_promises.push(get_app({name: 'player'})); } //--------------------------------------------- @@ -1471,18 +1471,21 @@ async function suggest_app_for_fsentry(fsentry, options){ monitor.label("third party associations"); for ( const app_id of apps ) { - // retrieve app from DB - const third_party_app = await get_app({id: app_id}) - if ( ! third_party_app ) continue; - // only add if the app is approved for opening items or the app is owned by this user - if( third_party_app.approved_for_opening_items || - (options !== undefined && options.user !== undefined && options.user.id === third_party_app.owner_user_id)) - suggested_apps.push(third_party_app) + this.suggested_apps_promises.push((async () => { + // retrieve app from DB + const third_party_app = await get_app({id: app_id}) + if ( ! third_party_app ) return; + // only add if the app is approved for opening items or the app is owned by this user + if( third_party_app.approved_for_opening_items || + (options !== undefined && options.user !== undefined && options.user.id === third_party_app.owner_user_id)) + return third_party_app; + })()); } monitor.stamp(); monitor.end(); // return list + const suggested_apps = await Promise.all(suggested_apps_promises); return suggested_apps.filter((suggested_app, pos, self) => { // Remove any null values caused by calling `get_app()` for apps that don't exist. // This happens on self-host because we don't include `code`, among others.