From 79611a27e030b4227c43b81aefec3094b309f718 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Mon, 20 Nov 2023 17:08:29 +0100 Subject: [PATCH] Hopefully finally squashed the race condition that would randomly reload files --- common/spaces/evented_space_primitives.ts | 165 +++++++++++++--------- common/spaces/plug_space_primitives.ts | 7 +- 2 files changed, 104 insertions(+), 68 deletions(-) diff --git a/common/spaces/evented_space_primitives.ts b/common/spaces/evented_space_primitives.ts index def2a0e..216e53f 100644 --- a/common/spaces/evented_space_primitives.ts +++ b/common/spaces/evented_space_primitives.ts @@ -12,7 +12,9 @@ import type { SpacePrimitives } from "./space_primitives.ts"; * - page:deleted (string) */ export class EventedSpacePrimitives implements SpacePrimitives { + // Avoid file listing while performing another fetch (read, write, meta) operation alreadyFetching = false; + initialFileListLoad = true; spaceSnapshot: Record = {}; @@ -28,55 +30,73 @@ export class EventedSpacePrimitives implements SpacePrimitives { async fetchFileList(): Promise { const newFileList = await this.wrapped.fetchFileList(); if (this.alreadyFetching) { - // Avoid race conditions + // Avoid race conditions, let's just skip this in terms of event triggering: that's ok return newFileList; } - this.alreadyFetching = true; - const deletedFiles = new Set(Object.keys(this.spaceSnapshot)); - for (const meta of newFileList) { - const oldHash = this.spaceSnapshot[meta.name]; - const newHash = meta.lastModified; - if ( - ( - // New file scenario - !oldHash && !this.initialFileListLoad - ) || ( - // Changed file scenario - oldHash && - oldHash !== newHash - ) - ) { - await this.dispatchEvent("file:changed", meta.name); + try { + this.alreadyFetching = true; + const deletedFiles = new Set(Object.keys(this.spaceSnapshot)); + for (const meta of newFileList) { + const oldHash = this.spaceSnapshot[meta.name]; + const newHash = meta.lastModified; + // Update in snapshot + this.spaceSnapshot[meta.name] = newHash; + + // Check what happened to the file + if ( + ( + // New file scenario + !oldHash && !this.initialFileListLoad + ) || ( + // Changed file scenario + oldHash && + oldHash !== newHash + ) + ) { + await this.dispatchEvent( + "file:changed", + meta.name, + false, + oldHash, + newHash, + ); + } + // Page found, not deleted + deletedFiles.delete(meta.name); } - // Page found, not deleted - deletedFiles.delete(meta.name); - // Update in snapshot - this.spaceSnapshot[meta.name] = newHash; - } + for (const deletedFile of deletedFiles) { + delete this.spaceSnapshot[deletedFile]; + await this.dispatchEvent("file:deleted", deletedFile); - for (const deletedFile of deletedFiles) { - delete this.spaceSnapshot[deletedFile]; - await this.dispatchEvent("file:deleted", deletedFile); - - if (deletedFile.endsWith(".md")) { - const pageName = deletedFile.substring(0, deletedFile.length - 3); - await this.dispatchEvent("page:deleted", pageName); + if (deletedFile.endsWith(".md")) { + const pageName = deletedFile.substring(0, deletedFile.length - 3); + await this.dispatchEvent("page:deleted", pageName); + } } - } - await this.dispatchEvent("file:listed", newFileList); - this.alreadyFetching = false; - this.initialFileListLoad = false; - return newFileList; + await this.dispatchEvent("file:listed", newFileList); + this.initialFileListLoad = false; + return newFileList; + } finally { + this.alreadyFetching = false; + } } async readFile( name: string, ): Promise<{ data: Uint8Array; meta: FileMeta }> { - const data = await this.wrapped.readFile(name); - this.triggerEventsAndCache(name, data.meta.lastModified); - return data; + try { + // Fetching mutex + this.alreadyFetching = true; + + // Fetch file + const data = await this.wrapped.readFile(name); + this.triggerEventsAndCache(name, data.meta.lastModified); + return data; + } finally { + this.alreadyFetching = false; + } } async writeFile( @@ -85,31 +105,36 @@ export class EventedSpacePrimitives implements SpacePrimitives { selfUpdate?: boolean, meta?: FileMeta, ): Promise { - const newMeta = await this.wrapped.writeFile( - name, - data, - selfUpdate, - meta, - ); - if (!selfUpdate) { - await this.dispatchEvent("file:changed", name, true); - } - this.spaceSnapshot[name] = newMeta.lastModified; + try { + this.alreadyFetching = true; + const newMeta = await this.wrapped.writeFile( + name, + data, + selfUpdate, + meta, + ); + if (!selfUpdate) { + await this.dispatchEvent("file:changed", name, true); + } + this.spaceSnapshot[name] = newMeta.lastModified; - if (name.endsWith(".md")) { - // Let's trigger some page-specific events - const pageName = name.substring(0, name.length - 3); - let text = ""; - const decoder = new TextDecoder("utf-8"); - text = decoder.decode(data); + if (name.endsWith(".md")) { + // Let's trigger some page-specific events + const pageName = name.substring(0, name.length - 3); + let text = ""; + const decoder = new TextDecoder("utf-8"); + text = decoder.decode(data); - await this.dispatchEvent("page:saved", pageName, newMeta); - await this.dispatchEvent("page:index_text", { - name: pageName, - text, - }); + await this.dispatchEvent("page:saved", pageName, newMeta); + await this.dispatchEvent("page:index_text", { + name: pageName, + text, + }); + } + return newMeta; + } finally { + this.alreadyFetching = false; } - return newMeta; } triggerEventsAndCache(name: string, newHash: number) { @@ -124,6 +149,7 @@ export class EventedSpacePrimitives implements SpacePrimitives { async getFileMeta(name: string): Promise { try { + this.alreadyFetching = true; const newMeta = await this.wrapped.getFileMeta(name); this.triggerEventsAndCache(name, newMeta.lastModified); return newMeta; @@ -137,17 +163,24 @@ export class EventedSpacePrimitives implements SpacePrimitives { } } throw e; + } finally { + this.alreadyFetching = false; } } async deleteFile(name: string): Promise { - if (name.endsWith(".md")) { - const pageName = name.substring(0, name.length - 3); - await this.dispatchEvent("page:deleted", pageName); + try { + this.alreadyFetching = true; + if (name.endsWith(".md")) { + const pageName = name.substring(0, name.length - 3); + await this.dispatchEvent("page:deleted", pageName); + } + // await this.getPageMeta(name); // Check if page exists, if not throws Error + await this.wrapped.deleteFile(name); + delete this.spaceSnapshot[name]; + await this.dispatchEvent("file:deleted", name); + } finally { + this.alreadyFetching = false; } - // await this.getPageMeta(name); // Check if page exists, if not throws Error - await this.wrapped.deleteFile(name); - delete this.spaceSnapshot[name]; - await this.dispatchEvent("file:deleted", name); } } diff --git a/common/spaces/plug_space_primitives.ts b/common/spaces/plug_space_primitives.ts index 343601c..5990f7e 100644 --- a/common/spaces/plug_space_primitives.ts +++ b/common/spaces/plug_space_primitives.ts @@ -53,8 +53,11 @@ export class PlugSpacePrimitives implements SpacePrimitives { for (const pm of await plug.invoke(name, [])) { allFiles.push(pm); } - } catch (e) { - console.error("Error listing files", e); + } catch (e: any) { + if (!e.message.includes("not available")) { + // Don't report "not available in" environments errors + console.error("Error listing files", e); + } } } }