Files
suwayomi-material-you-webui/src/lib/Queue.ts
schroda 4d04b9cecd Only abort pending image requests
In case images are requested of a slow source, aborting image requests that are already in progress will cause the server to take forever for new source image requests because it's still processing the aborted image requests.
Thus, aborting ongoing image request might cause more harm than good, since the server will basically become "unresponsive" for new images requests of this source.

By only aborting pending requests, that have not been sent to the server yet, new image requests will only be handled once the active requests are done, preventing the server from getting spammed with image requests that potentially are just getting aborted immediately again (e.g. due to scrolling quickly in the source browse page), but still will be handled by the server, blocking new requests
2024-05-06 02:37:00 +02:00

94 lines
2.8 KiB
TypeScript

/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import pLimit, { LimitFunction } from 'p-limit';
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
export enum Priority {
LOW = 0,
NORMAL = 1,
HIGH = 2,
}
export type QueuePriority = Priority | number;
type Key = string;
type QueueItemFunction<T = any> = () => PromiseLike<T> | T;
export class Queue {
private readonly queue: LimitFunction;
private counter: number = 0;
private pendingKeyToPriorityMap = new Map<Key, QueuePriority>();
private pendingKeyToFnMap = new Map<Key, QueueItemFunction>();
private pendingKeyToPromiseMap = new Map<Key, ControlledPromise<any>>();
constructor(concurrency: number) {
this.queue = pLimit(concurrency);
}
clear(): void {
this.pendingKeyToPromiseMap.forEach((promise) => {
promise.reject(new Error('Queue::clear: called'));
});
this.pendingKeyToPriorityMap.clear();
this.pendingKeyToFnMap.clear();
this.pendingKeyToPromiseMap.clear();
}
enqueue<T>(
key: Key,
fn: () => PromiseLike<T> | T,
priority: QueuePriority = Priority.NORMAL,
): { key: string; promise: Promise<T> } {
this.counter = (this.counter + 1) % Infinity;
const actualKey = `${key}_${this.counter}`;
this.pendingKeyToPriorityMap.set(actualKey, priority);
this.pendingKeyToFnMap.set(actualKey, fn);
const processPromise = new ControlledPromise<T>();
this.pendingKeyToPromiseMap.set(actualKey, processPromise);
this.queue(() => this.process());
return { key: actualKey, promise: processPromise.promise };
}
isProcessing(key: string): boolean {
return !this.pendingKeyToFnMap.has(key);
}
private async process(): Promise<void> {
const { fn, promise } = this.getNextItemToProcess();
try {
const result = await fn();
promise.resolve(result);
} catch (e) {
promise.reject(e);
}
}
private getNextItemToProcess<T>(): { key: Key; fn: QueueItemFunction<T>; promise: ControlledPromise<T> } {
const [key] = [...this.pendingKeyToPriorityMap.entries()].toSorted(
([, priorityA], [, priorityB]) => priorityB - priorityA,
)[0];
const fn = this.pendingKeyToFnMap.get(key) as () => PromiseLike<T> | T;
const promise = this.pendingKeyToPromiseMap.get(key) as ControlledPromise<T>;
this.pendingKeyToPriorityMap.delete(key);
this.pendingKeyToFnMap.delete(key);
this.pendingKeyToPromiseMap.delete(key);
return { key, fn, promise };
}
}