mirror of
https://github.com/vladmandic/automatic
synced 2026-09-18 16:54:33 +02:00
Remove need for timeout failsafe
All the failure routes should either: - Get far enough along to not leave checked values unable to reach the finished state... - or all be tied into the same AbortController so everything aborts together. ... I think...
This commit is contained in:
+36
-31
@@ -23,10 +23,14 @@ const el = {
|
||||
|
||||
const SUPPORTED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'webp', 'tiff', 'jp2', 'jxl', 'gif', 'mp4', 'mkv', 'avi', 'mjpeg', 'mpg', 'avr'];
|
||||
|
||||
async function awaitForIDB(num = 0, signal = null) {
|
||||
const timeout = AbortSignal.timeout(180000); // Failsafe to ensure no memory leaks
|
||||
const combinedSignals = signal ? AbortSignal.any([timeout, signal]) : timeout;
|
||||
while (outstanding > num && !combinedSignals.aborted) await new Promise((resolve) => { setTimeout(resolve, 50); });
|
||||
/**
|
||||
* Wait for the `outstanding` variable to be below the specified value
|
||||
* @param {number} num - Threshold for `outstanding`
|
||||
* @param {AbortSignal} signal - AbortController signal
|
||||
*/
|
||||
async function awaitForOutstanding(num, signal) {
|
||||
while (outstanding > num && !signal.aborted) await new Promise((resolve) => { setTimeout(resolve, 50); });
|
||||
signal.throwIfAborted();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,9 +39,8 @@ async function awaitForIDB(num = 0, signal = null) {
|
||||
* @param {AbortSignal} signal - AbortController signal
|
||||
*/
|
||||
async function awaitForGallery(expectedSize, signal) {
|
||||
const timeout = AbortSignal.timeout(180000); // Failsafe to ensure no memory leaks
|
||||
const combinedSignals = AbortSignal.any([timeout, signal]);
|
||||
while (galleryHashes.size < expectedSize && !combinedSignals.aborted) await new Promise((resolve) => { setTimeout(resolve, 500); }); // longer interval because it's a low priority check
|
||||
while (galleryHashes.size < expectedSize && !signal.aborted) await new Promise((resolve) => { setTimeout(resolve, 500); }); // longer interval because it's a low priority check
|
||||
signal.throwIfAborted();
|
||||
}
|
||||
|
||||
function updateGalleryStyles() {
|
||||
@@ -270,29 +273,32 @@ async function addSeparators() {
|
||||
}
|
||||
}
|
||||
|
||||
async function delayFetchThumb(fn) {
|
||||
await awaitForIDB(16);
|
||||
outstanding++;
|
||||
const ts = Date.now().toString();
|
||||
const res = await authFetch(`${window.api}/browser/thumb?file=${encodeURI(fn)}&ts=${ts}`, { priority: 'low' });
|
||||
if (!res.ok) {
|
||||
error(`fetchThumb: ${res.statusText}`);
|
||||
async function delayFetchThumb(fn, signal) {
|
||||
await awaitForOutstanding(16, signal);
|
||||
try {
|
||||
outstanding++;
|
||||
const ts = Date.now().toString();
|
||||
const res = await authFetch(`${window.api}/browser/thumb?file=${encodeURI(fn)}&ts=${ts}`, { priority: 'low' });
|
||||
if (!res.ok) {
|
||||
error(`fetchThumb: ${res.statusText}`);
|
||||
return undefined;
|
||||
}
|
||||
const json = await res.json();
|
||||
if (!res || !json || json.error || Object.keys(json).length === 0) {
|
||||
if (json.error) error(`fetchThumb: ${json.error}`);
|
||||
return undefined;
|
||||
}
|
||||
return json;
|
||||
} finally {
|
||||
outstanding--;
|
||||
return undefined;
|
||||
}
|
||||
const json = await res.json();
|
||||
outstanding--;
|
||||
if (!res || !json || json.error || Object.keys(json).length === 0) {
|
||||
if (json.error) error(`fetchThumb: ${json.error}`);
|
||||
return undefined;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
class GalleryFile extends HTMLElement {
|
||||
/** @type {AbortSignal} */
|
||||
#signal;
|
||||
|
||||
constructor(folder, file, signal = undefined) {
|
||||
constructor(folder, file, signal) {
|
||||
super();
|
||||
this.folder = folder;
|
||||
this.name = file;
|
||||
@@ -324,7 +330,7 @@ class GalleryFile extends HTMLElement {
|
||||
}
|
||||
|
||||
this.hash = await getHash(`${this.folder}/${this.name}/${this.size}/${this.mtime}`); // eslint-disable-line no-use-before-define
|
||||
const cache = (this.hash && opts.browser_cache) ? await idbGet(this.hash) : undefined;
|
||||
const cache = (this.hash && opts.browser_cache) ? await idbGet(this.hash).catch(() => undefined) : undefined;
|
||||
const img = document.createElement('img');
|
||||
img.className = 'gallery-file';
|
||||
img.loading = 'lazy';
|
||||
@@ -348,7 +354,7 @@ class GalleryFile extends HTMLElement {
|
||||
this.mtime = new Date(cache.mtime);
|
||||
} else {
|
||||
try {
|
||||
const json = await delayFetchThumb(this.src);
|
||||
const json = await delayFetchThumb(this.src, this.#signal);
|
||||
if (!json) {
|
||||
ok = false;
|
||||
} else {
|
||||
@@ -377,14 +383,13 @@ class GalleryFile extends HTMLElement {
|
||||
img.src = `file=${this.src}`;
|
||||
}
|
||||
}
|
||||
if (this.#signal && !this.#signal.aborted) {
|
||||
// Guard against accessing external context from a stale initialization
|
||||
galleryHashes.add(this.hash); // Add to hashes Set *after* any database operations
|
||||
this.#signal = null; // Clean up reference to AbortSignal
|
||||
}
|
||||
if (!ok) {
|
||||
if (this.#signal.aborted) { // Do not change the operations order from here...
|
||||
return;
|
||||
}
|
||||
galleryHashes.add(this.hash);
|
||||
if (!ok) {
|
||||
return;
|
||||
} // ... to here unless modifications are also being made to maintenance functionality and the usage of AbortController/AbortSignal
|
||||
img.onclick = () => {
|
||||
currentImage = this.src;
|
||||
el.btnSend.click();
|
||||
|
||||
+12
-11
@@ -90,6 +90,8 @@ async function idbGetAllKeys(index = null, query = null) {
|
||||
try {
|
||||
let request;
|
||||
const transaction = db.transaction('thumbs', 'readonly');
|
||||
transaction.onabort = (e) => reject(e);
|
||||
|
||||
const store = transaction.objectStore('thumbs');
|
||||
if (index) {
|
||||
request = store.index(index).getAllKeys(query);
|
||||
@@ -98,7 +100,6 @@ async function idbGetAllKeys(index = null, query = null) {
|
||||
}
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = (e) => reject(e);
|
||||
transaction.onabort = (e) => reject(e);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
@@ -117,6 +118,8 @@ async function idbCount(folder = null) {
|
||||
try {
|
||||
let request;
|
||||
const transaction = db.transaction('thumbs', 'readonly');
|
||||
transaction.onabort = (e) => reject(e);
|
||||
|
||||
const store = transaction.objectStore('thumbs');
|
||||
if (folder) {
|
||||
request = store.index('folder').count(folder);
|
||||
@@ -125,7 +128,6 @@ async function idbCount(folder = null) {
|
||||
}
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = (e) => reject(e);
|
||||
transaction.onabort = (e) => reject(e);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
@@ -161,15 +163,6 @@ async function idbFolderCleanup(keepSet, folder, signal) {
|
||||
transaction.abort();
|
||||
}
|
||||
signal.addEventListener('abort', abortTransaction);
|
||||
|
||||
try {
|
||||
const store = transaction.objectStore('thumbs');
|
||||
removals.forEach((entry) => { store.delete(entry); });
|
||||
} catch (err) {
|
||||
error(err);
|
||||
abortTransaction();
|
||||
}
|
||||
|
||||
transaction.onabort = () => {
|
||||
signal.removeEventListener('abort', abortTransaction);
|
||||
reject(`Aborting. ${signal.reason}`); // eslint-disable-line prefer-promise-reject-errors
|
||||
@@ -182,6 +175,14 @@ async function idbFolderCleanup(keepSet, folder, signal) {
|
||||
signal.removeEventListener('abort', abortTransaction);
|
||||
resolve(totalRemovals);
|
||||
};
|
||||
|
||||
try {
|
||||
const store = transaction.objectStore('thumbs');
|
||||
removals.forEach((entry) => { store.delete(entry); });
|
||||
} catch (err) {
|
||||
error(err);
|
||||
abortTransaction();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user