Skip to content

Commit 03f8b20

Browse files
committed
refactor: Implement abortable source polling
Use abort-controller-x with signals to interrupt polling process #483
1 parent c49cdba commit 03f8b20

File tree

7 files changed

+208
-72
lines changed

7 files changed

+208
-72
lines changed

package-lock.json

Lines changed: 25 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"@tailwindcss/vite": "^4.1.16",
7979
"@types/express-serve-static-core": "^4.19.6",
8080
"@xhayper/discord-rpc": "^1.3.0",
81+
"abort-controller-x": "^0.5.0",
8182
"address": "^1.2.2",
8283
"ajv": "^8.18.0",
8384
"ajv-formats": "^3.0.1",
@@ -193,7 +194,7 @@
193194
"@types/xml2js": "^0.4.11",
194195
"@vitejs/plugin-react": "^4.2.1",
195196
"chai": "^4.3.6",
196-
"chai-as-promised": "^7.1.1",
197+
"chai-as-promised": "^8.0.2",
197198
"eslint": "^8.56.0",
198199
"eslint-plugin-prefer-arrow-functions": "^3.2.4",
199200
"eslint-plugin-storybook": "10.1.11",

src/backend/common/errors/MSErrors.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { parseRegexSingle } from "@foxxmd/regex-buddy-core";
22
import mergeErrorCause from 'merge-error-cause';
3-
import { findCauseByFunc, findCauseByReference } from "../../utils/ErrorUtils.js";
3+
import { findCauseByFunc, findCauseByReference, isAbortReasonErrorLike } from "../../utils/ErrorUtils.js";
44
import { UpstreamError, UpstreamErrorOptions } from "./UpstreamError.js";
5+
import { isAbortError } from "abort-controller-x";
56

67
export abstract class NamedError extends Error {
78
public abstract name: string;
@@ -39,21 +40,29 @@ export class SimpleError extends Error implements HasSimpleError {
3940
simple: boolean;
4041
name = 'Error';
4142

43+
stackShortened: boolean = false;
44+
45+
shortenStack() {
46+
const atIndex = parseRegexSingle(STACK_AT_REGEX,this.stack);
47+
if(atIndex !== undefined) {
48+
const firstn = this.stack.indexOf('\n', atIndex.index + atIndex.match.length);
49+
if(firstn !== -1) {
50+
this.stack = this.stack.slice(0, firstn);
51+
this.stackShortened = true;
52+
}
53+
}
54+
}
55+
4256
public constructor(msg: string, options?: ErrorOptions & { simple?: boolean, shortStack?: boolean }) {
4357
super(msg, options);
4458
const {
4559
simple = true,
4660
shortStack = false
4761
} = options || {};
4862
this.simple = simple;
63+
Error.captureStackTrace(this, this.constructor);
4964
if(shortStack) {
50-
const atIndex = parseRegexSingle(STACK_AT_REGEX,this.stack);
51-
if(atIndex !== undefined) {
52-
const firstn = this.stack.indexOf('\n', atIndex.index + atIndex.match.length);
53-
if(firstn !== -1) {
54-
this.stack = this.stack.slice(0, firstn);
55-
}
56-
}
65+
this.shortenStack();
5766
}
5867
}
5968
}
@@ -104,4 +113,19 @@ export class ScrobbleSubmitError<T extends (object | string) = object> extends U
104113
super(message, options);
105114
this.payload = options?.payload;
106115
}
116+
}
117+
118+
export class AbortedError extends SimpleError {
119+
name = 'Aborted Operation';
120+
}
121+
export const generateLoggableAbortReason = (msg: string, signal: AbortSignal): AbortedError => {
122+
const reason = signal.reason;
123+
let err: AbortedError;
124+
if(isAbortReasonErrorLike(signal)) {
125+
err = new AbortedError(msg, {cause: reason});
126+
} else {
127+
err = new AbortedError(`${msg} => ${reason ?? 'No Reason Given'}`, {simple: true, shortStack: true});
128+
}
129+
Error.captureStackTrace(err, generateLoggableAbortReason);
130+
return err;
107131
}

src/backend/server/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { setupWebscrobblerRoutes } from "./webscrobblerRoutes.js";
3333
import ScrobbleSources from "../sources/ScrobbleSources.js";
3434
import ScrobbleClients from "../scrobblers/ScrobbleClients.js";
3535
import prom from 'prom-client';
36+
import { SimpleError } from "../common/errors/MSErrors.js";
3637

3738
const maxBufferSize = 300;
3839
const output: Record<number, FixedSizeList<LogDataPretty>> = {};
@@ -435,7 +436,7 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream:
435436

436437
if(source.polling) {
437438
source.logger.info('Source is already polling! Restarting polling...');
438-
const stopRes = await source.tryStopPolling();
439+
const stopRes = await source.tryStopPolling(new SimpleError('user initiated', {simple: true, shortStack: true}));
439440
if(stopRes === true) {
440441
source.poll({force, notify: false}).catch(e => source.logger.error(e));
441442
}

0 commit comments

Comments
 (0)