1
0

Hopefully finally squashed the race condition that would randomly reload files

This commit is contained in:
Zef Hemel 2023-11-20 17:08:29 +01:00
parent d388796b1c
commit 79611a27e0
2 changed files with 104 additions and 68 deletions

View File

@ -12,7 +12,9 @@ import type { SpacePrimitives } from "./space_primitives.ts";
* - page:deleted (string) * - page:deleted (string)
*/ */
export class EventedSpacePrimitives implements SpacePrimitives { export class EventedSpacePrimitives implements SpacePrimitives {
// Avoid file listing while performing another fetch (read, write, meta) operation
alreadyFetching = false; alreadyFetching = false;
initialFileListLoad = true; initialFileListLoad = true;
spaceSnapshot: Record<string, number> = {}; spaceSnapshot: Record<string, number> = {};
@ -28,55 +30,73 @@ export class EventedSpacePrimitives implements SpacePrimitives {
async fetchFileList(): Promise<FileMeta[]> { async fetchFileList(): Promise<FileMeta[]> {
const newFileList = await this.wrapped.fetchFileList(); const newFileList = await this.wrapped.fetchFileList();
if (this.alreadyFetching) { if (this.alreadyFetching) {
// Avoid race conditions // Avoid race conditions, let's just skip this in terms of event triggering: that's ok
return newFileList; return newFileList;
} }
this.alreadyFetching = true; try {
const deletedFiles = new Set<string>(Object.keys(this.spaceSnapshot)); this.alreadyFetching = true;
for (const meta of newFileList) { const deletedFiles = new Set<string>(Object.keys(this.spaceSnapshot));
const oldHash = this.spaceSnapshot[meta.name]; for (const meta of newFileList) {
const newHash = meta.lastModified; const oldHash = this.spaceSnapshot[meta.name];
if ( const newHash = meta.lastModified;
( // Update in snapshot
// New file scenario this.spaceSnapshot[meta.name] = newHash;
!oldHash && !this.initialFileListLoad
) || ( // Check what happened to the file
// Changed file scenario if (
oldHash && (
oldHash !== newHash // New file scenario
) !oldHash && !this.initialFileListLoad
) { ) || (
await this.dispatchEvent("file:changed", meta.name); // 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 for (const deletedFile of deletedFiles) {
this.spaceSnapshot[meta.name] = newHash; delete this.spaceSnapshot[deletedFile];
} await this.dispatchEvent("file:deleted", deletedFile);
for (const deletedFile of deletedFiles) { if (deletedFile.endsWith(".md")) {
delete this.spaceSnapshot[deletedFile]; const pageName = deletedFile.substring(0, deletedFile.length - 3);
await this.dispatchEvent("file:deleted", deletedFile); 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); await this.dispatchEvent("file:listed", newFileList);
this.alreadyFetching = false; this.initialFileListLoad = false;
this.initialFileListLoad = false; return newFileList;
return newFileList; } finally {
this.alreadyFetching = false;
}
} }
async readFile( async readFile(
name: string, name: string,
): Promise<{ data: Uint8Array; meta: FileMeta }> { ): Promise<{ data: Uint8Array; meta: FileMeta }> {
const data = await this.wrapped.readFile(name); try {
this.triggerEventsAndCache(name, data.meta.lastModified); // Fetching mutex
return data; 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( async writeFile(
@ -85,31 +105,36 @@ export class EventedSpacePrimitives implements SpacePrimitives {
selfUpdate?: boolean, selfUpdate?: boolean,
meta?: FileMeta, meta?: FileMeta,
): Promise<FileMeta> { ): Promise<FileMeta> {
const newMeta = await this.wrapped.writeFile( try {
name, this.alreadyFetching = true;
data, const newMeta = await this.wrapped.writeFile(
selfUpdate, name,
meta, data,
); selfUpdate,
if (!selfUpdate) { meta,
await this.dispatchEvent("file:changed", name, true); );
} if (!selfUpdate) {
this.spaceSnapshot[name] = newMeta.lastModified; await this.dispatchEvent("file:changed", name, true);
}
this.spaceSnapshot[name] = newMeta.lastModified;
if (name.endsWith(".md")) { if (name.endsWith(".md")) {
// Let's trigger some page-specific events // Let's trigger some page-specific events
const pageName = name.substring(0, name.length - 3); const pageName = name.substring(0, name.length - 3);
let text = ""; let text = "";
const decoder = new TextDecoder("utf-8"); const decoder = new TextDecoder("utf-8");
text = decoder.decode(data); text = decoder.decode(data);
await this.dispatchEvent("page:saved", pageName, newMeta); await this.dispatchEvent("page:saved", pageName, newMeta);
await this.dispatchEvent("page:index_text", { await this.dispatchEvent("page:index_text", {
name: pageName, name: pageName,
text, text,
}); });
}
return newMeta;
} finally {
this.alreadyFetching = false;
} }
return newMeta;
} }
triggerEventsAndCache(name: string, newHash: number) { triggerEventsAndCache(name: string, newHash: number) {
@ -124,6 +149,7 @@ export class EventedSpacePrimitives implements SpacePrimitives {
async getFileMeta(name: string): Promise<FileMeta> { async getFileMeta(name: string): Promise<FileMeta> {
try { try {
this.alreadyFetching = true;
const newMeta = await this.wrapped.getFileMeta(name); const newMeta = await this.wrapped.getFileMeta(name);
this.triggerEventsAndCache(name, newMeta.lastModified); this.triggerEventsAndCache(name, newMeta.lastModified);
return newMeta; return newMeta;
@ -137,17 +163,24 @@ export class EventedSpacePrimitives implements SpacePrimitives {
} }
} }
throw e; throw e;
} finally {
this.alreadyFetching = false;
} }
} }
async deleteFile(name: string): Promise<void> { async deleteFile(name: string): Promise<void> {
if (name.endsWith(".md")) { try {
const pageName = name.substring(0, name.length - 3); this.alreadyFetching = true;
await this.dispatchEvent("page:deleted", pageName); 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);
} }
} }

View File

@ -53,8 +53,11 @@ export class PlugSpacePrimitives implements SpacePrimitives {
for (const pm of await plug.invoke(name, [])) { for (const pm of await plug.invoke(name, [])) {
allFiles.push(pm); allFiles.push(pm);
} }
} catch (e) { } catch (e: any) {
console.error("Error listing files", e); if (!e.message.includes("not available")) {
// Don't report "not available in" environments errors
console.error("Error listing files", e);
}
} }
} }
} }