diff --git a/packages/gridproxy_client/src/builders/abstract_builder.ts b/packages/gridproxy_client/src/builders/abstract_builder.ts index 1c32e312df..50fd55b404 100644 --- a/packages/gridproxy_client/src/builders/abstract_builder.ts +++ b/packages/gridproxy_client/src/builders/abstract_builder.ts @@ -40,13 +40,23 @@ export abstract class AbstractBuilder { } } - public async build(path: string, timeout = 10000): Promise { + public async build(path: string, timeout = 10000, signal?: AbortSignal): Promise { assertString(path); assertPattern(path, /^\//); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); + // Combine external signal with timeout signal + if (signal) { + // If signal is already aborted, abort immediately + if (signal.aborted) { + controller.abort(); + } else { + signal.addEventListener("abort", () => controller.abort()); + } + } + try { const out: string[] = []; diff --git a/packages/gridproxy_client/src/modules/farms.ts b/packages/gridproxy_client/src/modules/farms.ts index fe51c861b3..392aede40b 100644 --- a/packages/gridproxy_client/src/modules/farms.ts +++ b/packages/gridproxy_client/src/modules/farms.ts @@ -30,8 +30,8 @@ export class FarmsClient extends AbstractClient { }); } - public async list(queries: Partial = {}) { - const res = await this.builder(queries).build("/farms"); + public async list(queries: Partial = {}, signal?: AbortSignal) { + const res = await this.builder(queries).build("/farms", 10000, signal); return resolvePaginator(res); } diff --git a/packages/gridproxy_client/src/modules/nodes.ts b/packages/gridproxy_client/src/modules/nodes.ts index ef6bf4d69c..a3c4eb47b9 100644 --- a/packages/gridproxy_client/src/modules/nodes.ts +++ b/packages/gridproxy_client/src/modules/nodes.ts @@ -32,22 +32,28 @@ export class NodesClient extends AbstractClient { this.setTwin = this.setTwin.bind(this); } - public async list(queries: Partial = {}, extraOptions: NodesExtractOptions = {}) { - const res = await this.builder(queries).build("/nodes"); + public async list(queries: Partial = {}, extraOptions: NodesExtractOptions = {}, signal?: AbortSignal) { + const res = await this.builder(queries).build("/nodes", 10000, signal); const nodes = await resolvePaginator(res); if (extraOptions.loadFarm) { - await this.loadFarms(nodes.data.map(n => n.farmId)); + await this.loadFarms( + nodes.data.map(n => n.farmId), + signal, + ); nodes.data = nodes.data.map(this.setFarm); } if (extraOptions.loadTwin) { - await this.loadTwins(nodes.data.map(n => n.twinId)); + await this.loadTwins( + nodes.data.map(n => n.twinId), + signal, + ); nodes.data = nodes.data.map(this.setTwin); } if (extraOptions.loadStats) { - const nodesStats = await Promise.all(nodes.data.map(n => this.statsById(n.nodeId))); + const nodesStats = await Promise.all(nodes.data.map(n => this.statsById(n.nodeId, signal))); nodes.data = nodes.data.map((n, index) => { n.stats = nodesStats[index]; return n; @@ -57,8 +63,8 @@ export class NodesClient extends AbstractClient { return nodes; } - public async byId(nodeId: number, extraOptions: NodesExtractOptions = {}): Promise { - const res = await this.builder({}).build(`/nodes/${nodeId}`); + public async byId(nodeId: number, extraOptions: NodesExtractOptions = {}, signal?: AbortSignal): Promise { + const res = await this.builder({}).build(`/nodes/${nodeId}`, 10000, signal); let node: GridNode = await res.json(); const capacity = Reflect.get(node, "capacity"); @@ -68,37 +74,37 @@ export class NodesClient extends AbstractClient { } if (extraOptions.loadFarm && node) { - await this.loadFarms([node.farmId]); + await this.loadFarms([node.farmId], signal); node = this.setFarm(node); } if (extraOptions.loadTwin) { - await this.loadTwins([node.twinId]); + await this.loadTwins([node.twinId], signal); node = this.setTwin(node); } if (extraOptions.loadStats) { - node.stats = await this.statsById(node.nodeId); + node.stats = await this.statsById(node.nodeId, signal); } return node; } - public async statsById(nodeId: number): Promise { - const res = await this.builder({}).build(`/nodes/${nodeId}/statistics`); + public async statsById(nodeId: number, signal?: AbortSignal): Promise { + const res = await this.builder({}).build(`/nodes/${nodeId}/statistics`, 10000, signal); return res.json(); } - public async gpuById(nodeId: number): Promise { - const res = await this.builder({}).build(`/nodes/${nodeId}/gpu`); + public async gpuById(nodeId: number, signal?: AbortSignal): Promise { + const res = await this.builder({}).build(`/nodes/${nodeId}/gpu`, 10000, signal); return res.json(); } - private async loadFarms(farmIds: number[]): Promise { + private async loadFarms(farmIds: number[], signal?: AbortSignal): Promise { farmIds = farmIds.filter(id => !this.farms.has(id)); const ids = Array.from(new Set(farmIds)); if (!ids.length) return; - const farms = await Promise.all(ids.map(farmId => this.__farmsClient.list({ farmId }))); + const farms = await Promise.all(ids.map(farmId => this.__farmsClient.list({ farmId }, signal))); for (const { data } of farms) { const [farm] = data; this.farms = this.farms.set(farm.farmId, farm); @@ -124,11 +130,11 @@ export class NodesClient extends AbstractClient { }; } - private async loadTwins(twinIds: number[]): Promise { + private async loadTwins(twinIds: number[], signal?: AbortSignal): Promise { twinIds = twinIds.filter(id => !this.twins.has(id)); const ids = Array.from(new Set(twinIds)); if (!ids.length) return; - const twins = await Promise.all(ids.map(twinId => this.__twinsClient.list({ twinId }))); + const twins = await Promise.all(ids.map(twinId => this.__twinsClient.list({ twinId }, signal))); for (const { data } of twins) { const [twin] = data; this.twins = this.twins.set(twin.twinId, twin); diff --git a/packages/gridproxy_client/src/modules/stats.ts b/packages/gridproxy_client/src/modules/stats.ts index 569a49789c..dbb196cb50 100644 --- a/packages/gridproxy_client/src/modules/stats.ts +++ b/packages/gridproxy_client/src/modules/stats.ts @@ -28,8 +28,8 @@ export class StatsClient extends AbstractClient { }); } - public async get(queries: Partial = {}): Promise { - const res = await this.builder(queries).build("/stats"); + public async get(queries: Partial = {}, signal?: AbortSignal): Promise { + const res = await this.builder(queries).build("/stats", 10000, signal); return res.json(); } } diff --git a/packages/gridproxy_client/src/modules/twins.ts b/packages/gridproxy_client/src/modules/twins.ts index 67cc99fc92..0e788f5861 100644 --- a/packages/gridproxy_client/src/modules/twins.ts +++ b/packages/gridproxy_client/src/modules/twins.ts @@ -21,8 +21,8 @@ export class TwinsClient extends AbstractClient { }); } - public async list(queries: Partial = {}) { - const res = await this.builder(queries).build("/twins"); + public async list(queries: Partial = {}, signal?: AbortSignal) { + const res = await this.builder(queries).build("/twins", 10000, signal); return resolvePaginator(res); } diff --git a/packages/playground/package.json b/packages/playground/package.json index f771a8423b..252e2a6d26 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -6,7 +6,7 @@ "dev": "vite", "build": "run-p type-check build-only", "preview": "vite preview", - "test:unit": "vitest", + "test:unit": "vitest --run", "build-only": "vite build", "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false" }, diff --git a/packages/playground/src/components/app_info.vue b/packages/playground/src/components/app_info.vue index 5147ddae27..a083f2fa50 100644 --- a/packages/playground/src/components/app_info.vue +++ b/packages/playground/src/components/app_info.vue @@ -27,9 +27,7 @@ - - Close - + Close @@ -41,6 +39,7 @@ import { marked } from "marked"; import { computed, type ComputedRef, ref } from "vue"; import { useRoute } from "vue-router"; +import { isAbortError, useFetch } from "../hooks/useAbortController"; import type { InfoMeta } from "../router"; export interface InfoFileMeta { @@ -59,22 +58,29 @@ export default { const title = ref(""); const subtitle = ref(""); const html = ref(""); + const fetchWithAbort = useFetch(); async function setOpenInfo(value: boolean) { openInfo.value = value; if (value) { loading.value = true; - const res = await fetch(import.meta.env.BASE_URL + info.value.page); - const markdown = await res.text(); - - const { attributes, body } = fm(markdown); - - title.value = attributes.title || ""; - subtitle.value = attributes.subtitle || ""; - html.value = await marked.parse(body); - - loading.value = false; + try { + const res = await fetchWithAbort(import.meta.env.BASE_URL + info.value.page); + const markdown = await res.text(); + + const { attributes, body } = fm(markdown); + + title.value = attributes.title || ""; + subtitle.value = attributes.subtitle || ""; + html.value = await marked.parse(body); + + loading.value = false; + } catch (error) { + if (isAbortError(error)) return; + loading.value = false; + throw error; + } } } diff --git a/packages/playground/src/dashboard/components/user_nodes.vue b/packages/playground/src/dashboard/components/user_nodes.vue index a9a741ad61..baea4a720b 100644 --- a/packages/playground/src/dashboard/components/user_nodes.vue +++ b/packages/playground/src/dashboard/components/user_nodes.vue @@ -1,9 +1,7 @@